Share retained stroke sample dirty tracking

This commit is contained in:
2026-06-13 10:01:56 +02:00
parent 7b99dabb33
commit c1724edc47
4 changed files with 139 additions and 55 deletions

View File

@@ -18,6 +18,11 @@ agent or engineer to remove them without reconstructing context from chat.
## Recent Reductions ## Recent Reductions
- 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` current and
dual live sample frame/face traversal plus dirty tracking now route through
`legacy_canvas_stroke_execution_services.h`; shader timing, sampler/texture
binding, framebuffer ownership, pad execution, and final OpenGL draw remain
retained in `Canvas`.
- 2026-06-13: DEBT-0036 preview-adapter hardening grew again. - 2026-06-13: DEBT-0036 preview-adapter hardening grew again.
`pp_paint_renderer_compositor_tests` now covers `pp_paint_renderer_compositor_tests` now covers
`legacy_node_stroke_preview_execution_services.h` framebuffer-feedback `legacy_node_stroke_preview_execution_services.h` framebuffer-feedback

View File

@@ -3089,6 +3089,10 @@ Results:
plus retained preview composite slot intent. Preview texture binding, plus retained preview composite slot intent. Preview texture binding,
framebuffer copies, mixer ownership, and final OpenGL draw ordering remain framebuffer copies, mixer ownership, and final OpenGL draw ordering remain
retained. retained.
- `Canvas::stroke_draw` current and dual live sample frame/face traversal plus
dirty tracking now share one retained stroke execution helper surface, while
shader timing, sampler/texture binding, framebuffer ownership, pad execution,
and final OpenGL draw ordering remain in the legacy Canvas path.
- `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

@@ -625,7 +625,6 @@ void Canvas::stroke_draw()
} }
m_dirty = true; m_dirty = true;
std::array<bool, 6> merge_faces;
const auto vp = query_canvas_viewport(); const auto vp = query_canvas_viewport();
const auto cc = query_canvas_clear_color(); const auto cc = query_canvas_clear_color();
@@ -689,47 +688,39 @@ void Canvas::stroke_draw()
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);
glm::vec4 pad_color; glm::vec4 pad_color;
pp::panopainter::execute_legacy_canvas_stroke_frame_faces( pp::panopainter::execute_legacy_canvas_stroke_frame_samples_with_dirty_tracking(
frames, frames,
stroke_extent,
std::span<glm::vec4>(m_dirty_box),
std::span<glm::vec4>(box_face),
std::span<const bool>(include_main_dirty),
[&](auto& f) { [&](auto& f) {
if (brush->m_tip_mix > 0.f) if (brush->m_tip_mix > 0.f)
{ {
stroke_draw_mix(xy(f.m_mixer_rect), zw(f.m_mixer_rect)); stroke_draw_mix(xy(f.m_mixer_rect), zw(f.m_mixer_rect));
} }
pad_color = f.col;
},
[&](auto&, int i, auto&) {
m_dirty_face[i] = true;
box_dirty[i] = true;
m_tmp[i].bindFramebuffer();
}, },
[&](auto& f, int i, auto& P) { [&](auto& f, int i, auto& P) {
m_dirty_face[i] = true; pp::panopainter::use_legacy_stroke_shader();
merge_faces[i] = true; pp::panopainter::apply_legacy_stroke_sample_uniforms(
box_dirty[i] = true; pp::panopainter::LegacyStrokeSampleUniforms {
.color = f.col,
[[maybe_unused]] const auto dirty_update = pp::panopainter::execute_legacy_canvas_stroke_face_sample( .alpha = f.flow,
pp::panopainter::LegacyCanvasStrokeFaceDirtyRequest { .opacity = f.opacity,
.extent = stroke_extent, });
.previous_accumulated_dirty_box = m_dirty_box[i], return stroke_draw_samples(i, P, copy_stroke_destination);
.previous_pass_dirty_box = box_face[i], },
.include_in_committed_dirty_box = true, [&](auto&, int, auto&, auto&) {},
}, [&](auto&, int i, auto&, glm::vec4) {
[&] { m_tmp[i].unbindFramebuffer();
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[i].bindFramebuffer();
},
[&](auto&) {},
[&](glm::vec4) {
m_tmp[i].unbindFramebuffer();
},
&m_dirty_box[i],
&box_face[i]);
pad_color = f.col;
}); });
set_active_texture_unit(3); set_active_texture_unit(3);
@@ -806,8 +797,14 @@ void Canvas::stroke_draw()
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); auto frames_dual = stroke_draw_compute(*m_dual_stroke);
pp::panopainter::execute_legacy_canvas_stroke_frame_faces( const std::array<bool, 6> include_dual_dirty =
SIXPLETTE(stroke_material.composite_pass.dual_blend_mode == 0);
pp::panopainter::execute_legacy_canvas_stroke_frame_samples_with_dirty_tracking(
frames_dual, frames_dual,
stroke_extent,
std::span<glm::vec4>(m_dirty_box),
std::span<glm::vec4>(),
std::span<const bool>(include_dual_dirty),
[&](auto& f) { [&](auto& f) {
pp::panopainter::apply_legacy_stroke_sample_uniforms( pp::panopainter::apply_legacy_stroke_sample_uniforms(
pp::panopainter::LegacyStrokeSampleUniforms { pp::panopainter::LegacyStrokeSampleUniforms {
@@ -816,28 +813,17 @@ void Canvas::stroke_draw()
.opacity = f.opacity, .opacity = f.opacity,
}); });
}, },
[&](auto&, int i, auto&) {
m_tmp_dual[i].bindFramebuffer();
},
[&](auto&, int i, auto& P) { [&](auto&, int i, auto& P) {
[[maybe_unused]] const auto dirty_update = pp::panopainter::execute_legacy_canvas_stroke_face_sample( return stroke_draw_samples(i, P, copy_stroke_destination);
pp::panopainter::LegacyCanvasStrokeFaceDirtyRequest { },
.extent = stroke_extent, [&](auto&, int, auto&, auto& request) {
.previous_accumulated_dirty_box = m_dirty_box[i], request.previous_pass_dirty_box = request.sample_dirty_box;
.previous_pass_dirty_box = glm::vec4(0.0f), },
.include_in_committed_dirty_box = [&](auto&, int i, auto&, glm::vec4) {
stroke_material.composite_pass.dual_blend_mode == 0, m_tmp_dual[i].unbindFramebuffer();
},
[&] {
return stroke_draw_samples(i, P, copy_stroke_destination);
},
[&] {
m_tmp_dual[i].bindFramebuffer();
},
[&](auto& request) {
request.previous_pass_dirty_box = request.sample_dirty_box;
},
[&](glm::vec4) {
m_tmp_dual[i].unbindFramebuffer();
},
&m_dirty_box[i]);
}); });
} }

View File

@@ -218,6 +218,95 @@ std::size_t execute_legacy_canvas_stroke_frame_samples(
return executed_faces; return executed_faces;
} }
template <
typename Frames,
typename BeginFrame,
typename BeginFace,
typename ExecuteSample,
typename PrepareDirtyRequest,
typename FinishFace>
std::size_t execute_legacy_canvas_stroke_frame_samples_with_dirty_tracking(
Frames&& frames,
pp::renderer::Extent2D extent,
std::span<glm::vec4> accumulated_dirty_boxes,
std::span<glm::vec4> pass_dirty_boxes,
std::span<const bool> include_in_committed_dirty_box,
BeginFrame&& begin_frame,
BeginFace&& begin_face,
ExecuteSample&& execute_sample,
PrepareDirtyRequest&& prepare_dirty_request,
FinishFace&& finish_face,
std::span<bool> committed_dirty_faces = {},
std::span<bool> pass_dirty_faces = {})
{
std::size_t executed_faces = 0;
for (auto& frame : frames) {
begin_frame(frame);
for (int face_index = 0; face_index < 6; ++face_index) {
auto& vertices = frame.shapes[face_index];
if (vertices.size() < 3) {
continue;
}
const glm::vec4 previous_accumulated_dirty_box =
face_index < accumulated_dirty_boxes.size()
? accumulated_dirty_boxes[face_index]
: glm::vec4(0.0f);
const glm::vec4 previous_pass_dirty_box =
face_index < pass_dirty_boxes.size()
? pass_dirty_boxes[face_index]
: glm::vec4(0.0f);
const bool include_dirty_box =
face_index < include_in_committed_dirty_box.size()
? include_in_committed_dirty_box[face_index]
: true;
glm::vec4* accumulated_dirty_box =
face_index < accumulated_dirty_boxes.size()
? &accumulated_dirty_boxes[face_index]
: nullptr;
glm::vec4* pass_dirty_box =
face_index < pass_dirty_boxes.size()
? &pass_dirty_boxes[face_index]
: nullptr;
bool* committed_dirty =
face_index < committed_dirty_faces.size()
? &committed_dirty_faces[face_index]
: nullptr;
bool* pass_dirty =
face_index < pass_dirty_faces.size()
? &pass_dirty_faces[face_index]
: nullptr;
execute_legacy_canvas_stroke_face_sample(
LegacyCanvasStrokeFaceDirtyRequest {
.extent = extent,
.previous_accumulated_dirty_box = previous_accumulated_dirty_box,
.previous_pass_dirty_box = previous_pass_dirty_box,
.include_in_committed_dirty_box = include_dirty_box,
},
[&] {
return execute_sample(frame, face_index, vertices);
},
[&] {
begin_face(frame, face_index, vertices);
},
[&](auto& request) {
prepare_dirty_request(frame, face_index, vertices, request);
},
[&](glm::vec4 sample_dirty_box) {
finish_face(frame, face_index, vertices, sample_dirty_box);
},
accumulated_dirty_box,
pass_dirty_box,
committed_dirty,
pass_dirty);
++executed_faces;
}
}
return executed_faces;
}
[[nodiscard]] inline pp::paint_renderer::CanvasStrokeBox legacy_canvas_stroke_box(glm::vec4 box) noexcept [[nodiscard]] inline pp::paint_renderer::CanvasStrokeBox legacy_canvas_stroke_box(glm::vec4 box) noexcept
{ {
return pp::paint_renderer::CanvasStrokeBox { return pp::paint_renderer::CanvasStrokeBox {