diff --git a/Ryujinx.Common/Memory/SpanOrArray.cs b/Ryujinx.Common/Memory/SpanOrArray.cs new file mode 100644 index 0000000000..c1f0665553 --- /dev/null +++ b/Ryujinx.Common/Memory/SpanOrArray.cs @@ -0,0 +1,89 @@ +using System; + +namespace Ryujinx.Common.Memory +{ + /// + /// A struct that can represent both a Span and Array. + /// This is useful to keep the Array representation when possible to avoid copies. + /// + /// Element Type + public ref struct SpanOrArray where T : unmanaged + { + public readonly T[] Array; + public readonly ReadOnlySpan Span; + + /// + /// Create a new SpanOrArray from an array. + /// + /// Array to store + public SpanOrArray(T[] array) + { + Array = array; + Span = ReadOnlySpan.Empty; + } + + /// + /// Create a new SpanOrArray from a readonly span. + /// + /// Span to store + public SpanOrArray(ReadOnlySpan span) + { + Array = null; + Span = span; + } + + /// + /// Return the contained array, or convert the span if necessary. + /// + /// An array containing the data + public T[] ToArray() + { + return Array ?? Span.ToArray(); + } + + /// + /// Return a ReadOnlySpan from either the array or ReadOnlySpan. + /// + /// A ReadOnlySpan containing the data + public ReadOnlySpan AsSpan() + { + return Array ?? Span; + } + + /// + /// Cast an array to a SpanOrArray. + /// + /// Source array + public static implicit operator SpanOrArray(T[] array) + { + return new SpanOrArray(array); + } + + /// + /// Cast a ReadOnlySpan to a SpanOrArray. + /// + /// Source ReadOnlySpan + public static implicit operator SpanOrArray(ReadOnlySpan span) + { + return new SpanOrArray(span); + } + + /// + /// Cast a Span to a SpanOrArray. + /// + /// Source Span + public static implicit operator SpanOrArray(Span span) + { + return new SpanOrArray(span); + } + + /// + /// Cast a SpanOrArray to a ReadOnlySpan + /// + /// Source SpanOrArray + public static implicit operator ReadOnlySpan(SpanOrArray spanOrArray) + { + return spanOrArray.AsSpan(); + } + } +} diff --git a/Ryujinx.Graphics.GAL/ITexture.cs b/Ryujinx.Graphics.GAL/ITexture.cs index 7f46806c7a..4dc9330363 100644 --- a/Ryujinx.Graphics.GAL/ITexture.cs +++ b/Ryujinx.Graphics.GAL/ITexture.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common.Memory; using System; namespace Ryujinx.Graphics.GAL @@ -17,9 +18,9 @@ namespace Ryujinx.Graphics.GAL ReadOnlySpan GetData(); ReadOnlySpan GetData(int layer, int level); - void SetData(ReadOnlySpan data); - void SetData(ReadOnlySpan data, int layer, int level); - void SetData(ReadOnlySpan data, int layer, int level, Rectangle region); + void SetData(SpanOrArray data); + void SetData(SpanOrArray data, int layer, int level); + void SetData(SpanOrArray data, int layer, int level, Rectangle region); void SetStorage(BufferRange buffer); void Release(); } diff --git a/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs index 1e7d86ba33..1267ab797a 100644 --- a/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs +++ b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs @@ -1,4 +1,5 @@ -using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture; +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture; using Ryujinx.Graphics.GAL.Multithreading.Model; using System; @@ -107,19 +108,19 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources } } - public void SetData(ReadOnlySpan data) + public void SetData(SpanOrArray data) { _renderer.New().Set(Ref(this), Ref(data.ToArray())); _renderer.QueueCommand(); } - public void SetData(ReadOnlySpan data, int layer, int level) + public void SetData(SpanOrArray data, int layer, int level) { _renderer.New().Set(Ref(this), Ref(data.ToArray()), layer, level); _renderer.QueueCommand(); } - public void SetData(ReadOnlySpan data, int layer, int level, Rectangle region) + public void SetData(SpanOrArray data, int layer, int level, Rectangle region) { _renderer.New().Set(Ref(this), Ref(data.ToArray()), layer, level, region); _renderer.QueueCommand(); diff --git a/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs b/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs index da25a89d84..aa94f1f881 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs @@ -229,7 +229,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma if (target != null) { - ReadOnlySpan data; + byte[] data; if (srcLinear) { data = LayoutConverter.ConvertLinearStridedToLinear( diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index 320bc01490..c104e860dc 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -1,5 +1,6 @@ using Ryujinx.Common; using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Texture; @@ -720,9 +721,9 @@ namespace Ryujinx.Graphics.Gpu.Image } } - data = ConvertToHostCompatibleFormat(data); + SpanOrArray result = ConvertToHostCompatibleFormat(data); - HostTexture.SetData(data); + HostTexture.SetData(result); _hasData = true; } @@ -731,7 +732,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// Uploads new texture data to the host GPU. /// /// New data - public void SetData(ReadOnlySpan data) + public void SetData(SpanOrArray data) { BlacklistScale(); @@ -750,7 +751,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// New data /// Target layer /// Target level - public void SetData(ReadOnlySpan data, int layer, int level) + public void SetData(SpanOrArray data, int layer, int level) { BlacklistScale(); @@ -786,7 +787,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// Mip level to convert /// True to convert a single slice /// Converted data - public ReadOnlySpan ConvertToHostCompatibleFormat(ReadOnlySpan data, int level = 0, bool single = false) + public SpanOrArray ConvertToHostCompatibleFormat(ReadOnlySpan data, int level = 0, bool single = false) { int width = Info.Width; int height = Info.Height; @@ -799,9 +800,11 @@ namespace Ryujinx.Graphics.Gpu.Image height = Math.Max(height >> level, 1); depth = Math.Max(depth >> level, 1); + SpanOrArray result; + if (Info.IsLinear) { - data = LayoutConverter.ConvertLinearStridedToLinear( + result = LayoutConverter.ConvertLinearStridedToLinear( width, height, Info.FormatInfo.BlockWidth, @@ -813,7 +816,7 @@ namespace Ryujinx.Graphics.Gpu.Image } else { - data = LayoutConverter.ConvertBlockLinearToLinear( + result = LayoutConverter.ConvertBlockLinearToLinear( width, height, depth, @@ -836,7 +839,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (!_context.Capabilities.SupportsAstcCompression && Format.IsAstc()) { if (!AstcDecoder.TryDecodeToRgba8P( - data.ToArray(), + result.ToArray(), Info.FormatInfo.BlockWidth, Info.FormatInfo.BlockHeight, width, @@ -856,11 +859,11 @@ namespace Ryujinx.Graphics.Gpu.Image decoded = BCnEncoder.EncodeBC7(decoded, width, height, depth, levels, layers); } - data = decoded; + result = decoded; } else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm) { - data = PixelConverter.ConvertR4G4ToR4G4B4A4(data); + result = PixelConverter.ConvertR4G4ToR4G4B4A4(result); } else if (!TextureCompatibility.HostSupportsBcFormat(Format, Target, _context.Capabilities)) { @@ -868,36 +871,36 @@ namespace Ryujinx.Graphics.Gpu.Image { case Format.Bc1RgbaSrgb: case Format.Bc1RgbaUnorm: - data = BCnDecoder.DecodeBC1(data, width, height, depth, levels, layers); + result = BCnDecoder.DecodeBC1(result, width, height, depth, levels, layers); break; case Format.Bc2Srgb: case Format.Bc2Unorm: - data = BCnDecoder.DecodeBC2(data, width, height, depth, levels, layers); + result = BCnDecoder.DecodeBC2(result, width, height, depth, levels, layers); break; case Format.Bc3Srgb: case Format.Bc3Unorm: - data = BCnDecoder.DecodeBC3(data, width, height, depth, levels, layers); + result = BCnDecoder.DecodeBC3(result, width, height, depth, levels, layers); break; case Format.Bc4Snorm: case Format.Bc4Unorm: - data = BCnDecoder.DecodeBC4(data, width, height, depth, levels, layers, Format == Format.Bc4Snorm); + result = BCnDecoder.DecodeBC4(result, width, height, depth, levels, layers, Format == Format.Bc4Snorm); break; case Format.Bc5Snorm: case Format.Bc5Unorm: - data = BCnDecoder.DecodeBC5(data, width, height, depth, levels, layers, Format == Format.Bc5Snorm); + result = BCnDecoder.DecodeBC5(result, width, height, depth, levels, layers, Format == Format.Bc5Snorm); break; case Format.Bc6HSfloat: case Format.Bc6HUfloat: - data = BCnDecoder.DecodeBC6(data, width, height, depth, levels, layers, Format == Format.Bc6HSfloat); + result = BCnDecoder.DecodeBC6(result, width, height, depth, levels, layers, Format == Format.Bc6HSfloat); break; case Format.Bc7Srgb: case Format.Bc7Unorm: - data = BCnDecoder.DecodeBC7(data, width, height, depth, levels, layers); + result = BCnDecoder.DecodeBC7(result, width, height, depth, levels, layers); break; } } - return data; + return result; } /// diff --git a/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs index 4bdc507886..9efd18028b 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs @@ -1,4 +1,5 @@ -using Ryujinx.Cpu.Tracking; +using Ryujinx.Common.Memory; +using Ryujinx.Cpu.Tracking; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Texture; @@ -348,9 +349,9 @@ namespace Ryujinx.Graphics.Gpu.Image ReadOnlySpan data = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)offset, (ulong)size)); - data = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel, true); + SpanOrArray result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel, true); - Storage.SetData(data, info.BaseLayer, info.BaseLevel); + Storage.SetData(result, info.BaseLayer, info.BaseLevel); offsetIndex++; } diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs b/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs index e46d5c48e2..76d0149b08 100644 --- a/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs +++ b/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs @@ -1,4 +1,5 @@ using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using System; @@ -48,17 +49,19 @@ namespace Ryujinx.Graphics.OpenGL.Image return GetData(); } - public void SetData(ReadOnlySpan data) + public void SetData(SpanOrArray data) { - Buffer.SetData(_buffer, _bufferOffset, data.Slice(0, Math.Min(data.Length, _bufferSize))); + var dataSpan = data.AsSpan(); + + Buffer.SetData(_buffer, _bufferOffset, dataSpan.Slice(0, Math.Min(dataSpan.Length, _bufferSize))); } - public void SetData(ReadOnlySpan data, int layer, int level) + public void SetData(SpanOrArray data, int layer, int level) { throw new NotSupportedException(); } - public void SetData(ReadOnlySpan data, int layer, int level, Rectangle region) + public void SetData(SpanOrArray data, int layer, int level, Rectangle region) { throw new NotSupportedException(); } diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs index f17243d2bf..3e7da6e366 100644 --- a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs +++ b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs @@ -1,5 +1,6 @@ using OpenTK.Graphics.OpenGL; using Ryujinx.Common; +using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using System; @@ -317,32 +318,36 @@ namespace Ryujinx.Graphics.OpenGL.Image } } - public void SetData(ReadOnlySpan data) + public void SetData(SpanOrArray data) { + var dataSpan = data.AsSpan(); + if (Format == Format.S8UintD24Unorm) { - data = FormatConverter.ConvertS8D24ToD24S8(data); + dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan); } unsafe { - fixed (byte* ptr = data) + fixed (byte* ptr = dataSpan) { - ReadFrom((IntPtr)ptr, data.Length); + ReadFrom((IntPtr)ptr, dataSpan.Length); } } } - public void SetData(ReadOnlySpan data, int layer, int level) + public void SetData(SpanOrArray data, int layer, int level) { + var dataSpan = data.AsSpan(); + if (Format == Format.S8UintD24Unorm) { - data = FormatConverter.ConvertS8D24ToD24S8(data); + dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan); } unsafe { - fixed (byte* ptr = data) + fixed (byte* ptr = dataSpan) { int width = Math.Max(Info.Width >> level, 1); int height = Math.Max(Info.Height >> level, 1); @@ -352,11 +357,13 @@ namespace Ryujinx.Graphics.OpenGL.Image } } - public void SetData(ReadOnlySpan data, int layer, int level, Rectangle region) + public void SetData(SpanOrArray data, int layer, int level, Rectangle region) { + var dataSpan = data.AsSpan(); + if (Format == Format.S8UintD24Unorm) { - data = FormatConverter.ConvertS8D24ToD24S8(data); + dataSpan = FormatConverter.ConvertS8D24ToD24S8(dataSpan); } int wInBlocks = BitUtils.DivRoundUp(region.Width, Info.BlockWidth); @@ -364,7 +371,7 @@ namespace Ryujinx.Graphics.OpenGL.Image unsafe { - fixed (byte* ptr = data) + fixed (byte* ptr = dataSpan) { ReadFrom2D( (IntPtr)ptr, diff --git a/Ryujinx.Graphics.Texture/LayoutConverter.cs b/Ryujinx.Graphics.Texture/LayoutConverter.cs index 2b327375af..188ae0c157 100644 --- a/Ryujinx.Graphics.Texture/LayoutConverter.cs +++ b/Ryujinx.Graphics.Texture/LayoutConverter.cs @@ -93,7 +93,7 @@ namespace Ryujinx.Graphics.Texture }; } - public static Span ConvertBlockLinearToLinear( + public static byte[] ConvertBlockLinearToLinear( int width, int height, int depth, @@ -119,7 +119,7 @@ namespace Ryujinx.Graphics.Texture blockHeight, bytesPerPixel); - Span output = new byte[outSize]; + byte[] output = new byte[outSize]; int outOffs = 0; @@ -246,7 +246,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static Span ConvertLinearStridedToLinear( + public static byte[] ConvertLinearStridedToLinear( int width, int height, int blockWidth, @@ -262,14 +262,15 @@ namespace Ryujinx.Graphics.Texture int outStride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); lineSize = Math.Min(lineSize, outStride); - Span output = new byte[h * outStride]; + byte[] output = new byte[h * outStride]; + Span outSpan = output; int outOffs = 0; int inOffs = 0; for (int y = 0; y < h; y++) { - data.Slice(inOffs, lineSize).CopyTo(output.Slice(outOffs, lineSize)); + data.Slice(inOffs, lineSize).CopyTo(outSpan.Slice(outOffs, lineSize)); inOffs += stride; outOffs += outStride; diff --git a/Ryujinx.Graphics.Vulkan/TextureBuffer.cs b/Ryujinx.Graphics.Vulkan/TextureBuffer.cs index fca0598fe0..bf9a6eaddb 100644 --- a/Ryujinx.Graphics.Vulkan/TextureBuffer.cs +++ b/Ryujinx.Graphics.Vulkan/TextureBuffer.cs @@ -1,4 +1,5 @@ -using Ryujinx.Graphics.GAL; +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.GAL; using Silk.NET.Vulkan; using System; using System.Collections.Generic; @@ -90,17 +91,17 @@ namespace Ryujinx.Graphics.Vulkan _bufferView = null; } - public void SetData(ReadOnlySpan data) + public void SetData(SpanOrArray data) { _gd.SetBufferData(_bufferHandle, _offset, data); } - public void SetData(ReadOnlySpan data, int layer, int level) + public void SetData(SpanOrArray data, int layer, int level) { throw new NotSupportedException(); } - public void SetData(ReadOnlySpan data, int layer, int level, Rectangle region) + public void SetData(SpanOrArray data, int layer, int level, Rectangle region) { throw new NotSupportedException(); } diff --git a/Ryujinx.Graphics.Vulkan/TextureView.cs b/Ryujinx.Graphics.Vulkan/TextureView.cs index fbe32eca9c..129a77efa8 100644 --- a/Ryujinx.Graphics.Vulkan/TextureView.cs +++ b/Ryujinx.Graphics.Vulkan/TextureView.cs @@ -1,4 +1,5 @@ -using Ryujinx.Graphics.GAL; +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.GAL; using Silk.NET.Vulkan; using System; using System.Collections.Generic; @@ -873,17 +874,17 @@ namespace Ryujinx.Graphics.Vulkan return GetDataFromBuffer(result, size, result); } - public void SetData(ReadOnlySpan data) + public void SetData(SpanOrArray data) { SetData(data, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false); } - public void SetData(ReadOnlySpan data, int layer, int level) + public void SetData(SpanOrArray data, int layer, int level) { SetData(data, layer, level, 1, 1, singleSlice: true); } - public void SetData(ReadOnlySpan data, int layer, int level, Rectangle region) + public void SetData(SpanOrArray data, int layer, int level, Rectangle region) { SetData(data, layer, level, 1, 1, singleSlice: true, region); }