Extract pad stroke face orchestration

This commit is contained in:
2026-06-13 18:01:45 +02:00
parent 9bbc24b075
commit 11a62e9b43
5 changed files with 145 additions and 44 deletions

View File

@@ -86,9 +86,10 @@ agent or engineer to remove them without reconstructing context from chat.
`execute_legacy_canvas_stroke_main_pass_frame_callbacks(...)`; the retained `execute_legacy_canvas_stroke_main_pass_frame_callbacks(...)`; the retained
path still owns the concrete shader, sampler, and framebuffer wiring. path still owns the concrete shader, sampler, and framebuffer wiring.
- 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` pad-face - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` pad-face
orchestration remains retained and is next targeted for helper extraction; orchestration now routes through
pad destination dispatch already routes through a retained helper, but the `execute_legacy_canvas_stroke_pad_face_callbacks(...)`; the retained path
pad face loop and copy timing still live in `Canvas`. still owns the concrete brush shape, destination dispatch, and framebuffer
wiring.
- 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw_samples()` - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw_samples()`
now routes polygon triangulation, sample-point assembly, and retained now routes polygon triangulation, sample-point assembly, and retained
destination-copy / upload / draw helper handoff through destination-copy / upload / draw helper handoff through

View File

@@ -954,7 +954,7 @@ ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_ex
### STR-008 - Extract Pad Stroke Face Orchestration ### STR-008 - Extract Pad Stroke Face Orchestration
Status: Ready Status: Done
Score: +1 renderer boundary and OpenGL parity Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036` Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`, Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`,
@@ -980,3 +980,9 @@ Validation:
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_paint_renderer_stroke_execution_tests.vcxproj /p:Configuration=Debug /p:Platform=x64 & 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_paint_renderer_stroke_execution_tests.vcxproj /p:Configuration=Debug /p:Platform=x64
``` ```
### Completed Task Log
| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | STR-008 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure`; `MSBuild.exe out\build\windows-msvc-default\tests\pp_paint_renderer_stroke_execution_tests.vcxproj /p:Configuration=Debug /p:Platform=x64` | pending |

View File

@@ -858,26 +858,23 @@ void Canvas::stroke_draw()
m_tex[face_index].unbind(); m_tex[face_index].unbind();
}); });
}; };
[[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_face_callbacks(
pp::panopainter::LegacyCanvasStrokePadExecutionRequest { pad_faces,
.context = "Canvas::stroke_draw", stroke_extent,
.extent = stroke_extent, copy_stroke_destination,
.faces = pad_faces, [&](std::span<const vertex_t> pad_quad) {
.copy_stroke_destination = copy_stroke_destination,
.upload_pad_vertices = [&](std::span<const vertex_t> pad_quad) {
m_brush_shape.update_vertices( m_brush_shape.update_vertices(
const_cast<vertex_t*>(pad_quad.data()), const_cast<vertex_t*>(pad_quad.data()),
static_cast<int>(pad_quad.size())); static_cast<int>(pad_quad.size()));
}, },
.begin_face = [&](int face_index) { [&](int face_index) {
m_tmp[face_index].bindFramebuffer(); m_tmp[face_index].bindFramebuffer();
}, },
.bind_destination_texture = [&](int face_index) { [&](int face_index) {
pp::panopainter::bind_legacy_canvas_stroke_texture_inputs( pp::panopainter::bind_legacy_canvas_stroke_texture_inputs(
pad_destination_texture_binding, pad_destination_texture_binding,
make_pad_destination_texture_dispatch(face_index)); make_pad_destination_texture_dispatch(face_index));
}, },
.copy_framebuffer_to_destination_texture =
[&](const pp::paint_renderer::CanvasStrokeCopyRegion& copy_region) { [&](const pp::paint_renderer::CanvasStrokeCopyRegion& copy_region) {
copy_framebuffer_to_texture_2d( copy_framebuffer_to_texture_2d(
copy_region.x, copy_region.x,
@@ -887,17 +884,16 @@ void Canvas::stroke_draw()
copy_region.width, copy_region.width,
copy_region.height); copy_region.height);
}, },
.unbind_destination_texture = [&](int face_index) { [&](int face_index) {
pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs( pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs(
pad_destination_texture_binding, pad_destination_texture_binding,
make_pad_destination_texture_dispatch(face_index)); make_pad_destination_texture_dispatch(face_index));
}, },
.draw_pad = [&] { [&] {
m_brush_shape.draw_fill(); m_brush_shape.draw_fill();
}, },
.finish_face = [&](int face_index) { [&](int face_index) {
m_tmp[face_index].unbindFramebuffer(); m_tmp[face_index].unbindFramebuffer();
},
}); });
// DRAW DUAL BRUSH // DRAW DUAL BRUSH

View File

@@ -248,6 +248,9 @@ struct LegacyCanvasStrokePadExecutionResult {
std::size_t padded_faces = 0; std::size_t padded_faces = 0;
}; };
[[nodiscard]] inline LegacyCanvasStrokePadExecutionResult execute_legacy_canvas_stroke_pad_faces(
const LegacyCanvasStrokePadExecutionRequest& request);
struct LegacyCanvasStrokeMixPassPlane { struct LegacyCanvasStrokeMixPassPlane {
int index = 0; int index = 0;
bool visible = true; bool visible = true;
@@ -368,6 +371,35 @@ std::size_t execute_legacy_canvas_stroke_main_pass_frame_callbacks(
pass_dirty_faces); pass_dirty_faces);
} }
template <typename Faces, typename UploadPadVertices, typename BeginFace, typename BindDestinationTexture, typename CopyFramebufferToDestinationTexture, typename UnbindDestinationTexture, typename DrawPad, typename FinishFace>
std::size_t execute_legacy_canvas_stroke_pad_face_callbacks(
Faces&& faces,
pp::renderer::Extent2D extent,
bool copy_stroke_destination,
UploadPadVertices&& upload_pad_vertices,
BeginFace&& begin_face,
BindDestinationTexture&& bind_destination_texture,
CopyFramebufferToDestinationTexture&& copy_framebuffer_to_destination_texture,
UnbindDestinationTexture&& unbind_destination_texture,
DrawPad&& draw_pad,
FinishFace&& finish_face)
{
return execute_legacy_canvas_stroke_pad_faces(
LegacyCanvasStrokePadExecutionRequest {
.context = "Canvas::stroke_draw",
.extent = extent,
.faces = std::forward<Faces>(faces),
.copy_stroke_destination = copy_stroke_destination,
.upload_pad_vertices = std::forward<UploadPadVertices>(upload_pad_vertices),
.begin_face = std::forward<BeginFace>(begin_face),
.bind_destination_texture = std::forward<BindDestinationTexture>(bind_destination_texture),
.copy_framebuffer_to_destination_texture = std::forward<CopyFramebufferToDestinationTexture>(copy_framebuffer_to_destination_texture),
.unbind_destination_texture = std::forward<UnbindDestinationTexture>(unbind_destination_texture),
.draw_pad = std::forward<DrawPad>(draw_pad),
.finish_face = std::forward<FinishFace>(finish_face),
}).padded_faces;
}
[[nodiscard]] inline LegacyCanvasStrokeDualPassResult execute_legacy_canvas_stroke_dual_pass( [[nodiscard]] inline LegacyCanvasStrokeDualPassResult execute_legacy_canvas_stroke_dual_pass(
const LegacyCanvasStrokeDualPassRequest& request) const LegacyCanvasStrokeDualPassRequest& request)
{ {

View File

@@ -1437,6 +1437,72 @@ void retained_stroke_main_pass_frame_callbacks_preserve_order(pp::tests::Harness
PP_EXPECT(h, events == expected_events); PP_EXPECT(h, events == expected_events);
} }
void retained_stroke_pad_face_callbacks_preserve_order(pp::tests::Harness& h)
{
const std::array<bool, 3> dirty_faces { true, false, true };
const std::array<glm::vec4, 3> pass_dirty_boxes {
glm::vec4(5.0F, 10.0F, 20.0F, 30.0F),
glm::vec4(0.0F, 0.0F, 0.0F, 0.0F),
glm::vec4(60.0F, 15.0F, 70.0F, 25.0F),
};
const auto faces = pp::panopainter::make_legacy_canvas_stroke_pad_faces(dirty_faces, pass_dirty_boxes);
std::vector<std::string> events;
std::vector<pp::paint_renderer::CanvasStrokeCopyRegion> copy_regions;
std::array<vertex_t, 4> uploaded_quad {};
const auto result = pp::panopainter::execute_legacy_canvas_stroke_pad_face_callbacks(
std::span<const LegacyCanvasStrokePadFace>(faces),
pp::renderer::Extent2D { .width = 100, .height = 80 },
true,
[&](std::span<const vertex_t> vertices) {
events.emplace_back("upload:" + std::to_string(vertices.size()));
std::copy(vertices.begin(), vertices.end(), uploaded_quad.begin());
},
[&](int face_index) {
events.emplace_back("begin:" + std::to_string(face_index));
},
[&](int face_index) {
events.emplace_back("bind:" + std::to_string(face_index));
},
[&](const pp::paint_renderer::CanvasStrokeCopyRegion& copy_region) {
events.emplace_back("copy");
copy_regions.push_back(copy_region);
},
[&](int face_index) {
events.emplace_back("unbind:" + std::to_string(face_index));
},
[&] {
events.emplace_back("draw");
},
[&](int face_index) {
events.emplace_back("finish:" + std::to_string(face_index));
});
PP_EXPECT(h, result == 2U);
const std::vector<std::string> expected_events {
"upload:4",
"begin:0",
"bind:0",
"draw",
"unbind:0",
"finish:0",
"upload:4",
"begin:2",
"bind:2",
"copy",
"draw",
"unbind:2",
"finish:2",
};
PP_EXPECT(h, events == expected_events);
PP_EXPECT(h, copy_regions.size() == 1U);
PP_EXPECT(h, faces[0].dirty);
PP_EXPECT(h, !faces[1].dirty);
PP_EXPECT(h, faces[2].dirty);
PP_EXPECT(h, uploaded_quad.size() == 4U);
}
void retained_stroke_pad_executor_copies_destination_for_dirty_faces_only(pp::tests::Harness& h) void retained_stroke_pad_executor_copies_destination_for_dirty_faces_only(pp::tests::Harness& h)
{ {
const std::array<bool, 3> dirty_faces { true, false, true }; const std::array<bool, 3> dirty_faces { true, false, true };