Ryujinx/Ryujinx.Graphics.Vulkan/TextureView.cs
riperiperi e4f68592c3
Fix partial updates for textures. (#4401)
I was forcing some types of texture to partially update when investigating performance with games that stream in data, and noticed that partially loading texture data was really broken on both backends.

Fixes Vulkan texture set by getting the correct expected size for the texture. Fixes partial upload on both backends for both Texture 2D Array and Cubemap using the wrong offset and uploading to the first layer/level for a handle. 3D might also be affected.

This might fix textures randomly having incorrect data in games that render to it - jumbled in the case of OpenGL, and outdated/black in the case of Vulkan. This case typically happens in UE4 games.
2023-02-12 10:30:26 +01:00

875 lines
29 KiB
C#

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
{
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
{
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 ReadOnlySpan<byte> GetData()
{
BackgroundResource resources = _gd.BackgroundResources.Get();
if (_gd.CommandBufferPool.OwnedByCurrentThread)
{
_gd.FlushAllCommands();
return GetData(_gd.CommandBufferPool, resources.GetFlushBuffer());
}
else
{
return GetData(resources.GetPool(), resources.GetFlushBuffer());
}
}
public ReadOnlySpan<byte> GetData(int layer, int level)
{
BackgroundResource resources = _gd.BackgroundResources.Get();
if (_gd.CommandBufferPool.OwnedByCurrentThread)
{
_gd.FlushAllCommands();
return GetData(_gd.CommandBufferPool, resources.GetFlushBuffer(), layer, level);
}
else
{
return 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();
}
}
}