diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 9590867..7993701 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -18,6 +18,10 @@ agent or engineer to remove them without reconstructing context from chat. ## Recent Reductions +- 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw_mix()` now + routes retained mix-pass request wiring through + `make_legacy_canvas_stroke_mix_pass_request(...)`; the legacy path still + owns the concrete framebuffer setup and per-plane GL callbacks. - 2026-06-13: `PLT-004` was narrowed again. `LegacyPlatformServices::display_file()` and `share_file()` now delegate Apple file actions through `AppleDocumentPlatformServices`, leaving the legacy adapter with only the diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index 5d68e17..d402cdf 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -588,6 +588,9 @@ Progress Notes: execute, and unbind sequence through `execute_legacy_canvas_stroke_main_pass(...)`; the retained adapter still owns the concrete sampler and texture callbacks. +- 2026-06-13: `Canvas::stroke_draw_mix()` now routes retained mix-pass request + wiring through `make_legacy_canvas_stroke_mix_pass_request(...)`; the legacy + path still owns the concrete framebuffer setup and per-plane GL callbacks. - 2026-06-13: `Canvas::stroke_draw()` live-pass sampler wiring now reuses a retained helper builder, and the stroke execution tests cover it. `STR-004` is now done after the final pad-destination helper extraction and tracker diff --git a/src/canvas.cpp b/src/canvas.cpp index 4fd65fc..d3e51e0 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -420,61 +420,60 @@ void Canvas::stroke_draw_mix(const glm::vec2& bb_min, const glm::vec2& bb_sz) [&] { m_mixer.unbindFramebuffer(); }, - pp::panopainter::LegacyCanvasStrokeMixPassRequest { - .context = "Canvas::stroke_draw_mix", - .resolution = m_size, - .planes = mix_planes, - .bind_mix_samplers = [&] { - m_sampler.bind(0); - m_sampler.bind(1); - m_sampler.bind(2); - }, - .unbind_mix_samplers = [&] { - m_sampler.unbind(); - }, - .setup_plane_shader = [&](int plane_index, const glm::mat4& plane_mvp_z) { - (void)plane_index; - pp::panopainter::setup_legacy_stroke_composite_shader( - pp::panopainter::LegacyStrokeCompositeUniforms { - .resolution = m_size, - .mvp = plane_mvp_z, - .pattern_texture_slot = 3, - .layer_alpha = 1.0f, - .alpha_lock = false, /*current_layer.m_alpha_locked*/ - .mask_enabled = false, /*m_smask_active*/ - .use_fragcoord = false, - .blend_mode = b->m_blend_mode, - .use_dual = false, - .use_pattern = false, - }); - }, - .bind_layer_texture = [&](int plane_index) { - set_active_texture_unit(0); - current_layer.rtt(plane_index).bindTexture(); - }, - .bind_stroke_texture = [&](int plane_index) { - set_active_texture_unit(1); - m_tmp[plane_index].bindTexture(); - }, - .bind_mask_texture = [&](int plane_index) { - set_active_texture_unit(2); - m_smask.rtt(plane_index).bindTexture(); - }, - .draw_plane = [&] { - m_node->m_face_plane.draw_fill(); - }, - .unbind_mask_texture = [&](int plane_index) { - set_active_texture_unit(2); - m_smask.rtt(plane_index).unbindTexture(); - }, - .unbind_stroke_texture = [&](int plane_index) { - set_active_texture_unit(1); - m_tmp[plane_index].unbindTexture(); - }, - .unbind_layer_texture = [&](int plane_index) { - set_active_texture_unit(0); - current_layer.rtt(plane_index).unbindTexture(); - }, + pp::panopainter::make_legacy_canvas_stroke_mix_pass_request( + "Canvas::stroke_draw_mix", + m_size, + mix_planes, + [&] { + m_sampler.bind(0); + m_sampler.bind(1); + m_sampler.bind(2); + }, + [&] { + m_sampler.unbind(); + }, + [&](int plane_index, const glm::mat4& plane_mvp_z) { + (void)plane_index; + pp::panopainter::setup_legacy_stroke_composite_shader( + pp::panopainter::LegacyStrokeCompositeUniforms { + .resolution = m_size, + .mvp = plane_mvp_z, + .pattern_texture_slot = 3, + .layer_alpha = 1.0f, + .alpha_lock = false, /*current_layer.m_alpha_locked*/ + .mask_enabled = false, /*m_smask_active*/ + .use_fragcoord = false, + .blend_mode = b->m_blend_mode, + .use_dual = false, + .use_pattern = false, + }); + }, + [&](int plane_index) { + set_active_texture_unit(0); + current_layer.rtt(plane_index).bindTexture(); + }, + [&](int plane_index) { + set_active_texture_unit(1); + m_tmp[plane_index].bindTexture(); + }, + [&](int plane_index) { + set_active_texture_unit(2); + m_smask.rtt(plane_index).bindTexture(); + }, + [&] { + m_node->m_face_plane.draw_fill(); + }, + [&](int plane_index) { + set_active_texture_unit(2); + m_smask.rtt(plane_index).unbindTexture(); + }, + [&](int plane_index) { + set_active_texture_unit(1); + m_tmp[plane_index].unbindTexture(); + }, + [&](int plane_index) { + set_active_texture_unit(0); + current_layer.rtt(plane_index).unbindTexture(); }); gl.restore(); diff --git a/src/legacy_canvas_stroke_execution_services.h b/src/legacy_canvas_stroke_execution_services.h index 03da4a9..0e4600a 100644 --- a/src/legacy_canvas_stroke_execution_services.h +++ b/src/legacy_canvas_stroke_execution_services.h @@ -340,6 +340,38 @@ struct LegacyCanvasStrokeMixPassResult { std::size_t composed_planes = 0; }; +[[nodiscard]] inline LegacyCanvasStrokeMixPassRequest make_legacy_canvas_stroke_mix_pass_request( + std::string_view context, + glm::vec2 resolution, + std::span planes, + std::function bind_mix_samplers, + std::function unbind_mix_samplers, + std::function setup_plane_shader, + std::function bind_layer_texture, + std::function bind_stroke_texture, + std::function bind_mask_texture, + std::function draw_plane, + std::function unbind_mask_texture, + std::function unbind_stroke_texture, + std::function unbind_layer_texture) +{ + return LegacyCanvasStrokeMixPassRequest { + .context = context, + .resolution = resolution, + .planes = planes, + .bind_mix_samplers = std::move(bind_mix_samplers), + .unbind_mix_samplers = std::move(unbind_mix_samplers), + .setup_plane_shader = std::move(setup_plane_shader), + .bind_layer_texture = std::move(bind_layer_texture), + .bind_stroke_texture = std::move(bind_stroke_texture), + .bind_mask_texture = std::move(bind_mask_texture), + .draw_plane = std::move(draw_plane), + .unbind_mask_texture = std::move(unbind_mask_texture), + .unbind_stroke_texture = std::move(unbind_stroke_texture), + .unbind_layer_texture = std::move(unbind_layer_texture), + }; +} + struct LegacyCanvasStrokeDualPassRequest { std::string_view context; std::function bind_brush_tip; diff --git a/tests/paint_renderer/stroke_execution_tests.cpp b/tests/paint_renderer/stroke_execution_tests.cpp index fc8b4b9..74cc3da 100644 --- a/tests/paint_renderer/stroke_execution_tests.cpp +++ b/tests/paint_renderer/stroke_execution_tests.cpp @@ -1807,6 +1807,84 @@ void retained_stroke_mix_pass_rejects_incomplete_requests(pp::tests::Harness& h) PP_EXPECT(h, events.empty()); } +void retained_stroke_mix_pass_request_builder_preserves_callback_wiring(pp::tests::Harness& h) +{ + const std::array planes { + LegacyCanvasStrokeMixPassPlane { .index = 3, .visible = true, .has_target = true, .opacity = 0.75F }, + }; + + std::vector events; + const auto request = pp::panopainter::make_legacy_canvas_stroke_mix_pass_request( + "mix-test", + glm::vec2(128.0F, 64.0F), + planes, + [&] { events.emplace_back("bind-samplers"); }, + [&] { events.emplace_back("unbind-samplers"); }, + [&](int plane_index, const glm::mat4&) { + events.emplace_back("setup:" + std::to_string(plane_index)); + }, + [&](int plane_index) { + events.emplace_back("bind-layer:" + std::to_string(plane_index)); + }, + [&](int plane_index) { + events.emplace_back("bind-stroke:" + std::to_string(plane_index)); + }, + [&](int plane_index) { + events.emplace_back("bind-mask:" + std::to_string(plane_index)); + }, + [&] { events.emplace_back("draw"); }, + [&](int plane_index) { + events.emplace_back("unbind-mask:" + std::to_string(plane_index)); + }, + [&](int plane_index) { + events.emplace_back("unbind-stroke:" + std::to_string(plane_index)); + }, + [&](int plane_index) { + events.emplace_back("unbind-layer:" + std::to_string(plane_index)); + }); + + PP_EXPECT(h, request.context == "mix-test"); + PP_EXPECT(h, request.resolution.x == 128.0F); + PP_EXPECT(h, request.resolution.y == 64.0F); + PP_EXPECT(h, request.planes.size() == 1U); + PP_EXPECT(h, request.planes.front().index == 3); + PP_EXPECT(h, request.bind_mix_samplers); + PP_EXPECT(h, request.unbind_mix_samplers); + PP_EXPECT(h, request.setup_plane_shader); + PP_EXPECT(h, request.bind_layer_texture); + PP_EXPECT(h, request.bind_stroke_texture); + PP_EXPECT(h, request.bind_mask_texture); + PP_EXPECT(h, request.draw_plane); + PP_EXPECT(h, request.unbind_mask_texture); + PP_EXPECT(h, request.unbind_stroke_texture); + PP_EXPECT(h, request.unbind_layer_texture); + + request.bind_mix_samplers(); + request.setup_plane_shader(3, glm::mat4(1.0F)); + request.bind_layer_texture(3); + request.bind_stroke_texture(3); + request.bind_mask_texture(3); + request.draw_plane(); + request.unbind_mask_texture(3); + request.unbind_stroke_texture(3); + request.unbind_layer_texture(3); + request.unbind_mix_samplers(); + + const std::vector expected_events { + "bind-samplers", + "setup:3", + "bind-layer:3", + "bind-stroke:3", + "bind-mask:3", + "draw", + "unbind-mask:3", + "unbind-stroke:3", + "unbind-layer:3", + "unbind-samplers", + }; + PP_EXPECT(h, events == expected_events); +} + } // namespace int main() @@ -1911,5 +1989,8 @@ int main() harness.run( "retained_stroke_mix_pass_rejects_incomplete_requests", retained_stroke_mix_pass_rejects_incomplete_requests); + harness.run( + "retained_stroke_mix_pass_request_builder_preserves_callback_wiring", + retained_stroke_mix_pass_request_builder_preserves_callback_wiring); return harness.finish(); }