Narrow retained stroke preview execution

This commit is contained in:
2026-06-13 10:01:19 +02:00
parent 3f98e4e0c5
commit 7b99dabb33
4 changed files with 104 additions and 27 deletions

View File

@@ -18,6 +18,12 @@ agent or engineer to remove them without reconstructing context from chat.
## Recent Reductions ## 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` - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::stroke_draw_compute`
now routes preview quad frame planning through now routes preview quad frame planning through
`legacy_canvas_stroke_execution_services.h`. Preview stroke sampling, mixer `legacy_canvas_stroke_execution_services.h`. Preview stroke sampling, mixer

View File

@@ -3082,6 +3082,13 @@ Results:
- `NodeStrokePreview::stroke_draw_compute` now shares the retained stroke - `NodeStrokePreview::stroke_draw_compute` now shares the retained stroke
execution helper for preview quad frame planning. Preview sample execution, execution helper for preview quad frame planning. Preview sample execution,
mixer passes, texture binding, and final draw ordering remain retained. 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 - `Canvas::stroke_draw` pad-region planning now shares the retained stroke
execution helper wrapping `pp_paint_renderer`, while pad color selection, execution helper wrapping `pp_paint_renderer`, while pad color selection,
dirty-face iteration, framebuffer copies, quad upload, and draw execution dirty-face iteration, framebuffer copies, quad upload, and draw execution

View File

@@ -96,6 +96,25 @@ void apply_stroke_preview_capability(std::uint32_t state, bool enabled)
pp::legacy::ui_gl::set_capability(state, enabled, "NodeStrokePreview"); pp::legacy::ui_gl::set_capability(state, enabled, "NodeStrokePreview");
} }
template <typename Frames, typename BeforeFrame, typename DrawSample>
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 }; std::atomic_int NodeStrokePreview::s_instances{ 0 };
@@ -456,16 +475,14 @@ void NodeStrokePreview::draw_stroke_immediate()
dual_brush->m_tip_texture->bind() : dual_brush->m_tip_texture->bind() :
unbind_texture_2d(); unbind_texture_2d();
auto frames_dual = stroke_draw_compute(m_dual_stroke, zoom); auto frames_dual = stroke_draw_compute(m_dual_stroke, zoom);
for (auto& f : frames_dual) execute_stroke_preview_frames(
{ frames_dual,
pp::panopainter::apply_legacy_stroke_sample_uniforms( [](auto& frame) {
pp::panopainter::LegacyStrokeSampleUniforms { frame.col = { 0, 0, 0, 1 };
.color = { 0, 0, 0, 1 }, },
.alpha = f.flow, [&](auto& frame) {
.opacity = f.opacity, /*auto rect =*/ stroke_draw_samples(frame.shapes, m_tex_dual, copy_stroke_destination);
}); });
/*auto rect =*/ stroke_draw_samples(f.shapes, m_tex_dual, copy_stroke_destination);
}
// copy raw stroke to tex // copy raw stroke to tex
set_active_texture_unit(1U); 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(); preview_composite_plan.uses_mixer ? m_rtt_mixer.bindTexture() : unbind_texture_2d();
auto frames = stroke_draw_compute(m_stroke, zoom); auto frames = stroke_draw_compute(m_stroke, zoom);
m_rtt.clear(); m_rtt.clear();
for (auto& f : frames) execute_stroke_preview_frames(
{ frames,
if (b->m_tip_mix > 0.f) [&](auto& frame) {
{ if (b->m_tip_mix > 0.f)
stroke_draw_mix(xy(f.m_mixer_rect), zw(f.m_mixer_rect)); {
} stroke_draw_mix(xy(frame.m_mixer_rect), zw(frame.m_mixer_rect));
}
pp::panopainter::use_legacy_stroke_shader(); frame.col = b->m_blend_mode != 0 || b->m_tip_mix > 0.f ?
pp::panopainter::apply_legacy_stroke_sample_uniforms( glm::vec4 { .7, .4, .1, 1 } :
pp::panopainter::LegacyStrokeSampleUniforms { glm::vec4 { 0, 0, 0, 1 };
.color = b->m_blend_mode != 0 || b->m_tip_mix > 0.f ? frame.flow = glm::max(frame.flow, m_min_flow);
glm::vec4 { .7, .4, .1, 1 } /*f.col*/ : },
glm::vec4 { 0, 0, 0, 1 } /*f.col*/, [&](auto& frame) {
.alpha = glm::max(f.flow, m_min_flow), /*auto rect =*/ stroke_draw_samples(frame.shapes, m_tex, copy_stroke_destination);
.opacity = f.opacity, });
});
/*auto rect =*/ stroke_draw_samples(f.shapes, m_tex, copy_stroke_destination);
}
set_active_texture_unit(3U); set_active_texture_unit(3U);
m_rtt_mixer.unbindTexture(); m_rtt_mixer.unbindTexture();

View File

@@ -1,5 +1,6 @@
#include "assets/image_pixels.h" #include "assets/image_pixels.h"
#include "legacy_canvas_stroke_commit_services.h" #include "legacy_canvas_stroke_commit_services.h"
#include "legacy_node_stroke_preview_execution_services.h"
#include "paint_renderer/compositor.h" #include "paint_renderer/compositor.h"
#include "renderer_api/recording_renderer.h" #include "renderer_api/recording_renderer.h"
#include "test_harness.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)); 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) void plans_canvas_blend_gate_from_persisted_indices(pp::tests::Harness& h)
{ {
const std::vector<int> normal_layers { 0, 0, 0 }; const std::vector<int> normal_layers { 0, 0, 0 };
@@ -2450,6 +2493,12 @@ int main()
harness.run( harness.run(
"plans_stroke_preview_composite_with_all_retained_inputs", "plans_stroke_preview_composite_with_all_retained_inputs",
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("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_preserves_legacy_fallbacks", canvas_blend_gate_preserves_legacy_fallbacks);
harness.run("plans_canvas_stroke_feedback_paths", plans_canvas_stroke_feedback_paths); harness.run("plans_canvas_stroke_feedback_paths", plans_canvas_stroke_feedback_paths);