From 0685bffb2f060bf470aa8528a405ebbe17130076 Mon Sep 17 00:00:00 2001 From: bigjjmoneyman <6117778+bigjjmoneyman@users.noreply.github.com> Date: Tue, 27 Oct 2026 05:41:22 -0400 Subject: [PATCH] texture cache: convert D16<->D24/D32 depth copies instead of crashing (#1873) gpu: convert guest D16 depth between native D16 and stenciled four-byte host depth Co-authored-by: nmzik --- .../host_gpu/renderer/cache/textureCache.cpp | 60 ++++++------ tests/ShaderRecompilerComputeTests.cpp | 93 +++++++++++++------ 2 files changed, 97 insertions(+), 56 deletions(-) diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.cpp b/src/graphics/host_gpu/renderer/cache/textureCache.cpp index 45bfacfe4..3c496a56f 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/cache/textureCache.cpp @@ -488,16 +488,15 @@ void TextureCache::RefreshCopySource(ImageId id) { } bool TextureCache::CopyD16(Image& destination, Image& source) { - const bool source_depth = source.info.IsDepth(); - const bool destination_depth = destination.info.IsDepth(); - if (source_depth == destination_depth) { + // Converts guest D16 between a four-byte host depth aspect and two-byte color or D16 depth. + const bool source_wide = DepthAspectTransferBytes(source.backing.format) == sizeof(uint32_t); + if (source_wide == (DepthAspectTransferBytes(destination.backing.format) == sizeof(uint32_t))) { return false; } - auto& depth = source_depth ? source : destination; - auto& color = source_depth ? destination : source; - const auto transfer_bytes = DepthAspectTransferBytes(depth.backing.format); + auto& depth = source_wide ? source : destination; + auto& narrow = source_wide ? destination : source; if (depth.info.bytes_per_block != sizeof(uint16_t) || - color.info.bytes_per_block != sizeof(uint16_t) || transfer_bytes != sizeof(uint32_t)) { + narrow.info.bytes_per_block != sizeof(uint16_t)) { return false; } EXIT_IF(source.backing.samples != 1 || destination.backing.samples != 1 || @@ -507,42 +506,42 @@ bool TextureCache::CopyD16(Image& destination, Image& source) { const auto layers = depth.info.resources.layers; const uint64_t depth_slice = - static_cast(depth.info.pitch) * depth.info.extent.height * transfer_bytes; - const uint64_t color_slice = - static_cast(color.info.pitch) * color.info.extent.height * sizeof(uint16_t); - EXIT_IF(layers == 0 || depth_slice > UINT64_MAX / layers || color_slice > UINT64_MAX / layers); - const auto depth_size = depth_slice * layers; - const auto color_size = color_slice * layers; - const std::array depth_copies { + static_cast(depth.info.pitch) * depth.info.extent.height * sizeof(uint32_t); + const uint64_t narrow_slice = + static_cast(narrow.info.pitch) * narrow.info.extent.height * sizeof(uint16_t); + EXIT_IF(layers == 0 || depth_slice > UINT64_MAX / layers || narrow_slice > UINT64_MAX / layers); + const std::array depth_copies { vk::BufferImageCopy {0, depth.info.pitch, depth.info.extent.height, {vk::ImageAspectFlagBits::eDepth, 0, 0, layers}, {}, depth.info.extent}}; - const std::array color_copies { + const std::array narrow_copies { vk::BufferImageCopy {0, - color.info.pitch, - color.info.extent.height, - {vk::ImageAspectFlagBits::eColor, 0, 0, layers}, + narrow.info.pitch, + narrow.info.extent.height, + {narrow.info.IsDepth() ? vk::ImageAspectFlagBits::eDepth + : vk::ImageAspectFlagBits::eColor, + 0, 0, layers}, {}, - color.info.extent}}; + narrow.info.extent}}; - auto depth_buffer = m_tiler.GetScratchBuffer(depth_size); - auto color_buffer = m_tiler.GetScratchBuffer(color_size, depth_buffer.buffer); + auto depth_buffer = m_tiler.GetScratchBuffer(depth_slice * layers); + auto narrow_buffer = m_tiler.GetScratchBuffer(narrow_slice * layers, depth_buffer.buffer); const TileManager::D16Layout promote_layout { .width = depth.info.extent.width, .height = depth.info.extent.height, .layers = layers, - .source_row_stride = static_cast(color.info.pitch) * sizeof(uint16_t), - .target_row_stride = static_cast(depth.info.pitch) * transfer_bytes, - .source_slice_stride = color_slice, + .source_row_stride = static_cast(narrow.info.pitch) * sizeof(uint16_t), + .target_row_stride = static_cast(depth.info.pitch) * sizeof(uint32_t), + .source_slice_stride = narrow_slice, .target_slice_stride = depth_slice, }; const bool d32 = DepthAspectTransferFormat(depth.backing.format) == vk::Format::eD32Sfloat; - if (source_depth) { + if (source_wide) { source.Download(depth_copies, depth_buffer.buffer, depth_buffer.offset, depth_buffer.size); - m_tiler.ConvertD16(depth_buffer, color_buffer, TileManager::D16Direction::Demote, d32, + m_tiler.ConvertD16(depth_buffer, narrow_buffer, TileManager::D16Direction::Demote, d32, {.width = promote_layout.width, .height = promote_layout.height, .layers = promote_layout.layers, @@ -550,11 +549,12 @@ bool TextureCache::CopyD16(Image& destination, Image& source) { .target_row_stride = promote_layout.source_row_stride, .source_slice_stride = promote_layout.target_slice_stride, .target_slice_stride = promote_layout.source_slice_stride}); - destination.Upload(color_copies, color_buffer.buffer, color_buffer.offset, - color_buffer.size); + destination.Upload(narrow_copies, narrow_buffer.buffer, narrow_buffer.offset, + narrow_buffer.size); } else { - source.Download(color_copies, color_buffer.buffer, color_buffer.offset, color_buffer.size); - m_tiler.ConvertD16(color_buffer, depth_buffer, TileManager::D16Direction::Promote, d32, + source.Download(narrow_copies, narrow_buffer.buffer, narrow_buffer.offset, + narrow_buffer.size); + m_tiler.ConvertD16(narrow_buffer, depth_buffer, TileManager::D16Direction::Promote, d32, promote_layout); destination.Upload(depth_copies, depth_buffer.buffer, depth_buffer.offset, depth_buffer.size); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 035a820eb..a8b80137a 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -9978,37 +9978,78 @@ public: d16_storage_desc.view_info.usage = vk::ImageUsageFlagBits::eStorage; const auto d16_storage_image = texture_cache.FindImage(d16_storage_desc, true); - auto d16_storage_readback = CreateHostBuffer( - name, sizeof(d16_fallback_values), - vk::BufferUsageFlagBits::eTransferDst, std::vector(2, 0)); - auto &d16_storage_native = texture_cache.GetImage(d16_storage_image); - d16_storage_native.Transit(vk::ImageLayout::eTransferSrcOptimal, - vk::AccessFlagBits2::eTransferRead, {}, - scheduler.Current().Handle()); - vk::BufferImageCopy d16_storage_copy{}; - d16_storage_copy.bufferRowLength = 4; - d16_storage_copy.imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, - 0, 1}; - d16_storage_copy.imageExtent = {4, 1, 1}; - scheduler.Current().Handle().copyImageToBuffer( - d16_storage_native.backing.image, - vk::ImageLayout::eTransferSrcOptimal, d16_storage_readback.buffer, 1, - &d16_storage_copy); - HostReadBarrier(d16_storage_readback.buffer, d16_storage_readback.size, - vk::PipelineStageFlagBits::eTransfer, - vk::AccessFlagBits::eTransferWrite); - scheduler.Finish(); - const auto d16_storage_words = ReadBuffer(name, d16_storage_readback, 2); - std::array d16_storage_values{}; - std::memcpy(d16_storage_values.data(), d16_storage_words.data(), - sizeof(d16_storage_values)); + const auto read_texels = [&](ImageId id, vk::ImageAspectFlagBits aspect) { + auto readback = CreateHostBuffer(name, 4 * sizeof(u32), + vk::BufferUsageFlagBits::eTransferDst, + std::vector(4, 0)); + auto &image = texture_cache.GetImage(id); + image.Transit(vk::ImageLayout::eTransferSrcOptimal, + vk::AccessFlagBits2::eTransferRead, {}, + scheduler.Current().Handle()); + const vk::BufferImageCopy copy{0, 4, 0, {aspect, 0, 0, 1}, {}, {4, 1, 1}}; + scheduler.Current().Handle().copyImageToBuffer( + image.backing.image, vk::ImageLayout::eTransferSrcOptimal, + readback.buffer, 1, ©); + HostReadBarrier(readback.buffer, readback.size, + vk::PipelineStageFlagBits::eTransfer, + vk::AccessFlagBits::eTransferWrite); + scheduler.Finish(); + auto words = ReadBuffer(name, readback, 4); + DestroyBuffer(&readback); + return words; + }; + const auto d16_storage_words = + read_texels(d16_storage_image, vk::ImageAspectFlagBits::eColor); Require( name, "D16 fallback depth/storage copy", - d16_storage_values == d16_fallback_values && + std::memcmp(d16_storage_words.data(), d16_fallback_values.data(), + sizeof(d16_fallback_values)) == 0 && d16_storage_image != d16_depth_image && texture_cache.GetImage(d16_storage_image).IsGpuModified(), "D16 fallback reinterpreted four-byte host depth as two-byte color"); - DestroyBuffer(&d16_storage_readback); + + // Dropping and re-adding the stencil plane keeps the D16 depth values. + constexpr uint64_t d16_stenciled_offset = 0x33e000; + constexpr std::array d16_stale_values{}; + std::memcpy(memory + d16_stenciled_offset, d16_fallback_values.data(), + sizeof(d16_fallback_values)); + auto d16_stenciled_desc = d16_depth_desc; + d16_stenciled_desc.info.data.address = base + d16_stenciled_offset; + d16_stenciled_desc.info.stencil.address = base + 0x33f000; + const auto d16_stenciled_image = + texture_cache.FindImage(d16_stenciled_desc); + (void)texture_cache.FindDepthTarget(d16_stenciled_image, + d16_stenciled_desc); + texture_cache.MarkGpuWritten(d16_stenciled_image); + // Stale guest bytes: the values below can come only from image copies. + Libs::LibKernel::Memory::WriteBacking(base + d16_stenciled_offset, + d16_stale_values.data(), + sizeof(d16_stale_values)); + auto d16_native_desc = d16_stenciled_desc; + d16_native_desc.info.stencil = {}; + d16_native_desc.info.pixel_format = vk::Format::eD16Unorm; + d16_native_desc.view_info.format = vk::Format::eD16Unorm; + d16_native_desc.view_info.aspect = vk::ImageAspectFlagBits::eDepth; + const auto d16_native_image = texture_cache.FindImage(d16_native_desc); + const auto d16_native_words = + read_texels(d16_native_image, vk::ImageAspectFlagBits::eDepth); + Require(name, "D16 fallback depth/native depth copy", + std::memcmp(d16_native_words.data(), d16_fallback_values.data(), + sizeof(d16_fallback_values)) == 0 && + texture_cache.GetImage(d16_native_image).backing.format == + vk::Format::eD16Unorm, + "D16 depth lost its values when its target dropped the stencil"); + const auto d16_restenciled_image = + texture_cache.FindImage(d16_stenciled_desc); + const auto d16_restenciled_words = + read_texels(d16_restenciled_image, vk::ImageAspectFlagBits::eDepth); + bool d16_promoted = d16_restenciled_image != d16_native_image; + for (size_t i = 0; i < d16_fallback_values.size(); ++i) { + d16_promoted &= (d16_restenciled_words[i] & 0x00ffffffu) == + EncodeD16AsD24(d16_fallback_values[i]); + } + Require(name, "D16 native depth/fallback depth copy", d16_promoted, + "D16 depth lost its values when its target regained the stencil"); if (!texture_cache.GetImage(combined_destination_image).IsGpuModified()) { texture_cache.MarkGpuWritten(combined_destination_image);