diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 9df3782..92d9762 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -3127,6 +3127,9 @@ 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 texture + dispatch activation order and sampler-dispatch routing across brush tip, + destination, pattern, and mixer helper inputs. - `Canvas::stroke_draw` pad-pass destination bind/copy/unbind ordering now shares the retained stroke execution helper callback surface, while shader setup, pad color selection, framebuffer ownership, and final OpenGL draw diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index c602464..df0450b 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -509,6 +509,11 @@ Done Checks: Progress Notes: +- 2026-06-13: `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. Next test + slice should extend the dedicated lane into frame-sample loop ordering or + preview-side retained helper coverage. - 2026-06-13: Added `pp_paint_renderer_stroke_execution_tests` as a dedicated retained stroke execution-helper target covering texture-input binding order, sample destination-copy behavior, live-pass face-framebuffer dirty tracking, diff --git a/tests/paint_renderer/stroke_execution_tests.cpp b/tests/paint_renderer/stroke_execution_tests.cpp index a0a87a7..b1590e9 100644 --- a/tests/paint_renderer/stroke_execution_tests.cpp +++ b/tests/paint_renderer/stroke_execution_tests.cpp @@ -24,7 +24,9 @@ using pp::panopainter::LegacyCanvasStrokePadExecutionRequest; using pp::panopainter::LegacyCanvasStrokePadFace; +using pp::panopainter::LegacyCanvasStrokeSamplerDispatch; using pp::panopainter::LegacyCanvasStrokeTextureBinding; +using pp::panopainter::LegacyCanvasStrokeTextureInputDispatch; using pp::panopainter::LegacyCanvasStrokeTextureInput; using pp::panopainter::LegacyStrokeSampleExecutionRequest; @@ -87,6 +89,107 @@ void retained_stroke_texture_inputs_bind_and_unbind_in_declared_order(pp::tests: } } +void retained_stroke_texture_dispatch_activates_units_and_routes_per_input(pp::tests::Harness& h) +{ + const std::array bindings { + LegacyCanvasStrokeTextureBinding { .input = LegacyCanvasStrokeTextureInput::brush_tip, .slot = 2 }, + LegacyCanvasStrokeTextureBinding { .input = LegacyCanvasStrokeTextureInput::stroke_destination, .slot = 0 }, + LegacyCanvasStrokeTextureBinding { .input = LegacyCanvasStrokeTextureInput::pattern, .slot = 4 }, + LegacyCanvasStrokeTextureBinding { .input = LegacyCanvasStrokeTextureInput::mixer, .slot = 1 }, + }; + + std::vector events; + const LegacyCanvasStrokeTextureInputDispatch dispatch { + .activate_texture_unit = [&](int slot) { events.emplace_back("activate:" + std::to_string(slot)); }, + .bind_brush_tip = [&]() { events.emplace_back("bind:brush_tip"); }, + .unbind_brush_tip = [&]() { events.emplace_back("unbind:brush_tip"); }, + .bind_stroke_destination = [&]() { events.emplace_back("bind:stroke_destination"); }, + .unbind_stroke_destination = [&]() { events.emplace_back("unbind:stroke_destination"); }, + .bind_pattern = [&]() { events.emplace_back("bind:pattern"); }, + .unbind_pattern = [&]() { events.emplace_back("unbind:pattern"); }, + .bind_mixer = [&]() { events.emplace_back("bind:mixer"); }, + .unbind_mixer = [&]() { events.emplace_back("unbind:mixer"); }, + }; + + pp::panopainter::bind_legacy_canvas_stroke_texture_input( + LegacyCanvasStrokeTextureInput::brush_tip, + dispatch); + pp::panopainter::unbind_legacy_canvas_stroke_texture_input( + LegacyCanvasStrokeTextureInput::brush_tip, + dispatch); + pp::panopainter::bind_legacy_canvas_stroke_texture_inputs(bindings, dispatch); + pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs(bindings, dispatch); + + const std::vector expected_events { + "bind:brush_tip", + "unbind:brush_tip", + "activate:2", + "bind:brush_tip", + "activate:0", + "bind:stroke_destination", + "activate:4", + "bind:pattern", + "activate:1", + "bind:mixer", + "activate:2", + "unbind:brush_tip", + "activate:0", + "unbind:stroke_destination", + "activate:4", + "unbind:pattern", + "activate:1", + "unbind:mixer", + }; + PP_EXPECT(h, events == expected_events); +} + +void retained_stroke_sampler_dispatch_routes_bind_and_unbind_per_input(pp::tests::Harness& h) +{ + const std::array bindings { + LegacyCanvasStrokeTextureBinding { .input = LegacyCanvasStrokeTextureInput::brush_tip, .slot = 3 }, + LegacyCanvasStrokeTextureBinding { .input = LegacyCanvasStrokeTextureInput::stroke_destination, .slot = 1 }, + LegacyCanvasStrokeTextureBinding { .input = LegacyCanvasStrokeTextureInput::pattern, .slot = 7 }, + LegacyCanvasStrokeTextureBinding { .input = LegacyCanvasStrokeTextureInput::mixer, .slot = 5 }, + }; + + std::vector events; + const LegacyCanvasStrokeSamplerDispatch dispatch { + .bind_brush_tip_sampler = [&](int slot) { events.emplace_back("bind:brush_tip:" + std::to_string(slot)); }, + .unbind_brush_tip_sampler = [&]() { events.emplace_back("unbind:brush_tip"); }, + .bind_stroke_destination_sampler = + [&](int slot) { events.emplace_back("bind:stroke_destination:" + std::to_string(slot)); }, + .unbind_stroke_destination_sampler = [&]() { events.emplace_back("unbind:stroke_destination"); }, + .bind_pattern_sampler = [&](int slot) { events.emplace_back("bind:pattern:" + std::to_string(slot)); }, + .unbind_pattern_sampler = [&]() { events.emplace_back("unbind:pattern"); }, + .bind_mixer_sampler = [&](int slot) { events.emplace_back("bind:mixer:" + std::to_string(slot)); }, + .unbind_mixer_sampler = [&]() { events.emplace_back("unbind:mixer"); }, + }; + + pp::panopainter::bind_legacy_canvas_stroke_sampler_input( + LegacyCanvasStrokeTextureInput::mixer, + 9, + dispatch); + pp::panopainter::unbind_legacy_canvas_stroke_sampler_input( + LegacyCanvasStrokeTextureInput::mixer, + dispatch); + pp::panopainter::bind_legacy_canvas_stroke_sampler_inputs(bindings, dispatch); + pp::panopainter::unbind_legacy_canvas_stroke_sampler_inputs(bindings, dispatch); + + const std::vector expected_events { + "bind:mixer:9", + "unbind:mixer", + "bind:brush_tip:3", + "bind:stroke_destination:1", + "bind:pattern:7", + "bind:mixer:5", + "unbind:brush_tip", + "unbind:stroke_destination", + "unbind:pattern", + "unbind:mixer", + }; + PP_EXPECT(h, events == expected_events); +} + void retained_stroke_sample_executor_copies_destination_and_expands_quads(pp::tests::Harness& h) { const auto vertices = make_quad_vertices(); @@ -369,6 +472,12 @@ int main() harness.run( "retained_stroke_sample_executor_copies_destination_and_expands_quads", retained_stroke_sample_executor_copies_destination_and_expands_quads); + harness.run( + "retained_stroke_texture_dispatch_activates_units_and_routes_per_input", + retained_stroke_texture_dispatch_activates_units_and_routes_per_input); + harness.run( + "retained_stroke_sampler_dispatch_routes_bind_and_unbind_per_input", + retained_stroke_sampler_dispatch_routes_bind_and_unbind_per_input); harness.run( "retained_stroke_sample_executor_unbinds_and_skips_draw_when_bounds_are_empty", retained_stroke_sample_executor_unbinds_and_skips_draw_when_bounds_are_empty);