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

@@ -1,7 +1,7 @@
# Modernization Debt Log # Modernization Debt Log
Status: live Status: live
Last updated: 2026-06-06 Last updated: 2026-06-13
Every shortcut, temporary adapter, retained vendored dependency, skipped Every shortcut, temporary adapter, retained vendored dependency, skipped
platform gate, compatibility shim, or incomplete automation path must be platform gate, compatibility shim, or incomplete automation path must be
@@ -18,6 +18,16 @@ 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_commit` retained
commit input texture/sampler binding, erase/composite draw dispatch,
committed-copy-to-dilate-source, and dilate draw now route through
`legacy_canvas_stroke_commit_services.h`; Canvas still owns history
readback, `ActionStroke` population, layer dirty-box mutation, and retained
RTT/framebuffer ownership.
- 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` pad-pass
dirty-face iteration, pad-region planning, and NDC quad assembly now route
through a retained stroke execution helper callback boundary; Canvas still
owns framebuffer copies, brush-shape uploads, and draw execution.
- 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` current and - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` current and
dual stroke frame-face traversal now routes through the retained stroke dual stroke frame-face traversal now routes through the retained stroke
execution helper; framebuffer binding, shader uniform timing, dirty-box execution helper; framebuffer binding, shader uniform timing, dirty-box

View File

@@ -1,7 +1,7 @@
# PanoPainter Modernization Roadmap # PanoPainter Modernization Roadmap
Status: live Status: live
Last updated: 2026-06-12 Last updated: 2026-06-13
This is the living roadmap for modernizing PanoPainter into independently This is the living roadmap for modernizing PanoPainter into independently
testable C++23 components while retaining all existing functionality. Keep this testable C++23 components while retaining all existing functionality. Keep this
@@ -3083,6 +3083,15 @@ Results:
execution helper wrapping `pp_paint_renderer`, while pad color selection, execution helper wrapping `pp_paint_renderer`, while pad color selection,
dirty-face iteration, framebuffer copies, quad upload, and draw execution dirty-face iteration, framebuffer copies, quad upload, and draw execution
remain retained. remain retained.
- `Canvas::stroke_draw` pad-pass dirty-face iteration, pad-region planning, and
NDC quad assembly now share a retained stroke execution helper callback
boundary, while Canvas still owns framebuffer copies, brush-shape uploads,
and draw execution.
- `Canvas::stroke_commit` retained commit input texture/sampler binding,
erase/composite draw dispatch, committed-copy to the dilate scratch texture,
and dilate draw now share `legacy_canvas_stroke_commit_services.h`; history
readback, `ActionStroke` population, layer dirty-box mutation, and retained
RTT/framebuffer ownership remain in the legacy Canvas path.
- `Canvas::stroke_draw_compute` frame planning now shares the retained stroke - `Canvas::stroke_draw_compute` frame planning now shares the retained stroke
execution helper for brush-quad construction, mixer feedback bounds, 2D/3D execution helper for brush-quad construction, mixer feedback bounds, 2D/3D
projection selection intent, and frame assembly, while legacy projection projection selection intent, and frame assembly, while legacy projection

View File

@@ -1,7 +1,7 @@
# Modernization Task Tracker # Modernization Task Tracker
Status: live Status: live
Last updated: 2026-06-12 Last updated: 2026-06-13
This file turns the modernization roadmap into small, measurable work items. This file turns the modernization roadmap into small, measurable work items.
The roadmap explains direction, the debt log explains why shortcuts remain, and The roadmap explains direction, the debt log explains why shortcuts remain, and
@@ -509,6 +509,11 @@ Done Checks:
Progress Notes: Progress Notes:
- 2026-06-13: `Canvas::stroke_commit` now routes retained commit input
texture/sampler binding, erase/composite draw dispatch, committed-copy, and
dilate draw through `legacy_canvas_stroke_commit_services.h`; history
readback, `ActionStroke` population, and layer dirty-box mutation remain
local to Canvas.
- 2026-06-13: `pp_paint_renderer` owns tested Canvas stroke commit sequencing - 2026-06-13: `pp_paint_renderer` owns tested Canvas stroke commit sequencing
and commit texture slot intent. Next slice should wire and commit texture slot intent. Next slice should wire
`legacy_canvas_stroke_commit_services.h` into `Canvas::stroke_commit` while `legacy_canvas_stroke_commit_services.h` into `Canvas::stroke_commit` while

View File

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

View File

@@ -4,6 +4,7 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cstdint>
#include <functional> #include <functional>
#include <string_view> #include <string_view>
@@ -46,12 +47,100 @@ struct LegacyCanvasStrokeCommitResult {
int committed_faces = 0; int committed_faces = 0;
}; };
struct LegacyCanvasStrokeCommitCopyExtent {
int width = 0;
int height = 0;
};
[[nodiscard]] inline std::size_t legacy_canvas_stroke_commit_step_count( [[nodiscard]] inline std::size_t legacy_canvas_stroke_commit_step_count(
const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence) noexcept const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence) noexcept
{ {
return std::min(sequence.step_count, sequence.steps.size()); 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( [[nodiscard]] inline bool legacy_canvas_stroke_commit_callbacks_ready(
const LegacyCanvasStrokeCommitCallbacks& callbacks) noexcept const LegacyCanvasStrokeCommitCallbacks& callbacks) noexcept
{ {

View File

@@ -64,6 +64,24 @@ struct LegacyCanvasStrokePadRegionResult {
std::array<pp::paint_renderer::CanvasStrokePoint, 6> ndc_quad {}; 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 { struct LegacyCanvasStrokeComputeRequest {
StrokeSample previous_sample {}; StrokeSample previous_sample {};
std::span<const StrokeSample> samples; 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( [[nodiscard]] inline LegacyStrokeSampleExecutionResult execute_legacy_canvas_stroke_sample(
const LegacyStrokeSampleExecutionRequest& request) const LegacyStrokeSampleExecutionRequest& request)
{ {

View File

@@ -1832,6 +1832,99 @@ void retained_stroke_commit_runner_clamps_malformed_step_count(pp::tests::Harnes
PP_EXPECT(h, timelapse == 1); PP_EXPECT(h, timelapse == 1);
} }
void retained_stroke_commit_input_binder_uses_sequence_slots(pp::tests::Harness& h)
{
const auto sequence = plan_canvas_stroke_commit_sequence(
CanvasStrokeCommitRequest {
.erase_mode = false,
.alpha_locked = false,
.selection_mask_active = true,
.dual_stroke_enabled = true,
.pattern_enabled = true,
});
std::vector<int> active_slots;
std::vector<CanvasStrokeCommitTextureRole> bound_textures;
std::vector<std::pair<CanvasStrokeCommitTextureRole, int>> bound_samplers;
pp::panopainter::bind_legacy_canvas_stroke_commit_inputs(
sequence,
[&](int texture_slot) {
active_slots.push_back(texture_slot);
},
[&](CanvasStrokeCommitTextureRole role) {
bound_textures.push_back(role);
},
[&](CanvasStrokeCommitTextureRole role, int texture_slot) {
bound_samplers.emplace_back(role, texture_slot);
});
PP_EXPECT(h, active_slots.size() == 5U);
PP_EXPECT(h, active_slots[0] == 0);
PP_EXPECT(h, active_slots[1] == 1);
PP_EXPECT(h, active_slots[2] == 2);
PP_EXPECT(h, active_slots[3] == 3);
PP_EXPECT(h, active_slots[4] == 4);
PP_EXPECT(h, bound_textures.size() == 5U);
PP_EXPECT(h, bound_textures[0] == CanvasStrokeCommitTextureRole::layer_scratch);
PP_EXPECT(h, bound_textures[1] == CanvasStrokeCommitTextureRole::stroke);
PP_EXPECT(h, bound_textures[2] == CanvasStrokeCommitTextureRole::selection_mask);
PP_EXPECT(h, bound_textures[3] == CanvasStrokeCommitTextureRole::dual_stroke);
PP_EXPECT(h, bound_textures[4] == CanvasStrokeCommitTextureRole::pattern);
PP_EXPECT(h, bound_samplers.size() == 5U);
PP_EXPECT(h, bound_samplers[0].first == CanvasStrokeCommitTextureRole::layer_scratch);
PP_EXPECT(h, bound_samplers[0].second == 0);
PP_EXPECT(h, bound_samplers[4].first == CanvasStrokeCommitTextureRole::pattern);
PP_EXPECT(h, bound_samplers[4].second == 4);
}
void retained_stroke_commit_dilate_copy_uses_layer_scratch_slot(pp::tests::Harness& h)
{
const auto sequence = plan_canvas_stroke_commit_sequence(
CanvasStrokeCommitRequest {
.erase_mode = true,
.alpha_locked = true,
.selection_mask_active = false,
.dual_stroke_enabled = false,
.pattern_enabled = false,
});
int setup_calls = 0;
std::vector<int> active_slots;
int bind_layer_scratch_calls = 0;
std::vector<pp::paint_renderer::CanvasStrokeCopyRegion> copy_regions;
pp::panopainter::copy_legacy_canvas_stroke_commit_to_dilate_source(
sequence,
[&]() { ++setup_calls; },
[&](int texture_slot) { active_slots.push_back(texture_slot); },
[&]() { ++bind_layer_scratch_calls; },
[&](int src_x, int src_y, int dst_x, int dst_y, int width, int height) {
copy_regions.push_back(pp::paint_renderer::CanvasStrokeCopyRegion {
.x = src_x,
.y = src_y,
.width = width,
.height = height,
});
PP_EXPECT(h, dst_x == 0);
PP_EXPECT(h, dst_y == 0);
},
pp::panopainter::LegacyCanvasStrokeCommitCopyExtent {
.width = 256,
.height = 128,
});
PP_EXPECT(h, setup_calls == 1);
PP_EXPECT(h, active_slots.size() == 1U);
PP_EXPECT(h, active_slots[0] == 0);
PP_EXPECT(h, bind_layer_scratch_calls == 1);
PP_EXPECT(h, copy_regions.size() == 1U);
PP_EXPECT(h, copy_regions[0].x == 0);
PP_EXPECT(h, copy_regions[0].y == 0);
PP_EXPECT(h, copy_regions[0].width == 256);
PP_EXPECT(h, copy_regions[0].height == 128);
}
void plans_stroke_preview_composite_for_simple_brush(pp::tests::Harness& h) void plans_stroke_preview_composite_for_simple_brush(pp::tests::Harness& h)
{ {
const auto plan = plan_stroke_preview_composite(StrokePreviewCompositeRequest {}); const auto plan = plan_stroke_preview_composite(StrokePreviewCompositeRequest {});
@@ -2344,6 +2437,12 @@ int main()
harness.run( harness.run(
"retained_stroke_commit_runner_clamps_malformed_step_count", "retained_stroke_commit_runner_clamps_malformed_step_count",
retained_stroke_commit_runner_clamps_malformed_step_count); retained_stroke_commit_runner_clamps_malformed_step_count);
harness.run(
"retained_stroke_commit_input_binder_uses_sequence_slots",
retained_stroke_commit_input_binder_uses_sequence_slots);
harness.run(
"retained_stroke_commit_dilate_copy_uses_layer_scratch_slot",
retained_stroke_commit_dilate_copy_uses_layer_scratch_slot);
harness.run("plans_stroke_preview_composite_for_simple_brush", plans_stroke_preview_composite_for_simple_brush); harness.run("plans_stroke_preview_composite_for_simple_brush", plans_stroke_preview_composite_for_simple_brush);
harness.run("plans_stroke_preview_composite_with_mixer_input", plans_stroke_preview_composite_with_mixer_input); harness.run("plans_stroke_preview_composite_with_mixer_input", plans_stroke_preview_composite_with_mixer_input);
harness.run("plans_stroke_preview_composite_with_dual_input", plans_stroke_preview_composite_with_dual_input); harness.run("plans_stroke_preview_composite_with_dual_input", plans_stroke_preview_composite_with_dual_input);