diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index d6636f2..da174f3 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -71,6 +71,11 @@ agent or engineer to remove them without reconstructing context from chat. `execute_legacy_canvas_stroke_live_pass_with_face_framebuffers(...)`; the retained path still owns the concrete framebuffer array and per-face GL callbacks. +- 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` dual-pass + face execution now routes through + `execute_legacy_canvas_stroke_dual_pass_frame_callbacks(...)`; the retained + path still owns the concrete dual-brush shader, sampler, 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 f454423..9bd0b6c 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -883,7 +883,7 @@ ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_ex ### STR-006 - Extract Dual Stroke Face Execution 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`, @@ -910,3 +910,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-006 | +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 ddaf313..8f62790 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -941,7 +941,7 @@ void Canvas::stroke_draw() })); }, .execute_frame_pass = [&] { - pp::panopainter::execute_legacy_canvas_stroke_live_pass_with_face_framebuffers( + pp::panopainter::execute_legacy_canvas_stroke_dual_pass_frame_callbacks( frames_dual, stroke_extent, std::span(m_dirty_box), diff --git a/src/legacy_canvas_stroke_execution_services.h b/src/legacy_canvas_stroke_execution_services.h index 2ef0f0c..0b1c905 100644 --- a/src/legacy_canvas_stroke_execution_services.h +++ b/src/legacy_canvas_stroke_execution_services.h @@ -308,6 +308,36 @@ std::size_t execute_legacy_canvas_stroke_live_pass_with_face_framebuffers( std::span committed_dirty_faces = {}, std::span pass_dirty_faces = {}); +template +std::size_t execute_legacy_canvas_stroke_dual_pass_frame_callbacks( + Frames&& frames, + pp::renderer::Extent2D extent, + std::span accumulated_dirty_boxes, + std::span pass_dirty_boxes, + std::span include_in_committed_dirty_box, + BeginFrame&& begin_frame, + PrepareFace&& prepare_face, + ExecuteSample&& execute_sample, + Framebuffers& face_framebuffers, + bool preserve_sample_dirty_as_pass_dirty = false, + std::span committed_dirty_faces = {}, + std::span pass_dirty_faces = {}) +{ + return execute_legacy_canvas_stroke_live_pass_with_face_framebuffers( + std::forward(frames), + extent, + accumulated_dirty_boxes, + pass_dirty_boxes, + include_in_committed_dirty_box, + std::forward(begin_frame), + std::forward(prepare_face), + std::forward(execute_sample), + face_framebuffers, + preserve_sample_dirty_as_pass_dirty, + committed_dirty_faces, + pass_dirty_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 eb0032a..b2b10c1 100644 --- a/tests/paint_renderer/stroke_execution_tests.cpp +++ b/tests/paint_renderer/stroke_execution_tests.cpp @@ -1301,6 +1301,74 @@ void retained_stroke_live_pass_retains_previous_pass_dirty_union_without_overrid PP_EXPECT(h, pass_dirty_faces[1]); } +void retained_stroke_dual_pass_frame_callbacks_preserve_order(pp::tests::Harness& h) +{ + StrokeFrame frame; + frame.id = 21; + frame.shapes[0] = { + vertex_t(glm::vec2(1.0F, 1.0F)), + vertex_t(glm::vec2(2.0F, 1.0F)), + vertex_t(glm::vec2(2.0F, 2.0F)), + }; + frame.shapes[3] = { + vertex_t(glm::vec2(3.0F, 3.0F)), + vertex_t(glm::vec2(4.0F, 3.0F)), + vertex_t(glm::vec2(4.0F, 4.0F)), + }; + + std::array frames { frame }; + std::array accumulated_dirty_boxes; + std::array pass_dirty_boxes; + accumulated_dirty_boxes.fill(glm::vec4(64.0F, 64.0F, 0.0F, 0.0F)); + pass_dirty_boxes.fill(glm::vec4(64.0F, 64.0F, 0.0F, 0.0F)); + std::array include_in_committed_dirty_box { true, true, true, true, true, true }; + + std::vector events; + std::array face_framebuffers {}; + for (int face_index = 0; face_index < 6; ++face_index) { + face_framebuffers[face_index].events = &events; + face_framebuffers[face_index].face_index = face_index; + } + + const auto executed_faces = pp::panopainter::execute_legacy_canvas_stroke_dual_pass_frame_callbacks( + frames, + pp::renderer::Extent2D { .width = 64, .height = 64 }, + accumulated_dirty_boxes, + pass_dirty_boxes, + include_in_committed_dirty_box, + [&](StrokeFrame& current_frame) { + events.push_back("begin-frame:" + std::to_string(current_frame.id)); + }, + [&](StrokeFrame&, int face_index, std::span vertices) { + events.push_back( + "prepare:" + std::to_string(face_index) + ":" + std::to_string(vertices.size())); + }, + [&](StrokeFrame&, int face_index, std::span) { + events.push_back("execute:" + std::to_string(face_index)); + return glm::vec4( + static_cast(face_index + 10), + static_cast(face_index + 11), + static_cast(face_index + 12), + static_cast(face_index + 13)); + }, + face_framebuffers, + true); + + const std::vector expected_events { + "begin-frame:21", + "prepare:0:3", + "bind:0", + "execute:0", + "unbind:0", + "prepare:3:3", + "bind:3", + "execute:3", + "unbind:3", + }; + PP_EXPECT(h, executed_faces == 2U); + PP_EXPECT(h, events == expected_events); +} + void retained_stroke_pad_executor_copies_destination_for_dirty_faces_only(pp::tests::Harness& h) { const std::array dirty_faces { true, false, true }; @@ -1573,6 +1641,9 @@ int main() harness.run( "retained_stroke_live_pass_face_framebuffers_helper_preserves_callback_order", retained_stroke_live_pass_face_framebuffers_helper_preserves_callback_order); + harness.run( + "retained_stroke_dual_pass_frame_callbacks_preserve_order", + retained_stroke_dual_pass_frame_callbacks_preserve_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);