Add renderer resource factory contract

This commit is contained in:
2026-06-02 16:09:52 +02:00
parent 881b5271a2
commit 23c308db1b
9 changed files with 389 additions and 48 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -1,5 +1,7 @@
#pragma once
#include <utility>
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)
{
}

View File

@@ -1,5 +1,8 @@
#include "renderer_api/recording_renderer.h"
#include <new>
#include <utility>
namespace pp::renderer {
namespace {
@@ -18,6 +21,20 @@ void push_command(
}
}
template <typename Resource, typename Interface, typename... Args>
[[nodiscard]] pp::foundation::Result<std::unique_ptr<Interface>> make_recording_resource(
Args&&... args) noexcept
{
auto resource = std::unique_ptr<Resource>(new (std::nothrow) Resource(std::forward<Args>(args)...));
if (!resource) {
return pp::foundation::Result<std::unique_ptr<Interface>>::failure(
pp::foundation::Status::out_of_range("renderer resource allocation failed"));
}
std::unique_ptr<Interface> erased = std::move(resource);
return pp::foundation::Result<std::unique_ptr<Interface>>::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<std::unique_ptr<ITexture2D>> RecordingRenderDevice::create_texture(
TextureDesc desc) noexcept
{
const auto bytes = texture_byte_size(desc);
if (!bytes.ok()) {
return pp::foundation::Result<std::unique_ptr<ITexture2D>>::failure(bytes.status());
}
return make_recording_resource<RecordingTexture2D, ITexture2D>(desc);
}
pp::foundation::Result<std::unique_ptr<IRenderTarget>> RecordingRenderDevice::create_render_target(
TextureDesc color_desc) noexcept
{
if (!color_desc.render_target) {
return pp::foundation::Result<std::unique_ptr<IRenderTarget>>::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<std::unique_ptr<IRenderTarget>>::failure(bytes.status());
}
return make_recording_resource<RecordingRenderTarget, IRenderTarget>(color_desc);
}
pp::foundation::Result<std::unique_ptr<IShaderProgram>> RecordingRenderDevice::create_shader_program(
ShaderProgramDesc desc) noexcept
{
const auto status = validate_shader_program_desc(desc);
if (!status.ok()) {
return pp::foundation::Result<std::unique_ptr<IShaderProgram>>::failure(status);
}
return make_recording_resource<RecordingShaderProgram, IShaderProgram>(desc.debug_name);
}
pp::foundation::Result<std::unique_ptr<IMesh>> RecordingRenderDevice::create_mesh(
MeshDesc desc) noexcept
{
const auto status = validate_mesh_desc(desc);
if (!status.ok()) {
return pp::foundation::Result<std::unique_ptr<IMesh>>::failure(status);
}
return make_recording_resource<RecordingMesh, IMesh>(desc);
}
pp::foundation::Result<std::unique_ptr<IReadbackBuffer>> RecordingRenderDevice::create_readback_buffer(
std::uint64_t size_bytes) noexcept
{
if (size_bytes == 0U) {
return pp::foundation::Result<std::unique_ptr<IReadbackBuffer>>::failure(
pp::foundation::Status::invalid_argument("readback buffer size must be greater than zero"));
}
if (size_bytes > max_texture_bytes) {
return pp::foundation::Result<std::unique_ptr<IReadbackBuffer>>::failure(
pp::foundation::Status::out_of_range("readback buffer size exceeds the configured limit"));
}
return make_recording_resource<RecordingReadbackBuffer, IReadbackBuffer>(size_bytes);
}
ICommandContext& RecordingRenderDevice::immediate_context() noexcept
{
return context_;

View File

@@ -163,6 +163,16 @@ public:
RecordingRenderDevice() noexcept;
[[nodiscard]] const char* backend_name() const noexcept override;
[[nodiscard]] pp::foundation::Result<std::unique_ptr<ITexture2D>> create_texture(
TextureDesc desc) noexcept override;
[[nodiscard]] pp::foundation::Result<std::unique_ptr<IRenderTarget>> create_render_target(
TextureDesc color_desc) noexcept override;
[[nodiscard]] pp::foundation::Result<std::unique_ptr<IShaderProgram>> create_shader_program(
ShaderProgramDesc desc) noexcept override;
[[nodiscard]] pp::foundation::Result<std::unique_ptr<IMesh>> create_mesh(
MeshDesc desc) noexcept override;
[[nodiscard]] pp::foundation::Result<std::unique_ptr<IReadbackBuffer>> create_readback_buffer(
std::uint64_t size_bytes) noexcept override;
[[nodiscard]] ICommandContext& immediate_context() noexcept override;
[[nodiscard]] IRenderTrace* trace() noexcept override;

View File

@@ -4,6 +4,7 @@
#include <cstddef>
#include <cstdint>
#include <memory>
#include <span>
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<std::unique_ptr<ITexture2D>> create_texture(
TextureDesc desc) noexcept = 0;
[[nodiscard]] virtual pp::foundation::Result<std::unique_ptr<IRenderTarget>> create_render_target(
TextureDesc color_desc) noexcept = 0;
[[nodiscard]] virtual pp::foundation::Result<std::unique_ptr<IShaderProgram>> create_shader_program(
ShaderProgramDesc desc) noexcept = 0;
[[nodiscard]] virtual pp::foundation::Result<std::unique_ptr<IMesh>> create_mesh(
MeshDesc desc) noexcept = 0;
[[nodiscard]] virtual pp::foundation::Result<std::unique_ptr<IReadbackBuffer>> create_readback_buffer(
std::uint64_t size_bytes) noexcept = 0;
[[nodiscard]] virtual ICommandContext& immediate_context() noexcept = 0;
[[nodiscard]] virtual IRenderTrace* trace() noexcept = 0;
};

View File

@@ -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}"

View File

@@ -5,7 +5,10 @@
#include <array>
#include <cstddef>
#include <memory>
#include <new>
#include <string_view>
#include <utility>
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<std::unique_ptr<pp::renderer::ITexture2D>> create_texture(
TextureDesc desc) noexcept override
{
const auto bytes = texture_byte_size(desc);
if (!bytes.ok()) {
return pp::foundation::Result<std::unique_ptr<pp::renderer::ITexture2D>>::failure(bytes.status());
}
return allocate_resource<FakeTexture, pp::renderer::ITexture2D>(desc);
}
[[nodiscard]] pp::foundation::Result<std::unique_ptr<IRenderTarget>> create_render_target(
TextureDesc color_desc) noexcept override
{
if (!color_desc.render_target) {
return pp::foundation::Result<std::unique_ptr<IRenderTarget>>::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<std::unique_ptr<IRenderTarget>>::failure(bytes.status());
}
return allocate_resource<FakeRenderTarget, IRenderTarget>(color_desc);
}
[[nodiscard]] pp::foundation::Result<std::unique_ptr<IShaderProgram>> create_shader_program(
ShaderProgramDesc desc) noexcept override
{
const auto status = validate_shader_program_desc(desc);
if (!status.ok()) {
return pp::foundation::Result<std::unique_ptr<IShaderProgram>>::failure(status);
}
return allocate_resource<FakeShaderProgram, IShaderProgram>(desc.debug_name);
}
[[nodiscard]] pp::foundation::Result<std::unique_ptr<IMesh>> create_mesh(
MeshDesc desc) noexcept override
{
const auto status = validate_mesh_desc(desc);
if (!status.ok()) {
return pp::foundation::Result<std::unique_ptr<IMesh>>::failure(status);
}
return allocate_resource<FakeMesh, IMesh>(desc);
}
[[nodiscard]] pp::foundation::Result<std::unique_ptr<pp::renderer::IReadbackBuffer>> create_readback_buffer(
std::uint64_t size_bytes) noexcept override
{
if (size_bytes == 0U) {
return pp::foundation::Result<std::unique_ptr<pp::renderer::IReadbackBuffer>>::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<std::unique_ptr<pp::renderer::IReadbackBuffer>>::failure(
pp::foundation::Status::out_of_range("readback buffer size exceeds the configured limit"));
}
return allocate_resource<FakeReadbackBuffer, pp::renderer::IReadbackBuffer>(size_bytes);
}
[[nodiscard]] ICommandContext& immediate_context() noexcept override
{
return context;
@@ -381,6 +484,21 @@ public:
FakeCommandContext context;
FakeTrace trace_recorder;
private:
template <typename Resource, typename Interface, typename... Args>
[[nodiscard]] static pp::foundation::Result<std::unique_ptr<Interface>> allocate_resource(
Args&&... args) noexcept
{
auto resource = std::unique_ptr<Resource>(new (std::nothrow) Resource(std::forward<Args>(args)...));
if (!resource) {
return pp::foundation::Result<std::unique_ptr<Interface>>::failure(
pp::foundation::Status::out_of_range("renderer resource allocation failed"));
}
std::unique_ptr<Interface> erased = std::move(resource);
return pp::foundation::Result<std::unique_ptr<Interface>>::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();

View File

@@ -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<std::uint64_t>(args.width) * args.height * 4U);
const std::array<std::byte, 4> 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