diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index d01ed41..177ecd5 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -18,6 +18,12 @@ agent or engineer to remove them without reconstructing context from chat. ## Recent Reductions +- 2026-06-13: DEBT-0036 preview-adapter hardening grew again. + `pp_paint_renderer_compositor_tests` now covers + `legacy_node_stroke_preview_execution_services.h` framebuffer-feedback + fallback clamping plus retained preview composite slot intent, while live + preview sample execution, mixer passes, texture binding, and final draw + ordering remain retained. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::stroke_draw_compute` now routes preview quad frame planning through `legacy_canvas_stroke_execution_services.h`. Preview stroke sampling, mixer diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 460c52e..327b537 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -3082,6 +3082,13 @@ Results: - `NodeStrokePreview::stroke_draw_compute` now shares the retained stroke execution helper for preview quad frame planning. Preview sample execution, mixer passes, texture binding, and final draw ordering remain retained. +- `NodeStrokePreview` preview stroke frame sequencing now shares one retained + local executor for dual and main preview sample traversal, and + `pp_paint_renderer_compositor_tests` now covers + `legacy_node_stroke_preview_execution_services.h` feedback fallback clamping + plus retained preview composite slot intent. Preview texture binding, + framebuffer copies, mixer ownership, and final OpenGL draw ordering remain + retained. - `Canvas::stroke_draw` pad-region planning now shares the retained stroke execution helper wrapping `pp_paint_renderer`, while pad color selection, dirty-face iteration, framebuffer copies, quad upload, and draw execution diff --git a/src/node_stroke_preview.cpp b/src/node_stroke_preview.cpp index bc8aa56..c399602 100644 --- a/src/node_stroke_preview.cpp +++ b/src/node_stroke_preview.cpp @@ -96,6 +96,25 @@ void apply_stroke_preview_capability(std::uint32_t state, bool enabled) pp::legacy::ui_gl::set_capability(state, enabled, "NodeStrokePreview"); } +template +void execute_stroke_preview_frames( + Frames& frames, + BeforeFrame&& before_frame, + DrawSample&& draw_sample) +{ + for (auto& frame : frames) { + before_frame(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(frame); + } +} + } std::atomic_int NodeStrokePreview::s_instances{ 0 }; @@ -456,16 +475,14 @@ void NodeStrokePreview::draw_stroke_immediate() dual_brush->m_tip_texture->bind() : unbind_texture_2d(); auto frames_dual = stroke_draw_compute(m_dual_stroke, zoom); - for (auto& f : frames_dual) - { - pp::panopainter::apply_legacy_stroke_sample_uniforms( - pp::panopainter::LegacyStrokeSampleUniforms { - .color = { 0, 0, 0, 1 }, - .alpha = f.flow, - .opacity = f.opacity, - }); - /*auto rect =*/ stroke_draw_samples(f.shapes, m_tex_dual, copy_stroke_destination); - } + execute_stroke_preview_frames( + frames_dual, + [](auto& frame) { + frame.col = { 0, 0, 0, 1 }; + }, + [&](auto& frame) { + /*auto rect =*/ stroke_draw_samples(frame.shapes, m_tex_dual, copy_stroke_destination); + }); // copy raw stroke to tex set_active_texture_unit(1U); @@ -536,24 +553,22 @@ void NodeStrokePreview::draw_stroke_immediate() preview_composite_plan.uses_mixer ? m_rtt_mixer.bindTexture() : unbind_texture_2d(); auto frames = stroke_draw_compute(m_stroke, zoom); m_rtt.clear(); - for (auto& f : frames) - { - if (b->m_tip_mix > 0.f) - { - stroke_draw_mix(xy(f.m_mixer_rect), zw(f.m_mixer_rect)); - } + execute_stroke_preview_frames( + frames, + [&](auto& frame) { + if (b->m_tip_mix > 0.f) + { + stroke_draw_mix(xy(frame.m_mixer_rect), zw(frame.m_mixer_rect)); + } - pp::panopainter::use_legacy_stroke_shader(); - pp::panopainter::apply_legacy_stroke_sample_uniforms( - pp::panopainter::LegacyStrokeSampleUniforms { - .color = b->m_blend_mode != 0 || b->m_tip_mix > 0.f ? - glm::vec4 { .7, .4, .1, 1 } /*f.col*/ : - glm::vec4 { 0, 0, 0, 1 } /*f.col*/, - .alpha = glm::max(f.flow, m_min_flow), - .opacity = f.opacity, - }); - /*auto rect =*/ stroke_draw_samples(f.shapes, m_tex, copy_stroke_destination); - } + 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) { + /*auto rect =*/ stroke_draw_samples(frame.shapes, m_tex, copy_stroke_destination); + }); set_active_texture_unit(3U); m_rtt_mixer.unbindTexture(); diff --git a/tests/paint_renderer/compositor_tests.cpp b/tests/paint_renderer/compositor_tests.cpp index d9e776b..488ce54 100644 --- a/tests/paint_renderer/compositor_tests.cpp +++ b/tests/paint_renderer/compositor_tests.cpp @@ -1,5 +1,6 @@ #include "assets/image_pixels.h" #include "legacy_canvas_stroke_commit_services.h" +#include "legacy_node_stroke_preview_execution_services.h" #include "paint_renderer/compositor.h" #include "renderer_api/recording_renderer.h" #include "test_harness.h" @@ -2011,6 +2012,48 @@ void plans_stroke_preview_composite_with_all_retained_inputs(pp::tests::Harness& PP_EXPECT(h, has_preview_texture_slot(plan, StrokePreviewTextureRole::mixer, 3)); } +void legacy_node_stroke_preview_feedback_adapter_clamps_invalid_extent(pp::tests::Harness& h) +{ + const auto fetch = pp::panopainter::plan_legacy_node_stroke_preview_feedback( + RenderDeviceFeatures { .framebuffer_fetch = true }, + 32, + 16); + PP_EXPECT(h, fetch.path == StrokeCompositePath::framebuffer_fetch); + PP_EXPECT(h, fetch.reads_destination_color); + PP_EXPECT(h, !fetch.requires_auxiliary_texture); + PP_EXPECT(h, !fetch.compatibility_fallback); + + const auto fallback = pp::panopainter::plan_legacy_node_stroke_preview_feedback( + RenderDeviceFeatures { .texture_copy = true }, + -32, + 16); + PP_EXPECT(h, fallback.path == StrokeCompositePath::ping_pong_textures); + PP_EXPECT(h, !fallback.reads_destination_color); + PP_EXPECT(h, fallback.requires_auxiliary_texture); + PP_EXPECT(h, !fallback.requires_texture_copy); + PP_EXPECT(h, !fallback.requires_render_target_blit); + PP_EXPECT(h, fallback.compatibility_fallback); +} + +void legacy_node_stroke_preview_composite_adapter_preserves_retained_inputs(pp::tests::Harness& h) +{ + const auto plan = pp::panopainter::plan_legacy_node_stroke_preview_composite( + true, + true, + true); + + expect_preview_sequence(h, plan); + PP_EXPECT(h, plan.uses_mixer); + PP_EXPECT(h, plan.uses_dual); + PP_EXPECT(h, plan.uses_pattern); + PP_EXPECT(h, plan.texture_slot_count == 5U); + PP_EXPECT(h, has_preview_texture_slot(plan, StrokePreviewTextureRole::background, 0)); + PP_EXPECT(h, has_preview_texture_slot(plan, StrokePreviewTextureRole::stroke, 1)); + PP_EXPECT(h, has_preview_texture_slot(plan, StrokePreviewTextureRole::dual, 3)); + PP_EXPECT(h, has_preview_texture_slot(plan, StrokePreviewTextureRole::pattern, 4)); + PP_EXPECT(h, has_preview_texture_slot(plan, StrokePreviewTextureRole::mixer, 3)); +} + void plans_canvas_blend_gate_from_persisted_indices(pp::tests::Harness& h) { const std::vector normal_layers { 0, 0, 0 }; @@ -2450,6 +2493,12 @@ int main() harness.run( "plans_stroke_preview_composite_with_all_retained_inputs", plans_stroke_preview_composite_with_all_retained_inputs); + harness.run( + "legacy_node_stroke_preview_feedback_adapter_clamps_invalid_extent", + legacy_node_stroke_preview_feedback_adapter_clamps_invalid_extent); + harness.run( + "legacy_node_stroke_preview_composite_adapter_preserves_retained_inputs", + legacy_node_stroke_preview_composite_adapter_preserves_retained_inputs); 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("plans_canvas_stroke_feedback_paths", plans_canvas_stroke_feedback_paths);