Ryujinx/Ryujinx.Graphics.Vulkan/TextureBuffer.cs
riperiperi bf77d1cab9
GPU: Pass SpanOrArray for Texture SetData to avoid copy (#3745)
* GPU: Pass SpanOrArray for Texture SetData to avoid copy

Texture data is often converted before upload, meaning that an array was allocated to perform the conversion into. However, the backend SetData methods were being passed a Span of that data, and the Multithreaded layer does `ToArray()` on it so that it can be stored for later! This method can't extract the original array, so it creates a copy.

This PR changes the type passed for textures to a new ref struct called SpanOrArray, which is backed by either a ReadOnlySpan or an array. The benefit here is that we can have a ToArray method that doesn't copy if it is originally backed by an array.

This will also avoid a copy when running the ASTC decoder.

On NieR this was taking 38% of texture upload time, which it does a _lot_ of when you move between areas, so there should be a 1.6x performance boost when strictly uploading textures. No doubt this will also improve texture streaming performance in UE4 games, and maybe a small reduction with video playback.

From the numbers, it's probably possible to improve the upload rate by a further 1.6x by performing layout conversion on GPU. I'm not sure if we could improve it further than that - multithreading conversion on CPU would probably result in memory bottleneck.

This doesn't extend to buffers, since we don't convert their data on the GPU emulator side.

* Remove implicit cast to array.
2022-10-08 12:04:47 -03:00

160 lines
4.5 KiB
C#

using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
using VkFormat = Silk.NET.Vulkan.Format;
namespace Ryujinx.Graphics.Vulkan
{
class TextureBuffer : ITexture
{
private readonly VulkanRenderer _gd;
private BufferHandle _bufferHandle;
private int _offset;
private int _size;
private Auto<DisposableBufferView> _bufferView;
private Dictionary<GAL.Format, Auto<DisposableBufferView>> _selfManagedViews;
private int _bufferCount;
public int Width { get; }
public int Height { get; }
public VkFormat VkFormat { get; }
public float ScaleFactor { get; }
public TextureBuffer(VulkanRenderer gd, TextureCreateInfo info, float scale)
{
_gd = gd;
Width = info.Width;
Height = info.Height;
VkFormat = FormatTable.GetFormat(info.Format);
ScaleFactor = scale;
gd.Textures.Add(this);
}
public void CopyTo(ITexture destination, int firstLayer, int firstLevel)
{
throw new NotSupportedException();
}
public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel)
{
throw new NotSupportedException();
}
public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)
{
throw new NotSupportedException();
}
public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel)
{
throw new NotSupportedException();
}
public ReadOnlySpan<byte> GetData()
{
return _gd.GetBufferData(_bufferHandle, _offset, _size);
}
public ReadOnlySpan<byte> GetData(int layer, int level)
{
return GetData();
}
public void Release()
{
if (_gd.Textures.Remove(this))
{
ReleaseImpl();
}
}
private void ReleaseImpl()
{
if (_selfManagedViews != null)
{
foreach (var bufferView in _selfManagedViews.Values)
{
bufferView.Dispose();
}
_selfManagedViews = null;
}
_bufferView?.Dispose();
_bufferView = null;
}
public void SetData(SpanOrArray<byte> data)
{
_gd.SetBufferData(_bufferHandle, _offset, data);
}
public void SetData(SpanOrArray<byte> data, int layer, int level)
{
throw new NotSupportedException();
}
public void SetData(SpanOrArray<byte> data, int layer, int level, Rectangle<int> region)
{
throw new NotSupportedException();
}
public void SetStorage(BufferRange buffer)
{
if (_bufferHandle == buffer.Handle &&
_offset == buffer.Offset &&
_size == buffer.Size &&
_bufferCount == _gd.BufferManager.BufferCount)
{
return;
}
_bufferHandle = buffer.Handle;
_offset = buffer.Offset;
_size = buffer.Size;
_bufferCount = _gd.BufferManager.BufferCount;
ReleaseImpl();
}
public BufferView GetBufferView(CommandBufferScoped cbs)
{
if (_bufferView == null)
{
_bufferView = _gd.BufferManager.CreateView(_bufferHandle, VkFormat, _offset, _size);
}
return _bufferView?.Get(cbs, _offset, _size).Value ?? default;
}
public BufferView GetBufferView(CommandBufferScoped cbs, GAL.Format format)
{
var vkFormat = FormatTable.GetFormat(format);
if (vkFormat == VkFormat)
{
return GetBufferView(cbs);
}
if (_selfManagedViews != null && _selfManagedViews.TryGetValue(format, out var bufferView))
{
return bufferView.Get(cbs, _offset, _size).Value;
}
bufferView = _gd.BufferManager.CreateView(_bufferHandle, vkFormat, _offset, _size);
if (bufferView != null)
{
(_selfManagedViews ??= new Dictionary<GAL.Format, Auto<DisposableBufferView>>()).Add(format, bufferView);
}
return bufferView?.Get(cbs, _offset, _size).Value ?? default;
}
}
}