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
- 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.
`pp_paint_renderer_compositor_tests` now covers
`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,
framebuffer copies, mixer ownership, and final OpenGL draw ordering remain
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
execution helper wrapping `pp_paint_renderer`, while pad color selection,
dirty-face iteration, framebuffer copies, quad upload, and draw execution

View File

@@ -625,7 +625,6 @@ void Canvas::stroke_draw()
}
m_dirty = true;
std::array<bool, 6> merge_faces;
const auto vp = query_canvas_viewport();
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<bool, 6> box_dirty = SIXPLETTE(false);
const std::array<bool, 6> include_main_dirty = SIXPLETTE(true);
glm::vec4 pad_color;
pp::panopainter::execute_legacy_canvas_stroke_frame_faces(
pp::panopainter::execute_legacy_canvas_stroke_frame_samples_with_dirty_tracking(
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) {
if (brush->m_tip_mix > 0.f)
{
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) {
m_dirty_face[i] = true;
merge_faces[i] = true;
box_dirty[i] = true;
[[maybe_unused]] const auto dirty_update = pp::panopainter::execute_legacy_canvas_stroke_face_sample(
pp::panopainter::LegacyCanvasStrokeFaceDirtyRequest {
.extent = stroke_extent,
.previous_accumulated_dirty_box = m_dirty_box[i],
.previous_pass_dirty_box = box_face[i],
.include_in_committed_dirty_box = true,
},
[&] {
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;
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);
},
[&](auto&, int, auto&, auto&) {},
[&](auto&, int i, auto&, glm::vec4) {
m_tmp[i].unbindFramebuffer();
});
set_active_texture_unit(3);
@@ -806,8 +797,14 @@ void Canvas::stroke_draw()
dual_brush->m_tip_texture->bind() :
unbind_texture_2d();
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,
stroke_extent,
std::span<glm::vec4>(m_dirty_box),
std::span<glm::vec4>(),
std::span<const bool>(include_dual_dirty),
[&](auto& f) {
pp::panopainter::apply_legacy_stroke_sample_uniforms(
pp::panopainter::LegacyStrokeSampleUniforms {
@@ -816,28 +813,17 @@ void Canvas::stroke_draw()
.opacity = f.opacity,
});
},
[&](auto&, int i, auto&) {
m_tmp_dual[i].bindFramebuffer();
},
[&](auto&, int i, auto& P) {
[[maybe_unused]] const auto dirty_update = pp::panopainter::execute_legacy_canvas_stroke_face_sample(
pp::panopainter::LegacyCanvasStrokeFaceDirtyRequest {
.extent = stroke_extent,
.previous_accumulated_dirty_box = m_dirty_box[i],
.previous_pass_dirty_box = glm::vec4(0.0f),
.include_in_committed_dirty_box =
stroke_material.composite_pass.dual_blend_mode == 0,
},
[&] {
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]);
return stroke_draw_samples(i, P, copy_stroke_destination);
},
[&](auto&, int, auto&, auto& request) {
request.previous_pass_dirty_box = request.sample_dirty_box;
},
[&](auto&, int i, auto&, glm::vec4) {
m_tmp_dual[i].unbindFramebuffer();
});
}

View File

@@ -218,6 +218,95 @@ std::size_t execute_legacy_canvas_stroke_frame_samples(
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
{
return pp::paint_renderer::CanvasStrokeBox {