Extract stroke dirty bounds planning

This commit is contained in:
2026-06-13 04:35:14 +02:00
parent 458f9bef0c
commit 36861cbf97
8 changed files with 509 additions and 40 deletions

View File

@@ -84,6 +84,29 @@ pp::paint_renderer::CanvasStrokeMaterialPlan canvas_stroke_material_plan(
});
}
pp::renderer::Extent2D canvas_stroke_extent(int width, int height) noexcept
{
return pp::renderer::Extent2D {
.width = static_cast<std::uint32_t>(std::max(width, 0)),
.height = static_cast<std::uint32_t>(std::max(height, 0)),
};
}
pp::paint_renderer::CanvasStrokeBox canvas_stroke_box(glm::vec4 box) noexcept
{
return pp::paint_renderer::CanvasStrokeBox {
.min_x = box.x,
.min_y = box.y,
.max_x = box.z,
.max_y = box.w,
};
}
glm::vec4 glm_box(pp::paint_renderer::CanvasStrokeBox box) noexcept
{
return glm::vec4(box.min_x, box.min_y, box.max_x, box.max_y);
}
pp::paint_renderer::CanvasBlendGatePlan draw_merge_blend_gate_plan(
int width,
int height,
@@ -530,11 +553,21 @@ glm::vec4 Canvas::stroke_draw_samples(
P = triangulate_simple(P);
}
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",
.target_size = { m_width, m_height },
.vertices = P,
.sample_points = sample_points,
.copy_stroke_destination = copy_stroke_destination,
.bind_destination_texture = [&] {
set_active_texture_unit(1);
@@ -676,6 +709,7 @@ void Canvas::stroke_draw()
const auto stroke_rasterization = canvas_stroke_rasterization_plan(m_width, m_height);
const bool copy_stroke_destination = stroke_rasterization.copy_stroke_destination;
const auto stroke_material = canvas_stroke_material_plan(*brush, copy_stroke_destination);
const auto stroke_extent = canvas_stroke_extent(m_width, m_height);
apply_canvas_capability(blend_state(), false);
pp::panopainter::setup_legacy_stroke_shader(
@@ -745,8 +779,16 @@ void Canvas::stroke_draw()
m_tmp[i].unbindFramebuffer();
m_dirty_box[i] = glm::clamp(box_union(m_dirty_box[i], box_sample), glm::vec4(0), glm::vec4(m_width));
box_face[i] = box_union(box_face[i], box_sample);
const auto dirty_update = pp::paint_renderer::plan_canvas_stroke_face_dirty_update(
pp::paint_renderer::CanvasStrokeFaceDirtyUpdateRequest {
.extent = stroke_extent,
.previous_accumulated_dirty_box = canvas_stroke_box(m_dirty_box[i]),
.previous_pass_dirty_box = canvas_stroke_box(box_face[i]),
.sample_dirty_box = canvas_stroke_box(box_sample),
.include_in_committed_dirty_box = true,
});
m_dirty_box[i] = glm_box(dirty_update.accumulated_dirty_box);
box_face[i] = glm_box(dirty_update.pass_dirty_box);
// TODO: maybe average color?
pad_color = f.col;
}
@@ -779,34 +821,37 @@ void Canvas::stroke_draw()
{
if (!box_dirty[i])
continue;
const auto& b = box_face[i];
glm::vec2 box_size = zw(b) - xy(b);
glm::vec2 pad = { 20, 20 }; // pixels padding
glm::vec4 pad_box = {
glm::max({0, 0}, xy(b) - pad) * 2.f / m_size - 1.f,
glm::min(m_size, zw(b) + pad) * 2.f / m_size - 1.f
};
const auto pad_region = pp::paint_renderer::plan_canvas_stroke_pad_region(
pp::paint_renderer::CanvasStrokePadRegionRequest {
.extent = stroke_extent,
.pass_dirty_box = canvas_stroke_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_box.x, pad_box.y}), // A
vertex_t({pad_box.x, pad_box.w}), // B
vertex_t({pad_box.z, pad_box.w}), // C
vertex_t({pad_box.x, pad_box.y}), // A
vertex_t({pad_box.z, pad_box.w}), // C
vertex_t({pad_box.z, pad_box.y}), // D
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());
m_tmp[i].bindFramebuffer();
if (copy_stroke_destination)
{
glm::vec2 o = glm::max({0, 0}, xy(b) - pad);
glm::vec2 sz = glm::min(m_size, zw(b) + pad) - o;
m_tex[i].bind();
if (sz.x > 0 && sz.y > 0)
copy_framebuffer_to_texture_2d(o.x, o.y, o.x, o.y, sz.x, sz.y);
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();
@@ -845,8 +890,16 @@ void Canvas::stroke_draw()
m_tmp_dual[i].unbindFramebuffer();
// this mode overflows the main brush boundries
if (stroke_material.composite_pass.dual_blend_mode == 0)
m_dirty_box[i] = glm::clamp(box_union(m_dirty_box[i], box_sample), glm::vec4(0), glm::vec4(m_width));
const auto dirty_update = pp::paint_renderer::plan_canvas_stroke_face_dirty_update(
pp::paint_renderer::CanvasStrokeFaceDirtyUpdateRequest {
.extent = stroke_extent,
.previous_accumulated_dirty_box = canvas_stroke_box(m_dirty_box[i]),
.previous_pass_dirty_box = canvas_stroke_box(box_sample),
.sample_dirty_box = canvas_stroke_box(box_sample),
.include_in_committed_dirty_box =
stroke_material.composite_pass.dual_blend_mode == 0,
});
m_dirty_box[i] = glm_box(dirty_update.accumulated_dirty_box);
}
}
}