From 235fb5f8c27e77a6069527fcb4c42103e948576f Mon Sep 17 00:00:00 2001 From: avan Date: Sat, 8 Aug 2026 19:26:47 +0800 Subject: [PATCH] Fix view compatibility for compressed textures with different logical sizes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It has been confirmed that Divinity: Original Sin 2 may describe the same or overlapping guest GPU memory using BCn-compressed textures with different logical dimensions, such as 104×104 and 102×102. BCn formats store texture data in blocks, with each BC block covering 4×4 texels. After rounding the dimensions up to complete blocks, both 104×104 and 102×102 textures require a 26×26 grid of BC blocks. As a result, they have the same block footprint at the base mip level. Ryujinx originally determined size compatibility in TextureCompatibility.ViewSizeMatches() primarily from this block footprint, which could cause these textures to be classified as Full view-compatible and allowed to share the same Vulkan VkImage directly. However, because the two textures have different logical dimensions, their mip chains are also different. For example, at mip level 3, a 104×104 texture is reduced to 13×13, while a 102×102 texture is reduced to 12×12. If a 104×104 child texture view shares the backing image of a 102×102 parent texture, a subsequent full mip upload may attempt to write 13×13 compressed data into a Vulkan image subresource whose actual dimensions are only 12×12. This produces a compressed buffer-to-image copy that does not match the geometry of the backing image. After the invalid command is submitted to the GPU, the Vulkan driver may asynchronously report VK_ERROR_DEVICE_LOST during command buffer submission, waiting, or presentation. In Ryujinx, this eventually appears as: VulkanException: Unexpected API error "ErrorDeviceLost". The fix adds a logical-dimension check to TextureCompatibility.ViewSizeMatches(). If either the parent or child uses a compressed texture format, and the actual width or height of the corresponding parent mip does not match the logical width or height of the child, the relationship is no longer classified as Full view-compatible. Instead, it is downgraded to CopyOnly. CopyOnly indicates that the two textures may still describe the same or overlapping guest memory and that their contents must remain synchronized, but they cannot directly share a single fixed-size Vulkan image. Ryujinx instead allows each texture to retain a host texture matching its own logical dimensions and synchronizes their contents through the existing copy-dependency mechanism. As a result, the 13×13 mip of the 104×104 texture is uploaded to an actual 13×13 destination mip, while the 12×12 mip of the 102×102 texture is uploaded to an actual 12×12 destination mip. (cherry picked from commit e80fc0046260ef4754c6039f7d114c3db99d7edf) --- src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs index 35e1f80bd..fb99f0bd7 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs @@ -369,12 +369,15 @@ namespace Ryujinx.Graphics.Gpu.Image } // Some APIs align the width for copy and render target textures, - // so the width may not match in this case for different uses of the same texture. + // so the width may not match for different uses of the same texture. // To account for this, we compare the aligned width here. - // We expect height to always match exactly, if the texture is the same. + // However, matching block footprints are not sufficient for compressed textures; + // their logical dimensions must also match. if (alignedWidthMatches && lhsSize.Height == rhsSize.Height) { - return (exact && lhsSize.Width != rhsSize.Width) || lhsSize.Width < rhsSize.Width + return ((lhs.FormatInfo.IsCompressed || rhs.FormatInfo.IsCompressed) && + (Math.Max(1, lhs.Width >> level) != rhs.Width || Math.Max(1, lhs.Height >> level) != rhs.Height)) || + (exact && lhsSize.Width != rhsSize.Width) || lhsSize.Width < rhsSize.Width ? TextureViewCompatibility.CopyOnly : result; }