From 78790e9b52aeea6289ecaa8753c64a9f7fd2aa7a Mon Sep 17 00:00:00 2001 From: omigamedev Date: Sat, 13 Jun 2026 10:46:58 +0200 Subject: [PATCH] Share retained stroke sample and mix helpers --- docs/modernization/debt.md | 10 ++ docs/modernization/roadmap.md | 9 ++ docs/modernization/tasks.md | 14 +++ src/canvas.cpp | 20 +--- src/legacy_canvas_stroke_execution_services.h | 49 ++++++++++ src/node_stroke_preview.cpp | 91 ++++++++++++------- 6 files changed, 145 insertions(+), 48 deletions(-) diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 62fee94..a3d1c1f 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -23,6 +23,16 @@ agent or engineer to remove them without reconstructing context from chat. retained stroke execution helpers; concrete GL object mapping, framebuffer ownership, shader timing, and final draw execution remain retained in `Canvas`. +- 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw_samples()` + now routes polygon triangulation, sample-point assembly, and retained + destination-copy / upload / draw helper handoff through + `execute_legacy_canvas_stroke_sample_polygon(...)`; direct GL callback + wiring and remaining live draw ownership remain retained in `Canvas`. +- 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::stroke_draw_mix()` + now routes mixer framebuffer bind/unbind, viewport/scissor/blend state, + texture-slot binding, and final plane draw through one local helper; + material planning and shader uniform setup remain retained in the preview + node. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::stroke_draw_samples` now routes destination bind/unbind, framebuffer copy callback wrapping, sample-point assembly, and brush-vertex upload/draw through one local helper; diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 92d9762..67a254a 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -3123,6 +3123,15 @@ Results: texture-input dispatch now shares retained stroke execution helpers, while concrete GL object mapping, framebuffer ownership, shader timing, and final draw execution remain in the legacy Canvas path. +- `Canvas::stroke_draw_samples()` now shares + `execute_legacy_canvas_stroke_sample_polygon(...)` for polygon + triangulation, sample-point assembly, and retained destination-copy / upload + / draw helper handoff, while direct GL callback wiring and the remaining + live draw ownership stay in the legacy Canvas path. +- `NodeStrokePreview::stroke_draw_mix()` now shares one local helper for mixer + framebuffer bind/unbind, viewport/scissor/blend state, texture-slot + binding, and final plane draw, while material planning and shader uniform + setup remain in the preview node. - `pp_paint_renderer_stroke_execution_tests` now covers retained stroke texture input binding order, sample execution destination-copy behavior, live-pass face-framebuffer dirty tracking, and pad-face destination-copy behavior diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index df0450b..d19036b 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -509,6 +509,20 @@ Done Checks: Progress Notes: +- 2026-06-13: `Canvas::stroke_draw_samples()` now routes polygon + triangulation, sample-point assembly, and the retained destination-copy / + upload / draw helper handoff through + `execute_legacy_canvas_stroke_sample_polygon(...)`; direct GL callback + wiring and the remaining live draw ownership stay local to `Canvas`. Next + slice should target the remaining callback body or final temporary-texture + composite setup without reopening the landed sampler, dirty, face, or pad + helpers. +- 2026-06-13: `NodeStrokePreview::stroke_draw_mix()` now routes mixer + framebuffer bind/unbind, viewport/scissor/blend state, texture-slot binding, + and final plane draw through one local helper; material planning and shader + uniform setup remain local to the preview node. Next slice should target the + remaining mix-pass material/setup orchestration without reopening the landed + preview live-pass, binding, sample, or final composite helpers. - 2026-06-13: `pp_paint_renderer_stroke_execution_tests` now also covers retained texture-dispatch activation order and sampler-dispatch routing across brush tip, destination, pattern, and mixer helper inputs. Next test diff --git a/src/canvas.cpp b/src/canvas.cpp index 4dec328..a6ccb4e 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -535,25 +535,11 @@ glm::vec4 Canvas::stroke_draw_samples( std::vector& P, bool copy_stroke_destination) { - if (P.size() != 3 && P.size() != 4) { - P = triangulate_simple(P); - } - - std::vector sample_points; - sample_points.reserve(P.size()); - for (const auto& vertex : P) { - sample_points.push_back(pp::paint_renderer::CanvasStrokePoint { - .x = vertex.pos.x, - .y = vertex.pos.y, - }); - } - - const auto result = pp::panopainter::execute_legacy_canvas_stroke_sample( - pp::panopainter::LegacyStrokeSampleExecutionRequest { + const auto result = pp::panopainter::execute_legacy_canvas_stroke_sample_polygon( + pp::panopainter::LegacyStrokeSamplePolygonExecutionRequest { .context = "Canvas::stroke_draw_samples", .target_size = { m_width, m_height }, - .vertices = P, - .sample_points = sample_points, + .polygon_vertices = P, .copy_stroke_destination = copy_stroke_destination, .bind_destination_texture = [&] { set_active_texture_unit(1); diff --git a/src/legacy_canvas_stroke_execution_services.h b/src/legacy_canvas_stroke_execution_services.h index ec48957..a1f394c 100644 --- a/src/legacy_canvas_stroke_execution_services.h +++ b/src/legacy_canvas_stroke_execution_services.h @@ -37,6 +37,18 @@ struct LegacyStrokeSampleExecutionResult { glm::vec4 dirty_bounds {}; }; +struct LegacyStrokeSamplePolygonExecutionRequest { + std::string_view context; + glm::vec2 target_size {}; + std::span polygon_vertices; + bool copy_stroke_destination = false; + std::function bind_destination_texture; + std::function copy_framebuffer_to_destination_texture; + std::function unbind_destination_texture; + std::function)> upload_brush_vertices; + std::function draw_brush_shape; +}; + enum class LegacyCanvasStrokeTextureInput { brush_tip, stroke_destination, @@ -236,6 +248,7 @@ std::size_t execute_legacy_canvas_stroke_frame_faces( } template +requires std::invocable inline void bind_legacy_canvas_stroke_texture_inputs( const std::array& bindings, BindTextureInput&& bind_texture_input) @@ -246,6 +259,7 @@ inline void bind_legacy_canvas_stroke_texture_inputs( } template +requires std::invocable inline void unbind_legacy_canvas_stroke_texture_inputs( const std::array& bindings, UnbindTextureInput&& unbind_texture_input) @@ -874,4 +888,39 @@ template sample_vertices( + request.polygon_vertices.begin(), + request.polygon_vertices.end()); + if (sample_vertices.size() != 3 && sample_vertices.size() != 4) { + sample_vertices = triangulate_simple(sample_vertices); + } + + std::vector sample_points; + sample_points.reserve(sample_vertices.size()); + for (const auto& vertex : sample_vertices) { + sample_points.push_back(pp::paint_renderer::CanvasStrokePoint { + .x = vertex.pos.x, + .y = vertex.pos.y, + }); + } + + return execute_legacy_canvas_stroke_sample( + LegacyStrokeSampleExecutionRequest { + .context = request.context, + .target_size = request.target_size, + .vertices = sample_vertices, + .sample_points = sample_points, + .copy_stroke_destination = request.copy_stroke_destination, + .bind_destination_texture = request.bind_destination_texture, + .copy_framebuffer_to_destination_texture = + request.copy_framebuffer_to_destination_texture, + .unbind_destination_texture = request.unbind_destination_texture, + .upload_brush_vertices = request.upload_brush_vertices, + .draw_brush_shape = request.draw_brush_shape, + }); +} + } // namespace pp::panopainter diff --git a/src/node_stroke_preview.cpp b/src/node_stroke_preview.cpp index cd8a108..43dabcb 100644 --- a/src/node_stroke_preview.cpp +++ b/src/node_stroke_preview.cpp @@ -124,6 +124,52 @@ struct StrokePreviewCompositePassInputs { std::function draw_composite; }; +struct StrokePreviewMixPassInputs { + glm::vec2 scissor_min; + glm::vec2 scissor_size; + RTT& mixer_rtt; + Texture2D& background_texture; + RTT& stroke_rtt; + Texture2D& dual_texture; + const Brush& brush; + Sampler& linear_sampler; + std::function draw_mix; +}; + +void execute_stroke_preview_mix_pass(const StrokePreviewMixPassInputs& inputs) +{ + gl_state gl; + gl.save(); + + inputs.mixer_rtt.bindFramebuffer(); + + apply_stroke_preview_viewport(0, 0, inputs.mixer_rtt.getWidth(), inputs.mixer_rtt.getHeight()); + apply_stroke_preview_capability(pp::renderer::gl::depth_test_state(), false); + apply_stroke_preview_capability(pp::renderer::gl::scissor_test_state(), true); + apply_stroke_preview_capability(pp::renderer::gl::blend_state(), false); + apply_stroke_preview_scissor( + static_cast(inputs.scissor_min.x), + static_cast(inputs.scissor_min.y), + static_cast(inputs.scissor_size.x), + static_cast(inputs.scissor_size.y)); + + inputs.linear_sampler.bind(stroke_preview_composite_slots::kBackground); + set_active_texture_unit(stroke_preview_composite_slots::kBackground); + inputs.background_texture.bind(); + set_active_texture_unit(stroke_preview_composite_slots::kStroke); + inputs.stroke_rtt.bindTexture(); + set_active_texture_unit(stroke_preview_composite_slots::kDual); + inputs.dual_texture.bind(); + set_active_texture_unit(stroke_preview_composite_slots::kPattern); + inputs.brush.m_pattern_texture ? + inputs.brush.m_pattern_texture->bind() : + unbind_texture_2d(); + inputs.draw_mix(); + + inputs.mixer_rtt.unbindFramebuffer(); + gl.restore(); +} + void execute_stroke_preview_final_composite_pass(const StrokePreviewCompositePassInputs& inputs) { pp::panopainter::execute_legacy_stroke_preview_final_composite( @@ -504,22 +550,6 @@ void NodeStrokePreview::clear_context() void NodeStrokePreview::stroke_draw_mix(const glm::vec2& bb_min, const glm::vec2& bb_sz) { - gl_state gl; - gl.save(); - - m_rtt_mixer.bindFramebuffer(); - - apply_stroke_preview_viewport(0, 0, m_rtt_mixer.getWidth(), m_rtt_mixer.getHeight()); - apply_stroke_preview_capability(pp::renderer::gl::depth_test_state(), false); - apply_stroke_preview_capability(pp::renderer::gl::scissor_test_state(), true); - apply_stroke_preview_capability(pp::renderer::gl::blend_state(), false); - - apply_stroke_preview_scissor( - static_cast(bb_min.x), - static_cast(bb_min.y), - static_cast(bb_sz.x), - static_cast(bb_sz.y)); - const auto& b = m_brush; glm::vec2 patt_scale = glm::vec2(b->m_pattern_scale); if (b->m_pattern_flipx) patt_scale.x *= -1.f; @@ -550,21 +580,20 @@ void NodeStrokePreview::stroke_draw_mix(const glm::vec2& bb_min, const glm::vec2 .use_pattern = material.composite_pass.use_pattern, }); - m_sampler_linear.bind(0); - set_active_texture_unit(0U); - m_tex_background.bind(); - set_active_texture_unit(1U); - m_rtt.bindTexture(); - set_active_texture_unit(3U); - m_tex_dual.bind(); - set_active_texture_unit(4U); - b->m_pattern_texture ? - b->m_pattern_texture->bind() : - unbind_texture_2d(); - m_plane.draw_fill(); - - m_rtt_mixer.unbindFramebuffer(); - gl.restore(); + execute_stroke_preview_mix_pass( + StrokePreviewMixPassInputs { + .scissor_min = bb_min, + .scissor_size = bb_sz, + .mixer_rtt = m_rtt_mixer, + .background_texture = m_tex_background, + .stroke_rtt = m_rtt, + .dual_texture = m_tex_dual, + .brush = *b, + .linear_sampler = m_sampler_linear, + .draw_mix = [&] { + m_plane.draw_fill(); + }, + }); } glm::vec4 NodeStrokePreview::stroke_draw_samples(