/ src / Ryujinx.Graphics.Texture / SizeCalculator.cs
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  }