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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<byte> sliceResult = ConvertToHostCompatibleFormat(data[sliceOffset..], level, single: true);
|
||||
|
||||
HostTexture.SetData(sliceResult, layer, level);
|
||||
}
|
||||
}
|
||||
|
||||
_hasData = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
MemoryOwner<byte> result = ConvertToHostCompatibleFormat(data);
|
||||
|
||||
if (ScaleFactor != 1f && AllowScaledSetData())
|
||||
|
||||
@@ -115,6 +115,27 @@ namespace Ryujinx.Graphics.Texture.Astc
|
||||
return size * 4;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the fully-decoded (RGBA8) size in bytes using 64-bit arithmetic, so callers can detect
|
||||
/// when the result would overflow <see cref="int"/> (e.g. large 4K texture-pack array textures) and
|
||||
/// decode slice-by-slice instead of allocating a single oversized buffer.
|
||||
/// </summary>
|
||||
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<byte, Buffer16>(InputBuffer.Span)[index];
|
||||
|
||||
Reference in New Issue
Block a user