SizeCalculator.cs
1 using Ryujinx.Common; 2 using System; 3 using static Ryujinx.Graphics.Texture.BlockLinearConstants; 4 5 namespace Ryujinx.Graphics.Texture 6 { 7 public static class SizeCalculator 8 { 9 private const int StrideAlignment = 32; 10 11 private static int Calculate3DOffsetCount(int levels, int depth) 12 { 13 int offsetCount = depth; 14 15 while (--levels > 0) 16 { 17 depth = Math.Max(1, depth >> 1); 18 offsetCount += depth; 19 } 20 21 return offsetCount; 22 } 23 24 public static SizeInfo GetBlockLinearTextureSize( 25 int width, 26 int height, 27 int depth, 28 int levels, 29 int layers, 30 int blockWidth, 31 int blockHeight, 32 int bytesPerPixel, 33 int gobBlocksInY, 34 int gobBlocksInZ, 35 int gobBlocksInTileX, 36 int gpuLayerSize = 0) 37 { 38 bool is3D = depth > 1 || gobBlocksInZ > 1; 39 40 int layerSize = 0; 41 int layerSizeAligned = 0; 42 43 int[] allOffsets = new int[is3D ? Calculate3DOffsetCount(levels, depth) : levels * layers * depth]; 44 int[] mipOffsets = new int[levels]; 45 int[] sliceSizes = new int[levels]; 46 int[] levelSizes = new int[levels]; 47 48 int mipGobBlocksInY = gobBlocksInY; 49 int mipGobBlocksInZ = gobBlocksInZ; 50 51 int gobWidth = (GobStride / bytesPerPixel) * gobBlocksInTileX; 52 int gobHeight = gobBlocksInY * GobHeight; 53 54 int depthLevelOffset = 0; 55 56 for (int level = 0; level < levels; level++) 57 { 58 int w = Math.Max(1, width >> level); 59 int h = Math.Max(1, height >> level); 60 int d = Math.Max(1, depth >> level); 61 62 w = BitUtils.DivRoundUp(w, blockWidth); 63 h = BitUtils.DivRoundUp(h, blockHeight); 64 65 while (h <= (mipGobBlocksInY >> 1) * GobHeight && mipGobBlocksInY != 1) 66 { 67 mipGobBlocksInY >>= 1; 68 } 69 70 if (level > 0 && d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1) 71 { 72 mipGobBlocksInZ >>= 1; 73 } 74 75 int widthInGobs = BitUtils.DivRoundUp(w * bytesPerPixel, GobStride); 76 77 int alignment = gobBlocksInTileX; 78 79 if (d < gobBlocksInZ || w <= gobWidth || h <= gobHeight) 80 { 81 alignment = 1; 82 } 83 84 widthInGobs = BitUtils.AlignUp(widthInGobs, alignment); 85 86 int totalBlocksOfGobsInZ = BitUtils.DivRoundUp(d, mipGobBlocksInZ); 87 int totalBlocksOfGobsInY = BitUtils.DivRoundUp(BitUtils.DivRoundUp(h, GobHeight), mipGobBlocksInY); 88 89 int robSize = widthInGobs * mipGobBlocksInY * mipGobBlocksInZ * GobSize; 90 91 mipOffsets[level] = layerSize; 92 sliceSizes[level] = totalBlocksOfGobsInY * robSize; 93 levelSizes[level] = totalBlocksOfGobsInZ * sliceSizes[level]; 94 95 layerSizeAligned += levelSizes[level]; 96 97 if (is3D) 98 { 99 int gobSize = mipGobBlocksInY * GobSize; 100 101 int sliceSize = totalBlocksOfGobsInY * widthInGobs * gobSize; 102 103 int baseOffset = layerSize; 104 105 int mask = gobBlocksInZ - 1; 106 107 for (int z = 0; z < d; z++) 108 { 109 int zLow = z & mask; 110 int zHigh = z & ~mask; 111 112 allOffsets[z + depthLevelOffset] = baseOffset + zLow * gobSize + zHigh * sliceSize; 113 } 114 115 int gobRemainderZ = d % mipGobBlocksInZ; 116 117 if (gobRemainderZ != 0 && level == levels - 1) 118 { 119 // The slice only covers up to the end of this slice's depth, rather than the full aligned size. 120 // Avoids size being too large on partial views of 3d textures. 121 122 levelSizes[level] -= gobSize * (mipGobBlocksInZ - gobRemainderZ); 123 124 if (sliceSizes[level] > levelSizes[level]) 125 { 126 sliceSizes[level] = levelSizes[level]; 127 } 128 } 129 } 130 131 layerSize += levelSizes[level]; 132 133 depthLevelOffset += d; 134 } 135 136 int totalSize; 137 138 if (layers > 1) 139 { 140 layerSizeAligned = AlignLayerSize( 141 layerSizeAligned, 142 height, 143 depth, 144 blockHeight, 145 gobBlocksInY, 146 gobBlocksInZ, 147 gobBlocksInTileX); 148 149 if (layerSizeAligned < gpuLayerSize) 150 { 151 totalSize = (layers - 1) * gpuLayerSize + layerSizeAligned; 152 layerSizeAligned = gpuLayerSize; 153 } 154 else 155 { 156 totalSize = layerSizeAligned * layers; 157 } 158 } 159 else 160 { 161 totalSize = layerSize; 162 } 163 164 if (!is3D) 165 { 166 for (int layer = 0; layer < layers; layer++) 167 { 168 int baseIndex = layer * levels; 169 int baseOffset = layer * layerSizeAligned; 170 171 for (int level = 0; level < levels; level++) 172 { 173 allOffsets[baseIndex + level] = baseOffset + mipOffsets[level]; 174 } 175 } 176 } 177 178 return new SizeInfo(mipOffsets, allOffsets, sliceSizes, levelSizes, depth, levels, layerSizeAligned, totalSize, is3D); 179 } 180 181 public static SizeInfo GetLinearTextureSize(int stride, int height, int blockHeight) 182 { 183 // Non-2D or mipmapped linear textures are not supported by the Switch GPU, 184 // so we only need to handle a single case (2D textures without mipmaps). 185 int totalSize = stride * BitUtils.DivRoundUp(height, blockHeight); 186 187 return new SizeInfo(totalSize); 188 } 189 190 private static int AlignLayerSize( 191 int size, 192 int height, 193 int depth, 194 int blockHeight, 195 int gobBlocksInY, 196 int gobBlocksInZ, 197 int gobBlocksInTileX) 198 { 199 if (gobBlocksInTileX < 2) 200 { 201 height = BitUtils.DivRoundUp(height, blockHeight); 202 203 while (height <= (gobBlocksInY >> 1) * GobHeight && gobBlocksInY != 1) 204 { 205 gobBlocksInY >>= 1; 206 } 207 208 while (depth <= (gobBlocksInZ >> 1) && gobBlocksInZ != 1) 209 { 210 gobBlocksInZ >>= 1; 211 } 212 213 int blockOfGobsSize = gobBlocksInY * gobBlocksInZ * GobSize; 214 215 int sizeInBlockOfGobs = size / blockOfGobsSize; 216 217 if (size != sizeInBlockOfGobs * blockOfGobsSize) 218 { 219 size = (sizeInBlockOfGobs + 1) * blockOfGobsSize; 220 } 221 } 222 else 223 { 224 int alignment = (gobBlocksInTileX * GobSize) * gobBlocksInY * gobBlocksInZ; 225 226 size = BitUtils.AlignUp(size, alignment); 227 } 228 229 return size; 230 } 231 232 public static Size GetBlockLinearAlignedSize( 233 int width, 234 int height, 235 int depth, 236 int blockWidth, 237 int blockHeight, 238 int bytesPerPixel, 239 int gobBlocksInY, 240 int gobBlocksInZ, 241 int gobBlocksInTileX) 242 { 243 width = BitUtils.DivRoundUp(width, blockWidth); 244 height = BitUtils.DivRoundUp(height, blockHeight); 245 246 int gobWidth = (GobStride / bytesPerPixel) * gobBlocksInTileX; 247 int gobHeight = gobBlocksInY * GobHeight; 248 249 int alignment = gobWidth; 250 251 if (depth < gobBlocksInZ || width <= gobWidth || height <= gobHeight) 252 { 253 alignment = GobStride / bytesPerPixel; 254 } 255 256 // Height has already been divided by block height, so pass it as 1. 257 (gobBlocksInY, gobBlocksInZ) = GetMipGobBlockSizes(height, depth, 1, gobBlocksInY, gobBlocksInZ); 258 259 int blockOfGobsHeight = gobBlocksInY * GobHeight; 260 int blockOfGobsDepth = gobBlocksInZ; 261 262 width = BitUtils.AlignUp(width, alignment); 263 height = BitUtils.AlignUp(height, blockOfGobsHeight); 264 depth = BitUtils.AlignUp(depth, blockOfGobsDepth); 265 266 return new Size(width, height, depth); 267 } 268 269 public static Size GetLinearAlignedSize( 270 int width, 271 int height, 272 int blockWidth, 273 int blockHeight, 274 int bytesPerPixel) 275 { 276 width = BitUtils.DivRoundUp(width, blockWidth); 277 height = BitUtils.DivRoundUp(height, blockHeight); 278 279 int widthAlignment = StrideAlignment / bytesPerPixel; 280 281 width = BitUtils.AlignUp(width, widthAlignment); 282 283 return new Size(width, height, 1); 284 } 285 286 public static (int, int) GetMipGobBlockSizes( 287 int height, 288 int depth, 289 int blockHeight, 290 int gobBlocksInY, 291 int gobBlocksInZ, 292 int level = int.MaxValue) 293 { 294 height = BitUtils.DivRoundUp(height, blockHeight); 295 296 while (height <= (gobBlocksInY >> 1) * GobHeight && gobBlocksInY != 1) 297 { 298 gobBlocksInY >>= 1; 299 } 300 301 while (level-- > 0 && depth <= (gobBlocksInZ >> 1) && gobBlocksInZ != 1) 302 { 303 gobBlocksInZ >>= 1; 304 } 305 306 return (gobBlocksInY, gobBlocksInZ); 307 } 308 } 309 }