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
|
source code reintroduces raw `GL_*`/`WGL_*` constants outside the allowed
|
||||||
legacy OpenGL implementation files.
|
legacy OpenGL implementation files.
|
||||||
- `pp_renderer_api` exposes a headless `RecordingRenderDevice` that validates
|
- `pp_renderer_api` exposes a headless `RecordingRenderDevice` that validates
|
||||||
command order, scissor state, depth state, blend state, texture-slot binding,
|
backend-owned resource creation, command order, scissor state, depth state,
|
||||||
sampler-state binding, texture-upload byte counts, readback bounds,
|
blend state, texture-slot binding, sampler-state binding, texture-upload byte
|
||||||
frame-capture sources, destination buffer sizes, and render-target blit
|
counts, readback bounds, frame-capture sources, destination buffer sizes, and
|
||||||
regions, records render/scissor/depth/blend/texture-bind/sampler-bind/upload/
|
render-target blit regions, records
|
||||||
readback/frame-capture/blit commands, draw mesh inputs, and records trace
|
render/scissor/depth/blend/texture-bind/sampler-bind/upload/readback/
|
||||||
markers without a window or GL context.
|
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
|
- `pano_cli record-render` exposes the recording renderer through JSON
|
||||||
automation, including scissor/depth/blend/texture-bind/sampler-bind/upload/
|
automation, including scissor/depth/blend/texture-bind/sampler-bind/upload/
|
||||||
readback/frame-capture/blit command and byte totals plus draw vertex/index
|
readback/frame-capture/blit command and byte totals, backend resource
|
||||||
totals, and is covered by `pano_cli_record_render_smoke` plus
|
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_record_render_rejects_oversized_target`.
|
||||||
- `pano_cli simulate-document-history` exposes `pp_document::DocumentHistory`
|
- `pano_cli simulate-document-history` exposes `pp_document::DocumentHistory`
|
||||||
apply/undo/redo state through JSON automation and is covered by
|
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
|
PanoPainter shader catalog validation, readback byte-size and command-order
|
||||||
validation, texture-upload byte-count validation, frame-capture byte-size and
|
validation, texture-upload byte-count validation, frame-capture byte-size and
|
||||||
command-order validation, render-target blit validation, texture-slot binding
|
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
|
scissor/depth/blend/texture/sampler-bind/upload/readback/frame-capture/blit
|
||||||
command capture, draw mesh-input capture, and invalid catalog rejection.
|
command capture, draw mesh-input capture, and invalid catalog rejection.
|
||||||
- `pp_paint_renderer_compositor_tests` passed.
|
- `pp_paint_renderer_compositor_tests` passed.
|
||||||
@@ -819,17 +820,20 @@ Results:
|
|||||||
reintroduces raw `GL_*`/`WGL_*` constants outside the allowed legacy OpenGL
|
reintroduces raw `GL_*`/`WGL_*` constants outside the allowed legacy OpenGL
|
||||||
implementation files.
|
implementation files.
|
||||||
- `pp_renderer_api` now includes a headless `RecordingRenderDevice` with strict
|
- `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/
|
command-order/scissor-state/depth-state/blend-state/texture-bind/
|
||||||
sampler-bind/texture-upload/readback/frame-capture/blit validation; it
|
sampler-bind/texture-upload/readback/frame-capture/blit validation; it
|
||||||
records commands, trace markers, scissor state, depth state, blend state,
|
creates validated textures, render targets, shaders, meshes, and readback
|
||||||
texture/sampler binds, draw mesh inputs, uploads/readbacks, frame captures,
|
buffers, then records commands, trace markers, scissor state, depth state,
|
||||||
and render-target blits, giving automation a backend-neutral render path that
|
blend state, texture/sampler binds, draw mesh inputs, uploads/readbacks,
|
||||||
does not require a window or GL context.
|
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
|
- `pano_cli record-render` exercises that headless recording renderer and emits
|
||||||
JSON command counts, target dimensions, backend name, trace/draw summary, and
|
JSON command counts, resource creation counts, target dimensions, backend
|
||||||
draw vertex/index totals, scissor/depth/blend-state plus texture/sampler-bind/
|
name, trace/draw summary, and draw vertex/index totals, scissor/depth/
|
||||||
upload/readback/frame-capture/blit command/byte totals for agent automation,
|
blend-state plus texture/sampler-bind/upload/readback/frame-capture/blit
|
||||||
with an expected-failure smoke for oversized render/readback targets.
|
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
|
- `pano_cli simulate-document-history` exercises pure document history
|
||||||
apply/undo/redo behavior and emits JSON layer/frame/history state for agent
|
apply/undo/redo behavior and emits JSON layer/frame/history state for agent
|
||||||
automation.
|
automation.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace pp::foundation {
|
namespace pp::foundation {
|
||||||
|
|
||||||
enum class StatusCode {
|
enum class StatusCode {
|
||||||
@@ -38,7 +40,7 @@ class Result {
|
|||||||
public:
|
public:
|
||||||
[[nodiscard]] static constexpr Result success(T value) noexcept
|
[[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
|
[[nodiscard]] static constexpr Result failure(Status status) noexcept
|
||||||
@@ -61,6 +63,11 @@ public:
|
|||||||
return value_;
|
return value_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr T& value() noexcept
|
||||||
|
{
|
||||||
|
return value_;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] constexpr Status status() const noexcept
|
[[nodiscard]] constexpr Status status() const noexcept
|
||||||
{
|
{
|
||||||
return status_;
|
return status_;
|
||||||
@@ -68,7 +75,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
constexpr Result(T value, Status status) noexcept
|
constexpr Result(T value, Status status) noexcept
|
||||||
: value_(value)
|
: value_(std::move(value))
|
||||||
, status_(status)
|
, status_(status)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
#include "renderer_api/recording_renderer.h"
|
#include "renderer_api/recording_renderer.h"
|
||||||
|
|
||||||
|
#include <new>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace pp::renderer {
|
namespace pp::renderer {
|
||||||
|
|
||||||
namespace {
|
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
|
RecordingTexture2D::RecordingTexture2D(TextureDesc desc) noexcept
|
||||||
@@ -457,6 +474,71 @@ const char* RecordingRenderDevice::backend_name() const noexcept
|
|||||||
return "recording";
|
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
|
ICommandContext& RecordingRenderDevice::immediate_context() noexcept
|
||||||
{
|
{
|
||||||
return context_;
|
return context_;
|
||||||
|
|||||||
@@ -163,6 +163,16 @@ public:
|
|||||||
RecordingRenderDevice() noexcept;
|
RecordingRenderDevice() noexcept;
|
||||||
|
|
||||||
[[nodiscard]] const char* backend_name() const noexcept override;
|
[[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]] ICommandContext& immediate_context() noexcept override;
|
||||||
[[nodiscard]] IRenderTrace* trace() noexcept override;
|
[[nodiscard]] IRenderTrace* trace() noexcept override;
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|
||||||
namespace pp::renderer {
|
namespace pp::renderer {
|
||||||
@@ -237,6 +238,16 @@ class IRenderDevice {
|
|||||||
public:
|
public:
|
||||||
virtual ~IRenderDevice() = default;
|
virtual ~IRenderDevice() = default;
|
||||||
[[nodiscard]] virtual const char* backend_name() const noexcept = 0;
|
[[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 ICommandContext& immediate_context() noexcept = 0;
|
||||||
[[nodiscard]] virtual IRenderTrace* trace() 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)
|
COMMAND pano_cli record-render --width 32 --height 16)
|
||||||
set_tests_properties(pano_cli_record_render_smoke PROPERTIES
|
set_tests_properties(pano_cli_record_render_smoke PROPERTIES
|
||||||
LABELS "renderer;integration;desktop-fast"
|
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
|
add_test(NAME pano_cli_record_render_rejects_oversized_target
|
||||||
COMMAND "${CMAKE_COMMAND}"
|
COMMAND "${CMAKE_COMMAND}"
|
||||||
|
|||||||
@@ -5,7 +5,10 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
|
#include <new>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
using pp::foundation::StatusCode;
|
using pp::foundation::StatusCode;
|
||||||
using pp::renderer::BlitFilter;
|
using pp::renderer::BlitFilter;
|
||||||
@@ -81,42 +84,78 @@ namespace {
|
|||||||
|
|
||||||
class FakeRenderTarget final : public IRenderTarget {
|
class FakeRenderTarget final : public IRenderTarget {
|
||||||
public:
|
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
|
[[nodiscard]] TextureDesc color_desc() const noexcept override
|
||||||
{
|
{
|
||||||
return TextureDesc {
|
return desc_;
|
||||||
.extent = Extent2D { .width = 64, .height = 32 },
|
|
||||||
.format = TextureFormat::rgba8,
|
|
||||||
.render_target = true,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
TextureDesc desc_ {};
|
||||||
};
|
};
|
||||||
|
|
||||||
class FakeShaderProgram final : public IShaderProgram {
|
class FakeShaderProgram final : public IShaderProgram {
|
||||||
public:
|
public:
|
||||||
|
explicit FakeShaderProgram(const char* debug_name = "fake-shader") noexcept
|
||||||
|
: debug_name_(debug_name)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] const char* debug_name() const noexcept override
|
[[nodiscard]] const char* debug_name() const noexcept override
|
||||||
{
|
{
|
||||||
return "fake-shader";
|
return debug_name_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const char* debug_name_ = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
class FakeMesh final : public IMesh {
|
class FakeMesh final : public IMesh {
|
||||||
public:
|
public:
|
||||||
|
explicit FakeMesh(MeshDesc desc = MeshDesc {
|
||||||
|
.vertex_count = 3,
|
||||||
|
.index_count = 0,
|
||||||
|
.topology = PrimitiveTopology::triangles,
|
||||||
|
}) noexcept
|
||||||
|
: desc_(desc)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] MeshDesc desc() const noexcept override
|
[[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 {
|
class FakeTexture final : public pp::renderer::ITexture2D {
|
||||||
public:
|
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
|
[[nodiscard]] TextureDesc desc() const noexcept override
|
||||||
{
|
{
|
||||||
return TextureDesc {
|
return desc_;
|
||||||
.extent = Extent2D { .width = 64, .height = 32 },
|
|
||||||
.format = TextureFormat::rgba8,
|
|
||||||
.render_target = true,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
TextureDesc desc_ {};
|
||||||
};
|
};
|
||||||
|
|
||||||
class FakeReadbackBuffer final : public pp::renderer::IReadbackBuffer {
|
class FakeReadbackBuffer final : public pp::renderer::IReadbackBuffer {
|
||||||
@@ -369,6 +408,70 @@ public:
|
|||||||
return "fake";
|
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
|
[[nodiscard]] ICommandContext& immediate_context() noexcept override
|
||||||
{
|
{
|
||||||
return context;
|
return context;
|
||||||
@@ -381,6 +484,21 @@ public:
|
|||||||
|
|
||||||
FakeCommandContext context;
|
FakeCommandContext context;
|
||||||
FakeTrace trace_recorder;
|
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)
|
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);
|
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)
|
void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
|
||||||
{
|
{
|
||||||
RecordingRenderDevice device;
|
RecordingRenderDevice device;
|
||||||
@@ -1302,6 +1488,7 @@ int main()
|
|||||||
harness.run("validates_panopainter_shader_catalog", validates_panopainter_shader_catalog);
|
harness.run("validates_panopainter_shader_catalog", validates_panopainter_shader_catalog);
|
||||||
harness.run("rejects_invalid_shader_catalogs", rejects_invalid_shader_catalogs);
|
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("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_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);
|
harness.run("recording_renderer_rejects_invalid_command_order_and_targets", recording_renderer_rejects_invalid_command_order_and_targets);
|
||||||
return harness.finish();
|
return harness.finish();
|
||||||
|
|||||||
@@ -2211,22 +2211,22 @@ int record_render(int argc, char** argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
pp::renderer::RecordingRenderDevice device;
|
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 },
|
.extent = pp::renderer::Extent2D { .width = args.width, .height = args.height },
|
||||||
.format = pp::renderer::TextureFormat::rgba8,
|
.format = pp::renderer::TextureFormat::rgba8,
|
||||||
.render_target = true,
|
.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 },
|
.extent = pp::renderer::Extent2D { .width = args.width, .height = args.height },
|
||||||
.format = pp::renderer::TextureFormat::rgba8,
|
.format = pp::renderer::TextureFormat::rgba8,
|
||||||
.render_target = true,
|
.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 },
|
.extent = pp::renderer::Extent2D { .width = args.width, .height = args.height },
|
||||||
.format = pp::renderer::TextureFormat::rgba8,
|
.format = pp::renderer::TextureFormat::rgba8,
|
||||||
.render_target = true,
|
.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);
|
static_cast<std::uint64_t>(args.width) * args.height * 4U);
|
||||||
const std::array<std::byte, 4> upload_pixel {
|
const std::array<std::byte, 4> upload_pixel {
|
||||||
std::byte { 0xff },
|
std::byte { 0xff },
|
||||||
@@ -2234,17 +2234,54 @@ int record_render(int argc, char** argv)
|
|||||||
std::byte { 0xff },
|
std::byte { 0xff },
|
||||||
std::byte { 0xff },
|
std::byte { 0xff },
|
||||||
};
|
};
|
||||||
pp::renderer::RecordingShaderProgram shader("pano-cli-record-render");
|
static constexpr char shader_source[] = "void main() {}";
|
||||||
pp::renderer::RecordingMesh mesh(pp::renderer::MeshDesc {
|
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,
|
.vertex_count = 3,
|
||||||
.index_count = 3,
|
.index_count = 3,
|
||||||
.topology = pp::renderer::PrimitiveTopology::triangles,
|
.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");
|
device.trace()->marker("renderer", "pano_cli_record_render");
|
||||||
auto& context = device.immediate_context();
|
auto& context = device.immediate_context();
|
||||||
const auto upload_status = context.upload_texture(
|
const auto upload_status = context.upload_texture(
|
||||||
texture,
|
*texture.value(),
|
||||||
pp::renderer::ReadbackRegion {
|
pp::renderer::ReadbackRegion {
|
||||||
.x = 0,
|
.x = 0,
|
||||||
.y = 0,
|
.y = 0,
|
||||||
@@ -2258,7 +2295,7 @@ int record_render(int argc, char** argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto begin_status = context.begin_render_pass(
|
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 });
|
pp::renderer::ClearColor { .r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F });
|
||||||
if (!begin_status.ok()) {
|
if (!begin_status.ok()) {
|
||||||
print_error("record-render", begin_status.message);
|
print_error("record-render", begin_status.message);
|
||||||
@@ -2284,7 +2321,7 @@ int record_render(int argc, char** argv)
|
|||||||
return 2;
|
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 {
|
const auto blend_status = context.set_blend_state(pp::renderer::BlendState {
|
||||||
.enabled = true,
|
.enabled = true,
|
||||||
.source_color = pp::renderer::BlendFactor::source_alpha,
|
.source_color = pp::renderer::BlendFactor::source_alpha,
|
||||||
@@ -2297,7 +2334,7 @@ int record_render(int argc, char** argv)
|
|||||||
.write_enabled = true,
|
.write_enabled = true,
|
||||||
.compare = pp::renderer::CompareOp::less_or_equal,
|
.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 {
|
const auto bind_sampler_status = context.bind_sampler(0, pp::renderer::SamplerDesc {
|
||||||
.min_filter = pp::renderer::SamplerFilter::linear,
|
.min_filter = pp::renderer::SamplerFilter::linear,
|
||||||
.mag_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_v = pp::renderer::SamplerAddressMode::clamp_to_edge,
|
||||||
.address_w = 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();
|
const auto draw_status = context.draw();
|
||||||
context.end_render_pass();
|
context.end_render_pass();
|
||||||
|
|
||||||
@@ -2340,34 +2377,34 @@ int record_render(int argc, char** argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto readback_status = context.read_texture(
|
const auto readback_status = context.read_texture(
|
||||||
texture,
|
*texture.value(),
|
||||||
pp::renderer::ReadbackRegion {
|
pp::renderer::ReadbackRegion {
|
||||||
.x = 0,
|
.x = 0,
|
||||||
.y = 0,
|
.y = 0,
|
||||||
.width = args.width,
|
.width = args.width,
|
||||||
.height = args.height,
|
.height = args.height,
|
||||||
},
|
},
|
||||||
readback_buffer);
|
*readback_buffer.value());
|
||||||
if (!readback_status.ok()) {
|
if (!readback_status.ok()) {
|
||||||
print_error("record-render", readback_status.message);
|
print_error("record-render", readback_status.message);
|
||||||
return 2;
|
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()) {
|
if (!capture_status.ok()) {
|
||||||
print_error("record-render", capture_status.message);
|
print_error("record-render", capture_status.message);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto blit_status = context.blit_render_target(
|
const auto blit_status = context.blit_render_target(
|
||||||
target,
|
*target.value(),
|
||||||
pp::renderer::ReadbackRegion {
|
pp::renderer::ReadbackRegion {
|
||||||
.x = 0,
|
.x = 0,
|
||||||
.y = 0,
|
.y = 0,
|
||||||
.width = args.width,
|
.width = args.width,
|
||||||
.height = args.height,
|
.height = args.height,
|
||||||
},
|
},
|
||||||
blit_target,
|
*blit_target.value(),
|
||||||
pp::renderer::ReadbackRegion {
|
pp::renderer::ReadbackRegion {
|
||||||
.x = 0,
|
.x = 0,
|
||||||
.y = 0,
|
.y = 0,
|
||||||
@@ -2442,6 +2479,7 @@ int record_render(int argc, char** argv)
|
|||||||
<< ",\"target\":{\"width\":" << args.width
|
<< ",\"target\":{\"width\":" << args.width
|
||||||
<< ",\"height\":" << args.height
|
<< ",\"height\":" << args.height
|
||||||
<< ",\"format\":\"rgba8\"}"
|
<< ",\"format\":\"rgba8\"}"
|
||||||
|
<< ",\"createdResources\":" << created_resources
|
||||||
<< ",\"commands\":" << commands.size()
|
<< ",\"commands\":" << commands.size()
|
||||||
<< ",\"drawCommands\":" << draw_commands
|
<< ",\"drawCommands\":" << draw_commands
|
||||||
<< ",\"drawVertices\":" << draw_vertices
|
<< ",\"drawVertices\":" << draw_vertices
|
||||||
|
|||||||
Reference in New Issue
Block a user