From 323abdea5731bb9fbb02876616c63535755cbdfd Mon Sep 17 00:00:00 2001 From: omigamedev Date: Sat, 13 Jun 2026 10:30:13 +0200 Subject: [PATCH] Share retained stroke texture binding helpers --- docs/modernization/debt.md | 5 + docs/modernization/roadmap.md | 4 + docs/modernization/tasks.md | 7 + src/canvas.cpp | 139 ++++++++++++++---- src/legacy_canvas_stroke_execution_services.h | 32 ++++ 5 files changed, 161 insertions(+), 26 deletions(-) diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 3f6e854..3d15bc8 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` 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 diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 4d0fcc0..6f8772b 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -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 diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index fb6e758..1736211 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` 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; diff --git a/src/canvas.cpp b/src/canvas.cpp index 5a03e6b..1accbd1 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -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 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(); diff --git a/src/legacy_canvas_stroke_execution_services.h b/src/legacy_canvas_stroke_execution_services.h index d011892..b34c2c8 100644 --- a/src/legacy_canvas_stroke_execution_services.h +++ b/src/legacy_canvas_stroke_execution_services.h @@ -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 +inline void bind_legacy_canvas_stroke_texture_inputs( + const std::array& bindings, + BindTextureInput&& bind_texture_input) +{ + for (const auto& binding : bindings) { + bind_texture_input(binding.input, binding.slot); + } +} + +template +inline void unbind_legacy_canvas_stroke_texture_inputs( + const std::array& bindings, + UnbindTextureInput&& unbind_texture_input) +{ + for (const auto& binding : bindings) { + unbind_texture_input(binding.input, binding.slot); + } +} + template std::size_t execute_legacy_canvas_stroke_frame_samples( Frames&& frames,