Expose recording renderer through pano cli
This commit is contained in:
@@ -244,6 +244,8 @@ Known local toolchain state:
|
||||
- `pp_renderer_api` exposes a headless `RecordingRenderDevice` that validates
|
||||
command order, records render commands, and records trace markers without a
|
||||
window or GL context.
|
||||
- `pano_cli record-render` exposes the recording renderer through JSON
|
||||
automation and is covered by `pano_cli_record_render_smoke`.
|
||||
- `pp_ui_core` consumes vcpkg tinyxml2 only when `PP_USE_VCPKG_TINYXML2=ON`
|
||||
through the vcpkg preset; default and Android validation still use the
|
||||
retained vendored fallback tracked by DEBT-0012.
|
||||
|
||||
@@ -743,6 +743,9 @@ Results:
|
||||
- `pp_renderer_api` now includes a headless `RecordingRenderDevice` with strict
|
||||
command-order validation and command/trace capture, giving automation a
|
||||
backend-neutral render path that does not require a window or GL context.
|
||||
- `pano_cli record-render` exercises that headless recording renderer and emits
|
||||
JSON command counts, target dimensions, backend name, and trace/draw summary
|
||||
for agent automation.
|
||||
- PowerShell package-smoke wrapper validates the Windows CMake app executable
|
||||
and runtime `data/` copy.
|
||||
- Android arm64 configured with NDK 29.0.14206865 through the platform-build
|
||||
|
||||
@@ -279,6 +279,12 @@ if(TARGET pano_cli)
|
||||
set_tests_properties(pano_cli_parse_layout_smoke PROPERTIES
|
||||
LABELS "ui;integration;desktop-fast")
|
||||
|
||||
add_test(NAME pano_cli_record_render_smoke
|
||||
COMMAND pano_cli record-render --width 32 --height 16)
|
||||
set_tests_properties(pano_cli_record_render_smoke PROPERTIES
|
||||
LABELS "renderer;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"backend\":\"recording\".*\"width\":32.*\"height\":16.*\"commands\":7.*\"drawCommands\":1")
|
||||
|
||||
add_test(NAME pano_cli_simulate_stroke_smoke
|
||||
COMMAND pano_cli simulate-stroke --x1 0 --y1 0 --x2 10 --y2 0 --spacing 2)
|
||||
set_tests_properties(pano_cli_simulate_stroke_smoke PROPERTIES
|
||||
|
||||
@@ -6,4 +6,5 @@ target_link_libraries(pano_cli PRIVATE
|
||||
pp_foundation
|
||||
pp_assets
|
||||
pp_document
|
||||
pp_renderer_api
|
||||
pp_ui_core)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "foundation/result.h"
|
||||
#include "paint/stroke.h"
|
||||
#include "paint/stroke_script.h"
|
||||
#include "renderer_api/recording_renderer.h"
|
||||
#include "ui_core/layout_xml.h"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -52,6 +53,11 @@ struct SimulateStrokeScriptArgs {
|
||||
std::string path;
|
||||
};
|
||||
|
||||
struct RecordRenderArgs {
|
||||
std::uint32_t width = 64;
|
||||
std::uint32_t height = 32;
|
||||
};
|
||||
|
||||
void print_error(std::string_view command, std::string_view message)
|
||||
{
|
||||
std::cout << "{\"ok\":false,\"command\":\"" << command
|
||||
@@ -109,6 +115,7 @@ void print_help()
|
||||
<< " inspect-project --path FILE\n"
|
||||
<< " load-project --path FILE\n"
|
||||
<< " parse-layout --path FILE\n"
|
||||
<< " record-render [--width N] [--height N]\n"
|
||||
<< " simulate-stroke --x1 N --y1 N --x2 N --y2 N [--spacing N]\n"
|
||||
<< " simulate-stroke-script --path FILE\n"
|
||||
<< " --help\n";
|
||||
@@ -624,6 +631,121 @@ int simulate_stroke_script(int argc, char** argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_record_render_args(int argc, char** argv, RecordRenderArgs& 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 = pp::foundation::parse_u32(argv[++i]);
|
||||
if (!value) {
|
||||
return value.status();
|
||||
}
|
||||
|
||||
if (key == "--width") {
|
||||
args.width = value.value();
|
||||
} else {
|
||||
args.height = value.value();
|
||||
}
|
||||
} else {
|
||||
return pp::foundation::Status::invalid_argument("unknown option");
|
||||
}
|
||||
}
|
||||
|
||||
if (args.width == 0 || args.height == 0) {
|
||||
return pp::foundation::Status::invalid_argument("width and height must be greater than zero");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
int record_render(int argc, char** argv)
|
||||
{
|
||||
RecordRenderArgs args;
|
||||
const auto status = parse_record_render_args(argc, argv, args);
|
||||
if (!status.ok()) {
|
||||
print_error("record-render", status.message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
pp::renderer::RecordingRenderDevice device;
|
||||
pp::renderer::RecordingRenderTarget target(pp::renderer::TextureDesc {
|
||||
.extent = pp::renderer::Extent2D { .width = args.width, .height = args.height },
|
||||
.format = pp::renderer::TextureFormat::rgba8,
|
||||
.render_target = true,
|
||||
});
|
||||
pp::renderer::RecordingShaderProgram shader("pano-cli-record-render");
|
||||
pp::renderer::RecordingMesh mesh(pp::renderer::MeshDesc {
|
||||
.vertex_count = 3,
|
||||
.index_count = 0,
|
||||
.topology = pp::renderer::PrimitiveTopology::triangles,
|
||||
});
|
||||
|
||||
device.trace()->marker("renderer", "pano_cli_record_render");
|
||||
auto& context = device.immediate_context();
|
||||
const auto begin_status = context.begin_render_pass(
|
||||
target,
|
||||
pp::renderer::ClearColor { .r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F });
|
||||
if (!begin_status.ok()) {
|
||||
print_error("record-render", begin_status.message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const auto viewport_status = context.set_viewport(
|
||||
pp::renderer::Viewport { .x = 0, .y = 0, .width = args.width, .height = args.height });
|
||||
if (!viewport_status.ok()) {
|
||||
print_error("record-render", viewport_status.message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const auto shader_status = context.bind_shader(shader);
|
||||
const auto mesh_status = context.bind_mesh(mesh);
|
||||
const auto draw_status = context.draw();
|
||||
context.end_render_pass();
|
||||
|
||||
if (!shader_status.ok()) {
|
||||
print_error("record-render", shader_status.message);
|
||||
return 2;
|
||||
}
|
||||
if (!mesh_status.ok()) {
|
||||
print_error("record-render", mesh_status.message);
|
||||
return 2;
|
||||
}
|
||||
if (!draw_status.ok()) {
|
||||
print_error("record-render", draw_status.message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::size_t draw_commands = 0;
|
||||
std::size_t trace_markers = 0;
|
||||
const auto commands = device.commands();
|
||||
for (const auto& command : commands) {
|
||||
if (command.kind == pp::renderer::RecordedRenderCommandKind::draw) {
|
||||
++draw_commands;
|
||||
} else if (command.kind == pp::renderer::RecordedRenderCommandKind::trace_marker) {
|
||||
++trace_markers;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "{\"ok\":true,\"command\":\"record-render\""
|
||||
<< ",\"backend\":\"" << device.backend_name() << "\""
|
||||
<< ",\"target\":{\"width\":" << args.width
|
||||
<< ",\"height\":" << args.height
|
||||
<< ",\"format\":\"rgba8\"}"
|
||||
<< ",\"commands\":" << commands.size()
|
||||
<< ",\"drawCommands\":" << draw_commands
|
||||
<< ",\"traceMarkers\":" << trace_markers
|
||||
<< ",\"first\":\""
|
||||
<< pp::renderer::recorded_render_command_kind_name(commands.front().kind)
|
||||
<< "\",\"last\":\""
|
||||
<< pp::renderer::recorded_render_command_kind_name(commands.back().kind)
|
||||
<< "\"}\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_layout_args(int argc, char** argv, ParseLayoutArgs& args)
|
||||
{
|
||||
for (int i = 2; i < argc; ++i) {
|
||||
@@ -721,6 +843,10 @@ int main(int argc, char** argv)
|
||||
return parse_layout(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "record-render") {
|
||||
return record_render(argc, argv);
|
||||
}
|
||||
|
||||
print_error(command, "unknown command");
|
||||
return 2;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user