From e7d96bfdc4be578d145a17e4209e4804c687d160 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Sat, 13 Jun 2026 22:57:22 +0200 Subject: [PATCH] Add stroke mix order regression --- docs/modernization/debt.md | 4 ++ docs/modernization/tasks.md | 33 +++++++++ tests/paint_renderer/compositor_tests.cpp | 85 +++++++++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 1a965b7..25a8299 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -525,6 +525,10 @@ agent or engineer to remove them without reconstructing context from chat. routes retained mix-shell execution through a local wrapper around `execute_legacy_canvas_stroke_mix_pass_shell(...)`; the live path still owns the concrete mixer framebuffer setup and GL capability toggles. +- 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw_mix()` now + has regression coverage for retained mixer-state callback ordering through + `execute_legacy_canvas_stroke_mix_pass(...)`; the live path still owns the + concrete mixer framebuffer setup and GL capability toggles. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge` checkerboard background shader setup and final merged-texture redraw setup now route through `legacy_canvas_draw_merge_services.h`. The retained Canvas path still diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index 3e27d53..07baf5a 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -842,6 +842,39 @@ Progress Notes: slice should target the remaining `stroke_draw_samples()` callback body or any final temporary-texture composite setup without reopening landed sample, sampler, dirty, face, or pad helpers. + +### STR-019 - Extract Stroke Draw Mix Mixer State And Copy Ordering + +Status: Done +Score: +1 renderer boundary and OpenGL parity +Debt: `DEBT-0036` +Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`, `tests/paint_renderer/compositor_tests.cpp` + +Goal: + +Move the remaining `stroke_draw_mix()` mixer-framebuffer state and copy-order +callbacks into a retained helper so `Canvas::stroke_draw_mix()` keeps only +branch selection plus concrete GL object wiring. + +Done Checks: + +- `Canvas::stroke_draw_mix()` no longer owns the remaining mixer framebuffer + state and copy ordering inline. +- Regression coverage proves the extracted helper preserves the mixer-state + callback order. +- `docs/modernization/debt.md` records the reduced stroke-mix shell surface. + +Validation: + +```powershell +ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure +``` + +### Completed Task Log + +| Date | Task | Score | Validation | Commit | +| --- | --- | --- | --- | --- | +| 2026-06-13 | STR-019 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `pending` | - 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 diff --git a/tests/paint_renderer/compositor_tests.cpp b/tests/paint_renderer/compositor_tests.cpp index 492f4a9..5698b7d 100644 --- a/tests/paint_renderer/compositor_tests.cpp +++ b/tests/paint_renderer/compositor_tests.cpp @@ -3172,6 +3172,88 @@ void canvas_blend_gate_combines_layer_and_stroke_complexity(pp::tests::Harness& PP_EXPECT(h, plan.value().path == StrokeCompositePath::framebuffer_fetch); } +void legacy_canvas_stroke_mix_executor_preserves_plane_callback_order(pp::tests::Harness& h) +{ + std::vector steps; + std::array planes { + pp::panopainter::LegacyCanvasStrokeMixPassPlane { + .index = 4, + .visible = true, + .has_target = true, + .opacity = 0.75f, + .mvp = glm::mat4(1.0f), + }, + pp::panopainter::LegacyCanvasStrokeMixPassPlane { + .index = 5, + .visible = false, + .has_target = true, + .opacity = 0.5f, + .mvp = glm::mat4(1.0f), + }, + pp::panopainter::LegacyCanvasStrokeMixPassPlane { + .index = 6, + .visible = true, + .has_target = false, + .opacity = 0.9f, + .mvp = glm::mat4(1.0f), + }, + }; + + const auto result = pp::panopainter::execute_legacy_canvas_stroke_mix_pass( + pp::panopainter::LegacyCanvasStrokeMixPassRequest { + .context = "mix-order", + .resolution = glm::vec2(256.0f, 128.0f), + .planes = planes, + .bind_mix_samplers = [&] { + steps.emplace_back("bind-mix"); + }, + .unbind_mix_samplers = [&] { + steps.emplace_back("unbind-mix"); + }, + .setup_plane_shader = [&](int index, const glm::mat4& mvp) { + steps.emplace_back("setup:" + std::to_string(index) + ":" + std::to_string(mvp[0][0])); + }, + .bind_layer_texture = [&](int index) { + steps.emplace_back("bind-layer:" + std::to_string(index)); + }, + .bind_stroke_texture = [&](int index) { + steps.emplace_back("bind-stroke:" + std::to_string(index)); + }, + .bind_mask_texture = [&](int index) { + steps.emplace_back("bind-mask:" + std::to_string(index)); + }, + .draw_plane = [&] { + steps.emplace_back("draw"); + }, + .unbind_mask_texture = [&](int index) { + steps.emplace_back("unbind-mask:" + std::to_string(index)); + }, + .unbind_stroke_texture = [&](int index) { + steps.emplace_back("unbind-stroke:" + std::to_string(index)); + }, + .unbind_layer_texture = [&](int index) { + steps.emplace_back("unbind-layer:" + std::to_string(index)); + }, + }); + + PP_EXPECT(h, result.ok); + PP_EXPECT(h, result.composed_planes == 1); + + const std::vector expected { + "bind-mix", + "setup:4:1", + "bind-layer:4", + "bind-stroke:4", + "bind-mask:4", + "draw", + "unbind-mask:4", + "unbind-stroke:4", + "unbind-layer:4", + "unbind-mix", + }; + PP_EXPECT(h, steps == expected); +} + void legacy_canvas_draw_merge_temporary_erase_helper_preserves_order(pp::tests::Harness& h) { std::vector order; @@ -3576,6 +3658,9 @@ int main() harness.run("plans_canvas_blend_gate_from_persisted_indices", plans_canvas_blend_gate_from_persisted_indices); harness.run("canvas_blend_gate_preserves_legacy_fallbacks", canvas_blend_gate_preserves_legacy_fallbacks); harness.run("canvas_blend_gate_combines_layer_and_stroke_complexity", canvas_blend_gate_combines_layer_and_stroke_complexity); + harness.run( + "legacy_canvas_stroke_mix_executor_preserves_plane_callback_order", + legacy_canvas_stroke_mix_executor_preserves_plane_callback_order); harness.run( "legacy_canvas_draw_merge_temporary_erase_helper_preserves_order", legacy_canvas_draw_merge_temporary_erase_helper_preserves_order);