Share retained stroke texture binding helpers

This commit is contained in:
2026-06-13 10:30:13 +02:00
parent 3f071620dc
commit 323abdea57
5 changed files with 161 additions and 26 deletions

View File

@@ -18,6 +18,11 @@ agent or engineer to remove them without reconstructing context from chat.
## Recent Reductions
- 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` main, pad,
and dual live-pass texture-input binding/unbinding intent now routes through
shared retained stroke execution helpers; sampler binding, concrete GL object
mapping, framebuffer ownership, and final draw execution remain retained in
`Canvas`.
- 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview` live-pass
sampler binding, dual/main pass texture binding, checkerboard/background
capture wrapping, and final preview copy-back now route through shared local

View File

@@ -3111,6 +3111,10 @@ Results:
binding, checkerboard/background capture wrapping, and final preview
copy-back now share named local helpers, while mixer state execution and
per-sample GL ordering remain in the preview node.
- `Canvas::stroke_draw` main, pad, and dual live-pass texture-input
binding/unbinding intent now shares retained stroke execution helpers, while
sampler binding, concrete GL object mapping, framebuffer ownership, and final
draw execution remain in the legacy Canvas path.
- `Canvas::stroke_draw` pad-pass destination bind/copy/unbind ordering now
shares the retained stroke execution helper callback surface, while shader
setup, pad color selection, framebuffer ownership, and final OpenGL draw

View File

@@ -509,6 +509,13 @@ Done Checks:
Progress Notes:
- 2026-06-13: `Canvas::stroke_draw` main, pad, and dual live-pass
texture-input binding/unbinding intent now routes through retained stroke
execution helpers; sampler binding, concrete GL object mapping, framebuffer
ownership, and final draw execution remain local to `Canvas`. Next slice
should target the remaining sampler binding/teardown or the direct
semantic-input-to-GL mapping callbacks without reopening the landed dirty,
face-framebuffer, or pad helpers.
- 2026-06-13: `NodeStrokePreview::draw_stroke_immediate()` live-pass sampler
binding, dual/main pass texture binding, checkerboard/background capture
wrapping, and final preview copy-back now route through named local helpers;

View File

@@ -673,15 +673,50 @@ void Canvas::stroke_draw()
});
// DRAW MAIN BRUSH
set_active_texture_unit(0);
brush->m_tip_texture->bind();
set_active_texture_unit(2);
brush->m_pattern_texture ?
brush->m_pattern_texture->bind() :
unbind_texture_2d();
set_active_texture_unit(3);
m_mixer.bindTexture();
constexpr std::array main_pass_texture_bindings {
pp::panopainter::LegacyCanvasStrokeTextureBinding {
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip,
.slot = 0,
},
pp::panopainter::LegacyCanvasStrokeTextureBinding {
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::pattern,
.slot = 2,
},
pp::panopainter::LegacyCanvasStrokeTextureBinding {
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::mixer,
.slot = 3,
},
};
constexpr std::array main_pass_texture_unbindings {
pp::panopainter::LegacyCanvasStrokeTextureBinding {
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::mixer,
.slot = 3,
},
pp::panopainter::LegacyCanvasStrokeTextureBinding {
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip,
.slot = 0,
},
};
pp::panopainter::bind_legacy_canvas_stroke_texture_inputs(
main_pass_texture_bindings,
[&](auto input, int texture_slot) {
set_active_texture_unit(texture_slot);
switch (input) {
case pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip:
brush->m_tip_texture->bind();
break;
case pp::panopainter::LegacyCanvasStrokeTextureInput::pattern:
brush->m_pattern_texture ?
brush->m_pattern_texture->bind() :
unbind_texture_2d();
break;
case pp::panopainter::LegacyCanvasStrokeTextureInput::mixer:
m_mixer.bindTexture();
break;
case pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination:
break;
}
});
auto frames = stroke_draw_compute(*m_current_stroke);
@@ -718,10 +753,22 @@ void Canvas::stroke_draw()
},
m_tmp);
set_active_texture_unit(3);
m_mixer.unbindTexture();
set_active_texture_unit(0);
brush->m_tip_texture->unbind();
pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs(
main_pass_texture_unbindings,
[&](auto input, int texture_slot) {
set_active_texture_unit(texture_slot);
switch (input) {
case pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip:
brush->m_tip_texture->unbind();
break;
case pp::panopainter::LegacyCanvasStrokeTextureInput::mixer:
m_mixer.unbindTexture();
break;
case pp::panopainter::LegacyCanvasStrokeTextureInput::pattern:
case pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination:
break;
}
});
// pad stroke
// In order to mitigate color bleeding at the edge of shapes in transparent layers
@@ -738,6 +785,12 @@ void Canvas::stroke_draw()
.uses_destination_feedback = copy_stroke_destination,
});
const auto pad_faces = pp::panopainter::make_legacy_canvas_stroke_pad_faces(box_dirty, box_face);
constexpr std::array pad_destination_texture_binding {
pp::panopainter::LegacyCanvasStrokeTextureBinding {
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination,
.slot = 1,
},
};
[[maybe_unused]] const auto pad_result = pp::panopainter::execute_legacy_canvas_stroke_pad_faces(
pp::panopainter::LegacyCanvasStrokePadExecutionRequest {
.context = "Canvas::stroke_draw",
@@ -753,8 +806,15 @@ void Canvas::stroke_draw()
m_tmp[face_index].bindFramebuffer();
},
.bind_destination_texture = [&](int face_index) {
set_active_texture_unit(1);
m_tex[face_index].bind();
pp::panopainter::bind_legacy_canvas_stroke_texture_inputs(
pad_destination_texture_binding,
[&](auto input, int texture_slot) {
if (input != pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination) {
return;
}
set_active_texture_unit(texture_slot);
m_tex[face_index].bind();
});
},
.copy_framebuffer_to_destination_texture =
[&](const pp::paint_renderer::CanvasStrokeCopyRegion& copy_region) {
@@ -767,8 +827,15 @@ void Canvas::stroke_draw()
copy_region.height);
},
.unbind_destination_texture = [&](int face_index) {
set_active_texture_unit(1);
m_tex[face_index].unbind();
pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs(
pad_destination_texture_binding,
[&](auto input, int texture_slot) {
if (input != pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination) {
return;
}
set_active_texture_unit(texture_slot);
m_tex[face_index].unbind();
});
},
.draw_pad = [&] {
m_brush_shape.draw_fill();
@@ -784,11 +851,24 @@ void Canvas::stroke_draw()
{
const auto& dual_brush = m_dual_stroke->m_brush;
pp::panopainter::setup_legacy_stroke_dual_shader(stroke_material.dual_pass.uses_pattern);
set_active_texture_unit(0);
dual_brush->m_tip_texture ?
dual_brush->m_tip_texture->bind() :
unbind_texture_2d();
constexpr std::array dual_pass_texture_bindings {
pp::panopainter::LegacyCanvasStrokeTextureBinding {
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip,
.slot = 0,
},
};
pp::panopainter::bind_legacy_canvas_stroke_texture_inputs(
dual_pass_texture_bindings,
[&](auto input, int texture_slot) {
if (input != pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip) {
return;
}
set_active_texture_unit(texture_slot);
dual_brush->m_tip_texture ?
dual_brush->m_tip_texture->bind() :
unbind_texture_2d();
});
auto frames_dual = stroke_draw_compute(*m_dual_stroke);
const std::array<bool, 6> include_dual_dirty =
SIXPLETTE(stroke_material.composite_pass.dual_blend_mode == 0);
@@ -813,10 +893,17 @@ void Canvas::stroke_draw()
m_tmp_dual,
true);
set_active_texture_unit(0);
dual_brush->m_tip_texture ?
dual_brush->m_tip_texture->unbind() :
unbind_texture_2d();
pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs(
dual_pass_texture_bindings,
[&](auto input, int texture_slot) {
if (input != pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip) {
return;
}
set_active_texture_unit(texture_slot);
dual_brush->m_tip_texture ?
dual_brush->m_tip_texture->unbind() :
unbind_texture_2d();
});
}
m_sampler_brush.unbind();

View File

@@ -37,6 +37,18 @@ struct LegacyStrokeSampleExecutionResult {
glm::vec4 dirty_bounds {};
};
enum class LegacyCanvasStrokeTextureInput {
brush_tip,
stroke_destination,
pattern,
mixer,
};
struct LegacyCanvasStrokeTextureBinding {
LegacyCanvasStrokeTextureInput input = LegacyCanvasStrokeTextureInput::brush_tip;
int slot = 0;
};
struct LegacyCanvasStrokeFaceDirtyRequest {
pp::renderer::Extent2D extent {};
glm::vec4 previous_accumulated_dirty_box {};
@@ -200,6 +212,26 @@ std::size_t execute_legacy_canvas_stroke_frame_faces(
return executed_faces;
}
template <typename BindTextureInput, std::size_t BindingCount>
inline void bind_legacy_canvas_stroke_texture_inputs(
const std::array<LegacyCanvasStrokeTextureBinding, BindingCount>& bindings,
BindTextureInput&& bind_texture_input)
{
for (const auto& binding : bindings) {
bind_texture_input(binding.input, binding.slot);
}
}
template <typename UnbindTextureInput, std::size_t BindingCount>
inline void unbind_legacy_canvas_stroke_texture_inputs(
const std::array<LegacyCanvasStrokeTextureBinding, BindingCount>& bindings,
UnbindTextureInput&& unbind_texture_input)
{
for (const auto& binding : bindings) {
unbind_texture_input(binding.input, binding.slot);
}
}
template <typename Frames, typename BeginFrame, typename BeginFace, typename ExecuteSample, typename FinishFace>
std::size_t execute_legacy_canvas_stroke_frame_samples(
Frames&& frames,