Extract main stroke live-pass orchestration

This commit is contained in:
2026-06-13 18:44:36 +02:00
parent 93a5d1ac07
commit 58885187ba
5 changed files with 119 additions and 39 deletions

View File

@@ -18,6 +18,10 @@ agent or engineer to remove them without reconstructing context from chat.
## Recent Reductions ## Recent Reductions
- 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw()` now
routes its main live-pass bind, 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: RND-005 was completed. `desktop-gpu` now has a second - 2026-06-13: RND-005 was completed. `desktop-gpu` now has a second
deterministic OpenGL readback fixture and the CTest registration uses the deterministic OpenGL readback fixture and the CTest registration uses the
configured target path so the gate actually runs. configured target path so the gate actually runs.

View File

@@ -575,6 +575,10 @@ Done Checks:
Progress Notes: Progress Notes:
- 2026-06-13: `Canvas::stroke_draw()` now routes its main live-pass bind,
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()` live-pass sampler wiring now reuses a - 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` retained helper builder, and the stroke execution tests cover it. `STR-004`
is now done after the final pad-destination helper extraction and tracker is now done after the final pad-destination helper extraction and tracker

View File

@@ -780,51 +780,67 @@ void Canvas::stroke_draw()
[&] { [&] {
m_mixer.unbindTexture(); m_mixer.unbindTexture();
}); });
pp::panopainter::bind_legacy_canvas_stroke_sampler_inputs(
live_pass_sampler_bindings,
live_pass_sampler_dispatch);
pp::panopainter::bind_legacy_canvas_stroke_texture_inputs(
main_pass_texture_bindings,
main_pass_texture_dispatch);
auto frames = stroke_draw_compute(*m_current_stroke); auto frames = stroke_draw_compute(*m_current_stroke);
std::array<glm::vec4, 6> box_face = SIXPLETTE(glm::vec4(m_size, 0, 0)); std::array<glm::vec4, 6> box_face = SIXPLETTE(glm::vec4(m_size, 0, 0));
std::array<bool, 6> box_dirty = SIXPLETTE(false); std::array<bool, 6> box_dirty = SIXPLETTE(false);
const std::array<bool, 6> include_main_dirty = SIXPLETTE(true); const std::array<bool, 6> include_main_dirty = SIXPLETTE(true);
glm::vec4 pad_color; glm::vec4 pad_color;
pp::panopainter::execute_legacy_canvas_stroke_main_pass_frame_callbacks( [[maybe_unused]] const auto main_pass_result =
frames, pp::panopainter::execute_legacy_canvas_stroke_main_pass(
stroke_extent, pp::panopainter::LegacyCanvasStrokeMainPassExecutionRequest {
std::span<glm::vec4>(m_dirty_box), .context = "Canvas::stroke_draw",
std::span<glm::vec4>(box_face), .bind_samplers = [&] {
std::span<const bool>(include_main_dirty), pp::panopainter::bind_legacy_canvas_stroke_sampler_inputs(
[&](auto& f) { live_pass_sampler_bindings,
if (brush->m_tip_mix > 0.f) live_pass_sampler_dispatch);
{ },
stroke_draw_mix(xy(f.m_mixer_rect), zw(f.m_mixer_rect)); .bind_textures = [&] {
} pp::panopainter::bind_legacy_canvas_stroke_texture_inputs(
pad_color = f.col; main_pass_texture_bindings,
}, main_pass_texture_dispatch);
[&](auto&, int i, auto&) { },
m_dirty_face[i] = true; .execute_frame_pass = [&] {
box_dirty[i] = true; pp::panopainter::execute_legacy_canvas_stroke_main_pass_frame_callbacks(
}, frames,
[&](auto& f, int i, auto& P) { stroke_extent,
pp::panopainter::use_legacy_stroke_shader(); std::span<glm::vec4>(m_dirty_box),
pp::panopainter::apply_legacy_stroke_sample_uniforms( std::span<glm::vec4>(box_face),
pp::panopainter::LegacyStrokeSampleUniforms { std::span<const bool>(include_main_dirty),
.color = f.col, [&](auto& f) {
.alpha = f.flow, if (brush->m_tip_mix > 0.f)
.opacity = f.opacity, {
}); stroke_draw_mix(xy(f.m_mixer_rect), zw(f.m_mixer_rect));
return stroke_draw_samples(i, P, copy_stroke_destination); }
}, pad_color = f.col;
m_tmp); },
[&](auto&, int i, auto&) {
pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs( m_dirty_face[i] = true;
main_pass_texture_unbindings, box_dirty[i] = true;
main_pass_texture_dispatch); },
[&](auto& f, int i, auto& P) {
pp::panopainter::use_legacy_stroke_shader();
pp::panopainter::apply_legacy_stroke_sample_uniforms(
pp::panopainter::LegacyStrokeSampleUniforms {
.color = f.col,
.alpha = f.flow,
.opacity = f.opacity,
});
return stroke_draw_samples(i, P, copy_stroke_destination);
},
m_tmp);
},
.unbind_textures = [&] {
pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs(
main_pass_texture_unbindings,
main_pass_texture_dispatch);
},
.unbind_samplers = [&] {
pp::panopainter::unbind_legacy_canvas_stroke_sampler_inputs(
live_pass_sampler_bindings,
live_pass_sampler_dispatch);
},
});
// pad stroke // pad stroke
// In order to mitigate color bleeding at the edge of shapes in transparent layers // In order to mitigate color bleeding at the edge of shapes in transparent layers

View File

@@ -151,6 +151,15 @@ struct LegacyCanvasStrokeSamplerDispatch {
std::function<void()> unbind_mixer_sampler; std::function<void()> unbind_mixer_sampler;
}; };
struct LegacyCanvasStrokeMainPassExecutionRequest {
std::string_view context;
std::function<void()> bind_samplers;
std::function<void()> bind_textures;
std::function<void()> execute_frame_pass;
std::function<void()> unbind_textures;
std::function<void()> unbind_samplers;
};
[[nodiscard]] inline LegacyCanvasStrokeSamplerDispatch make_legacy_canvas_stroke_live_pass_sampler_dispatch( [[nodiscard]] inline LegacyCanvasStrokeSamplerDispatch make_legacy_canvas_stroke_live_pass_sampler_dispatch(
std::function<void(int)> bind_brush_tip_sampler, std::function<void(int)> bind_brush_tip_sampler,
std::function<void()> unbind_brush_tip_sampler, std::function<void()> unbind_brush_tip_sampler,
@@ -195,6 +204,25 @@ struct LegacyCanvasStrokeSamplerDispatch {
}; };
} }
[[nodiscard]] inline bool execute_legacy_canvas_stroke_main_pass(
const LegacyCanvasStrokeMainPassExecutionRequest& request)
{
if (!request.bind_samplers ||
!request.bind_textures ||
!request.execute_frame_pass ||
!request.unbind_textures ||
!request.unbind_samplers) {
return false;
}
request.bind_samplers();
request.bind_textures();
request.execute_frame_pass();
request.unbind_textures();
request.unbind_samplers();
return true;
}
struct LegacyCanvasStrokeFaceDirtyRequest { struct LegacyCanvasStrokeFaceDirtyRequest {
pp::renderer::Extent2D extent {}; pp::renderer::Extent2D extent {};
glm::vec4 previous_accumulated_dirty_box {}; glm::vec4 previous_accumulated_dirty_box {};

View File

@@ -1437,6 +1437,31 @@ void retained_stroke_main_pass_frame_callbacks_preserve_order(pp::tests::Harness
PP_EXPECT(h, events == expected_events); PP_EXPECT(h, events == expected_events);
} }
void retained_stroke_main_pass_execution_preserves_bind_and_unbind_order(pp::tests::Harness& h)
{
std::vector<std::string> events;
const auto ok = pp::panopainter::execute_legacy_canvas_stroke_main_pass(
pp::panopainter::LegacyCanvasStrokeMainPassExecutionRequest {
.context = "test",
.bind_samplers = [&] { events.emplace_back("bind-samplers"); },
.bind_textures = [&] { events.emplace_back("bind-textures"); },
.execute_frame_pass = [&] { events.emplace_back("execute"); },
.unbind_textures = [&] { events.emplace_back("unbind-textures"); },
.unbind_samplers = [&] { events.emplace_back("unbind-samplers"); },
});
const std::vector<std::string> expected_events {
"bind-samplers",
"bind-textures",
"execute",
"unbind-textures",
"unbind-samplers",
};
PP_EXPECT(h, ok);
PP_EXPECT(h, events == expected_events);
}
void retained_stroke_pad_face_callbacks_preserve_order(pp::tests::Harness& h) void retained_stroke_pad_face_callbacks_preserve_order(pp::tests::Harness& h)
{ {
const std::array<bool, 3> dirty_faces { true, false, true }; const std::array<bool, 3> dirty_faces { true, false, true };
@@ -1781,6 +1806,9 @@ int main()
harness.run( harness.run(
"retained_stroke_main_pass_frame_callbacks_preserve_order", "retained_stroke_main_pass_frame_callbacks_preserve_order",
retained_stroke_main_pass_frame_callbacks_preserve_order); retained_stroke_main_pass_frame_callbacks_preserve_order);
harness.run(
"retained_stroke_main_pass_execution_preserves_bind_and_unbind_order",
retained_stroke_main_pass_execution_preserves_bind_and_unbind_order);
harness.run( harness.run(
"retained_stroke_live_pass_sampler_dispatch_helper_builds_expected_callback_wiring", "retained_stroke_live_pass_sampler_dispatch_helper_builds_expected_callback_wiring",
retained_stroke_live_pass_sampler_dispatch_helper_builds_expected_callback_wiring); retained_stroke_live_pass_sampler_dispatch_helper_builds_expected_callback_wiring);