aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Vulkan/TextureStorage.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Graphics.Vulkan/TextureStorage.cs')
-rw-r--r--Ryujinx.Graphics.Vulkan/TextureStorage.cs504
1 files changed, 504 insertions, 0 deletions
diff --git a/Ryujinx.Graphics.Vulkan/TextureStorage.cs b/Ryujinx.Graphics.Vulkan/TextureStorage.cs
new file mode 100644
index 00000000..b2cbd602
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/TextureStorage.cs
@@ -0,0 +1,504 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using VkBuffer = Silk.NET.Vulkan.Buffer;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class TextureStorage : IDisposable
+ {
+ private const MemoryPropertyFlags DefaultImageMemoryFlags =
+ MemoryPropertyFlags.MemoryPropertyDeviceLocalBit;
+
+ private const ImageUsageFlags DefaultUsageFlags =
+ ImageUsageFlags.ImageUsageSampledBit |
+ ImageUsageFlags.ImageUsageTransferSrcBit |
+ ImageUsageFlags.ImageUsageTransferDstBit;
+
+ public const AccessFlags DefaultAccessMask =
+ AccessFlags.AccessShaderReadBit |
+ AccessFlags.AccessShaderWriteBit |
+ AccessFlags.AccessColorAttachmentReadBit |
+ AccessFlags.AccessColorAttachmentWriteBit |
+ AccessFlags.AccessDepthStencilAttachmentReadBit |
+ AccessFlags.AccessDepthStencilAttachmentWriteBit |
+ AccessFlags.AccessTransferReadBit |
+ AccessFlags.AccessTransferWriteBit;
+
+ private readonly VulkanRenderer _gd;
+
+ private readonly Device _device;
+
+ private TextureCreateInfo _info;
+
+ public TextureCreateInfo Info => _info;
+
+ private readonly Image _image;
+ private readonly Auto<DisposableImage> _imageAuto;
+ private readonly Auto<MemoryAllocation> _allocationAuto;
+ private Auto<MemoryAllocation> _foreignAllocationAuto;
+
+ private Dictionary<GAL.Format, TextureStorage> _aliasedStorages;
+
+ private AccessFlags _lastModificationAccess;
+ private PipelineStageFlags _lastModificationStage;
+
+ private int _viewsCount;
+ private ulong _size;
+
+ public VkFormat VkFormat { get; }
+ public float ScaleFactor { get; }
+
+ public unsafe TextureStorage(
+ VulkanRenderer gd,
+ PhysicalDevice physicalDevice,
+ Device device,
+ TextureCreateInfo info,
+ float scaleFactor,
+ Auto<MemoryAllocation> foreignAllocation = null)
+ {
+ _gd = gd;
+ _device = device;
+ _info = info;
+ ScaleFactor = scaleFactor;
+
+ var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format);
+ var levels = (uint)info.Levels;
+ var layers = (uint)info.GetLayers();
+ var depth = (uint)(info.Target == Target.Texture3D ? info.Depth : 1);
+
+ VkFormat = format;
+
+ var type = info.Target.Convert();
+
+ var extent = new Extent3D((uint)info.Width, (uint)info.Height, depth);
+
+ var sampleCountFlags = ConvertToSampleCountFlags((uint)info.Samples);
+
+ var usage = DefaultUsageFlags;
+
+ if (info.Format.IsDepthOrStencil())
+ {
+ usage |= ImageUsageFlags.ImageUsageDepthStencilAttachmentBit;
+ }
+ else if (info.Format.IsRtColorCompatible())
+ {
+ usage |= ImageUsageFlags.ImageUsageColorAttachmentBit;
+ }
+
+ if (info.Format.IsImageCompatible())
+ {
+ usage |= ImageUsageFlags.ImageUsageStorageBit;
+ }
+
+ var flags = ImageCreateFlags.ImageCreateMutableFormatBit;
+
+ // This flag causes mipmapped texture arrays to break on AMD GCN, so for that copy dependencies are forced for aliasing as cube.
+ bool isCube = info.Target == Target.Cubemap || info.Target == Target.CubemapArray;
+ bool cubeCompatible = gd.IsAmdGcn ? isCube : (info.Width == info.Height && layers >= 6);
+
+ if (type == ImageType.ImageType2D && cubeCompatible)
+ {
+ flags |= ImageCreateFlags.ImageCreateCubeCompatibleBit;
+ }
+
+ if (type == ImageType.ImageType3D)
+ {
+ flags |= ImageCreateFlags.ImageCreate2DArrayCompatibleBit;
+ }
+
+ var imageCreateInfo = new ImageCreateInfo()
+ {
+ SType = StructureType.ImageCreateInfo,
+ ImageType = type,
+ Format = format,
+ Extent = extent,
+ MipLevels = levels,
+ ArrayLayers = layers,
+ Samples = sampleCountFlags,
+ Tiling = ImageTiling.Optimal,
+ Usage = usage,
+ SharingMode = SharingMode.Exclusive,
+ InitialLayout = ImageLayout.Undefined,
+ Flags = flags
+ };
+
+ gd.Api.CreateImage(device, imageCreateInfo, null, out _image).ThrowOnError();
+
+ if (foreignAllocation == null)
+ {
+ gd.Api.GetImageMemoryRequirements(device, _image, out var requirements);
+ var allocation = gd.MemoryAllocator.AllocateDeviceMemory(physicalDevice, requirements, DefaultImageMemoryFlags);
+
+ if (allocation.Memory.Handle == 0UL)
+ {
+ gd.Api.DestroyImage(device, _image, null);
+ throw new Exception("Image initialization failed.");
+ }
+
+ _size = requirements.Size;
+
+ gd.Api.BindImageMemory(device, _image, allocation.Memory, allocation.Offset).ThrowOnError();
+
+ _allocationAuto = new Auto<MemoryAllocation>(allocation);
+ _imageAuto = new Auto<DisposableImage>(new DisposableImage(_gd.Api, device, _image), null, _allocationAuto);
+
+ InitialTransition(ImageLayout.Undefined, ImageLayout.General);
+ }
+ else
+ {
+ _foreignAllocationAuto = foreignAllocation;
+ foreignAllocation.IncrementReferenceCount();
+ var allocation = foreignAllocation.GetUnsafe();
+
+ gd.Api.BindImageMemory(device, _image, allocation.Memory, allocation.Offset).ThrowOnError();
+
+ _imageAuto = new Auto<DisposableImage>(new DisposableImage(_gd.Api, device, _image));
+
+ InitialTransition(ImageLayout.Preinitialized, ImageLayout.General);
+ }
+ }
+
+ public TextureStorage CreateAliasedColorForDepthStorageUnsafe(GAL.Format format)
+ {
+ var colorFormat = format switch
+ {
+ GAL.Format.S8Uint => GAL.Format.R8Unorm,
+ GAL.Format.D16Unorm => GAL.Format.R16Unorm,
+ GAL.Format.S8UintD24Unorm => GAL.Format.R8G8B8A8Unorm,
+ GAL.Format.D32Float => GAL.Format.R32Float,
+ GAL.Format.D24UnormS8Uint => GAL.Format.R8G8B8A8Unorm,
+ GAL.Format.D32FloatS8Uint => GAL.Format.R32G32Float,
+ _ => throw new ArgumentException($"\"{format}\" is not a supported depth or stencil format.")
+ };
+
+ return CreateAliasedStorageUnsafe(colorFormat);
+ }
+
+ public TextureStorage CreateAliasedStorageUnsafe(GAL.Format format)
+ {
+ if (_aliasedStorages == null || !_aliasedStorages.TryGetValue(format, out var storage))
+ {
+ _aliasedStorages ??= new Dictionary<GAL.Format, TextureStorage>();
+
+ var info = NewCreateInfoWith(ref _info, format, _info.BytesPerPixel);
+
+ storage = new TextureStorage(_gd, default, _device, info, ScaleFactor, _allocationAuto);
+
+ _aliasedStorages.Add(format, storage);
+ }
+
+ return storage;
+ }
+
+ public static TextureCreateInfo NewCreateInfoWith(ref TextureCreateInfo info, GAL.Format format, int bytesPerPixel)
+ {
+ return NewCreateInfoWith(ref info, format, bytesPerPixel, info.Width, info.Height);
+ }
+
+ public static TextureCreateInfo NewCreateInfoWith(
+ ref TextureCreateInfo info,
+ GAL.Format format,
+ int bytesPerPixel,
+ int width,
+ int height)
+ {
+ return new TextureCreateInfo(
+ width,
+ height,
+ info.Depth,
+ info.Levels,
+ info.Samples,
+ info.BlockWidth,
+ info.BlockHeight,
+ bytesPerPixel,
+ format,
+ info.DepthStencilMode,
+ info.Target,
+ info.SwizzleR,
+ info.SwizzleG,
+ info.SwizzleB,
+ info.SwizzleA);
+ }
+
+ public Auto<DisposableImage> GetImage()
+ {
+ return _imageAuto;
+ }
+
+ public Image GetImageForViewCreation()
+ {
+ return _image;
+ }
+
+ public bool HasCommandBufferDependency(CommandBufferScoped cbs)
+ {
+ if (_foreignAllocationAuto != null)
+ {
+ return _foreignAllocationAuto.HasCommandBufferDependency(cbs);
+ }
+ else if (_allocationAuto != null)
+ {
+ return _allocationAuto.HasCommandBufferDependency(cbs);
+ }
+
+ return false;
+ }
+
+ private unsafe void InitialTransition(ImageLayout srcLayout, ImageLayout dstLayout)
+ {
+ CommandBufferScoped cbs;
+ bool useTempCbs = !_gd.CommandBufferPool.OwnedByCurrentThread;
+
+ if (useTempCbs)
+ {
+ cbs = _gd.BackgroundResources.Get().GetPool().Rent();
+ }
+ else
+ {
+ if (_gd.PipelineInternal != null)
+ {
+ cbs = _gd.PipelineInternal.GetPreloadCommandBuffer();
+ }
+ else
+ {
+ cbs = _gd.CommandBufferPool.Rent();
+ useTempCbs = true;
+ }
+ }
+
+ var aspectFlags = _info.Format.ConvertAspectFlags();
+
+ var subresourceRange = new ImageSubresourceRange(aspectFlags, 0, (uint)_info.Levels, 0, (uint)_info.GetLayers());
+
+ var barrier = new ImageMemoryBarrier()
+ {
+ SType = StructureType.ImageMemoryBarrier,
+ SrcAccessMask = 0,
+ DstAccessMask = DefaultAccessMask,
+ OldLayout = srcLayout,
+ NewLayout = dstLayout,
+ SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
+ DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
+ Image = _imageAuto.Get(cbs).Value,
+ SubresourceRange = subresourceRange
+ };
+
+ _gd.Api.CmdPipelineBarrier(
+ cbs.CommandBuffer,
+ PipelineStageFlags.PipelineStageTopOfPipeBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ 0,
+ 0,
+ null,
+ 0,
+ null,
+ 1,
+ barrier);
+
+ if (useTempCbs)
+ {
+ cbs.Dispose();
+ }
+ }
+
+ public static SampleCountFlags ConvertToSampleCountFlags(uint samples)
+ {
+ if (samples == 0 || samples > (uint)SampleCountFlags.SampleCount64Bit)
+ {
+ return SampleCountFlags.SampleCount1Bit;
+ }
+
+ // Round up to the nearest power of two.
+ return (SampleCountFlags)(1u << (31 - BitOperations.LeadingZeroCount(samples)));
+ }
+
+ public TextureView CreateView(TextureCreateInfo info, int firstLayer, int firstLevel)
+ {
+ return new TextureView(_gd, _device, info, this, firstLayer, firstLevel);
+ }
+
+ public void CopyFromOrToBuffer(
+ CommandBuffer commandBuffer,
+ VkBuffer buffer,
+ Image image,
+ int size,
+ bool to,
+ int x,
+ int y,
+ int dstLayer,
+ int dstLevel,
+ int dstLayers,
+ int dstLevels,
+ bool singleSlice,
+ ImageAspectFlags aspectFlags,
+ bool forFlush)
+ {
+ bool is3D = Info.Target == Target.Texture3D;
+ int width = Info.Width;
+ int height = Info.Height;
+ int depth = is3D && !singleSlice ? Info.Depth : 1;
+ int layer = is3D ? 0 : dstLayer;
+ int layers = dstLayers;
+ int levels = dstLevels;
+
+ int offset = 0;
+
+ for (int level = 0; level < levels; level++)
+ {
+ int mipSize = Info.GetMipSize(level);
+
+ if (forFlush)
+ {
+ mipSize = GetBufferDataLength(mipSize);
+ }
+
+ int endOffset = offset + mipSize;
+
+ if ((uint)endOffset > (uint)size)
+ {
+ break;
+ }
+
+ int rowLength = (Info.GetMipStride(level) / Info.BytesPerPixel) * Info.BlockWidth;
+
+ var sl = new ImageSubresourceLayers(
+ aspectFlags,
+ (uint)(dstLevel + level),
+ (uint)layer,
+ (uint)layers);
+
+ var extent = new Extent3D((uint)width, (uint)height, (uint)depth);
+
+ int z = is3D ? dstLayer : 0;
+
+ var region = new BufferImageCopy(
+ (ulong)offset,
+ (uint)BitUtils.AlignUp(rowLength, Info.BlockWidth),
+ (uint)BitUtils.AlignUp(height, Info.BlockHeight),
+ sl,
+ new Offset3D(x, y, z),
+ extent);
+
+ if (to)
+ {
+ _gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region);
+ }
+ else
+ {
+ _gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region);
+ }
+
+ offset += mipSize;
+
+ width = Math.Max(1, width >> 1);
+ height = Math.Max(1, height >> 1);
+
+ if (Info.Target == Target.Texture3D)
+ {
+ depth = Math.Max(1, depth >> 1);
+ }
+ }
+ }
+
+ private int GetBufferDataLength(int length)
+ {
+ if (NeedsD24S8Conversion())
+ {
+ return length * 2;
+ }
+
+ return length;
+ }
+
+ private bool NeedsD24S8Conversion()
+ {
+ return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint;
+ }
+
+ public void SetModification(AccessFlags accessFlags, PipelineStageFlags stage)
+ {
+ _lastModificationAccess = accessFlags;
+ _lastModificationStage = stage;
+ }
+
+ public void InsertBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
+ {
+ if (_lastModificationAccess != AccessFlags.AccessNoneKhr)
+ {
+ ImageAspectFlags aspectFlags;
+
+ if (_info.Format.IsDepthOrStencil())
+ {
+ if (_info.Format == GAL.Format.S8Uint)
+ {
+ aspectFlags = ImageAspectFlags.ImageAspectStencilBit;
+ }
+ else if (_info.Format == GAL.Format.D16Unorm || _info.Format == GAL.Format.D32Float)
+ {
+ aspectFlags = ImageAspectFlags.ImageAspectDepthBit;
+ }
+ else
+ {
+ aspectFlags = ImageAspectFlags.ImageAspectDepthBit | ImageAspectFlags.ImageAspectStencilBit;
+ }
+ }
+ else
+ {
+ aspectFlags = ImageAspectFlags.ImageAspectColorBit;
+ }
+
+ TextureView.InsertImageBarrier(
+ _gd.Api,
+ cbs.CommandBuffer,
+ _imageAuto.Get(cbs).Value,
+ _lastModificationAccess,
+ dstAccessFlags,
+ _lastModificationStage,
+ dstStageFlags,
+ aspectFlags,
+ 0,
+ 0,
+ _info.GetLayers(),
+ _info.Levels);
+
+ _lastModificationAccess = AccessFlags.AccessNoneKhr;
+ }
+ }
+
+ public void IncrementViewsCount()
+ {
+ _viewsCount++;
+ }
+
+ public void DecrementViewsCount()
+ {
+ if (--_viewsCount == 0)
+ {
+ _gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_imageAuto, _size);
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_aliasedStorages != null)
+ {
+ foreach (var storage in _aliasedStorages.Values)
+ {
+ storage.Dispose();
+ }
+
+ _aliasedStorages.Clear();
+ }
+
+ _imageAuto.Dispose();
+ _allocationAuto?.Dispose();
+ _foreignAllocationAuto?.DecrementReferenceCount();
+ _foreignAllocationAuto = null;
+ }
+ }
+}