From dd754065d8686cc81fb2db6bcc258c8b01137bd6 Mon Sep 17 00:00:00 2001 From: Claxten Date: Wed, 7 Oct 2026 20:03:59 +0400 Subject: [PATCH] graphics: preserve full tiled array backing in image-to-buffer copies --- .../host_gpu/renderer/cache/textureCache.cpp | 10 +-- tests/ShaderRecompilerComputeTests.cpp | 77 ++++++++++++++++++- 2 files changed, 80 insertions(+), 7 deletions(-) diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.cpp b/src/graphics/host_gpu/renderer/cache/textureCache.cpp index d43ed4998..d4902f99b 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/cache/textureCache.cpp @@ -1792,14 +1792,12 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uin const auto available = buffer.Size() - buf_offset; uint32_t levels = 0; uint64_t copy_size = 0; - if (image.info.IsVolume()) { - // Volume mips contain strided block slices, so a mip's linear span cannot prove that - // every retained slice fits. Keep volume synchronization whole-image only. - if (!buffer.IsInBounds(image.info.data.address, image.info.data.size)) { - return false; - } + if (buffer.IsInBounds(image.info.data.address, image.info.data.size)) { levels = image.info.resources.levels; copy_size = image.info.data.size; + } else if (image.info.IsVolume() || image.info.resources.layers > 1) { + // Array and volume slices are strided across the full mip chain. + return false; } else { for (; levels < image.info.resources.levels; ++levels) { const auto& mip = image.info.mip_layout[levels]; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 90caf1310..df0c2befd 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -5112,7 +5112,7 @@ public: void CheckUnifiedTextureCacheFlow() { constexpr const char *name = "UnifiedTextureCacheFlow"; constexpr uintptr_t base = 0x0000000200600000ull; - constexpr uint64_t allocation_size = 0x2c00000; + constexpr uint64_t allocation_size = 0x3000000; constexpr uint64_t allocation_alignment = 0x200000; EnsureRuntimeContext(); RenderContext context(m_runtime_context); @@ -5670,6 +5670,81 @@ public: "raw render-target copying overwrote the BC5 guest mip layout"); DestroyBuffer(&bc5_readback); + { + constexpr uint64_t tiled_array_offset = 0x2c00000; + constexpr uint32_t tiled_levels = 5, tiled_layers = 6; + TileSurfaceLayout surface{}; + Require(name, "tiled array alias layout", + TileGetTiledTextureLayout( + {Prospero::BufferFormat::k32UInt, Prospero::TileMode::kStandard64KB, + TileSurfaceDimension::Dim2D, 256, 256, 1, tiled_levels, tiled_layers}, + surface) && surface.total_size == 0x240000 && + surface.block_slice_size == 0x60000 && + surface.mips[0].offset + surface.mips[0].size * tiled_layers == 0x1a0000, + "fixture lost the gap between an array mip span and its full backing"); + auto tiled_array = MakeLinearDesc( + base + tiled_array_offset, surface.total_size, vk::Format::eR32Uint, + Prospero::BufferFormat::k32UInt, Prospero::ImageType::kColor2D, + {256, 256, 1}, tiled_layers, sizeof(uint32_t), 1); + tiled_array.info.tile_mode = Prospero::TileMode::kStandard64KB; + tiled_array.info.resources.levels = tiled_levels; + tiled_array.view_info.level_count = tiled_levels; + for (uint32_t level = 0; level < tiled_levels; ++level) { + const auto &mip = surface.mips[level]; + tiled_array.info.mip_layout[level] = { + mip.offset, mip.size * tiled_layers, mip.padded_width, mip.padded_height}; + } + std::memset(memory + tiled_array_offset, 0, surface.total_size); + const auto tiled_array_id = texture_cache.FindImage(tiled_array); + (void)texture_cache.FindTexture(tiled_array_id, tiled_array); + constexpr uint32_t all_mips = 0x12345678u, last_mip = 0x89abcdefu; + vk::ClearValue clear{}; + clear.color.uint32[0] = all_mips; + TextureCacheTestAccess::ClearImage( + texture_cache, scheduler.Current(), tiled_array_id, + {vk::ImageAspectFlagBits::eColor, 0, tiled_levels, 0, tiled_layers}, clear); + clear.color.uint32[0] = last_mip; + TextureCacheTestAccess::ClearImage( + texture_cache, scheduler.Current(), tiled_array_id, + {vk::ImageAspectFlagBits::eColor, tiled_levels - 1, 1, tiled_layers - 1, 1}, clear); + Libs::Graphics::Buffer full_array( + m_runtime_context, scheduler, MemoryUsage::DeviceLocal, + tiled_array.info.data.address, AllFlags, surface.total_size); + Libs::Graphics::Buffer partial_array( + m_runtime_context, scheduler, MemoryUsage::DeviceLocal, + tiled_array.info.data.address, AllFlags, 0x1a0000); + Require(name, "tiled array alias containment", + BufferCacheTestAccess::SynchronizeBufferFromImage( + resources.GetBufferCache(), full_array, + tiled_array.info.data.address, surface.total_size) && + !BufferCacheTestAccess::SynchronizeBufferFromImage( + resources.GetBufferCache(), partial_array, + tiled_array.info.data.address, 0x1a0000), + "full array synchronization failed or accepted an incomplete slice chain"); + auto readback = CreateHostBuffer(name, 2 * sizeof(uint32_t), + vk::BufferUsageFlagBits::eTransferDst, {0, 0}); + TransferReadBarrier(full_array.Handle(), surface.total_size); + for (uint32_t probe = 0; probe < 2; ++probe) { + const auto &mip = surface.mips[probe == 0 ? 0 : tiled_levels - 1]; + uint32_t within_block = 0; + Require(name, "tiled array probe address", + TileGetBlockOffset(surface.texture.block, mip.tail_x, mip.tail_y, + 0, within_block), + "last-layer mip origin has no guest address"); + const vk::BufferCopy copy{ + (tiled_layers - 1) * surface.block_slice_size + mip.offset + within_block, + probe * sizeof(uint32_t), sizeof(uint32_t)}; + command.Handle().copyBuffer(full_array.Handle(), readback.buffer, 1, ©); + } + HostReadBarrier(readback.buffer, readback.size, vk::PipelineStageFlagBits::eTransfer, + vk::AccessFlagBits::eTransferWrite); + scheduler.Finish(); + Require(name, "tiled array last-layer contents", + ReadBuffer(name, readback, 2) == std::vector{all_mips, last_mip}, + "array synchronization lost GPU contents beyond the first mip span"); + DestroyBuffer(&readback); + } + // A formatted Buffer read must use the private // Exercise the cache-native image-copy path. Use a request larger // than the stream shortcut and poison guest backing