diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index c4f8862..90460a4 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -537,6 +537,10 @@ agent or engineer to remove them without reconstructing context from chat. sampler dispatch now has regression coverage through `make_legacy_canvas_stroke_live_pass_sampler_dispatch(...)`; the live path still owns the concrete sampler objects and binding state. +- 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` dual-pass + brush-tip dispatch now uses a retained helper object, with regression + coverage proving the helper order; the live path still owns the concrete + brush-tip texture object and dual-pass branch wiring. - 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 d0b8a91..9baba1d 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -668,6 +668,12 @@ Validation: ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure ``` +### Completed Task Log + +| Date | Task | Score | Validation | Commit | +| --- | --- | --- | --- | --- | +| 2026-06-13 | STR-022 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `pending` | + Progress Notes: - 2026-06-13: `NodeStrokePreview::draw_stroke_immediate()` now routes final diff --git a/src/canvas.cpp b/src/canvas.cpp index 15fdf0d..42cdbf1 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -1121,6 +1121,22 @@ void Canvas::stroke_draw() .slot = 0, }, }; + const auto dual_pass_brush_tip_dispatch = + pp::panopainter::make_legacy_canvas_stroke_brush_tip_texture_dispatch( + [&](int texture_slot) { + set_active_texture_unit(texture_slot); + }, + [&](int) { + dual_brush->m_tip_texture ? + dual_brush->m_tip_texture->bind() : + unbind_texture_2d(); + }, + [&](int) { + dual_brush->m_tip_texture ? + dual_brush->m_tip_texture->unbind() : + unbind_texture_2d(); + }, + 0); auto frames_dual = stroke_draw_compute(*m_dual_stroke); const std::array include_dual_dirty = SIXPLETTE(stroke_material.composite_pass.dual_blend_mode == 0); @@ -1131,40 +1147,12 @@ void Canvas::stroke_draw() .bind_brush_tip = [&] { pp::panopainter::bind_legacy_canvas_stroke_texture_inputs( dual_pass_texture_bindings, - pp::panopainter::make_legacy_canvas_stroke_brush_tip_texture_dispatch( - [&](int texture_slot) { - set_active_texture_unit(texture_slot); - }, - [&](int) { - dual_brush->m_tip_texture ? - dual_brush->m_tip_texture->bind() : - unbind_texture_2d(); - }, - [&](int) { - dual_brush->m_tip_texture ? - dual_brush->m_tip_texture->unbind() : - unbind_texture_2d(); - }, - 0)); + dual_pass_brush_tip_dispatch); }, .unbind_brush_tip = [&] { pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs( dual_pass_texture_bindings, - pp::panopainter::make_legacy_canvas_stroke_brush_tip_texture_dispatch( - [&](int texture_slot) { - set_active_texture_unit(texture_slot); - }, - [&](int) { - dual_brush->m_tip_texture ? - dual_brush->m_tip_texture->bind() : - unbind_texture_2d(); - }, - [&](int) { - dual_brush->m_tip_texture ? - dual_brush->m_tip_texture->unbind() : - unbind_texture_2d(); - }, - 0)); + dual_pass_brush_tip_dispatch); }, .setup_dual_shader = [&] { pp::panopainter::setup_legacy_stroke_dual_shader( diff --git a/tests/paint_renderer/compositor_tests.cpp b/tests/paint_renderer/compositor_tests.cpp index 6ba2383..f09803e 100644 --- a/tests/paint_renderer/compositor_tests.cpp +++ b/tests/paint_renderer/compositor_tests.cpp @@ -1892,6 +1892,48 @@ void legacy_canvas_stroke_live_pass_sampler_dispatch_preserves_order(pp::tests:: PP_EXPECT(h, steps == expected); } +void legacy_canvas_stroke_dual_pass_brush_tip_dispatch_preserves_order(pp::tests::Harness& h) +{ + std::vector steps; + const auto dispatch = pp::panopainter::make_legacy_canvas_stroke_brush_tip_texture_dispatch( + [&](int slot) { + steps.emplace_back("activate:" + std::to_string(slot)); + }, + [&](int) { + steps.emplace_back("bind-tip"); + }, + [&](int) { + steps.emplace_back("unbind-tip"); + }, + 0); + + pp::panopainter::bind_legacy_canvas_stroke_texture_inputs( + std::array { + pp::panopainter::LegacyCanvasStrokeTextureBinding { + .input = pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip, + .slot = 0, + }, + }, + dispatch); + + pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs( + std::array { + pp::panopainter::LegacyCanvasStrokeTextureBinding { + .input = pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip, + .slot = 0, + }, + }, + dispatch); + + const std::vector expected { + "activate:0", + "bind-tip", + "activate:0", + "unbind-tip", + }; + PP_EXPECT(h, steps == expected); +} + void plans_canvas_stroke_commit_erase_sequence(pp::tests::Harness& h) { const auto plan = plan_canvas_stroke_commit_sequence( @@ -3767,6 +3809,9 @@ int main() harness.run( "legacy_canvas_stroke_live_pass_sampler_dispatch_preserves_order", legacy_canvas_stroke_live_pass_sampler_dispatch_preserves_order); + harness.run( + "legacy_canvas_stroke_dual_pass_brush_tip_dispatch_preserves_order", + legacy_canvas_stroke_dual_pass_brush_tip_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(