diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 177ecd5..896c885 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -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 diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 327b537..533c7b0 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -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 diff --git a/src/canvas.cpp b/src/canvas.cpp index daa8189..0779733 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -625,7 +625,6 @@ void Canvas::stroke_draw() } m_dirty = true; - std::array 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 box_face = SIXPLETTE(glm::vec4(m_size, 0, 0)); std::array box_dirty = SIXPLETTE(false); + const std::array 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(m_dirty_box), + std::span(box_face), + std::span(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 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(m_dirty_box), + std::span(), + std::span(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(); }); } diff --git a/src/legacy_canvas_stroke_execution_services.h b/src/legacy_canvas_stroke_execution_services.h index a3eb301..8ea0ce6 100644 --- a/src/legacy_canvas_stroke_execution_services.h +++ b/src/legacy_canvas_stroke_execution_services.h @@ -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 accumulated_dirty_boxes, + std::span pass_dirty_boxes, + std::span include_in_committed_dirty_box, + BeginFrame&& begin_frame, + BeginFace&& begin_face, + ExecuteSample&& execute_sample, + PrepareDirtyRequest&& prepare_dirty_request, + FinishFace&& finish_face, + std::span committed_dirty_faces = {}, + std::span 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 {