Share retained stroke sample and mix helpers

This commit is contained in:
2026-06-13 10:46:58 +02:00
parent 2fadfdcd3e
commit 78790e9b52
6 changed files with 145 additions and 48 deletions

View File

@@ -23,6 +23,16 @@ agent or engineer to remove them without reconstructing context from chat.
retained stroke execution helpers; concrete GL object mapping, framebuffer retained stroke execution helpers; concrete GL object mapping, framebuffer
ownership, shader timing, and final draw execution remain retained in ownership, shader timing, and final draw execution remain retained in
`Canvas`. `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` - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::stroke_draw_samples`
now routes destination bind/unbind, framebuffer copy callback wrapping, now routes destination bind/unbind, framebuffer copy callback wrapping,
sample-point assembly, and brush-vertex upload/draw through one local helper; sample-point assembly, and brush-vertex upload/draw through one local helper;

View File

@@ -3123,6 +3123,15 @@ Results:
texture-input dispatch now shares retained stroke execution helpers, while texture-input dispatch now shares retained stroke execution helpers, while
concrete GL object mapping, framebuffer ownership, shader timing, and final concrete GL object mapping, framebuffer ownership, shader timing, and final
draw execution remain in the legacy Canvas path. 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 - `pp_paint_renderer_stroke_execution_tests` now covers retained stroke texture
input binding order, sample execution destination-copy behavior, live-pass input binding order, sample execution destination-copy behavior, live-pass
face-framebuffer dirty tracking, and pad-face destination-copy behavior face-framebuffer dirty tracking, and pad-face destination-copy behavior

View File

@@ -509,6 +509,20 @@ Done Checks:
Progress Notes: 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 - 2026-06-13: `pp_paint_renderer_stroke_execution_tests` now also covers
retained texture-dispatch activation order and sampler-dispatch routing retained texture-dispatch activation order and sampler-dispatch routing
across brush tip, destination, pattern, and mixer helper inputs. Next test across brush tip, destination, pattern, and mixer helper inputs. Next test

View File

@@ -535,25 +535,11 @@ glm::vec4 Canvas::stroke_draw_samples(
std::vector<vertex_t>& P, std::vector<vertex_t>& P,
bool copy_stroke_destination) bool copy_stroke_destination)
{ {
if (P.size() != 3 && P.size() != 4) { const auto result = pp::panopainter::execute_legacy_canvas_stroke_sample_polygon(
P = triangulate_simple(P); pp::panopainter::LegacyStrokeSamplePolygonExecutionRequest {
}
std::vector<pp::paint_renderer::CanvasStrokePoint> 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 {
.context = "Canvas::stroke_draw_samples", .context = "Canvas::stroke_draw_samples",
.target_size = { m_width, m_height }, .target_size = { m_width, m_height },
.vertices = P, .polygon_vertices = P,
.sample_points = sample_points,
.copy_stroke_destination = copy_stroke_destination, .copy_stroke_destination = copy_stroke_destination,
.bind_destination_texture = [&] { .bind_destination_texture = [&] {
set_active_texture_unit(1); set_active_texture_unit(1);

View File

@@ -37,6 +37,18 @@ struct LegacyStrokeSampleExecutionResult {
glm::vec4 dirty_bounds {}; glm::vec4 dirty_bounds {};
}; };
struct LegacyStrokeSamplePolygonExecutionRequest {
std::string_view context;
glm::vec2 target_size {};
std::span<const vertex_t> polygon_vertices;
bool copy_stroke_destination = false;
std::function<void()> bind_destination_texture;
std::function<void(int, int, int, int, int, int)> copy_framebuffer_to_destination_texture;
std::function<void()> unbind_destination_texture;
std::function<void(std::span<const vertex_t>)> upload_brush_vertices;
std::function<void()> draw_brush_shape;
};
enum class LegacyCanvasStrokeTextureInput { enum class LegacyCanvasStrokeTextureInput {
brush_tip, brush_tip,
stroke_destination, stroke_destination,
@@ -236,6 +248,7 @@ std::size_t execute_legacy_canvas_stroke_frame_faces(
} }
template <typename BindTextureInput, std::size_t BindingCount> template <typename BindTextureInput, std::size_t BindingCount>
requires std::invocable<BindTextureInput&, LegacyCanvasStrokeTextureInput, int>
inline void bind_legacy_canvas_stroke_texture_inputs( inline void bind_legacy_canvas_stroke_texture_inputs(
const std::array<LegacyCanvasStrokeTextureBinding, BindingCount>& bindings, const std::array<LegacyCanvasStrokeTextureBinding, BindingCount>& bindings,
BindTextureInput&& bind_texture_input) BindTextureInput&& bind_texture_input)
@@ -246,6 +259,7 @@ inline void bind_legacy_canvas_stroke_texture_inputs(
} }
template <typename UnbindTextureInput, std::size_t BindingCount> template <typename UnbindTextureInput, std::size_t BindingCount>
requires std::invocable<UnbindTextureInput&, LegacyCanvasStrokeTextureInput, int>
inline void unbind_legacy_canvas_stroke_texture_inputs( inline void unbind_legacy_canvas_stroke_texture_inputs(
const std::array<LegacyCanvasStrokeTextureBinding, BindingCount>& bindings, const std::array<LegacyCanvasStrokeTextureBinding, BindingCount>& bindings,
UnbindTextureInput&& unbind_texture_input) UnbindTextureInput&& unbind_texture_input)
@@ -874,4 +888,39 @@ template <typename ExecuteSample, typename BeginFace, typename PrepareDirtyReque
return result; return result;
} }
[[nodiscard]] inline LegacyStrokeSampleExecutionResult execute_legacy_canvas_stroke_sample_polygon(
const LegacyStrokeSamplePolygonExecutionRequest& request)
{
std::vector<vertex_t> 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<pp::paint_renderer::CanvasStrokePoint> 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 } // namespace pp::panopainter

View File

@@ -124,6 +124,52 @@ struct StrokePreviewCompositePassInputs {
std::function<void()> draw_composite; std::function<void()> 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<void()> 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<int>(inputs.scissor_min.x),
static_cast<int>(inputs.scissor_min.y),
static_cast<int>(inputs.scissor_size.x),
static_cast<int>(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) void execute_stroke_preview_final_composite_pass(const StrokePreviewCompositePassInputs& inputs)
{ {
pp::panopainter::execute_legacy_stroke_preview_final_composite( 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) 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<int>(bb_min.x),
static_cast<int>(bb_min.y),
static_cast<int>(bb_sz.x),
static_cast<int>(bb_sz.y));
const auto& b = m_brush; const auto& b = m_brush;
glm::vec2 patt_scale = glm::vec2(b->m_pattern_scale); glm::vec2 patt_scale = glm::vec2(b->m_pattern_scale);
if (b->m_pattern_flipx) patt_scale.x *= -1.f; 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, .use_pattern = material.composite_pass.use_pattern,
}); });
m_sampler_linear.bind(0); execute_stroke_preview_mix_pass(
set_active_texture_unit(0U); StrokePreviewMixPassInputs {
m_tex_background.bind(); .scissor_min = bb_min,
set_active_texture_unit(1U); .scissor_size = bb_sz,
m_rtt.bindTexture(); .mixer_rtt = m_rtt_mixer,
set_active_texture_unit(3U); .background_texture = m_tex_background,
m_tex_dual.bind(); .stroke_rtt = m_rtt,
set_active_texture_unit(4U); .dual_texture = m_tex_dual,
b->m_pattern_texture ? .brush = *b,
b->m_pattern_texture->bind() : .linear_sampler = m_sampler_linear,
unbind_texture_2d(); .draw_mix = [&] {
m_plane.draw_fill(); m_plane.draw_fill();
},
m_rtt_mixer.unbindFramebuffer(); });
gl.restore();
} }
glm::vec4 NodeStrokePreview::stroke_draw_samples( glm::vec4 NodeStrokePreview::stroke_draw_samples(