/ src / Ryujinx.Graphics.Vulkan / TextureStorage.cs
TextureStorage.cs
  1  using Ryujinx.Common;
  2  using Ryujinx.Graphics.GAL;
  3  using Silk.NET.Vulkan;
  4  using System;
  5  using System.Collections.Generic;
  6  using System.Numerics;
  7  using System.Runtime.CompilerServices;
  8  using Format = Ryujinx.Graphics.GAL.Format;
  9  using VkBuffer = Silk.NET.Vulkan.Buffer;
 10  using VkFormat = Silk.NET.Vulkan.Format;
 11  
 12  namespace Ryujinx.Graphics.Vulkan
 13  {
 14      class TextureStorage : IDisposable
 15      {
 16          private struct TextureSliceInfo
 17          {
 18              public int BindCount;
 19          }
 20  
 21          private const MemoryPropertyFlags DefaultImageMemoryFlags =
 22              MemoryPropertyFlags.DeviceLocalBit;
 23  
 24          private const ImageUsageFlags DefaultUsageFlags =
 25              ImageUsageFlags.SampledBit |
 26              ImageUsageFlags.TransferSrcBit |
 27              ImageUsageFlags.TransferDstBit;
 28  
 29          public const AccessFlags DefaultAccessMask =
 30              AccessFlags.ShaderReadBit |
 31              AccessFlags.ShaderWriteBit |
 32              AccessFlags.ColorAttachmentReadBit |
 33              AccessFlags.ColorAttachmentWriteBit |
 34              AccessFlags.DepthStencilAttachmentReadBit |
 35              AccessFlags.DepthStencilAttachmentWriteBit |
 36              AccessFlags.TransferReadBit |
 37              AccessFlags.TransferWriteBit;
 38  
 39          private readonly VulkanRenderer _gd;
 40  
 41          private readonly Device _device;
 42  
 43          private TextureCreateInfo _info;
 44  
 45          public TextureCreateInfo Info => _info;
 46  
 47          public bool Disposed { get; private set; }
 48  
 49          private readonly Image _image;
 50          private readonly Auto<DisposableImage> _imageAuto;
 51          private readonly Auto<MemoryAllocation> _allocationAuto;
 52          private readonly int _depthOrLayers;
 53          private Auto<MemoryAllocation> _foreignAllocationAuto;
 54  
 55          private Dictionary<Format, TextureStorage> _aliasedStorages;
 56  
 57          private AccessFlags _lastModificationAccess;
 58          private PipelineStageFlags _lastModificationStage;
 59          private AccessFlags _lastReadAccess;
 60          private PipelineStageFlags _lastReadStage;
 61  
 62          private int _viewsCount;
 63          private readonly ulong _size;
 64  
 65          private int _bindCount;
 66          private readonly TextureSliceInfo[] _slices;
 67  
 68          public VkFormat VkFormat { get; }
 69  
 70          public unsafe TextureStorage(
 71              VulkanRenderer gd,
 72              Device device,
 73              TextureCreateInfo info,
 74              Auto<MemoryAllocation> foreignAllocation = null)
 75          {
 76              _gd = gd;
 77              _device = device;
 78              _info = info;
 79  
 80              var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format);
 81              var levels = (uint)info.Levels;
 82              var layers = (uint)info.GetLayers();
 83              var depth = (uint)(info.Target == Target.Texture3D ? info.Depth : 1);
 84  
 85              VkFormat = format;
 86              _depthOrLayers = info.GetDepthOrLayers();
 87  
 88              var type = info.Target.Convert();
 89  
 90              var extent = new Extent3D((uint)info.Width, (uint)info.Height, depth);
 91  
 92              var sampleCountFlags = ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)info.Samples);
 93  
 94              var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities);
 95  
 96              var flags = ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit;
 97  
 98              // This flag causes mipmapped texture arrays to break on AMD GCN, so for that copy dependencies are forced for aliasing as cube.
 99              bool isCube = info.Target == Target.Cubemap || info.Target == Target.CubemapArray;
100              bool cubeCompatible = gd.IsAmdGcn ? isCube : (info.Width == info.Height && layers >= 6);
101  
102              if (type == ImageType.Type2D && cubeCompatible)
103              {
104                  flags |= ImageCreateFlags.CreateCubeCompatibleBit;
105              }
106  
107              if (type == ImageType.Type3D && !gd.Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.No3DImageView))
108              {
109                  flags |= ImageCreateFlags.Create2DArrayCompatibleBit;
110              }
111  
112              var imageCreateInfo = new ImageCreateInfo
113              {
114                  SType = StructureType.ImageCreateInfo,
115                  ImageType = type,
116                  Format = format,
117                  Extent = extent,
118                  MipLevels = levels,
119                  ArrayLayers = layers,
120                  Samples = sampleCountFlags,
121                  Tiling = ImageTiling.Optimal,
122                  Usage = usage,
123                  SharingMode = SharingMode.Exclusive,
124                  InitialLayout = ImageLayout.Undefined,
125                  Flags = flags,
126              };
127  
128              gd.Api.CreateImage(device, in imageCreateInfo, null, out _image).ThrowOnError();
129  
130              if (foreignAllocation == null)
131              {
132                  gd.Api.GetImageMemoryRequirements(device, _image, out var requirements);
133                  var allocation = gd.MemoryAllocator.AllocateDeviceMemory(requirements, DefaultImageMemoryFlags);
134  
135                  if (allocation.Memory.Handle == 0UL)
136                  {
137                      gd.Api.DestroyImage(device, _image, null);
138                      throw new Exception("Image initialization failed.");
139                  }
140  
141                  _size = requirements.Size;
142  
143                  gd.Api.BindImageMemory(device, _image, allocation.Memory, allocation.Offset).ThrowOnError();
144  
145                  _allocationAuto = new Auto<MemoryAllocation>(allocation);
146                  _imageAuto = new Auto<DisposableImage>(new DisposableImage(_gd.Api, device, _image), null, _allocationAuto);
147  
148                  InitialTransition(ImageLayout.Undefined, ImageLayout.General);
149              }
150              else
151              {
152                  _foreignAllocationAuto = foreignAllocation;
153                  foreignAllocation.IncrementReferenceCount();
154                  var allocation = foreignAllocation.GetUnsafe();
155  
156                  gd.Api.BindImageMemory(device, _image, allocation.Memory, allocation.Offset).ThrowOnError();
157  
158                  _imageAuto = new Auto<DisposableImage>(new DisposableImage(_gd.Api, device, _image));
159  
160                  InitialTransition(ImageLayout.Preinitialized, ImageLayout.General);
161              }
162  
163              _slices = new TextureSliceInfo[levels * _depthOrLayers];
164          }
165  
166          public TextureStorage CreateAliasedColorForDepthStorageUnsafe(Format format)
167          {
168              var colorFormat = format switch
169              {
170                  Format.S8Uint => Format.R8Unorm,
171                  Format.D16Unorm => Format.R16Unorm,
172                  Format.D24UnormS8Uint or Format.S8UintD24Unorm or Format.X8UintD24Unorm => Format.R8G8B8A8Unorm,
173                  Format.D32Float => Format.R32Float,
174                  Format.D32FloatS8Uint => Format.R32G32Float,
175                  _ => throw new ArgumentException($"\"{format}\" is not a supported depth or stencil format."),
176              };
177  
178              return CreateAliasedStorageUnsafe(colorFormat);
179          }
180  
181          public TextureStorage CreateAliasedStorageUnsafe(Format format)
182          {
183              if (_aliasedStorages == null || !_aliasedStorages.TryGetValue(format, out var storage))
184              {
185                  _aliasedStorages ??= new Dictionary<Format, TextureStorage>();
186  
187                  var info = NewCreateInfoWith(ref _info, format, _info.BytesPerPixel);
188  
189                  storage = new TextureStorage(_gd, _device, info, _allocationAuto);
190  
191                  _aliasedStorages.Add(format, storage);
192              }
193  
194              return storage;
195          }
196  
197          public static TextureCreateInfo NewCreateInfoWith(ref TextureCreateInfo info, Format format, int bytesPerPixel)
198          {
199              return NewCreateInfoWith(ref info, format, bytesPerPixel, info.Width, info.Height);
200          }
201  
202          public static TextureCreateInfo NewCreateInfoWith(
203              ref TextureCreateInfo info,
204              Format format,
205              int bytesPerPixel,
206              int width,
207              int height)
208          {
209              return new TextureCreateInfo(
210                  width,
211                  height,
212                  info.Depth,
213                  info.Levels,
214                  info.Samples,
215                  info.BlockWidth,
216                  info.BlockHeight,
217                  bytesPerPixel,
218                  format,
219                  info.DepthStencilMode,
220                  info.Target,
221                  info.SwizzleR,
222                  info.SwizzleG,
223                  info.SwizzleB,
224                  info.SwizzleA);
225          }
226  
227          public Auto<DisposableImage> GetImage()
228          {
229              return _imageAuto;
230          }
231  
232          public Image GetImageForViewCreation()
233          {
234              return _image;
235          }
236  
237          public bool HasCommandBufferDependency(CommandBufferScoped cbs)
238          {
239              if (_foreignAllocationAuto != null)
240              {
241                  return _foreignAllocationAuto.HasCommandBufferDependency(cbs);
242              }
243              else if (_allocationAuto != null)
244              {
245                  return _allocationAuto.HasCommandBufferDependency(cbs);
246              }
247  
248              return false;
249          }
250  
251          private unsafe void InitialTransition(ImageLayout srcLayout, ImageLayout dstLayout)
252          {
253              CommandBufferScoped cbs;
254              bool useTempCbs = !_gd.CommandBufferPool.OwnedByCurrentThread;
255  
256              if (useTempCbs)
257              {
258                  cbs = _gd.BackgroundResources.Get().GetPool().Rent();
259              }
260              else
261              {
262                  if (_gd.PipelineInternal != null)
263                  {
264                      cbs = _gd.PipelineInternal.GetPreloadCommandBuffer();
265                  }
266                  else
267                  {
268                      cbs = _gd.CommandBufferPool.Rent();
269                      useTempCbs = true;
270                  }
271              }
272  
273              var aspectFlags = _info.Format.ConvertAspectFlags();
274  
275              var subresourceRange = new ImageSubresourceRange(aspectFlags, 0, (uint)_info.Levels, 0, (uint)_info.GetLayers());
276  
277              var barrier = new ImageMemoryBarrier
278              {
279                  SType = StructureType.ImageMemoryBarrier,
280                  SrcAccessMask = 0,
281                  DstAccessMask = DefaultAccessMask,
282                  OldLayout = srcLayout,
283                  NewLayout = dstLayout,
284                  SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
285                  DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
286                  Image = _imageAuto.Get(cbs).Value,
287                  SubresourceRange = subresourceRange,
288              };
289  
290              _gd.Api.CmdPipelineBarrier(
291                  cbs.CommandBuffer,
292                  PipelineStageFlags.TopOfPipeBit,
293                  PipelineStageFlags.AllCommandsBit,
294                  0,
295                  0,
296                  null,
297                  0,
298                  null,
299                  1,
300                  in barrier);
301  
302              if (useTempCbs)
303              {
304                  cbs.Dispose();
305              }
306          }
307  
308          public static ImageUsageFlags GetImageUsage(Format format, Target target, in HardwareCapabilities capabilities)
309          {
310              var usage = DefaultUsageFlags;
311  
312              if (format.IsDepthOrStencil())
313              {
314                  usage |= ImageUsageFlags.DepthStencilAttachmentBit;
315              }
316              else if (format.IsRtColorCompatible())
317              {
318                  usage |= ImageUsageFlags.ColorAttachmentBit;
319              }
320  
321              bool supportsMsStorage = capabilities.SupportsShaderStorageImageMultisample;
322  
323              if (format.IsImageCompatible() && (supportsMsStorage || !target.IsMultisample()))
324              {
325                  usage |= ImageUsageFlags.StorageBit;
326              }
327  
328              if (capabilities.SupportsAttachmentFeedbackLoop &&
329                  (usage & (ImageUsageFlags.DepthStencilAttachmentBit | ImageUsageFlags.ColorAttachmentBit)) != 0)
330              {
331                  usage |= ImageUsageFlags.AttachmentFeedbackLoopBitExt;
332              }
333  
334              return usage;
335          }
336  
337          public static SampleCountFlags ConvertToSampleCountFlags(SampleCountFlags supportedSampleCounts, uint samples)
338          {
339              if (samples == 0 || samples > (uint)SampleCountFlags.Count64Bit)
340              {
341                  return SampleCountFlags.Count1Bit;
342              }
343  
344              // Round up to the nearest power of two.
345              SampleCountFlags converted = (SampleCountFlags)(1u << (31 - BitOperations.LeadingZeroCount(samples)));
346  
347              // Pick nearest sample count that the host actually supports.
348              while (converted != SampleCountFlags.Count1Bit && (converted & supportedSampleCounts) == 0)
349              {
350                  converted = (SampleCountFlags)((uint)converted >> 1);
351              }
352  
353              return converted;
354          }
355  
356          public TextureView CreateView(TextureCreateInfo info, int firstLayer, int firstLevel)
357          {
358              return new TextureView(_gd, _device, info, this, firstLayer, firstLevel);
359          }
360  
361          public void CopyFromOrToBuffer(
362              CommandBuffer commandBuffer,
363              VkBuffer buffer,
364              Image image,
365              int size,
366              bool to,
367              int x,
368              int y,
369              int dstLayer,
370              int dstLevel,
371              int dstLayers,
372              int dstLevels,
373              bool singleSlice,
374              ImageAspectFlags aspectFlags,
375              bool forFlush)
376          {
377              bool is3D = Info.Target == Target.Texture3D;
378              int width = Info.Width;
379              int height = Info.Height;
380              int depth = is3D && !singleSlice ? Info.Depth : 1;
381              int layer = is3D ? 0 : dstLayer;
382              int layers = dstLayers;
383              int levels = dstLevels;
384  
385              int offset = 0;
386  
387              for (int level = 0; level < levels; level++)
388              {
389                  int mipSize = Info.GetMipSize(level);
390  
391                  if (forFlush)
392                  {
393                      mipSize = GetBufferDataLength(mipSize);
394                  }
395  
396                  int endOffset = offset + mipSize;
397  
398                  if ((uint)endOffset > (uint)size)
399                  {
400                      break;
401                  }
402  
403                  int rowLength = (Info.GetMipStride(level) / Info.BytesPerPixel) * Info.BlockWidth;
404  
405                  var sl = new ImageSubresourceLayers(
406                      aspectFlags,
407                      (uint)(dstLevel + level),
408                      (uint)layer,
409                      (uint)layers);
410  
411                  var extent = new Extent3D((uint)width, (uint)height, (uint)depth);
412  
413                  int z = is3D ? dstLayer : 0;
414  
415                  var region = new BufferImageCopy(
416                      (ulong)offset,
417                      (uint)BitUtils.AlignUp(rowLength, Info.BlockWidth),
418                      (uint)BitUtils.AlignUp(height, Info.BlockHeight),
419                      sl,
420                      new Offset3D(x, y, z),
421                      extent);
422  
423                  if (to)
424                  {
425                      _gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, in region);
426                  }
427                  else
428                  {
429                      _gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, in region);
430                  }
431  
432                  offset += mipSize;
433  
434                  width = Math.Max(1, width >> 1);
435                  height = Math.Max(1, height >> 1);
436  
437                  if (Info.Target == Target.Texture3D)
438                  {
439                      depth = Math.Max(1, depth >> 1);
440                  }
441              }
442          }
443  
444          private int GetBufferDataLength(int length)
445          {
446              if (NeedsD24S8Conversion())
447              {
448                  return length * 2;
449              }
450  
451              return length;
452          }
453  
454          private bool NeedsD24S8Conversion()
455          {
456              return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint;
457          }
458  
459          public void AddStoreOpUsage(bool depthStencil)
460          {
461              _lastModificationStage = depthStencil ?
462                  PipelineStageFlags.LateFragmentTestsBit :
463                  PipelineStageFlags.ColorAttachmentOutputBit;
464  
465              _lastModificationAccess = depthStencil ?
466                  AccessFlags.DepthStencilAttachmentWriteBit :
467                  AccessFlags.ColorAttachmentWriteBit;
468          }
469  
470          public void QueueLoadOpBarrier(CommandBufferScoped cbs, bool depthStencil)
471          {
472              PipelineStageFlags srcStageFlags = _lastReadStage | _lastModificationStage;
473              PipelineStageFlags dstStageFlags = depthStencil ?
474                  PipelineStageFlags.EarlyFragmentTestsBit | PipelineStageFlags.LateFragmentTestsBit :
475                  PipelineStageFlags.ColorAttachmentOutputBit;
476  
477              AccessFlags srcAccessFlags = _lastModificationAccess | _lastReadAccess;
478              AccessFlags dstAccessFlags = depthStencil ?
479                  AccessFlags.DepthStencilAttachmentWriteBit | AccessFlags.DepthStencilAttachmentReadBit :
480                  AccessFlags.ColorAttachmentWriteBit | AccessFlags.ColorAttachmentReadBit;
481  
482              if (srcAccessFlags != AccessFlags.None)
483              {
484                  ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
485                  ImageMemoryBarrier barrier = TextureView.GetImageBarrier(
486                      _imageAuto.Get(cbs).Value,
487                      srcAccessFlags,
488                      dstAccessFlags,
489                      aspectFlags,
490                      0,
491                      0,
492                      _info.GetLayers(),
493                      _info.Levels);
494  
495                  _gd.Barriers.QueueBarrier(barrier, this, srcStageFlags, dstStageFlags);
496  
497                  _lastReadStage = PipelineStageFlags.None;
498                  _lastReadAccess = AccessFlags.None;
499              }
500  
501              _lastModificationStage = depthStencil ?
502                  PipelineStageFlags.LateFragmentTestsBit :
503                  PipelineStageFlags.ColorAttachmentOutputBit;
504  
505              _lastModificationAccess = depthStencil ?
506                  AccessFlags.DepthStencilAttachmentWriteBit :
507                  AccessFlags.ColorAttachmentWriteBit;
508          }
509  
510          public void QueueWriteToReadBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
511          {
512              _lastReadAccess |= dstAccessFlags;
513              _lastReadStage |= dstStageFlags;
514  
515              if (_lastModificationAccess != AccessFlags.None)
516              {
517                  ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
518                  ImageMemoryBarrier barrier = TextureView.GetImageBarrier(
519                      _imageAuto.Get(cbs).Value,
520                      _lastModificationAccess,
521                      dstAccessFlags,
522                      aspectFlags,
523                      0,
524                      0,
525                      _info.GetLayers(),
526                      _info.Levels);
527  
528                  _gd.Barriers.QueueBarrier(barrier, this, _lastModificationStage, dstStageFlags);
529  
530                  _lastModificationAccess = AccessFlags.None;
531              }
532          }
533  
534          public void AddBinding(TextureView view)
535          {
536              // Assumes a view only has a first level.
537  
538              int index = view.FirstLevel * _depthOrLayers + view.FirstLayer;
539              int layers = view.Layers;
540  
541              for (int i = 0; i < layers; i++)
542              {
543                  ref TextureSliceInfo info = ref _slices[index++];
544  
545                  info.BindCount++;
546              }
547  
548              _bindCount++;
549          }
550  
551          public void ClearBindings()
552          {
553              if (_bindCount != 0)
554              {
555                  Array.Clear(_slices, 0, _slices.Length);
556  
557                  _bindCount = 0;
558              }
559          }
560  
561          [MethodImpl(MethodImplOptions.AggressiveInlining)]
562          public bool IsBound(TextureView view)
563          {
564              if (_bindCount != 0)
565              {
566                  int index = view.FirstLevel * _depthOrLayers + view.FirstLayer;
567                  int layers = view.Layers;
568  
569                  for (int i = 0; i < layers; i++)
570                  {
571                      ref TextureSliceInfo info = ref _slices[index++];
572  
573                      if (info.BindCount != 0)
574                      {
575                          return true;
576                      }
577                  }
578              }
579  
580              return false;
581          }
582  
583          public void IncrementViewsCount()
584          {
585              _viewsCount++;
586          }
587  
588          public void DecrementViewsCount()
589          {
590              if (--_viewsCount == 0)
591              {
592                  _gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_imageAuto, _size);
593  
594                  Dispose();
595              }
596          }
597  
598          public void Dispose()
599          {
600              Disposed = true;
601  
602              if (_aliasedStorages != null)
603              {
604                  foreach (var storage in _aliasedStorages.Values)
605                  {
606                      storage.Dispose();
607                  }
608  
609                  _aliasedStorages.Clear();
610              }
611  
612              _imageAuto.Dispose();
613              _allocationAuto?.Dispose();
614              _foreignAllocationAuto?.DecrementReferenceCount();
615              _foreignAllocationAuto = null;
616          }
617      }
618  }