Add renderer mipmap command contract
This commit is contained in:
@@ -288,13 +288,14 @@ Known local toolchain state:
|
||||
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
|
||||
counts, texture mip-level counts, mipmap-generation commands,
|
||||
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
|
||||
window or GL context.
|
||||
sampler-bind/draw/upload/mipmap-generation/texture-copy/readback/
|
||||
frame-capture/blit commands, draw mesh inputs, explicit draw ranges, and
|
||||
records trace markers without a window or GL context.
|
||||
- `pano_cli record-render` exposes the recording renderer through JSON
|
||||
automation, including render-pass/depth-clear counts, scissor/depth/blend/
|
||||
shader-uniform/texture-bind/sampler-bind/upload/texture-copy/readback/
|
||||
|
||||
@@ -417,12 +417,13 @@ Status: started. `pp_renderer_api` exists as a headless renderer-neutral target
|
||||
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,
|
||||
texture mip-level validation, texture-upload/readback command validation,
|
||||
mipmap-generation 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.
|
||||
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
|
||||
@@ -722,7 +723,8 @@ Results:
|
||||
plus malformed payload rejection at the export boundary.
|
||||
- `pp_renderer_api_tests` passed, including shader descriptor validation,
|
||||
PanoPainter shader catalog validation, explicit texture usage validation,
|
||||
readback byte-size and command-order validation, texture-upload byte-count
|
||||
texture mip-level validation, readback byte-size and command-order
|
||||
validation, texture-upload byte-count validation, mipmap-generation command
|
||||
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
|
||||
@@ -829,15 +831,15 @@ Results:
|
||||
renderer-owned resource factory and
|
||||
command-order/render-pass-clear/scissor-state/depth-state/blend-state/
|
||||
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
|
||||
mipmap-generation/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
|
||||
blits, giving automation a backend-neutral render path that does not require
|
||||
a window or GL context.
|
||||
ranges, texture uploads/mipmap generations/copies/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, resource creation counts, target dimensions, backend
|
||||
name, trace/draw summary, render-pass/depth-clear counts, and draw
|
||||
|
||||
@@ -409,6 +409,32 @@ pp::foundation::Status RecordingCommandContext::upload_texture(
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Status RecordingCommandContext::generate_mipmaps(ITexture2D& texture) noexcept
|
||||
{
|
||||
if (in_render_pass_) {
|
||||
return pp::foundation::Status::invalid_argument("mipmap generation must be outside a render pass");
|
||||
}
|
||||
|
||||
const auto desc = texture.desc();
|
||||
const auto desc_status = validate_mipmap_generation_desc(desc);
|
||||
if (!desc_status.ok()) {
|
||||
return desc_status;
|
||||
}
|
||||
|
||||
const auto bytes = texture_byte_size(desc);
|
||||
if (!bytes.ok()) {
|
||||
return bytes.status();
|
||||
}
|
||||
|
||||
push_command(commands_, RecordedRenderCommand {
|
||||
.kind = RecordedRenderCommandKind::generate_mipmaps,
|
||||
.texture_desc = desc,
|
||||
.generated_mip_levels = desc.mip_levels,
|
||||
.generated_mip_bytes = bytes.value(),
|
||||
});
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Status RecordingCommandContext::copy_texture(
|
||||
ITexture2D& source,
|
||||
ReadbackRegion source_region,
|
||||
@@ -691,6 +717,8 @@ const char* recorded_render_command_kind_name(RecordedRenderCommandKind kind) no
|
||||
return "draw";
|
||||
case RecordedRenderCommandKind::upload_texture:
|
||||
return "upload_texture";
|
||||
case RecordedRenderCommandKind::generate_mipmaps:
|
||||
return "generate_mipmaps";
|
||||
case RecordedRenderCommandKind::copy_texture:
|
||||
return "copy_texture";
|
||||
case RecordedRenderCommandKind::read_texture:
|
||||
|
||||
@@ -20,6 +20,7 @@ enum class RecordedRenderCommandKind : std::uint8_t {
|
||||
bind_mesh,
|
||||
draw,
|
||||
upload_texture,
|
||||
generate_mipmaps,
|
||||
copy_texture,
|
||||
read_texture,
|
||||
capture_frame,
|
||||
@@ -54,6 +55,8 @@ struct RecordedRenderCommand {
|
||||
ReadbackRegion destination_region {};
|
||||
BlitFilter blit_filter = BlitFilter::nearest;
|
||||
std::uint64_t upload_bytes = 0;
|
||||
std::uint32_t generated_mip_levels = 0;
|
||||
std::uint64_t generated_mip_bytes = 0;
|
||||
std::uint64_t copy_source_bytes = 0;
|
||||
std::uint64_t copy_destination_bytes = 0;
|
||||
std::uint64_t readback_bytes = 0;
|
||||
@@ -141,6 +144,8 @@ public:
|
||||
ITexture2D& texture,
|
||||
ReadbackRegion region,
|
||||
std::span<const std::byte> rgba_or_channel_bytes) noexcept override;
|
||||
[[nodiscard]] pp::foundation::Status generate_mipmaps(
|
||||
ITexture2D& texture) noexcept override;
|
||||
[[nodiscard]] pp::foundation::Status copy_texture(
|
||||
ITexture2D& source,
|
||||
ReadbackRegion source_region,
|
||||
|
||||
@@ -31,6 +31,18 @@ namespace {
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] Extent2D mip_level_extent(Extent2D extent, std::uint32_t level) noexcept
|
||||
{
|
||||
auto width = extent.width;
|
||||
auto height = extent.height;
|
||||
for (std::uint32_t index = 0; index < level; ++index) {
|
||||
width = width > 1U ? width / 2U : 1U;
|
||||
height = height > 1U ? height / 2U : 1U;
|
||||
}
|
||||
|
||||
return Extent2D { .width = width, .height = height };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::uint32_t bytes_per_pixel(TextureFormat format) noexcept
|
||||
@@ -47,6 +59,22 @@ std::uint32_t bytes_per_pixel(TextureFormat format) noexcept
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::uint32_t max_mip_levels_for_extent(Extent2D extent) noexcept
|
||||
{
|
||||
if (extent.width == 0U || extent.height == 0U) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto dimension = extent.width > extent.height ? extent.width : extent.height;
|
||||
std::uint32_t levels = 1;
|
||||
while (dimension > 1U && levels < max_texture_mip_levels) {
|
||||
dimension /= 2U;
|
||||
++levels;
|
||||
}
|
||||
|
||||
return levels;
|
||||
}
|
||||
|
||||
bool has_texture_usage(TextureUsage usage, TextureUsage required) noexcept
|
||||
{
|
||||
const auto usage_bits = static_cast<std::uint32_t>(usage);
|
||||
@@ -100,6 +128,14 @@ pp::foundation::Status validate_texture_desc(TextureDesc desc) noexcept
|
||||
return pp::foundation::Status::invalid_argument("texture format is not supported");
|
||||
}
|
||||
|
||||
if (desc.mip_levels == 0U) {
|
||||
return pp::foundation::Status::invalid_argument("texture mip level count must be greater than zero");
|
||||
}
|
||||
|
||||
if (desc.mip_levels > max_mip_levels_for_extent(desc.extent)) {
|
||||
return pp::foundation::Status::out_of_range("texture mip level count exceeds the texture extent");
|
||||
}
|
||||
|
||||
return validate_texture_usage(desc.usage);
|
||||
}
|
||||
|
||||
@@ -111,20 +147,30 @@ pp::foundation::Result<std::uint64_t> texture_byte_size(TextureDesc desc) noexce
|
||||
}
|
||||
|
||||
const auto bpp = static_cast<std::uint64_t>(bytes_per_pixel(desc.format));
|
||||
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) {
|
||||
return pp::foundation::Result<std::uint64_t>::failure(
|
||||
pp::foundation::Status::out_of_range("texture size overflows uint64"));
|
||||
std::uint64_t bytes = 0;
|
||||
for (std::uint32_t level = 0; level < desc.mip_levels; ++level) {
|
||||
const auto level_extent = mip_level_extent(desc.extent, level);
|
||||
const auto width = static_cast<std::uint64_t>(level_extent.width);
|
||||
const auto height = static_cast<std::uint64_t>(level_extent.height);
|
||||
if (width > std::numeric_limits<std::uint64_t>::max() / height) {
|
||||
return pp::foundation::Result<std::uint64_t>::failure(
|
||||
pp::foundation::Status::out_of_range("texture size overflows uint64"));
|
||||
}
|
||||
|
||||
const auto pixels = width * height;
|
||||
if (pixels > std::numeric_limits<std::uint64_t>::max() / bpp) {
|
||||
return pp::foundation::Result<std::uint64_t>::failure(
|
||||
pp::foundation::Status::out_of_range("texture byte size overflows uint64"));
|
||||
}
|
||||
|
||||
const auto level_bytes = pixels * bpp;
|
||||
if (bytes > std::numeric_limits<std::uint64_t>::max() - level_bytes) {
|
||||
return pp::foundation::Result<std::uint64_t>::failure(
|
||||
pp::foundation::Status::out_of_range("texture byte size overflows uint64"));
|
||||
}
|
||||
bytes += level_bytes;
|
||||
}
|
||||
|
||||
const auto pixels = width * height;
|
||||
if (pixels > std::numeric_limits<std::uint64_t>::max() / bpp) {
|
||||
return pp::foundation::Result<std::uint64_t>::failure(
|
||||
pp::foundation::Status::out_of_range("texture byte size overflows uint64"));
|
||||
}
|
||||
|
||||
const auto bytes = pixels * bpp;
|
||||
if (bytes > max_texture_bytes) {
|
||||
return pp::foundation::Result<std::uint64_t>::failure(
|
||||
pp::foundation::Status::out_of_range("texture byte size exceeds the configured limit"));
|
||||
@@ -562,6 +608,32 @@ pp::foundation::Status validate_texture_copy_descs(
|
||||
return validate_readback_region(destination, destination_region);
|
||||
}
|
||||
|
||||
pp::foundation::Status validate_mipmap_generation_desc(TextureDesc desc) noexcept
|
||||
{
|
||||
const auto desc_status = validate_texture_desc(desc);
|
||||
if (!desc_status.ok()) {
|
||||
return desc_status;
|
||||
}
|
||||
|
||||
if (desc.mip_levels <= 1U) {
|
||||
return pp::foundation::Status::invalid_argument("mipmap generation requires more than one mip level");
|
||||
}
|
||||
|
||||
if (!has_texture_usage(desc.usage, TextureUsage::sampled)) {
|
||||
return pp::foundation::Status::invalid_argument("mipmap texture must allow sampled usage");
|
||||
}
|
||||
|
||||
if (!has_texture_usage(desc.usage, TextureUsage::copy_source)) {
|
||||
return pp::foundation::Status::invalid_argument("mipmap texture must allow copy_source usage");
|
||||
}
|
||||
|
||||
if (!has_texture_usage(desc.usage, TextureUsage::copy_destination)) {
|
||||
return pp::foundation::Status::invalid_argument("mipmap texture must allow copy_destination usage");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Status validate_blit_filter(BlitFilter filter) noexcept
|
||||
{
|
||||
switch (filter) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace pp::renderer {
|
||||
|
||||
constexpr std::uint32_t max_texture_dimension = 32768;
|
||||
constexpr std::uint32_t max_texture_mip_levels = 16;
|
||||
constexpr std::uint32_t max_mesh_vertices = 16777216;
|
||||
constexpr std::uint32_t max_texture_slots = 32;
|
||||
constexpr std::uint64_t max_texture_bytes = 1024ULL * 1024ULL * 1024ULL;
|
||||
@@ -58,6 +59,7 @@ struct Extent2D {
|
||||
struct TextureDesc {
|
||||
Extent2D extent;
|
||||
TextureFormat format = TextureFormat::rgba8;
|
||||
std::uint32_t mip_levels = 1;
|
||||
TextureUsage usage = TextureUsage::sampled
|
||||
| TextureUsage::upload_destination
|
||||
| TextureUsage::readback_source
|
||||
@@ -275,6 +277,8 @@ public:
|
||||
ITexture2D& texture,
|
||||
ReadbackRegion region,
|
||||
std::span<const std::byte> rgba_or_channel_bytes) noexcept = 0;
|
||||
[[nodiscard]] virtual pp::foundation::Status generate_mipmaps(
|
||||
ITexture2D& texture) noexcept = 0;
|
||||
[[nodiscard]] virtual pp::foundation::Status copy_texture(
|
||||
ITexture2D& source,
|
||||
ReadbackRegion source_region,
|
||||
@@ -311,6 +315,7 @@ public:
|
||||
};
|
||||
|
||||
[[nodiscard]] std::uint32_t bytes_per_pixel(TextureFormat format) noexcept;
|
||||
[[nodiscard]] std::uint32_t max_mip_levels_for_extent(Extent2D extent) 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;
|
||||
@@ -344,6 +349,7 @@ public:
|
||||
ReadbackRegion source_region,
|
||||
TextureDesc destination,
|
||||
ReadbackRegion destination_region) noexcept;
|
||||
[[nodiscard]] pp::foundation::Status validate_mipmap_generation_desc(TextureDesc desc) noexcept;
|
||||
[[nodiscard]] pp::foundation::Status validate_blit_filter(BlitFilter filter) noexcept;
|
||||
[[nodiscard]] pp::foundation::Status validate_blit_descs(
|
||||
TextureDesc source,
|
||||
|
||||
@@ -58,6 +58,7 @@ using pp::renderer::TextureUsage;
|
||||
using pp::renderer::Viewport;
|
||||
using pp::renderer::max_shader_source_bytes;
|
||||
using pp::renderer::max_shader_uniform_bytes;
|
||||
using pp::renderer::max_mip_levels_for_extent;
|
||||
using pp::renderer::max_texture_dimension;
|
||||
using pp::renderer::max_texture_slots;
|
||||
using pp::renderer::panopainter_shader_catalog;
|
||||
@@ -77,6 +78,7 @@ using pp::renderer::validate_compare_op;
|
||||
using pp::renderer::validate_depth_state;
|
||||
using pp::renderer::validate_draw_desc;
|
||||
using pp::renderer::validate_mesh_desc;
|
||||
using pp::renderer::validate_mipmap_generation_desc;
|
||||
using pp::renderer::validate_readback_region;
|
||||
using pp::renderer::validate_render_pass_desc;
|
||||
using pp::renderer::validate_sampler_address_mode;
|
||||
@@ -397,6 +399,24 @@ public:
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] pp::foundation::Status generate_mipmaps(
|
||||
pp::renderer::ITexture2D& texture) noexcept override
|
||||
{
|
||||
const auto status = validate_mipmap_generation_desc(texture.desc());
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
const auto bytes = texture_byte_size(texture.desc());
|
||||
if (!bytes.ok()) {
|
||||
return bytes.status();
|
||||
}
|
||||
|
||||
last_generated_mip_levels = texture.desc().mip_levels;
|
||||
last_generated_mip_bytes = bytes.value();
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] pp::foundation::Status copy_texture(
|
||||
pp::renderer::ITexture2D& source,
|
||||
ReadbackRegion source_region,
|
||||
@@ -497,6 +517,8 @@ public:
|
||||
std::uint32_t last_sampler_slot = 0;
|
||||
SamplerDesc last_sampler_desc {};
|
||||
std::uint64_t last_upload_bytes = 0;
|
||||
std::uint32_t last_generated_mip_levels = 0;
|
||||
std::uint64_t last_generated_mip_bytes = 0;
|
||||
std::uint64_t last_copy_source_bytes = 0;
|
||||
std::uint64_t last_copy_destination_bytes = 0;
|
||||
std::uint64_t last_readback_bytes = 0;
|
||||
@@ -629,6 +651,14 @@ void computes_texture_sizes(pp::tests::Harness& h)
|
||||
PP_EXPECT(h, r8.ok());
|
||||
PP_EXPECT(h, r8.value() == 128U);
|
||||
PP_EXPECT(h, texture_format_name(TextureFormat::depth24_stencil8) == std::string_view("depth24_stencil8"));
|
||||
|
||||
const auto mipmapped = texture_byte_size(TextureDesc {
|
||||
.extent = Extent2D { .width = 4, .height = 4 },
|
||||
.format = TextureFormat::rgba8,
|
||||
.mip_levels = 3,
|
||||
});
|
||||
PP_EXPECT(h, mipmapped.ok());
|
||||
PP_EXPECT(h, mipmapped.value() == 84U);
|
||||
}
|
||||
|
||||
void validates_texture_usage_contract(pp::tests::Harness& h)
|
||||
@@ -676,6 +706,68 @@ void validates_texture_usage_contract(pp::tests::Harness& h)
|
||||
PP_EXPECT(h, unknown_format.code == StatusCode::invalid_argument);
|
||||
}
|
||||
|
||||
void validates_mipmap_generation_contract(pp::tests::Harness& h)
|
||||
{
|
||||
const TextureDesc mipmapped_desc {
|
||||
.extent = Extent2D { .width = 4, .height = 4 },
|
||||
.format = TextureFormat::rgba8,
|
||||
.mip_levels = 3,
|
||||
.usage = TextureUsage::sampled | TextureUsage::copy_source | TextureUsage::copy_destination,
|
||||
};
|
||||
const TextureDesc one_mip_desc {
|
||||
.extent = Extent2D { .width = 4, .height = 4 },
|
||||
.format = TextureFormat::rgba8,
|
||||
.mip_levels = 1,
|
||||
.usage = TextureUsage::sampled | TextureUsage::copy_source | TextureUsage::copy_destination,
|
||||
};
|
||||
const TextureDesc too_many_mips_desc {
|
||||
.extent = Extent2D { .width = 4, .height = 4 },
|
||||
.format = TextureFormat::rgba8,
|
||||
.mip_levels = 4,
|
||||
.usage = TextureUsage::sampled | TextureUsage::copy_source | TextureUsage::copy_destination,
|
||||
};
|
||||
const TextureDesc missing_sampled_desc {
|
||||
.extent = Extent2D { .width = 4, .height = 4 },
|
||||
.format = TextureFormat::rgba8,
|
||||
.mip_levels = 3,
|
||||
.usage = TextureUsage::copy_source | TextureUsage::copy_destination,
|
||||
};
|
||||
const TextureDesc missing_copy_source_desc {
|
||||
.extent = Extent2D { .width = 4, .height = 4 },
|
||||
.format = TextureFormat::rgba8,
|
||||
.mip_levels = 3,
|
||||
.usage = TextureUsage::sampled | TextureUsage::copy_destination,
|
||||
};
|
||||
const TextureDesc missing_copy_destination_desc {
|
||||
.extent = Extent2D { .width = 4, .height = 4 },
|
||||
.format = TextureFormat::rgba8,
|
||||
.mip_levels = 3,
|
||||
.usage = TextureUsage::sampled | TextureUsage::copy_source,
|
||||
};
|
||||
|
||||
PP_EXPECT(h, max_mip_levels_for_extent(Extent2D { .width = 1, .height = 1 }) == 1U);
|
||||
PP_EXPECT(h, max_mip_levels_for_extent(Extent2D { .width = 4, .height = 3 }) == 3U);
|
||||
PP_EXPECT(h, max_mip_levels_for_extent(Extent2D { .width = max_texture_dimension, .height = 1 }) == 16U);
|
||||
PP_EXPECT(h, validate_mipmap_generation_desc(mipmapped_desc).ok());
|
||||
|
||||
const auto one_mip = validate_mipmap_generation_desc(one_mip_desc);
|
||||
const auto too_many_mips = validate_mipmap_generation_desc(too_many_mips_desc);
|
||||
const auto missing_sampled = validate_mipmap_generation_desc(missing_sampled_desc);
|
||||
const auto missing_copy_source = validate_mipmap_generation_desc(missing_copy_source_desc);
|
||||
const auto missing_copy_destination = validate_mipmap_generation_desc(missing_copy_destination_desc);
|
||||
|
||||
PP_EXPECT(h, !one_mip.ok());
|
||||
PP_EXPECT(h, one_mip.code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !too_many_mips.ok());
|
||||
PP_EXPECT(h, too_many_mips.code == StatusCode::out_of_range);
|
||||
PP_EXPECT(h, !missing_sampled.ok());
|
||||
PP_EXPECT(h, missing_sampled.code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !missing_copy_source.ok());
|
||||
PP_EXPECT(h, missing_copy_source.code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !missing_copy_destination.ok());
|
||||
PP_EXPECT(h, missing_copy_destination.code == StatusCode::invalid_argument);
|
||||
}
|
||||
|
||||
void rejects_invalid_or_excessive_extents(pp::tests::Harness& h)
|
||||
{
|
||||
const auto zero = validate_extent(Extent2D { .width = 0, .height = 1 });
|
||||
@@ -1410,6 +1502,48 @@ void recording_renderer_records_shader_uniform_writes(pp::tests::Harness& h)
|
||||
PP_EXPECT(h, commands[3].kind == RecordedRenderCommandKind::end_render_pass);
|
||||
}
|
||||
|
||||
void recording_renderer_records_mipmap_generation(pp::tests::Harness& h)
|
||||
{
|
||||
RecordingRenderDevice device;
|
||||
RecordingTexture2D texture(TextureDesc {
|
||||
.extent = Extent2D { .width = 4, .height = 4 },
|
||||
.format = TextureFormat::rgba8,
|
||||
.mip_levels = 3,
|
||||
.usage = TextureUsage::sampled | TextureUsage::copy_source | TextureUsage::copy_destination,
|
||||
});
|
||||
RecordingTexture2D one_mip_texture(TextureDesc {
|
||||
.extent = Extent2D { .width = 4, .height = 4 },
|
||||
.format = TextureFormat::rgba8,
|
||||
.mip_levels = 1,
|
||||
.usage = TextureUsage::sampled | TextureUsage::copy_source | TextureUsage::copy_destination,
|
||||
});
|
||||
RecordingRenderTarget target(TextureDesc {
|
||||
.extent = Extent2D { .width = 4, .height = 4 },
|
||||
.format = TextureFormat::rgba8,
|
||||
.usage = all_texture_usages,
|
||||
});
|
||||
|
||||
auto& context = device.immediate_context();
|
||||
PP_EXPECT(h, context.generate_mipmaps(texture).ok());
|
||||
const auto commands = device.commands();
|
||||
PP_EXPECT(h, commands.size() == 1U);
|
||||
PP_EXPECT(h, commands[0].kind == RecordedRenderCommandKind::generate_mipmaps);
|
||||
PP_EXPECT(h, commands[0].texture_desc.mip_levels == 3U);
|
||||
PP_EXPECT(h, commands[0].generated_mip_levels == 3U);
|
||||
PP_EXPECT(h, commands[0].generated_mip_bytes == 84U);
|
||||
PP_EXPECT(h, recorded_render_command_kind_name(commands[0].kind) == std::string_view("generate_mipmaps"));
|
||||
|
||||
const auto one_mip = context.generate_mipmaps(one_mip_texture);
|
||||
PP_EXPECT(h, !one_mip.ok());
|
||||
PP_EXPECT(h, one_mip.code == StatusCode::invalid_argument);
|
||||
|
||||
PP_EXPECT(h, context.begin_render_pass(target, RenderPassDesc {}).ok());
|
||||
const auto during_render_pass = context.generate_mipmaps(texture);
|
||||
PP_EXPECT(h, !during_render_pass.ok());
|
||||
PP_EXPECT(h, during_render_pass.code == StatusCode::invalid_argument);
|
||||
context.end_render_pass();
|
||||
}
|
||||
|
||||
void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
|
||||
{
|
||||
RecordingRenderDevice device;
|
||||
@@ -1997,6 +2131,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("validates_mipmap_generation_contract", validates_mipmap_generation_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);
|
||||
@@ -2016,6 +2151,7 @@ int main()
|
||||
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_shader_uniform_writes", recording_renderer_records_shader_uniform_writes);
|
||||
harness.run("recording_renderer_records_mipmap_generation", recording_renderer_records_mipmap_generation);
|
||||
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