Add stroke composite feedback planner

This commit is contained in:
2026-06-03 18:07:08 +02:00
parent 94a6877e7c
commit 2ec11e5099
9 changed files with 531 additions and 3 deletions

View File

@@ -31,6 +31,7 @@
#include "foundation/parse.h"
#include "foundation/result.h"
#include "paint/blend.h"
#include "paint_renderer/compositor.h"
#include "paint/stroke.h"
#include "paint/stroke_script.h"
#include "renderer_api/recording_renderer.h"
@@ -347,6 +348,21 @@ struct PlanPaintFeedbackArgs {
bool depth_target = false;
};
struct PlanStrokeCompositeArgs {
int width = 64;
int height = 32;
int layer_blend_mode = 0;
int stroke_blend_mode = 0;
bool dual_brush_blend = false;
bool pattern_blend = false;
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;
@@ -1766,6 +1782,7 @@ void print_help()
<< " 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-stroke-composite [--width N] [--height N] [--layer-blend N] [--stroke-blend N] [--dual-blend] [--pattern-blend] [--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"
@@ -4933,6 +4950,137 @@ int plan_paint_feedback(int argc, char** argv)
return 0;
}
pp::foundation::Status parse_plan_stroke_composite_args(
int argc,
char** argv,
PlanStrokeCompositeArgs& args)
{
for (int i = 2; i < argc; ++i) {
const std::string_view key(argv[i]);
if (key == "--width" || key == "--height" || key == "--layer-blend" || key == "--stroke-blend") {
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 (key == "--width" || key == "--height") {
if (value.value() <= 0) {
return pp::foundation::Status::invalid_argument("stroke composite extent must be greater than zero");
}
if (key == "--width") {
args.width = value.value();
} else {
args.height = value.value();
}
} else if (key == "--layer-blend") {
args.layer_blend_mode = value.value();
} else {
args.stroke_blend_mode = value.value();
}
} else if (key == "--dual-blend") {
args.dual_brush_blend = true;
} else if (key == "--pattern-blend") {
args.pattern_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");
}
}
if (args.layer_blend_mode < 0 || args.layer_blend_mode > 4) {
return pp::foundation::Status::out_of_range("layer blend mode must be in the range [0, 4]");
}
if (args.stroke_blend_mode < 0 || args.stroke_blend_mode > 10) {
return pp::foundation::Status::out_of_range("stroke blend mode must be in the range [0, 10]");
}
return pp::foundation::Status::success();
}
int plan_stroke_composite(int argc, char** argv)
{
PlanStrokeCompositeArgs args;
const auto status = parse_plan_stroke_composite_args(argc, argv, args);
if (!status.ok()) {
print_error("plan-stroke-composite", 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::paint_renderer::StrokeCompositeRequest request {
.extent = pp::renderer::Extent2D {
.width = static_cast<std::uint32_t>(args.width),
.height = static_cast<std::uint32_t>(args.height),
},
.target_format = args.depth_target
? pp::renderer::TextureFormat::depth24_stencil8
: pp::renderer::TextureFormat::rgba8,
.target_usage = usage,
.layer_blend_mode = static_cast<pp::paint::BlendMode>(args.layer_blend_mode),
.stroke_blend_mode = static_cast<pp::paint::StrokeBlendMode>(args.stroke_blend_mode),
.dual_brush_blend = args.dual_brush_blend,
.pattern_blend = args.pattern_blend,
};
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 auto plan = pp::paint_renderer::plan_stroke_composite(features, request);
if (!plan) {
print_error("plan-stroke-composite", plan.status().message);
return 2;
}
const auto& value = plan.value();
std::cout << "{\"ok\":true,\"command\":\"plan-stroke-composite\""
<< ",\"state\":{\"width\":" << args.width
<< ",\"height\":" << args.height
<< ",\"layerBlend\":\"" << pp::paint::blend_mode_name(request.layer_blend_mode)
<< "\",\"strokeBlend\":\"" << pp::paint::stroke_blend_mode_name(request.stroke_blend_mode)
<< "\",\"dualBrushBlend\":" << json_bool(args.dual_brush_blend)
<< ",\"patternBlend\":" << json_bool(args.pattern_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::paint_renderer::stroke_composite_path_name(value.path)
<< "\",\"feedbackPath\":\"" << pp::renderer::paint_feedback_path_name(value.feedback.path)
<< "\",\"targetBytes\":" << value.target_bytes
<< ",\"auxiliaryBytes\":" << value.auxiliary_bytes
<< ",\"estimatedWorkingBytes\":" << value.estimated_working_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,
@@ -8067,6 +8215,10 @@ int main(int argc, char** argv)
return plan_paint_feedback(argc, argv);
}
if (command == "plan-stroke-composite") {
return plan_stroke_composite(argc, argv);
}
if (command == "plan-canvas-tool") {
return plan_canvas_tool(argc, argv);
}