From b82cc1e4bdad00f2a1d0479093c63d69f1044c26 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Tue, 2 Jun 2026 09:47:09 +0200 Subject: [PATCH] Expose recording renderer through pano cli --- docs/modernization/build-inventory.md | 2 + docs/modernization/roadmap.md | 3 + tests/CMakeLists.txt | 6 ++ tools/pano_cli/CMakeLists.txt | 1 + tools/pano_cli/main.cpp | 126 ++++++++++++++++++++++++++ 5 files changed, 138 insertions(+) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 9363bff..1aee7e6 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -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. diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 0037632..ae8875b 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e3a003f..dec87b8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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 diff --git a/tools/pano_cli/CMakeLists.txt b/tools/pano_cli/CMakeLists.txt index 0d2d00c..c9d56c8 100644 --- a/tools/pano_cli/CMakeLists.txt +++ b/tools/pano_cli/CMakeLists.txt @@ -6,4 +6,5 @@ target_link_libraries(pano_cli PRIVATE pp_foundation pp_assets pp_document + pp_renderer_api pp_ui_core) diff --git a/tools/pano_cli/main.cpp b/tools/pano_cli/main.cpp index 1b20375..baeb7a9 100644 --- a/tools/pano_cli/main.cpp +++ b/tools/pano_cli/main.cpp @@ -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 @@ -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; }