diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 67a254a..d858f4f 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -3136,6 +3136,10 @@ Results: input binding order, sample execution destination-copy behavior, live-pass face-framebuffer dirty tracking, and pad-face destination-copy behavior without depending on the broader compositor test translation unit. +- `pp_paint_renderer_stroke_execution_tests` now also covers retained preview + background capture ordering, final composite ordering, and preview + texture-copy bind-before-copy behavior through + `legacy_canvas_stroke_preview_services.h`. - `pp_paint_renderer_stroke_execution_tests` now also covers retained texture dispatch activation order and sampler-dispatch routing across brush tip, destination, pattern, and mixer helper inputs. diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index d19036b..6b8705a 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -509,6 +509,12 @@ Done Checks: Progress Notes: +- 2026-06-13: `pp_paint_renderer_stroke_execution_tests` now also covers + retained preview background capture ordering, final composite ordering, and + preview texture-copy bind-before-copy behavior via + `legacy_canvas_stroke_preview_services.h`. Next test slice should target the + next header-level preview live sample or mix-pass ordering surface without + reopening production files. - 2026-06-13: `Canvas::stroke_draw_samples()` now routes polygon triangulation, sample-point assembly, and the retained destination-copy / upload / draw helper handoff through diff --git a/tests/paint_renderer/stroke_execution_tests.cpp b/tests/paint_renderer/stroke_execution_tests.cpp index b1590e9..b34a5a6 100644 --- a/tests/paint_renderer/stroke_execution_tests.cpp +++ b/tests/paint_renderer/stroke_execution_tests.cpp @@ -20,14 +20,18 @@ #include #include "legacy_canvas_stroke_execution_services.h" +#include "legacy_canvas_stroke_preview_services.h" #include "test_harness.h" using pp::panopainter::LegacyCanvasStrokePadExecutionRequest; using pp::panopainter::LegacyCanvasStrokePadFace; +using pp::panopainter::LegacyCanvasStrokeMixPassPlane; +using pp::panopainter::LegacyCanvasStrokeMixPassRequest; using pp::panopainter::LegacyCanvasStrokeSamplerDispatch; using pp::panopainter::LegacyCanvasStrokeTextureBinding; using pp::panopainter::LegacyCanvasStrokeTextureInputDispatch; using pp::panopainter::LegacyCanvasStrokeTextureInput; +using pp::panopainter::LegacyStrokePreviewCopySize; using pp::panopainter::LegacyStrokeSampleExecutionRequest; namespace { @@ -461,6 +465,148 @@ void retained_stroke_pad_executor_copies_destination_for_dirty_faces_only(pp::te PP_EXPECT(h, copy_regions[0].height == 50); } +void retained_stroke_preview_background_capture_preserves_retained_call_order(pp::tests::Harness& h) +{ + std::vector events; + std::array copy_args {}; + + pp::panopainter::execute_legacy_stroke_preview_background_capture( + [&]() { events.emplace_back("setup"); }, + [&]() { events.emplace_back("draw"); }, + [&]() { events.emplace_back("bind"); }, + [&](int dst_x, int dst_y, int src_x, int src_y, int width, int height) { + events.emplace_back("copy"); + copy_args = { dst_x, dst_y, src_x, src_y, width, height }; + }, + LegacyStrokePreviewCopySize { .width = 48, .height = 32 }); + + const std::vector expected_events { "setup", "draw", "bind", "copy" }; + PP_EXPECT(h, events == expected_events); + PP_EXPECT(h, copy_args[0] == 0); + PP_EXPECT(h, copy_args[1] == 0); + PP_EXPECT(h, copy_args[2] == 0); + PP_EXPECT(h, copy_args[3] == 0); + PP_EXPECT(h, copy_args[4] == 48); + PP_EXPECT(h, copy_args[5] == 32); +} + +void retained_stroke_preview_final_composite_preserves_retained_call_order(pp::tests::Harness& h) +{ + std::vector events; + + pp::panopainter::execute_legacy_stroke_preview_final_composite( + [&]() { events.emplace_back("setup"); }, + [&]() { events.emplace_back("bind-samplers"); }, + [&]() { events.emplace_back("bind-inputs"); }, + [&]() { events.emplace_back("draw"); }); + + const std::vector expected_events { + "setup", + "bind-samplers", + "bind-inputs", + "draw", + }; + PP_EXPECT(h, events == expected_events); +} + +void retained_stroke_preview_texture_copy_binds_before_copy(pp::tests::Harness& h) +{ + std::vector events; + std::array copy_args {}; + + pp::panopainter::copy_legacy_stroke_preview_texture( + [&]() { events.emplace_back("bind"); }, + [&](int dst_x, int dst_y, int src_x, int src_y, int width, int height) { + events.emplace_back("copy"); + copy_args = { dst_x, dst_y, src_x, src_y, width, height }; + }, + LegacyStrokePreviewCopySize { .width = 96, .height = 64 }); + + const std::vector expected_events { "bind", "copy" }; + PP_EXPECT(h, events == expected_events); + PP_EXPECT(h, copy_args[0] == 0); + PP_EXPECT(h, copy_args[1] == 0); + PP_EXPECT(h, copy_args[2] == 0); + PP_EXPECT(h, copy_args[3] == 0); + PP_EXPECT(h, copy_args[4] == 96); + PP_EXPECT(h, copy_args[5] == 64); +} + +void retained_stroke_mix_pass_skips_inactive_planes_and_preserves_texture_order(pp::tests::Harness& h) +{ + const std::array planes { + LegacyCanvasStrokeMixPassPlane { .index = 0, .visible = true, .has_target = true, .opacity = 1.0F }, + LegacyCanvasStrokeMixPassPlane { .index = 1, .visible = false, .has_target = true, .opacity = 1.0F }, + LegacyCanvasStrokeMixPassPlane { .index = 2, .visible = true, .has_target = false, .opacity = 1.0F }, + LegacyCanvasStrokeMixPassPlane { .index = 3, .visible = true, .has_target = true, .opacity = 0.0F }, + }; + + std::vector events; + const auto result = pp::panopainter::execute_legacy_canvas_stroke_mix_pass( + LegacyCanvasStrokeMixPassRequest { + .context = "test", + .resolution = glm::vec2(128.0F, 64.0F), + .planes = planes, + .bind_mix_samplers = [&]() { events.emplace_back("bind-samplers"); }, + .unbind_mix_samplers = [&]() { events.emplace_back("unbind-samplers"); }, + .setup_plane_shader = [&](int plane_index, const glm::mat4&) { + events.emplace_back("setup:" + std::to_string(plane_index)); + }, + .bind_layer_texture = [&](int plane_index) { + events.emplace_back("bind-layer:" + std::to_string(plane_index)); + }, + .bind_stroke_texture = [&](int plane_index) { + events.emplace_back("bind-stroke:" + std::to_string(plane_index)); + }, + .bind_mask_texture = [&](int plane_index) { + events.emplace_back("bind-mask:" + std::to_string(plane_index)); + }, + .draw_plane = [&]() { + events.emplace_back("draw"); + }, + .unbind_mask_texture = [&](int plane_index) { + events.emplace_back("unbind-mask:" + std::to_string(plane_index)); + }, + .unbind_stroke_texture = [&](int plane_index) { + events.emplace_back("unbind-stroke:" + std::to_string(plane_index)); + }, + .unbind_layer_texture = [&](int plane_index) { + events.emplace_back("unbind-layer:" + std::to_string(plane_index)); + }, + }); + + PP_EXPECT(h, result.ok); + PP_EXPECT(h, result.composed_planes == 1U); + const std::vector expected_events { + "bind-samplers", + "setup:0", + "bind-layer:0", + "bind-stroke:0", + "bind-mask:0", + "draw", + "unbind-mask:0", + "unbind-stroke:0", + "unbind-layer:0", + "unbind-samplers", + }; + PP_EXPECT(h, events == expected_events); +} + +void retained_stroke_mix_pass_rejects_incomplete_requests(pp::tests::Harness& h) +{ + std::vector events; + const auto result = pp::panopainter::execute_legacy_canvas_stroke_mix_pass( + LegacyCanvasStrokeMixPassRequest { + .context = "test", + .resolution = glm::vec2(128.0F, 64.0F), + .bind_mix_samplers = [&]() { events.emplace_back("bind-samplers"); }, + }); + + PP_EXPECT(h, !result.ok); + PP_EXPECT(h, result.composed_planes == 0U); + PP_EXPECT(h, events.empty()); +} + } // namespace int main() @@ -487,5 +633,20 @@ int main() harness.run( "retained_stroke_pad_executor_copies_destination_for_dirty_faces_only", retained_stroke_pad_executor_copies_destination_for_dirty_faces_only); + harness.run( + "retained_stroke_preview_background_capture_preserves_retained_call_order", + retained_stroke_preview_background_capture_preserves_retained_call_order); + harness.run( + "retained_stroke_preview_final_composite_preserves_retained_call_order", + retained_stroke_preview_final_composite_preserves_retained_call_order); + harness.run( + "retained_stroke_preview_texture_copy_binds_before_copy", + retained_stroke_preview_texture_copy_binds_before_copy); + harness.run( + "retained_stroke_mix_pass_skips_inactive_planes_and_preserves_texture_order", + retained_stroke_mix_pass_skips_inactive_planes_and_preserves_texture_order); + harness.run( + "retained_stroke_mix_pass_rejects_incomplete_requests", + retained_stroke_mix_pass_rejects_incomplete_requests); return harness.finish(); }