Extract canvas document IO and preview pass setup

This commit is contained in:
2026-06-16 10:53:20 +02:00
parent 551fe6c94a
commit 8afeb087b8
7 changed files with 1341 additions and 1179 deletions

View File

@@ -22,6 +22,7 @@ set(PP_LEGACY_PAINT_DOCUMENT_SOURCES
src/canvas.cpp
src/canvas_actions.cpp
src/canvas_layer.cpp
src/legacy_canvas_document_io_services.cpp
src/legacy_canvas_state_services.cpp
src/event.cpp
)

View File

@@ -79,13 +79,13 @@ What is still carrying too much live ownership:
Current hotspot files:
- `src/canvas.cpp`: 3622 lines
- `src/canvas.cpp`: 2645 lines
- `src/app_layout.cpp`: 1498 lines
- `src/canvas_modes.cpp`: 1798 lines
- `src/node.cpp`: 1551 lines
- `src/main.cpp`: 1374 lines
- `src/node_panel_brush.cpp`: 1197 lines
- `src/node_stroke_preview.cpp`: 1129 lines
- `src/node_stroke_preview.cpp`: 933 lines
- `src/node_canvas.cpp`: 888 lines
- `src/app.cpp`: 950 lines
- `src/app_dialogs.cpp`: 908 lines
@@ -155,8 +155,11 @@ Current architecture mismatches that must be treated as real blockers:
canvas state-management cluster for picking, clear/clear-all, layer
add/remove/order/lookups, animation frame control, resize, and snapshot
save/restore now lives in `src/legacy_canvas_state_services.cpp` instead of
`src/canvas.cpp`, even though the bridge still owns worker-side readback flow
and encoder-state label reads.
`src/canvas.cpp`, while the larger import/export/save/open/thumbnail
document-IO cluster now lives in `src/legacy_canvas_document_io_services.cpp`
and `NodeStrokePreview` render-target setup plus immediate-pass sequencing
now route through retained preview execution helpers, even though the bridge
still owns worker-side readback flow and encoder-state label reads.
- Modern C++23 usage exists in extracted components, especially `std::span`,
explicit result/status objects, and a few concepts, but the live app still
does not consistently express ownership, thread affinity, or renderer

View File

@@ -91,7 +91,7 @@ Status: In Progress
Why now:
`src/canvas.cpp` is still the biggest single architectural blocker at about
3622 lines.
2645 lines.
Current slice:
- Canvas state-management helpers for picking, clear/clear-all, layer
@@ -99,6 +99,10 @@ Current slice:
save/restore now live in `src/legacy_canvas_state_services.cpp` instead of
staying inline in `src/canvas.cpp`, but the file still owns the larger
document-plus-render shell.
- Canvas import/export/save/open/thumbnail ownership now lives in
`src/legacy_canvas_document_io_services.cpp` instead of staying inline in
`src/canvas.cpp`, which materially reduces document IO ownership in the live
render shell.
Write scope:
- `src/canvas.cpp`
@@ -138,6 +142,12 @@ Current slice:
- `NodeStrokePreview` final composite plus preview-texture copy now route
through `legacy_node_stroke_preview_execution_services.h`, but the preview
node still owns most live-pass and retained GL resource execution.
- `NodeStrokePreview` render-target setup plus immediate-pass sequence
orchestration now also route through
`legacy_node_stroke_preview_execution_services.h`, and duplicate render-
target setup was removed from `render_to_image()` and the queued worker path,
but the preview node still owns broader live-pass state and thread-facing
orchestration.
- `NodeCanvas` merged-path per-plane merged-texture draw execution now also
routes through `execute_legacy_canvas_draw_merge_layer_texture(...)`.
- `NodeCanvas` merged-path and non-blend checkerboard background setup now also

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -148,6 +148,40 @@ struct LegacyNodeStrokePreviewPassOrchestrationPlan {
bool background_colorize = false;
};
struct LegacyNodeStrokePreviewRenderTargetSetup {
RTT& preview_rtt;
RTT& preview_rtt_mixer;
Texture2D& preview_stroke_texture;
Texture2D& preview_dual_texture;
Texture2D& preview_background_texture;
Texture2D& preview_image_texture;
glm::vec2 size {};
};
[[nodiscard]] inline bool ensure_legacy_node_stroke_preview_render_targets(
const LegacyNodeStrokePreviewRenderTargetSetup& setup)
{
if (setup.size.x <= 0.0f || setup.size.y <= 0.0f) {
return false;
}
const auto width = static_cast<int>(setup.size.x);
const auto height = static_cast<int>(setup.size.y);
if (!setup.preview_image_texture.ready() || setup.preview_image_texture.size() != setup.size) {
setup.preview_image_texture.create(width, height);
}
if (setup.preview_stroke_texture.size() != setup.size) {
setup.preview_rtt.create(width, height);
setup.preview_rtt_mixer.create(width, height);
setup.preview_stroke_texture.create(width, height);
setup.preview_dual_texture.create(width, height);
setup.preview_background_texture.create(width, height);
}
return true;
}
struct LegacyNodeStrokePreviewMainPassTextureDispatch {
std::function<void(int)> activate_texture_unit;
std::function<void()> bind_brush_tip;
@@ -245,6 +279,34 @@ template <typename Frame>
return true;
}
struct LegacyNodeStrokePreviewImmediatePassSequenceRequest {
std::function<void()> execute_dual_pass;
std::function<void()> capture_background;
std::function<bool()> execute_main_live_pass;
std::function<bool()> execute_final_composite;
};
[[nodiscard]] inline bool execute_legacy_node_stroke_preview_immediate_pass_sequence(
const LegacyNodeStrokePreviewImmediatePassSequenceRequest& request)
{
if (!request.capture_background ||
!request.execute_main_live_pass ||
!request.execute_final_composite) {
return false;
}
if (request.execute_dual_pass) {
request.execute_dual_pass();
}
request.capture_background();
if (!request.execute_main_live_pass()) {
return false;
}
return request.execute_final_composite();
}
[[nodiscard]] inline bool execute_legacy_node_stroke_preview_final_composite(
glm::vec2 size,
glm::vec2 pattern_scale,

View File

@@ -588,6 +588,19 @@ void NodeStrokePreview::draw_stroke_immediate()
if (m_size.x == 0 || m_size.y == 0)
return;
if (!pp::panopainter::ensure_legacy_node_stroke_preview_render_targets(
pp::panopainter::LegacyNodeStrokePreviewRenderTargetSetup {
.preview_rtt = m_rtt,
.preview_rtt_mixer = m_rtt_mixer,
.preview_stroke_texture = m_tex,
.preview_dual_texture = m_tex_dual,
.preview_background_texture = m_tex_background,
.preview_image_texture = m_tex_preview,
.size = m_preview_size,
})) {
return;
}
const auto vp = query_stroke_preview_viewport();
const auto cc = query_stroke_preview_clear_color();
@@ -665,8 +678,6 @@ void NodeStrokePreview::draw_stroke_immediate()
m_dual_stroke.add_point(point.position, point.pressure);
}
const glm::vec2 patt_scale = stroke_setup.pattern_scale;
apply_stroke_preview_capability(pp::renderer::gl::blend_state(), false);
const auto pass_orchestration = pp::panopainter::plan_legacy_node_stroke_preview_pass_orchestration(
pp::panopainter::LegacyNodeStrokePreviewPassOrchestrationRequest {
@@ -696,64 +707,64 @@ 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 = [&] {
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);
}
const bool sequence_ok = pp::panopainter::execute_legacy_node_stroke_preview_immediate_pass_sequence(
pp::panopainter::LegacyNodeStrokePreviewImmediatePassSequenceRequest {
.execute_dual_pass = [&] {
if (!pass_orchestration.material.dual_pass.enabled) {
return;
}
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,
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));
if (!main_live_ok) {
return false;
}
const bool final_composite_ok = pp::panopainter::execute_legacy_node_stroke_preview_final_composite(
size,
glm::vec2(b->m_pattern_scale),
*b,
material.composite_pass,
m_tex_background,
m_tex,
m_tex_dual,
m_tex_preview,
m_sampler_linear,
m_sampler_linear_repeat,
[&] {
b->m_pattern_texture ? b->m_pattern_texture->bind() : unbind_texture_2d();
size);
},
[&] {
m_plane.draw_fill();
});
if (!final_composite_ok) {
return false;
}
return true;
}();
.capture_background = [&] {
execute_stroke_preview_background_capture_pass(
size,
pass_orchestration.background_colorize,
m_tex_background,
[&] {
m_plane.draw_fill();
});
},
.execute_main_live_pass = [&]() -> bool {
return 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));
},
.execute_final_composite = [&]() -> bool {
return pp::panopainter::execute_legacy_node_stroke_preview_final_composite(
size,
glm::vec2(b->m_pattern_scale),
*b,
material.composite_pass,
m_tex_background,
m_tex,
m_tex_dual,
m_tex_preview,
m_sampler_linear,
m_sampler_linear_repeat,
[&] {
b->m_pattern_texture ? b->m_pattern_texture->bind() : unbind_texture_2d();
},
[&] {
m_plane.draw_fill();
});
},
});
assert(sequence_ok);
m_rtt.unbindFramebuffer();
@@ -888,17 +899,6 @@ Image NodeStrokePreview::render_to_image()
std::lock_guard<std::mutex> _lock(s_render_mutex);
App::I->render_task([this] {
auto new_size = m_preview_size;
if (!m_tex_preview.ready() || m_tex_preview.size() != new_size)
m_tex_preview.create((int)new_size.x, (int)new_size.y);
if (m_tex.size() != new_size)
{
m_rtt.create((int)new_size.x, (int)new_size.y);
m_rtt_mixer.create((int)new_size.x, (int)new_size.y);
m_tex.create((int)new_size.x, (int)new_size.y);
m_tex_dual.create((int)new_size.x, (int)new_size.y);
m_tex_background.create((int)new_size.x, (int)new_size.y);
}
draw_stroke_immediate();
});
return m_tex_preview.get_image();
@@ -940,18 +940,6 @@ void NodeStrokePreview::draw_stroke()
gl_state gl;
gl.save();
auto new_size = node->m_preview_size;
if (!node->m_tex_preview.ready() || node->m_tex_preview.size() != new_size)
node->m_tex_preview.create((int)new_size.x, (int)new_size.y);
if (m_tex.size() != new_size)
{
m_rtt.create((int)new_size.x, (int)new_size.y);
m_rtt_mixer.create((int)new_size.x, (int)new_size.y);
m_tex.create((int)new_size.x, (int)new_size.y);
m_tex_dual.create((int)new_size.x, (int)new_size.y);
m_tex_background.create((int)new_size.x, (int)new_size.y);
}
node->m_brush->load();
node->draw_stroke_immediate();
if (to_unload)