Ryujinx/src/Ryujinx.Graphics.Texture/SizeCalculator.cs

299 lines
9.4 KiB
C#
Raw Normal View History

2019-10-13 08:02:07 +02:00
using Ryujinx.Common;
using System;
using static Ryujinx.Graphics.Texture.BlockLinearConstants;
namespace Ryujinx.Graphics.Texture
{
public static class SizeCalculator
{
private const int StrideAlignment = 32;
private static int Calculate3DOffsetCount(int levels, int depth)
{
int offsetCount = depth;
while (--levels > 0)
{
depth = Math.Max(1, depth >> 1);
offsetCount += depth;
}
return offsetCount;
}
2019-10-13 08:02:07 +02:00
public static SizeInfo GetBlockLinearTextureSize(
int width,
int height,
int depth,
int levels,
int layers,
int blockWidth,
int blockHeight,
int bytesPerPixel,
int gobBlocksInY,
int gobBlocksInZ,
int gobBlocksInTileX,
int gpuLayerSize = 0)
2019-10-13 08:02:07 +02:00
{
Texture: Fix layout conversion when gobs in z is used with depth = 1 (#5220) * Texture: Fix layout conversion when gobs in z is used with depth = 1 The size calculator methods deliberately reduce the gob size of textures if they are deemed too small for it. This is required to get correct sizes when iterating mip levels of a texture. Rendering to a slice of a 3D texture can produce a 3D texture with depth 1, but a gob size matching a much larger texture. We _can't_ "correct" this gob size, as it is intended as a slice of a larger 3D texture. Ignoring it causes layout conversion to break on read and flush. This caused an issue in Tears of the Kingdom where the compressed 3D texture used for the gloom would always break on OpenGL, and seemingly randomly break on Vulkan. In the first case, the data is forcibly flushed to decompress the BC4 texture on the CPU to upload it as 3D, which was broken due to the incorrect layout. In the second, the data may be randomly flushed if it falls out of the cache, but it will appear correct if it's able to form copy dependencies. This change only allows gob sizes to be reduced once per mip level. For the purpose of aligned size, it can still be reduced infinitely as our texture cache isn't properly able to handle a view being _misaligned_. The SizeCalculator has also been changed to reduce the size of rendered depth slices to only include the exact range a single depth slice will cover. (before, the size was way too small with gobs in z reduced to 1, and too large when using the correct value) Gobs in Y logic remains untouched, we don't support Y slices of textures so it's fine as is. This is probably worth testing in a few games as it also affects texture size and view logic. * Improve wording * Maybe a bit better
2023-06-04 22:25:57 +02:00
bool is3D = depth > 1 || gobBlocksInZ > 1;
2019-10-13 08:02:07 +02:00
int layerSize = 0;
int[] allOffsets = new int[is3D ? Calculate3DOffsetCount(levels, depth) : levels * layers * depth];
2019-10-13 08:02:07 +02:00
int[] mipOffsets = new int[levels];
int[] sliceSizes = new int[levels];
int[] levelSizes = new int[levels];
2019-10-13 08:02:07 +02:00
int mipGobBlocksInY = gobBlocksInY;
int mipGobBlocksInZ = gobBlocksInZ;
int gobWidth = (GobStride / bytesPerPixel) * gobBlocksInTileX;
int gobHeight = gobBlocksInY * GobHeight;
int depthLevelOffset = 0;
2019-10-13 08:02:07 +02:00
for (int level = 0; level < levels; level++)
{
int w = Math.Max(1, width >> level);
int h = Math.Max(1, height >> level);
int d = Math.Max(1, depth >> level);
w = BitUtils.DivRoundUp(w, blockWidth);
h = BitUtils.DivRoundUp(h, blockHeight);
while (h <= (mipGobBlocksInY >> 1) * GobHeight && mipGobBlocksInY != 1)
{
mipGobBlocksInY >>= 1;
}
Texture: Fix layout conversion when gobs in z is used with depth = 1 (#5220) * Texture: Fix layout conversion when gobs in z is used with depth = 1 The size calculator methods deliberately reduce the gob size of textures if they are deemed too small for it. This is required to get correct sizes when iterating mip levels of a texture. Rendering to a slice of a 3D texture can produce a 3D texture with depth 1, but a gob size matching a much larger texture. We _can't_ "correct" this gob size, as it is intended as a slice of a larger 3D texture. Ignoring it causes layout conversion to break on read and flush. This caused an issue in Tears of the Kingdom where the compressed 3D texture used for the gloom would always break on OpenGL, and seemingly randomly break on Vulkan. In the first case, the data is forcibly flushed to decompress the BC4 texture on the CPU to upload it as 3D, which was broken due to the incorrect layout. In the second, the data may be randomly flushed if it falls out of the cache, but it will appear correct if it's able to form copy dependencies. This change only allows gob sizes to be reduced once per mip level. For the purpose of aligned size, it can still be reduced infinitely as our texture cache isn't properly able to handle a view being _misaligned_. The SizeCalculator has also been changed to reduce the size of rendered depth slices to only include the exact range a single depth slice will cover. (before, the size was way too small with gobs in z reduced to 1, and too large when using the correct value) Gobs in Y logic remains untouched, we don't support Y slices of textures so it's fine as is. This is probably worth testing in a few games as it also affects texture size and view logic. * Improve wording * Maybe a bit better
2023-06-04 22:25:57 +02:00
if (level > 0 && d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
2019-10-13 08:02:07 +02:00
{
mipGobBlocksInZ >>= 1;
}
int widthInGobs = BitUtils.DivRoundUp(w * bytesPerPixel, GobStride);
int alignment = gobBlocksInTileX;
if (d < gobBlocksInZ || w <= gobWidth || h <= gobHeight)
{
alignment = 1;
}
widthInGobs = BitUtils.AlignUp(widthInGobs, alignment);
2019-10-13 08:02:07 +02:00
int totalBlocksOfGobsInZ = BitUtils.DivRoundUp(d, mipGobBlocksInZ);
int totalBlocksOfGobsInY = BitUtils.DivRoundUp(BitUtils.DivRoundUp(h, GobHeight), mipGobBlocksInY);
int robSize = widthInGobs * mipGobBlocksInY * mipGobBlocksInZ * GobSize;
Texture: Fix layout conversion when gobs in z is used with depth = 1 (#5220) * Texture: Fix layout conversion when gobs in z is used with depth = 1 The size calculator methods deliberately reduce the gob size of textures if they are deemed too small for it. This is required to get correct sizes when iterating mip levels of a texture. Rendering to a slice of a 3D texture can produce a 3D texture with depth 1, but a gob size matching a much larger texture. We _can't_ "correct" this gob size, as it is intended as a slice of a larger 3D texture. Ignoring it causes layout conversion to break on read and flush. This caused an issue in Tears of the Kingdom where the compressed 3D texture used for the gloom would always break on OpenGL, and seemingly randomly break on Vulkan. In the first case, the data is forcibly flushed to decompress the BC4 texture on the CPU to upload it as 3D, which was broken due to the incorrect layout. In the second, the data may be randomly flushed if it falls out of the cache, but it will appear correct if it's able to form copy dependencies. This change only allows gob sizes to be reduced once per mip level. For the purpose of aligned size, it can still be reduced infinitely as our texture cache isn't properly able to handle a view being _misaligned_. The SizeCalculator has also been changed to reduce the size of rendered depth slices to only include the exact range a single depth slice will cover. (before, the size was way too small with gobs in z reduced to 1, and too large when using the correct value) Gobs in Y logic remains untouched, we don't support Y slices of textures so it's fine as is. This is probably worth testing in a few games as it also affects texture size and view logic. * Improve wording * Maybe a bit better
2023-06-04 22:25:57 +02:00
mipOffsets[level] = layerSize;
sliceSizes[level] = totalBlocksOfGobsInY * robSize;
if (is3D)
{
int gobSize = mipGobBlocksInY * GobSize;
int sliceSize = totalBlocksOfGobsInY * widthInGobs * gobSize;
int baseOffset = layerSize;
int mask = gobBlocksInZ - 1;
for (int z = 0; z < d; z++)
{
int zLow = z & mask;
int zHigh = z & ~mask;
allOffsets[z + depthLevelOffset] = baseOffset + zLow * gobSize + zHigh * sliceSize;
}
Texture: Fix layout conversion when gobs in z is used with depth = 1 (#5220) * Texture: Fix layout conversion when gobs in z is used with depth = 1 The size calculator methods deliberately reduce the gob size of textures if they are deemed too small for it. This is required to get correct sizes when iterating mip levels of a texture. Rendering to a slice of a 3D texture can produce a 3D texture with depth 1, but a gob size matching a much larger texture. We _can't_ "correct" this gob size, as it is intended as a slice of a larger 3D texture. Ignoring it causes layout conversion to break on read and flush. This caused an issue in Tears of the Kingdom where the compressed 3D texture used for the gloom would always break on OpenGL, and seemingly randomly break on Vulkan. In the first case, the data is forcibly flushed to decompress the BC4 texture on the CPU to upload it as 3D, which was broken due to the incorrect layout. In the second, the data may be randomly flushed if it falls out of the cache, but it will appear correct if it's able to form copy dependencies. This change only allows gob sizes to be reduced once per mip level. For the purpose of aligned size, it can still be reduced infinitely as our texture cache isn't properly able to handle a view being _misaligned_. The SizeCalculator has also been changed to reduce the size of rendered depth slices to only include the exact range a single depth slice will cover. (before, the size was way too small with gobs in z reduced to 1, and too large when using the correct value) Gobs in Y logic remains untouched, we don't support Y slices of textures so it's fine as is. This is probably worth testing in a few games as it also affects texture size and view logic. * Improve wording * Maybe a bit better
2023-06-04 22:25:57 +02:00
int gobRemainderZ = d % mipGobBlocksInZ;
if (gobRemainderZ != 0 && level == levels - 1)
{
// The slice only covers up to the end of this slice's depth, rather than the full aligned size.
// Avoids size being too large on partial views of 3d textures.
sliceSizes[level] -= gobSize * (mipGobBlocksInZ - gobRemainderZ);
}
}
levelSizes[level] = totalBlocksOfGobsInZ * sliceSizes[level];
layerSize += levelSizes[level];
2019-10-13 08:02:07 +02:00
depthLevelOffset += d;
2019-10-13 08:02:07 +02:00
}
if (layers > 1)
{
layerSize = AlignLayerSize(
layerSize,
height,
depth,
blockHeight,
gobBlocksInY,
gobBlocksInZ,
gobBlocksInTileX);
}
int totalSize;
if (layerSize < gpuLayerSize)
{
totalSize = (layers - 1) * gpuLayerSize + layerSize;
layerSize = gpuLayerSize;
}
else
{
totalSize = layerSize * layers;
}
2019-10-13 08:02:07 +02:00
if (!is3D)
2019-10-13 08:02:07 +02:00
{
for (int layer = 0; layer < layers; layer++)
2019-10-13 08:02:07 +02:00
{
int baseIndex = layer * levels;
int baseOffset = layer * layerSize;
for (int level = 0; level < levels; level++)
{
allOffsets[baseIndex + level] = baseOffset + mipOffsets[level];
}
2019-10-13 08:02:07 +02:00
}
}
return new SizeInfo(mipOffsets, allOffsets, sliceSizes, levelSizes, depth, levels, layerSize, totalSize, is3D);
2019-10-13 08:02:07 +02:00
}
public static SizeInfo GetLinearTextureSize(int stride, int height, int blockHeight)
{
// Non-2D or mipmapped linear textures are not supported by the Switch GPU,
// so we only need to handle a single case (2D textures without mipmaps).
int totalSize = stride * BitUtils.DivRoundUp(height, blockHeight);
return new SizeInfo(totalSize);
2019-10-13 08:02:07 +02:00
}
private static int AlignLayerSize(
int size,
int height,
int depth,
int blockHeight,
int gobBlocksInY,
int gobBlocksInZ,
int gobBlocksInTileX)
2019-10-13 08:02:07 +02:00
{
if (gobBlocksInTileX < 2)
2019-10-13 08:02:07 +02:00
{
height = BitUtils.DivRoundUp(height, blockHeight);
2019-10-13 08:02:07 +02:00
while (height <= (gobBlocksInY >> 1) * GobHeight && gobBlocksInY != 1)
{
gobBlocksInY >>= 1;
}
while (depth <= (gobBlocksInZ >> 1) && gobBlocksInZ != 1)
{
gobBlocksInZ >>= 1;
}
2019-10-13 08:02:07 +02:00
int blockOfGobsSize = gobBlocksInY * gobBlocksInZ * GobSize;
2019-10-13 08:02:07 +02:00
int sizeInBlockOfGobs = size / blockOfGobsSize;
2019-10-13 08:02:07 +02:00
if (size != sizeInBlockOfGobs * blockOfGobsSize)
{
size = (sizeInBlockOfGobs + 1) * blockOfGobsSize;
}
}
else
2019-10-13 08:02:07 +02:00
{
int alignment = (gobBlocksInTileX * GobSize) * gobBlocksInY * gobBlocksInZ;
size = BitUtils.AlignUp(size, alignment);
2019-10-13 08:02:07 +02:00
}
return size;
}
public static Size GetBlockLinearAlignedSize(
int width,
int height,
int depth,
int blockWidth,
int blockHeight,
int bytesPerPixel,
int gobBlocksInY,
int gobBlocksInZ,
int gobBlocksInTileX)
{
width = BitUtils.DivRoundUp(width, blockWidth);
height = BitUtils.DivRoundUp(height, blockHeight);
int gobWidth = (GobStride / bytesPerPixel) * gobBlocksInTileX;
int gobHeight = gobBlocksInY * GobHeight;
int alignment = gobWidth;
if (depth < gobBlocksInZ || width <= gobWidth || height <= gobHeight)
{
alignment = GobStride / bytesPerPixel;
}
// Height has already been divided by block height, so pass it as 1.
(gobBlocksInY, gobBlocksInZ) = GetMipGobBlockSizes(height, depth, 1, gobBlocksInY, gobBlocksInZ);
2019-10-13 08:02:07 +02:00
int blockOfGobsHeight = gobBlocksInY * GobHeight;
int blockOfGobsDepth = gobBlocksInZ;
width = BitUtils.AlignUp(width, alignment);
2019-10-13 08:02:07 +02:00
height = BitUtils.AlignUp(height, blockOfGobsHeight);
depth = BitUtils.AlignUp(depth, blockOfGobsDepth);
return new Size(width, height, depth);
}
public static Size GetLinearAlignedSize(
int width,
int height,
int blockWidth,
int blockHeight,
int bytesPerPixel)
{
width = BitUtils.DivRoundUp(width, blockWidth);
height = BitUtils.DivRoundUp(height, blockHeight);
int widthAlignment = StrideAlignment / bytesPerPixel;
width = BitUtils.AlignUp(width, widthAlignment);
return new Size(width, height, 1);
}
public static (int, int) GetMipGobBlockSizes(
int height,
int depth,
int blockHeight,
int gobBlocksInY,
Texture: Fix layout conversion when gobs in z is used with depth = 1 (#5220) * Texture: Fix layout conversion when gobs in z is used with depth = 1 The size calculator methods deliberately reduce the gob size of textures if they are deemed too small for it. This is required to get correct sizes when iterating mip levels of a texture. Rendering to a slice of a 3D texture can produce a 3D texture with depth 1, but a gob size matching a much larger texture. We _can't_ "correct" this gob size, as it is intended as a slice of a larger 3D texture. Ignoring it causes layout conversion to break on read and flush. This caused an issue in Tears of the Kingdom where the compressed 3D texture used for the gloom would always break on OpenGL, and seemingly randomly break on Vulkan. In the first case, the data is forcibly flushed to decompress the BC4 texture on the CPU to upload it as 3D, which was broken due to the incorrect layout. In the second, the data may be randomly flushed if it falls out of the cache, but it will appear correct if it's able to form copy dependencies. This change only allows gob sizes to be reduced once per mip level. For the purpose of aligned size, it can still be reduced infinitely as our texture cache isn't properly able to handle a view being _misaligned_. The SizeCalculator has also been changed to reduce the size of rendered depth slices to only include the exact range a single depth slice will cover. (before, the size was way too small with gobs in z reduced to 1, and too large when using the correct value) Gobs in Y logic remains untouched, we don't support Y slices of textures so it's fine as is. This is probably worth testing in a few games as it also affects texture size and view logic. * Improve wording * Maybe a bit better
2023-06-04 22:25:57 +02:00
int gobBlocksInZ,
int level = int.MaxValue)
2019-10-13 08:02:07 +02:00
{
height = BitUtils.DivRoundUp(height, blockHeight);
while (height <= (gobBlocksInY >> 1) * GobHeight && gobBlocksInY != 1)
{
gobBlocksInY >>= 1;
}
Texture: Fix layout conversion when gobs in z is used with depth = 1 (#5220) * Texture: Fix layout conversion when gobs in z is used with depth = 1 The size calculator methods deliberately reduce the gob size of textures if they are deemed too small for it. This is required to get correct sizes when iterating mip levels of a texture. Rendering to a slice of a 3D texture can produce a 3D texture with depth 1, but a gob size matching a much larger texture. We _can't_ "correct" this gob size, as it is intended as a slice of a larger 3D texture. Ignoring it causes layout conversion to break on read and flush. This caused an issue in Tears of the Kingdom where the compressed 3D texture used for the gloom would always break on OpenGL, and seemingly randomly break on Vulkan. In the first case, the data is forcibly flushed to decompress the BC4 texture on the CPU to upload it as 3D, which was broken due to the incorrect layout. In the second, the data may be randomly flushed if it falls out of the cache, but it will appear correct if it's able to form copy dependencies. This change only allows gob sizes to be reduced once per mip level. For the purpose of aligned size, it can still be reduced infinitely as our texture cache isn't properly able to handle a view being _misaligned_. The SizeCalculator has also been changed to reduce the size of rendered depth slices to only include the exact range a single depth slice will cover. (before, the size was way too small with gobs in z reduced to 1, and too large when using the correct value) Gobs in Y logic remains untouched, we don't support Y slices of textures so it's fine as is. This is probably worth testing in a few games as it also affects texture size and view logic. * Improve wording * Maybe a bit better
2023-06-04 22:25:57 +02:00
while (level-- > 0 && depth <= (gobBlocksInZ >> 1) && gobBlocksInZ != 1)
2019-10-13 08:02:07 +02:00
{
gobBlocksInZ >>= 1;
}
return (gobBlocksInY, gobBlocksInZ);
}
}
}