From 23c308db1bc181a1d6768742266f29eb64f95aa0 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Tue, 2 Jun 2026 16:09:52 +0200 Subject: [PATCH] Add renderer resource factory contract --- docs/modernization/build-inventory.md | 18 +- docs/modernization/roadmap.md | 22 ++- src/foundation/result.h | 11 +- src/renderer_api/recording_renderer.cpp | 82 +++++++++ src/renderer_api/recording_renderer.h | 10 + src/renderer_api/renderer_api.h | 11 ++ tests/CMakeLists.txt | 2 +- tests/renderer_api/renderer_api_tests.cpp | 211 ++++++++++++++++++++-- tools/pano_cli/main.cpp | 70 +++++-- 9 files changed, 389 insertions(+), 48 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 2892be4..21ea273 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -285,16 +285,18 @@ Known local toolchain state: source code reintroduces raw `GL_*`/`WGL_*` constants outside the allowed legacy OpenGL implementation files. - `pp_renderer_api` exposes a headless `RecordingRenderDevice` that validates - command order, scissor state, depth state, blend state, texture-slot binding, - sampler-state binding, texture-upload byte counts, readback bounds, - frame-capture sources, destination buffer sizes, and render-target blit - regions, records render/scissor/depth/blend/texture-bind/sampler-bind/upload/ - readback/frame-capture/blit commands, draw mesh inputs, and records trace - markers without a window or GL context. + backend-owned resource creation, command order, scissor state, depth state, + blend state, texture-slot binding, sampler-state binding, texture-upload byte + counts, readback bounds, frame-capture sources, destination buffer sizes, and + render-target blit regions, records + render/scissor/depth/blend/texture-bind/sampler-bind/upload/readback/ + frame-capture/blit commands, draw mesh inputs, and records trace markers + without a window or GL context. - `pano_cli record-render` exposes the recording renderer through JSON automation, including scissor/depth/blend/texture-bind/sampler-bind/upload/ - readback/frame-capture/blit command and byte totals plus draw vertex/index - totals, and is covered by `pano_cli_record_render_smoke` plus + readback/frame-capture/blit command and byte totals, backend resource + creation counts, plus draw vertex/index totals, and is covered by + `pano_cli_record_render_smoke` plus `pano_cli_record_render_rejects_oversized_target`. - `pano_cli simulate-document-history` exposes `pp_document::DocumentHistory` apply/undo/redo state through JSON automation and is covered by diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index eedf591..423e073 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -723,7 +723,8 @@ Results: PanoPainter shader catalog validation, readback byte-size and command-order validation, texture-upload byte-count validation, frame-capture byte-size and command-order validation, render-target blit validation, texture-slot binding - validation, blend-state validation, scissor-state validation, recording + validation, blend-state validation, scissor-state validation, + backend-neutral resource factory validation, recording scissor/depth/blend/texture/sampler-bind/upload/readback/frame-capture/blit command capture, draw mesh-input capture, and invalid catalog rejection. - `pp_paint_renderer_compositor_tests` passed. @@ -819,17 +820,20 @@ Results: reintroduces raw `GL_*`/`WGL_*` constants outside the allowed legacy OpenGL implementation files. - `pp_renderer_api` now includes a headless `RecordingRenderDevice` with strict + renderer-owned resource factory and command-order/scissor-state/depth-state/blend-state/texture-bind/ sampler-bind/texture-upload/readback/frame-capture/blit validation; it - records commands, trace markers, scissor state, depth state, blend state, - texture/sampler binds, draw mesh inputs, uploads/readbacks, frame captures, - and render-target blits, giving automation a backend-neutral render path that - does not require a window or GL context. + creates validated textures, render targets, shaders, meshes, and readback + buffers, then records commands, trace markers, scissor state, depth state, + blend state, texture/sampler binds, draw mesh inputs, uploads/readbacks, + frame captures, and render-target blits, 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, trace/draw summary, and - draw vertex/index totals, scissor/depth/blend-state plus texture/sampler-bind/ - upload/readback/frame-capture/blit command/byte totals for agent automation, - with an expected-failure smoke for oversized render/readback targets. + JSON command counts, resource creation counts, target dimensions, backend + name, trace/draw summary, and draw vertex/index totals, scissor/depth/ + blend-state plus texture/sampler-bind/upload/readback/frame-capture/blit + command/byte totals for agent automation, with an expected-failure smoke for + oversized render/readback targets. - `pano_cli simulate-document-history` exercises pure document history apply/undo/redo behavior and emits JSON layer/frame/history state for agent automation. diff --git a/src/foundation/result.h b/src/foundation/result.h index 1d059b0..955f4ed 100644 --- a/src/foundation/result.h +++ b/src/foundation/result.h @@ -1,5 +1,7 @@ #pragma once +#include + namespace pp::foundation { enum class StatusCode { @@ -38,7 +40,7 @@ class Result { public: [[nodiscard]] static constexpr Result success(T value) noexcept { - return Result(value, Status::success()); + return Result(std::move(value), Status::success()); } [[nodiscard]] static constexpr Result failure(Status status) noexcept @@ -61,6 +63,11 @@ public: return value_; } + [[nodiscard]] constexpr T& value() noexcept + { + return value_; + } + [[nodiscard]] constexpr Status status() const noexcept { return status_; @@ -68,7 +75,7 @@ public: private: constexpr Result(T value, Status status) noexcept - : value_(value) + : value_(std::move(value)) , status_(status) { } diff --git a/src/renderer_api/recording_renderer.cpp b/src/renderer_api/recording_renderer.cpp index 60ee32d..c8fd326 100644 --- a/src/renderer_api/recording_renderer.cpp +++ b/src/renderer_api/recording_renderer.cpp @@ -1,5 +1,8 @@ #include "renderer_api/recording_renderer.h" +#include +#include + namespace pp::renderer { namespace { @@ -18,6 +21,20 @@ void push_command( } } +template +[[nodiscard]] pp::foundation::Result> make_recording_resource( + Args&&... args) noexcept +{ + auto resource = std::unique_ptr(new (std::nothrow) Resource(std::forward(args)...)); + if (!resource) { + return pp::foundation::Result>::failure( + pp::foundation::Status::out_of_range("renderer resource allocation failed")); + } + + std::unique_ptr erased = std::move(resource); + return pp::foundation::Result>::success(std::move(erased)); +} + } RecordingTexture2D::RecordingTexture2D(TextureDesc desc) noexcept @@ -457,6 +474,71 @@ const char* RecordingRenderDevice::backend_name() const noexcept return "recording"; } +pp::foundation::Result> RecordingRenderDevice::create_texture( + TextureDesc desc) noexcept +{ + const auto bytes = texture_byte_size(desc); + if (!bytes.ok()) { + return pp::foundation::Result>::failure(bytes.status()); + } + + return make_recording_resource(desc); +} + +pp::foundation::Result> RecordingRenderDevice::create_render_target( + TextureDesc color_desc) noexcept +{ + if (!color_desc.render_target) { + return pp::foundation::Result>::failure( + pp::foundation::Status::invalid_argument("render target texture must be flagged as render_target")); + } + + const auto bytes = texture_byte_size(color_desc); + if (!bytes.ok()) { + return pp::foundation::Result>::failure(bytes.status()); + } + + return make_recording_resource(color_desc); +} + +pp::foundation::Result> RecordingRenderDevice::create_shader_program( + ShaderProgramDesc desc) noexcept +{ + const auto status = validate_shader_program_desc(desc); + if (!status.ok()) { + return pp::foundation::Result>::failure(status); + } + + return make_recording_resource(desc.debug_name); +} + +pp::foundation::Result> RecordingRenderDevice::create_mesh( + MeshDesc desc) noexcept +{ + const auto status = validate_mesh_desc(desc); + if (!status.ok()) { + return pp::foundation::Result>::failure(status); + } + + return make_recording_resource(desc); +} + +pp::foundation::Result> RecordingRenderDevice::create_readback_buffer( + std::uint64_t size_bytes) noexcept +{ + if (size_bytes == 0U) { + return pp::foundation::Result>::failure( + pp::foundation::Status::invalid_argument("readback buffer size must be greater than zero")); + } + + if (size_bytes > max_texture_bytes) { + return pp::foundation::Result>::failure( + pp::foundation::Status::out_of_range("readback buffer size exceeds the configured limit")); + } + + return make_recording_resource(size_bytes); +} + ICommandContext& RecordingRenderDevice::immediate_context() noexcept { return context_; diff --git a/src/renderer_api/recording_renderer.h b/src/renderer_api/recording_renderer.h index b35b3f6..0025702 100644 --- a/src/renderer_api/recording_renderer.h +++ b/src/renderer_api/recording_renderer.h @@ -163,6 +163,16 @@ public: RecordingRenderDevice() noexcept; [[nodiscard]] const char* backend_name() const noexcept override; + [[nodiscard]] pp::foundation::Result> create_texture( + TextureDesc desc) noexcept override; + [[nodiscard]] pp::foundation::Result> create_render_target( + TextureDesc color_desc) noexcept override; + [[nodiscard]] pp::foundation::Result> create_shader_program( + ShaderProgramDesc desc) noexcept override; + [[nodiscard]] pp::foundation::Result> create_mesh( + MeshDesc desc) noexcept override; + [[nodiscard]] pp::foundation::Result> create_readback_buffer( + std::uint64_t size_bytes) noexcept override; [[nodiscard]] ICommandContext& immediate_context() noexcept override; [[nodiscard]] IRenderTrace* trace() noexcept override; diff --git a/src/renderer_api/renderer_api.h b/src/renderer_api/renderer_api.h index 464c40b..aad5a46 100644 --- a/src/renderer_api/renderer_api.h +++ b/src/renderer_api/renderer_api.h @@ -4,6 +4,7 @@ #include #include +#include #include namespace pp::renderer { @@ -237,6 +238,16 @@ class IRenderDevice { public: virtual ~IRenderDevice() = default; [[nodiscard]] virtual const char* backend_name() const noexcept = 0; + [[nodiscard]] virtual pp::foundation::Result> create_texture( + TextureDesc desc) noexcept = 0; + [[nodiscard]] virtual pp::foundation::Result> create_render_target( + TextureDesc color_desc) noexcept = 0; + [[nodiscard]] virtual pp::foundation::Result> create_shader_program( + ShaderProgramDesc desc) noexcept = 0; + [[nodiscard]] virtual pp::foundation::Result> create_mesh( + MeshDesc desc) noexcept = 0; + [[nodiscard]] virtual pp::foundation::Result> create_readback_buffer( + std::uint64_t size_bytes) noexcept = 0; [[nodiscard]] virtual ICommandContext& immediate_context() noexcept = 0; [[nodiscard]] virtual IRenderTrace* trace() noexcept = 0; }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e7be721..23ba72b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -365,7 +365,7 @@ if(TARGET pano_cli) 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\":16.*\"drawCommands\":1.*\"drawVertices\":3.*\"drawIndices\":3.*\"scissorCommands\":1.*\"blendCommands\":1.*\"depthCommands\":1.*\"bindTextureCommands\":1.*\"bindSamplerCommands\":1.*\"boundTextureBytes\":2048.*\"uploadCommands\":1.*\"uploadBytes\":4.*\"readbackCommands\":1.*\"readbackBytes\":2048.*\"captureCommands\":1.*\"captureBytes\":2048.*\"blitCommands\":1.*\"blitSourceBytes\":2048.*\"blitDestinationBytes\":2048") + PASS_REGULAR_EXPRESSION "\"backend\":\"recording\".*\"width\":32.*\"height\":16.*\"createdResources\":6.*\"commands\":16.*\"drawCommands\":1.*\"drawVertices\":3.*\"drawIndices\":3.*\"scissorCommands\":1.*\"blendCommands\":1.*\"depthCommands\":1.*\"bindTextureCommands\":1.*\"bindSamplerCommands\":1.*\"boundTextureBytes\":2048.*\"uploadCommands\":1.*\"uploadBytes\":4.*\"readbackCommands\":1.*\"readbackBytes\":2048.*\"captureCommands\":1.*\"captureBytes\":2048.*\"blitCommands\":1.*\"blitSourceBytes\":2048.*\"blitDestinationBytes\":2048") add_test(NAME pano_cli_record_render_rejects_oversized_target COMMAND "${CMAKE_COMMAND}" diff --git a/tests/renderer_api/renderer_api_tests.cpp b/tests/renderer_api/renderer_api_tests.cpp index 7bef042..3c787d4 100644 --- a/tests/renderer_api/renderer_api_tests.cpp +++ b/tests/renderer_api/renderer_api_tests.cpp @@ -5,7 +5,10 @@ #include #include +#include +#include #include +#include using pp::foundation::StatusCode; using pp::renderer::BlitFilter; @@ -81,42 +84,78 @@ namespace { class FakeRenderTarget final : public IRenderTarget { public: + explicit FakeRenderTarget(TextureDesc desc = TextureDesc { + .extent = Extent2D { .width = 64, .height = 32 }, + .format = TextureFormat::rgba8, + .render_target = true, + }) noexcept + : desc_(desc) + { + } + [[nodiscard]] TextureDesc color_desc() const noexcept override { - return TextureDesc { - .extent = Extent2D { .width = 64, .height = 32 }, - .format = TextureFormat::rgba8, - .render_target = true, - }; + return desc_; } + +private: + TextureDesc desc_ {}; }; class FakeShaderProgram final : public IShaderProgram { public: + explicit FakeShaderProgram(const char* debug_name = "fake-shader") noexcept + : debug_name_(debug_name) + { + } + [[nodiscard]] const char* debug_name() const noexcept override { - return "fake-shader"; + return debug_name_; } + +private: + const char* debug_name_ = ""; }; class FakeMesh final : public IMesh { public: + explicit FakeMesh(MeshDesc desc = MeshDesc { + .vertex_count = 3, + .index_count = 0, + .topology = PrimitiveTopology::triangles, + }) noexcept + : desc_(desc) + { + } + [[nodiscard]] MeshDesc desc() const noexcept override { - return MeshDesc { .vertex_count = 3, .index_count = 0, .topology = PrimitiveTopology::triangles }; + return desc_; } + +private: + MeshDesc desc_ {}; }; class FakeTexture final : public pp::renderer::ITexture2D { public: + explicit FakeTexture(TextureDesc desc = TextureDesc { + .extent = Extent2D { .width = 64, .height = 32 }, + .format = TextureFormat::rgba8, + .render_target = true, + }) noexcept + : desc_(desc) + { + } + [[nodiscard]] TextureDesc desc() const noexcept override { - return TextureDesc { - .extent = Extent2D { .width = 64, .height = 32 }, - .format = TextureFormat::rgba8, - .render_target = true, - }; + return desc_; } + +private: + TextureDesc desc_ {}; }; class FakeReadbackBuffer final : public pp::renderer::IReadbackBuffer { @@ -369,6 +408,70 @@ public: return "fake"; } + [[nodiscard]] pp::foundation::Result> create_texture( + TextureDesc desc) noexcept override + { + const auto bytes = texture_byte_size(desc); + if (!bytes.ok()) { + return pp::foundation::Result>::failure(bytes.status()); + } + + return allocate_resource(desc); + } + + [[nodiscard]] pp::foundation::Result> create_render_target( + TextureDesc color_desc) noexcept override + { + if (!color_desc.render_target) { + return pp::foundation::Result>::failure( + pp::foundation::Status::invalid_argument("render target texture must be flagged as render_target")); + } + + const auto bytes = texture_byte_size(color_desc); + if (!bytes.ok()) { + return pp::foundation::Result>::failure(bytes.status()); + } + + return allocate_resource(color_desc); + } + + [[nodiscard]] pp::foundation::Result> create_shader_program( + ShaderProgramDesc desc) noexcept override + { + const auto status = validate_shader_program_desc(desc); + if (!status.ok()) { + return pp::foundation::Result>::failure(status); + } + + return allocate_resource(desc.debug_name); + } + + [[nodiscard]] pp::foundation::Result> create_mesh( + MeshDesc desc) noexcept override + { + const auto status = validate_mesh_desc(desc); + if (!status.ok()) { + return pp::foundation::Result>::failure(status); + } + + return allocate_resource(desc); + } + + [[nodiscard]] pp::foundation::Result> create_readback_buffer( + std::uint64_t size_bytes) noexcept override + { + if (size_bytes == 0U) { + return pp::foundation::Result>::failure( + pp::foundation::Status::invalid_argument("readback buffer size must be greater than zero")); + } + if (size_bytes > pp::renderer::max_texture_bytes) { + return pp::foundation::Result>::failure( + pp::foundation::Status::out_of_range("readback buffer size exceeds the configured limit")); + } + + return allocate_resource(size_bytes); + } + [[nodiscard]] ICommandContext& immediate_context() noexcept override { return context; @@ -381,6 +484,21 @@ public: FakeCommandContext context; FakeTrace trace_recorder; + +private: + template + [[nodiscard]] static pp::foundation::Result> allocate_resource( + Args&&... args) noexcept + { + auto resource = std::unique_ptr(new (std::nothrow) Resource(std::forward(args)...)); + if (!resource) { + return pp::foundation::Result>::failure( + pp::foundation::Status::out_of_range("renderer resource allocation failed")); + } + + std::unique_ptr erased = std::move(resource); + return pp::foundation::Result>::success(std::move(erased)); + } }; void computes_texture_sizes(pp::tests::Harness& h) @@ -857,6 +975,74 @@ void renderer_interfaces_support_backend_neutral_dispatch(pp::tests::Harness& h) PP_EXPECT(h, device.context.last_blit_filter == BlitFilter::linear); } +void render_devices_create_validated_resources(pp::tests::Harness& h) +{ + static constexpr char shader_source[] = "void main() {}"; + + RecordingRenderDevice device; + const auto texture = device.create_texture(TextureDesc { + .extent = Extent2D { .width = 8, .height = 4 }, + .format = TextureFormat::rgba8, + .render_target = false, + }); + const auto target = device.create_render_target(TextureDesc { + .extent = Extent2D { .width = 8, .height = 4 }, + .format = TextureFormat::rgba8, + .render_target = true, + }); + const auto shader = device.create_shader_program(ShaderProgramDesc { + .debug_name = "factory-shader", + .vertex = ShaderStageSource { .source = shader_source, .source_size = sizeof(shader_source) - 1U }, + .fragment = ShaderStageSource { .source = shader_source, .source_size = sizeof(shader_source) - 1U }, + }); + const auto mesh = device.create_mesh(MeshDesc { + .vertex_count = 6, + .index_count = 6, + .topology = PrimitiveTopology::triangles, + }); + const auto readback = device.create_readback_buffer(8U * 4U * 4U); + + PP_EXPECT(h, texture.ok()); + PP_EXPECT(h, texture.value()->desc().extent.width == 8U); + PP_EXPECT(h, !texture.value()->desc().render_target); + PP_EXPECT(h, target.ok()); + PP_EXPECT(h, target.value()->color_desc().render_target); + PP_EXPECT(h, shader.ok()); + PP_EXPECT(h, shader.value()->debug_name() == std::string_view("factory-shader")); + PP_EXPECT(h, mesh.ok()); + PP_EXPECT(h, mesh.value()->desc().index_count == 6U); + PP_EXPECT(h, readback.ok()); + PP_EXPECT(h, readback.value()->size_bytes() == 128U); + + const auto bad_texture = device.create_texture(TextureDesc { + .extent = Extent2D { .width = 0, .height = 4 }, + .format = TextureFormat::rgba8, + }); + const auto bad_target = device.create_render_target(TextureDesc { + .extent = Extent2D { .width = 8, .height = 4 }, + .format = TextureFormat::rgba8, + .render_target = false, + }); + const auto bad_shader = device.create_shader_program(ShaderProgramDesc { + .debug_name = "bad-shader", + .vertex = ShaderStageSource {}, + .fragment = ShaderStageSource {}, + }); + const auto bad_mesh = device.create_mesh(MeshDesc {}); + const auto bad_readback = device.create_readback_buffer(0); + + PP_EXPECT(h, !bad_texture.ok()); + PP_EXPECT(h, bad_texture.status().code == StatusCode::invalid_argument); + PP_EXPECT(h, !bad_target.ok()); + PP_EXPECT(h, bad_target.status().code == StatusCode::invalid_argument); + PP_EXPECT(h, !bad_shader.ok()); + PP_EXPECT(h, bad_shader.status().code == StatusCode::invalid_argument); + PP_EXPECT(h, !bad_mesh.ok()); + PP_EXPECT(h, bad_mesh.status().code == StatusCode::invalid_argument); + PP_EXPECT(h, !bad_readback.ok()); + PP_EXPECT(h, bad_readback.status().code == StatusCode::invalid_argument); +} + void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h) { RecordingRenderDevice device; @@ -1302,6 +1488,7 @@ int main() harness.run("validates_panopainter_shader_catalog", validates_panopainter_shader_catalog); harness.run("rejects_invalid_shader_catalogs", rejects_invalid_shader_catalogs); harness.run("renderer_interfaces_support_backend_neutral_dispatch", renderer_interfaces_support_backend_neutral_dispatch); + harness.run("render_devices_create_validated_resources", render_devices_create_validated_resources); harness.run("recording_renderer_records_valid_command_sequences", recording_renderer_records_valid_command_sequences); harness.run("recording_renderer_rejects_invalid_command_order_and_targets", recording_renderer_rejects_invalid_command_order_and_targets); return harness.finish(); diff --git a/tools/pano_cli/main.cpp b/tools/pano_cli/main.cpp index 77fd978..ba6b3c0 100644 --- a/tools/pano_cli/main.cpp +++ b/tools/pano_cli/main.cpp @@ -2211,22 +2211,22 @@ int record_render(int argc, char** argv) } pp::renderer::RecordingRenderDevice device; - pp::renderer::RecordingTexture2D texture(pp::renderer::TextureDesc { + const auto texture = device.create_texture(pp::renderer::TextureDesc { .extent = pp::renderer::Extent2D { .width = args.width, .height = args.height }, .format = pp::renderer::TextureFormat::rgba8, .render_target = true, }); - pp::renderer::RecordingRenderTarget target(pp::renderer::TextureDesc { + const auto target = device.create_render_target(pp::renderer::TextureDesc { .extent = pp::renderer::Extent2D { .width = args.width, .height = args.height }, .format = pp::renderer::TextureFormat::rgba8, .render_target = true, }); - pp::renderer::RecordingRenderTarget blit_target(pp::renderer::TextureDesc { + const auto blit_target = device.create_render_target(pp::renderer::TextureDesc { .extent = pp::renderer::Extent2D { .width = args.width, .height = args.height }, .format = pp::renderer::TextureFormat::rgba8, .render_target = true, }); - pp::renderer::RecordingReadbackBuffer readback_buffer( + const auto readback_buffer = device.create_readback_buffer( static_cast(args.width) * args.height * 4U); const std::array upload_pixel { std::byte { 0xff }, @@ -2234,17 +2234,54 @@ int record_render(int argc, char** argv) std::byte { 0xff }, std::byte { 0xff }, }; - pp::renderer::RecordingShaderProgram shader("pano-cli-record-render"); - pp::renderer::RecordingMesh mesh(pp::renderer::MeshDesc { + static constexpr char shader_source[] = "void main() {}"; + const auto shader = device.create_shader_program(pp::renderer::ShaderProgramDesc { + .debug_name = "pano-cli-record-render", + .vertex = pp::renderer::ShaderStageSource { + .source = shader_source, + .source_size = sizeof(shader_source) - 1U, + }, + .fragment = pp::renderer::ShaderStageSource { + .source = shader_source, + .source_size = sizeof(shader_source) - 1U, + }, + }); + const auto mesh = device.create_mesh(pp::renderer::MeshDesc { .vertex_count = 3, .index_count = 3, .topology = pp::renderer::PrimitiveTopology::triangles, }); + if (!texture.ok()) { + print_error("record-render", texture.status().message); + return 2; + } + if (!target.ok()) { + print_error("record-render", target.status().message); + return 2; + } + if (!blit_target.ok()) { + print_error("record-render", blit_target.status().message); + return 2; + } + if (!readback_buffer.ok()) { + print_error("record-render", readback_buffer.status().message); + return 2; + } + if (!shader.ok()) { + print_error("record-render", shader.status().message); + return 2; + } + if (!mesh.ok()) { + print_error("record-render", mesh.status().message); + return 2; + } + constexpr std::size_t created_resources = 6; + device.trace()->marker("renderer", "pano_cli_record_render"); auto& context = device.immediate_context(); const auto upload_status = context.upload_texture( - texture, + *texture.value(), pp::renderer::ReadbackRegion { .x = 0, .y = 0, @@ -2258,7 +2295,7 @@ int record_render(int argc, char** argv) } const auto begin_status = context.begin_render_pass( - target, + *target.value(), 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); @@ -2284,7 +2321,7 @@ int record_render(int argc, char** argv) return 2; } - const auto shader_status = context.bind_shader(shader); + const auto shader_status = context.bind_shader(*shader.value()); const auto blend_status = context.set_blend_state(pp::renderer::BlendState { .enabled = true, .source_color = pp::renderer::BlendFactor::source_alpha, @@ -2297,7 +2334,7 @@ int record_render(int argc, char** argv) .write_enabled = true, .compare = pp::renderer::CompareOp::less_or_equal, }); - const auto bind_texture_status = context.bind_texture(0, texture); + const auto bind_texture_status = context.bind_texture(0, *texture.value()); const auto bind_sampler_status = context.bind_sampler(0, pp::renderer::SamplerDesc { .min_filter = pp::renderer::SamplerFilter::linear, .mag_filter = pp::renderer::SamplerFilter::linear, @@ -2306,7 +2343,7 @@ int record_render(int argc, char** argv) .address_v = pp::renderer::SamplerAddressMode::clamp_to_edge, .address_w = pp::renderer::SamplerAddressMode::clamp_to_edge, }); - const auto mesh_status = context.bind_mesh(mesh); + const auto mesh_status = context.bind_mesh(*mesh.value()); const auto draw_status = context.draw(); context.end_render_pass(); @@ -2340,34 +2377,34 @@ int record_render(int argc, char** argv) } const auto readback_status = context.read_texture( - texture, + *texture.value(), pp::renderer::ReadbackRegion { .x = 0, .y = 0, .width = args.width, .height = args.height, }, - readback_buffer); + *readback_buffer.value()); if (!readback_status.ok()) { print_error("record-render", readback_status.message); return 2; } - const auto capture_status = context.capture_frame(target, readback_buffer); + const auto capture_status = context.capture_frame(*target.value(), *readback_buffer.value()); if (!capture_status.ok()) { print_error("record-render", capture_status.message); return 2; } const auto blit_status = context.blit_render_target( - target, + *target.value(), pp::renderer::ReadbackRegion { .x = 0, .y = 0, .width = args.width, .height = args.height, }, - blit_target, + *blit_target.value(), pp::renderer::ReadbackRegion { .x = 0, .y = 0, @@ -2442,6 +2479,7 @@ int record_render(int argc, char** argv) << ",\"target\":{\"width\":" << args.width << ",\"height\":" << args.height << ",\"format\":\"rgba8\"}" + << ",\"createdResources\":" << created_resources << ",\"commands\":" << commands.size() << ",\"drawCommands\":" << draw_commands << ",\"drawVertices\":" << draw_vertices