Extract canvas stroke commit and brush preset services

This commit is contained in:
2026-06-16 19:07:04 +02:00
parent a5002a4e3e
commit 200265e11d
10 changed files with 1188 additions and 1005 deletions

View File

@@ -932,448 +932,6 @@ pp::panopainter::LegacyCanvasStrokeMixPassShell make_canvas_stroke_mix_pass_shel
});
}
static void stamp_canvas_stroke_commit_action(
Canvas& canvas,
ActionStroke* action);
static void capture_canvas_stroke_commit_layer_state(
Canvas& canvas,
ActionStroke* action,
int i);
static void apply_canvas_stroke_commit_dirty_mutation(
Canvas& canvas,
int i);
static void copy_canvas_stroke_commit_layer_image(
Canvas& canvas,
int i);
template <typename SetActiveTextureUnit>
static auto make_canvas_stroke_commit_callbacks(
Canvas& canvas,
pp::renderer::gl::OpenGlViewportRect vp,
std::array<float, 4> cc,
bool blend,
SetActiveTextureUnit&& set_active_texture_unit,
ActionStroke* action,
const Stroke* current_stroke,
const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence,
const pp::paint_renderer::CanvasStrokeMaterialPlan& stroke_material)
{
const auto& b = current_stroke->m_brush;
auto bind_commit_inputs = [&](int i) {
pp::panopainter::bind_legacy_canvas_stroke_commit_face_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;
}
});
};
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();
},
[]() {},
[&]() {
apply_canvas_viewport(0, 0, canvas.m_width, canvas.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);
},
[&]() { stamp_canvas_stroke_commit_action(canvas, action); },
[&]() {
canvas.stroke_commit_timelapse();
},
[](int) {},
[&](int i) { capture_canvas_stroke_commit_layer_state(canvas, action, i); },
[&](int i) { apply_canvas_stroke_commit_dirty_mutation(canvas, i); },
[&](int i) { copy_canvas_stroke_commit_layer_image(canvas, i); },
[&](int i) {
bind_commit_inputs(i);
},
[&](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<float>(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<vertex_t>& P,
bool copy_stroke_destination)
{
constexpr std::array destination_texture_binding {
pp::panopainter::LegacyCanvasStrokeTextureBinding {
.input = pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination,
.slot = 1,
},
};
const auto result = pp::panopainter::execute_legacy_canvas_stroke_face_sample_polygon(
make_stroke_draw_samples_request(
i,
P,
copy_stroke_destination),
destination_texture_binding,
make_stroke_draw_samples_destination_texture_dispatch(i));
return result.dirty_bounds;
}
pp::panopainter::LegacyCanvasStrokeTextureInputDispatch Canvas::make_stroke_draw_samples_destination_texture_dispatch(
int face_index)
{
return pp::panopainter::make_legacy_canvas_stroke_destination_texture_dispatch(
[&](int texture_slot) {
set_active_texture_unit(texture_slot);
},
[&, face_index] {
m_tex[face_index].bind(); // bg, copy of framebuffer (copied before drawing)
},
[&, face_index] {
m_tex[face_index].unbind();
});
}
pp::panopainter::LegacyStrokeFaceSamplePolygonExecutionRequest Canvas::make_stroke_draw_samples_request(
int face_index,
std::vector<vertex_t>& polygon_vertices,
bool copy_stroke_destination)
{
return pp::panopainter::LegacyStrokeFaceSamplePolygonExecutionRequest {
.context = "Canvas::stroke_draw_samples",
.target_size = { m_width, m_height },
.polygon_vertices = polygon_vertices,
.face_index = face_index,
.copy_stroke_destination = copy_stroke_destination,
.copy_framebuffer_to_destination_texture = [](
int,
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);
},
.upload_brush_vertices = [&](int, std::span<const vertex_t> vertices) {
m_brush_shape.update_vertices(
const_cast<vertex_t*>(vertices.data()),
static_cast<int>(vertices.size()));
},
.draw_brush_shape = [&](int) {
m_brush_shape.draw_fill();
},
};
}
template <typename BuildRequest>
static auto execute_canvas_stroke_commit_sequence(
BuildRequest&& build_request)
{
return pp::panopainter::execute_legacy_canvas_stroke_commit_sequence(
build_request());
}
template <typename SetActiveTextureUnit>
static auto make_canvas_stroke_commit_request(
Canvas& canvas,
pp::renderer::gl::OpenGlViewportRect vp,
std::array<float, 4> cc,
bool blend,
SetActiveTextureUnit&& set_active_texture_unit,
ActionStroke* action,
const Stroke* current_stroke,
const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence,
const pp::paint_renderer::CanvasStrokeMaterialPlan& stroke_material)
{
const auto commit_callbacks = make_canvas_stroke_commit_callbacks(
canvas,
vp,
cc,
blend,
std::forward<SetActiveTextureUnit>(set_active_texture_unit),
action,
current_stroke,
sequence,
stroke_material);
const std::array<pp::panopainter::LegacyCanvasStrokeCommitFace, 6> faces {
pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 0, .dirty = canvas.m_dirty_face[0] },
pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 1, .dirty = canvas.m_dirty_face[1] },
pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 2, .dirty = canvas.m_dirty_face[2] },
pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 3, .dirty = canvas.m_dirty_face[3] },
pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 4, .dirty = canvas.m_dirty_face[4] },
pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 5, .dirty = canvas.m_dirty_face[5] },
};
return pp::panopainter::make_legacy_canvas_stroke_commit_request(faces, sequence, commit_callbacks);
}
template <typename SetActiveTextureUnit>
static auto execute_canvas_stroke_commit_request(
Canvas& canvas,
pp::renderer::gl::OpenGlViewportRect vp,
std::array<float, 4> cc,
bool blend,
SetActiveTextureUnit&& set_active_texture_unit,
ActionStroke* action,
const Stroke* current_stroke,
const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence,
const pp::paint_renderer::CanvasStrokeMaterialPlan& stroke_material)
{
return execute_canvas_stroke_commit_sequence([&]() {
return make_canvas_stroke_commit_request(
canvas,
vp,
cc,
blend,
std::forward<SetActiveTextureUnit>(set_active_texture_unit),
action,
current_stroke,
sequence,
stroke_material);
});
}
template <typename SetActiveTextureUnit>
static auto execute_canvas_stroke_commit_dispatch(
Canvas& canvas,
pp::renderer::gl::OpenGlViewportRect vp,
std::array<float, 4> cc,
bool blend,
SetActiveTextureUnit&& set_active_texture_unit,
ActionStroke* action,
const Stroke* current_stroke,
const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence,
const pp::paint_renderer::CanvasStrokeMaterialPlan& stroke_material)
{
return execute_canvas_stroke_commit_request(
canvas,
vp,
cc,
blend,
std::forward<SetActiveTextureUnit>(set_active_texture_unit),
action,
current_stroke,
sequence,
stroke_material);
}
struct CanvasStrokeCommitPrelude {
pp::renderer::gl::OpenGlViewportRect viewport;
std::array<float, 4> clear_color;
bool blend;
ActionStroke* action;
};
static CanvasStrokeCommitPrelude make_canvas_stroke_commit_prelude(Canvas& canvas)
{
CanvasStrokeCommitPrelude prelude {
.viewport = query_canvas_viewport(),
.clear_color = query_canvas_clear_color(),
.blend = query_canvas_capability(blend_state()),
.action = new ActionStroke,
};
prelude.action->was_saved = !canvas.m_unsaved;
return prelude;
}
static pp::paint_renderer::CanvasStrokeCommitSequencePlan
make_canvas_stroke_commit_sequence_plan(
const Canvas& canvas,
kCanvasMode current_mode,
int current_layer_idx,
bool smask_active,
const pp::paint_renderer::CanvasStrokeMaterialPlan& stroke_material)
{
return pp::paint_renderer::plan_canvas_stroke_commit_sequence(
pp::paint_renderer::CanvasStrokeCommitRequest {
.erase_mode = current_mode == kCanvasMode::Erase,
.alpha_locked = canvas.m_layers[current_layer_idx]->m_alpha_locked,
.selection_mask_active = smask_active,
.dual_stroke_enabled = stroke_material.composite_pass.use_dual,
.pattern_enabled = stroke_material.composite_pass.use_pattern,
});
}
static void stamp_canvas_stroke_commit_action(
Canvas& canvas,
ActionStroke* action)
{
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);
}
static void capture_canvas_stroke_commit_layer_state(
Canvas& canvas,
ActionStroke* action,
int i)
{
canvas.m_layers[canvas.m_current_layer_idx]->rtt(i).bindFramebuffer();
glm::vec2 box_sz = zw(canvas.m_dirty_box[i]) - xy(canvas.m_dirty_box[i]);
action->m_image[i] = std::make_unique<uint8_t[]>(
static_cast<std::size_t>(box_sz.x * box_sz.y * 4));
canvas.m_layers[canvas.m_current_layer_idx]->rtt(i).readPixelsRgba8(
static_cast<int>(canvas.m_dirty_box[i].x),
static_cast<int>(canvas.m_dirty_box[i].y),
static_cast<int>(box_sz.x),
static_cast<int>(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);
}
static void apply_canvas_stroke_commit_dirty_mutation(
Canvas& canvas,
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;
}
static void copy_canvas_stroke_commit_layer_image(
Canvas& canvas,
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();
}
std::vector<Canvas::StrokeFrame> Canvas::stroke_draw_compute(Stroke& stroke) const
{
auto samples = stroke.compute_samples();
@@ -2063,34 +1621,6 @@ glm::vec3 Canvas::point_trace(glm::vec2 loc)
{
return pp::panopainter::legacy_canvas_point_trace(*this, loc);
}
void Canvas::stroke_commit()
{
if (!m_dirty || m_layers.empty())
return;
const auto prelude = make_canvas_stroke_commit_prelude(*this);
const auto& b = m_current_stroke->m_brush;
const auto stroke_material = canvas_stroke_material_plan(*b, false);
const auto stroke_commit_sequence = make_canvas_stroke_commit_sequence_plan(
*this,
m_current_mode,
m_current_layer_idx,
m_smask_active,
stroke_material);
[[maybe_unused]] const auto commit_result = execute_canvas_stroke_commit_dispatch(
*this,
prelude.viewport,
prelude.clear_color,
prelude.blend,
[&](int texture_slot) {
set_active_texture_unit(texture_slot);
},
prelude.action,
m_current_stroke.get(),
stroke_commit_sequence,
stroke_material);
}
void Canvas::stroke_commit_timelapse()
{
if (m_encoder && App::I->rec_running)