diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index c2780a7..6538869 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -33,6 +33,11 @@ agent or engineer to remove them without reconstructing context from chat. final plane draw ordering through `execute_legacy_canvas_stroke_mix_pass(...)`; mixer framebuffer/state setup and per-plane shader material/MVP preparation remain retained in `Canvas`. +- 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw_samples()` + now routes face-indexed destination bind/copy/unbind and brush upload/draw + through `execute_legacy_canvas_stroke_face_sample_polygon(...)`; the + retained sample executor owns the face-aware dispatch contract while + `Canvas` keeps only concrete GL object callbacks. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::stroke_draw_mix()` now routes mixer framebuffer bind/unbind, viewport/scissor/blend state, texture-slot binding, and final plane draw through one local helper; diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index aada2f1..276fe9c 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -3133,6 +3133,10 @@ Results: retained sampler/texture-slot binding, and final plane draw ordering, while mixer framebuffer/state setup and per-plane shader material/MVP preparation remain in the legacy Canvas path. +- `Canvas::stroke_draw_samples()` now shares + `execute_legacy_canvas_stroke_face_sample_polygon(...)` for face-indexed + destination bind/copy/unbind and brush upload/draw dispatch, while concrete + GL object callbacks remain in the legacy Canvas path. - `NodeStrokePreview::stroke_draw_mix()` now shares one local helper for mixer framebuffer bind/unbind, viewport/scissor/blend state, texture-slot binding, and final plane draw, while material planning and shader uniform diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index f359ed8..c85e8c7 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -509,6 +509,13 @@ Done Checks: Progress Notes: +- 2026-06-13: `Canvas::stroke_draw_samples()` now routes face-indexed + destination bind/copy/unbind and brush upload/draw through + `execute_legacy_canvas_stroke_face_sample_polygon(...)`; the retained sample + executor now owns the face-aware dispatch contract while `Canvas` keeps only + the concrete GL object callbacks. Next slice should target any remaining + final temporary-texture composite setup without reopening landed sample, + mix, sampler, dirty, face, or pad helpers. - 2026-06-13: `pp_paint_renderer_stroke_execution_tests` now also covers direct retained frame-sample callback ordering plus `execute_legacy_canvas_stroke_frame_samples_with_dirty_tracking(...)` diff --git a/src/canvas.cpp b/src/canvas.cpp index 0244641..d09e51a 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -565,17 +565,33 @@ glm::vec4 Canvas::stroke_draw_samples( std::vector& P, bool copy_stroke_destination) { - const auto result = pp::panopainter::execute_legacy_canvas_stroke_sample_polygon( - pp::panopainter::LegacyStrokeSamplePolygonExecutionRequest { + constexpr std::array destination_texture_binding { + pp::panopainter::LegacyCanvasStrokeTextureBinding { + .input = pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination, + .slot = 1, + }, + }; + const auto result = pp::panopainter::execute_legacy_canvas_stroke_face_sample_polygon( + pp::panopainter::LegacyStrokeFaceSamplePolygonExecutionRequest { .context = "Canvas::stroke_draw_samples", .target_size = { m_width, m_height }, .polygon_vertices = P, + .face_index = i, .copy_stroke_destination = copy_stroke_destination, - .bind_destination_texture = [&] { - set_active_texture_unit(1); - m_tex[i].bind(); // bg, copy of framebuffer (copied before drawing) + .bind_destination_texture = [&](int face_index) { + pp::panopainter::bind_legacy_canvas_stroke_texture_inputs( + destination_texture_binding, + pp::panopainter::LegacyCanvasStrokeTextureInputDispatch { + .activate_texture_unit = [&](int texture_slot) { + set_active_texture_unit(texture_slot); + }, + .bind_stroke_destination = [&] { + m_tex[face_index].bind(); // bg, copy of framebuffer (copied before drawing) + }, + }); }, .copy_framebuffer_to_destination_texture = []( + int, int src_x, int src_y, int dst_x, @@ -584,16 +600,24 @@ glm::vec4 Canvas::stroke_draw_samples( int height) { copy_framebuffer_to_texture_2d(src_x, src_y, dst_x, dst_y, width, height); }, - .unbind_destination_texture = [&] { - set_active_texture_unit(1); - m_tex[i].unbind(); + .unbind_destination_texture = [&](int face_index) { + pp::panopainter::unbind_legacy_canvas_stroke_texture_inputs( + destination_texture_binding, + pp::panopainter::LegacyCanvasStrokeTextureInputDispatch { + .activate_texture_unit = [&](int texture_slot) { + set_active_texture_unit(texture_slot); + }, + .unbind_stroke_destination = [&] { + m_tex[face_index].unbind(); + }, + }); }, - .upload_brush_vertices = [&](std::span vertices) { + .upload_brush_vertices = [&](int, std::span vertices) { m_brush_shape.update_vertices( const_cast(vertices.data()), static_cast(vertices.size())); }, - .draw_brush_shape = [&] { + .draw_brush_shape = [&](int) { m_brush_shape.draw_fill(); }, }); diff --git a/src/legacy_canvas_stroke_execution_services.h b/src/legacy_canvas_stroke_execution_services.h index c3d5f4f..6639ad8 100644 --- a/src/legacy_canvas_stroke_execution_services.h +++ b/src/legacy_canvas_stroke_execution_services.h @@ -49,6 +49,19 @@ struct LegacyStrokeSamplePolygonExecutionRequest { std::function draw_brush_shape; }; +struct LegacyStrokeFaceSamplePolygonExecutionRequest { + std::string_view context; + glm::vec2 target_size {}; + std::span polygon_vertices; + int face_index = 0; + bool copy_stroke_destination = false; + std::function bind_destination_texture; + std::function copy_framebuffer_to_destination_texture; + std::function unbind_destination_texture; + std::function)> upload_brush_vertices; + std::function draw_brush_shape; +}; + enum class LegacyCanvasStrokeTextureInput { brush_tip, stroke_destination, @@ -952,6 +965,52 @@ template vertices) { + request.upload_brush_vertices(request.face_index, vertices); + }, + .draw_brush_shape = [&] { + request.draw_brush_shape(request.face_index); + }, + }); +} + [[nodiscard]] inline LegacyCanvasStrokeMixPassResult execute_legacy_canvas_stroke_mix_pass( const LegacyCanvasStrokeMixPassRequest& request) {