Add renderer texture usage contract

This commit is contained in:
2026-06-02 16:42:53 +02:00
parent 75dd5cfdc9
commit 901aff1051
7 changed files with 345 additions and 68 deletions

View File

@@ -285,12 +285,12 @@ 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
backend-owned resource creation, command order, render-pass color/depth/
stencil clear intent, scissor state, depth state, blend state, texture-slot
binding, sampler-state binding, texture-upload byte counts,
shader-uniform writes, explicit draw descriptor ranges, texture-copy regions,
readback bounds, frame-capture sources, destination buffer sizes, and
render-target blit regions, records
backend-owned resource creation, explicit texture usage flags, command order,
render-pass color/depth/stencil clear intent, scissor state, depth state,
blend state, texture-slot binding, sampler-state binding, texture-upload byte
counts, shader-uniform writes, explicit draw descriptor ranges,
texture-copy regions, readback bounds, frame-capture sources, destination
buffer sizes, and render-target blit regions, records
render-pass-clear/scissor/depth/blend/shader-uniform/texture-bind/
sampler-bind/draw/upload/texture-copy/readback/frame-capture/blit commands,
draw mesh inputs, explicit draw ranges, and records trace markers without a

View File

@@ -414,14 +414,15 @@ Goal: make OpenGL an implementation detail and establish parity tests before
adding new backends.
Status: started. `pp_renderer_api` exists as a headless renderer-neutral target
with texture descriptor, byte-size, viewport, mesh, readback bounds, command
context, render device, shader program descriptor, mesh, render target,
readback byte-size helpers, texture-upload/readback command validation,
frame-capture byte-size helpers, frame-capture command validation,
render-target blit validation, texture-slot binding validation, blend-state
validation, scissor-state validation, depth-state validation, trace interface
validation, sampler-state validation, and the canonical PanoPainter shader
catalog now consumed by the legacy OpenGL app initialization path.
with explicit texture usage flags, texture descriptor, byte-size, viewport,
mesh, readback bounds, command context, render device, shader program
descriptor, mesh, render target, readback byte-size helpers,
texture-upload/readback command validation, frame-capture byte-size helpers,
frame-capture command validation, render-target blit validation, texture-slot
binding validation, blend-state validation, scissor-state validation,
depth-state validation, trace interface validation, sampler-state validation,
and the canonical PanoPainter shader catalog now consumed by the legacy
OpenGL app initialization path.
`pp_renderer_gl` now exists as the first OpenGL backend library and owns pure
OpenGL capability detection for framebuffer fetch, map-buffer alignment, and
float texture support. It also owns the OpenGL texture upload-type mapping used
@@ -720,13 +721,14 @@ Results:
per-layer frame duration, and PNG-encoded face-payload export to PPI bytes,
plus malformed payload rejection at the export boundary.
- `pp_renderer_api_tests` passed, including shader descriptor validation,
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,
render-pass color/depth/stencil clear validation, shader-uniform write
validation, draw descriptor/range validation, backend-neutral resource
factory validation, texture-copy validation, recording
PanoPainter shader catalog validation, explicit texture usage 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, render-pass color/depth/stencil clear
validation, shader-uniform write validation, draw descriptor/range
validation, backend-neutral resource factory validation, texture-copy
validation, recording
render-pass clear/scissor/depth/blend/shader-uniform/texture/sampler-bind/
upload/texture-copy/readback/frame-capture/blit command capture, draw
mesh-input capture, explicit draw-range capture, and invalid catalog
@@ -826,10 +828,11 @@ Results:
- `pp_renderer_api` now includes a headless `RecordingRenderDevice` with strict
renderer-owned resource factory and
command-order/render-pass-clear/scissor-state/depth-state/blend-state/
texture-bind/sampler-bind/shader-uniform/texture-upload/readback/
frame-capture/blit validation plus explicit draw descriptor and texture-copy
validation; it creates validated textures, render targets, shaders, meshes,
and readback buffers, then records commands, trace markers, render-pass
texture-usage/texture-bind/sampler-bind/shader-uniform/texture-upload/
readback/frame-capture/blit validation plus explicit draw descriptor and
texture-copy validation; it creates validated textures, render targets,
shaders, meshes, and readback buffers, then records commands, trace markers,
render-pass
color/depth/stencil clear intent, scissor state, depth state, blend state,
shader uniform writes, texture/sampler binds, draw mesh inputs, explicit draw
ranges, texture uploads/copies/readbacks, frame captures, and render-target

View File

@@ -101,8 +101,8 @@ pp::foundation::Status RecordingCommandContext::begin_render_pass(
}
active_target_ = target.color_desc();
if (!active_target_.render_target) {
return pp::foundation::Status::invalid_argument("render target texture must be flagged as render_target");
if (!has_texture_usage(active_target_.usage, TextureUsage::render_target)) {
return pp::foundation::Status::invalid_argument("render target texture must allow render_target usage");
}
const auto size_status = texture_byte_size(active_target_);
@@ -256,6 +256,10 @@ pp::foundation::Status RecordingCommandContext::bind_texture(
}
const auto desc = texture.desc();
if (!has_texture_usage(desc.usage, TextureUsage::sampled)) {
return pp::foundation::Status::invalid_argument("bound texture must allow sampled usage");
}
const auto size_status = texture_byte_size(desc);
if (!size_status.ok()) {
return size_status.status();
@@ -351,6 +355,10 @@ pp::foundation::Status RecordingCommandContext::read_texture(
}
const auto desc = texture.desc();
if (!has_texture_usage(desc.usage, TextureUsage::readback_source)) {
return pp::foundation::Status::invalid_argument("readback texture must allow readback_source usage");
}
const auto bytes = readback_byte_size(desc, region);
if (!bytes) {
return bytes.status();
@@ -379,6 +387,10 @@ pp::foundation::Status RecordingCommandContext::upload_texture(
}
const auto desc = texture.desc();
if (!has_texture_usage(desc.usage, TextureUsage::upload_destination)) {
return pp::foundation::Status::invalid_argument("texture upload destination must allow upload_destination usage");
}
const auto bytes = readback_byte_size(desc, region);
if (!bytes) {
return bytes.status();
@@ -560,6 +572,11 @@ const char* RecordingRenderDevice::backend_name() const noexcept
pp::foundation::Result<std::unique_ptr<ITexture2D>> RecordingRenderDevice::create_texture(
TextureDesc desc) noexcept
{
const auto desc_status = validate_texture_desc(desc);
if (!desc_status.ok()) {
return pp::foundation::Result<std::unique_ptr<ITexture2D>>::failure(desc_status);
}
const auto bytes = texture_byte_size(desc);
if (!bytes.ok()) {
return pp::foundation::Result<std::unique_ptr<ITexture2D>>::failure(bytes.status());
@@ -571,9 +588,14 @@ pp::foundation::Result<std::unique_ptr<ITexture2D>> RecordingRenderDevice::creat
pp::foundation::Result<std::unique_ptr<IRenderTarget>> RecordingRenderDevice::create_render_target(
TextureDesc color_desc) noexcept
{
if (!color_desc.render_target) {
if (!has_texture_usage(color_desc.usage, TextureUsage::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"));
pp::foundation::Status::invalid_argument("render target texture must allow render_target usage"));
}
const auto desc_status = validate_texture_desc(color_desc);
if (!desc_status.ok()) {
return pp::foundation::Result<std::unique_ptr<IRenderTarget>>::failure(desc_status);
}
const auto bytes = texture_byte_size(color_desc);

View File

@@ -47,6 +47,13 @@ std::uint32_t bytes_per_pixel(TextureFormat format) noexcept
return 0;
}
bool has_texture_usage(TextureUsage usage, TextureUsage required) noexcept
{
const auto usage_bits = static_cast<std::uint32_t>(usage);
const auto required_bits = static_cast<std::uint32_t>(required);
return required_bits != 0U && (usage_bits & required_bits) == required_bits;
}
pp::foundation::Status validate_extent(Extent2D extent) noexcept
{
if (extent.width == 0 || extent.height == 0) {
@@ -60,19 +67,50 @@ pp::foundation::Status validate_extent(Extent2D extent) noexcept
return pp::foundation::Status::success();
}
pp::foundation::Result<std::uint64_t> texture_byte_size(TextureDesc desc) noexcept
pp::foundation::Status validate_texture_usage(TextureUsage usage) noexcept
{
constexpr auto allowed_usage = TextureUsage::sampled
| TextureUsage::render_target
| TextureUsage::upload_destination
| TextureUsage::readback_source
| TextureUsage::copy_source
| TextureUsage::copy_destination;
const auto usage_bits = static_cast<std::uint32_t>(usage);
const auto allowed_bits = static_cast<std::uint32_t>(allowed_usage);
if (usage_bits == 0U) {
return pp::foundation::Status::invalid_argument("texture usage must not be empty");
}
if ((usage_bits & ~allowed_bits) != 0U) {
return pp::foundation::Status::invalid_argument("texture usage contains unsupported flags");
}
return pp::foundation::Status::success();
}
pp::foundation::Status validate_texture_desc(TextureDesc desc) noexcept
{
const auto extent_status = validate_extent(desc.extent);
if (!extent_status.ok()) {
return pp::foundation::Result<std::uint64_t>::failure(extent_status);
return extent_status;
}
if (bytes_per_pixel(desc.format) == 0U) {
return pp::foundation::Status::invalid_argument("texture format is not supported");
}
return validate_texture_usage(desc.usage);
}
pp::foundation::Result<std::uint64_t> texture_byte_size(TextureDesc desc) noexcept
{
const auto desc_status = validate_texture_desc(desc);
if (!desc_status.ok()) {
return pp::foundation::Result<std::uint64_t>::failure(desc_status);
}
const auto bpp = static_cast<std::uint64_t>(bytes_per_pixel(desc.format));
if (bpp == 0) {
return pp::foundation::Result<std::uint64_t>::failure(
pp::foundation::Status::invalid_argument("texture format is not supported"));
}
const auto width = static_cast<std::uint64_t>(desc.extent.width);
const auto height = static_cast<std::uint64_t>(desc.extent.height);
if (width > std::numeric_limits<std::uint64_t>::max() / height) {
@@ -481,11 +519,16 @@ pp::foundation::Result<std::uint64_t> readback_byte_size(TextureDesc desc, Readb
pp::foundation::Result<std::uint64_t> frame_capture_byte_size(TextureDesc desc) noexcept
{
if (!desc.render_target) {
if (!has_texture_usage(desc.usage, TextureUsage::render_target)) {
return pp::foundation::Result<std::uint64_t>::failure(
pp::foundation::Status::invalid_argument("frame capture source must be a render target"));
}
if (!has_texture_usage(desc.usage, TextureUsage::readback_source)) {
return pp::foundation::Result<std::uint64_t>::failure(
pp::foundation::Status::invalid_argument("frame capture source must allow readback"));
}
return texture_byte_size(desc);
}
@@ -495,6 +538,14 @@ pp::foundation::Status validate_texture_copy_descs(
TextureDesc destination,
ReadbackRegion destination_region) noexcept
{
if (!has_texture_usage(source.usage, TextureUsage::copy_source)) {
return pp::foundation::Status::invalid_argument("texture copy source must allow copy_source usage");
}
if (!has_texture_usage(destination.usage, TextureUsage::copy_destination)) {
return pp::foundation::Status::invalid_argument("texture copy destination must allow copy_destination usage");
}
if (source.format != destination.format) {
return pp::foundation::Status::invalid_argument("texture copy endpoints must use matching formats");
}
@@ -524,10 +575,19 @@ pp::foundation::Status validate_blit_filter(BlitFilter filter) noexcept
pp::foundation::Status validate_blit_descs(TextureDesc source, TextureDesc destination) noexcept
{
if (!source.render_target || !destination.render_target) {
if (!has_texture_usage(source.usage, TextureUsage::render_target)
|| !has_texture_usage(destination.usage, TextureUsage::render_target)) {
return pp::foundation::Status::invalid_argument("blit endpoints must be render targets");
}
if (!has_texture_usage(source.usage, TextureUsage::copy_source)) {
return pp::foundation::Status::invalid_argument("blit source must allow copy_source usage");
}
if (!has_texture_usage(destination.usage, TextureUsage::copy_destination)) {
return pp::foundation::Status::invalid_argument("blit destination must allow copy_destination usage");
}
if (source.format != destination.format) {
return pp::foundation::Status::invalid_argument("blit endpoints must use matching texture formats");
}

View File

@@ -22,6 +22,34 @@ enum class TextureFormat : std::uint8_t {
depth24_stencil8,
};
enum class TextureUsage : std::uint32_t {
none = 0,
sampled = 1U << 0U,
render_target = 1U << 1U,
upload_destination = 1U << 2U,
readback_source = 1U << 3U,
copy_source = 1U << 4U,
copy_destination = 1U << 5U,
};
[[nodiscard]] constexpr TextureUsage operator|(TextureUsage lhs, TextureUsage rhs) noexcept
{
return static_cast<TextureUsage>(
static_cast<std::uint32_t>(lhs) | static_cast<std::uint32_t>(rhs));
}
[[nodiscard]] constexpr TextureUsage operator&(TextureUsage lhs, TextureUsage rhs) noexcept
{
return static_cast<TextureUsage>(
static_cast<std::uint32_t>(lhs) & static_cast<std::uint32_t>(rhs));
}
constexpr TextureUsage& operator|=(TextureUsage& lhs, TextureUsage rhs) noexcept
{
lhs = lhs | rhs;
return lhs;
}
struct Extent2D {
std::uint32_t width = 0;
std::uint32_t height = 0;
@@ -30,7 +58,11 @@ struct Extent2D {
struct TextureDesc {
Extent2D extent;
TextureFormat format = TextureFormat::rgba8;
bool render_target = false;
TextureUsage usage = TextureUsage::sampled
| TextureUsage::upload_destination
| TextureUsage::readback_source
| TextureUsage::copy_source
| TextureUsage::copy_destination;
};
struct ReadbackRegion {
@@ -279,7 +311,10 @@ public:
};
[[nodiscard]] std::uint32_t bytes_per_pixel(TextureFormat format) noexcept;
[[nodiscard]] bool has_texture_usage(TextureUsage usage, TextureUsage required) noexcept;
[[nodiscard]] pp::foundation::Status validate_extent(Extent2D extent) noexcept;
[[nodiscard]] pp::foundation::Status validate_texture_usage(TextureUsage usage) noexcept;
[[nodiscard]] pp::foundation::Status validate_texture_desc(TextureDesc desc) noexcept;
[[nodiscard]] pp::foundation::Status validate_viewport(Viewport viewport, Extent2D target_extent) noexcept;
[[nodiscard]] pp::foundation::Status validate_scissor(ScissorRect scissor, Extent2D target_extent) noexcept;
[[nodiscard]] pp::foundation::Status validate_render_pass_desc(RenderPassDesc desc) noexcept;

View File

@@ -26,6 +26,7 @@ using pp::renderer::DepthState;
using pp::renderer::DrawDesc;
using pp::renderer::Extent2D;
using pp::renderer::frame_capture_byte_size;
using pp::renderer::has_texture_usage;
using pp::renderer::ICommandContext;
using pp::renderer::IMesh;
using pp::renderer::IRenderDevice;
@@ -53,6 +54,7 @@ using pp::renderer::ShaderProgramDesc;
using pp::renderer::ShaderStageSource;
using pp::renderer::TextureDesc;
using pp::renderer::TextureFormat;
using pp::renderer::TextureUsage;
using pp::renderer::Viewport;
using pp::renderer::max_shader_source_bytes;
using pp::renderer::max_shader_uniform_bytes;
@@ -85,7 +87,9 @@ using pp::renderer::validate_shader_catalog;
using pp::renderer::validate_shader_program_desc;
using pp::renderer::validate_shader_uniform_write;
using pp::renderer::validate_texture_copy_descs;
using pp::renderer::validate_texture_desc;
using pp::renderer::validate_texture_slot;
using pp::renderer::validate_texture_usage;
using pp::renderer::validate_viewport;
namespace {
@@ -95,7 +99,7 @@ public:
explicit FakeRenderTarget(TextureDesc desc = TextureDesc {
.extent = Extent2D { .width = 64, .height = 32 },
.format = TextureFormat::rgba8,
.render_target = true,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
}) noexcept
: desc_(desc)
{
@@ -151,7 +155,7 @@ public:
explicit FakeTexture(TextureDesc desc = TextureDesc {
.extent = Extent2D { .width = 64, .height = 32 },
.format = TextureFormat::rgba8,
.render_target = true,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
}) noexcept
: desc_(desc)
{
@@ -204,6 +208,9 @@ public:
if (!render_pass_status.ok()) {
return render_pass_status;
}
if (!has_texture_usage(target.color_desc().usage, TextureUsage::render_target)) {
return pp::foundation::Status::invalid_argument("render target texture must allow render_target usage");
}
in_render_pass = true;
last_render_pass_desc = desc;
return validate_extent(target.color_desc().extent);
@@ -291,6 +298,9 @@ public:
if (!slot_status.ok()) {
return slot_status;
}
if (!has_texture_usage(texture.desc().usage, TextureUsage::sampled)) {
return pp::foundation::Status::invalid_argument("bound texture must allow sampled usage");
}
const auto bytes = texture_byte_size(texture.desc());
if (!bytes) {
return bytes.status();
@@ -354,6 +364,9 @@ public:
ReadbackRegion region,
pp::renderer::IReadbackBuffer& destination) noexcept override
{
if (!has_texture_usage(texture.desc().usage, TextureUsage::readback_source)) {
return pp::foundation::Status::invalid_argument("readback texture must allow readback_source usage");
}
const auto bytes = readback_byte_size(texture.desc(), region);
if (!bytes) {
return bytes.status();
@@ -370,6 +383,9 @@ public:
ReadbackRegion region,
std::span<const std::byte> rgba_or_channel_bytes) noexcept override
{
if (!has_texture_usage(texture.desc().usage, TextureUsage::upload_destination)) {
return pp::foundation::Status::invalid_argument("texture upload destination must allow upload_destination usage");
}
const auto bytes = readback_byte_size(texture.desc(), region);
if (!bytes) {
return bytes.status();
@@ -511,9 +527,9 @@ public:
[[nodiscard]] pp::foundation::Result<std::unique_ptr<IRenderTarget>> create_render_target(
TextureDesc color_desc) noexcept override
{
if (!color_desc.render_target) {
if (!has_texture_usage(color_desc.usage, TextureUsage::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"));
pp::foundation::Status::invalid_argument("render target texture must allow render_target usage"));
}
const auto bytes = texture_byte_size(color_desc);
@@ -590,6 +606,13 @@ private:
}
};
constexpr TextureUsage all_texture_usages = TextureUsage::sampled
| TextureUsage::render_target
| TextureUsage::upload_destination
| TextureUsage::readback_source
| TextureUsage::copy_source
| TextureUsage::copy_destination;
void computes_texture_sizes(pp::tests::Harness& h)
{
const auto rgba = texture_byte_size(TextureDesc {
@@ -608,6 +631,51 @@ void computes_texture_sizes(pp::tests::Harness& h)
PP_EXPECT(h, texture_format_name(TextureFormat::depth24_stencil8) == std::string_view("depth24_stencil8"));
}
void validates_texture_usage_contract(pp::tests::Harness& h)
{
const TextureDesc sampled_desc {
.extent = Extent2D { .width = 4, .height = 4 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::sampled,
};
const TextureDesc all_usage_desc {
.extent = Extent2D { .width = 4, .height = 4 },
.format = TextureFormat::rgba8,
.usage = all_texture_usages,
};
const TextureDesc empty_usage_desc {
.extent = Extent2D { .width = 4, .height = 4 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::none,
};
const TextureDesc unknown_usage_desc {
.extent = Extent2D { .width = 4, .height = 4 },
.format = TextureFormat::rgba8,
.usage = static_cast<TextureUsage>(1U << 31U),
};
const TextureDesc unknown_format_desc {
.extent = Extent2D { .width = 4, .height = 4 },
.format = static_cast<TextureFormat>(255),
};
PP_EXPECT(h, has_texture_usage(all_texture_usages, TextureUsage::render_target));
PP_EXPECT(h, !has_texture_usage(TextureUsage::sampled, TextureUsage::render_target));
PP_EXPECT(h, validate_texture_usage(TextureUsage::sampled | TextureUsage::copy_source).ok());
PP_EXPECT(h, validate_texture_desc(sampled_desc).ok());
PP_EXPECT(h, validate_texture_desc(all_usage_desc).ok());
const auto empty_usage = validate_texture_desc(empty_usage_desc);
const auto unknown_usage = validate_texture_desc(unknown_usage_desc);
const auto unknown_format = validate_texture_desc(unknown_format_desc);
PP_EXPECT(h, !empty_usage.ok());
PP_EXPECT(h, empty_usage.code == StatusCode::invalid_argument);
PP_EXPECT(h, !unknown_usage.ok());
PP_EXPECT(h, unknown_usage.code == StatusCode::invalid_argument);
PP_EXPECT(h, !unknown_format.ok());
PP_EXPECT(h, unknown_format.code == StatusCode::invalid_argument);
}
void rejects_invalid_or_excessive_extents(pp::tests::Harness& h)
{
const auto zero = validate_extent(Extent2D { .width = 0, .height = 1 });
@@ -630,7 +698,7 @@ void validates_readback_bounds(pp::tests::Harness& h)
const TextureDesc desc {
.extent = Extent2D { .width = 64, .height = 32 },
.format = TextureFormat::rgba8,
.render_target = true,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
};
PP_EXPECT(h, validate_readback_region(desc, ReadbackRegion { .x = 0, .y = 0, .width = 64, .height = 32 }).ok());
@@ -653,12 +721,12 @@ void computes_readback_byte_sizes(pp::tests::Harness& h)
const TextureDesc rgba_desc {
.extent = Extent2D { .width = 64, .height = 32 },
.format = TextureFormat::rgba8,
.render_target = true,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
};
const TextureDesc r8_desc {
.extent = Extent2D { .width = 64, .height = 32 },
.format = TextureFormat::r8,
.render_target = true,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
};
const auto rgba = readback_byte_size(rgba_desc, ReadbackRegion { .x = 4, .y = 2, .width = 8, .height = 3 });
@@ -678,12 +746,12 @@ void computes_frame_capture_byte_sizes(pp::tests::Harness& h)
const TextureDesc target_desc {
.extent = Extent2D { .width = 16, .height = 8 },
.format = TextureFormat::rgba8,
.render_target = true,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
};
const TextureDesc texture_desc {
.extent = Extent2D { .width = 16, .height = 8 },
.format = TextureFormat::rgba8,
.render_target = false,
.usage = TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
};
const auto capture = frame_capture_byte_size(target_desc);
@@ -700,17 +768,17 @@ void validates_blit_contract(pp::tests::Harness& h)
const TextureDesc target_desc {
.extent = Extent2D { .width = 16, .height = 8 },
.format = TextureFormat::rgba8,
.render_target = true,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
};
const TextureDesc r8_target_desc {
.extent = Extent2D { .width = 16, .height = 8 },
.format = TextureFormat::r8,
.render_target = true,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
};
const TextureDesc texture_desc {
.extent = Extent2D { .width = 16, .height = 8 },
.format = TextureFormat::rgba8,
.render_target = false,
.usage = TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
};
PP_EXPECT(h, validate_blit_descs(target_desc, target_desc).ok());
@@ -1239,12 +1307,12 @@ void render_devices_create_validated_resources(pp::tests::Harness& h)
const auto texture = device.create_texture(TextureDesc {
.extent = Extent2D { .width = 8, .height = 4 },
.format = TextureFormat::rgba8,
.render_target = false,
.usage = TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
});
const auto target = device.create_render_target(TextureDesc {
.extent = Extent2D { .width = 8, .height = 4 },
.format = TextureFormat::rgba8,
.render_target = true,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
});
const auto shader = device.create_shader_program(ShaderProgramDesc {
.debug_name = "factory-shader",
@@ -1260,9 +1328,9 @@ void render_devices_create_validated_resources(pp::tests::Harness& h)
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, !has_texture_usage(texture.value()->desc().usage, TextureUsage::render_target));
PP_EXPECT(h, target.ok());
PP_EXPECT(h, target.value()->color_desc().render_target);
PP_EXPECT(h, has_texture_usage(target.value()->color_desc().usage, TextureUsage::render_target));
PP_EXPECT(h, shader.ok());
PP_EXPECT(h, shader.value()->debug_name() == std::string_view("factory-shader"));
PP_EXPECT(h, mesh.ok());
@@ -1277,7 +1345,7 @@ void render_devices_create_validated_resources(pp::tests::Harness& h)
const auto bad_target = device.create_render_target(TextureDesc {
.extent = Extent2D { .width = 8, .height = 4 },
.format = TextureFormat::rgba8,
.render_target = false,
.usage = TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
});
const auto bad_shader = device.create_shader_program(ShaderProgramDesc {
.debug_name = "bad-shader",
@@ -1305,7 +1373,7 @@ void recording_renderer_records_shader_uniform_writes(pp::tests::Harness& h)
RecordingRenderTarget target(TextureDesc {
.extent = Extent2D { .width = 16, .height = 8 },
.format = TextureFormat::rgba8,
.render_target = true,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
});
RecordingShaderProgram shader("uniform-shader");
const std::array<std::byte, 64> uniform_bytes {};
@@ -1348,19 +1416,19 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
RecordingTexture2D texture(TextureDesc {
.extent = Extent2D { .width = 64, .height = 32 },
.format = TextureFormat::rgba8,
.render_target = true,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
});
RecordingReadbackBuffer readback_buffer(64U * 32U * 4U);
const std::array<std::byte, 96> upload_bytes {};
RecordingRenderTarget target(TextureDesc {
.extent = Extent2D { .width = 64, .height = 32 },
.format = TextureFormat::rgba8,
.render_target = true,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
});
RecordingRenderTarget blit_target(TextureDesc {
.extent = Extent2D { .width = 64, .height = 32 },
.format = TextureFormat::rgba8,
.render_target = true,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
});
RecordingShaderProgram shader("recorded-shader");
RecordingMesh mesh(MeshDesc { .vertex_count = 3, .index_count = 3, .topology = PrimitiveTopology::triangles });
@@ -1540,22 +1608,57 @@ void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Har
RecordingRenderTarget target(TextureDesc {
.extent = Extent2D { .width = 32, .height = 16 },
.format = TextureFormat::rgba8,
.render_target = true,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
});
RecordingRenderTarget non_render_target(TextureDesc {
.extent = Extent2D { .width = 32, .height = 16 },
.format = TextureFormat::rgba8,
.render_target = false,
.usage = TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
});
RecordingRenderTarget r8_target(TextureDesc {
.extent = Extent2D { .width = 32, .height = 16 },
.format = TextureFormat::r8,
.render_target = true,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
});
RecordingTexture2D texture(TextureDesc {
.extent = Extent2D { .width = 32, .height = 16 },
.format = TextureFormat::rgba8,
.render_target = true,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
});
RecordingTexture2D texture_without_sampled(TextureDesc {
.extent = Extent2D { .width = 32, .height = 16 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
});
RecordingTexture2D texture_without_upload(TextureDesc {
.extent = Extent2D { .width = 32, .height = 16 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::sampled | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
});
RecordingTexture2D texture_without_readback(TextureDesc {
.extent = Extent2D { .width = 32, .height = 16 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::copy_source | TextureUsage::copy_destination,
});
RecordingTexture2D texture_without_copy_source(TextureDesc {
.extent = Extent2D { .width = 32, .height = 16 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_destination,
});
RecordingTexture2D texture_without_copy_destination(TextureDesc {
.extent = Extent2D { .width = 32, .height = 16 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source,
});
RecordingRenderTarget target_without_copy_source(TextureDesc {
.extent = Extent2D { .width = 32, .height = 16 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::render_target | TextureUsage::copy_destination | TextureUsage::readback_source,
});
RecordingRenderTarget target_without_copy_destination(TextureDesc {
.extent = Extent2D { .width = 32, .height = 16 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::render_target | TextureUsage::copy_source | TextureUsage::readback_source,
});
RecordingReadbackBuffer small_readback_buffer(3U);
RecordingReadbackBuffer full_readback_buffer(32U * 16U * 4U);
@@ -1692,6 +1795,11 @@ void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Har
const auto bind_texture_bad_slot = context.bind_texture(max_texture_slots, texture);
PP_EXPECT(h, !bind_texture_bad_slot.ok());
PP_EXPECT(h, bind_texture_bad_slot.code == StatusCode::out_of_range);
const auto bind_texture_without_sampled = context.bind_texture(0, texture_without_sampled);
PP_EXPECT(h, !bind_texture_without_sampled.ok());
PP_EXPECT(h, bind_texture_without_sampled.code == StatusCode::invalid_argument);
PP_EXPECT(h, context.bind_texture(0, texture).ok());
const auto bind_sampler_bad_slot = context.bind_sampler(max_texture_slots, SamplerDesc {});
@@ -1759,6 +1867,13 @@ void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Har
PP_EXPECT(h, !upload_outside_bounds.ok());
PP_EXPECT(h, upload_outside_bounds.code == StatusCode::out_of_range);
const auto upload_without_usage = context.upload_texture(
texture_without_upload,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 },
one_pixel_upload);
PP_EXPECT(h, !upload_without_usage.ok());
PP_EXPECT(h, upload_without_usage.code == StatusCode::invalid_argument);
const auto copy_mismatched_regions = context.copy_texture(
texture,
ReadbackRegion { .x = 0, .y = 0, .width = 2, .height = 1 },
@@ -1775,6 +1890,22 @@ void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Har
PP_EXPECT(h, !copy_outside_bounds.ok());
PP_EXPECT(h, copy_outside_bounds.code == StatusCode::out_of_range);
const auto copy_without_source_usage = context.copy_texture(
texture_without_copy_source,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 },
texture,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 });
PP_EXPECT(h, !copy_without_source_usage.ok());
PP_EXPECT(h, copy_without_source_usage.code == StatusCode::invalid_argument);
const auto copy_without_destination_usage = context.copy_texture(
texture,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 },
texture_without_copy_destination,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 });
PP_EXPECT(h, !copy_without_destination_usage.ok());
PP_EXPECT(h, copy_without_destination_usage.code == StatusCode::invalid_argument);
const auto upload_with_wrong_size = context.upload_texture(
texture,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 },
@@ -1782,6 +1913,13 @@ void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Har
PP_EXPECT(h, !upload_with_wrong_size.ok());
PP_EXPECT(h, upload_with_wrong_size.code == StatusCode::invalid_argument);
const auto read_without_usage = context.read_texture(
texture_without_readback,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 },
full_readback_buffer);
PP_EXPECT(h, !read_without_usage.ok());
PP_EXPECT(h, read_without_usage.code == StatusCode::invalid_argument);
const auto read_into_small_buffer = context.read_texture(
texture,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 },
@@ -1815,6 +1953,24 @@ void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Har
PP_EXPECT(h, !blit_mismatched_format.ok());
PP_EXPECT(h, blit_mismatched_format.code == StatusCode::invalid_argument);
const auto blit_without_source_usage = context.blit_render_target(
target_without_copy_source,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 },
target,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 },
BlitFilter::nearest);
PP_EXPECT(h, !blit_without_source_usage.ok());
PP_EXPECT(h, blit_without_source_usage.code == StatusCode::invalid_argument);
const auto blit_without_destination_usage = context.blit_render_target(
target,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 },
target_without_copy_destination,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 },
BlitFilter::nearest);
PP_EXPECT(h, !blit_without_destination_usage.ok());
PP_EXPECT(h, blit_without_destination_usage.code == StatusCode::invalid_argument);
const auto blit_outside_bounds = context.blit_render_target(
target,
ReadbackRegion { .x = 31, .y = 15, .width = 2, .height = 1 },
@@ -1840,6 +1996,7 @@ int main()
{
pp::tests::Harness harness;
harness.run("computes_texture_sizes", computes_texture_sizes);
harness.run("validates_texture_usage_contract", validates_texture_usage_contract);
harness.run("rejects_invalid_or_excessive_extents", rejects_invalid_or_excessive_extents);
harness.run("validates_readback_bounds", validates_readback_bounds);
harness.run("computes_readback_byte_sizes", computes_readback_byte_sizes);

View File

@@ -1642,7 +1642,7 @@ pp::foundation::Status parse_simulate_document_edits_args(
const auto render_target_size = pp::renderer::texture_byte_size(pp::renderer::TextureDesc {
.extent = pp::renderer::Extent2D { .width = args.width, .height = args.height },
.format = pp::renderer::TextureFormat::rgba8,
.render_target = true,
.usage = pp::renderer::TextureUsage::render_target | pp::renderer::TextureUsage::sampled | pp::renderer::TextureUsage::upload_destination | pp::renderer::TextureUsage::readback_source | pp::renderer::TextureUsage::copy_source | pp::renderer::TextureUsage::copy_destination,
});
if (!render_target_size) {
return render_target_size.status();
@@ -2214,17 +2214,17 @@ int record_render(int argc, char** argv)
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,
.usage = pp::renderer::TextureUsage::render_target | pp::renderer::TextureUsage::sampled | pp::renderer::TextureUsage::upload_destination | pp::renderer::TextureUsage::readback_source | pp::renderer::TextureUsage::copy_source | pp::renderer::TextureUsage::copy_destination,
});
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,
.usage = pp::renderer::TextureUsage::render_target | pp::renderer::TextureUsage::sampled | pp::renderer::TextureUsage::upload_destination | pp::renderer::TextureUsage::readback_source | pp::renderer::TextureUsage::copy_source | pp::renderer::TextureUsage::copy_destination,
});
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,
.usage = pp::renderer::TextureUsage::render_target | pp::renderer::TextureUsage::sampled | pp::renderer::TextureUsage::upload_destination | pp::renderer::TextureUsage::readback_source | pp::renderer::TextureUsage::copy_source | pp::renderer::TextureUsage::copy_destination,
});
const auto readback_buffer = device.create_readback_buffer(
static_cast<std::uint64_t>(args.width) * args.height * 4U);