From fe16a6a270e8e402030ff273261c76b1662cd7cb Mon Sep 17 00:00:00 2001 From: omigamedev Date: Sat, 13 Jun 2026 18:31:42 +0200 Subject: [PATCH] Extract preview main pass orchestration --- docs/modernization/debt.md | 5 + docs/modernization/tasks.md | 8 +- ...y_node_stroke_preview_execution_services.h | 54 +++++++++++ src/node_stroke_preview.cpp | 95 +++++++++++-------- tests/paint_renderer/compositor_tests.cpp | 74 +++++++++++++++ 5 files changed, 197 insertions(+), 39 deletions(-) diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index fb77dfd..ee46ecf 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -18,6 +18,11 @@ agent or engineer to remove them without reconstructing context from chat. ## Recent Reductions +- 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::draw_stroke_immediate()` + now routes the main live-pass setup, frame execution, and preview copy-back + through `execute_legacy_node_stroke_preview_main_live_pass(...)`; the + retained path still owns the concrete frame mutation, sample shader setup, + and final mixer unbind. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge()` now routes the remaining per-layer branch orchestration through `execute_legacy_canvas_draw_merge_layer_composite(...)`; the retained path diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index c420732..523ae85 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -1025,7 +1025,7 @@ Completed Task Log: ### STR-011 - Extract Preview Main Pass Orchestration -Status: Ready +Status: Done Score: no score movement Debt: `DEBT-0036` Scope: `src/node_stroke_preview.cpp`, `tests/paint_renderer/compositor_tests.cpp` @@ -1050,6 +1050,12 @@ ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_composito cmake --build --preset windows-msvc-default --config Debug --target PanoPainter ``` +Completed Task Log: + +| Date | Task | Score | Validation | Commit | +| --- | --- | ---: | --- | --- | +| 2026-06-13 | STR-011 | no score movement | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | `pending` | + ### STR-010 - Extract Remaining Draw Merge Composite Orchestration Status: Done diff --git a/src/legacy_node_stroke_preview_execution_services.h b/src/legacy_node_stroke_preview_execution_services.h index 53d058c..3fdc307 100644 --- a/src/legacy_node_stroke_preview_execution_services.h +++ b/src/legacy_node_stroke_preview_execution_services.h @@ -234,6 +234,60 @@ struct LegacyNodeStrokePreviewPassOrchestrationPlan { bool background_colorize = false; }; +template +struct LegacyNodeStrokePreviewMainLivePassRequestT { + std::function setup_blend_uniforms; + std::function bind_main_pass_textures; + std::function clear_target; + std::function()> compute_frames; + std::function before_frame; + std::function setup_sample_shader; + std::function draw_sample; + std::function copy_pass_result; + std::function finish_main_pass; +}; + +template +[[nodiscard]] inline bool execute_legacy_node_stroke_preview_main_live_pass( + const LegacyNodeStrokePreviewMainLivePassRequestT& request) +{ + if (!request.setup_blend_uniforms || + !request.bind_main_pass_textures || + !request.clear_target || + !request.compute_frames || + !request.before_frame || + !request.setup_sample_shader || + !request.draw_sample || + !request.copy_pass_result || + !request.finish_main_pass) { + return false; + } + + request.setup_blend_uniforms(); + request.bind_main_pass_textures(); + pp::panopainter::execute_legacy_stroke_preview_live_pass( + [&] { + request.clear_target(); + }, + [&] { + return request.compute_frames(); + }, + [&](Frame& frame) { + request.before_frame(frame); + }, + [&](Frame& frame) { + request.setup_sample_shader(frame); + }, + [&](Frame& frame) { + request.draw_sample(frame); + }, + [&] { + request.copy_pass_result(); + }); + request.finish_main_pass(); + return true; +} + struct LegacyNodeStrokePreviewPassOrchestrationRequest { pp::renderer::RenderDeviceFeatures features {}; glm::vec2 preview_size {}; diff --git a/src/node_stroke_preview.cpp b/src/node_stroke_preview.cpp index e730926..57bdcc9 100644 --- a/src/node_stroke_preview.cpp +++ b/src/node_stroke_preview.cpp @@ -779,47 +779,66 @@ void NodeStrokePreview::draw_stroke_immediate() pass_orchestration.composite.uses_mixer); }, .execute_main_pass = [&] { - pp::panopainter::execute_legacy_stroke_preview_live_pass( - [&] { - m_rtt.clear(); - }, - [&] { - return stroke_draw_compute(m_stroke, zoom); - }, - [&](auto& frame) { - if (b->m_tip_mix > 0.f) - { - stroke_draw_mix(xy(frame.m_mixer_rect), zw(frame.m_mixer_rect)); - } + [[maybe_unused]] const bool main_live_ok = + pp::panopainter::execute_legacy_node_stroke_preview_main_live_pass( + pp::panopainter::LegacyNodeStrokePreviewMainLivePassRequestT { + .setup_blend_uniforms = [&] { + pp::panopainter::apply_legacy_stroke_blend_uniforms( + material.stroke_pass.uses_pattern, + b->m_tip_mix, + b->m_tip_wet, + b->m_tip_noise); + }, + .bind_main_pass_textures = [&] { + bind_stroke_preview_main_pass_textures( + *b, + m_tex, + m_rtt_mixer, + copy_stroke_destination, + material.stroke_pass.uses_mixer); + }, + .clear_target = [&] { + m_rtt.clear(); + }, + .compute_frames = [&] { + return stroke_draw_compute(m_stroke, zoom); + }, + .before_frame = [&](auto& frame) { + if (b->m_tip_mix > 0.f) + { + stroke_draw_mix(xy(frame.m_mixer_rect), zw(frame.m_mixer_rect)); + } - frame.col = b->m_blend_mode != 0 || b->m_tip_mix > 0.f ? - glm::vec4 { .7, .4, .1, 1 } : - glm::vec4 { 0, 0, 0, 1 }; - frame.flow = glm::max(frame.flow, m_min_flow); - }, - [&](auto& frame) { - pp::panopainter::use_legacy_stroke_shader(); - pp::panopainter::apply_legacy_stroke_sample_uniforms( - pp::panopainter::LegacyStrokeSampleUniforms { - .color = frame.col, - .alpha = frame.flow, - .opacity = frame.opacity, - }); - }, - [&](auto& frame) { - /*auto rect =*/ stroke_draw_samples(frame.shapes, m_tex, copy_stroke_destination); - }, - [&] { - copy_stroke_preview_framebuffer_to_texture( - m_tex, - size, - stroke_preview_composite_slots::kStroke); + frame.col = b->m_blend_mode != 0 || b->m_tip_mix > 0.f ? + glm::vec4 { .7, .4, .1, 1 } : + glm::vec4 { 0, 0, 0, 1 }; + frame.flow = glm::max(frame.flow, m_min_flow); + }, + .setup_sample_shader = [&](auto& frame) { + pp::panopainter::use_legacy_stroke_shader(); + pp::panopainter::apply_legacy_stroke_sample_uniforms( + pp::panopainter::LegacyStrokeSampleUniforms { + .color = frame.col, + .alpha = frame.flow, + .opacity = frame.opacity, + }); + }, + .draw_sample = [&](auto& frame) { + /*auto rect =*/ stroke_draw_samples(frame.shapes, m_tex, copy_stroke_destination); + }, + .copy_pass_result = [&] { + copy_stroke_preview_framebuffer_to_texture( + m_tex, + size, + stroke_preview_composite_slots::kStroke); + }, + .finish_main_pass = [&] { + set_active_texture_unit(stroke_preview_live_slots::kMixer); + m_rtt_mixer.unbindTexture(); + }, }); }, - .finish_main_pass = [&] { - set_active_texture_unit(stroke_preview_live_slots::kMixer); - m_rtt_mixer.unbindTexture(); - }, + .finish_main_pass = [&] {}, .execute_final_composite = [&] { execute_stroke_preview_final_composite_pass( StrokePreviewCompositePassInputs { diff --git a/tests/paint_renderer/compositor_tests.cpp b/tests/paint_renderer/compositor_tests.cpp index 49c56e5..f39cff3 100644 --- a/tests/paint_renderer/compositor_tests.cpp +++ b/tests/paint_renderer/compositor_tests.cpp @@ -2540,6 +2540,77 @@ void legacy_node_stroke_preview_pass_sequence_preserves_dual_main_and_composite_ PP_EXPECT(h, !missing_required); } +void legacy_node_stroke_preview_main_live_pass_preserves_order(pp::tests::Harness& h) +{ + struct Frame { + int id = 0; + }; + + std::vector steps; + const auto run = [&](std::vector frames) { + steps.clear(); + const bool ok = pp::panopainter::execute_legacy_node_stroke_preview_main_live_pass( + pp::panopainter::LegacyNodeStrokePreviewMainLivePassRequestT { + .setup_blend_uniforms = [&] { + steps.emplace_back("setup_blend"); + }, + .bind_main_pass_textures = [&] { + steps.emplace_back("bind_textures"); + }, + .clear_target = [&] { + steps.emplace_back("clear"); + }, + .compute_frames = [&] { + steps.emplace_back("compute"); + return frames; + }, + .before_frame = [&](Frame& frame) { + steps.emplace_back("before:" + std::to_string(frame.id)); + }, + .setup_sample_shader = [&](Frame& frame) { + steps.emplace_back("setup:" + std::to_string(frame.id)); + }, + .draw_sample = [&](Frame& frame) { + steps.emplace_back("draw:" + std::to_string(frame.id)); + }, + .copy_pass_result = [&] { + steps.emplace_back("copy"); + }, + .finish_main_pass = [&] { + steps.emplace_back("finish"); + }, + }); + PP_EXPECT(h, ok); + }; + + run({}); + PP_EXPECT(h, steps == std::vector { + "setup_blend", + "bind_textures", + "compute", + "clear", + "copy", + "finish", + }); + + run({ Frame { .id = 7 } }); + PP_EXPECT(h, steps == std::vector { + "setup_blend", + "bind_textures", + "compute", + "clear", + "before:7", + "setup:7", + "draw:7", + "copy", + "finish", + }); + + const bool invalid = pp::panopainter::execute_legacy_node_stroke_preview_main_live_pass( + pp::panopainter::LegacyNodeStrokePreviewMainLivePassRequestT {}); + PP_EXPECT(h, !invalid); +} + void legacy_node_stroke_preview_stroke_setup_plan_preserves_curve_and_dual_inputs(pp::tests::Harness& h) { const auto plan = pp::panopainter::plan_legacy_node_stroke_preview_stroke_setup( @@ -3130,6 +3201,9 @@ int main() harness.run( "legacy_node_stroke_preview_pass_sequence_preserves_dual_main_and_composite_order", legacy_node_stroke_preview_pass_sequence_preserves_dual_main_and_composite_order); + harness.run( + "legacy_node_stroke_preview_main_live_pass_preserves_order", + legacy_node_stroke_preview_main_live_pass_preserves_order); harness.run( "legacy_node_stroke_preview_stroke_setup_plan_preserves_curve_and_dual_inputs", legacy_node_stroke_preview_stroke_setup_plan_preserves_curve_and_dual_inputs);