Add paint feedback strategy planner

This commit is contained in:
2026-06-03 17:58:24 +02:00
parent dc23a5648d
commit 94a6877e7c
8 changed files with 360 additions and 3 deletions

View File

@@ -36,8 +36,8 @@ and validation command.
| ABR import | `ABR`, `Brush` | `pp_assets`, `pp_paint` | Sample ABR and malformed ABR |
| PPBR import/export | brush panel/dialog | `pp_assets`, `pp_panopainter_ui` | Round-trip fixture |
| Stroke sampling | `Stroke`, `Canvas` | `pp_paint` | Property tests for spacing, pressure, jitter |
| Dual brush/pattern behavior | `Brush`, shaders | `pp_paint`, `pp_paint_renderer` | Stroke-alpha CPU reference and GPU golden |
| Blend modes | GLSL include files, layer rendering | `pp_paint`, `pp_paint_renderer` | Final RGBA and stroke-alpha CPU reference vectors plus GPU parity |
| Dual brush/pattern behavior | `Brush`, shaders | `pp_paint`, `pp_paint_renderer` | Stroke-alpha CPU reference, GPU golden, and paint feedback path planning |
| Blend modes | GLSL include files, layer rendering | `pp_paint`, `pp_paint_renderer` | Final RGBA and stroke-alpha CPU reference vectors, framebuffer-fetch/ping-pong planning, and GPU parity |
| Erase/flood fill/masks | `Canvas`, modes, shaders | `pp_document`, `pp_paint_renderer` | Edge masks, alpha lock, dirty rects |
## Layers And Animation

View File

@@ -53,6 +53,7 @@ agent or engineer to remove them without reconstructing context from chat.
| DEBT-0033 | Open | Modernization | Tools menu planning and direct command execution dispatch now consume pure `pp_app_core` through `App::init_menu_tools`, `pano_cli plan-tools-menu`, `pano_cli plan-tools-panel`, and the `ToolsMenuServices` boundary, but live adapters still construct legacy `NodePanelFloating` panels, mutate legacy panel nodes, clear `CanvasModeGrid`, reset `NodeCanvas` camera state, open legacy shortcuts UI, and call the iOS SonarPen bridge directly | Preserve current Tools menu behavior while UI shell actions move toward app/UI/platform services | `pp_app_core_tools_menu_tests`; `pano_cli plan-tools-menu --command shortcuts`; `pano_cli plan-tools-panel --panel layers`; `pano_cli plan-tools-panel --panel animation --already-visible`; `ctest --preset desktop-fast --build-config Debug` | Tools panel creation, submenu routing, grid clear, camera reset, shortcuts dialog, and SonarPen dispatch are owned by injected app/UI/platform services with `App::init_menu_tools` acting only as a UI adapter and no legacy Tools adapter |
| DEBT-0034 | Open | Modernization | About menu command planning and execution dispatch now consume pure `pp_app_core` through `App::init_menu_about`, `pano_cli plan-about-menu`, and the `AboutMenuServices` boundary, but the live adapter still opens legacy About/manual/what's-new dialogs, invokes the injected crash hook, and runs the legacy Canvas stroke performance test directly | Preserve About menu behavior while dialogs and diagnostics move toward app/UI/platform services | `pp_app_core_about_menu_tests`; `pano_cli plan-about-menu --command news --version-major 2 --version-minor 5 --version-fix 7`; `pano_cli plan-about-menu --command performance --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | About/manual/what's-new dialog dispatch, crash-test dispatch, and performance-test execution are owned by injected app/UI/platform services with `App::init_menu_about` acting only as a UI adapter and no legacy About adapter |
| DEBT-0035 | Open | Modernization | Main toolbar/status command planning and execution dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, `pano_cli plan-main-toolbar`, and the `MainToolbarServices` boundary, and history/canvas commands now hand off through `HistoryUiServices` and `DocumentCanvasClearServices`, but the live adapter still opens legacy open/save/settings/message-box dialogs and delegates to legacy history/canvas adapters | Preserve reachable toolbar/status behavior while app shell commands move toward app/document/UI services | `pp_app_core_main_toolbar_tests`; `pano_cli plan-main-toolbar --command undo --undo-count 2`; `pano_cli plan-main-toolbar --command clear-canvas --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Open/save/settings/message-box routing, undo/redo/clear-history execution, and canvas-clear execution are owned by injected app/document/UI services with `App::init_toolbar_main` acting only as a UI adapter and no legacy toolbar adapter |
| DEBT-0036 | Open | Modernization | `pp_renderer_api` and `pano_cli plan-paint-feedback` can choose a backend-neutral complex paint feedback strategy for framebuffer-fetch-capable renderers or ping-pong render targets, but live stroke compositing still uses the legacy OpenGL canvas path | Preserve current painting behavior while the renderer boundary matures for OpenGL parity and later Vulkan/Metal experiments | `pp_renderer_api_tests`; `pano_cli plan-paint-feedback --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-paint-feedback --texture-copy`; `ctest --preset desktop-fast --build-config Debug` | Stroke/layer compositing chooses its feedback path through renderer services, with OpenGL golden parity and Vulkan/Metal lab tests covering framebuffer-fetch and ping-pong behavior |
## Closed Debt

View File

@@ -862,6 +862,12 @@ nested passes, texture I/O or blits inside a pass, and unclosed passes. It
also validates executable command dependencies, including shader-before-uniform
and shader-plus-mesh before draw within each render pass, and rejects invalid
texture/sampler bind slots in malformed recorded streams.
The renderer-neutral API now also plans complex paint feedback strategies for
future stroke/layer compositing work: framebuffer-fetch-capable backends can
read destination color directly, while other backends must use ping-pong render
targets backed by texture copy or render-target blit support. This is exposed
through `pano_cli plan-paint-feedback` and tracked by DEBT-0036 until the live
paint renderer consumes the plan.
The existing renderer classes are not yet fully
behind the renderer interfaces.
@@ -1107,7 +1113,10 @@ Results:
render-pass clear/scissor/depth/blend/shader-uniform/texture/sampler-bind/
upload/texture-copy/readback/frame-capture/blit command capture, draw
mesh-input capture, explicit draw-range capture, and invalid catalog
rejection.
rejection. The same suite now covers complex paint feedback planning for
framebuffer-fetch backends, ping-pong texture-copy/blit fallbacks, simple
no-feedback blends, invalid render-target usage, unsupported backends, and
depth-target rejection.
- `pp_paint_renderer_compositor_tests` passed.
- `pp_ui_core_color_tests` passed.
- `pp_ui_core_layout_value_tests` passed.

View File

@@ -823,6 +823,65 @@ pp::foundation::Status validate_blit_descs(TextureDesc source, TextureDesc desti
return pp::foundation::Status::success();
}
pp::foundation::Result<PaintFeedbackPlan> plan_paint_feedback(
RenderDeviceFeatures features,
TextureDesc target_desc,
bool complex_blend) noexcept
{
const auto target_status = validate_texture_desc(target_desc);
if (!target_status.ok()) {
return pp::foundation::Result<PaintFeedbackPlan>::failure(target_status);
}
if (!has_texture_usage(target_desc.usage, TextureUsage::render_target)) {
return pp::foundation::Result<PaintFeedbackPlan>::failure(
pp::foundation::Status::invalid_argument("paint feedback target must allow render_target usage"));
}
if (target_desc.format == TextureFormat::depth24_stencil8) {
return pp::foundation::Result<PaintFeedbackPlan>::failure(
pp::foundation::Status::invalid_argument("paint feedback target must be a color texture"));
}
const auto target_bytes = texture_byte_size(target_desc);
if (!target_bytes.ok()) {
return pp::foundation::Result<PaintFeedbackPlan>::failure(target_bytes.status());
}
PaintFeedbackPlan plan;
plan.target_desc = target_desc;
plan.target_bytes = target_bytes.value();
plan.complex_blend = complex_blend;
if (!complex_blend) {
return pp::foundation::Result<PaintFeedbackPlan>::success(plan);
}
plan.reads_destination_color = true;
if (features.framebuffer_fetch) {
plan.path = PaintFeedbackPath::framebuffer_fetch;
plan.requires_explicit_transition = features.explicit_texture_transitions;
return pp::foundation::Result<PaintFeedbackPlan>::success(plan);
}
const bool can_ping_pong = has_texture_usage(target_desc.usage, TextureUsage::sampled)
&& has_texture_usage(target_desc.usage, TextureUsage::copy_source)
&& has_texture_usage(target_desc.usage, TextureUsage::copy_destination)
&& (features.texture_copy || features.render_target_blit);
if (!can_ping_pong) {
return pp::foundation::Result<PaintFeedbackPlan>::failure(
pp::foundation::Status::invalid_argument(
"complex paint feedback requires framebuffer fetch or sampled copy-capable render targets"));
}
plan.path = PaintFeedbackPath::ping_pong_textures;
plan.requires_auxiliary_texture = true;
plan.requires_texture_copy = features.texture_copy;
plan.requires_render_target_blit = !features.texture_copy && features.render_target_blit;
plan.requires_explicit_transition = features.explicit_texture_transitions;
plan.auxiliary_desc = target_desc;
return pp::foundation::Result<PaintFeedbackPlan>::success(plan);
}
const char* texture_format_name(TextureFormat format) noexcept
{
switch (format) {
@@ -887,6 +946,20 @@ const char* blit_filter_name(BlitFilter filter) noexcept
return "unknown";
}
const char* paint_feedback_path_name(PaintFeedbackPath path) noexcept
{
switch (path) {
case PaintFeedbackPath::none:
return "none";
case PaintFeedbackPath::framebuffer_fetch:
return "framebuffer_fetch";
case PaintFeedbackPath::ping_pong_textures:
return "ping_pong_textures";
}
return "unknown";
}
const char* blend_factor_name(BlendFactor factor) noexcept
{
switch (factor) {

View File

@@ -132,6 +132,12 @@ enum class BlitFilter : std::uint8_t {
linear,
};
enum class PaintFeedbackPath : std::uint8_t {
none,
framebuffer_fetch,
ping_pong_textures,
};
enum class BlendFactor : std::uint8_t {
zero,
one,
@@ -236,6 +242,19 @@ struct RenderDeviceFeatures {
bool float32_render_targets = false;
};
struct PaintFeedbackPlan {
PaintFeedbackPath path = PaintFeedbackPath::none;
TextureDesc target_desc {};
TextureDesc auxiliary_desc {};
std::uint64_t target_bytes = 0;
bool complex_blend = false;
bool reads_destination_color = false;
bool requires_auxiliary_texture = false;
bool requires_texture_copy = false;
bool requires_render_target_blit = false;
bool requires_explicit_transition = false;
};
class ITexture2D {
public:
virtual ~ITexture2D() = default;
@@ -393,10 +412,15 @@ public:
[[nodiscard]] pp::foundation::Status validate_blit_descs(
TextureDesc source,
TextureDesc destination) noexcept;
[[nodiscard]] pp::foundation::Result<PaintFeedbackPlan> plan_paint_feedback(
RenderDeviceFeatures features,
TextureDesc target_desc,
bool complex_blend) noexcept;
[[nodiscard]] const char* texture_format_name(TextureFormat format) noexcept;
[[nodiscard]] const char* texture_state_name(TextureState state) noexcept;
[[nodiscard]] const char* primitive_topology_name(PrimitiveTopology topology) noexcept;
[[nodiscard]] const char* blit_filter_name(BlitFilter filter) noexcept;
[[nodiscard]] const char* paint_feedback_path_name(PaintFeedbackPath path) noexcept;
[[nodiscard]] const char* blend_factor_name(BlendFactor factor) noexcept;
[[nodiscard]] const char* blend_op_name(BlendOp op) noexcept;
[[nodiscard]] const char* compare_op_name(CompareOp op) noexcept;

View File

@@ -1192,6 +1192,36 @@ if(TARGET pano_cli)
LABELS "app;paint;integration;desktop-fast;fuzz"
WILL_FAIL TRUE)
add_test(NAME pano_cli_plan_paint_feedback_framebuffer_fetch_smoke
COMMAND pano_cli plan-paint-feedback --framebuffer-fetch --explicit-transitions --render-only)
set_tests_properties(pano_cli_plan_paint_feedback_framebuffer_fetch_smoke PROPERTIES
LABELS "renderer;paint;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-paint-feedback\".*\"path\":\"framebuffer_fetch\".*\"readsDestinationColor\":true.*\"requiresAuxiliaryTexture\":false.*\"requiresExplicitTransition\":true")
add_test(NAME pano_cli_plan_paint_feedback_ping_pong_copy_smoke
COMMAND pano_cli plan-paint-feedback --texture-copy)
set_tests_properties(pano_cli_plan_paint_feedback_ping_pong_copy_smoke PROPERTIES
LABELS "renderer;paint;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-paint-feedback\".*\"path\":\"ping_pong_textures\".*\"requiresAuxiliaryTexture\":true.*\"requiresTextureCopy\":true.*\"requiresRenderTargetBlit\":false")
add_test(NAME pano_cli_plan_paint_feedback_simple_smoke
COMMAND pano_cli plan-paint-feedback --simple --render-only)
set_tests_properties(pano_cli_plan_paint_feedback_simple_smoke PROPERTIES
LABELS "renderer;paint;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-paint-feedback\".*\"path\":\"none\".*\"complexBlend\":false.*\"readsDestinationColor\":false")
add_test(NAME pano_cli_plan_paint_feedback_rejects_unsupported
COMMAND pano_cli plan-paint-feedback)
set_tests_properties(pano_cli_plan_paint_feedback_rejects_unsupported PROPERTIES
LABELS "renderer;paint;integration;desktop-fast;fuzz"
WILL_FAIL TRUE)
add_test(NAME pano_cli_plan_paint_feedback_rejects_depth
COMMAND pano_cli plan-paint-feedback --texture-copy --depth)
set_tests_properties(pano_cli_plan_paint_feedback_rejects_depth PROPERTIES
LABELS "renderer;paint;integration;desktop-fast;fuzz"
WILL_FAIL TRUE)
add_test(NAME pano_cli_plan_canvas_tool_draw_smoke
COMMAND pano_cli plan-canvas-tool --kind draw)
set_tests_properties(pano_cli_plan_canvas_tool_draw_smoke PROPERTIES

View File

@@ -34,6 +34,7 @@ using pp::renderer::IRenderTarget;
using pp::renderer::IRenderTrace;
using pp::renderer::IShaderProgram;
using pp::renderer::MeshDesc;
using pp::renderer::PaintFeedbackPath;
using pp::renderer::PrimitiveTopology;
using pp::renderer::ReadbackRegion;
using pp::renderer::RecordedRenderCommandKind;
@@ -66,6 +67,8 @@ using pp::renderer::max_texture_dimension;
using pp::renderer::max_texture_slots;
using pp::renderer::max_trace_label_bytes;
using pp::renderer::panopainter_shader_catalog;
using pp::renderer::paint_feedback_path_name;
using pp::renderer::plan_paint_feedback;
using pp::renderer::primitive_topology_name;
using pp::renderer::readback_byte_size;
using pp::renderer::recorded_render_command_kind_name;
@@ -1210,6 +1213,94 @@ void validates_blit_contract(pp::tests::Harness& h)
PP_EXPECT(h, blit_filter_name(static_cast<BlitFilter>(255)) == std::string_view("unknown"));
}
void plans_paint_feedback_paths(pp::tests::Harness& h)
{
const TextureDesc render_target {
.extent = Extent2D { .width = 64, .height = 32 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::render_target
| TextureUsage::sampled
| TextureUsage::copy_source
| TextureUsage::copy_destination,
.debug_name = "paint-target",
};
const TextureDesc render_only_target {
.extent = Extent2D { .width = 64, .height = 32 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::render_target,
.debug_name = "paint-render-only",
};
const TextureDesc depth_target {
.extent = Extent2D { .width = 64, .height = 32 },
.format = TextureFormat::depth24_stencil8,
.usage = TextureUsage::render_target
| TextureUsage::sampled
| TextureUsage::copy_source
| TextureUsage::copy_destination,
.debug_name = "paint-depth",
};
const RenderDeviceFeatures framebuffer_fetch_features {
.framebuffer_fetch = true,
.explicit_texture_transitions = true,
};
const RenderDeviceFeatures copy_features {
.texture_copy = true,
};
const RenderDeviceFeatures blit_features {
.render_target_blit = true,
};
const auto simple = plan_paint_feedback(copy_features, render_only_target, false);
PP_EXPECT(h, simple);
if (simple) {
PP_EXPECT(h, simple.value().path == PaintFeedbackPath::none);
PP_EXPECT(h, !simple.value().reads_destination_color);
PP_EXPECT(h, simple.value().target_bytes == 8192U);
}
const auto fetch = plan_paint_feedback(framebuffer_fetch_features, render_only_target, true);
PP_EXPECT(h, fetch);
if (fetch) {
PP_EXPECT(h, fetch.value().path == PaintFeedbackPath::framebuffer_fetch);
PP_EXPECT(h, fetch.value().reads_destination_color);
PP_EXPECT(h, !fetch.value().requires_auxiliary_texture);
PP_EXPECT(h, !fetch.value().requires_texture_copy);
PP_EXPECT(h, fetch.value().requires_explicit_transition);
}
const auto ping_pong_copy = plan_paint_feedback(copy_features, render_target, true);
PP_EXPECT(h, ping_pong_copy);
if (ping_pong_copy) {
PP_EXPECT(h, ping_pong_copy.value().path == PaintFeedbackPath::ping_pong_textures);
PP_EXPECT(h, ping_pong_copy.value().requires_auxiliary_texture);
PP_EXPECT(h, ping_pong_copy.value().requires_texture_copy);
PP_EXPECT(h, !ping_pong_copy.value().requires_render_target_blit);
PP_EXPECT(h, ping_pong_copy.value().auxiliary_desc.extent.width == render_target.extent.width);
}
const auto ping_pong_blit = plan_paint_feedback(blit_features, render_target, true);
PP_EXPECT(h, ping_pong_blit);
if (ping_pong_blit) {
PP_EXPECT(h, ping_pong_blit.value().path == PaintFeedbackPath::ping_pong_textures);
PP_EXPECT(h, !ping_pong_blit.value().requires_texture_copy);
PP_EXPECT(h, ping_pong_blit.value().requires_render_target_blit);
}
const auto unsupported = plan_paint_feedback(RenderDeviceFeatures {}, render_target, true);
const auto missing_usage = plan_paint_feedback(copy_features, render_only_target, true);
const auto depth = plan_paint_feedback(copy_features, depth_target, true);
PP_EXPECT(h, !unsupported.ok());
PP_EXPECT(h, unsupported.status().code == StatusCode::invalid_argument);
PP_EXPECT(h, !missing_usage.ok());
PP_EXPECT(h, missing_usage.status().code == StatusCode::invalid_argument);
PP_EXPECT(h, !depth.ok());
PP_EXPECT(h, depth.status().code == StatusCode::invalid_argument);
PP_EXPECT(h, paint_feedback_path_name(PaintFeedbackPath::framebuffer_fetch) == std::string_view("framebuffer_fetch"));
PP_EXPECT(h, paint_feedback_path_name(PaintFeedbackPath::ping_pong_textures) == std::string_view("ping_pong_textures"));
PP_EXPECT(h, paint_feedback_path_name(static_cast<PaintFeedbackPath>(255)) == std::string_view("unknown"));
}
void validates_texture_copy_contract(pp::tests::Harness& h)
{
const TextureDesc rgba_desc {
@@ -2696,6 +2787,7 @@ int main()
harness.run("computes_readback_byte_sizes", computes_readback_byte_sizes);
harness.run("computes_frame_capture_byte_sizes", computes_frame_capture_byte_sizes);
harness.run("validates_blit_contract", validates_blit_contract);
harness.run("plans_paint_feedback_paths", plans_paint_feedback_paths);
harness.run("validates_texture_copy_contract", validates_texture_copy_contract);
harness.run("validates_blend_contract", validates_blend_contract);
harness.run("validates_depth_contract", validates_depth_contract);

View File

@@ -335,6 +335,18 @@ struct PlanBrushStrokeControlArgs {
int blend_mode = 0;
};
struct PlanPaintFeedbackArgs {
int width = 64;
int height = 32;
bool complex_blend = true;
bool framebuffer_fetch = false;
bool explicit_texture_transitions = false;
bool texture_copy = false;
bool render_target_blit = false;
bool render_only_target = false;
bool depth_target = false;
};
struct PlanGridOperationArgs {
std::string kind = "pick";
std::string path;
@@ -1753,6 +1765,7 @@ void print_help()
<< " plan-brush-operation --kind color|tip|pattern|dual|preset|settings [--path FILE] [--thumb FILE] [--r N] [--g N] [--b N] [--a N] [--no-brush]\n"
<< " plan-brush-texture-list --kind add|remove|move [--dir NAME] [--data-path DIR] [--source FILE] [--item-count N] [--current-index N] [--offset N] [--user-texture]\n"
<< " plan-brush-stroke-control --kind float|bool|blend|tip-aspect-reset|default-reset [--setting NAME] [--value N] [--enabled|--disabled] [--blend-mode N]\n"
<< " plan-paint-feedback [--width N] [--height N] [--simple|--complex] [--framebuffer-fetch] [--texture-copy] [--blit] [--explicit-transitions] [--render-only] [--depth]\n"
<< " plan-canvas-tool --kind draw|erase|line|camera|grid|copy|cut|fill|mask-free|mask-line|bucket|pick|touch-lock [--current-mode-draw]\n"
<< " plan-canvas-tool-state [--mode draw|erase|line|camera|grid|copy|cut|fill|mask-free|mask-line|bucket] [--picking] [--touch-lock]\n"
<< " plan-grid-operation --kind pick|load|reload|clear|render|commit [--path FILE] [--no-heightmap] [--no-canvas] [--float32] [--float16] [--texture-resolution N] [--samples N]\n"
@@ -4809,6 +4822,117 @@ int plan_brush_stroke_control(int argc, char** argv)
return 0;
}
pp::foundation::Status parse_plan_paint_feedback_args(
int argc,
char** argv,
PlanPaintFeedbackArgs& args)
{
for (int i = 2; i < argc; ++i) {
const std::string_view key(argv[i]);
if (key == "--width" || key == "--height") {
if (i + 1 >= argc) {
return pp::foundation::Status::invalid_argument("missing value for option");
}
const auto value = parse_i32_arg(argv[++i]);
if (!value) {
return value.status();
}
if (value.value() <= 0) {
return pp::foundation::Status::invalid_argument("paint feedback extent must be greater than zero");
}
if (key == "--width") {
args.width = value.value();
} else {
args.height = value.value();
}
} else if (key == "--simple") {
args.complex_blend = false;
} else if (key == "--complex") {
args.complex_blend = true;
} else if (key == "--framebuffer-fetch") {
args.framebuffer_fetch = true;
} else if (key == "--explicit-transitions") {
args.explicit_texture_transitions = true;
} else if (key == "--texture-copy") {
args.texture_copy = true;
} else if (key == "--blit") {
args.render_target_blit = true;
} else if (key == "--render-only") {
args.render_only_target = true;
} else if (key == "--depth") {
args.depth_target = true;
} else {
return pp::foundation::Status::invalid_argument("unknown option");
}
}
return pp::foundation::Status::success();
}
int plan_paint_feedback(int argc, char** argv)
{
PlanPaintFeedbackArgs args;
const auto status = parse_plan_paint_feedback_args(argc, argv, args);
if (!status.ok()) {
print_error("plan-paint-feedback", status.message);
return 2;
}
pp::renderer::TextureUsage usage = pp::renderer::TextureUsage::render_target;
if (!args.render_only_target) {
usage |= pp::renderer::TextureUsage::sampled;
usage |= pp::renderer::TextureUsage::copy_source;
usage |= pp::renderer::TextureUsage::copy_destination;
}
const pp::renderer::RenderDeviceFeatures features {
.framebuffer_fetch = args.framebuffer_fetch,
.explicit_texture_transitions = args.explicit_texture_transitions,
.texture_copy = args.texture_copy,
.render_target_blit = args.render_target_blit,
};
const pp::renderer::TextureDesc target {
.extent = pp::renderer::Extent2D {
.width = static_cast<std::uint32_t>(args.width),
.height = static_cast<std::uint32_t>(args.height),
},
.format = args.depth_target
? pp::renderer::TextureFormat::depth24_stencil8
: pp::renderer::TextureFormat::rgba8,
.usage = usage,
.debug_name = "paint-feedback-target",
};
const auto plan = pp::renderer::plan_paint_feedback(features, target, args.complex_blend);
if (!plan) {
print_error("plan-paint-feedback", plan.status().message);
return 2;
}
const auto& value = plan.value();
std::cout << "{\"ok\":true,\"command\":\"plan-paint-feedback\""
<< ",\"state\":{\"width\":" << args.width
<< ",\"height\":" << args.height
<< ",\"complexBlend\":" << json_bool(args.complex_blend)
<< ",\"framebufferFetch\":" << json_bool(args.framebuffer_fetch)
<< ",\"explicitTransitions\":" << json_bool(args.explicit_texture_transitions)
<< ",\"textureCopy\":" << json_bool(args.texture_copy)
<< ",\"blit\":" << json_bool(args.render_target_blit)
<< ",\"renderOnlyTarget\":" << json_bool(args.render_only_target)
<< ",\"depthTarget\":" << json_bool(args.depth_target)
<< "},\"plan\":{\"path\":\"" << pp::renderer::paint_feedback_path_name(value.path)
<< "\",\"targetFormat\":\"" << pp::renderer::texture_format_name(value.target_desc.format)
<< "\",\"targetBytes\":" << value.target_bytes
<< ",\"complexBlend\":" << json_bool(value.complex_blend)
<< ",\"readsDestinationColor\":" << json_bool(value.reads_destination_color)
<< ",\"requiresAuxiliaryTexture\":" << json_bool(value.requires_auxiliary_texture)
<< ",\"requiresTextureCopy\":" << json_bool(value.requires_texture_copy)
<< ",\"requiresRenderTargetBlit\":" << json_bool(value.requires_render_target_blit)
<< ",\"requiresExplicitTransition\":" << json_bool(value.requires_explicit_transition)
<< "}}\n";
return 0;
}
pp::foundation::Status parse_plan_canvas_tool_args(
int argc,
char** argv,
@@ -7939,6 +8063,10 @@ int main(int argc, char** argv)
return plan_brush_stroke_control(argc, argv);
}
if (command == "plan-paint-feedback") {
return plan_paint_feedback(argc, argv);
}
if (command == "plan-canvas-tool") {
return plan_canvas_tool(argc, argv);
}