aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Vulkan/TextureView.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Graphics.Vulkan/TextureView.cs')
-rw-r--r--src/Ryujinx.Graphics.Vulkan/TextureView.cs885
1 files changed, 885 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
new file mode 100644
index 00000000..cd280d5f
--- /dev/null
+++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
@@ -0,0 +1,885 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using System.Collections.Generic;
+using VkBuffer = Silk.NET.Vulkan.Buffer;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class TextureView : ITexture, IDisposable
+ {
+ private readonly VulkanRenderer _gd;
+
+ private readonly Device _device;
+
+ private readonly Auto<DisposableImageView> _imageView;
+ private readonly Auto<DisposableImageView> _imageViewIdentity;
+ private readonly Auto<DisposableImageView> _imageView2dArray;
+ private Dictionary<GAL.Format, TextureView> _selfManagedViews;
+
+ private TextureCreateInfo _info;
+
+ public TextureCreateInfo Info => _info;
+
+ public TextureStorage Storage { get; }
+
+ public int Width => Info.Width;
+ public int Height => Info.Height;
+ public int Layers => Info.GetDepthOrLayers();
+ public int FirstLayer { get; }
+ public int FirstLevel { get; }
+ public float ScaleFactor => Storage.ScaleFactor;
+ public VkFormat VkFormat { get; }
+ public bool Valid { get; private set; }
+
+ public TextureView(
+ VulkanRenderer gd,
+ Device device,
+ TextureCreateInfo info,
+ TextureStorage storage,
+ int firstLayer,
+ int firstLevel)
+ {
+ _gd = gd;
+ _device = device;
+ _info = info;
+ Storage = storage;
+ FirstLayer = firstLayer;
+ FirstLevel = firstLevel;
+
+ storage.IncrementViewsCount();
+
+ gd.Textures.Add(this);
+
+ var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format);
+ var usage = TextureStorage.GetImageUsageFromFormat(info.Format);
+ var levels = (uint)info.Levels;
+ var layers = (uint)info.GetLayers();
+
+ VkFormat = format;
+
+ var type = info.Target.ConvertView();
+
+ var swizzleR = info.SwizzleR.Convert();
+ var swizzleG = info.SwizzleG.Convert();
+ var swizzleB = info.SwizzleB.Convert();
+ var swizzleA = info.SwizzleA.Convert();
+
+ if (info.Format == GAL.Format.R5G5B5A1Unorm ||
+ info.Format == GAL.Format.R5G5B5X1Unorm ||
+ info.Format == GAL.Format.R5G6B5Unorm)
+ {
+ var temp = swizzleR;
+
+ swizzleR = swizzleB;
+ swizzleB = temp;
+ }
+ else if (VkFormat == VkFormat.R4G4B4A4UnormPack16 || info.Format == GAL.Format.A1B5G5R5Unorm)
+ {
+ var tempB = swizzleB;
+ var tempA = swizzleA;
+
+ swizzleB = swizzleG;
+ swizzleA = swizzleR;
+ swizzleR = tempA;
+ swizzleG = tempB;
+ }
+
+ var componentMapping = new ComponentMapping(swizzleR, swizzleG, swizzleB, swizzleA);
+
+ var aspectFlags = info.Format.ConvertAspectFlags(info.DepthStencilMode);
+ var aspectFlagsDepth = info.Format.ConvertAspectFlags();
+
+ var subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, layers);
+ var subresourceRangeDepth = new ImageSubresourceRange(aspectFlagsDepth, (uint)firstLevel, levels, (uint)firstLayer, layers);
+
+ unsafe Auto<DisposableImageView> CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType, ImageUsageFlags usageFlags)
+ {
+ var usage = new ImageViewUsageCreateInfo()
+ {
+ SType = StructureType.ImageViewUsageCreateInfo,
+ Usage = usageFlags
+ };
+
+ var imageCreateInfo = new ImageViewCreateInfo()
+ {
+ SType = StructureType.ImageViewCreateInfo,
+ Image = storage.GetImageForViewCreation(),
+ ViewType = viewType,
+ Format = format,
+ Components = cm,
+ SubresourceRange = sr,
+ PNext = &usage
+ };
+
+ gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError();
+ return new Auto<DisposableImageView>(new DisposableImageView(gd.Api, device, imageView), null, storage.GetImage());
+ }
+
+ _imageView = CreateImageView(componentMapping, subresourceRange, type, ImageUsageFlags.SampledBit);
+
+ // Framebuffer attachments and storage images requires a identity component mapping.
+ var identityComponentMapping = new ComponentMapping(
+ ComponentSwizzle.R,
+ ComponentSwizzle.G,
+ ComponentSwizzle.B,
+ ComponentSwizzle.A);
+
+ _imageViewIdentity = CreateImageView(identityComponentMapping, subresourceRangeDepth, type, usage);
+
+ // Framebuffer attachments also require 3D textures to be bound as 2D array.
+ if (info.Target == Target.Texture3D)
+ {
+ if (gd.Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.No3DImageView))
+ {
+ if (levels == 1 && (info.Format.IsRtColorCompatible() || info.Format.IsDepthOrStencil()))
+ {
+ subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, 1);
+
+ _imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2D, ImageUsageFlags.ColorAttachmentBit);
+ }
+ }
+ else
+ {
+ subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth);
+
+ _imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray, usage);
+ }
+ }
+
+ Valid = true;
+ }
+
+ public Auto<DisposableImage> GetImage()
+ {
+ return Storage.GetImage();
+ }
+
+ public Auto<DisposableImageView> GetImageView()
+ {
+ return _imageView;
+ }
+
+ public Auto<DisposableImageView> GetIdentityImageView()
+ {
+ return _imageViewIdentity;
+ }
+
+ public Auto<DisposableImageView> GetImageViewForAttachment()
+ {
+ return _imageView2dArray ?? _imageViewIdentity;
+ }
+
+ public void CopyTo(ITexture destination, int firstLayer, int firstLevel)
+ {
+ var src = this;
+ var dst = (TextureView)destination;
+
+ if (!Valid || !dst.Valid)
+ {
+ return;
+ }
+
+ _gd.PipelineInternal.EndRenderPass();
+
+ var cbs = _gd.PipelineInternal.CurrentCommandBuffer;
+
+ var srcImage = src.GetImage().Get(cbs).Value;
+ var dstImage = dst.GetImage().Get(cbs).Value;
+
+ if (!dst.Info.Target.IsMultisample() && Info.Target.IsMultisample())
+ {
+ int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer);
+ _gd.HelperShader.CopyMSToNonMS(_gd, cbs, src, dst, 0, firstLayer, layers);
+ }
+ else if (dst.Info.Target.IsMultisample() && !Info.Target.IsMultisample())
+ {
+ int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer);
+ _gd.HelperShader.CopyNonMSToMS(_gd, cbs, src, dst, 0, firstLayer, layers);
+ }
+ else if (dst.Info.BytesPerPixel != Info.BytesPerPixel)
+ {
+ int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer);
+ int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel);
+ _gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, 0, firstLayer, 0, firstLevel, layers, levels);
+ }
+ else
+ {
+ TextureCopy.Copy(
+ _gd.Api,
+ cbs.CommandBuffer,
+ srcImage,
+ dstImage,
+ src.Info,
+ dst.Info,
+ src.FirstLayer,
+ dst.FirstLayer,
+ src.FirstLevel,
+ dst.FirstLevel,
+ 0,
+ firstLayer,
+ 0,
+ firstLevel);
+ }
+ }
+
+ public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel)
+ {
+ var src = this;
+ var dst = (TextureView)destination;
+
+ if (!Valid || !dst.Valid)
+ {
+ return;
+ }
+
+ _gd.PipelineInternal.EndRenderPass();
+
+ var cbs = _gd.PipelineInternal.CurrentCommandBuffer;
+
+ var srcImage = src.GetImage().Get(cbs).Value;
+ var dstImage = dst.GetImage().Get(cbs).Value;
+
+ if (!dst.Info.Target.IsMultisample() && Info.Target.IsMultisample())
+ {
+ _gd.HelperShader.CopyMSToNonMS(_gd, cbs, src, dst, srcLayer, dstLayer, 1);
+ }
+ else if (dst.Info.Target.IsMultisample() && !Info.Target.IsMultisample())
+ {
+ _gd.HelperShader.CopyNonMSToMS(_gd, cbs, src, dst, srcLayer, dstLayer, 1);
+ }
+ else if (dst.Info.BytesPerPixel != Info.BytesPerPixel)
+ {
+ _gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
+ }
+ else
+ {
+ TextureCopy.Copy(
+ _gd.Api,
+ cbs.CommandBuffer,
+ srcImage,
+ dstImage,
+ src.Info,
+ dst.Info,
+ src.FirstLayer,
+ dst.FirstLayer,
+ src.FirstLevel,
+ dst.FirstLevel,
+ srcLayer,
+ dstLayer,
+ srcLevel,
+ dstLevel,
+ 1,
+ 1);
+ }
+ }
+
+ public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)
+ {
+ var dst = (TextureView)destination;
+
+ if (_gd.CommandBufferPool.OwnedByCurrentThread)
+ {
+ _gd.PipelineInternal.EndRenderPass();
+
+ var cbs = _gd.PipelineInternal.CurrentCommandBuffer;
+
+ CopyToImpl(cbs, dst, srcRegion, dstRegion, linearFilter);
+ }
+ else
+ {
+ var cbp = _gd.BackgroundResources.Get().GetPool();
+
+ using var cbs = cbp.Rent();
+
+ CopyToImpl(cbs, dst, srcRegion, dstRegion, linearFilter);
+ }
+ }
+
+ private void CopyToImpl(CommandBufferScoped cbs, TextureView dst, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)
+ {
+ var src = this;
+
+ var srcFormat = GetCompatibleGalFormat(src.Info.Format);
+ var dstFormat = GetCompatibleGalFormat(dst.Info.Format);
+
+ bool srcUsesStorageFormat = src.VkFormat == src.Storage.VkFormat;
+ bool dstUsesStorageFormat = dst.VkFormat == dst.Storage.VkFormat;
+
+ int layers = Math.Min(dst.Info.GetDepthOrLayers(), src.Info.GetDepthOrLayers());
+ int levels = Math.Min(dst.Info.Levels, src.Info.Levels);
+
+ if (srcUsesStorageFormat && dstUsesStorageFormat)
+ {
+ if ((srcRegion.X1 | dstRegion.X1) == 0 &&
+ (srcRegion.Y1 | dstRegion.Y1) == 0 &&
+ srcRegion.X2 == src.Width &&
+ srcRegion.Y2 == src.Height &&
+ dstRegion.X2 == dst.Width &&
+ dstRegion.Y2 == dst.Height &&
+ src.Width == dst.Width &&
+ src.Height == dst.Height &&
+ src.VkFormat == dst.VkFormat)
+ {
+ if (src.Info.Samples > 1 && src.Info.Samples != dst.Info.Samples && src.Info.Format.IsDepthOrStencil())
+ {
+ // CmdResolveImage does not support depth-stencil resolve, so we need to use an alternative path
+ // for those textures.
+ TextureCopy.ResolveDepthStencil(_gd, _device, cbs, src, dst);
+ }
+ else
+ {
+ TextureCopy.Copy(
+ _gd.Api,
+ cbs.CommandBuffer,
+ src.GetImage().Get(cbs).Value,
+ dst.GetImage().Get(cbs).Value,
+ src.Info,
+ dst.Info,
+ src.FirstLayer,
+ dst.FirstLayer,
+ src.FirstLevel,
+ dst.FirstLevel,
+ 0,
+ 0,
+ 0,
+ 0,
+ layers,
+ levels);
+ }
+
+ return;
+ }
+ else if (_gd.FormatCapabilities.OptimalFormatSupports(FormatFeatureFlags.BlitSrcBit, srcFormat) &&
+ _gd.FormatCapabilities.OptimalFormatSupports(FormatFeatureFlags.BlitDstBit, dstFormat))
+ {
+ TextureCopy.Blit(
+ _gd.Api,
+ cbs.CommandBuffer,
+ src.GetImage().Get(cbs).Value,
+ dst.GetImage().Get(cbs).Value,
+ src.Info,
+ dst.Info,
+ srcRegion,
+ dstRegion,
+ src.FirstLayer,
+ dst.FirstLayer,
+ src.FirstLevel,
+ dst.FirstLevel,
+ layers,
+ levels,
+ linearFilter);
+
+ return;
+ }
+ }
+
+ bool isDepthOrStencil = dst.Info.Format.IsDepthOrStencil();
+
+ if (VulkanConfiguration.UseSlowSafeBlitOnAmd && (_gd.Vendor == Vendor.Amd || _gd.IsMoltenVk))
+ {
+ _gd.HelperShader.Blit(
+ _gd,
+ src,
+ dst,
+ srcRegion,
+ dstRegion,
+ layers,
+ levels,
+ isDepthOrStencil,
+ linearFilter);
+
+ return;
+ }
+
+ Auto<DisposableImage> srcImage;
+ Auto<DisposableImage> dstImage;
+
+ if (isDepthOrStencil)
+ {
+ srcImage = src.Storage.CreateAliasedColorForDepthStorageUnsafe(srcFormat).GetImage();
+ dstImage = dst.Storage.CreateAliasedColorForDepthStorageUnsafe(dstFormat).GetImage();
+ }
+ else
+ {
+ srcImage = src.Storage.CreateAliasedStorageUnsafe(srcFormat).GetImage();
+ dstImage = dst.Storage.CreateAliasedStorageUnsafe(dstFormat).GetImage();
+ }
+
+ TextureCopy.Blit(
+ _gd.Api,
+ cbs.CommandBuffer,
+ srcImage.Get(cbs).Value,
+ dstImage.Get(cbs).Value,
+ src.Info,
+ dst.Info,
+ srcRegion,
+ dstRegion,
+ src.FirstLayer,
+ dst.FirstLayer,
+ src.FirstLevel,
+ dst.FirstLevel,
+ layers,
+ levels,
+ linearFilter,
+ ImageAspectFlags.ColorBit,
+ ImageAspectFlags.ColorBit);
+ }
+
+ public static unsafe void InsertImageBarrier(
+ Vk api,
+ CommandBuffer commandBuffer,
+ Image image,
+ AccessFlags srcAccessMask,
+ AccessFlags dstAccessMask,
+ PipelineStageFlags srcStageMask,
+ PipelineStageFlags dstStageMask,
+ ImageAspectFlags aspectFlags,
+ int firstLayer,
+ int firstLevel,
+ int layers,
+ int levels)
+ {
+ ImageMemoryBarrier memoryBarrier = new ImageMemoryBarrier()
+ {
+ SType = StructureType.ImageMemoryBarrier,
+ SrcAccessMask = srcAccessMask,
+ DstAccessMask = dstAccessMask,
+ SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
+ DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
+ Image = image,
+ OldLayout = ImageLayout.General,
+ NewLayout = ImageLayout.General,
+ SubresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, (uint)levels, (uint)firstLayer, (uint)layers)
+ };
+
+ api.CmdPipelineBarrier(
+ commandBuffer,
+ srcStageMask,
+ dstStageMask,
+ 0,
+ 0,
+ null,
+ 0,
+ null,
+ 1,
+ memoryBarrier);
+ }
+
+ public TextureView GetView(GAL.Format format)
+ {
+ if (format == Info.Format)
+ {
+ return this;
+ }
+
+ if (_selfManagedViews != null && _selfManagedViews.TryGetValue(format, out var view))
+ {
+ return view;
+ }
+
+ view = CreateViewImpl(new TextureCreateInfo(
+ Info.Width,
+ Info.Height,
+ Info.Depth,
+ Info.Levels,
+ Info.Samples,
+ Info.BlockWidth,
+ Info.BlockHeight,
+ Info.BytesPerPixel,
+ format,
+ Info.DepthStencilMode,
+ Info.Target,
+ Info.SwizzleR,
+ Info.SwizzleG,
+ Info.SwizzleB,
+ Info.SwizzleA), 0, 0);
+
+ (_selfManagedViews ??= new Dictionary<GAL.Format, TextureView>()).Add(format, view);
+
+ return view;
+ }
+
+ public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel)
+ {
+ return CreateViewImpl(info, firstLayer, firstLevel);
+ }
+
+ public TextureView CreateViewImpl(TextureCreateInfo info, int firstLayer, int firstLevel)
+ {
+ return new TextureView(_gd, _device, info, Storage, FirstLayer + firstLayer, FirstLevel + firstLevel);
+ }
+
+ public byte[] GetData(int x, int y, int width, int height)
+ {
+ int size = width * height * Info.BytesPerPixel;
+ using var bufferHolder = _gd.BufferManager.Create(_gd, size);
+
+ using (var cbs = _gd.CommandBufferPool.Rent())
+ {
+ var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value;
+ var image = GetImage().Get(cbs).Value;
+
+ CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, size, true, 0, 0, x, y, width, height);
+ }
+
+ bufferHolder.WaitForFences();
+ byte[] bitmap = new byte[size];
+ GetDataFromBuffer(bufferHolder.GetDataStorage(0, size), size, Span<byte>.Empty).CopyTo(bitmap);
+ return bitmap;
+ }
+
+ public PinnedSpan<byte> GetData()
+ {
+ BackgroundResource resources = _gd.BackgroundResources.Get();
+
+ if (_gd.CommandBufferPool.OwnedByCurrentThread)
+ {
+ _gd.FlushAllCommands();
+
+ return PinnedSpan<byte>.UnsafeFromSpan(GetData(_gd.CommandBufferPool, resources.GetFlushBuffer()));
+ }
+ else
+ {
+ return PinnedSpan<byte>.UnsafeFromSpan(GetData(resources.GetPool(), resources.GetFlushBuffer()));
+ }
+ }
+
+ public PinnedSpan<byte> GetData(int layer, int level)
+ {
+ BackgroundResource resources = _gd.BackgroundResources.Get();
+
+ if (_gd.CommandBufferPool.OwnedByCurrentThread)
+ {
+ _gd.FlushAllCommands();
+
+ return PinnedSpan<byte>.UnsafeFromSpan(GetData(_gd.CommandBufferPool, resources.GetFlushBuffer(), layer, level));
+ }
+ else
+ {
+ return PinnedSpan<byte>.UnsafeFromSpan(GetData(resources.GetPool(), resources.GetFlushBuffer(), layer, level));
+ }
+ }
+
+ private ReadOnlySpan<byte> GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer)
+ {
+ int size = 0;
+
+ for (int level = 0; level < Info.Levels; level++)
+ {
+ size += Info.GetMipSize(level);
+ }
+
+ size = GetBufferDataLength(size);
+
+ Span<byte> result = flushBuffer.GetTextureData(cbp, this, size);
+ return GetDataFromBuffer(result, size, result);
+ }
+
+ private ReadOnlySpan<byte> GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer, int layer, int level)
+ {
+ int size = GetBufferDataLength(Info.GetMipSize(level));
+
+ Span<byte> result = flushBuffer.GetTextureData(cbp, this, size, layer, level);
+ return GetDataFromBuffer(result, size, result);
+ }
+
+ public void SetData(SpanOrArray<byte> data)
+ {
+ SetData(data, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false);
+ }
+
+ public void SetData(SpanOrArray<byte> data, int layer, int level)
+ {
+ SetData(data, layer, level, 1, 1, singleSlice: true);
+ }
+
+ public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region)
+ {
+ SetData(data, layer, level, 1, 1, singleSlice: true, region);
+ }
+
+ private void SetData(ReadOnlySpan<byte> data, int layer, int level, int layers, int levels, bool singleSlice, Rectangle<int>? region = null)
+ {
+ int bufferDataLength = GetBufferDataLength(data.Length);
+
+ using var bufferHolder = _gd.BufferManager.Create(_gd, bufferDataLength);
+
+ Auto<DisposableImage> imageAuto = GetImage();
+
+ // Load texture data inline if the texture has been used on the current command buffer.
+
+ bool loadInline = Storage.HasCommandBufferDependency(_gd.PipelineInternal.CurrentCommandBuffer);
+
+ var cbs = loadInline ? _gd.PipelineInternal.CurrentCommandBuffer : _gd.PipelineInternal.GetPreloadCommandBuffer();
+
+ if (loadInline)
+ {
+ _gd.PipelineInternal.EndRenderPass();
+ }
+
+ CopyDataToBuffer(bufferHolder.GetDataStorage(0, bufferDataLength), data);
+
+ var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value;
+ var image = imageAuto.Get(cbs).Value;
+
+ if (region.HasValue)
+ {
+ CopyFromOrToBuffer(
+ cbs.CommandBuffer,
+ buffer,
+ image,
+ bufferDataLength,
+ false,
+ layer,
+ level,
+ region.Value.X,
+ region.Value.Y,
+ region.Value.Width,
+ region.Value.Height);
+ }
+ else
+ {
+ CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, bufferDataLength, false, layer, level, layers, levels, singleSlice);
+ }
+ }
+
+ private int GetBufferDataLength(int length)
+ {
+ if (NeedsD24S8Conversion())
+ {
+ return length * 2;
+ }
+
+ return length;
+ }
+
+ private GAL.Format GetCompatibleGalFormat(GAL.Format format)
+ {
+ if (NeedsD24S8Conversion())
+ {
+ return GAL.Format.D32FloatS8Uint;
+ }
+
+ return format;
+ }
+
+ private void CopyDataToBuffer(Span<byte> storage, ReadOnlySpan<byte> input)
+ {
+ if (NeedsD24S8Conversion())
+ {
+ FormatConverter.ConvertD24S8ToD32FS8(storage, input);
+ return;
+ }
+
+ input.CopyTo(storage);
+ }
+
+ private ReadOnlySpan<byte> GetDataFromBuffer(ReadOnlySpan<byte> storage, int size, Span<byte> output)
+ {
+ if (NeedsD24S8Conversion())
+ {
+ if (output.IsEmpty)
+ {
+ output = new byte[GetBufferDataLength(size)];
+ }
+
+ FormatConverter.ConvertD32FS8ToD24S8(output, storage);
+ return output;
+ }
+
+ return storage;
+ }
+
+ private bool NeedsD24S8Conversion()
+ {
+ return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint;
+ }
+
+ public void CopyFromOrToBuffer(
+ CommandBuffer commandBuffer,
+ VkBuffer buffer,
+ Image image,
+ int size,
+ bool to,
+ int dstLayer,
+ int dstLevel,
+ int dstLayers,
+ int dstLevels,
+ bool singleSlice)
+ {
+ bool is3D = Info.Target == Target.Texture3D;
+ int width = Math.Max(1, Info.Width >> dstLevel);
+ int height = Math.Max(1, Info.Height >> dstLevel);
+ int depth = is3D && !singleSlice ? Math.Max(1, Info.Depth >> dstLevel) : 1;
+ int layer = is3D ? 0 : dstLayer;
+ int layers = dstLayers;
+ int levels = dstLevels;
+
+ int offset = 0;
+
+ for (int level = 0; level < levels; level++)
+ {
+ int mipSize = GetBufferDataLength(Info.GetMipSize2D(dstLevel + level) * dstLayers);
+
+ int endOffset = offset + mipSize;
+
+ if ((uint)endOffset > (uint)size)
+ {
+ break;
+ }
+
+ int rowLength = (Info.GetMipStride(dstLevel + level) / Info.BytesPerPixel) * Info.BlockWidth;
+
+ var aspectFlags = Info.Format.ConvertAspectFlags();
+
+ if (aspectFlags == (ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit))
+ {
+ aspectFlags = ImageAspectFlags.DepthBit;
+ }
+
+ var sl = new ImageSubresourceLayers(
+ aspectFlags,
+ (uint)(FirstLevel + dstLevel + level),
+ (uint)(FirstLayer + 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)AlignUpNpot(rowLength, Info.BlockWidth),
+ (uint)AlignUpNpot(height, Info.BlockHeight),
+ sl,
+ new Offset3D(0, 0, 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 void CopyFromOrToBuffer(
+ CommandBuffer commandBuffer,
+ VkBuffer buffer,
+ Image image,
+ int size,
+ bool to,
+ int dstLayer,
+ int dstLevel,
+ int x,
+ int y,
+ int width,
+ int height)
+ {
+ var aspectFlags = Info.Format.ConvertAspectFlags();
+
+ if (aspectFlags == (ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit))
+ {
+ aspectFlags = ImageAspectFlags.DepthBit;
+ }
+
+ var sl = new ImageSubresourceLayers(aspectFlags, (uint)(FirstLevel + dstLevel), (uint)(FirstLayer + dstLayer), 1);
+
+ var extent = new Extent3D((uint)width, (uint)height, 1);
+
+ int rowLengthAlignment = Info.BlockWidth;
+
+ // We expect all data being written into the texture to have a stride aligned by 4.
+ if (!to && Info.BytesPerPixel < 4)
+ {
+ rowLengthAlignment = 4 / Info.BytesPerPixel;
+ }
+
+ var region = new BufferImageCopy(
+ 0,
+ (uint)AlignUpNpot(width, rowLengthAlignment),
+ (uint)AlignUpNpot(height, Info.BlockHeight),
+ sl,
+ new Offset3D(x, y, 0),
+ extent);
+
+ if (to)
+ {
+ _gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region);
+ }
+ else
+ {
+ _gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region);
+ }
+ }
+
+ private static int AlignUpNpot(int size, int alignment)
+ {
+ int remainder = size % alignment;
+ if (remainder == 0)
+ {
+ return size;
+ }
+
+ return size + (alignment - remainder);
+ }
+
+ public void SetStorage(BufferRange buffer)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ Valid = false;
+
+ if (_gd.Textures.Remove(this))
+ {
+ _imageView.Dispose();
+ _imageViewIdentity.Dispose();
+ _imageView2dArray?.Dispose();
+
+ Storage.DecrementViewsCount();
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_selfManagedViews != null)
+ {
+ foreach (var view in _selfManagedViews.Values)
+ {
+ view.Dispose();
+ }
+
+ _selfManagedViews = null;
+ }
+
+ Dispose(true);
+ }
+
+ public void Release()
+ {
+ Dispose();
+ }
+ }
+}