Add paint feedback strategy planner
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user