Narrow retained canvas stroke execution helpers

This commit is contained in:
2026-06-13 06:52:09 +02:00
parent d2fb4057ab
commit 33e62a1c4a
7 changed files with 418 additions and 127 deletions

View File

@@ -753,45 +753,41 @@ void Canvas::stroke_draw()
{
set_active_texture_unit(1);
}
for (int i = 0; i < 6; i++)
{
if (!box_dirty[i])
continue;
const auto pad_region = pp::panopainter::plan_legacy_canvas_stroke_pad_region(
pp::panopainter::LegacyCanvasStrokePadRegionRequest {
.extent = stroke_extent,
.pass_dirty_box = box_face[i],
});
if (!pad_region.has_pixels)
continue;
// B(xw)--(zw)C box
// | // | coordinates
// A(xy)--(zy)D mapping
std::array<vertex_t, 6> pad_quad = {
vertex_t({pad_region.ndc_quad[0].x, pad_region.ndc_quad[0].y}), // A
vertex_t({pad_region.ndc_quad[1].x, pad_region.ndc_quad[1].y}), // B
vertex_t({pad_region.ndc_quad[2].x, pad_region.ndc_quad[2].y}), // C
vertex_t({pad_region.ndc_quad[3].x, pad_region.ndc_quad[3].y}), // A
vertex_t({pad_region.ndc_quad[4].x, pad_region.ndc_quad[4].y}), // C
vertex_t({pad_region.ndc_quad[5].x, pad_region.ndc_quad[5].y}), // D
};
m_brush_shape.update_vertices(pad_quad.data(), pad_quad.size());
const std::array<pp::panopainter::LegacyCanvasStrokePadFace, 6> pad_faces = {
pp::panopainter::LegacyCanvasStrokePadFace { .index = 0, .dirty = box_dirty[0], .pass_dirty_box = box_face[0] },
pp::panopainter::LegacyCanvasStrokePadFace { .index = 1, .dirty = box_dirty[1], .pass_dirty_box = box_face[1] },
pp::panopainter::LegacyCanvasStrokePadFace { .index = 2, .dirty = box_dirty[2], .pass_dirty_box = box_face[2] },
pp::panopainter::LegacyCanvasStrokePadFace { .index = 3, .dirty = box_dirty[3], .pass_dirty_box = box_face[3] },
pp::panopainter::LegacyCanvasStrokePadFace { .index = 4, .dirty = box_dirty[4], .pass_dirty_box = box_face[4] },
pp::panopainter::LegacyCanvasStrokePadFace { .index = 5, .dirty = box_dirty[5], .pass_dirty_box = box_face[5] },
};
[[maybe_unused]] const auto pad_result = pp::panopainter::execute_legacy_canvas_stroke_pad_faces(
pp::panopainter::LegacyCanvasStrokePadExecutionRequest {
.context = "Canvas::stroke_draw",
.extent = stroke_extent,
.faces = pad_faces,
.execute_face =
[&](int face_index,
const pp::panopainter::LegacyCanvasStrokePadRegionResult& pad_region,
std::span<vertex_t> pad_quad) {
m_brush_shape.update_vertices(pad_quad.data(), pad_quad.size());
m_tmp[i].bindFramebuffer();
if (copy_stroke_destination)
{
m_tex[i].bind();
copy_framebuffer_to_texture_2d(
pad_region.copy_region.x,
pad_region.copy_region.y,
pad_region.copy_region.x,
pad_region.copy_region.y,
pad_region.copy_region.width,
pad_region.copy_region.height);
}
m_brush_shape.draw_fill();
m_tmp[i].unbindFramebuffer();
}
m_tmp[face_index].bindFramebuffer();
if (copy_stroke_destination)
{
m_tex[face_index].bind();
copy_framebuffer_to_texture_2d(
pad_region.copy_region.x,
pad_region.copy_region.y,
pad_region.copy_region.x,
pad_region.copy_region.y,
pad_region.copy_region.width,
pad_region.copy_region.height);
}
m_brush_shape.draw_fill();
m_tmp[face_index].unbindFramebuffer();
},
});
if (copy_stroke_destination)
{
unbind_texture_2d();
@@ -1047,104 +1043,128 @@ void Canvas::stroke_commit()
m_tex2[i].unbind();
},
.bind_commit_inputs = [&](int i) {
m_tmp[i].bindTexture();
set_active_texture_unit(1);
m_tex2[i].bind();
m_sampler.bind(0);
m_sampler_nearest.bind(1);
m_sampler.bind(2);
m_sampler.bind(3);
m_sampler_stencil.bind(4);
},
.execute_erase_composite = [&](int i) {
pp::panopainter::setup_legacy_stroke_erase_shader(
pp::panopainter::LegacyStrokeEraseUniforms {
.mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f),
.texture_slot = 0,
.stroke_texture_slot = 1,
.mask_texture_slot = 2,
.alpha = 1.0f,
.mask_enabled = m_smask_active,
pp::panopainter::bind_legacy_canvas_stroke_commit_inputs(
sequence,
[&](int texture_slot) {
set_active_texture_unit(texture_slot);
},
[&](pp::paint_renderer::CanvasStrokeCommitTextureRole role) {
switch (role) {
case pp::paint_renderer::CanvasStrokeCommitTextureRole::layer_scratch:
m_tex2[i].bind();
break;
case pp::paint_renderer::CanvasStrokeCommitTextureRole::stroke:
m_tmp[i].bindTexture();
break;
case pp::paint_renderer::CanvasStrokeCommitTextureRole::selection_mask:
m_smask.rtt(i).bindTexture();
break;
case pp::paint_renderer::CanvasStrokeCommitTextureRole::dual_stroke:
m_tmp_dual[i].bindTexture();
break;
case pp::paint_renderer::CanvasStrokeCommitTextureRole::pattern:
b->m_pattern_texture ? b->m_pattern_texture->bind() : unbind_texture_2d();
break;
}
},
[&](pp::paint_renderer::CanvasStrokeCommitTextureRole role, int texture_slot) {
switch (role) {
case pp::paint_renderer::CanvasStrokeCommitTextureRole::layer_scratch:
m_sampler.bind(texture_slot);
break;
case pp::paint_renderer::CanvasStrokeCommitTextureRole::stroke:
m_sampler_nearest.bind(texture_slot);
break;
case pp::paint_renderer::CanvasStrokeCommitTextureRole::selection_mask:
case pp::paint_renderer::CanvasStrokeCommitTextureRole::dual_stroke:
m_sampler.bind(texture_slot);
break;
case pp::paint_renderer::CanvasStrokeCommitTextureRole::pattern:
m_sampler_stencil.bind(texture_slot);
break;
}
});
set_active_texture_unit(0);
m_tex2[i].bind();
set_active_texture_unit(1);
m_tmp[i].bindTexture();
set_active_texture_unit(2);
m_smask.rtt(i).bindTexture();
m_plane.draw_fill();
m_smask.rtt(i).unbindTexture();
set_active_texture_unit(1);
m_tmp[i].unbindTexture();
set_active_texture_unit(0);
m_tex2[i].unbind();
},
.execute_paint_composite = [&](int i) {
.execute_erase_composite = [&](int) {
pp::panopainter::execute_legacy_canvas_stroke_commit_erase(
[&]() {
pp::panopainter::setup_legacy_stroke_erase_shader(
pp::panopainter::LegacyStrokeEraseUniforms {
.mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f),
.texture_slot = 0,
.stroke_texture_slot = 1,
.mask_texture_slot = 2,
.alpha = 1.0f,
.mask_enabled = m_smask_active,
});
},
[&]() {
m_plane.draw_fill();
});
},
.execute_paint_composite = [&](int) {
glm::vec2 patt_scale = glm::vec2(b->m_pattern_scale);
if (b->m_pattern_flipx) patt_scale.x *= -1.f;
if (b->m_pattern_flipy) patt_scale.y *= -1.f;
pp::panopainter::setup_legacy_stroke_composite_shader(
pp::panopainter::LegacyStrokeCompositeUniforms {
.resolution = m_size,
.pattern = {
.scale = patt_scale,
.invert = static_cast<float>(b->m_pattern_invert),
.brightness = b->m_pattern_brightness,
.contrast = b->m_pattern_contrast,
.depth = b->m_pattern_depth,
.blend_mode = b->m_pattern_blend_mode,
.offset = m_pattern_offset,
},
.mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f),
.layer_alpha = 1.0f,
.alpha_lock = m_layers[m_current_layer_idx]->m_alpha_locked,
.mask_enabled = m_smask_active,
.use_fragcoord = false,
.blend_mode = b->m_blend_mode,
.use_dual = stroke_material.composite_pass.use_dual,
.dual_blend_mode = stroke_material.composite_pass.dual_blend_mode,
.dual_alpha = stroke_material.composite_pass.dual_alpha,
.use_pattern = stroke_material.composite_pass.use_pattern,
pp::panopainter::execute_legacy_canvas_stroke_commit_paint(
[&]() {
pp::panopainter::setup_legacy_stroke_composite_shader(
pp::panopainter::LegacyStrokeCompositeUniforms {
.resolution = m_size,
.pattern = {
.scale = patt_scale,
.invert = static_cast<float>(b->m_pattern_invert),
.brightness = b->m_pattern_brightness,
.contrast = b->m_pattern_contrast,
.depth = b->m_pattern_depth,
.blend_mode = b->m_pattern_blend_mode,
.offset = m_pattern_offset,
},
.mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f),
.layer_alpha = 1.0f,
.alpha_lock = m_layers[m_current_layer_idx]->m_alpha_locked,
.mask_enabled = m_smask_active,
.use_fragcoord = false,
.blend_mode = b->m_blend_mode,
.use_dual = stroke_material.composite_pass.use_dual,
.dual_blend_mode = stroke_material.composite_pass.dual_blend_mode,
.dual_alpha = stroke_material.composite_pass.dual_alpha,
.use_pattern = stroke_material.composite_pass.use_pattern,
});
},
[&]() {
m_plane.draw_fill();
});
set_active_texture_unit(0);
m_tex2[i].bind();
set_active_texture_unit(1);
m_tmp[i].bindTexture();
set_active_texture_unit(2);
m_smask.rtt(i).bindTexture();
set_active_texture_unit(3);
if (stroke_material.composite_pass.use_dual)
m_tmp_dual[i].bindTexture();
set_active_texture_unit(4);
b->m_pattern_texture ?
b->m_pattern_texture->bind() :
unbind_texture_2d();
m_plane.draw_fill();
set_active_texture_unit(3);
if (stroke_material.composite_pass.use_dual)
m_tmp_dual[i].unbindTexture();
set_active_texture_unit(2);
m_smask.rtt(i).unbindTexture();
set_active_texture_unit(1);
m_tmp[i].unbindTexture();
set_active_texture_unit(0);
m_tex2[i].unbind();
},
.copy_committed_to_dilate_source = [&](int i) {
// Dilate borders to avoid interpolation bleeding
pp::panopainter::setup_legacy_stroke_dilate_shader(
pp::panopainter::LegacyStrokeDilateUniforms {
.mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f),
pp::panopainter::copy_legacy_canvas_stroke_commit_to_dilate_source(
sequence,
[&]() {
// Dilate borders to avoid interpolation bleeding
pp::panopainter::setup_legacy_stroke_dilate_shader(
pp::panopainter::LegacyStrokeDilateUniforms {
.mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f),
});
},
[&](int texture_slot) {
set_active_texture_unit(texture_slot);
},
[&]() {
m_tex2[i].bind();
},
[&](int src_x, int src_y, int dst_x, int dst_y, int width, int height) {
copy_framebuffer_to_texture_2d(src_x, src_y, dst_x, dst_y, width, height);
},
pp::panopainter::LegacyCanvasStrokeCommitCopyExtent {
.width = m_width,
.height = m_height,
});
set_active_texture_unit(0);
m_tex2[i].bind();
copy_framebuffer_to_texture_2d(0, 0, 0, 0, m_width, m_height);
},
.execute_commit_dilate = [&](int) {
m_plane.draw_fill();
pp::panopainter::execute_legacy_canvas_stroke_commit_dilate([&]() {
m_plane.draw_fill();
});
},
.unbind_layer_framebuffer = [&](int i) {
m_layers[m_current_layer_idx]->rtt(i).unbindFramebuffer();

View File

@@ -4,6 +4,7 @@
#include <algorithm>
#include <array>
#include <cstdint>
#include <functional>
#include <string_view>
@@ -46,12 +47,100 @@ struct LegacyCanvasStrokeCommitResult {
int committed_faces = 0;
};
struct LegacyCanvasStrokeCommitCopyExtent {
int width = 0;
int height = 0;
};
[[nodiscard]] inline std::size_t legacy_canvas_stroke_commit_step_count(
const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence) noexcept
{
return std::min(sequence.step_count, sequence.steps.size());
}
[[nodiscard]] inline std::size_t legacy_canvas_stroke_commit_texture_binding_count(
const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence) noexcept
{
return std::min(sequence.texture_binding_count, sequence.texture_bindings.size());
}
[[nodiscard]] inline int legacy_canvas_stroke_commit_texture_slot(
const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence,
pp::paint_renderer::CanvasStrokeCommitTextureRole role) noexcept
{
const auto binding_count = legacy_canvas_stroke_commit_texture_binding_count(sequence);
for (std::size_t binding_index = 0; binding_index < binding_count; ++binding_index) {
const auto& binding = sequence.texture_bindings[binding_index];
if (binding.role == role) {
return static_cast<int>(binding.slot);
}
}
return -1;
}
template <typename SetActiveTextureUnit, typename BindTextureRole, typename BindSamplerRole>
inline void bind_legacy_canvas_stroke_commit_inputs(
const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence,
SetActiveTextureUnit&& set_active_texture_unit,
BindTextureRole&& bind_texture_role,
BindSamplerRole&& bind_sampler_role)
{
const auto binding_count = legacy_canvas_stroke_commit_texture_binding_count(sequence);
for (std::size_t binding_index = 0; binding_index < binding_count; ++binding_index) {
const auto& binding = sequence.texture_bindings[binding_index];
set_active_texture_unit(static_cast<int>(binding.slot));
bind_texture_role(binding.role);
bind_sampler_role(binding.role, static_cast<int>(binding.slot));
}
}
template <typename SetupShader, typename DrawPlane>
inline void execute_legacy_canvas_stroke_commit_erase(
SetupShader&& setup_shader,
DrawPlane&& draw_plane)
{
setup_shader();
draw_plane();
}
template <typename SetupShader, typename DrawPlane>
inline void execute_legacy_canvas_stroke_commit_paint(
SetupShader&& setup_shader,
DrawPlane&& draw_plane)
{
setup_shader();
draw_plane();
}
template <typename SetupShader, typename SetActiveTextureUnit, typename BindLayerScratch, typename CopyFramebufferToTexture>
inline void copy_legacy_canvas_stroke_commit_to_dilate_source(
const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence,
SetupShader&& setup_shader,
SetActiveTextureUnit&& set_active_texture_unit,
BindLayerScratch&& bind_layer_scratch,
CopyFramebufferToTexture&& copy_framebuffer_to_texture,
LegacyCanvasStrokeCommitCopyExtent extent)
{
const auto layer_scratch_slot = legacy_canvas_stroke_commit_texture_slot(
sequence,
pp::paint_renderer::CanvasStrokeCommitTextureRole::layer_scratch);
if (layer_scratch_slot < 0 || extent.width <= 0 || extent.height <= 0) {
return;
}
setup_shader();
set_active_texture_unit(layer_scratch_slot);
bind_layer_scratch();
copy_framebuffer_to_texture(0, 0, 0, 0, extent.width, extent.height);
}
template <typename DrawPlane>
inline void execute_legacy_canvas_stroke_commit_dilate(DrawPlane&& draw_plane)
{
draw_plane();
}
[[nodiscard]] inline bool legacy_canvas_stroke_commit_callbacks_ready(
const LegacyCanvasStrokeCommitCallbacks& callbacks) noexcept
{

View File

@@ -64,6 +64,24 @@ struct LegacyCanvasStrokePadRegionResult {
std::array<pp::paint_renderer::CanvasStrokePoint, 6> ndc_quad {};
};
struct LegacyCanvasStrokePadFace {
int index = 0;
bool dirty = false;
glm::vec4 pass_dirty_box {};
};
struct LegacyCanvasStrokePadExecutionRequest {
std::string_view context;
pp::renderer::Extent2D extent {};
std::span<const LegacyCanvasStrokePadFace> faces;
std::function<void(int, const LegacyCanvasStrokePadRegionResult&, std::span<vertex_t>)> execute_face;
};
struct LegacyCanvasStrokePadExecutionResult {
bool ok = false;
std::size_t padded_faces = 0;
};
struct LegacyCanvasStrokeComputeRequest {
StrokeSample previous_sample {};
std::span<const StrokeSample> samples;
@@ -228,6 +246,47 @@ std::size_t execute_legacy_canvas_stroke_frame_faces(
};
}
[[nodiscard]] inline LegacyCanvasStrokePadExecutionResult execute_legacy_canvas_stroke_pad_faces(
const LegacyCanvasStrokePadExecutionRequest& request)
{
LegacyCanvasStrokePadExecutionResult result;
if (request.extent.width == 0U ||
request.extent.height == 0U ||
!request.execute_face) {
return result;
}
for (const auto& face : request.faces) {
if (!face.dirty) {
continue;
}
const auto pad_region = plan_legacy_canvas_stroke_pad_region(
LegacyCanvasStrokePadRegionRequest {
.extent = request.extent,
.pass_dirty_box = face.pass_dirty_box,
});
if (!pad_region.has_pixels) {
continue;
}
std::array<vertex_t, 6> pad_quad = {
vertex_t({ pad_region.ndc_quad[0].x, pad_region.ndc_quad[0].y }),
vertex_t({ pad_region.ndc_quad[1].x, pad_region.ndc_quad[1].y }),
vertex_t({ pad_region.ndc_quad[2].x, pad_region.ndc_quad[2].y }),
vertex_t({ pad_region.ndc_quad[3].x, pad_region.ndc_quad[3].y }),
vertex_t({ pad_region.ndc_quad[4].x, pad_region.ndc_quad[4].y }),
vertex_t({ pad_region.ndc_quad[5].x, pad_region.ndc_quad[5].y }),
};
request.execute_face(face.index, pad_region, pad_quad);
++result.padded_faces;
}
result.ok = true;
return result;
}
[[nodiscard]] inline LegacyStrokeSampleExecutionResult execute_legacy_canvas_stroke_sample(
const LegacyStrokeSampleExecutionRequest& request)
{