diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 2407656..3f6e854 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -18,6 +18,11 @@ agent or engineer to remove them without reconstructing context from chat. ## Recent Reductions +- 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview` live-pass + sampler binding, dual/main pass texture binding, checkerboard/background + capture wrapping, and final preview copy-back now route through shared local + helpers; mixer state execution and per-sample GL ordering remain retained in + the preview node. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview` dual-pass and main-pass frame-loop execution plus full-frame copy-back now route through shared local helpers; checkerboard/background capture ordering, texture-unit diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 7c66641..4d0fcc0 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -3107,6 +3107,10 @@ Results: full-frame copy-back now share one local retained helper surface, while checkerboard/background capture ordering, texture-unit binding, mixer ownership, and final composite semantics remain in the preview node. +- `NodeStrokePreview` live-pass sampler binding, dual/main pass texture + binding, checkerboard/background capture wrapping, and final preview + copy-back now share named local helpers, while mixer state execution and + per-sample GL ordering remain in the preview node. - `Canvas::stroke_draw` pad-pass destination bind/copy/unbind ordering now shares the retained stroke execution helper callback surface, while shader setup, pad color selection, framebuffer ownership, and final OpenGL draw diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index 0ff33de..fb6e758 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -509,6 +509,13 @@ Done Checks: Progress Notes: +- 2026-06-13: `NodeStrokePreview::draw_stroke_immediate()` live-pass sampler + binding, dual/main pass texture binding, checkerboard/background capture + wrapping, and final preview copy-back now route through named local helpers; + mixer state execution and per-sample GL ordering remain local to the preview + node. Next slice should target the remaining mixer-pass state/copy ordering + or sample-pass destination callback wrapping without reopening the landed + preview live-pass or final-composite helpers. - 2026-06-13: `NodeStrokePreview::draw_stroke_immediate()` dual-pass and main-pass frame-loop execution plus full-frame copy-back now route through shared local helpers; checkerboard/background capture ordering, texture-unit diff --git a/src/node_stroke_preview.cpp b/src/node_stroke_preview.cpp index 3b92536..e22cf4e 100644 --- a/src/node_stroke_preview.cpp +++ b/src/node_stroke_preview.cpp @@ -103,6 +103,14 @@ constexpr std::uint32_t kDual = 3U; constexpr std::uint32_t kPattern = 4U; } +namespace stroke_preview_live_slots { +constexpr std::uint32_t kTip = 0U; +constexpr std::uint32_t kDestination = 1U; +constexpr std::uint32_t kPattern = 2U; +constexpr std::uint32_t kMixer = 3U; +constexpr std::uint32_t kReservedLinear = 4U; +} + struct StrokePreviewCompositePassInputs { glm::vec2 resolution; glm::vec2 pattern_scale; @@ -203,6 +211,110 @@ void copy_stroke_preview_framebuffer_to_texture( static_cast(size.y)); } +void bind_stroke_preview_live_samplers( + Sampler& mipmap_sampler, + Sampler& linear_sampler, + Sampler& repeat_sampler) +{ + mipmap_sampler.bind(stroke_preview_live_slots::kTip); + linear_sampler.bind(stroke_preview_live_slots::kDestination); + repeat_sampler.bind(stroke_preview_live_slots::kPattern); + linear_sampler.bind(stroke_preview_live_slots::kMixer); + linear_sampler.bind(stroke_preview_live_slots::kReservedLinear); +} + +void bind_stroke_preview_dual_pass_textures(const Brush& dual_brush) +{ + set_active_texture_unit(stroke_preview_live_slots::kTip); + dual_brush.m_tip_texture ? + dual_brush.m_tip_texture->bind() : + unbind_texture_2d(); +} + +void bind_stroke_preview_main_pass_textures( + const Brush& brush, + Texture2D& stroke_destination_texture, + RTT& mixer_rtt, + bool copy_stroke_destination, + bool uses_mixer) +{ + set_active_texture_unit(stroke_preview_live_slots::kTip); + brush.m_tip_texture ? + brush.m_tip_texture->bind() : + unbind_texture_2d(); + + if (copy_stroke_destination) + { + set_active_texture_unit(stroke_preview_live_slots::kDestination); + stroke_destination_texture.bind(); + } + + set_active_texture_unit(stroke_preview_live_slots::kPattern); + brush.m_pattern_texture ? + brush.m_pattern_texture->bind() : + unbind_texture_2d(); + + set_active_texture_unit(stroke_preview_live_slots::kMixer); + uses_mixer ? mixer_rtt.bindTexture() : unbind_texture_2d(); +} + +void execute_stroke_preview_background_capture_pass( + glm::vec2 size, + bool colorize, + Texture2D& background_texture, + const std::function& draw_checkerboard) +{ + pp::panopainter::execute_legacy_stroke_preview_background_capture( + [&] { + const float aspect = size.x / size.y; + pp::panopainter::setup_legacy_canvas_draw_merge_checkerboard_shader( + pp::panopainter::LegacyCanvasDrawMergeCheckerboardUniforms { + .mvp = glm::ortho(-.5f, .5f, -.5f / aspect, .5f / aspect, -1.f, 1.f), + .colorize = colorize, + }); + }, + [&] { + draw_checkerboard(); + }, + [&] { + background_texture.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::LegacyStrokePreviewCopySize { + .width = static_cast(size.x), + .height = static_cast(size.y), + }); +} + +void copy_stroke_preview_result_to_texture(Texture2D& preview_texture, glm::vec2 size) +{ + pp::panopainter::copy_legacy_stroke_preview_texture( + [&] { + preview_texture.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::LegacyStrokePreviewCopySize { + .width = static_cast(size.x), + .height = static_cast(size.y), + }); +} + template void execute_stroke_preview_live_pass( Texture2D& output_texture, @@ -471,11 +583,10 @@ void NodeStrokePreview::draw_stroke_immediate() apply_stroke_preview_viewport(0, 0, m_rtt.getWidth(), m_rtt.getHeight()); m_rtt.bindFramebuffer(); m_rtt.clear(); - m_sampler_mipmap.bind(0); - m_sampler_linear.bind(1); - m_sampler_linear_repeat.bind(2); - m_sampler_linear.bind(3); - m_sampler_linear.bind(4); + bind_stroke_preview_live_samplers( + m_sampler_mipmap, + m_sampler_linear, + m_sampler_linear_repeat); const auto& b = m_brush; @@ -581,10 +692,7 @@ void NodeStrokePreview::draw_stroke_immediate() if (material.dual_pass.enabled) { pp::panopainter::setup_legacy_stroke_dual_shader(material.dual_pass.uses_pattern); - set_active_texture_unit(0U); - dual_brush->m_tip_texture ? - dual_brush->m_tip_texture->bind() : - unbind_texture_2d(); + bind_stroke_preview_dual_pass_textures(*dual_brush); execute_stroke_preview_live_pass( m_tex_dual, size, @@ -605,35 +713,12 @@ void NodeStrokePreview::draw_stroke_immediate() // CHEKCERBOARD - pp::panopainter::execute_legacy_stroke_preview_background_capture( - [&] { - // copy background color to tex2 - const float aspect = size.x / size.y; - pp::panopainter::setup_legacy_canvas_draw_merge_checkerboard_shader( - pp::panopainter::LegacyCanvasDrawMergeCheckerboardUniforms { - .mvp = glm::ortho(-.5f, .5f, -.5f / aspect, .5f / aspect, -1.f, 1.f), - .colorize = b->m_tip_mix > 0.f || b->m_blend_mode != 0, - }); - }, + execute_stroke_preview_background_capture_pass( + size, + b->m_tip_mix > 0.f || b->m_blend_mode != 0, + m_tex_background, [&] { m_plane.draw_fill(); - }, - [&] { - //m_rtt.clear({ .3f, .3f, .3f, 1.f }); - m_tex_background.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::LegacyStrokePreviewCopySize { - .width = static_cast(size.x), - .height = static_cast(size.y), }); // DRAW MAIN BRUSH @@ -645,19 +730,12 @@ void NodeStrokePreview::draw_stroke_immediate() b->m_tip_wet, b->m_tip_noise); - set_active_texture_unit(0U); - b->m_tip_texture->bind(); - if (copy_stroke_destination) - { - set_active_texture_unit(1U); - m_tex.bind(); // tmp swap for blending - } - set_active_texture_unit(2U); - b->m_pattern_texture ? - b->m_pattern_texture->bind() : - unbind_texture_2d(); - set_active_texture_unit(3U); - preview_composite_plan.uses_mixer ? m_rtt_mixer.bindTexture() : unbind_texture_2d(); + bind_stroke_preview_main_pass_textures( + *b, + m_tex, + m_rtt_mixer, + copy_stroke_destination, + preview_composite_plan.uses_mixer); execute_stroke_preview_live_pass( m_tex, size, @@ -682,7 +760,7 @@ void NodeStrokePreview::draw_stroke_immediate() [&](auto& frame, Texture2D& blend_texture, bool copy_destination) { /*auto rect =*/ stroke_draw_samples(frame.shapes, blend_texture, copy_destination); }); - set_active_texture_unit(3U); + set_active_texture_unit(stroke_preview_live_slots::kMixer); m_rtt_mixer.unbindTexture(); // COMPOSITE @@ -704,23 +782,7 @@ void NodeStrokePreview::draw_stroke_immediate() }); // copy the result to the actual preview - pp::panopainter::copy_legacy_stroke_preview_texture( - [&] { - m_tex_preview.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::LegacyStrokePreviewCopySize { - .width = static_cast(size.x), - .height = static_cast(size.y), - }); + copy_stroke_preview_result_to_texture(m_tex_preview, size); m_rtt.unbindFramebuffer();