diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 8d596b6..ca7b052 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -18,6 +18,12 @@ 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 dirty semantics now route through + `execute_legacy_canvas_stroke_live_pass_with_dirty_tracking(...)` in + `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 was narrowed again. `NodeStrokePreview` final composite sampler/input binding and slot intent now route through one local preview helper; mixer execution, per-sample stroke callbacks, framebuffer diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index ec38b4c..4d6fb6f 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -3093,6 +3093,11 @@ Results: 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` main and dual live-pass dirty semantics now share one + retained helper around the face loop, current/dual dirty accumulation, and + 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` 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/src/canvas.cpp b/src/canvas.cpp index 3c247d4..0ab4bbf 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -630,7 +630,6 @@ void Canvas::stroke_draw() const auto cc = query_canvas_clear_color(); const auto& brush = m_current_stroke->m_brush; - const auto& dual_brush = m_dual_stroke->m_brush; auto ortho_proj = glm::ortho(0.f, (float)m_width, 0.f, (float)m_height, -1.f, 1.f); apply_canvas_viewport(0, 0, m_width, m_height); @@ -690,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_frame_samples_with_dirty_tracking( + pp::panopainter::execute_legacy_canvas_stroke_live_pass_with_dirty_tracking( frames, stroke_extent, std::span(m_dirty_box), @@ -718,7 +717,6 @@ void Canvas::stroke_draw() }); return stroke_draw_samples(i, P, copy_stroke_destination); }, - [&](auto&, int, auto&, auto&) {}, [&](auto&, int i, auto&, glm::vec4) { m_tmp[i].unbindFramebuffer(); }); @@ -792,8 +790,9 @@ void Canvas::stroke_draw() // DRAW DUAL BRUSH - if (stroke_material.dual_pass.enabled) + if (stroke_material.dual_pass.enabled && m_dual_stroke) { + const auto& dual_brush = m_dual_stroke->m_brush; pp::panopainter::setup_legacy_stroke_dual_shader(stroke_material.dual_pass.uses_pattern); set_active_texture_unit(0); @@ -803,7 +802,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_frame_samples_with_dirty_tracking( + pp::panopainter::execute_legacy_canvas_stroke_live_pass_with_dirty_tracking( frames_dual, stroke_extent, std::span(m_dirty_box), @@ -823,12 +822,15 @@ void Canvas::stroke_draw() [&](auto&, int i, auto& P) { 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(); - }); + }, + true); + + set_active_texture_unit(0); + dual_brush->m_tip_texture ? + dual_brush->m_tip_texture->unbind() : + unbind_texture_2d(); } m_sampler_brush.unbind(); diff --git a/src/legacy_canvas_stroke_execution_services.h b/src/legacy_canvas_stroke_execution_services.h index eaaadfd..c158d67 100644 --- a/src/legacy_canvas_stroke_execution_services.h +++ b/src/legacy_canvas_stroke_execution_services.h @@ -315,6 +315,42 @@ std::size_t execute_legacy_canvas_stroke_frame_samples_with_dirty_tracking( return executed_faces; } +template +std::size_t execute_legacy_canvas_stroke_live_pass_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, + FinishFace&& finish_face, + bool preserve_sample_dirty_as_pass_dirty = false, + std::span committed_dirty_faces = {}, + std::span pass_dirty_faces = {}) +{ + return execute_legacy_canvas_stroke_frame_samples_with_dirty_tracking( + std::forward(frames), + extent, + accumulated_dirty_boxes, + pass_dirty_boxes, + include_in_committed_dirty_box, + std::forward(begin_frame), + std::forward(begin_face), + std::forward(execute_sample), + [&](auto&, int, auto&, auto& request) { + if (preserve_sample_dirty_as_pass_dirty) { + request.previous_pass_dirty_box = request.sample_dirty_box; + } + }, + [&](auto& frame, int face_index, auto& vertices, glm::vec4 sample_dirty_box) { + finish_face(frame, face_index, vertices, sample_dirty_box); + }, + 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 {