Extract preview final composite orchestration

This commit is contained in:
2026-06-13 18:36:43 +02:00
parent 3d3a99a536
commit 87c4bee112
4 changed files with 195 additions and 65 deletions

View File

@@ -18,6 +18,10 @@ agent or engineer to remove them without reconstructing context from chat.
## Recent Reductions
- 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::draw_stroke_immediate()`
now routes final-composite setup and preview copy-back through retained
helpers in `legacy_node_stroke_preview_execution_services.h`; the retained
path still owns the concrete texture objects and pass-order wiring.
- 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::draw_stroke_immediate()`
now routes main-pass texture binding through
`bind_legacy_node_stroke_preview_main_pass_textures(...)`; the retained path

View File

@@ -6,6 +6,7 @@
#include "legacy_canvas_stroke_shader_services.h"
#include "legacy_canvas_stroke_services.h"
#include "paint_renderer/compositor.h"
#include "texture.h"
#include <algorithm>
#include <cmath>
@@ -242,6 +243,69 @@ struct LegacyNodeStrokePreviewMainPassTextureDispatch {
std::function<void()> bind_mixer;
};
struct LegacyNodeStrokePreviewFinalCompositeRequest {
glm::vec2 resolution {};
glm::vec2 pattern_scale {};
const Brush* brush = nullptr;
const pp::paint_renderer::CanvasStrokeCompositePassPlan* composite_pass = nullptr;
std::function<void()> setup_composite_shader;
std::function<void()> bind_composite_samplers;
std::function<void()> bind_composite_inputs;
std::function<void()> draw_composite;
};
[[nodiscard]] inline bool execute_legacy_node_stroke_preview_final_composite(
const LegacyNodeStrokePreviewFinalCompositeRequest& request)
{
if (!request.brush ||
!request.composite_pass ||
!request.setup_composite_shader ||
!request.bind_composite_samplers ||
!request.bind_composite_inputs ||
!request.draw_composite) {
return false;
}
pp::panopainter::execute_legacy_stroke_preview_final_composite(
[&] {
request.setup_composite_shader();
},
[&] {
request.bind_composite_samplers();
},
[&] {
request.bind_composite_inputs();
},
[&] {
request.draw_composite();
});
return true;
}
struct LegacyNodeStrokePreviewCopyResultRequest {
Texture2D* preview_texture = nullptr;
glm::vec2 size {};
std::function<void(int, int, int, int, int, int)> copy_framebuffer_to_texture;
};
[[nodiscard]] inline bool copy_legacy_node_stroke_preview_result(
const LegacyNodeStrokePreviewCopyResultRequest& request)
{
if (!request.preview_texture || !request.copy_framebuffer_to_texture) {
return false;
}
pp::panopainter::copy_legacy_stroke_preview_texture(
[&] {
request.preview_texture->bind();
},
request.copy_framebuffer_to_texture,
pp::panopainter::LegacyStrokePreviewCopySize {
.width = static_cast<int>(request.size.x),
.height = static_cast<int>(request.size.y),
});
return true;
}
[[nodiscard]] inline LegacyNodeStrokePreviewMainPassTextureDispatch make_legacy_node_stroke_preview_main_pass_texture_dispatch(
std::function<void(int)> activate_texture_unit,
std::function<void()> bind_brush_tip,

View File

@@ -151,54 +151,62 @@ pp::panopainter::LegacyStrokeCompositeUniforms make_stroke_preview_mix_composite
void execute_stroke_preview_final_composite_pass(const StrokePreviewCompositePassInputs& inputs)
{
pp::panopainter::execute_legacy_stroke_preview_final_composite(
[&] {
pp::panopainter::setup_legacy_stroke_composite_shader(
pp::panopainter::LegacyStrokeCompositeUniforms {
.resolution = inputs.resolution,
.pattern = {
.scale = inputs.pattern_scale,
.invert = static_cast<float>(inputs.brush.m_pattern_invert),
.brightness = inputs.brush.m_pattern_brightness,
.contrast = inputs.brush.m_pattern_contrast,
.depth = inputs.brush.m_pattern_depth,
.blend_mode = inputs.composite_pass.pattern_blend_mode,
.offset = glm::vec2(inputs.brush.m_pattern_rand_offset ? 0.5f : 0.0f),
},
.mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f),
.layer_alpha = 1.0f,
.alpha_lock = false,
.mask_enabled = false,
.use_fragcoord = false,
.blend_mode = inputs.brush.m_blend_mode,
.use_dual = inputs.composite_pass.use_dual,
.dual_blend_mode = inputs.composite_pass.dual_blend_mode,
.dual_alpha = inputs.composite_pass.dual_alpha,
.use_pattern = inputs.composite_pass.use_pattern,
});
},
[&] {
inputs.linear_sampler.bind(stroke_preview_composite_slots::kBackground);
inputs.linear_sampler.bind(stroke_preview_composite_slots::kStroke);
inputs.linear_sampler.bind(2U);
inputs.linear_sampler.bind(stroke_preview_composite_slots::kDual);
inputs.repeat_sampler.bind(stroke_preview_composite_slots::kPattern);
},
[&] {
set_active_texture_unit(stroke_preview_composite_slots::kBackground);
inputs.background_texture.bind();
set_active_texture_unit(stroke_preview_composite_slots::kStroke);
inputs.stroke_texture.bind();
set_active_texture_unit(stroke_preview_composite_slots::kDual);
inputs.dual_texture.bind();
set_active_texture_unit(stroke_preview_composite_slots::kPattern);
inputs.brush.m_pattern_texture ?
inputs.brush.m_pattern_texture->bind() :
unbind_texture_2d();
},
[&] {
inputs.draw_composite();
});
[[maybe_unused]] const bool composite_ok =
pp::panopainter::execute_legacy_node_stroke_preview_final_composite(
pp::panopainter::LegacyNodeStrokePreviewFinalCompositeRequest {
.resolution = inputs.resolution,
.pattern_scale = inputs.pattern_scale,
.brush = &inputs.brush,
.composite_pass = &inputs.composite_pass,
.setup_composite_shader = [&] {
pp::panopainter::setup_legacy_stroke_composite_shader(
pp::panopainter::LegacyStrokeCompositeUniforms {
.resolution = inputs.resolution,
.pattern = {
.scale = inputs.pattern_scale,
.invert = static_cast<float>(inputs.brush.m_pattern_invert),
.brightness = inputs.brush.m_pattern_brightness,
.contrast = inputs.brush.m_pattern_contrast,
.depth = inputs.brush.m_pattern_depth,
.blend_mode = inputs.composite_pass.pattern_blend_mode,
.offset = glm::vec2(inputs.brush.m_pattern_rand_offset ? 0.5f : 0.0f),
},
.mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f),
.layer_alpha = 1.0f,
.alpha_lock = false,
.mask_enabled = false,
.use_fragcoord = false,
.blend_mode = inputs.brush.m_blend_mode,
.use_dual = inputs.composite_pass.use_dual,
.dual_blend_mode = inputs.composite_pass.dual_blend_mode,
.dual_alpha = inputs.composite_pass.dual_alpha,
.use_pattern = inputs.composite_pass.use_pattern,
});
},
.bind_composite_samplers = [&] {
inputs.linear_sampler.bind(stroke_preview_composite_slots::kBackground);
inputs.linear_sampler.bind(stroke_preview_composite_slots::kStroke);
inputs.linear_sampler.bind(2U);
inputs.linear_sampler.bind(stroke_preview_composite_slots::kDual);
inputs.repeat_sampler.bind(stroke_preview_composite_slots::kPattern);
},
.bind_composite_inputs = [&] {
set_active_texture_unit(stroke_preview_composite_slots::kBackground);
inputs.background_texture.bind();
set_active_texture_unit(stroke_preview_composite_slots::kStroke);
inputs.stroke_texture.bind();
set_active_texture_unit(stroke_preview_composite_slots::kDual);
inputs.dual_texture.bind();
set_active_texture_unit(stroke_preview_composite_slots::kPattern);
inputs.brush.m_pattern_texture ?
inputs.brush.m_pattern_texture->bind() :
unbind_texture_2d();
},
.draw_composite = [&] {
inputs.draw_composite();
},
});
assert(composite_ok);
}
void copy_stroke_preview_framebuffer_to_texture(
@@ -394,23 +402,22 @@ void execute_stroke_preview_background_capture_pass(
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<int>(size.x),
.height = static_cast<int>(size.y),
});
[[maybe_unused]] const bool copy_ok =
pp::panopainter::copy_legacy_node_stroke_preview_result(
pp::panopainter::LegacyNodeStrokePreviewCopyResultRequest {
.preview_texture = &preview_texture,
.size = size,
.copy_framebuffer_to_texture = [](
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);
},
});
assert(copy_ok);
}
}

View File

@@ -2659,6 +2659,58 @@ void legacy_node_stroke_preview_main_pass_texture_dispatch_preserves_order(pp::t
});
}
void legacy_node_stroke_preview_final_composite_and_copy_helpers_preserve_order(pp::tests::Harness& h)
{
std::vector<std::string> steps;
const bool composite_ok = pp::panopainter::execute_legacy_node_stroke_preview_final_composite(
pp::panopainter::LegacyNodeStrokePreviewFinalCompositeRequest {
.resolution = glm::vec2(64.0F, 32.0F),
.pattern_scale = glm::vec2(0.25F, -0.5F),
.brush = reinterpret_cast<const Brush*>(1),
.composite_pass = reinterpret_cast<const pp::paint_renderer::CanvasStrokeCompositePassPlan*>(1),
.setup_composite_shader = [&] {
steps.emplace_back("setup");
},
.bind_composite_samplers = [&] {
steps.emplace_back("bind_samplers");
},
.bind_composite_inputs = [&] {
steps.emplace_back("bind_inputs");
},
.draw_composite = [&] {
steps.emplace_back("draw");
},
});
PP_EXPECT(h, composite_ok);
PP_EXPECT(h, steps == std::vector<std::string> {
"setup",
"bind_samplers",
"bind_inputs",
"draw",
});
steps.clear();
const bool copy_ok = pp::panopainter::copy_legacy_node_stroke_preview_result(
pp::panopainter::LegacyNodeStrokePreviewCopyResultRequest {
.preview_texture = reinterpret_cast<Texture2D*>(1),
.size = glm::vec2(32.0F, 16.0F),
.copy_framebuffer_to_texture = [&](int src_x, int src_y, int dst_x, int dst_y, int width, int height) {
steps.emplace_back(
"copy:" +
std::to_string(src_x) + "," +
std::to_string(src_y) + "," +
std::to_string(dst_x) + "," +
std::to_string(dst_y) + "," +
std::to_string(width) + "," +
std::to_string(height));
},
});
PP_EXPECT(h, copy_ok);
PP_EXPECT(h, steps == std::vector<std::string> {
"copy:0,0,0,0,32,16",
});
}
void legacy_node_stroke_preview_stroke_setup_plan_preserves_curve_and_dual_inputs(pp::tests::Harness& h)
{
const auto plan = pp::panopainter::plan_legacy_node_stroke_preview_stroke_setup(
@@ -3255,6 +3307,9 @@ int main()
harness.run(
"legacy_node_stroke_preview_main_pass_texture_dispatch_preserves_order",
legacy_node_stroke_preview_main_pass_texture_dispatch_preserves_order);
harness.run(
"legacy_node_stroke_preview_final_composite_and_copy_helpers_preserve_order",
legacy_node_stroke_preview_final_composite_and_copy_helpers_preserve_order);
harness.run(
"legacy_node_stroke_preview_stroke_setup_plan_preserves_curve_and_dual_inputs",
legacy_node_stroke_preview_stroke_setup_plan_preserves_curve_and_dual_inputs);