diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index a3d1c1f..c2780a7 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -28,6 +28,11 @@ agent or engineer to remove them without reconstructing context from chat. destination-copy / upload / draw helper handoff through `execute_legacy_canvas_stroke_sample_polygon(...)`; direct GL callback wiring and remaining live draw ownership remain retained in `Canvas`. +- 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw_mix()` now + routes visible-plane filtering, retained sampler/texture-slot binding, and + 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. `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 d858f4f..959dd93 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -3128,6 +3128,11 @@ Results: triangulation, sample-point assembly, and retained destination-copy / upload / draw helper handoff, while direct GL callback wiring and the remaining live draw ownership stay in the legacy Canvas path. +- `Canvas::stroke_draw_mix()` now shares + `execute_legacy_canvas_stroke_mix_pass(...)` for visible-plane filtering, + 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. - `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 6b8705a..2dd9e03 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_mix()` now routes visible-plane filtering, + retained sampler/texture-slot binding, and final plane draw ordering through + `execute_legacy_canvas_stroke_mix_pass(...)`; mixer framebuffer/state setup + and per-plane shader material/MVP preparation remain local to `Canvas`. Next + slice should target the remaining `stroke_draw_samples()` callback body or + any final temporary-texture composite setup without reopening landed + sample, sampler, dirty, face, or pad helpers. - 2026-06-13: `pp_paint_renderer_stroke_execution_tests` now also covers retained preview background capture ordering, final composite ordering, and preview texture-copy bind-before-copy behavior via diff --git a/src/canvas.cpp b/src/canvas.cpp index a6ccb4e..0244641 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -405,51 +405,81 @@ void Canvas::stroke_draw_mix(const glm::vec2& bb_min, const glm::vec2& bb_sz) static_cast(bb_sz.y)); auto layer_index = m_current_layer_idx; - for (int plane_index = 0; plane_index < 6; plane_index++) + std::array mix_planes {}; + const auto mix_mvp_base = + glm::scale(glm::vec3(1, -1, 1)) * + m_proj * m_mv; + for (int plane_index = 0; plane_index < 6; ++plane_index) { - if (!m_layers[layer_index]->m_visible || - m_layers[layer_index]->m_opacity == .0f || - !m_layers[layer_index]->face(plane_index)) - continue; - - //glm::mat4 proj = glm::perspective(glm::radians(m_cam_fov), (float)m_mixer.getWidth() / m_mixer.getHeight(), 0.1f, 1000.f); - auto plane_mvp_z = - glm::scale(glm::vec3(1, -1, 1)) * - m_proj * m_mv * - m_plane_transform[plane_index] * - glm::translate(glm::vec3(0, 0, -1)); - - m_sampler.bind(0); - m_sampler.bind(1); - m_sampler.bind(2); - const auto& b = m_current_stroke->m_brush; - pp::panopainter::setup_legacy_stroke_composite_shader( - pp::panopainter::LegacyStrokeCompositeUniforms { - .resolution = m_size, - .mvp = plane_mvp_z, - .pattern_texture_slot = 3, - .layer_alpha = 1.0f, - .alpha_lock = false, /*m_layers[layer_index]->m_alpha_locked*/ - .mask_enabled = false, /*m_smask_active*/ - .use_fragcoord = false, - .blend_mode = b->m_blend_mode, - .use_dual = false, - .use_pattern = false, - }); - set_active_texture_unit(0); - m_layers[layer_index]->rtt(plane_index).bindTexture(); - set_active_texture_unit(1); - m_tmp[plane_index].bindTexture(); - set_active_texture_unit(2); - m_smask.rtt(plane_index).bindTexture(); - m_node->m_face_plane.draw_fill(); - m_smask.rtt(plane_index).unbindTexture(); - set_active_texture_unit(1); - m_tmp[plane_index].unbindTexture(); - set_active_texture_unit(0); - m_layers[layer_index]->rtt(plane_index).unbindTexture(); + mix_planes[plane_index] = pp::panopainter::LegacyCanvasStrokeMixPassPlane { + .index = plane_index, + .visible = m_layers[layer_index]->m_visible, + .has_target = static_cast(m_layers[layer_index]->face(plane_index)), + .opacity = m_layers[layer_index]->m_opacity, + .mvp = mix_mvp_base * + m_plane_transform[plane_index] * + glm::translate(glm::vec3(0, 0, -1)), + }; } - m_sampler.unbind(); + + const auto& b = m_current_stroke->m_brush; + [[maybe_unused]] const auto mix_result = pp::panopainter::execute_legacy_canvas_stroke_mix_pass( + pp::panopainter::LegacyCanvasStrokeMixPassRequest { + .context = "Canvas::stroke_draw_mix", + .resolution = m_size, + .planes = mix_planes, + .bind_mix_samplers = [&] { + m_sampler.bind(0); + m_sampler.bind(1); + m_sampler.bind(2); + }, + .unbind_mix_samplers = [&] { + m_sampler.unbind(); + }, + .setup_plane_shader = [&](int plane_index, const glm::mat4& plane_mvp_z) { + (void)plane_index; + pp::panopainter::setup_legacy_stroke_composite_shader( + pp::panopainter::LegacyStrokeCompositeUniforms { + .resolution = m_size, + .mvp = plane_mvp_z, + .pattern_texture_slot = 3, + .layer_alpha = 1.0f, + .alpha_lock = false, /*m_layers[layer_index]->m_alpha_locked*/ + .mask_enabled = false, /*m_smask_active*/ + .use_fragcoord = false, + .blend_mode = b->m_blend_mode, + .use_dual = false, + .use_pattern = false, + }); + }, + .bind_layer_texture = [&](int plane_index) { + set_active_texture_unit(0); + m_layers[layer_index]->rtt(plane_index).bindTexture(); + }, + .bind_stroke_texture = [&](int plane_index) { + set_active_texture_unit(1); + m_tmp[plane_index].bindTexture(); + }, + .bind_mask_texture = [&](int plane_index) { + set_active_texture_unit(2); + m_smask.rtt(plane_index).bindTexture(); + }, + .draw_plane = [&] { + m_node->m_face_plane.draw_fill(); + }, + .unbind_mask_texture = [&](int plane_index) { + set_active_texture_unit(2); + m_smask.rtt(plane_index).unbindTexture(); + }, + .unbind_stroke_texture = [&](int plane_index) { + set_active_texture_unit(1); + m_tmp[plane_index].unbindTexture(); + }, + .unbind_layer_texture = [&](int plane_index) { + set_active_texture_unit(0); + m_layers[layer_index]->rtt(plane_index).unbindTexture(); + }, + }); m_mixer.unbindFramebuffer(); gl.restore(); diff --git a/src/legacy_canvas_stroke_execution_services.h b/src/legacy_canvas_stroke_execution_services.h index a1f394c..c3d5f4f 100644 --- a/src/legacy_canvas_stroke_execution_services.h +++ b/src/legacy_canvas_stroke_execution_services.h @@ -137,6 +137,35 @@ struct LegacyCanvasStrokePadExecutionResult { std::size_t padded_faces = 0; }; +struct LegacyCanvasStrokeMixPassPlane { + int index = 0; + bool visible = true; + bool has_target = false; + float opacity = 1.0f; + glm::mat4 mvp { 1.0f }; +}; + +struct LegacyCanvasStrokeMixPassRequest { + std::string_view context; + glm::vec2 resolution {}; + std::span planes; + std::function bind_mix_samplers; + std::function unbind_mix_samplers; + std::function setup_plane_shader; + std::function bind_layer_texture; + std::function bind_stroke_texture; + std::function bind_mask_texture; + std::function draw_plane; + std::function unbind_mask_texture; + std::function unbind_stroke_texture; + std::function unbind_layer_texture; +}; + +struct LegacyCanvasStrokeMixPassResult { + bool ok = false; + std::size_t composed_planes = 0; +}; + struct LegacyCanvasStrokeComputeRequest { StrokeSample previous_sample {}; std::span samples; @@ -923,4 +952,49 @@ template