Share retained stroke texture binding helpers

This commit is contained in:
2026-06-13 10:30:13 +02:00
parent 3f071620dc
commit 323abdea57
5 changed files with 161 additions and 26 deletions

View File

@@ -18,6 +18,11 @@ agent or engineer to remove them without reconstructing context from chat.
## Recent Reductions ## Recent Reductions
- 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` main, pad,
and dual live-pass texture-input binding/unbinding intent now routes through
shared retained stroke execution helpers; sampler binding, concrete GL object
mapping, framebuffer ownership, and final draw execution remain retained in
`Canvas`.
- 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview` live-pass - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview` live-pass
sampler binding, dual/main pass texture binding, checkerboard/background sampler binding, dual/main pass texture binding, checkerboard/background
capture wrapping, and final preview copy-back now route through shared local capture wrapping, and final preview copy-back now route through shared local

View File

@@ -3111,6 +3111,10 @@ Results:
binding, checkerboard/background capture wrapping, and final preview binding, checkerboard/background capture wrapping, and final preview
copy-back now share named local helpers, while mixer state execution and copy-back now share named local helpers, while mixer state execution and
per-sample GL ordering remain in the preview node. per-sample GL ordering remain in the preview node.
- `Canvas::stroke_draw` main, pad, and dual live-pass texture-input
binding/unbinding intent now shares retained stroke execution helpers, while
sampler binding, concrete GL object mapping, framebuffer ownership, and final
draw execution remain in the legacy Canvas path.
- `Canvas::stroke_draw` pad-pass destination bind/copy/unbind ordering now - `Canvas::stroke_draw` pad-pass destination bind/copy/unbind ordering now
shares the retained stroke execution helper callback surface, while shader shares the retained stroke execution helper callback surface, while shader
setup, pad color selection, framebuffer ownership, and final OpenGL draw setup, pad color selection, framebuffer ownership, and final OpenGL draw

View File

@@ -509,6 +509,13 @@ Done Checks:
Progress Notes: Progress Notes:
- 2026-06-13: `Canvas::stroke_draw` main, pad, and dual live-pass
texture-input binding/unbinding intent now routes through retained stroke
execution helpers; sampler binding, concrete GL object mapping, framebuffer
ownership, and final draw execution remain local to `Canvas`. Next slice
should target the remaining sampler binding/teardown or the direct
semantic-input-to-GL mapping callbacks without reopening the landed dirty,
face-framebuffer, or pad helpers.
- 2026-06-13: `NodeStrokePreview::draw_stroke_immediate()` live-pass sampler - 2026-06-13: `NodeStrokePreview::draw_stroke_immediate()` live-pass sampler
binding, dual/main pass texture binding, checkerboard/background capture binding, dual/main pass texture binding, checkerboard/background capture
wrapping, and final preview copy-back now route through named local helpers; wrapping, and final preview copy-back now route through named local helpers;

View File

@@ -673,15 +673,50 @@ void Canvas::stroke_draw()
}); });
// DRAW MAIN BRUSH // DRAW MAIN BRUSH
constexpr std::array main_pass_texture_bindings {
set_active_texture_unit(0); pp::panopainter::LegacyCanvasStrokeTextureBinding {
brush->m_tip_texture->bind(); .input = pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip,
set_active_texture_unit(2); .slot = 0,
brush->m_pattern_texture ? },
brush->m_pattern_texture->bind() : pp::panopainter::LegacyCanvasStrokeTextureBinding {
unbind_texture_2d(); .input = pp::panopainter::LegacyCanvasStrokeTextureInput::pattern,
set_active_texture_unit(3); .slot = 2,
m_mixer.bindTexture(); },
pp::panopainter::LegacyCanvasStrokeTextureBinding {
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::mixer,
.slot = 3,
},
};
constexpr std::array main_pass_texture_unbindings {
pp::panopainter::LegacyCanvasStrokeTextureBinding {
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::mixer,
.slot = 3,
},
pp::panopainter::LegacyCanvasStrokeTextureBinding {
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip,
.slot = 0,
},
};
pp::panopainter::bind_legacy_canvas_stroke_texture_inputs(
main_pass_texture_bindings,
[&](auto input, int texture_slot) {
set_active_texture_unit(texture_slot);
switch (input) {
case pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip:
brush->m_tip_texture->bind();
break;
case pp::panopainter::LegacyCanvasStrokeTextureInput::pattern:
brush->m_pattern_texture ?
brush->m_pattern_texture->bind() :
unbind_texture_2d();
break;
case pp::panopainter::LegacyCanvasStrokeTextureInput::mixer:
m_mixer.bindTexture();
break;
case pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination:
break;
}
});
auto frames = stroke_draw_compute(*m_current_stroke); auto frames = stroke_draw_compute(*m_current_stroke);
@@ -718,10 +753,22 @@ void Canvas::stroke_draw()
}, },
m_tmp); m_tmp);
set_active_texture_unit(3); pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs(
m_mixer.unbindTexture(); main_pass_texture_unbindings,
set_active_texture_unit(0); [&](auto input, int texture_slot) {
brush->m_tip_texture->unbind(); set_active_texture_unit(texture_slot);
switch (input) {
case pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip:
brush->m_tip_texture->unbind();
break;
case pp::panopainter::LegacyCanvasStrokeTextureInput::mixer:
m_mixer.unbindTexture();
break;
case pp::panopainter::LegacyCanvasStrokeTextureInput::pattern:
case pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination:
break;
}
});
// pad stroke // pad stroke
// In order to mitigate color bleeding at the edge of shapes in transparent layers // In order to mitigate color bleeding at the edge of shapes in transparent layers
@@ -738,6 +785,12 @@ void Canvas::stroke_draw()
.uses_destination_feedback = copy_stroke_destination, .uses_destination_feedback = copy_stroke_destination,
}); });
const auto pad_faces = pp::panopainter::make_legacy_canvas_stroke_pad_faces(box_dirty, box_face); const auto pad_faces = pp::panopainter::make_legacy_canvas_stroke_pad_faces(box_dirty, box_face);
constexpr std::array pad_destination_texture_binding {
pp::panopainter::LegacyCanvasStrokeTextureBinding {
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination,
.slot = 1,
},
};
[[maybe_unused]] const auto pad_result = pp::panopainter::execute_legacy_canvas_stroke_pad_faces( [[maybe_unused]] const auto pad_result = pp::panopainter::execute_legacy_canvas_stroke_pad_faces(
pp::panopainter::LegacyCanvasStrokePadExecutionRequest { pp::panopainter::LegacyCanvasStrokePadExecutionRequest {
.context = "Canvas::stroke_draw", .context = "Canvas::stroke_draw",
@@ -753,8 +806,15 @@ void Canvas::stroke_draw()
m_tmp[face_index].bindFramebuffer(); m_tmp[face_index].bindFramebuffer();
}, },
.bind_destination_texture = [&](int face_index) { .bind_destination_texture = [&](int face_index) {
set_active_texture_unit(1); pp::panopainter::bind_legacy_canvas_stroke_texture_inputs(
m_tex[face_index].bind(); pad_destination_texture_binding,
[&](auto input, int texture_slot) {
if (input != pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination) {
return;
}
set_active_texture_unit(texture_slot);
m_tex[face_index].bind();
});
}, },
.copy_framebuffer_to_destination_texture = .copy_framebuffer_to_destination_texture =
[&](const pp::paint_renderer::CanvasStrokeCopyRegion& copy_region) { [&](const pp::paint_renderer::CanvasStrokeCopyRegion& copy_region) {
@@ -767,8 +827,15 @@ void Canvas::stroke_draw()
copy_region.height); copy_region.height);
}, },
.unbind_destination_texture = [&](int face_index) { .unbind_destination_texture = [&](int face_index) {
set_active_texture_unit(1); pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs(
m_tex[face_index].unbind(); pad_destination_texture_binding,
[&](auto input, int texture_slot) {
if (input != pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination) {
return;
}
set_active_texture_unit(texture_slot);
m_tex[face_index].unbind();
});
}, },
.draw_pad = [&] { .draw_pad = [&] {
m_brush_shape.draw_fill(); m_brush_shape.draw_fill();
@@ -784,11 +851,24 @@ void Canvas::stroke_draw()
{ {
const auto& dual_brush = m_dual_stroke->m_brush; const auto& dual_brush = m_dual_stroke->m_brush;
pp::panopainter::setup_legacy_stroke_dual_shader(stroke_material.dual_pass.uses_pattern); pp::panopainter::setup_legacy_stroke_dual_shader(stroke_material.dual_pass.uses_pattern);
set_active_texture_unit(0); constexpr std::array dual_pass_texture_bindings {
dual_brush->m_tip_texture ? pp::panopainter::LegacyCanvasStrokeTextureBinding {
dual_brush->m_tip_texture->bind() : .input = pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip,
unbind_texture_2d(); .slot = 0,
},
};
pp::panopainter::bind_legacy_canvas_stroke_texture_inputs(
dual_pass_texture_bindings,
[&](auto input, int texture_slot) {
if (input != pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip) {
return;
}
set_active_texture_unit(texture_slot);
dual_brush->m_tip_texture ?
dual_brush->m_tip_texture->bind() :
unbind_texture_2d();
});
auto frames_dual = stroke_draw_compute(*m_dual_stroke); auto frames_dual = stroke_draw_compute(*m_dual_stroke);
const std::array<bool, 6> include_dual_dirty = const std::array<bool, 6> include_dual_dirty =
SIXPLETTE(stroke_material.composite_pass.dual_blend_mode == 0); SIXPLETTE(stroke_material.composite_pass.dual_blend_mode == 0);
@@ -813,10 +893,17 @@ void Canvas::stroke_draw()
m_tmp_dual, m_tmp_dual,
true); true);
set_active_texture_unit(0); pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs(
dual_brush->m_tip_texture ? dual_pass_texture_bindings,
dual_brush->m_tip_texture->unbind() : [&](auto input, int texture_slot) {
unbind_texture_2d(); if (input != pp::panopainter::LegacyCanvasStrokeTextureInput::brush_tip) {
return;
}
set_active_texture_unit(texture_slot);
dual_brush->m_tip_texture ?
dual_brush->m_tip_texture->unbind() :
unbind_texture_2d();
});
} }
m_sampler_brush.unbind(); m_sampler_brush.unbind();

View File

@@ -37,6 +37,18 @@ struct LegacyStrokeSampleExecutionResult {
glm::vec4 dirty_bounds {}; glm::vec4 dirty_bounds {};
}; };
enum class LegacyCanvasStrokeTextureInput {
brush_tip,
stroke_destination,
pattern,
mixer,
};
struct LegacyCanvasStrokeTextureBinding {
LegacyCanvasStrokeTextureInput input = LegacyCanvasStrokeTextureInput::brush_tip;
int slot = 0;
};
struct LegacyCanvasStrokeFaceDirtyRequest { struct LegacyCanvasStrokeFaceDirtyRequest {
pp::renderer::Extent2D extent {}; pp::renderer::Extent2D extent {};
glm::vec4 previous_accumulated_dirty_box {}; glm::vec4 previous_accumulated_dirty_box {};
@@ -200,6 +212,26 @@ std::size_t execute_legacy_canvas_stroke_frame_faces(
return executed_faces; return executed_faces;
} }
template <typename BindTextureInput, std::size_t BindingCount>
inline void bind_legacy_canvas_stroke_texture_inputs(
const std::array<LegacyCanvasStrokeTextureBinding, BindingCount>& bindings,
BindTextureInput&& bind_texture_input)
{
for (const auto& binding : bindings) {
bind_texture_input(binding.input, binding.slot);
}
}
template <typename UnbindTextureInput, std::size_t BindingCount>
inline void unbind_legacy_canvas_stroke_texture_inputs(
const std::array<LegacyCanvasStrokeTextureBinding, BindingCount>& bindings,
UnbindTextureInput&& unbind_texture_input)
{
for (const auto& binding : bindings) {
unbind_texture_input(binding.input, binding.slot);
}
}
template <typename Frames, typename BeginFrame, typename BeginFace, typename ExecuteSample, typename FinishFace> template <typename Frames, typename BeginFrame, typename BeginFace, typename ExecuteSample, typename FinishFace>
std::size_t execute_legacy_canvas_stroke_frame_samples( std::size_t execute_legacy_canvas_stroke_frame_samples(
Frames&& frames, Frames&& frames,