From 11a62e9b43c04a3d0c87c78a2aa6c2ed3990ae41 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Sat, 13 Jun 2026 18:01:45 +0200 Subject: [PATCH] Extract pad stroke face orchestration --- docs/modernization/debt.md | 7 +- docs/modernization/tasks.md | 8 +- src/canvas.cpp | 76 +++++++++---------- src/legacy_canvas_stroke_execution_services.h | 32 ++++++++ .../paint_renderer/stroke_execution_tests.cpp | 66 ++++++++++++++++ 5 files changed, 145 insertions(+), 44 deletions(-) diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 3b66387..5c96296 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -86,9 +86,10 @@ agent or engineer to remove them without reconstructing context from chat. `execute_legacy_canvas_stroke_main_pass_frame_callbacks(...)`; the retained path still owns the concrete shader, sampler, and framebuffer wiring. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` pad-face - orchestration remains retained and is next targeted for helper extraction; - pad destination dispatch already routes through a retained helper, but the - pad face loop and copy timing still live in `Canvas`. + orchestration now routes through + `execute_legacy_canvas_stroke_pad_face_callbacks(...)`; the retained path + still owns the concrete brush shape, destination dispatch, and framebuffer + wiring. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw_samples()` now routes polygon triangulation, sample-point assembly, and retained destination-copy / upload / draw helper handoff through diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index c20273c..e5d0b31 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -954,7 +954,7 @@ ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_ex ### STR-008 - Extract Pad Stroke Face Orchestration -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`, @@ -980,3 +980,9 @@ Validation: ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure & 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_paint_renderer_stroke_execution_tests.vcxproj /p:Configuration=Debug /p:Platform=x64 ``` + +### Completed Task Log + +| Date | Task | Score | Validation | Commit | +| --- | --- | ---: | --- | --- | +| 2026-06-13 | STR-008 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure`; `MSBuild.exe out\build\windows-msvc-default\tests\pp_paint_renderer_stroke_execution_tests.vcxproj /p:Configuration=Debug /p:Platform=x64` | pending | diff --git a/src/canvas.cpp b/src/canvas.cpp index 79b2643..667d0e8 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -858,46 +858,42 @@ void Canvas::stroke_draw() m_tex[face_index].unbind(); }); }; - [[maybe_unused]] const auto pad_result = pp::panopainter::execute_legacy_canvas_stroke_pad_faces( - pp::panopainter::LegacyCanvasStrokePadExecutionRequest { - .context = "Canvas::stroke_draw", - .extent = stroke_extent, - .faces = pad_faces, - .copy_stroke_destination = copy_stroke_destination, - .upload_pad_vertices = [&](std::span pad_quad) { - m_brush_shape.update_vertices( - const_cast(pad_quad.data()), - static_cast(pad_quad.size())); - }, - .begin_face = [&](int face_index) { - m_tmp[face_index].bindFramebuffer(); - }, - .bind_destination_texture = [&](int face_index) { - pp::panopainter::bind_legacy_canvas_stroke_texture_inputs( - pad_destination_texture_binding, - make_pad_destination_texture_dispatch(face_index)); - }, - .copy_framebuffer_to_destination_texture = - [&](const pp::paint_renderer::CanvasStrokeCopyRegion& copy_region) { - copy_framebuffer_to_texture_2d( - copy_region.x, - copy_region.y, - copy_region.x, - copy_region.y, - copy_region.width, - copy_region.height); - }, - .unbind_destination_texture = [&](int face_index) { - pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs( - pad_destination_texture_binding, - make_pad_destination_texture_dispatch(face_index)); - }, - .draw_pad = [&] { - m_brush_shape.draw_fill(); - }, - .finish_face = [&](int face_index) { - m_tmp[face_index].unbindFramebuffer(); - }, + [[maybe_unused]] const auto pad_result = pp::panopainter::execute_legacy_canvas_stroke_pad_face_callbacks( + pad_faces, + stroke_extent, + copy_stroke_destination, + [&](std::span pad_quad) { + m_brush_shape.update_vertices( + const_cast(pad_quad.data()), + static_cast(pad_quad.size())); + }, + [&](int face_index) { + m_tmp[face_index].bindFramebuffer(); + }, + [&](int face_index) { + pp::panopainter::bind_legacy_canvas_stroke_texture_inputs( + pad_destination_texture_binding, + make_pad_destination_texture_dispatch(face_index)); + }, + [&](const pp::paint_renderer::CanvasStrokeCopyRegion& copy_region) { + copy_framebuffer_to_texture_2d( + copy_region.x, + copy_region.y, + copy_region.x, + copy_region.y, + copy_region.width, + copy_region.height); + }, + [&](int face_index) { + pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs( + pad_destination_texture_binding, + make_pad_destination_texture_dispatch(face_index)); + }, + [&] { + m_brush_shape.draw_fill(); + }, + [&](int face_index) { + m_tmp[face_index].unbindFramebuffer(); }); // DRAW DUAL BRUSH diff --git a/src/legacy_canvas_stroke_execution_services.h b/src/legacy_canvas_stroke_execution_services.h index 28a465a..84efdf6 100644 --- a/src/legacy_canvas_stroke_execution_services.h +++ b/src/legacy_canvas_stroke_execution_services.h @@ -248,6 +248,9 @@ struct LegacyCanvasStrokePadExecutionResult { std::size_t padded_faces = 0; }; +[[nodiscard]] inline LegacyCanvasStrokePadExecutionResult execute_legacy_canvas_stroke_pad_faces( + const LegacyCanvasStrokePadExecutionRequest& request); + struct LegacyCanvasStrokeMixPassPlane { int index = 0; bool visible = true; @@ -368,6 +371,35 @@ std::size_t execute_legacy_canvas_stroke_main_pass_frame_callbacks( pass_dirty_faces); } +template +std::size_t execute_legacy_canvas_stroke_pad_face_callbacks( + Faces&& faces, + pp::renderer::Extent2D extent, + bool copy_stroke_destination, + UploadPadVertices&& upload_pad_vertices, + BeginFace&& begin_face, + BindDestinationTexture&& bind_destination_texture, + CopyFramebufferToDestinationTexture&& copy_framebuffer_to_destination_texture, + UnbindDestinationTexture&& unbind_destination_texture, + DrawPad&& draw_pad, + FinishFace&& finish_face) +{ + return execute_legacy_canvas_stroke_pad_faces( + LegacyCanvasStrokePadExecutionRequest { + .context = "Canvas::stroke_draw", + .extent = extent, + .faces = std::forward(faces), + .copy_stroke_destination = copy_stroke_destination, + .upload_pad_vertices = std::forward(upload_pad_vertices), + .begin_face = std::forward(begin_face), + .bind_destination_texture = std::forward(bind_destination_texture), + .copy_framebuffer_to_destination_texture = std::forward(copy_framebuffer_to_destination_texture), + .unbind_destination_texture = std::forward(unbind_destination_texture), + .draw_pad = std::forward(draw_pad), + .finish_face = std::forward(finish_face), + }).padded_faces; +} + [[nodiscard]] inline LegacyCanvasStrokeDualPassResult execute_legacy_canvas_stroke_dual_pass( const LegacyCanvasStrokeDualPassRequest& request) { diff --git a/tests/paint_renderer/stroke_execution_tests.cpp b/tests/paint_renderer/stroke_execution_tests.cpp index ae1069a..9217923 100644 --- a/tests/paint_renderer/stroke_execution_tests.cpp +++ b/tests/paint_renderer/stroke_execution_tests.cpp @@ -1437,6 +1437,72 @@ void retained_stroke_main_pass_frame_callbacks_preserve_order(pp::tests::Harness 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 }; + const std::array pass_dirty_boxes { + glm::vec4(5.0F, 10.0F, 20.0F, 30.0F), + glm::vec4(0.0F, 0.0F, 0.0F, 0.0F), + glm::vec4(60.0F, 15.0F, 70.0F, 25.0F), + }; + const auto faces = pp::panopainter::make_legacy_canvas_stroke_pad_faces(dirty_faces, pass_dirty_boxes); + + std::vector events; + std::vector copy_regions; + std::array uploaded_quad {}; + + const auto result = pp::panopainter::execute_legacy_canvas_stroke_pad_face_callbacks( + std::span(faces), + pp::renderer::Extent2D { .width = 100, .height = 80 }, + true, + [&](std::span vertices) { + events.emplace_back("upload:" + std::to_string(vertices.size())); + std::copy(vertices.begin(), vertices.end(), uploaded_quad.begin()); + }, + [&](int face_index) { + events.emplace_back("begin:" + std::to_string(face_index)); + }, + [&](int face_index) { + events.emplace_back("bind:" + std::to_string(face_index)); + }, + [&](const pp::paint_renderer::CanvasStrokeCopyRegion& copy_region) { + events.emplace_back("copy"); + copy_regions.push_back(copy_region); + }, + [&](int face_index) { + events.emplace_back("unbind:" + std::to_string(face_index)); + }, + [&] { + events.emplace_back("draw"); + }, + [&](int face_index) { + events.emplace_back("finish:" + std::to_string(face_index)); + }); + + PP_EXPECT(h, result == 2U); + const std::vector expected_events { + "upload:4", + "begin:0", + "bind:0", + "draw", + "unbind:0", + "finish:0", + "upload:4", + "begin:2", + "bind:2", + "copy", + "draw", + "unbind:2", + "finish:2", + }; + PP_EXPECT(h, events == expected_events); + PP_EXPECT(h, copy_regions.size() == 1U); + PP_EXPECT(h, faces[0].dirty); + PP_EXPECT(h, !faces[1].dirty); + PP_EXPECT(h, faces[2].dirty); + PP_EXPECT(h, uploaded_quad.size() == 4U); +} + void retained_stroke_pad_executor_copies_destination_for_dirty_faces_only(pp::tests::Harness& h) { const std::array dirty_faces { true, false, true };