diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 9c9111a..d01ed41 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -18,6 +18,10 @@ agent or engineer to remove them without reconstructing context from chat. ## Recent Reductions +- 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::stroke_draw_compute` + now routes preview quad frame planning through + `legacy_canvas_stroke_execution_services.h`. Preview stroke sampling, mixer + execution, texture binding, and final OpenGL draw ordering remain retained. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` current and dual stroke per-face framebuffer/sample callback ordering now routes through the retained stroke execution helper; framebuffer ownership, shader uniform diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 2205e12..460c52e 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -3079,6 +3079,9 @@ Results: compositor coverage now includes malformed retained commit plans plus all-input stroke-preview composite planning. Live stroke rasterization, callback execution, texture binding, and history mutation remain retained. +- `NodeStrokePreview::stroke_draw_compute` now shares the retained stroke + execution helper for preview quad frame planning. Preview sample execution, + mixer passes, texture binding, and final draw ordering remain retained. - `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 8fcf762..daa8189 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -690,7 +690,7 @@ void Canvas::stroke_draw() std::array box_face = SIXPLETTE(glm::vec4(m_size, 0, 0)); std::array box_dirty = SIXPLETTE(false); glm::vec4 pad_color; - pp::panopainter::execute_legacy_canvas_stroke_frame_samples( + pp::panopainter::execute_legacy_canvas_stroke_frame_faces( frames, [&](auto& f) { if (brush->m_tip_mix > 0.f) @@ -698,33 +698,36 @@ void Canvas::stroke_draw() stroke_draw_mix(xy(f.m_mixer_rect), zw(f.m_mixer_rect)); } }, - [&](auto&, int i, auto&) { + [&](auto& f, int i, auto& P) { m_dirty_face[i] = true; merge_faces[i] = true; box_dirty[i] = true; - m_tmp[i].bindFramebuffer(); - }, - [&](auto& f, int i, auto& P) { - 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& f, int i, glm::vec4 box_sample) { - m_tmp[i].unbindFramebuffer(); - [[maybe_unused]] const auto dirty_update = pp::panopainter::apply_legacy_canvas_stroke_face_dirty_update( + + [[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], - .sample_dirty_box = box_sample, .include_in_committed_dirty_box = true, }, - m_dirty_box[i], + [&] { + 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; }); @@ -803,7 +806,7 @@ 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_samples( + pp::panopainter::execute_legacy_canvas_stroke_frame_faces( frames_dual, [&](auto& f) { pp::panopainter::apply_legacy_stroke_sample_uniforms( @@ -813,24 +816,28 @@ void Canvas::stroke_draw() .opacity = f.opacity, }); }, - [&](auto&, int i, auto&) { - m_tmp_dual[i].bindFramebuffer(); - }, [&](auto&, int i, auto& P) { - return stroke_draw_samples(i, P, copy_stroke_destination); - }, - [&](auto&, int i, glm::vec4 box_sample) { - m_tmp_dual[i].unbindFramebuffer(); - [[maybe_unused]] const auto dirty_update = pp::panopainter::apply_legacy_canvas_stroke_face_dirty_update( - pp::panopainter::LegacyCanvasStrokeFaceDirtyRequest { - .extent = stroke_extent, - .previous_accumulated_dirty_box = m_dirty_box[i], - .previous_pass_dirty_box = box_sample, - .sample_dirty_box = box_sample, - .include_in_committed_dirty_box = - stroke_material.composite_pass.dual_blend_mode == 0, - }, - m_dirty_box[i]); + [[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]); }); } diff --git a/src/legacy_canvas_stroke_execution_services.h b/src/legacy_canvas_stroke_execution_services.h index 901fa20..a3eb301 100644 --- a/src/legacy_canvas_stroke_execution_services.h +++ b/src/legacy_canvas_stroke_execution_services.h @@ -293,6 +293,41 @@ std::size_t execute_legacy_canvas_stroke_frame_samples( return result; } +template +[[nodiscard]] inline LegacyCanvasStrokeFaceDirtyResult execute_legacy_canvas_stroke_face_sample( + const LegacyCanvasStrokeFaceDirtyRequest& dirty_request, + ExecuteSample&& execute_sample, + BeginFace&& begin_face, + PrepareDirtyRequest&& prepare_dirty_request, + FinishFace&& finish_face, + glm::vec4* accumulated_dirty_box = nullptr, + glm::vec4* pass_dirty_box = nullptr, + bool* committed_dirty = nullptr, + bool* pass_dirty = nullptr) noexcept(noexcept(execute_sample())) +{ + LegacyCanvasStrokeFaceDirtyResult result; + begin_face(); + + auto request = dirty_request; + request.sample_dirty_box = execute_sample(); + prepare_dirty_request(request); + + finish_face(request.sample_dirty_box); + + glm::vec4 accumulated = request.previous_accumulated_dirty_box; + result = apply_legacy_canvas_stroke_face_dirty_update( + request, + accumulated, + pass_dirty_box, + committed_dirty, + pass_dirty); + result.accumulated_dirty_box = accumulated; + if (accumulated_dirty_box) { + *accumulated_dirty_box = accumulated; + } + return result; +} + [[nodiscard]] inline LegacyCanvasStrokePadExecutionResult execute_legacy_canvas_stroke_pad_faces( const LegacyCanvasStrokePadExecutionRequest& request) { diff --git a/src/node_stroke_preview.cpp b/src/node_stroke_preview.cpp index b7c2d3a..bc8aa56 100644 --- a/src/node_stroke_preview.cpp +++ b/src/node_stroke_preview.cpp @@ -289,61 +289,40 @@ glm::vec4 NodeStrokePreview::stroke_draw_samples( std::vector NodeStrokePreview::stroke_draw_compute(Stroke& stroke, float zoom) const { - std::vector ret; - StrokeSample prev = stroke.m_prev_sample; auto samples = stroke.compute_samples(); - std::array B = { - vertex_t{ {0, 0, 1, 1}, {0, 0}, {0, 0} }, - vertex_t{ {0, 0, 1, 1}, {0, 1}, {0, 1} }, - vertex_t{ {0, 0, 1, 1}, {1, 1}, {1, 1} }, - vertex_t{ {0, 0, 1, 1}, {1, 0}, {1, 0} }, - }; - for (const auto& s : samples) - { - if (!s.valid()) - continue; - - ret.emplace_back(); - auto& f = ret.back(); - - glm::vec2 dx_mix(prev.size * 0.5f, 0), dy_mix(0, prev.size * 0.5f); - glm::vec2 off_mix[4] = { - -dx_mix - dy_mix, // A - bottom-left - -dx_mix + dy_mix, // B - top-left - +dx_mix + dy_mix, // C - top-right - +dx_mix - dy_mix, // D - bottom-right - }; - // P is the initial square centered at the cursor location - glm::vec2 dx(s.size * 0.5f, 0), dy(0, s.size * 0.5f); - glm::vec2 off[4] = { - -dx - dy, // A - bottom-left - -dx + dy, // B - top-left - +dx + dy, // C - top-right - +dx - dy, // D - bottom-right - }; - - glm::vec2 mixer_sz(m_rtt.getWidth(), m_rtt.getHeight()); - glm::vec2 mixer_bb_min(mixer_sz); - glm::vec2 mixer_bb_max(0, 0); - for (int j = 0; j < 4; j++) - { - auto p = (xy(prev.pos) + zoom * s.scale * off_mix[j] * glm::orientate2(-s.angle)); - mixer_bb_min = glm::max({ 0, 0 }, glm::min(mixer_bb_min, p)); - mixer_bb_max = glm::min(mixer_sz, glm::max(mixer_bb_max, p)); - - B[j].pos = glm::vec4(xy(s.pos) + zoom * s.scale * off[j] * glm::orientate2(-s.angle) - glm::vec2(0, 1), 1, 1); - B[j].uvs2 = p / mixer_sz; - } - - f.m_mixer_rect = { glm::floor(mixer_bb_min), glm::ceil(mixer_bb_max - mixer_bb_min) }; - f.col = glm::vec4(s.col, 1); - f.flow = s.flow; - f.opacity = s.opacity; - f.shapes = B; - - prev = s; + StrokeSample previous_sample = stroke.m_prev_sample; + previous_sample.size *= zoom; + for (auto& sample : samples) { + sample.size *= zoom; } - return ret; + + return pp::panopainter::plan_legacy_canvas_stroke_frames( + pp::panopainter::LegacyCanvasStrokeComputeRequest { + .previous_sample = previous_sample, + .samples = samples, + .zoom = 1.0f, + .mixer_size = glm::vec2(m_rtt.getWidth(), m_rtt.getHeight()), + }, + []( + std::array& brush_quad, + bool /*project_3d*/, + glm::mat4 /*model_view*/) { + return brush_quad; + }, + []( + glm::vec4 mixer_rect, + glm::vec4 color, + float flow, + float opacity, + std::array&& shapes) { + return StrokeFrame { + .col = color, + .flow = flow, + .opacity = opacity, + .shapes = std::move(shapes), + .m_mixer_rect = mixer_rect, + }; + }); } void NodeStrokePreview::draw_stroke_immediate()