Add renderer resource factory contract
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user