diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index bd4af42..8c6c744 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -547,6 +547,10 @@ agent or engineer to remove them without reconstructing context from chat. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` dual-pass shader setup now uses a retained wrapper helper; the dual-pass branch still owns the concrete shader selection and framebuffer wiring. +- 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` pad + destination dispatch now reuses a retained helper object, with regression + coverage proving the helper order; the pad branch still owns the concrete + texture-object wiring and copy timing. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge` checkerboard background shader setup and final merged-texture redraw setup now route through `legacy_canvas_draw_merge_services.h`. The retained Canvas path still diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index 5bf900d..108274d 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -791,7 +791,7 @@ ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_composito ### STR-027 - Extract Stroke Draw Pad Destination Dispatch -Status: Ready +Status: Done Score: +1 renderer boundary and OpenGL parity Debt: `DEBT-0036` Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`, `tests/paint_renderer/compositor_tests.cpp` @@ -817,6 +817,12 @@ Validation: ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-onfailure ``` +### Completed Task Log + +| Date | Task | Score | Validation | Commit | +| --- | --- | --- | --- | --- | +| 2026-06-13 | STR-027 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-onfailure` | `pending` | + ### STR-028 - Extract Stroke Draw Pad Face Orchestration Status: Ready diff --git a/src/canvas.cpp b/src/canvas.cpp index 513477e..4c8ea8d 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -1052,6 +1052,18 @@ void Canvas::stroke_draw() .slot = 1, }, }; + const auto pad_destination_texture_dispatch = + pp::panopainter::make_legacy_canvas_stroke_pad_destination_texture_dispatch( + [&](int texture_slot) { + set_active_texture_unit(texture_slot); + }, + [&](int dst_face_index) { + m_tex[dst_face_index].bind(); + }, + [&](int dst_face_index) { + m_tex[dst_face_index].unbind(); + }, + 0); [[maybe_unused]] const auto pad_result = pp::panopainter::execute_legacy_canvas_stroke_pad_face_callbacks( pad_faces, stroke_extent, @@ -1067,17 +1079,7 @@ void Canvas::stroke_draw() [&](int face_index) { pp::panopainter::bind_legacy_canvas_stroke_texture_inputs( pad_destination_texture_binding, - pp::panopainter::make_legacy_canvas_stroke_pad_destination_texture_dispatch( - [&](int texture_slot) { - set_active_texture_unit(texture_slot); - }, - [&](int dst_face_index) { - m_tex[dst_face_index].bind(); - }, - [&](int dst_face_index) { - m_tex[dst_face_index].unbind(); - }, - face_index)); + pad_destination_texture_dispatch); }, [&](const pp::paint_renderer::CanvasStrokeCopyRegion& copy_region) { pp::panopainter::execute_legacy_canvas_stroke_pad_copy_region( @@ -1087,17 +1089,7 @@ void Canvas::stroke_draw() [&](int face_index) { pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs( pad_destination_texture_binding, - pp::panopainter::make_legacy_canvas_stroke_pad_destination_texture_dispatch( - [&](int texture_slot) { - set_active_texture_unit(texture_slot); - }, - [&](int dst_face_index) { - m_tex[dst_face_index].bind(); - }, - [&](int dst_face_index) { - m_tex[dst_face_index].unbind(); - }, - face_index)); + pad_destination_texture_dispatch); }, [&] { m_brush_shape.draw_fill(); diff --git a/tests/paint_renderer/compositor_tests.cpp b/tests/paint_renderer/compositor_tests.cpp index 6b43810..63ab5d9 100644 --- a/tests/paint_renderer/compositor_tests.cpp +++ b/tests/paint_renderer/compositor_tests.cpp @@ -1968,6 +1968,47 @@ void legacy_canvas_stroke_dual_shader_wrapper_preserves_setup(pp::tests::Harness PP_EXPECT(h, steps.size() == 1U); } +void legacy_canvas_stroke_pad_destination_dispatch_preserves_order(pp::tests::Harness& h) +{ + std::vector steps; + const auto dispatch = pp::panopainter::make_legacy_canvas_stroke_pad_destination_texture_dispatch( + [&](int slot) { + steps.emplace_back("activate:" + std::to_string(slot)); + }, + [&](int face_index) { + steps.emplace_back("bind:" + std::to_string(face_index)); + }, + [&](int face_index) { + steps.emplace_back("unbind:" + std::to_string(face_index)); + }, + 4); + + pp::panopainter::bind_legacy_canvas_stroke_texture_inputs( + std::array { + pp::panopainter::LegacyCanvasStrokeTextureBinding { + .input = pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination, + .slot = 1, + }, + }, + dispatch); + pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs( + std::array { + pp::panopainter::LegacyCanvasStrokeTextureBinding { + .input = pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination, + .slot = 1, + }, + }, + dispatch); + + const std::vector expected { + "activate:1", + "bind:4", + "activate:1", + "unbind:4", + }; + PP_EXPECT(h, steps == expected); +} + void plans_canvas_stroke_commit_erase_sequence(pp::tests::Harness& h) { const auto plan = plan_canvas_stroke_commit_sequence( @@ -3849,6 +3890,9 @@ int main() harness.run( "legacy_canvas_stroke_pad_copy_region_preserves_coordinates", legacy_canvas_stroke_pad_copy_region_preserves_coordinates); + harness.run( + "legacy_canvas_stroke_pad_destination_dispatch_preserves_order", + legacy_canvas_stroke_pad_destination_dispatch_preserves_order); harness.run("plans_canvas_stroke_commit_erase_sequence", plans_canvas_stroke_commit_erase_sequence); harness.run("plans_canvas_stroke_commit_composite_sequence", plans_canvas_stroke_commit_composite_sequence); harness.run(