Reduce retained stroke preview helper surface

This commit is contained in:
2026-06-15 19:28:29 +02:00
parent 565564c061
commit e1e686d3f7
7 changed files with 245 additions and 474 deletions

View File

@@ -173,7 +173,7 @@ pp::panopainter::LegacyStrokeCompositeUniforms make_stroke_preview_mix_composite
};
}
pp::panopainter::LegacyNodeStrokePreviewMixExecutionRequest make_stroke_preview_mix_execution_request(
pp::panopainter::LegacyCanvasStrokeMixPassRequest make_stroke_preview_mix_pass_execution_request(
const pp::panopainter::LegacyNodeStrokePreviewMixPassPlan::ShaderPlan& shader,
RTT& mixer_rtt,
const Brush& brush,
@@ -181,39 +181,18 @@ pp::panopainter::LegacyNodeStrokePreviewMixExecutionRequest make_stroke_preview_
Texture2D& background_texture,
Texture2D& stroke_texture,
Texture2D& dual_texture,
gl_state& gl,
const glm::vec2& bb_min,
const glm::vec2& bb_sz,
std::span<const pp::panopainter::LegacyCanvasStrokeMixPassPlane> mix_planes,
std::function<void()> draw_mix)
{
return pp::panopainter::LegacyNodeStrokePreviewMixExecutionRequest {
.shader = shader,
.mixer_width = mixer_rtt.getWidth(),
.mixer_height = mixer_rtt.getHeight(),
.scissor_x = static_cast<int>(bb_min.x),
.scissor_y = static_cast<int>(bb_min.y),
.scissor_width = static_cast<int>(bb_sz.x),
.scissor_height = static_cast<int>(bb_sz.y),
.save_state = [&] { gl.save(); },
.setup_mix_shader = [&](const pp::panopainter::LegacyNodeStrokePreviewMixPassPlan::ShaderPlan& shader_plan) {
pp::panopainter::setup_legacy_stroke_composite_shader(
make_stroke_preview_mix_composite_uniforms(shader_plan));
},
.bind_mixer_framebuffer = [&] { mixer_rtt.bindFramebuffer(); },
.configure_mix_target_state = [&](int mixer_width,
int mixer_height,
int scissor_x,
int scissor_y,
int scissor_width,
int scissor_height) {
apply_stroke_preview_viewport(0, 0, mixer_width, mixer_height);
apply_stroke_preview_capability(pp::renderer::gl::depth_test_state(), false);
apply_stroke_preview_capability(pp::renderer::gl::scissor_test_state(), true);
apply_stroke_preview_capability(pp::renderer::gl::blend_state(), false);
apply_stroke_preview_scissor(scissor_x, scissor_y, scissor_width, scissor_height);
},
.bind_mix_inputs = [&] {
return pp::panopainter::make_legacy_canvas_stroke_mix_pass_request(
"NodeStrokePreview::stroke_draw_mix",
glm::vec2(static_cast<float>(mixer_rtt.getWidth()), static_cast<float>(mixer_rtt.getHeight())),
mix_planes,
[&] {
linear_sampler.bind(stroke_preview_composite_slots::kBackground);
linear_sampler.bind(stroke_preview_composite_slots::kStroke);
linear_sampler.bind(stroke_preview_composite_slots::kDual);
linear_sampler.bind(stroke_preview_composite_slots::kPattern);
set_active_texture_unit(stroke_preview_composite_slots::kBackground);
background_texture.bind();
set_active_texture_unit(stroke_preview_composite_slots::kStroke);
@@ -223,21 +202,48 @@ pp::panopainter::LegacyNodeStrokePreviewMixExecutionRequest make_stroke_preview_
set_active_texture_unit(stroke_preview_composite_slots::kPattern);
brush.m_pattern_texture ? brush.m_pattern_texture->bind() : unbind_texture_2d();
},
.draw_mix = std::move(draw_mix),
.unbind_mixer_framebuffer = [&] { mixer_rtt.unbindFramebuffer(); },
.restore_state = [&] { gl.restore(); },
};
[] {},
[&](int, const glm::mat4& plane_mvp) {
auto uniforms = make_stroke_preview_mix_composite_uniforms(shader);
uniforms.mvp = plane_mvp;
pp::panopainter::setup_legacy_stroke_composite_shader(uniforms);
},
[&](int) {
set_active_texture_unit(stroke_preview_composite_slots::kBackground);
background_texture.bind();
},
[&](int) {
set_active_texture_unit(stroke_preview_composite_slots::kStroke);
stroke_texture.bind();
},
[&](int) {
set_active_texture_unit(stroke_preview_composite_slots::kDual);
dual_texture.bind();
},
std::move(draw_mix),
[&](int) {
set_active_texture_unit(stroke_preview_composite_slots::kDual);
dual_texture.unbind();
},
[&](int) {
set_active_texture_unit(stroke_preview_composite_slots::kStroke);
stroke_texture.unbind();
},
[&](int) {
set_active_texture_unit(stroke_preview_composite_slots::kBackground);
background_texture.unbind();
});
}
pp::panopainter::LegacyNodeStrokePreviewFinalCompositeRequest make_stroke_preview_final_composite_request(
const StrokePreviewCompositePassInputs& inputs)
void copy_stroke_preview_result_to_texture(Texture2D& texture, glm::vec2 size);
void execute_stroke_preview_final_composite_and_copy(
const StrokePreviewCompositePassInputs& inputs,
Texture2D& preview_texture,
glm::vec2 size)
{
return pp::panopainter::LegacyNodeStrokePreviewFinalCompositeRequest {
.resolution = inputs.resolution,
.pattern_scale = inputs.pattern_scale,
.brush = &inputs.brush,
.composite_pass = &inputs.composite_pass,
.setup_composite_shader = [&] {
pp::panopainter::execute_legacy_stroke_preview_final_composite(
[&] {
pp::panopainter::setup_legacy_stroke_composite_shader(
pp::panopainter::LegacyStrokeCompositeUniforms {
.resolution = inputs.resolution,
@@ -262,14 +268,14 @@ pp::panopainter::LegacyNodeStrokePreviewFinalCompositeRequest make_stroke_previe
.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);
@@ -281,28 +287,9 @@ pp::panopainter::LegacyNodeStrokePreviewFinalCompositeRequest make_stroke_previe
inputs.brush.m_pattern_texture->bind() :
unbind_texture_2d();
},
.draw_composite = [&] {
[&] {
inputs.draw_composite();
},
};
}
void execute_stroke_preview_final_composite_pass(const StrokePreviewCompositePassInputs& inputs)
{
[[maybe_unused]] const bool composite_ok =
pp::panopainter::execute_legacy_node_stroke_preview_final_composite(
make_stroke_preview_final_composite_request(inputs));
assert(composite_ok);
}
void copy_stroke_preview_result_to_texture(Texture2D& texture, glm::vec2 size);
void execute_stroke_preview_final_composite_and_copy(
const StrokePreviewCompositePassInputs& inputs,
Texture2D& preview_texture,
glm::vec2 size)
{
execute_stroke_preview_final_composite_pass(inputs);
});
copy_stroke_preview_result_to_texture(preview_texture, size);
}
@@ -486,18 +473,14 @@ void execute_stroke_preview_background_capture_pass(
Texture2D& background_texture,
const std::function<void()>& 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();
},
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();
const auto copy_status = pp::paint_renderer::copy_stroke_preview_result_to_texture(
[&] {
background_texture.bind();
},
@@ -510,30 +493,27 @@ void execute_stroke_preview_background_capture_pass(
int height) {
copy_framebuffer_to_texture_2d(src_x, src_y, dst_x, dst_y, width, height);
},
pp::panopainter::LegacyStrokePreviewCopySize {
pp::paint_renderer::StrokePreviewCopySize {
.width = static_cast<int>(size.x),
.height = static_cast<int>(size.y),
});
assert(copy_status.ok());
}
void copy_stroke_preview_result_to_texture(Texture2D& preview_texture, glm::vec2 size)
{
[[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);
const auto result = pp::paint_renderer::copy_stroke_preview_result_to_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::paint_renderer::StrokePreviewCopySize {
.width = static_cast<int>(size.x),
.height = static_cast<int>(size.y),
});
assert(result.ok());
}
}
@@ -617,14 +597,47 @@ void NodeStrokePreview::stroke_draw_mix(const glm::vec2& bb_min, const glm::vec2
const auto& b = m_brush;
const auto mix_pass = pp::panopainter::plan_legacy_node_stroke_preview_mix_pass(
make_stroke_preview_mix_pass_request(*b, m_size));
const auto mix_planes = std::array {
pp::panopainter::LegacyCanvasStrokeMixPassPlane {
.index = 0,
.visible = true,
.has_target = true,
.opacity = 1.0f,
.mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f),
},
};
gl_state gl;
[[maybe_unused]] const bool mix_ok = pp::panopainter::execute_legacy_node_stroke_preview_mix_pass(
make_stroke_draw_mix_execution_request(
mix_pass,
gl,
bb_min,
bb_sz));
assert(mix_ok);
const auto mix_result = pp::panopainter::execute_legacy_canvas_stroke_mix_pass_with_setup(
[&] {
apply_stroke_preview_viewport(0, 0, m_rtt_mixer.getWidth(), m_rtt_mixer.getHeight());
apply_stroke_preview_capability(pp::renderer::gl::depth_test_state(), false);
apply_stroke_preview_capability(pp::renderer::gl::scissor_test_state(), true);
apply_stroke_preview_capability(pp::renderer::gl::blend_state(), false);
apply_stroke_preview_scissor(
static_cast<std::int32_t>(bb_min.x),
static_cast<std::int32_t>(bb_min.y),
static_cast<std::int32_t>(bb_sz.x),
static_cast<std::int32_t>(bb_sz.y));
gl.save();
m_rtt_mixer.bindFramebuffer();
},
[&] {
m_rtt_mixer.unbindFramebuffer();
gl.restore();
},
make_stroke_preview_mix_pass_execution_request(
mix_pass.shader,
m_rtt_mixer,
*m_brush,
m_sampler_linear,
m_tex_background,
m_tex,
m_tex_dual,
mix_planes,
[this] {
m_plane.draw_fill();
}));
assert(mix_result.ok);
}
glm::vec4 NodeStrokePreview::stroke_draw_samples(
@@ -792,16 +805,60 @@ void NodeStrokePreview::draw_stroke_immediate()
const auto& material = pass_orchestration.material;
pp::panopainter::setup_legacy_stroke_shader(pass_orchestration.stroke_shader);
const bool sequence_ok = pp::panopainter::execute_legacy_node_stroke_preview_pass_sequence(
make_stroke_draw_immediate_pass_sequence_request(
m_stroke,
m_dual_stroke,
*b,
pass_orchestration,
dual_brush,
copy_stroke_destination,
zoom,
size));
const bool sequence_ok = [&] {
if (pass_orchestration.material.dual_pass.enabled) {
pp::panopainter::setup_legacy_stroke_dual_shader(
pass_orchestration.material.dual_pass.uses_pattern);
bind_stroke_preview_dual_pass_textures(*dual_brush);
execute_stroke_draw_immediate_dual_pass(
m_dual_stroke,
*b,
pass_orchestration,
std::move(dual_brush),
copy_stroke_destination,
zoom,
size);
}
execute_stroke_preview_background_capture_pass(
size,
pass_orchestration.background_colorize,
m_tex_background,
[&] {
m_plane.draw_fill();
});
[[maybe_unused]] const bool main_live_ok =
pp::panopainter::execute_legacy_node_stroke_preview_main_live_pass(
make_stroke_draw_immediate_main_live_pass_request(
m_stroke,
*b,
pass_orchestration,
copy_stroke_destination,
zoom,
size));
if (!main_live_ok) {
return false;
}
execute_stroke_preview_final_composite_and_copy(
StrokePreviewCompositePassInputs(
size,
glm::vec2(b->m_pattern_scale),
*b,
material.composite_pass,
m_tex_background,
m_tex,
m_tex_dual,
m_sampler_linear,
m_sampler_linear_repeat,
[&] {
m_plane.draw_fill();
}),
m_tex_preview,
size);
return true;
}();
assert(sequence_ok);
m_rtt.unbindFramebuffer();
@@ -913,79 +970,6 @@ void NodeStrokePreview::execute_stroke_draw_immediate_dual_pass(
});
}
pp::panopainter::LegacyNodeStrokePreviewPassSequenceRequest
NodeStrokePreview::make_stroke_draw_immediate_pass_sequence_request(
Stroke& stroke,
Stroke& dual_stroke,
const Brush& brush,
const pp::panopainter::LegacyNodeStrokePreviewPassOrchestrationPlan& pass_orchestration,
std::shared_ptr<Brush> dual_brush,
bool copy_stroke_destination,
float zoom,
const glm::vec2& size)
{
const auto& material = pass_orchestration.material;
return pp::panopainter::LegacyNodeStrokePreviewPassSequenceRequest {
.dual_pass_enabled = material.dual_pass.enabled,
.prepare_dual_pass = [&] {
pp::panopainter::setup_legacy_stroke_dual_shader(material.dual_pass.uses_pattern);
bind_stroke_preview_dual_pass_textures(*dual_brush);
},
.execute_dual_pass = [&] {
execute_stroke_draw_immediate_dual_pass(
dual_stroke,
brush,
pass_orchestration,
dual_brush,
copy_stroke_destination,
zoom,
size);
},
.capture_background = [&] {
execute_stroke_preview_background_capture_pass(
size,
pass_orchestration.background_colorize,
m_tex_background,
[&] {
m_plane.draw_fill();
});
},
.prepare_main_pass = [&] {},
.execute_main_pass = [&] {
[[maybe_unused]] const bool main_live_ok =
pp::panopainter::execute_legacy_node_stroke_preview_main_live_pass(
make_stroke_draw_immediate_main_live_pass_request(
stroke,
brush,
pass_orchestration,
copy_stroke_destination,
zoom,
size));
},
.finish_main_pass = [&] {},
.execute_final_composite = [&] {
std::function<void()> draw_composite = [&] {
m_plane.draw_fill();
};
execute_stroke_preview_final_composite_and_copy(
StrokePreviewCompositePassInputs(
size,
glm::vec2(brush.m_pattern_scale),
brush,
material.composite_pass,
m_tex_background,
m_tex,
m_tex_dual,
m_sampler_linear,
m_sampler_linear_repeat,
std::move(draw_composite)),
m_tex_preview,
size);
},
.copy_preview_result = [&] {},
};
}
void NodeStrokePreview::execute_stroke_draw_immediate_main_live_sample_pass(
const Brush& brush,
bool copy_stroke_destination,
@@ -1004,29 +988,6 @@ void NodeStrokePreview::execute_stroke_draw_immediate_main_live_sample_pass(
});
}
pp::panopainter::LegacyNodeStrokePreviewMixExecutionRequest
NodeStrokePreview::make_stroke_draw_mix_execution_request(
const pp::panopainter::LegacyNodeStrokePreviewMixPassPlan& mix_pass,
gl_state& gl,
const glm::vec2& bb_min,
const glm::vec2& bb_sz)
{
return make_stroke_preview_mix_execution_request(
mix_pass.shader,
m_rtt_mixer,
*m_brush,
m_sampler_linear,
m_tex_background,
m_tex,
m_tex_dual,
gl,
bb_min,
bb_sz,
[this] {
m_plane.draw_fill();
});
}
Image NodeStrokePreview::render_to_image()
{
std::lock_guard<std::mutex> _lock(s_render_mutex);