From 31aacdd05b872b9f2127b85ccc22085044a40efc Mon Sep 17 00:00:00 2001 From: The Roofer Dev Date: Tue, 16 Jun 2026 17:43:13 -0400 Subject: [PATCH] Graphics: decode oversized ASTC array textures slice-by-slice Large 2D-array ASTC textures (e.g. from the TOTK 4K texture pack) can decode to more than 2 GB of RGBA8, which overflows the Int32 size computation in QueryDecompressedSize and crashes MemoryOwner.Rent. Add QueryDecompressedSizeLong to detect the overflow and, in that case, decode and upload the texture one layer/level slice at a time instead of allocating a single oversized buffer. Co-Authored-By: Claude Opus 4.8 --- src/Ryujinx.Graphics.Gpu/Image/Texture.cs | 26 +++++++++++++++++++ .../Astc/AstcDecoder.cs | 21 +++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs index 05a316c45..7008fe6a3 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -661,6 +661,32 @@ namespace Ryujinx.Graphics.Gpu.Image } } + // Large 4K texture-pack array textures (e.g. a 2048x2048 ASTC 2D array with 121 layers) decode to + // more than 2 GiB of RGBA8, which overflows Int32 and cannot fit in a single array. Decode and + // upload them one slice at a time instead (each slice is only a few MiB), reusing the same + // single-slice path that partial synchronization already uses. + if (Info.FormatInfo.Format.IsAstc && + !_context.Capabilities.SupportsAstcCompression && + _depth == 1 && + AstcDecoder.QueryDecompressedSizeLong(Info.Width, Info.Height, 1, Info.Levels, _layers) > int.MaxValue) + { + for (int layer = 0; layer < _layers; layer++) + { + for (int level = 0; level < Info.Levels; level++) + { + int sliceOffset = _sizeInfo.AllOffsets[layer * Info.Levels + level]; + + MemoryOwner sliceResult = ConvertToHostCompatibleFormat(data[sliceOffset..], level, single: true); + + HostTexture.SetData(sliceResult, layer, level); + } + } + + _hasData = true; + + return; + } + MemoryOwner result = ConvertToHostCompatibleFormat(data); if (ScaleFactor != 1f && AllowScaledSetData()) diff --git a/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs b/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs index 1053f6f18..3cfe36559 100644 --- a/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs +++ b/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs @@ -115,6 +115,27 @@ namespace Ryujinx.Graphics.Texture.Astc return size * 4; } + /// + /// Computes the fully-decoded (RGBA8) size in bytes using 64-bit arithmetic, so callers can detect + /// when the result would overflow (e.g. large 4K texture-pack array textures) and + /// decode slice-by-slice instead of allocating a single oversized buffer. + /// + public static long QueryDecompressedSizeLong(int sizeX, int sizeY, int sizeZ, int levelCount, int layerCount) + { + long size = 0; + + for (int i = 0; i < levelCount; i++) + { + long levelSizeX = Math.Max(1, sizeX >> i); + long levelSizeY = Math.Max(1, sizeY >> i); + long levelSizeZ = Math.Max(1, sizeZ >> i); + + size += levelSizeX * levelSizeY * levelSizeZ * layerCount; + } + + return size * 4; + } + public void ProcessBlock(int index) { Buffer16 inputBlock = MemoryMarshal.Cast(InputBuffer.Span)[index];