/ src / Ryujinx.Graphics.Shader / Translation / ResourceManager.cs
ResourceManager.cs
  1  using Ryujinx.Common;
  2  using Ryujinx.Graphics.Shader.IntermediateRepresentation;
  3  using Ryujinx.Graphics.Shader.StructuredIr;
  4  using System;
  5  using System.Collections.Generic;
  6  using System.Globalization;
  7  
  8  namespace Ryujinx.Graphics.Shader.Translation
  9  {
 10      class ResourceManager
 11      {
 12          // Those values are used if the shader as local or shared memory access,
 13          // but for some reason the supplied size was 0.
 14          private const int DefaultLocalMemorySize = 128;
 15          private const int DefaultSharedMemorySize = 4096;
 16  
 17          private static readonly string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
 18  
 19          private readonly IGpuAccessor _gpuAccessor;
 20          private readonly ShaderStage _stage;
 21          private readonly string _stagePrefix;
 22  
 23          private readonly SetBindingPair[] _cbSlotToBindingMap;
 24          private readonly SetBindingPair[] _sbSlotToBindingMap;
 25          private uint _sbSlotWritten;
 26  
 27          private readonly Dictionary<int, int> _sbSlots;
 28          private readonly Dictionary<int, int> _sbSlotsReverse;
 29  
 30          private readonly HashSet<int> _usedConstantBufferBindings;
 31  
 32          private readonly record struct TextureInfo(int CbufSlot, int Handle, int ArrayLength, bool Separate, SamplerType Type, TextureFormat Format);
 33  
 34          private struct TextureMeta
 35          {
 36              public int Set;
 37              public int Binding;
 38              public bool AccurateType;
 39              public SamplerType Type;
 40              public TextureUsageFlags UsageFlags;
 41          }
 42  
 43          private readonly Dictionary<TextureInfo, TextureMeta> _usedTextures;
 44          private readonly Dictionary<TextureInfo, TextureMeta> _usedImages;
 45  
 46          public int LocalMemoryId { get; private set; }
 47          public int SharedMemoryId { get; private set; }
 48  
 49          public int LocalVertexDataMemoryId { get; private set; }
 50          public int LocalTopologyRemapMemoryId { get; private set; }
 51          public int LocalVertexIndexVertexRateMemoryId { get; private set; }
 52          public int LocalVertexIndexInstanceRateMemoryId { get; private set; }
 53          public int LocalGeometryOutputVertexCountMemoryId { get; private set; }
 54          public int LocalGeometryOutputIndexCountMemoryId { get; private set; }
 55  
 56          public ShaderProperties Properties { get; }
 57  
 58          public ResourceReservations Reservations { get; }
 59  
 60          public ResourceManager(ShaderStage stage, IGpuAccessor gpuAccessor, ResourceReservations reservations = null)
 61          {
 62              _gpuAccessor = gpuAccessor;
 63              Properties = new();
 64              Reservations = reservations;
 65              _stage = stage;
 66              _stagePrefix = GetShaderStagePrefix(stage);
 67  
 68              _cbSlotToBindingMap = new SetBindingPair[18];
 69              _sbSlotToBindingMap = new SetBindingPair[16];
 70              _cbSlotToBindingMap.AsSpan().Fill(new(-1, -1));
 71              _sbSlotToBindingMap.AsSpan().Fill(new(-1, -1));
 72  
 73              _sbSlots = new();
 74              _sbSlotsReverse = new();
 75  
 76              _usedConstantBufferBindings = new();
 77  
 78              _usedTextures = new();
 79              _usedImages = new();
 80  
 81              Properties.AddOrUpdateConstantBuffer(new(BufferLayout.Std140, 0, SupportBuffer.Binding, "support_buffer", SupportBuffer.GetStructureType()));
 82  
 83              LocalMemoryId = -1;
 84              SharedMemoryId = -1;
 85          }
 86  
 87          public void SetCurrentLocalMemory(int size, bool isUsed)
 88          {
 89              if (isUsed)
 90              {
 91                  if (size <= 0)
 92                  {
 93                      size = DefaultLocalMemorySize;
 94                  }
 95  
 96                  var lmem = new MemoryDefinition("local_memory", AggregateType.Array | AggregateType.U32, BitUtils.DivRoundUp(size, sizeof(uint)));
 97  
 98                  LocalMemoryId = Properties.AddLocalMemory(lmem);
 99              }
100              else
101              {
102                  LocalMemoryId = -1;
103              }
104          }
105  
106          public void SetCurrentSharedMemory(int size, bool isUsed)
107          {
108              if (isUsed)
109              {
110                  if (size <= 0)
111                  {
112                      size = DefaultSharedMemorySize;
113                  }
114  
115                  var smem = new MemoryDefinition("shared_memory", AggregateType.Array | AggregateType.U32, BitUtils.DivRoundUp(size, sizeof(uint)));
116  
117                  SharedMemoryId = Properties.AddSharedMemory(smem);
118              }
119              else
120              {
121                  SharedMemoryId = -1;
122              }
123          }
124  
125          public void SetVertexAsComputeLocalMemories(ShaderStage stage, InputTopology inputTopology)
126          {
127              LocalVertexDataMemoryId = AddMemoryDefinition("local_vertex_data", AggregateType.Array | AggregateType.FP32, Reservations.OutputSizePerInvocation);
128  
129              if (stage == ShaderStage.Vertex)
130              {
131                  LocalVertexIndexVertexRateMemoryId = AddMemoryDefinition("local_vertex_index_vr", AggregateType.U32);
132                  LocalVertexIndexInstanceRateMemoryId = AddMemoryDefinition("local_vertex_index_ir", AggregateType.U32);
133              }
134              else if (stage == ShaderStage.Geometry)
135              {
136                  LocalTopologyRemapMemoryId = AddMemoryDefinition("local_topology_remap", AggregateType.Array | AggregateType.U32, inputTopology.ToInputVertices());
137  
138                  LocalGeometryOutputVertexCountMemoryId = AddMemoryDefinition("local_geometry_output_vertex", AggregateType.U32);
139                  LocalGeometryOutputIndexCountMemoryId = AddMemoryDefinition("local_geometry_output_index", AggregateType.U32);
140              }
141          }
142  
143          private int AddMemoryDefinition(string name, AggregateType type, int arrayLength = 1)
144          {
145              return Properties.AddLocalMemory(new MemoryDefinition(name, type, arrayLength));
146          }
147  
148          public int GetConstantBufferBinding(int slot)
149          {
150              SetBindingPair setAndBinding = _cbSlotToBindingMap[slot];
151              if (setAndBinding.Binding < 0)
152              {
153                  setAndBinding = _gpuAccessor.CreateConstantBufferBinding(slot);
154                  _cbSlotToBindingMap[slot] = setAndBinding;
155                  string slotNumber = slot.ToString(CultureInfo.InvariantCulture);
156                  AddNewConstantBuffer(setAndBinding.SetIndex, setAndBinding.Binding, $"{_stagePrefix}_c{slotNumber}");
157              }
158  
159              return setAndBinding.Binding;
160          }
161  
162          public bool TryGetStorageBufferBinding(int sbCbSlot, int sbCbOffset, bool write, out int binding)
163          {
164              if (!TryGetSbSlot((byte)sbCbSlot, (ushort)sbCbOffset, out int slot))
165              {
166                  binding = 0;
167                  return false;
168              }
169  
170              SetBindingPair setAndBinding = _sbSlotToBindingMap[slot];
171  
172              if (setAndBinding.Binding < 0)
173              {
174                  setAndBinding = _gpuAccessor.CreateStorageBufferBinding(slot);
175                  _sbSlotToBindingMap[slot] = setAndBinding;
176                  string slotNumber = slot.ToString(CultureInfo.InvariantCulture);
177                  AddNewStorageBuffer(setAndBinding.SetIndex, setAndBinding.Binding, $"{_stagePrefix}_s{slotNumber}");
178              }
179  
180              if (write)
181              {
182                  _sbSlotWritten |= 1u << slot;
183              }
184  
185              binding = setAndBinding.Binding;
186              return true;
187          }
188  
189          private bool TryGetSbSlot(byte sbCbSlot, ushort sbCbOffset, out int slot)
190          {
191              int key = PackSbCbInfo(sbCbSlot, sbCbOffset);
192  
193              if (!_sbSlots.TryGetValue(key, out slot))
194              {
195                  slot = _sbSlots.Count;
196  
197                  if (slot >= _sbSlotToBindingMap.Length)
198                  {
199                      return false;
200                  }
201  
202                  _sbSlots.Add(key, slot);
203                  _sbSlotsReverse.Add(slot, key);
204              }
205  
206              return true;
207          }
208  
209          public bool TryGetConstantBufferSlot(int binding, out int slot)
210          {
211              for (slot = 0; slot < _cbSlotToBindingMap.Length; slot++)
212              {
213                  if (_cbSlotToBindingMap[slot].Binding == binding)
214                  {
215                      return true;
216                  }
217              }
218  
219              slot = 0;
220              return false;
221          }
222  
223          public SetBindingPair GetTextureOrImageBinding(
224              Instruction inst,
225              SamplerType type,
226              TextureFormat format,
227              TextureFlags flags,
228              int cbufSlot,
229              int handle,
230              int arrayLength = 1,
231              bool separate = false)
232          {
233              inst &= Instruction.Mask;
234              bool isImage = inst.IsImage();
235              bool isWrite = inst.IsImageStore();
236              bool accurateType = !inst.IsTextureQuery();
237              bool intCoords = isImage || flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureQuerySize;
238              bool coherent = flags.HasFlag(TextureFlags.Coherent);
239  
240              if (!isImage)
241              {
242                  format = TextureFormat.Unknown;
243              }
244  
245              SetBindingPair setAndBinding = GetTextureOrImageBinding(
246                  cbufSlot,
247                  handle,
248                  arrayLength,
249                  type,
250                  format,
251                  isImage,
252                  intCoords,
253                  isWrite,
254                  accurateType,
255                  coherent,
256                  separate);
257  
258              _gpuAccessor.RegisterTexture(handle, cbufSlot);
259  
260              return setAndBinding;
261          }
262  
263          private SetBindingPair GetTextureOrImageBinding(
264              int cbufSlot,
265              int handle,
266              int arrayLength,
267              SamplerType type,
268              TextureFormat format,
269              bool isImage,
270              bool intCoords,
271              bool write,
272              bool accurateType,
273              bool coherent,
274              bool separate)
275          {
276              var dimensions = type == SamplerType.None ? 0 : type.GetDimensions();
277              var dict = isImage ? _usedImages : _usedTextures;
278  
279              var usageFlags = TextureUsageFlags.None;
280  
281              if (intCoords)
282              {
283                  usageFlags |= TextureUsageFlags.NeedsScaleValue;
284  
285                  var canScale = _stage.SupportsRenderScale() && arrayLength == 1 && !write && dimensions == 2;
286  
287                  if (!canScale)
288                  {
289                      // Resolution scaling cannot be applied to this texture right now.
290                      // Flag so that we know to blacklist scaling on related textures when binding them.
291                      usageFlags |= TextureUsageFlags.ResScaleUnsupported;
292                  }
293              }
294  
295              if (write)
296              {
297                  usageFlags |= TextureUsageFlags.ImageStore;
298              }
299  
300              if (coherent)
301              {
302                  usageFlags |= TextureUsageFlags.ImageCoherent;
303              }
304  
305              // For array textures, we also want to use type as key,
306              // since we may have texture handles stores in the same buffer, but for textures with different types.
307              var keyType = arrayLength > 1 ? type : SamplerType.None;
308              var info = new TextureInfo(cbufSlot, handle, arrayLength, separate, keyType, format);
309              var meta = new TextureMeta()
310              {
311                  AccurateType = accurateType,
312                  Type = type,
313                  UsageFlags = usageFlags,
314              };
315  
316              int setIndex;
317              int binding;
318  
319              if (dict.TryGetValue(info, out var existingMeta))
320              {
321                  dict[info] = MergeTextureMeta(meta, existingMeta);
322                  setIndex = existingMeta.Set;
323                  binding = existingMeta.Binding;
324              }
325              else
326              {
327                  if (arrayLength > 1 && (setIndex = _gpuAccessor.CreateExtraSet()) >= 0)
328                  {
329                      // We reserved an "extra set" for the array.
330                      // In this case the binding is always the first one (0).
331                      // Using separate sets for array is better as we need to do less descriptor set updates.
332  
333                      binding = 0;
334                  }
335                  else
336                  {
337                      bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer;
338  
339                      SetBindingPair setAndBinding = isImage
340                          ? _gpuAccessor.CreateImageBinding(arrayLength, isBuffer)
341                          : _gpuAccessor.CreateTextureBinding(arrayLength, isBuffer);
342  
343                      setIndex = setAndBinding.SetIndex;
344                      binding = setAndBinding.Binding;
345                  }
346  
347                  meta.Set = setIndex;
348                  meta.Binding = binding;
349  
350                  dict.Add(info, meta);
351              }
352  
353              string nameSuffix;
354              string prefix = isImage ? "i" : "t";
355  
356              if (arrayLength != 1 && type != SamplerType.None)
357              {
358                  prefix += type.ToShortSamplerType();
359              }
360  
361              if (isImage)
362              {
363                  nameSuffix = cbufSlot < 0
364                      ? $"{prefix}_tcb_{handle:X}_{format.ToGlslFormat()}"
365                      : $"{prefix}_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}";
366              }
367              else if (type == SamplerType.None)
368              {
369                  nameSuffix = cbufSlot < 0 ? $"s_tcb_{handle:X}" : $"s_cb{cbufSlot}_{handle:X}";
370              }
371              else
372              {
373                  nameSuffix = cbufSlot < 0 ? $"{prefix}_tcb_{handle:X}" : $"{prefix}_cb{cbufSlot}_{handle:X}";
374              }
375  
376              var definition = new TextureDefinition(
377                  setIndex,
378                  binding,
379                  arrayLength,
380                  separate,
381                  $"{_stagePrefix}_{nameSuffix}",
382                  meta.Type,
383                  info.Format,
384                  meta.UsageFlags);
385  
386              if (isImage)
387              {
388                  Properties.AddOrUpdateImage(definition);
389              }
390              else
391              {
392                  Properties.AddOrUpdateTexture(definition);
393              }
394  
395              return new SetBindingPair(setIndex, binding);
396          }
397  
398          private static TextureMeta MergeTextureMeta(TextureMeta meta, TextureMeta existingMeta)
399          {
400              meta.Set = existingMeta.Set;
401              meta.Binding = existingMeta.Binding;
402              meta.UsageFlags |= existingMeta.UsageFlags;
403  
404              // If the texture we have has inaccurate type information, then
405              // we prefer the most accurate one.
406              if (existingMeta.AccurateType)
407              {
408                  meta.AccurateType = true;
409                  meta.Type = existingMeta.Type;
410              }
411  
412              return meta;
413          }
414  
415          public void SetUsageFlagsForTextureQuery(int binding, SamplerType type)
416          {
417              TextureInfo selectedInfo = default;
418              TextureMeta selectedMeta = default;
419              bool found = false;
420  
421              foreach ((TextureInfo info, TextureMeta meta) in _usedTextures)
422              {
423                  if (meta.Binding == binding)
424                  {
425                      selectedInfo = info;
426                      selectedMeta = meta;
427                      found = true;
428                      break;
429                  }
430              }
431  
432              if (found)
433              {
434                  selectedMeta.UsageFlags |= TextureUsageFlags.NeedsScaleValue;
435  
436                  var dimensions = type.GetDimensions();
437                  var canScale = _stage.SupportsRenderScale() && selectedInfo.ArrayLength == 1 && dimensions == 2;
438  
439                  if (!canScale)
440                  {
441                      // Resolution scaling cannot be applied to this texture right now.
442                      // Flag so that we know to blacklist scaling on related textures when binding them.
443                      selectedMeta.UsageFlags |= TextureUsageFlags.ResScaleUnsupported;
444                  }
445  
446                  _usedTextures[selectedInfo] = selectedMeta;
447              }
448          }
449  
450          public void SetUsedConstantBufferBinding(int binding)
451          {
452              _usedConstantBufferBindings.Add(binding);
453          }
454  
455          public BufferDescriptor[] GetConstantBufferDescriptors()
456          {
457              var descriptors = new BufferDescriptor[_usedConstantBufferBindings.Count];
458  
459              int descriptorIndex = 0;
460  
461              for (int slot = 0; slot < _cbSlotToBindingMap.Length; slot++)
462              {
463                  SetBindingPair setAndBinding = _cbSlotToBindingMap[slot];
464  
465                  if (setAndBinding.Binding >= 0 && _usedConstantBufferBindings.Contains(setAndBinding.Binding))
466                  {
467                      descriptors[descriptorIndex++] = new BufferDescriptor(setAndBinding.SetIndex, setAndBinding.Binding, slot);
468                  }
469              }
470  
471              if (descriptors.Length != descriptorIndex)
472              {
473                  Array.Resize(ref descriptors, descriptorIndex);
474              }
475  
476              return descriptors;
477          }
478  
479          public BufferDescriptor[] GetStorageBufferDescriptors()
480          {
481              var descriptors = new BufferDescriptor[_sbSlots.Count];
482  
483              int descriptorIndex = 0;
484  
485              foreach ((int key, int slot) in _sbSlots)
486              {
487                  SetBindingPair setAndBinding = _sbSlotToBindingMap[slot];
488  
489                  if (setAndBinding.Binding >= 0)
490                  {
491                      (int sbCbSlot, int sbCbOffset) = UnpackSbCbInfo(key);
492                      BufferUsageFlags flags = (_sbSlotWritten & (1u << slot)) != 0 ? BufferUsageFlags.Write : BufferUsageFlags.None;
493                      descriptors[descriptorIndex++] = new BufferDescriptor(setAndBinding.SetIndex, setAndBinding.Binding, slot, sbCbSlot, sbCbOffset, flags);
494                  }
495              }
496  
497              if (descriptors.Length != descriptorIndex)
498              {
499                  Array.Resize(ref descriptors, descriptorIndex);
500              }
501  
502              return descriptors;
503          }
504  
505          public TextureDescriptor[] GetTextureDescriptors(bool includeArrays = true)
506          {
507              return GetDescriptors(_usedTextures, includeArrays);
508          }
509  
510          public TextureDescriptor[] GetImageDescriptors(bool includeArrays = true)
511          {
512              return GetDescriptors(_usedImages, includeArrays);
513          }
514  
515          private static TextureDescriptor[] GetDescriptors(IReadOnlyDictionary<TextureInfo, TextureMeta> usedResources, bool includeArrays)
516          {
517              List<TextureDescriptor> descriptors = new();
518  
519              bool hasAnyArray = false;
520  
521              foreach ((TextureInfo info, TextureMeta meta) in usedResources)
522              {
523                  if (info.ArrayLength > 1)
524                  {
525                      hasAnyArray = true;
526                      continue;
527                  }
528  
529                  descriptors.Add(new TextureDescriptor(
530                      meta.Set,
531                      meta.Binding,
532                      meta.Type,
533                      info.Format,
534                      info.CbufSlot,
535                      info.Handle,
536                      info.ArrayLength,
537                      info.Separate,
538                      meta.UsageFlags));
539              }
540  
541              if (hasAnyArray && includeArrays)
542              {
543                  foreach ((TextureInfo info, TextureMeta meta) in usedResources)
544                  {
545                      if (info.ArrayLength <= 1)
546                      {
547                          continue;
548                      }
549  
550                      descriptors.Add(new TextureDescriptor(
551                          meta.Set,
552                          meta.Binding,
553                          meta.Type,
554                          info.Format,
555                          info.CbufSlot,
556                          info.Handle,
557                          info.ArrayLength,
558                          info.Separate,
559                          meta.UsageFlags));
560                  }
561              }
562  
563              return descriptors.ToArray();
564          }
565  
566          public bool TryGetCbufSlotAndHandleForTexture(int binding, out int cbufSlot, out int handle)
567          {
568              foreach ((TextureInfo info, TextureMeta meta) in _usedTextures)
569              {
570                  if (meta.Binding == binding)
571                  {
572                      cbufSlot = info.CbufSlot;
573                      handle = info.Handle;
574  
575                      return true;
576                  }
577              }
578  
579              cbufSlot = 0;
580              handle = 0;
581              return false;
582          }
583  
584          private static int FindDescriptorIndex(TextureDescriptor[] array, int binding)
585          {
586              return Array.FindIndex(array, x => x.Binding == binding);
587          }
588  
589          public int FindTextureDescriptorIndex(int binding)
590          {
591              return FindDescriptorIndex(GetTextureDescriptors(), binding);
592          }
593  
594          public int FindImageDescriptorIndex(int binding)
595          {
596              return FindDescriptorIndex(GetImageDescriptors(), binding);
597          }
598  
599          public bool IsArrayOfTexturesOrImages(int binding, bool isImage)
600          {
601              foreach ((TextureInfo info, TextureMeta meta) in isImage ? _usedImages : _usedTextures)
602              {
603                  if (meta.Binding == binding)
604                  {
605                      return info.ArrayLength != 1;
606                  }
607              }
608  
609              return false;
610          }
611  
612          private void AddNewConstantBuffer(int setIndex, int binding, string name)
613          {
614              StructureType type = new(new[]
615              {
616                  new StructureField(AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32, "data", Constants.ConstantBufferSize / 16),
617              });
618  
619              Properties.AddOrUpdateConstantBuffer(new(BufferLayout.Std140, setIndex, binding, name, type));
620          }
621  
622          private void AddNewStorageBuffer(int setIndex, int binding, string name)
623          {
624              StructureType type = new(new[]
625              {
626                  new StructureField(AggregateType.Array | AggregateType.U32, "data", 0),
627              });
628  
629              Properties.AddOrUpdateStorageBuffer(new(BufferLayout.Std430, setIndex, binding, name, type));
630          }
631  
632          public static string GetShaderStagePrefix(ShaderStage stage)
633          {
634              uint index = (uint)stage;
635  
636              return index >= _stagePrefixes.Length ? "invalid" : _stagePrefixes[index];
637          }
638  
639          private static int PackSbCbInfo(int sbCbSlot, int sbCbOffset)
640          {
641              return sbCbOffset | (sbCbSlot << 16);
642          }
643  
644          private static (int, int) UnpackSbCbInfo(int key)
645          {
646              return ((byte)(key >> 16), (ushort)key);
647          }
648      }
649  }