From 58885187bae563b4a8fccac7e718e5ccabb621a9 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Sat, 13 Jun 2026 18:44:36 +0200 Subject: [PATCH] Extract main stroke live-pass orchestration --- docs/modernization/debt.md | 4 + docs/modernization/tasks.md | 4 + src/canvas.cpp | 94 +++++++++++-------- src/legacy_canvas_stroke_execution_services.h | 28 ++++++ .../paint_renderer/stroke_execution_tests.cpp | 28 ++++++ 5 files changed, 119 insertions(+), 39 deletions(-) diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 6f5cba7..4345d71 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -18,6 +18,10 @@ agent or engineer to remove them without reconstructing context from chat. ## Recent Reductions +- 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw()` now + routes its main live-pass bind, execute, and unbind sequence through + `execute_legacy_canvas_stroke_main_pass(...)`; the retained adapter still + owns the concrete sampler and texture callbacks. - 2026-06-13: RND-005 was completed. `desktop-gpu` now has a second deterministic OpenGL readback fixture and the CTest registration uses the configured target path so the gate actually runs. diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index 50dfaad..4b071c9 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -575,6 +575,10 @@ Done Checks: Progress Notes: +- 2026-06-13: `Canvas::stroke_draw()` now routes its main live-pass bind, + execute, and unbind sequence through + `execute_legacy_canvas_stroke_main_pass(...)`; the retained adapter still + owns the concrete sampler and texture callbacks. - 2026-06-13: `Canvas::stroke_draw()` live-pass sampler wiring now reuses a retained helper builder, and the stroke execution tests cover it. `STR-004` is now done after the final pad-destination helper extraction and tracker diff --git a/src/canvas.cpp b/src/canvas.cpp index 2fdf264..20ec46a 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -780,51 +780,67 @@ void Canvas::stroke_draw() [&] { 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, - main_pass_texture_dispatch); - auto frames = stroke_draw_compute(*m_current_stroke); std::array box_face = SIXPLETTE(glm::vec4(m_size, 0, 0)); std::array box_dirty = SIXPLETTE(false); const std::array include_main_dirty = SIXPLETTE(true); glm::vec4 pad_color; - pp::panopainter::execute_legacy_canvas_stroke_main_pass_frame_callbacks( - frames, - stroke_extent, - std::span(m_dirty_box), - std::span(box_face), - std::span(include_main_dirty), - [&](auto& f) { - if (brush->m_tip_mix > 0.f) - { - stroke_draw_mix(xy(f.m_mixer_rect), zw(f.m_mixer_rect)); - } - pad_color = f.col; - }, - [&](auto&, int i, auto&) { - m_dirty_face[i] = true; - box_dirty[i] = true; - }, - [&](auto& f, int i, auto& P) { - pp::panopainter::use_legacy_stroke_shader(); - pp::panopainter::apply_legacy_stroke_sample_uniforms( - pp::panopainter::LegacyStrokeSampleUniforms { - .color = f.col, - .alpha = f.flow, - .opacity = f.opacity, - }); - return stroke_draw_samples(i, P, copy_stroke_destination); - }, - m_tmp); - - pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs( - main_pass_texture_unbindings, - main_pass_texture_dispatch); + [[maybe_unused]] const auto main_pass_result = + pp::panopainter::execute_legacy_canvas_stroke_main_pass( + pp::panopainter::LegacyCanvasStrokeMainPassExecutionRequest { + .context = "Canvas::stroke_draw", + .bind_samplers = [&] { + pp::panopainter::bind_legacy_canvas_stroke_sampler_inputs( + live_pass_sampler_bindings, + live_pass_sampler_dispatch); + }, + .bind_textures = [&] { + pp::panopainter::bind_legacy_canvas_stroke_texture_inputs( + main_pass_texture_bindings, + main_pass_texture_dispatch); + }, + .execute_frame_pass = [&] { + pp::panopainter::execute_legacy_canvas_stroke_main_pass_frame_callbacks( + frames, + stroke_extent, + std::span(m_dirty_box), + std::span(box_face), + std::span(include_main_dirty), + [&](auto& f) { + if (brush->m_tip_mix > 0.f) + { + stroke_draw_mix(xy(f.m_mixer_rect), zw(f.m_mixer_rect)); + } + pad_color = f.col; + }, + [&](auto&, int i, auto&) { + m_dirty_face[i] = true; + box_dirty[i] = true; + }, + [&](auto& f, int i, auto& P) { + pp::panopainter::use_legacy_stroke_shader(); + pp::panopainter::apply_legacy_stroke_sample_uniforms( + pp::panopainter::LegacyStrokeSampleUniforms { + .color = f.col, + .alpha = f.flow, + .opacity = f.opacity, + }); + return stroke_draw_samples(i, P, copy_stroke_destination); + }, + m_tmp); + }, + .unbind_textures = [&] { + pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs( + main_pass_texture_unbindings, + main_pass_texture_dispatch); + }, + .unbind_samplers = [&] { + pp::panopainter::unbind_legacy_canvas_stroke_sampler_inputs( + live_pass_sampler_bindings, + live_pass_sampler_dispatch); + }, + }); // pad stroke // In order to mitigate color bleeding at the edge of shapes in transparent layers diff --git a/src/legacy_canvas_stroke_execution_services.h b/src/legacy_canvas_stroke_execution_services.h index 84efdf6..4dff71a 100644 --- a/src/legacy_canvas_stroke_execution_services.h +++ b/src/legacy_canvas_stroke_execution_services.h @@ -151,6 +151,15 @@ struct LegacyCanvasStrokeSamplerDispatch { std::function unbind_mixer_sampler; }; +struct LegacyCanvasStrokeMainPassExecutionRequest { + std::string_view context; + std::function bind_samplers; + std::function bind_textures; + std::function execute_frame_pass; + std::function unbind_textures; + std::function unbind_samplers; +}; + [[nodiscard]] inline LegacyCanvasStrokeSamplerDispatch make_legacy_canvas_stroke_live_pass_sampler_dispatch( std::function bind_brush_tip_sampler, std::function unbind_brush_tip_sampler, @@ -195,6 +204,25 @@ struct LegacyCanvasStrokeSamplerDispatch { }; } +[[nodiscard]] inline bool execute_legacy_canvas_stroke_main_pass( + const LegacyCanvasStrokeMainPassExecutionRequest& request) +{ + if (!request.bind_samplers || + !request.bind_textures || + !request.execute_frame_pass || + !request.unbind_textures || + !request.unbind_samplers) { + return false; + } + + request.bind_samplers(); + request.bind_textures(); + request.execute_frame_pass(); + request.unbind_textures(); + request.unbind_samplers(); + return true; +} + struct LegacyCanvasStrokeFaceDirtyRequest { pp::renderer::Extent2D extent {}; glm::vec4 previous_accumulated_dirty_box {}; diff --git a/tests/paint_renderer/stroke_execution_tests.cpp b/tests/paint_renderer/stroke_execution_tests.cpp index 9217923..ec280f6 100644 --- a/tests/paint_renderer/stroke_execution_tests.cpp +++ b/tests/paint_renderer/stroke_execution_tests.cpp @@ -1437,6 +1437,31 @@ void retained_stroke_main_pass_frame_callbacks_preserve_order(pp::tests::Harness PP_EXPECT(h, events == expected_events); } +void retained_stroke_main_pass_execution_preserves_bind_and_unbind_order(pp::tests::Harness& h) +{ + std::vector events; + + const auto ok = pp::panopainter::execute_legacy_canvas_stroke_main_pass( + pp::panopainter::LegacyCanvasStrokeMainPassExecutionRequest { + .context = "test", + .bind_samplers = [&] { events.emplace_back("bind-samplers"); }, + .bind_textures = [&] { events.emplace_back("bind-textures"); }, + .execute_frame_pass = [&] { events.emplace_back("execute"); }, + .unbind_textures = [&] { events.emplace_back("unbind-textures"); }, + .unbind_samplers = [&] { events.emplace_back("unbind-samplers"); }, + }); + + const std::vector expected_events { + "bind-samplers", + "bind-textures", + "execute", + "unbind-textures", + "unbind-samplers", + }; + PP_EXPECT(h, ok); + PP_EXPECT(h, events == expected_events); +} + void retained_stroke_pad_face_callbacks_preserve_order(pp::tests::Harness& h) { const std::array dirty_faces { true, false, true }; @@ -1781,6 +1806,9 @@ int main() harness.run( "retained_stroke_main_pass_frame_callbacks_preserve_order", retained_stroke_main_pass_frame_callbacks_preserve_order); + harness.run( + "retained_stroke_main_pass_execution_preserves_bind_and_unbind_order", + retained_stroke_main_pass_execution_preserves_bind_and_unbind_order); harness.run( "retained_stroke_live_pass_sampler_dispatch_helper_builds_expected_callback_wiring", retained_stroke_live_pass_sampler_dispatch_helper_builds_expected_callback_wiring);