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