From 341d114d9946a2f7347084361c8c95965685d528 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Sat, 13 Jun 2026 19:51:03 +0200 Subject: [PATCH] Extract stroke commit callback helper --- docs/modernization/debt.md | 6 + docs/modernization/tasks.md | 6 + src/canvas.cpp | 403 +++++++++++++++++++----------------- 3 files changed, 221 insertions(+), 194 deletions(-) diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 6646bde..ef014e7 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -99,6 +99,12 @@ agent or engineer to remove them without reconstructing context from chat. leaving the method with only shell assembly and executor dispatch. - 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw_mix()` now keeps only retained shell assembly and executor dispatch at the callsite. +- 2026-06-13: `DEBT-0036` was narrowed again. `Canvas::stroke_commit()` + now routes the large retained callback bundle through a local helper, leaving + the callsite with sequence planning and helper invocation. +- 2026-06-13: `DEBT-0036` was narrowed again. `Canvas::stroke_commit()` + now keeps the commit callback bundle in a local helper, leaving the callsite + with sequence planning and retained callback invocation only. - 2026-06-13: `DEBT-0036` was narrowed again. `NodeStrokePreview::draw_stroke_immediate()` now routes final composite execution and preview copy-back through a retained local wrapper, leaving the call site with only sequence wiring. diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index e49f4d0..26e070d 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -644,6 +644,12 @@ Progress Notes: - 2026-06-13: `Canvas::stroke_draw_mix()` now keeps just the retained shell assembly and executor dispatch at the callsite; the framebuffer setup callbacks are isolated in the helper. +- 2026-06-13: `Canvas::stroke_commit()` now routes the large retained callback + bundle through a local helper, leaving the callsite with sequence planning + and helper invocation. +- 2026-06-13: `Canvas::stroke_commit()` now keeps the commit callback bundle + in a local helper, leaving the callsite with sequence planning and retained + callback invocation only. - 2026-06-13: `Canvas::stroke_draw_samples()` now reuses a retained destination texture dispatch helper for the live sample path; `Canvas` still owns the concrete face textures and callback execution. diff --git a/src/canvas.cpp b/src/canvas.cpp index 1cfa7e6..383cad8 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -531,6 +531,206 @@ static auto make_canvas_stroke_mix_pass_shell( }); } +static auto make_canvas_stroke_commit_callbacks( + Canvas& canvas, + const glm::vec4& vp, + const glm::vec4& cc, + bool blend, + ActionStroke* action, + const Stroke* current_stroke, + const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence, + const pp::paint_renderer::CanvasStrokeCommitMaterialPlan& stroke_material) +{ + const auto& b = current_stroke->m_brush; + return pp::panopainter::make_legacy_canvas_stroke_commit_callbacks( + [&]() { + canvas.m_dirty = false; + canvas.m_dirty_stroke = true; // new stroke ready for timelapse capture + App::I->redraw = true; + canvas.m_unsaved = true; + App::I->title_update(); + }, + []() {}, + [&]() { + canvas.apply_canvas_viewport(0, 0, canvas.m_width, canvas.m_height); + canvas.apply_canvas_capability(canvas.blend_state(), false); + }, + [&]() { + blend ? canvas.apply_canvas_capability(canvas.blend_state(), true) : canvas.apply_canvas_capability(canvas.blend_state(), false); + canvas.apply_canvas_viewport(vp.x, vp.y, vp.width, vp.height); + canvas.apply_canvas_clear_color(cc); + set_active_texture_unit(0); + }, + [&]() { + action->m_layer_idx = canvas.m_current_layer_idx; + action->m_frame_idx = canvas.layer().m_frame_index; + action->m_canvas = &canvas; + ActionManager::add(action); + }, + [&]() { + canvas.stroke_commit_timelapse(); + }, + [&](int i) { + canvas.m_layers[canvas.m_current_layer_idx]->rtt(i).bindFramebuffer(); + }, + [&](int i) { + glm::vec2 box_sz = zw(canvas.m_dirty_box[i]) - xy(canvas.m_dirty_box[i]); + action->m_image[i] = std::make_unique( + static_cast(box_sz.x * box_sz.y * 4)); + canvas.m_layers[canvas.m_current_layer_idx]->rtt(i).readPixelsRgba8( + static_cast(canvas.m_dirty_box[i].x), + static_cast(canvas.m_dirty_box[i].y), + static_cast(box_sz.x), + static_cast(box_sz.y), + action->m_image[i].get()); + + action->m_box[i] = canvas.m_dirty_box[i]; + action->m_old_box[i] = canvas.m_layers[canvas.m_current_layer_idx]->box(i); + action->m_old_dirty[i] = canvas.m_layers[canvas.m_current_layer_idx]->face(i); + }, + [&](int i) { + if (!canvas.m_layers[canvas.m_current_layer_idx]->m_alpha_locked) { + auto& lbox = canvas.m_layers[canvas.m_current_layer_idx]->box(i); + lbox = glm::vec4( + glm::min(xy(canvas.m_dirty_box[i]), xy(lbox)), + glm::max(zw(canvas.m_dirty_box[i]), zw(lbox))); + } + canvas.m_layers[canvas.m_current_layer_idx]->face(i) = true; + }, + [&](int i) { + set_active_texture_unit(0); + canvas.m_tex2[i].bind(); + copy_framebuffer_to_texture_2d(0, 0, 0, 0, canvas.m_width, canvas.m_height); + canvas.m_tex2[i].unbind(); + }, + [&](int i) { + pp::panopainter::bind_legacy_canvas_stroke_commit_inputs( + sequence, + [&](int texture_slot) { + set_active_texture_unit(texture_slot); + }, + [&](pp::paint_renderer::CanvasStrokeCommitTextureRole role) { + switch (role) { + case pp::paint_renderer::CanvasStrokeCommitTextureRole::layer_scratch: + canvas.m_tex2[i].bind(); + break; + case pp::paint_renderer::CanvasStrokeCommitTextureRole::stroke: + canvas.m_tmp[i].bindTexture(); + break; + case pp::paint_renderer::CanvasStrokeCommitTextureRole::selection_mask: + canvas.m_smask.rtt(i).bindTexture(); + break; + case pp::paint_renderer::CanvasStrokeCommitTextureRole::dual_stroke: + canvas.m_tmp_dual[i].bindTexture(); + break; + case pp::paint_renderer::CanvasStrokeCommitTextureRole::pattern: + b->m_pattern_texture ? b->m_pattern_texture->bind() : unbind_texture_2d(); + break; + } + }, + [&](pp::paint_renderer::CanvasStrokeCommitTextureRole role, int texture_slot) { + switch (role) { + case pp::paint_renderer::CanvasStrokeCommitTextureRole::layer_scratch: + canvas.m_sampler.bind(texture_slot); + break; + case pp::paint_renderer::CanvasStrokeCommitTextureRole::stroke: + canvas.m_sampler_nearest.bind(texture_slot); + break; + case pp::paint_renderer::CanvasStrokeCommitTextureRole::selection_mask: + case pp::paint_renderer::CanvasStrokeCommitTextureRole::dual_stroke: + canvas.m_sampler.bind(texture_slot); + break; + case pp::paint_renderer::CanvasStrokeCommitTextureRole::pattern: + canvas.m_sampler_stencil.bind(texture_slot); + break; + } + }); + }, + [&](int) { + pp::panopainter::execute_legacy_canvas_stroke_commit_erase( + [&]() { + pp::panopainter::setup_legacy_stroke_erase_shader( + pp::panopainter::LegacyStrokeEraseUniforms { + .mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f), + .texture_slot = 0, + .stroke_texture_slot = 1, + .mask_texture_slot = 2, + .alpha = 1.0f, + .mask_enabled = canvas.m_smask_active, + }); + }, + [&]() { + canvas.m_plane.draw_fill(); + }); + }, + [&](int) { + glm::vec2 patt_scale = glm::vec2(b->m_pattern_scale); + if (b->m_pattern_flipx) patt_scale.x *= -1.f; + if (b->m_pattern_flipy) patt_scale.y *= -1.f; + + pp::panopainter::execute_legacy_canvas_stroke_commit_paint( + [&]() { + pp::panopainter::setup_legacy_stroke_composite_shader( + pp::panopainter::LegacyStrokeCompositeUniforms { + .resolution = canvas.m_size, + .pattern = { + .scale = patt_scale, + .invert = static_cast(b->m_pattern_invert), + .brightness = b->m_pattern_brightness, + .contrast = b->m_pattern_contrast, + .depth = b->m_pattern_depth, + .blend_mode = b->m_pattern_blend_mode, + .offset = canvas.m_pattern_offset, + }, + .mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f), + .layer_alpha = 1.0f, + .alpha_lock = canvas.m_layers[canvas.m_current_layer_idx]->m_alpha_locked, + .mask_enabled = canvas.m_smask_active, + .use_fragcoord = false, + .blend_mode = b->m_blend_mode, + .use_dual = stroke_material.composite_pass.use_dual, + .dual_blend_mode = stroke_material.composite_pass.dual_blend_mode, + .dual_alpha = stroke_material.composite_pass.dual_alpha, + .use_pattern = stroke_material.composite_pass.use_pattern, + }); + }, + [&]() { + canvas.m_plane.draw_fill(); + }); + }, + [&](int i) { + pp::panopainter::copy_legacy_canvas_stroke_commit_to_dilate_source( + sequence, + [&]() { + pp::panopainter::setup_legacy_stroke_dilate_shader( + pp::panopainter::LegacyStrokeDilateUniforms { + .mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f), + }); + }, + [&](int texture_slot) { + set_active_texture_unit(texture_slot); + }, + [&]() { + canvas.m_tex2[i].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::LegacyCanvasStrokeCommitCopyExtent { + .width = canvas.m_width, + .height = canvas.m_height, + }); + }, + [&](int) { + pp::panopainter::execute_legacy_canvas_stroke_commit_dilate([&]() { + canvas.m_plane.draw_fill(); + }); + }, + [&](int i) { + canvas.m_layers[canvas.m_current_layer_idx]->rtt(i).unbindFramebuffer(); + }); +} + glm::vec4 Canvas::stroke_draw_samples( int i, std::vector& P, @@ -1120,200 +1320,15 @@ void Canvas::stroke_commit() }; } - const auto commit_callbacks = pp::panopainter::make_legacy_canvas_stroke_commit_callbacks( - [&]() { - m_dirty = false; - m_dirty_stroke = true; // new stroke ready for timelapse capture - App::I->redraw = true; - m_unsaved = true; - App::I->title_update(); - }, - []() {}, - [&]() { - apply_canvas_viewport(0, 0, m_width, m_height); - apply_canvas_capability(blend_state(), false); - }, - [&]() { - blend ? apply_canvas_capability(blend_state(), true) : apply_canvas_capability(blend_state(), false); - apply_canvas_viewport(vp.x, vp.y, vp.width, vp.height); - apply_canvas_clear_color(cc); - set_active_texture_unit(0); - }, - [&]() { - action->m_layer_idx = m_current_layer_idx; - action->m_frame_idx = layer().m_frame_index; - action->m_canvas = this; - //action->m_stroke = std::move(m_current_stroke); - ActionManager::add(action); - }, - [&]() { - stroke_commit_timelapse(); - }, - [&](int i) { - m_layers[m_current_layer_idx]->rtt(i).bindFramebuffer(); - }, - [&](int i) { - // save image before commit - glm::vec2 box_sz = zw(m_dirty_box[i]) - xy(m_dirty_box[i]); - action->m_image[i] = std::make_unique( - static_cast(box_sz.x * box_sz.y * 4)); - m_layers[m_current_layer_idx]->rtt(i).readPixelsRgba8( - static_cast(m_dirty_box[i].x), - static_cast(m_dirty_box[i].y), - static_cast(box_sz.x), - static_cast(box_sz.y), - action->m_image[i].get()); - - action->m_box[i] = m_dirty_box[i]; - action->m_old_box[i] = m_layers[m_current_layer_idx]->box(i); - action->m_old_dirty[i] = m_layers[m_current_layer_idx]->face(i); - }, - [&](int i) { - if (!m_layers[m_current_layer_idx]->m_alpha_locked) - { - auto& lbox = m_layers[m_current_layer_idx]->box(i); - lbox = glm::vec4( - glm::min(xy(m_dirty_box[i]), xy(lbox)), - glm::max(zw(m_dirty_box[i]), zw(lbox)) - ); - } - m_layers[m_current_layer_idx]->face(i) = true; - }, - [&](int i) { - // copy to tmp2 for layer blending - set_active_texture_unit(0); - m_tex2[i].bind(); - copy_framebuffer_to_texture_2d(0, 0, 0, 0, m_width, m_height); - m_tex2[i].unbind(); - }, - [&](int i) { - pp::panopainter::bind_legacy_canvas_stroke_commit_inputs( - sequence, - [&](int texture_slot) { - set_active_texture_unit(texture_slot); - }, - [&](pp::paint_renderer::CanvasStrokeCommitTextureRole role) { - switch (role) { - case pp::paint_renderer::CanvasStrokeCommitTextureRole::layer_scratch: - m_tex2[i].bind(); - break; - case pp::paint_renderer::CanvasStrokeCommitTextureRole::stroke: - m_tmp[i].bindTexture(); - break; - case pp::paint_renderer::CanvasStrokeCommitTextureRole::selection_mask: - m_smask.rtt(i).bindTexture(); - break; - case pp::paint_renderer::CanvasStrokeCommitTextureRole::dual_stroke: - m_tmp_dual[i].bindTexture(); - break; - case pp::paint_renderer::CanvasStrokeCommitTextureRole::pattern: - b->m_pattern_texture ? b->m_pattern_texture->bind() : unbind_texture_2d(); - break; - } - }, - [&](pp::paint_renderer::CanvasStrokeCommitTextureRole role, int texture_slot) { - switch (role) { - case pp::paint_renderer::CanvasStrokeCommitTextureRole::layer_scratch: - m_sampler.bind(texture_slot); - break; - case pp::paint_renderer::CanvasStrokeCommitTextureRole::stroke: - m_sampler_nearest.bind(texture_slot); - break; - case pp::paint_renderer::CanvasStrokeCommitTextureRole::selection_mask: - case pp::paint_renderer::CanvasStrokeCommitTextureRole::dual_stroke: - m_sampler.bind(texture_slot); - break; - case pp::paint_renderer::CanvasStrokeCommitTextureRole::pattern: - m_sampler_stencil.bind(texture_slot); - break; - } - }); - }, - [&](int) { - pp::panopainter::execute_legacy_canvas_stroke_commit_erase( - [&]() { - pp::panopainter::setup_legacy_stroke_erase_shader( - pp::panopainter::LegacyStrokeEraseUniforms { - .mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f), - .texture_slot = 0, - .stroke_texture_slot = 1, - .mask_texture_slot = 2, - .alpha = 1.0f, - .mask_enabled = m_smask_active, - }); - }, - [&]() { - m_plane.draw_fill(); - }); - }, - [&](int) { - glm::vec2 patt_scale = glm::vec2(b->m_pattern_scale); - if (b->m_pattern_flipx) patt_scale.x *= -1.f; - if (b->m_pattern_flipy) patt_scale.y *= -1.f; - - pp::panopainter::execute_legacy_canvas_stroke_commit_paint( - [&]() { - pp::panopainter::setup_legacy_stroke_composite_shader( - pp::panopainter::LegacyStrokeCompositeUniforms { - .resolution = m_size, - .pattern = { - .scale = patt_scale, - .invert = static_cast(b->m_pattern_invert), - .brightness = b->m_pattern_brightness, - .contrast = b->m_pattern_contrast, - .depth = b->m_pattern_depth, - .blend_mode = b->m_pattern_blend_mode, - .offset = m_pattern_offset, - }, - .mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f), - .layer_alpha = 1.0f, - .alpha_lock = m_layers[m_current_layer_idx]->m_alpha_locked, - .mask_enabled = m_smask_active, - .use_fragcoord = false, - .blend_mode = b->m_blend_mode, - .use_dual = stroke_material.composite_pass.use_dual, - .dual_blend_mode = stroke_material.composite_pass.dual_blend_mode, - .dual_alpha = stroke_material.composite_pass.dual_alpha, - .use_pattern = stroke_material.composite_pass.use_pattern, - }); - }, - [&]() { - m_plane.draw_fill(); - }); - }, - [&](int i) { - pp::panopainter::copy_legacy_canvas_stroke_commit_to_dilate_source( - sequence, - [&]() { - // Dilate borders to avoid interpolation bleeding - pp::panopainter::setup_legacy_stroke_dilate_shader( - pp::panopainter::LegacyStrokeDilateUniforms { - .mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f), - }); - }, - [&](int texture_slot) { - set_active_texture_unit(texture_slot); - }, - [&]() { - m_tex2[i].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::LegacyCanvasStrokeCommitCopyExtent { - .width = m_width, - .height = m_height, - }); - }, - [&](int) { - pp::panopainter::execute_legacy_canvas_stroke_commit_dilate([&]() { - m_plane.draw_fill(); - }); - }, - [&](int i) { - m_layers[m_current_layer_idx]->rtt(i).unbindFramebuffer(); - } - ); + const auto commit_callbacks = make_canvas_stroke_commit_callbacks( + *this, + vp, + cc, + blend, + action, + m_current_stroke, + sequence, + stroke_material); [[maybe_unused]] const auto commit_result = pp::panopainter::execute_legacy_canvas_stroke_commit_sequence( pp::panopainter::LegacyCanvasStrokeCommitRequest {