Share retained stroke sampler dispatch helpers

This commit is contained in:
2026-06-13 10:36:52 +02:00
parent 24c0452229
commit 0a5e7302bc
5 changed files with 309 additions and 69 deletions

View File

@@ -18,6 +18,11 @@ agent or engineer to remove them without reconstructing context from chat.
## Recent Reductions ## Recent Reductions
- 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` live-pass
sampler bind/unbind plus semantic texture-input dispatch now route through
retained stroke execution helpers; concrete GL object mapping, framebuffer
ownership, shader timing, and final draw execution remain retained in
`Canvas`.
- 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::stroke_draw_samples` - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::stroke_draw_samples`
now routes destination bind/unbind, framebuffer copy callback wrapping, now routes destination bind/unbind, framebuffer copy callback wrapping,
sample-point assembly, and brush-vertex upload/draw through one local helper; sample-point assembly, and brush-vertex upload/draw through one local helper;

View File

@@ -3119,6 +3119,10 @@ Results:
destination bind/unbind, framebuffer copy callback wrapping, sample-point destination bind/unbind, framebuffer copy callback wrapping, sample-point
assembly, and brush-vertex upload/draw, while mixer-pass state execution and assembly, and brush-vertex upload/draw, while mixer-pass state execution and
higher-level pass orchestration remain in the preview node. higher-level pass orchestration remain in the preview node.
- `Canvas::stroke_draw` live-pass sampler bind/unbind plus semantic
texture-input dispatch now shares retained stroke execution helpers, while
concrete GL object mapping, framebuffer ownership, shader timing, and final
draw execution remain in the legacy Canvas path.
- `Canvas::stroke_draw` pad-pass destination bind/copy/unbind ordering now - `Canvas::stroke_draw` pad-pass destination bind/copy/unbind ordering now
shares the retained stroke execution helper callback surface, while shader shares the retained stroke execution helper callback surface, while shader
setup, pad color selection, framebuffer ownership, and final OpenGL draw setup, pad color selection, framebuffer ownership, and final OpenGL draw

View File

@@ -509,6 +509,13 @@ Done Checks:
Progress Notes: Progress Notes:
- 2026-06-13: `Canvas::stroke_draw` live-pass sampler bind/unbind plus
semantic texture-input dispatch now routes through retained stroke execution
helpers; concrete GL object mapping, framebuffer ownership, shader timing,
and final draw execution remain local to `Canvas`. Next slice should target
the remaining live draw execution boundary inside `stroke_draw_samples()` or
the final temporary-texture composite setup without reopening the landed
dirty, face-framebuffer, pad, or texture-intent helpers.
- 2026-06-13: `NodeStrokePreview::stroke_draw_samples()` now routes - 2026-06-13: `NodeStrokePreview::stroke_draw_samples()` now routes
destination bind/unbind, framebuffer copy callback wrapping, sample-point destination bind/unbind, framebuffer copy callback wrapping, sample-point
assembly, and brush-vertex upload/draw through one local helper; mixer-pass assembly, and brush-vertex upload/draw through one local helper; mixer-pass

View File

@@ -634,12 +634,6 @@ void Canvas::stroke_draw()
apply_canvas_viewport(0, 0, m_width, m_height); apply_canvas_viewport(0, 0, m_width, m_height);
m_sampler_brush.bind(0);
m_sampler_nearest.bind(1);
m_sampler_stencil.bind(2);
m_sampler.bind(3);
//m_sampler_linear.bind(5);
glm::vec2 patt_scale = glm::vec2(brush->m_pattern_scale); glm::vec2 patt_scale = glm::vec2(brush->m_pattern_scale);
if (brush->m_pattern_flipx) patt_scale.x *= -1.f; if (brush->m_pattern_flipx) patt_scale.x *= -1.f;
if (brush->m_pattern_flipy) patt_scale.y *= -1.f; if (brush->m_pattern_flipy) patt_scale.y *= -1.f;
@@ -687,6 +681,24 @@ void Canvas::stroke_draw()
.slot = 3, .slot = 3,
}, },
}; };
constexpr std::array live_pass_sampler_bindings {
pp::panopainter::LegacyCanvasStrokeTextureBinding {
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip,
.slot = 0,
},
pp::panopainter::LegacyCanvasStrokeTextureBinding {
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination,
.slot = 1,
},
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 { constexpr std::array main_pass_texture_unbindings {
pp::panopainter::LegacyCanvasStrokeTextureBinding { pp::panopainter::LegacyCanvasStrokeTextureBinding {
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::mixer, .input = pp::panopainter::LegacyCanvasStrokeTextureInput::mixer,
@@ -697,26 +709,60 @@ void Canvas::stroke_draw()
.slot = 0, .slot = 0,
}, },
}; };
const pp::panopainter::LegacyCanvasStrokeSamplerDispatch live_pass_sampler_dispatch {
.bind_brush_tip_sampler = [&](int slot) {
m_sampler_brush.bind(slot);
},
.unbind_brush_tip_sampler = [&] {
m_sampler_brush.unbind();
},
.bind_stroke_destination_sampler = [&](int slot) {
m_sampler_nearest.bind(slot);
},
.unbind_stroke_destination_sampler = [&] {
m_sampler_nearest.unbind();
},
.bind_pattern_sampler = [&](int slot) {
m_sampler_stencil.bind(slot);
},
.unbind_pattern_sampler = [&] {
m_sampler_stencil.unbind();
},
.bind_mixer_sampler = [&](int slot) {
m_sampler.bind(slot);
},
.unbind_mixer_sampler = [&] {
m_sampler.unbind();
},
};
const pp::panopainter::LegacyCanvasStrokeTextureInputDispatch main_pass_texture_dispatch {
.activate_texture_unit = [&](int texture_slot) {
set_active_texture_unit(texture_slot);
},
.bind_brush_tip = [&] {
brush->m_tip_texture->bind();
},
.unbind_brush_tip = [&] {
brush->m_tip_texture->unbind();
},
.bind_pattern = [&] {
brush->m_pattern_texture ?
brush->m_pattern_texture->bind() :
unbind_texture_2d();
},
.bind_mixer = [&] {
m_mixer.bindTexture();
},
.unbind_mixer = [&] {
m_mixer.unbindTexture();
},
};
pp::panopainter::bind_legacy_canvas_stroke_sampler_inputs(
live_pass_sampler_bindings,
live_pass_sampler_dispatch);
pp::panopainter::bind_legacy_canvas_stroke_texture_inputs( pp::panopainter::bind_legacy_canvas_stroke_texture_inputs(
main_pass_texture_bindings, main_pass_texture_bindings,
[&](auto input, int texture_slot) { main_pass_texture_dispatch);
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); auto frames = stroke_draw_compute(*m_current_stroke);
@@ -755,20 +801,7 @@ void Canvas::stroke_draw()
pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs( pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs(
main_pass_texture_unbindings, main_pass_texture_unbindings,
[&](auto input, int texture_slot) { main_pass_texture_dispatch);
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 // pad stroke
// In order to mitigate color bleeding at the edge of shapes in transparent layers // In order to mitigate color bleeding at the edge of shapes in transparent layers
@@ -808,12 +841,13 @@ void Canvas::stroke_draw()
.bind_destination_texture = [&](int face_index) { .bind_destination_texture = [&](int face_index) {
pp::panopainter::bind_legacy_canvas_stroke_texture_inputs( pp::panopainter::bind_legacy_canvas_stroke_texture_inputs(
pad_destination_texture_binding, pad_destination_texture_binding,
[&](auto input, int texture_slot) { pp::panopainter::LegacyCanvasStrokeTextureInputDispatch {
if (input != pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination) { .activate_texture_unit = [&](int texture_slot) {
return; set_active_texture_unit(texture_slot);
} },
set_active_texture_unit(texture_slot); .bind_stroke_destination = [&] {
m_tex[face_index].bind(); m_tex[face_index].bind();
},
}); });
}, },
.copy_framebuffer_to_destination_texture = .copy_framebuffer_to_destination_texture =
@@ -829,12 +863,13 @@ void Canvas::stroke_draw()
.unbind_destination_texture = [&](int face_index) { .unbind_destination_texture = [&](int face_index) {
pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs( pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs(
pad_destination_texture_binding, pad_destination_texture_binding,
[&](auto input, int texture_slot) { pp::panopainter::LegacyCanvasStrokeTextureInputDispatch {
if (input != pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination) { .activate_texture_unit = [&](int texture_slot) {
return; set_active_texture_unit(texture_slot);
} },
set_active_texture_unit(texture_slot); .unbind_stroke_destination = [&] {
m_tex[face_index].unbind(); m_tex[face_index].unbind();
},
}); });
}, },
.draw_pad = [&] { .draw_pad = [&] {
@@ -860,14 +895,20 @@ void Canvas::stroke_draw()
}; };
pp::panopainter::bind_legacy_canvas_stroke_texture_inputs( pp::panopainter::bind_legacy_canvas_stroke_texture_inputs(
dual_pass_texture_bindings, dual_pass_texture_bindings,
[&](auto input, int texture_slot) { pp::panopainter::LegacyCanvasStrokeTextureInputDispatch {
if (input != pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip) { .activate_texture_unit = [&](int texture_slot) {
return; set_active_texture_unit(texture_slot);
} },
set_active_texture_unit(texture_slot); .bind_brush_tip = [&] {
dual_brush->m_tip_texture ? dual_brush->m_tip_texture ?
dual_brush->m_tip_texture->bind() : dual_brush->m_tip_texture->bind() :
unbind_texture_2d(); unbind_texture_2d();
},
.unbind_brush_tip = [&] {
dual_brush->m_tip_texture ?
dual_brush->m_tip_texture->unbind() :
unbind_texture_2d();
},
}); });
auto frames_dual = stroke_draw_compute(*m_dual_stroke); auto frames_dual = stroke_draw_compute(*m_dual_stroke);
const std::array<bool, 6> include_dual_dirty = const std::array<bool, 6> include_dual_dirty =
@@ -895,20 +936,21 @@ void Canvas::stroke_draw()
pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs( pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs(
dual_pass_texture_bindings, dual_pass_texture_bindings,
[&](auto input, int texture_slot) { pp::panopainter::LegacyCanvasStrokeTextureInputDispatch {
if (input != pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip) { .activate_texture_unit = [&](int texture_slot) {
return; set_active_texture_unit(texture_slot);
} },
set_active_texture_unit(texture_slot); .unbind_brush_tip = [&] {
dual_brush->m_tip_texture ? dual_brush->m_tip_texture ?
dual_brush->m_tip_texture->unbind() : dual_brush->m_tip_texture->unbind() :
unbind_texture_2d(); unbind_texture_2d();
},
}); });
} }
m_sampler_brush.unbind(); pp::panopainter::unbind_legacy_canvas_stroke_sampler_inputs(
m_sampler_nearest.unbind(); live_pass_sampler_bindings,
m_sampler_stencil.unbind(); live_pass_sampler_dispatch);
apply_canvas_viewport(vp.x, vp.y, vp.width, vp.height); apply_canvas_viewport(vp.x, vp.y, vp.width, vp.height);
apply_canvas_clear_color(cc); apply_canvas_clear_color(cc);

View File

@@ -49,6 +49,29 @@ struct LegacyCanvasStrokeTextureBinding {
int slot = 0; int slot = 0;
}; };
struct LegacyCanvasStrokeTextureInputDispatch {
std::function<void(int)> activate_texture_unit;
std::function<void()> bind_brush_tip;
std::function<void()> unbind_brush_tip;
std::function<void()> bind_stroke_destination;
std::function<void()> unbind_stroke_destination;
std::function<void()> bind_pattern;
std::function<void()> unbind_pattern;
std::function<void()> bind_mixer;
std::function<void()> unbind_mixer;
};
struct LegacyCanvasStrokeSamplerDispatch {
std::function<void(int)> bind_brush_tip_sampler;
std::function<void()> unbind_brush_tip_sampler;
std::function<void(int)> bind_stroke_destination_sampler;
std::function<void()> unbind_stroke_destination_sampler;
std::function<void(int)> bind_pattern_sampler;
std::function<void()> unbind_pattern_sampler;
std::function<void(int)> bind_mixer_sampler;
std::function<void()> unbind_mixer_sampler;
};
struct LegacyCanvasStrokeFaceDirtyRequest { struct LegacyCanvasStrokeFaceDirtyRequest {
pp::renderer::Extent2D extent {}; pp::renderer::Extent2D extent {};
glm::vec4 previous_accumulated_dirty_box {}; glm::vec4 previous_accumulated_dirty_box {};
@@ -232,6 +255,165 @@ inline void unbind_legacy_canvas_stroke_texture_inputs(
} }
} }
inline void bind_legacy_canvas_stroke_texture_input(
LegacyCanvasStrokeTextureInput input,
const LegacyCanvasStrokeTextureInputDispatch& dispatch)
{
switch (input) {
case LegacyCanvasStrokeTextureInput::brush_tip:
if (dispatch.bind_brush_tip) {
dispatch.bind_brush_tip();
}
break;
case LegacyCanvasStrokeTextureInput::stroke_destination:
if (dispatch.bind_stroke_destination) {
dispatch.bind_stroke_destination();
}
break;
case LegacyCanvasStrokeTextureInput::pattern:
if (dispatch.bind_pattern) {
dispatch.bind_pattern();
}
break;
case LegacyCanvasStrokeTextureInput::mixer:
if (dispatch.bind_mixer) {
dispatch.bind_mixer();
}
break;
}
}
inline void unbind_legacy_canvas_stroke_texture_input(
LegacyCanvasStrokeTextureInput input,
const LegacyCanvasStrokeTextureInputDispatch& dispatch)
{
switch (input) {
case LegacyCanvasStrokeTextureInput::brush_tip:
if (dispatch.unbind_brush_tip) {
dispatch.unbind_brush_tip();
}
break;
case LegacyCanvasStrokeTextureInput::stroke_destination:
if (dispatch.unbind_stroke_destination) {
dispatch.unbind_stroke_destination();
}
break;
case LegacyCanvasStrokeTextureInput::pattern:
if (dispatch.unbind_pattern) {
dispatch.unbind_pattern();
}
break;
case LegacyCanvasStrokeTextureInput::mixer:
if (dispatch.unbind_mixer) {
dispatch.unbind_mixer();
}
break;
}
}
inline void bind_legacy_canvas_stroke_sampler_input(
LegacyCanvasStrokeTextureInput input,
int slot,
const LegacyCanvasStrokeSamplerDispatch& dispatch)
{
switch (input) {
case LegacyCanvasStrokeTextureInput::brush_tip:
if (dispatch.bind_brush_tip_sampler) {
dispatch.bind_brush_tip_sampler(slot);
}
break;
case LegacyCanvasStrokeTextureInput::stroke_destination:
if (dispatch.bind_stroke_destination_sampler) {
dispatch.bind_stroke_destination_sampler(slot);
}
break;
case LegacyCanvasStrokeTextureInput::pattern:
if (dispatch.bind_pattern_sampler) {
dispatch.bind_pattern_sampler(slot);
}
break;
case LegacyCanvasStrokeTextureInput::mixer:
if (dispatch.bind_mixer_sampler) {
dispatch.bind_mixer_sampler(slot);
}
break;
}
}
inline void unbind_legacy_canvas_stroke_sampler_input(
LegacyCanvasStrokeTextureInput input,
const LegacyCanvasStrokeSamplerDispatch& dispatch)
{
switch (input) {
case LegacyCanvasStrokeTextureInput::brush_tip:
if (dispatch.unbind_brush_tip_sampler) {
dispatch.unbind_brush_tip_sampler();
}
break;
case LegacyCanvasStrokeTextureInput::stroke_destination:
if (dispatch.unbind_stroke_destination_sampler) {
dispatch.unbind_stroke_destination_sampler();
}
break;
case LegacyCanvasStrokeTextureInput::pattern:
if (dispatch.unbind_pattern_sampler) {
dispatch.unbind_pattern_sampler();
}
break;
case LegacyCanvasStrokeTextureInput::mixer:
if (dispatch.unbind_mixer_sampler) {
dispatch.unbind_mixer_sampler();
}
break;
}
}
template <std::size_t BindingCount>
inline void bind_legacy_canvas_stroke_texture_inputs(
const std::array<LegacyCanvasStrokeTextureBinding, BindingCount>& bindings,
const LegacyCanvasStrokeTextureInputDispatch& dispatch)
{
for (const auto& binding : bindings) {
if (dispatch.activate_texture_unit) {
dispatch.activate_texture_unit(binding.slot);
}
bind_legacy_canvas_stroke_texture_input(binding.input, dispatch);
}
}
template <std::size_t BindingCount>
inline void unbind_legacy_canvas_stroke_texture_inputs(
const std::array<LegacyCanvasStrokeTextureBinding, BindingCount>& bindings,
const LegacyCanvasStrokeTextureInputDispatch& dispatch)
{
for (const auto& binding : bindings) {
if (dispatch.activate_texture_unit) {
dispatch.activate_texture_unit(binding.slot);
}
unbind_legacy_canvas_stroke_texture_input(binding.input, dispatch);
}
}
template <std::size_t BindingCount>
inline void bind_legacy_canvas_stroke_sampler_inputs(
const std::array<LegacyCanvasStrokeTextureBinding, BindingCount>& bindings,
const LegacyCanvasStrokeSamplerDispatch& dispatch)
{
for (const auto& binding : bindings) {
bind_legacy_canvas_stroke_sampler_input(binding.input, binding.slot, dispatch);
}
}
template <std::size_t BindingCount>
inline void unbind_legacy_canvas_stroke_sampler_inputs(
const std::array<LegacyCanvasStrokeTextureBinding, BindingCount>& bindings,
const LegacyCanvasStrokeSamplerDispatch& dispatch)
{
for (const auto& binding : bindings) {
unbind_legacy_canvas_stroke_sampler_input(binding.input, dispatch);
}
}
template <typename Frames, typename BeginFrame, typename BeginFace, typename ExecuteSample, typename FinishFace> template <typename Frames, typename BeginFrame, typename BeginFace, typename ExecuteSample, typename FinishFace>
std::size_t execute_legacy_canvas_stroke_frame_samples( std::size_t execute_legacy_canvas_stroke_frame_samples(
Frames&& frames, Frames&& frames,