From 0a5e7302bc237c0cd23497eb92408dfbe3d1e810 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Sat, 13 Jun 2026 10:36:52 +0200 Subject: [PATCH] Share retained stroke sampler dispatch helpers --- docs/modernization/debt.md | 5 + docs/modernization/roadmap.md | 4 + docs/modernization/tasks.md | 7 + src/canvas.cpp | 180 ++++++++++------- src/legacy_canvas_stroke_execution_services.h | 182 ++++++++++++++++++ 5 files changed, 309 insertions(+), 69 deletions(-) diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 8e2f348..62fee94 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -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` 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` now routes destination bind/unbind, framebuffer copy callback wrapping, sample-point assembly, and brush-vertex upload/draw through one local helper; diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 7e535cc..5953f79 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -3119,6 +3119,10 @@ Results: destination bind/unbind, framebuffer copy callback wrapping, sample-point assembly, and brush-vertex upload/draw, while mixer-pass state execution and 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 shares the retained stroke execution helper callback surface, while shader setup, pad color selection, framebuffer ownership, and final OpenGL draw diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index c18d7f9..985507d 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -509,6 +509,13 @@ Done Checks: 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 destination bind/unbind, framebuffer copy callback wrapping, sample-point assembly, and brush-vertex upload/draw through one local helper; mixer-pass diff --git a/src/canvas.cpp b/src/canvas.cpp index 1accbd1..4dec328 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -634,12 +634,6 @@ void Canvas::stroke_draw() 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); if (brush->m_pattern_flipx) patt_scale.x *= -1.f; if (brush->m_pattern_flipy) patt_scale.y *= -1.f; @@ -687,6 +681,24 @@ void Canvas::stroke_draw() .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 { pp::panopainter::LegacyCanvasStrokeTextureBinding { .input = pp::panopainter::LegacyCanvasStrokeTextureInput::mixer, @@ -697,26 +709,60 @@ void Canvas::stroke_draw() .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( 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; - } - }); + main_pass_texture_dispatch); auto frames = stroke_draw_compute(*m_current_stroke); @@ -755,20 +801,7 @@ void Canvas::stroke_draw() 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; - } - }); + main_pass_texture_dispatch); // pad stroke // 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) { 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(); + pp::panopainter::LegacyCanvasStrokeTextureInputDispatch { + .activate_texture_unit = [&](int texture_slot) { + set_active_texture_unit(texture_slot); + }, + .bind_stroke_destination = [&] { + m_tex[face_index].bind(); + }, }); }, .copy_framebuffer_to_destination_texture = @@ -829,12 +863,13 @@ void Canvas::stroke_draw() .unbind_destination_texture = [&](int face_index) { 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(); + pp::panopainter::LegacyCanvasStrokeTextureInputDispatch { + .activate_texture_unit = [&](int texture_slot) { + set_active_texture_unit(texture_slot); + }, + .unbind_stroke_destination = [&] { + m_tex[face_index].unbind(); + }, }); }, .draw_pad = [&] { @@ -860,14 +895,20 @@ void Canvas::stroke_draw() }; 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(); + pp::panopainter::LegacyCanvasStrokeTextureInputDispatch { + .activate_texture_unit = [&](int texture_slot) { + set_active_texture_unit(texture_slot); + }, + .bind_brush_tip = [&] { + dual_brush->m_tip_texture ? + dual_brush->m_tip_texture->bind() : + 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); const std::array include_dual_dirty = @@ -895,20 +936,21 @@ void Canvas::stroke_draw() 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(); + pp::panopainter::LegacyCanvasStrokeTextureInputDispatch { + .activate_texture_unit = [&](int texture_slot) { + set_active_texture_unit(texture_slot); + }, + .unbind_brush_tip = [&] { + dual_brush->m_tip_texture ? + dual_brush->m_tip_texture->unbind() : + unbind_texture_2d(); + }, }); } - m_sampler_brush.unbind(); - m_sampler_nearest.unbind(); - m_sampler_stencil.unbind(); + pp::panopainter::unbind_legacy_canvas_stroke_sampler_inputs( + live_pass_sampler_bindings, + live_pass_sampler_dispatch); apply_canvas_viewport(vp.x, vp.y, vp.width, vp.height); apply_canvas_clear_color(cc); diff --git a/src/legacy_canvas_stroke_execution_services.h b/src/legacy_canvas_stroke_execution_services.h index b34c2c8..ec48957 100644 --- a/src/legacy_canvas_stroke_execution_services.h +++ b/src/legacy_canvas_stroke_execution_services.h @@ -49,6 +49,29 @@ struct LegacyCanvasStrokeTextureBinding { int slot = 0; }; +struct LegacyCanvasStrokeTextureInputDispatch { + std::function activate_texture_unit; + std::function bind_brush_tip; + std::function unbind_brush_tip; + std::function bind_stroke_destination; + std::function unbind_stroke_destination; + std::function bind_pattern; + std::function unbind_pattern; + std::function bind_mixer; + std::function unbind_mixer; +}; + +struct LegacyCanvasStrokeSamplerDispatch { + std::function bind_brush_tip_sampler; + std::function unbind_brush_tip_sampler; + std::function bind_stroke_destination_sampler; + std::function unbind_stroke_destination_sampler; + std::function bind_pattern_sampler; + std::function unbind_pattern_sampler; + std::function bind_mixer_sampler; + std::function unbind_mixer_sampler; +}; + struct LegacyCanvasStrokeFaceDirtyRequest { pp::renderer::Extent2D extent {}; 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 +inline void bind_legacy_canvas_stroke_texture_inputs( + const std::array& 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 +inline void unbind_legacy_canvas_stroke_texture_inputs( + const std::array& 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 +inline void bind_legacy_canvas_stroke_sampler_inputs( + const std::array& bindings, + const LegacyCanvasStrokeSamplerDispatch& dispatch) +{ + for (const auto& binding : bindings) { + bind_legacy_canvas_stroke_sampler_input(binding.input, binding.slot, dispatch); + } +} + +template +inline void unbind_legacy_canvas_stroke_sampler_inputs( + const std::array& bindings, + const LegacyCanvasStrokeSamplerDispatch& dispatch) +{ + for (const auto& binding : bindings) { + unbind_legacy_canvas_stroke_sampler_input(binding.input, dispatch); + } +} + template std::size_t execute_legacy_canvas_stroke_frame_samples( Frames&& frames,