Narrow stroke execution planning helpers
This commit is contained in:
@@ -18,6 +18,13 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
|
||||
## Recent Reductions
|
||||
|
||||
- 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` face
|
||||
dirty-box planning now routes through a retained stroke execution helper
|
||||
wrapping `pp_paint_renderer`, retained stroke commit step dispatch clamps
|
||||
malformed step counts to the fixed plan array, and compositor coverage now
|
||||
exercises malformed retained commit plans plus all-input stroke-preview
|
||||
composite planning. Live stroke rasterization, callback execution, texture
|
||||
binding, and history mutation remain retained.
|
||||
- 2026-06-13: DEBT-0036 was narrowed again. Remaining live shader setup
|
||||
outside retained helper headers now routes through retained helper surfaces
|
||||
for canvas modes, equirect layer export, `NodeCanvas` debug dirty bounds,
|
||||
|
||||
@@ -3073,6 +3073,12 @@ Results:
|
||||
`NodeCanvas` debug dirty bounds, atlas image drawing, and text drawing, while
|
||||
geometry, framebuffer flow, texture/sampler binding, blend/depth state,
|
||||
readback, and draw execution remain retained.
|
||||
- `Canvas::stroke_draw` face dirty-box planning now shares a retained stroke
|
||||
execution helper wrapping `pp_paint_renderer`, retained stroke commit step
|
||||
dispatch clamps malformed step counts to the fixed plan array, and
|
||||
compositor coverage now includes malformed retained commit plans plus
|
||||
all-input stroke-preview composite planning. Live stroke rasterization,
|
||||
callback execution, texture binding, and history mutation remain retained.
|
||||
- Remaining simple color, hue, color-quad, grid heightmap, and pen/line
|
||||
preview shader setup in UI nodes and canvas modes now shares retained helper
|
||||
surfaces, while geometry, texture/sampler binding, blend/depth state,
|
||||
|
||||
@@ -780,16 +780,16 @@ void Canvas::stroke_draw()
|
||||
|
||||
m_tmp[i].unbindFramebuffer();
|
||||
|
||||
const auto dirty_update = pp::paint_renderer::plan_canvas_stroke_face_dirty_update(
|
||||
pp::paint_renderer::CanvasStrokeFaceDirtyUpdateRequest {
|
||||
const auto dirty_update = pp::panopainter::plan_legacy_canvas_stroke_face_dirty_update(
|
||||
pp::panopainter::LegacyCanvasStrokeFaceDirtyRequest {
|
||||
.extent = stroke_extent,
|
||||
.previous_accumulated_dirty_box = canvas_stroke_box(m_dirty_box[i]),
|
||||
.previous_pass_dirty_box = canvas_stroke_box(box_face[i]),
|
||||
.sample_dirty_box = canvas_stroke_box(box_sample),
|
||||
.previous_accumulated_dirty_box = m_dirty_box[i],
|
||||
.previous_pass_dirty_box = box_face[i],
|
||||
.sample_dirty_box = box_sample,
|
||||
.include_in_committed_dirty_box = true,
|
||||
});
|
||||
m_dirty_box[i] = glm_box(dirty_update.accumulated_dirty_box);
|
||||
box_face[i] = glm_box(dirty_update.pass_dirty_box);
|
||||
m_dirty_box[i] = dirty_update.accumulated_dirty_box;
|
||||
box_face[i] = dirty_update.pass_dirty_box;
|
||||
// TODO: maybe average color?
|
||||
pad_color = f.col;
|
||||
}
|
||||
@@ -891,16 +891,16 @@ void Canvas::stroke_draw()
|
||||
m_tmp_dual[i].unbindFramebuffer();
|
||||
|
||||
// this mode overflows the main brush boundries
|
||||
const auto dirty_update = pp::paint_renderer::plan_canvas_stroke_face_dirty_update(
|
||||
pp::paint_renderer::CanvasStrokeFaceDirtyUpdateRequest {
|
||||
const auto dirty_update = pp::panopainter::plan_legacy_canvas_stroke_face_dirty_update(
|
||||
pp::panopainter::LegacyCanvasStrokeFaceDirtyRequest {
|
||||
.extent = stroke_extent,
|
||||
.previous_accumulated_dirty_box = canvas_stroke_box(m_dirty_box[i]),
|
||||
.previous_pass_dirty_box = canvas_stroke_box(box_sample),
|
||||
.sample_dirty_box = canvas_stroke_box(box_sample),
|
||||
.previous_accumulated_dirty_box = m_dirty_box[i],
|
||||
.previous_pass_dirty_box = box_sample,
|
||||
.sample_dirty_box = box_sample,
|
||||
.include_in_committed_dirty_box =
|
||||
stroke_material.composite_pass.dual_blend_mode == 0,
|
||||
});
|
||||
m_dirty_box[i] = glm_box(dirty_update.accumulated_dirty_box);
|
||||
m_dirty_box[i] = dirty_update.accumulated_dirty_box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "paint_renderer/compositor.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
@@ -45,6 +46,12 @@ struct LegacyCanvasStrokeCommitResult {
|
||||
int committed_faces = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline std::size_t legacy_canvas_stroke_commit_step_count(
|
||||
const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence) noexcept
|
||||
{
|
||||
return std::min(sequence.step_count, sequence.steps.size());
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool legacy_canvas_stroke_commit_callbacks_ready(
|
||||
const LegacyCanvasStrokeCommitCallbacks& callbacks) noexcept
|
||||
{
|
||||
@@ -85,7 +92,8 @@ struct LegacyCanvasStrokeCommitResult {
|
||||
|
||||
request.callbacks.bind_layer_framebuffer(face.index);
|
||||
|
||||
for (std::size_t step_index = 0; step_index < request.sequence.step_count; ++step_index) {
|
||||
const auto step_count = legacy_canvas_stroke_commit_step_count(request.sequence);
|
||||
for (std::size_t step_index = 0; step_index < step_count; ++step_index) {
|
||||
switch (request.sequence.steps[step_index]) {
|
||||
case pp::paint_renderer::CanvasStrokeCommitStep::readback_history_region:
|
||||
request.callbacks.capture_history_region(face.index);
|
||||
|
||||
@@ -30,6 +30,60 @@ struct LegacyStrokeSampleExecutionResult {
|
||||
glm::vec4 dirty_bounds {};
|
||||
};
|
||||
|
||||
struct LegacyCanvasStrokeFaceDirtyRequest {
|
||||
pp::renderer::Extent2D extent {};
|
||||
glm::vec4 previous_accumulated_dirty_box {};
|
||||
glm::vec4 previous_pass_dirty_box {};
|
||||
glm::vec4 sample_dirty_box {};
|
||||
bool include_in_committed_dirty_box = true;
|
||||
};
|
||||
|
||||
struct LegacyCanvasStrokeFaceDirtyResult {
|
||||
glm::vec4 accumulated_dirty_box {};
|
||||
glm::vec4 pass_dirty_box {};
|
||||
bool has_dirty_pixels = false;
|
||||
bool committed_dirty = false;
|
||||
bool pass_dirty = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::paint_renderer::CanvasStrokeBox legacy_canvas_stroke_box(glm::vec4 box) noexcept
|
||||
{
|
||||
return pp::paint_renderer::CanvasStrokeBox {
|
||||
.min_x = box.x,
|
||||
.min_y = box.y,
|
||||
.max_x = box.z,
|
||||
.max_y = box.w,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline glm::vec4 legacy_canvas_stroke_glm_box(
|
||||
pp::paint_renderer::CanvasStrokeBox box) noexcept
|
||||
{
|
||||
return glm::vec4(box.min_x, box.min_y, box.max_x, box.max_y);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline LegacyCanvasStrokeFaceDirtyResult plan_legacy_canvas_stroke_face_dirty_update(
|
||||
const LegacyCanvasStrokeFaceDirtyRequest& request) noexcept
|
||||
{
|
||||
const auto plan = pp::paint_renderer::plan_canvas_stroke_face_dirty_update(
|
||||
pp::paint_renderer::CanvasStrokeFaceDirtyUpdateRequest {
|
||||
.extent = request.extent,
|
||||
.previous_accumulated_dirty_box =
|
||||
legacy_canvas_stroke_box(request.previous_accumulated_dirty_box),
|
||||
.previous_pass_dirty_box = legacy_canvas_stroke_box(request.previous_pass_dirty_box),
|
||||
.sample_dirty_box = legacy_canvas_stroke_box(request.sample_dirty_box),
|
||||
.include_in_committed_dirty_box = request.include_in_committed_dirty_box,
|
||||
});
|
||||
|
||||
return LegacyCanvasStrokeFaceDirtyResult {
|
||||
.accumulated_dirty_box = legacy_canvas_stroke_glm_box(plan.accumulated_dirty_box),
|
||||
.pass_dirty_box = legacy_canvas_stroke_glm_box(plan.pass_dirty_box),
|
||||
.has_dirty_pixels = plan.has_dirty_pixels,
|
||||
.committed_dirty = plan.committed_dirty,
|
||||
.pass_dirty = plan.pass_dirty,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline LegacyStrokeSampleExecutionResult execute_legacy_canvas_stroke_sample(
|
||||
const LegacyStrokeSampleExecutionRequest& request)
|
||||
{
|
||||
|
||||
46
src/legacy_node_stroke_preview_execution_services.h
Normal file
46
src/legacy_node_stroke_preview_execution_services.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "paint_renderer/compositor.h"
|
||||
#include "renderer_api/renderer_api.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
namespace pp::panopainter {
|
||||
|
||||
[[nodiscard]] inline pp::paint_renderer::CanvasStrokeFeedbackPlan plan_legacy_node_stroke_preview_feedback(
|
||||
pp::renderer::RenderDeviceFeatures features,
|
||||
int width,
|
||||
int height) noexcept
|
||||
{
|
||||
const auto plan = pp::paint_renderer::plan_canvas_stroke_feedback(
|
||||
features,
|
||||
pp::renderer::Extent2D {
|
||||
.width = static_cast<std::uint32_t>(std::max(width, 0)),
|
||||
.height = static_cast<std::uint32_t>(std::max(height, 0)),
|
||||
});
|
||||
if (plan) {
|
||||
return plan.value();
|
||||
}
|
||||
|
||||
pp::paint_renderer::CanvasStrokeFeedbackPlan fallback;
|
||||
fallback.compatibility_fallback = true;
|
||||
fallback.path = pp::paint_renderer::StrokeCompositePath::ping_pong_textures;
|
||||
fallback.requires_auxiliary_texture = true;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::paint_renderer::StrokePreviewCompositePlan plan_legacy_node_stroke_preview_composite(
|
||||
bool uses_mixer,
|
||||
bool uses_dual,
|
||||
bool uses_pattern) noexcept
|
||||
{
|
||||
return pp::paint_renderer::plan_stroke_preview_composite(
|
||||
pp::paint_renderer::StrokePreviewCompositeRequest {
|
||||
.uses_mixer = uses_mixer,
|
||||
.uses_dual = uses_dual,
|
||||
.uses_pattern = uses_pattern,
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace pp::panopainter
|
||||
@@ -12,11 +12,11 @@
|
||||
#include "legacy_canvas_stroke_preview_services.h"
|
||||
#include "legacy_canvas_stroke_shader_services.h"
|
||||
#include "legacy_canvas_stroke_services.h"
|
||||
#include "legacy_node_stroke_preview_execution_services.h"
|
||||
#include "legacy_ui_gl_dispatch.h"
|
||||
#include "paint_renderer/compositor.h"
|
||||
#include "renderer_gl/opengl_capabilities.h"
|
||||
#include "util.h"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
@@ -31,21 +31,10 @@ pp::paint_renderer::CanvasStrokeFeedbackPlan stroke_preview_destination_feedback
|
||||
int width,
|
||||
int height) noexcept
|
||||
{
|
||||
const auto plan = pp::paint_renderer::plan_canvas_stroke_feedback(
|
||||
return pp::panopainter::plan_legacy_node_stroke_preview_feedback(
|
||||
stroke_preview_render_device_features(),
|
||||
pp::renderer::Extent2D {
|
||||
.width = static_cast<std::uint32_t>(std::max(width, 0)),
|
||||
.height = static_cast<std::uint32_t>(std::max(height, 0)),
|
||||
});
|
||||
if (plan) {
|
||||
return plan.value();
|
||||
}
|
||||
|
||||
pp::paint_renderer::CanvasStrokeFeedbackPlan fallback;
|
||||
fallback.compatibility_fallback = true;
|
||||
fallback.path = pp::paint_renderer::StrokeCompositePath::ping_pong_textures;
|
||||
fallback.requires_auxiliary_texture = true;
|
||||
return fallback;
|
||||
width,
|
||||
height);
|
||||
}
|
||||
|
||||
pp::paint_renderer::CanvasStrokeMaterialPlan stroke_preview_material_plan(
|
||||
@@ -452,12 +441,10 @@ void NodeStrokePreview::draw_stroke_immediate()
|
||||
const auto stroke_feedback = stroke_preview_destination_feedback_plan(m_rtt.getWidth(), m_rtt.getHeight());
|
||||
const bool copy_stroke_destination = !stroke_feedback.reads_destination_color;
|
||||
const auto material = stroke_preview_material_plan(*b, copy_stroke_destination);
|
||||
const auto preview_composite_plan = pp::paint_renderer::plan_stroke_preview_composite(
|
||||
pp::paint_renderer::StrokePreviewCompositeRequest {
|
||||
.uses_mixer = b->m_tip_mix > 0.0f,
|
||||
.uses_dual = material.composite_pass.use_dual,
|
||||
.uses_pattern = material.composite_pass.use_pattern,
|
||||
});
|
||||
const auto preview_composite_plan = pp::panopainter::plan_legacy_node_stroke_preview_composite(
|
||||
b->m_tip_mix > 0.0f,
|
||||
material.composite_pass.use_dual,
|
||||
material.composite_pass.use_pattern);
|
||||
pp::panopainter::setup_legacy_stroke_shader(
|
||||
pp::panopainter::LegacyStrokeShaderSetupUniforms {
|
||||
.resolution = size,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "assets/image_pixels.h"
|
||||
#include "legacy_canvas_stroke_commit_services.h"
|
||||
#include "paint_renderer/compositor.h"
|
||||
#include "renderer_api/recording_renderer.h"
|
||||
#include "test_harness.h"
|
||||
@@ -1776,6 +1777,61 @@ void plans_canvas_stroke_commit_composite_sequence(pp::tests::Harness& h)
|
||||
PP_EXPECT(h, has_commit_texture_binding(plan, CanvasStrokeCommitTextureRole::pattern, 4));
|
||||
}
|
||||
|
||||
void retained_stroke_commit_runner_clamps_malformed_step_count(pp::tests::Harness& h)
|
||||
{
|
||||
pp::paint_renderer::CanvasStrokeCommitSequencePlan sequence;
|
||||
sequence.step_count = 99U;
|
||||
sequence.steps[0] = CanvasStrokeCommitStep::bind_commit_inputs;
|
||||
sequence.steps[1] = CanvasStrokeCommitStep::composite_draw;
|
||||
|
||||
int bind_inputs = 0;
|
||||
int paint_draws = 0;
|
||||
int erase_draws = 0;
|
||||
int started = 0;
|
||||
int restored = 0;
|
||||
int published = 0;
|
||||
int timelapse = 0;
|
||||
|
||||
const auto result = pp::panopainter::execute_legacy_canvas_stroke_commit_sequence(
|
||||
pp::panopainter::LegacyCanvasStrokeCommitRequest {
|
||||
.context = "test",
|
||||
.faces = {
|
||||
pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 0, .dirty = true },
|
||||
pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 1, .dirty = false },
|
||||
pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 2, .dirty = true },
|
||||
},
|
||||
.sequence = sequence,
|
||||
.callbacks = {
|
||||
.mark_commit_started = [&]() { ++started; },
|
||||
.capture_render_state = []() {},
|
||||
.prepare_render_state = []() {},
|
||||
.restore_render_state = [&]() { ++restored; },
|
||||
.publish_history = [&]() { ++published; },
|
||||
.capture_timelapse_frame = [&]() { ++timelapse; },
|
||||
.bind_layer_framebuffer = [](int) {},
|
||||
.capture_history_region = [](int) {},
|
||||
.apply_layer_dirty_region = [](int) {},
|
||||
.copy_layer_to_commit_destination = [](int) {},
|
||||
.bind_commit_inputs = [&](int) { ++bind_inputs; },
|
||||
.execute_erase_composite = [&](int) { ++erase_draws; },
|
||||
.execute_paint_composite = [&](int) { ++paint_draws; },
|
||||
.copy_committed_to_dilate_source = [](int) {},
|
||||
.execute_commit_dilate = [](int) {},
|
||||
.unbind_layer_framebuffer = [](int) {},
|
||||
},
|
||||
});
|
||||
|
||||
PP_EXPECT(h, result.ok);
|
||||
PP_EXPECT(h, result.committed_faces == 2);
|
||||
PP_EXPECT(h, bind_inputs == 2);
|
||||
PP_EXPECT(h, paint_draws == 2);
|
||||
PP_EXPECT(h, erase_draws == 0);
|
||||
PP_EXPECT(h, started == 1);
|
||||
PP_EXPECT(h, restored == 1);
|
||||
PP_EXPECT(h, published == 1);
|
||||
PP_EXPECT(h, timelapse == 1);
|
||||
}
|
||||
|
||||
void plans_stroke_preview_composite_for_simple_brush(pp::tests::Harness& h)
|
||||
{
|
||||
const auto plan = plan_stroke_preview_composite(StrokePreviewCompositeRequest {});
|
||||
@@ -1841,6 +1897,27 @@ void plans_stroke_preview_composite_with_pattern_input(pp::tests::Harness& h)
|
||||
PP_EXPECT(h, has_preview_texture_slot(plan, StrokePreviewTextureRole::pattern, 4));
|
||||
}
|
||||
|
||||
void plans_stroke_preview_composite_with_all_retained_inputs(pp::tests::Harness& h)
|
||||
{
|
||||
const auto plan = plan_stroke_preview_composite(
|
||||
StrokePreviewCompositeRequest {
|
||||
.uses_mixer = true,
|
||||
.uses_dual = true,
|
||||
.uses_pattern = true,
|
||||
});
|
||||
|
||||
expect_preview_sequence(h, plan);
|
||||
PP_EXPECT(h, plan.uses_mixer);
|
||||
PP_EXPECT(h, plan.uses_dual);
|
||||
PP_EXPECT(h, plan.uses_pattern);
|
||||
PP_EXPECT(h, plan.texture_slot_count == 5U);
|
||||
PP_EXPECT(h, has_preview_texture_slot(plan, StrokePreviewTextureRole::background, 0));
|
||||
PP_EXPECT(h, has_preview_texture_slot(plan, StrokePreviewTextureRole::stroke, 1));
|
||||
PP_EXPECT(h, has_preview_texture_slot(plan, StrokePreviewTextureRole::dual, 3));
|
||||
PP_EXPECT(h, has_preview_texture_slot(plan, StrokePreviewTextureRole::pattern, 4));
|
||||
PP_EXPECT(h, has_preview_texture_slot(plan, StrokePreviewTextureRole::mixer, 3));
|
||||
}
|
||||
|
||||
void plans_canvas_blend_gate_from_persisted_indices(pp::tests::Harness& h)
|
||||
{
|
||||
const std::vector<int> normal_layers { 0, 0, 0 };
|
||||
@@ -2264,10 +2341,16 @@ int main()
|
||||
harness.run("plans_canvas_stroke_dual_material_intent", plans_canvas_stroke_dual_material_intent);
|
||||
harness.run("plans_canvas_stroke_commit_erase_sequence", plans_canvas_stroke_commit_erase_sequence);
|
||||
harness.run("plans_canvas_stroke_commit_composite_sequence", plans_canvas_stroke_commit_composite_sequence);
|
||||
harness.run(
|
||||
"retained_stroke_commit_runner_clamps_malformed_step_count",
|
||||
retained_stroke_commit_runner_clamps_malformed_step_count);
|
||||
harness.run("plans_stroke_preview_composite_for_simple_brush", plans_stroke_preview_composite_for_simple_brush);
|
||||
harness.run("plans_stroke_preview_composite_with_mixer_input", plans_stroke_preview_composite_with_mixer_input);
|
||||
harness.run("plans_stroke_preview_composite_with_dual_input", plans_stroke_preview_composite_with_dual_input);
|
||||
harness.run("plans_stroke_preview_composite_with_pattern_input", plans_stroke_preview_composite_with_pattern_input);
|
||||
harness.run(
|
||||
"plans_stroke_preview_composite_with_all_retained_inputs",
|
||||
plans_stroke_preview_composite_with_all_retained_inputs);
|
||||
harness.run("plans_canvas_blend_gate_from_persisted_indices", plans_canvas_blend_gate_from_persisted_indices);
|
||||
harness.run("canvas_blend_gate_preserves_legacy_fallbacks", canvas_blend_gate_preserves_legacy_fallbacks);
|
||||
harness.run("plans_canvas_stroke_feedback_paths", plans_canvas_stroke_feedback_paths);
|
||||
|
||||
Reference in New Issue
Block a user