From a88181b89751d42ceccb9a58011df75242c4a788 Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 27 Sep 2026 11:10:02 +0400 Subject: [PATCH] Graphics: share texture layout for linear render-target mip chains --- .../host_gpu/renderer/colorRenderTarget.cpp | 17 ++---- tests/ShaderRecompilerComputeTests.cpp | 61 ++++++++++++++----- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/src/graphics/host_gpu/renderer/colorRenderTarget.cpp b/src/graphics/host_gpu/renderer/colorRenderTarget.cpp index 234570313..9c4877cad 100644 --- a/src/graphics/host_gpu/renderer/colorRenderTarget.cpp +++ b/src/graphics/host_gpu/renderer/colorRenderTarget.cpp @@ -160,9 +160,6 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor break; default: EXIT("unknown tile mode: %u\n", static_cast(rt.attrib3.tile_mode)); } - if (!tile && levels > 1) { - EXIT("linear mipmapped render targets are unsupported\n"); - } if (samples > 1 && (!tile || levels != 1)) { EXIT("multisampled render targets require a single-mip tiled surface\n"); } @@ -232,13 +229,14 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor } size = volume_layout.block_slice_size; backing_size = volume_layout.total_size; - } else if (tile) { + } else { TileSizeAlign layout {}; bool valid_layout = false; - if (texture_tile) { + if (!tile || texture_tile) { TileGetTextureSize(transfer_format, width, height, levels, rt.attrib3.tile_mode, &layout, mip_sizes, mip_padded); - valid_layout = layout.size != 0 && layout.align == texture_tile_layout.block.block_size; + const auto alignment = tile ? texture_tile_layout.block.block_size : 256u; + valid_layout = layout.size != 0 && layout.align == alignment; } else { valid_layout = levels == 1 ? TileGetRenderTargetSize(width, height, pitch, bytes_per_element, @@ -256,13 +254,6 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor mip_sizes[0] = {static_cast(size), 0, 0, 0, 0, 0}; mip_padded[0] = {pitch, height}; } - } else { - size = static_cast(pitch) * height * bytes_per_element * samples; - if (size > UINT32_MAX) { - EXIT("linear render-target slice exceeds the supported layout size\n"); - } - mip_sizes[0] = {static_cast(size), 0, 0, 0, 0, 0}; - mip_padded[0] = {pitch, height}; } if (size == 0 || (!volume && size > UINT64_MAX / view.image_layers)) { EXIT("render-target memory footprint is invalid\n"); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 85a5dcb58..6c2cceffe 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -8318,12 +8318,16 @@ public: uint32_t width, height, dimension, pitch; uint64_t size; Prospero::TileMode tile; + uint32_t levels = 1; + bool float16 = false; }; const std::array cases{ TargetCase{width, height, 0, pitch, layout.size, Prospero::TileMode::kRenderTarget}, // PPSA28068: the 1440x720 agreement target must alias its sampled texture. TargetCase{1440, 720, 1, 1472, 0x40b000, Prospero::TileMode::kLinear}, + // PPSA01416: linear RGBA16F chains place the smallest mip first. + TargetCase{128, 128, 1, 128, 0x2be00, Prospero::TileMode::kLinear, 7, true}, }; constexpr uint64_t allocation_size = 0x410000; @@ -8353,10 +8357,13 @@ public: HW::Shader shaders{}; registers.SetColorBase(0, {.addr = base}); registers.SetColorInfo( - 0, {.format = Prospero::ChannelLayout::k8_8_8_8, - .channel_type = Prospero::ChannelType::kSrgb, + 0, {.format = target.float16 ? Prospero::ChannelLayout::k16_16_16_16 + : Prospero::ChannelLayout::k8_8_8_8, + .channel_type = target.float16 ? Prospero::ChannelType::kFloat + : Prospero::ChannelType::kSrgb, .channel_order = Prospero::ChannelOrder::kStandard}); - registers.SetColorAttrib2(0, {.height = height - 1, .width = width - 1}); + registers.SetColorAttrib2(0, {.height = height - 1, .width = width - 1, + .num_mip_levels = target.levels - 1}); registers.SetColorAttrib3(0, {.tile_mode = target.tile, .dimension = target.dimension, @@ -8386,11 +8393,13 @@ public: color.desc.info.type == (is_1d ? Prospero::ImageType::kColor1D : Prospero::ImageType::kColor2D) && color.desc.info.extent == vk::Extent3D{width, height, 1} && - color.desc.info.resources == ImageSubresources{1, 1} && + color.desc.info.resources == ImageSubresources{target.levels, 1} && color.desc.info.pitch == target.pitch && color.desc.info.data.size == target.size && - color.desc.info.mip_layout[0].offset == 0 && - color.desc.info.mip_layout[0].size == target.size && + color.desc.info.mip_layout[0].offset == + (target.levels == 1 ? 0 : 0xbe00) && + color.desc.info.mip_layout[0].size == + (target.levels == 1 ? target.size : 0x20000) && color.desc.view_info.type == (is_1d ? vk::ImageViewType::e1D : vk::ImageViewType::e2D) && image.backing.image_type == (is_1d ? vk::ImageType::e1D @@ -8400,21 +8409,36 @@ public: rendering.num_color_attachments == 1, "color attachment lost its dimensions or padded guest layout"); + if (target.levels == 7) { + constexpr std::array offsets{0xbe00u, 0x3e00u, 0x1e00u, 0xe00u, + 0x600u, 0x200u, 0u}; + constexpr std::array pitches{128u, 64u, 32u, 32u, 32u, 32u, 32u}; + for (uint32_t mip = 0; mip < target.levels; ++mip) { + const auto &layout = color.desc.info.mip_layout[mip]; + Require(name, "linear mip chain", layout.offset == offsets[mip] && + layout.pitch == pitches[mip] && + layout.size == pitches[mip] * (height >> mip) * 8, + "linear target mip offsets or padded pitches differ from the SDK"); + } + } + if (target.tile == Prospero::TileMode::kLinear) { vk::ClearValue clear{}; clear.color.float32 = std::array{1.0f, 0.0f, 1.0f, 1.0f}; TextureCacheTestAccess::ClearImage( texture_cache, scheduler.Current(), color.image_id, - {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, clear); + {vk::ImageAspectFlagBits::eColor, 0, target.levels, 0, 1}, clear); RenderExecutorTestAccess::ResetBindings(executor); ShaderTextureResource descriptor{{ static_cast(base >> 8u), - (static_cast(Prospero::BufferFormat::k8_8_8_8Srgb) << 20u) | + (static_cast(target.float16 + ? Prospero::BufferFormat::k16_16_16_16Float + : Prospero::BufferFormat::k8_8_8_8Srgb) << 20u) | (((width - 1u) & 3u) << 30u), ((width - 1u) >> 2u) | ((height - 1u) << 14u), - DstSel(4, 5, 6, 7) | + DstSel(4, 5, 6, 7) | ((target.levels - 1) << 16u) | (static_cast(Prospero::ImageType::kColor2D) << 28u), - 0, 0x00700000u, 0, 0}}; + 0, 0x00700000u | ((target.levels - 1) << 4u), 0, 0}}; ShaderRecompiler::IR::DescriptorValue value{}; value.dword_count = 8; std::copy_n(descriptor.fields, 8, value.dwords.begin()); @@ -8431,11 +8455,18 @@ public: sampled.desc.info.data.size == target.size && sampled.desc.info.mip_layout == color.desc.info.mip_layout && texture_cache.FindTexture(sampled.image_id, sampled.desc) != nullptr, - "sampling the linear agreement target created a second image"); - Require(name, "linear target sampled contents", - ReadCachedTexel(name, context, sampled.image_id, - {1439, 719, 0}) == std::vector{0xffff00ffu}, - "the sampled linear target lost its GPU-written final texel"); + "sampling the linear target created a second image"); + const auto expected = target.float16 + ? std::vector{0x00003c00u, 0x3c003c00u} + : std::vector{0xffff00ffu}; + for (uint32_t mip = 0; mip < target.levels; ++mip) { + Require(name, "linear target sampled contents", + ReadCachedTexel(name, context, sampled.image_id, + {static_cast((width >> mip) - 1), + static_cast((height >> mip) - 1), 0}, + {1, 1, 1}, 0, mip) == expected, + "the sampled linear target lost its GPU-written final texel"); + } } RenderExecutorTestAccess::ResetBindings(executor);