diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index ca7b052..689f16c 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` main and + dual live-pass per-face framebuffer begin/end execution plus pad-face array + assembly now route through `legacy_canvas_stroke_execution_services.h`; + shader activation timing, texture/sampler binding, framebuffer ownership, pad + execution, and final OpenGL draw remain retained in `Canvas`. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` main and dual live-pass dirty semantics now route through `execute_legacy_canvas_stroke_live_pass_with_dirty_tracking(...)` in diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 4d6fb6f..c16c9cc 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -3098,6 +3098,11 @@ Results: dual-pass dirty preservation, while shader timing, sampler/texture binding, framebuffer ownership, pad execution, and final OpenGL draw ordering remain in the legacy Canvas path. +- `Canvas::stroke_draw` main and dual live-pass per-face framebuffer + bind/unbind execution now shares one retained helper, and pad-face array + assembly now shares one retained utility, while shader activation timing, + texture/sampler binding, framebuffer ownership, pad execution, and final + OpenGL draw ordering remain in the legacy Canvas path. - `Canvas::stroke_draw` pad-pass destination bind/copy/unbind ordering now shares the retained stroke execution helper callback surface, while shader setup, pad color selection, framebuffer ownership, and final OpenGL draw diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index 15abc21..9ee623b 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -509,6 +509,13 @@ Done Checks: Progress Notes: +- 2026-06-13: `Canvas::stroke_draw` main and dual live-pass per-face + framebuffer begin/end execution plus pad-face array assembly now route + through `legacy_canvas_stroke_execution_services.h`; shader activation + timing, texture/sampler binding, framebuffer ownership, and final draw + execution remain local to `Canvas`. Next slice should target the remaining + pass-level texture binding or final composite setup without reopening the + newer pad, dirty, or commit helpers. - 2026-06-13: `Canvas::stroke_draw` current and dual stroke per-face framebuffer/sample callback ordering now routes through `legacy_canvas_stroke_execution_services.h`; framebuffer ownership, shader diff --git a/src/canvas.cpp b/src/canvas.cpp index 0ab4bbf..5a03e6b 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -689,7 +689,7 @@ void Canvas::stroke_draw() std::array box_dirty = SIXPLETTE(false); const std::array include_main_dirty = SIXPLETTE(true); glm::vec4 pad_color; - pp::panopainter::execute_legacy_canvas_stroke_live_pass_with_dirty_tracking( + pp::panopainter::execute_legacy_canvas_stroke_live_pass_with_face_framebuffers( frames, stroke_extent, std::span(m_dirty_box), @@ -705,7 +705,6 @@ void Canvas::stroke_draw() [&](auto&, int i, auto&) { m_dirty_face[i] = true; box_dirty[i] = true; - m_tmp[i].bindFramebuffer(); }, [&](auto& f, int i, auto& P) { pp::panopainter::use_legacy_stroke_shader(); @@ -717,9 +716,7 @@ void Canvas::stroke_draw() }); return stroke_draw_samples(i, P, copy_stroke_destination); }, - [&](auto&, int i, auto&, glm::vec4) { - m_tmp[i].unbindFramebuffer(); - }); + m_tmp); set_active_texture_unit(3); m_mixer.unbindTexture(); @@ -740,14 +737,7 @@ void Canvas::stroke_draw() .color = pad_color, .uses_destination_feedback = copy_stroke_destination, }); - const std::array pad_faces = { - pp::panopainter::LegacyCanvasStrokePadFace { .index = 0, .dirty = box_dirty[0], .pass_dirty_box = box_face[0] }, - pp::panopainter::LegacyCanvasStrokePadFace { .index = 1, .dirty = box_dirty[1], .pass_dirty_box = box_face[1] }, - pp::panopainter::LegacyCanvasStrokePadFace { .index = 2, .dirty = box_dirty[2], .pass_dirty_box = box_face[2] }, - pp::panopainter::LegacyCanvasStrokePadFace { .index = 3, .dirty = box_dirty[3], .pass_dirty_box = box_face[3] }, - pp::panopainter::LegacyCanvasStrokePadFace { .index = 4, .dirty = box_dirty[4], .pass_dirty_box = box_face[4] }, - pp::panopainter::LegacyCanvasStrokePadFace { .index = 5, .dirty = box_dirty[5], .pass_dirty_box = box_face[5] }, - }; + const auto pad_faces = pp::panopainter::make_legacy_canvas_stroke_pad_faces(box_dirty, box_face); [[maybe_unused]] const auto pad_result = pp::panopainter::execute_legacy_canvas_stroke_pad_faces( pp::panopainter::LegacyCanvasStrokePadExecutionRequest { .context = "Canvas::stroke_draw", @@ -802,7 +792,7 @@ void Canvas::stroke_draw() auto frames_dual = stroke_draw_compute(*m_dual_stroke); const std::array include_dual_dirty = SIXPLETTE(stroke_material.composite_pass.dual_blend_mode == 0); - pp::panopainter::execute_legacy_canvas_stroke_live_pass_with_dirty_tracking( + pp::panopainter::execute_legacy_canvas_stroke_live_pass_with_face_framebuffers( frames_dual, stroke_extent, std::span(m_dirty_box), @@ -816,15 +806,11 @@ void Canvas::stroke_draw() .opacity = f.opacity, }); }, - [&](auto&, int i, auto&) { - m_tmp_dual[i].bindFramebuffer(); - }, + [](auto&, int, auto&) {}, [&](auto&, int i, auto& P) { return stroke_draw_samples(i, P, copy_stroke_destination); }, - [&](auto&, int i, auto&, glm::vec4) { - m_tmp_dual[i].unbindFramebuffer(); - }, + m_tmp_dual, true); set_active_texture_unit(0); diff --git a/src/legacy_canvas_stroke_execution_services.h b/src/legacy_canvas_stroke_execution_services.h index c158d67..d011892 100644 --- a/src/legacy_canvas_stroke_execution_services.h +++ b/src/legacy_canvas_stroke_execution_services.h @@ -351,6 +351,41 @@ std::size_t execute_legacy_canvas_stroke_live_pass_with_dirty_tracking( pass_dirty_faces); } +template +std::size_t execute_legacy_canvas_stroke_live_pass_with_face_framebuffers( + 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, + PrepareFace&& prepare_face, + ExecuteSample&& execute_sample, + Framebuffers& face_framebuffers, + bool preserve_sample_dirty_as_pass_dirty = false, + std::span committed_dirty_faces = {}, + std::span pass_dirty_faces = {}) +{ + return execute_legacy_canvas_stroke_live_pass_with_dirty_tracking( + std::forward(frames), + extent, + accumulated_dirty_boxes, + pass_dirty_boxes, + include_in_committed_dirty_box, + std::forward(begin_frame), + [&](auto& frame, int face_index, auto& vertices) { + prepare_face(frame, face_index, vertices); + face_framebuffers[face_index].bindFramebuffer(); + }, + std::forward(execute_sample), + [&](auto&, int face_index, auto&, glm::vec4) { + face_framebuffers[face_index].unbindFramebuffer(); + }, + preserve_sample_dirty_as_pass_dirty, + committed_dirty_faces, + pass_dirty_faces); +} + [[nodiscard]] inline pp::paint_renderer::CanvasStrokeBox legacy_canvas_stroke_box(glm::vec4 box) noexcept { return pp::paint_renderer::CanvasStrokeBox { @@ -405,6 +440,20 @@ std::size_t execute_legacy_canvas_stroke_live_pass_with_dirty_tracking( }; } +template +[[nodiscard]] inline std::array make_legacy_canvas_stroke_pad_faces( + const std::array& dirty_faces, + const std::array& pass_dirty_boxes) noexcept +{ + std::array faces {}; + for (std::size_t face_index = 0; face_index < FaceCount; ++face_index) { + faces[face_index].index = static_cast(face_index); + faces[face_index].dirty = dirty_faces[face_index]; + faces[face_index].pass_dirty_box = pass_dirty_boxes[face_index]; + } + return faces; +} + [[nodiscard]] inline LegacyCanvasStrokeFaceDirtyResult apply_legacy_canvas_stroke_face_dirty_update( const LegacyCanvasStrokeFaceDirtyRequest& request, glm::vec4& accumulated_dirty_box,