From 46d1554186cdfd2255cc3745e3e2cfe9130c5a84 Mon Sep 17 00:00:00 2001 From: Sssnowyyy <78863999+Sssnowyyy@users.noreply.github.com> Date: Mon, 2 Nov 2026 11:43:07 -0500 Subject: vulkan: back a sparse tiled image whole when its tiles can't be set up In the aliased mode (AMD) a SparseTiles image is created unbound, and only InitSparseTiles binds every block to the shared zeroed one. When it fails (an unusual block shape, no memory for the zeroed block or the mip tail, a failed bind), the constructor went on to the next branch and flagged the image UnwrittenSparse with nothing bound. AMD's drivers can't sample such an image, and it is sampled before its first write replaces it, which is the hang a855e8373f avoided for large sparse images. Create the image again as an ordinary backed image in that case, as before sparse tiles, and drop the Unbacked flag. The other modes keep falling back to UnwrittenSparse, since their drivers sample unbound memory. Follow-up to 130c255ed9 (with a855e8373f and 1c794af564). Assisted-by: Claude Sonnet 5.5 (implementation and commit message) --- .../renderer_vulkan/vk_texture_cache.cpp | 45 +++++++++++++++------- 1 file changed, 32 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 30981fe413..928146027d 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -2350,23 +2350,31 @@ void TextureCacheRuntime::TickFrame() { }); } +namespace { + +// The host image of a texture; unbacked images are created without memory (see MakeImage). +[[nodiscard]] vk::Image MakeHostImage(TextureCacheRuntime& runtime, const ImageInfo& info, + u32 host_level_offset, GPUVAddr gpu_addr, bool backed) { + const Device& device = runtime.device; + const bool sparse_tiles = !backed && UsesSparseTiles(device, info, gpu_addr); + return MakeImage( + device, runtime.memory_allocator, TrimmedHostInfo(device, info, host_level_offset), + IsHugeAstcArray(device, info) ? std::span{HugeAstcViewFormats} + : IsOpaqueBc1Astc(device, info) ? std::span{OpaqueAstcViewFormats} + : runtime.ViewFormats(info.format), + AstcHostFormat(device, info), + !backed && (UsesSparseLayers(device, info, gpu_addr) || sparse_tiles || + IsUnwrittenSparse(device, info, gpu_addr)), + sparse_tiles && SparseTilesModeOf(device) == SparseTilesMode::Aliased); +} + +} // Anonymous namespace + Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_, VAddr cpu_addr_) : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler}, runtime{&runtime_}, - original_image(MakeImage(runtime_.device, runtime_.memory_allocator, - TrimmedHostInfo(runtime_.device, info, host_level_offset), - IsHugeAstcArray(runtime_.device, info) - ? std::span{HugeAstcViewFormats} - : IsOpaqueBc1Astc(runtime_.device, info) - ? std::span{OpaqueAstcViewFormats} - : runtime->ViewFormats(info.format), - AstcHostFormat(runtime_.device, info), - UsesSparseLayers(runtime_.device, info, gpu_addr_) || - UsesSparseTiles(runtime_.device, info, gpu_addr_) || - IsUnwrittenSparse(runtime_.device, info, gpu_addr_), - UsesSparseTiles(runtime_.device, info, gpu_addr_) && - SparseTilesModeOf(runtime_.device) == SparseTilesMode::Aliased)), + original_image(MakeHostImage(runtime_, info, host_level_offset, gpu_addr_, false)), aspect_mask(ImageAspectMask(info.format)) { if (original_image.IsUnbacked()) { flags |= VideoCommon::ImageFlagBits::Unbacked; @@ -2377,6 +2385,17 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu flags |= VideoCommon::ImageFlagBits::SparseLayers; } else if (UsesSparseTiles(runtime_.device, info, gpu_addr_) && InitSparseTiles()) { flags |= VideoCommon::ImageFlagBits::SparseTiles; + } else if (UsesSparseTiles(runtime_.device, info, gpu_addr_) && + SparseTilesModeOf(runtime_.device) == SparseTilesMode::Aliased) { + // The driver can't sample unbound memory (that is why the blocks were to share one), + // so an image whose tiles could not be set up is backed whole like any other. + LOG_WARNING(Render_Vulkan, "Sparse tiled image {:#x}: backing it whole", gpu_addr_); + original_image = MakeHostImage(runtime_, info, host_level_offset, gpu_addr_, true); + if (!original_image.IsUnbacked()) { + flags &= ~VideoCommon::ImageFlagBits::Unbacked; + } else if (texels <= (u64{1} << 28)) { + flags |= VideoCommon::ImageFlagBits::UnwrittenSparse; + } } else if (texels <= (u64{1} << 28)) { // Not too big to back (see MemoryAllocator::CreateImage): unbacked because nothing // was written there yet, so the first write gets it replaced. -- cgit v1.2.3