diff options
Diffstat (limited to 'Ryujinx.Graphics.Vulkan/TextureStorage.cs')
-rw-r--r-- | Ryujinx.Graphics.Vulkan/TextureStorage.cs | 504 |
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; + } + } +} |