diff --git a/Ryujinx.Cpu/MemoryManager.cs b/Ryujinx.Cpu/MemoryManager.cs index 9eb27c4f4..153bff629 100644 --- a/Ryujinx.Cpu/MemoryManager.cs +++ b/Ryujinx.Cpu/MemoryManager.cs @@ -237,7 +237,7 @@ namespace Ryujinx.Cpu } /// - public unsafe WritableRegion GetWritableRegion(ulong va, int size) + public unsafe WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) { if (size == 0) { @@ -246,6 +246,11 @@ namespace Ryujinx.Cpu if (IsContiguousAndMapped(va, size)) { + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, true); + } + return new WritableRegion(null, va, new NativeMemoryManager((byte*)GetHostAddress(va), size).Memory); } else @@ -254,7 +259,7 @@ namespace Ryujinx.Cpu GetSpan(va, size).CopyTo(memory.Span); - return new WritableRegion(this, va, memory); + return new WritableRegion(this, va, memory, tracked); } } diff --git a/Ryujinx.Cpu/MemoryManagerHostMapped.cs b/Ryujinx.Cpu/MemoryManagerHostMapped.cs index 0b4e0e4cb..705cedeb9 100644 --- a/Ryujinx.Cpu/MemoryManagerHostMapped.cs +++ b/Ryujinx.Cpu/MemoryManagerHostMapped.cs @@ -285,9 +285,16 @@ namespace Ryujinx.Cpu } /// - public WritableRegion GetWritableRegion(ulong va, int size) + public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) { - AssertMapped(va, (ulong)size); + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, true); + } + else + { + AssertMapped(va, (ulong)size); + } return _addressSpaceMirror.GetWritableRegion(va, size); } diff --git a/Ryujinx.Graphics.GAL/IRenderer.cs b/Ryujinx.Graphics.GAL/IRenderer.cs index 18f10915b..56a401723 100644 --- a/Ryujinx.Graphics.GAL/IRenderer.cs +++ b/Ryujinx.Graphics.GAL/IRenderer.cs @@ -27,7 +27,7 @@ namespace Ryujinx.Graphics.GAL void DeleteBuffer(BufferHandle buffer); - byte[] GetBufferData(BufferHandle buffer, int offset, int size); + ReadOnlySpan GetBufferData(BufferHandle buffer, int offset, int size); Capabilities GetCapabilities(); diff --git a/Ryujinx.Graphics.GAL/ITexture.cs b/Ryujinx.Graphics.GAL/ITexture.cs index ad8fd297c..c011b05ce 100644 --- a/Ryujinx.Graphics.GAL/ITexture.cs +++ b/Ryujinx.Graphics.GAL/ITexture.cs @@ -14,7 +14,7 @@ namespace Ryujinx.Graphics.GAL ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel); - byte[] GetData(); + ReadOnlySpan GetData(); void SetData(ReadOnlySpan data); void SetData(ReadOnlySpan data, int layer, int level); diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index c9bff5611..a3105cf28 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -4,6 +4,7 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture.Astc; +using Ryujinx.Memory; using Ryujinx.Memory.Range; using System; using System.Collections.Generic; @@ -821,14 +822,7 @@ namespace Ryujinx.Graphics.Gpu.Image return; // Flushing this format is not supported, as it may have been converted to another host format. } - if (tracked) - { - _physicalMemory.Write(Range, GetTextureDataFromGpu(tracked)); - } - else - { - _physicalMemory.WriteUntracked(Range, GetTextureDataFromGpu(tracked)); - } + FlushTextureDataToGuest(tracked); } /// @@ -864,10 +858,44 @@ namespace Ryujinx.Graphics.Gpu.Image texture = _flushHostTexture = GetScaledHostTexture(1f, _flushHostTexture); } - _physicalMemory.WriteUntracked(Range, GetTextureDataFromGpu(false, texture)); + FlushTextureDataToGuest(false, texture); }); } + /// + /// Gets data from the host GPU, and flushes it to guest memory. + /// + /// + /// This method should be used to retrieve data that was modified by the host GPU. + /// This is not cheap, avoid doing that unless strictly needed. + /// When possible, the data is written directly into guest memory, rather than copied. + /// + /// True if writing the texture data is tracked, false otherwise + /// The specific host texture to flush. Defaults to this texture + private void FlushTextureDataToGuest(bool tracked, ITexture texture = null) + { + if (Range.Count == 1) + { + MemoryRange subrange = Range.GetSubRange(0); + + using (WritableRegion region = _physicalMemory.GetWritableRegion(subrange.Address, (int)subrange.Size, tracked)) + { + GetTextureDataFromGpu(region.Memory.Span, tracked, texture); + } + } + else + { + if (tracked) + { + _physicalMemory.Write(Range, GetTextureDataFromGpu(Span.Empty, true, texture)); + } + else + { + _physicalMemory.WriteUntracked(Range, GetTextureDataFromGpu(Span.Empty, false, texture)); + } + } + } + /// /// Gets data from the host GPU. /// @@ -875,8 +903,11 @@ namespace Ryujinx.Graphics.Gpu.Image /// This method should be used to retrieve data that was modified by the host GPU. /// This is not cheap, avoid doing that unless strictly needed. /// - /// Host texture data - private ReadOnlySpan GetTextureDataFromGpu(bool blacklist, ITexture texture = null) + /// An output span to place the texture data into. If empty, one is generated + /// True if the texture should be blacklisted, false otherwise + /// The specific host texture to flush. Defaults to this texture + /// The span containing the texture data + private ReadOnlySpan GetTextureDataFromGpu(Span output, bool blacklist, ITexture texture = null) { ReadOnlySpan data; @@ -909,6 +940,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (Info.IsLinear) { data = LayoutConverter.ConvertLinearToLinearStrided( + output, Info.Width, Info.Height, Info.FormatInfo.BlockWidth, @@ -920,6 +952,7 @@ namespace Ryujinx.Graphics.Gpu.Image else { data = LayoutConverter.ConvertLinearToBlockLinear( + output, Info.Width, Info.Height, _depth, diff --git a/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs index 96e10e77b..3f869a506 100644 --- a/Ryujinx.Graphics.Gpu/Memory/Buffer.cs +++ b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs @@ -412,7 +412,7 @@ namespace Ryujinx.Graphics.Gpu.Memory { int offset = (int)(address - Address); - byte[] data = _context.Renderer.GetBufferData(Handle, offset, (int)size); + ReadOnlySpan data = _context.Renderer.GetBufferData(Handle, offset, (int)size); // TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers. _physicalMemory.WriteUntracked(address, data); diff --git a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs index 289e7c1be..8df3c8fb3 100644 --- a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs +++ b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs @@ -128,10 +128,11 @@ namespace Ryujinx.Graphics.Gpu.Memory /// /// Start address of the range /// Size in bytes to be range + /// True if write tracking is triggered on the span /// A writable region with the data at the specified memory location - public WritableRegion GetWritableRegion(ulong address, int size) + public WritableRegion GetWritableRegion(ulong address, int size, bool tracked = false) { - return _cpuMemory.GetWritableRegion(address, size); + return _cpuMemory.GetWritableRegion(address, size, tracked); } /// diff --git a/Ryujinx.Graphics.OpenGL/Buffer.cs b/Ryujinx.Graphics.OpenGL/Buffer.cs index ecb7dc908..0f6a90e3c 100644 --- a/Ryujinx.Graphics.OpenGL/Buffer.cs +++ b/Ryujinx.Graphics.OpenGL/Buffer.cs @@ -55,15 +55,22 @@ namespace Ryujinx.Graphics.OpenGL (IntPtr)size); } - public static byte[] GetData(BufferHandle buffer, int offset, int size) + public static unsafe ReadOnlySpan GetData(Renderer renderer, BufferHandle buffer, int offset, int size) { - GL.BindBuffer(BufferTarget.CopyReadBuffer, buffer.ToInt32()); + if (HwCapabilities.UsePersistentBufferForFlush) + { + return renderer.PersistentBuffers.Default.GetBufferData(buffer, offset, size); + } + else + { + IntPtr target = renderer.PersistentBuffers.Default.GetHostArray(size); - byte[] data = new byte[size]; + GL.BindBuffer(BufferTarget.CopyReadBuffer, buffer.ToInt32()); - GL.GetBufferSubData(BufferTarget.CopyReadBuffer, (IntPtr)offset, size, data); + GL.GetBufferSubData(BufferTarget.CopyReadBuffer, (IntPtr)offset, size, target); - return data; + return new ReadOnlySpan(target.ToPointer(), size); + } } public static void Resize(BufferHandle handle, int size) diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs b/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs index f49a06478..8d407ccb2 100644 --- a/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs +++ b/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs @@ -38,9 +38,9 @@ namespace Ryujinx.Graphics.OpenGL.Image throw new NotSupportedException(); } - public byte[] GetData() + public ReadOnlySpan GetData() { - return Buffer.GetData(_buffer, _bufferOffset, _bufferSize); + return Buffer.GetData(_renderer, _buffer, _bufferOffset, _bufferSize); } public void SetData(ReadOnlySpan data) diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs index 5e7b5f226..460d1da40 100644 --- a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs +++ b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs @@ -119,7 +119,7 @@ namespace Ryujinx.Graphics.OpenGL.Image _renderer.TextureCopy.Copy(this, (TextureView)destination, srcRegion, dstRegion, linearFilter); } - public byte[] GetData() + public unsafe ReadOnlySpan GetData() { int size = 0; @@ -134,17 +134,11 @@ namespace Ryujinx.Graphics.OpenGL.Image } else { - byte[] data = new byte[size]; + IntPtr target = _renderer.PersistentBuffers.Default.GetHostArray(size); - unsafe - { - fixed (byte* ptr = data) - { - WriteTo((IntPtr)ptr); - } - } + WriteTo(target); - return data; + return new ReadOnlySpan(target.ToPointer(), size); } } diff --git a/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs b/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs index 740f8b987..fe224433c 100644 --- a/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs +++ b/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using OpenTK.Graphics.OpenGL; using Ryujinx.Common.Logging; @@ -27,6 +28,9 @@ namespace Ryujinx.Graphics.OpenGL private int _copyBufferHandle; private int _copyBufferSize; + private byte[] _data; + private IntPtr _dataMap; + private void EnsureBuffer(int requiredSize) { if (_copyBufferSize < requiredSize && _copyBufferHandle != 0) @@ -48,6 +52,18 @@ namespace Ryujinx.Graphics.OpenGL } } + public unsafe IntPtr GetHostArray(int requiredSize) + { + if (_data == null || _data.Length < requiredSize) + { + _data = GC.AllocateUninitializedArray(requiredSize, true); + + _dataMap = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(_data)); + } + + return _dataMap; + } + private void Sync() { GL.MemoryBarrier(MemoryBarrierFlags.ClientMappedBufferBarrierBit); @@ -63,7 +79,7 @@ namespace Ryujinx.Graphics.OpenGL GL.DeleteSync(sync); } - public byte[] GetTextureData(TextureView view, int size) + public unsafe ReadOnlySpan GetTextureData(TextureView view, int size) { EnsureBuffer(size); @@ -73,16 +89,12 @@ namespace Ryujinx.Graphics.OpenGL GL.BindBuffer(BufferTarget.PixelPackBuffer, 0); - byte[] data = new byte[size]; - Sync(); - Marshal.Copy(_bufferMap, data, 0, size); - - return data; + return new ReadOnlySpan(_bufferMap.ToPointer(), size); } - public byte[] GetBufferData(BufferHandle buffer, int offset, int size) + public unsafe ReadOnlySpan GetBufferData(BufferHandle buffer, int offset, int size) { EnsureBuffer(size); @@ -93,13 +105,9 @@ namespace Ryujinx.Graphics.OpenGL GL.BindBuffer(BufferTarget.CopyWriteBuffer, 0); - byte[] data = new byte[size]; - Sync(); - Marshal.Copy(_bufferMap, data, 0, size); - - return data; + return new ReadOnlySpan(_bufferMap.ToPointer(), size); } public void Dispose() diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs index 561f06849..c77492286 100644 --- a/Ryujinx.Graphics.OpenGL/Renderer.cs +++ b/Ryujinx.Graphics.OpenGL/Renderer.cs @@ -91,16 +91,9 @@ namespace Ryujinx.Graphics.OpenGL Buffer.Delete(buffer); } - public byte[] GetBufferData(BufferHandle buffer, int offset, int size) + public ReadOnlySpan GetBufferData(BufferHandle buffer, int offset, int size) { - if (HwCapabilities.UsePersistentBufferForFlush) - { - return PersistentBuffers.Default.GetBufferData(buffer, offset, size); - } - else - { - return Buffer.GetData(buffer, offset, size); - } + return Buffer.GetData(this, buffer, offset, size); } public Capabilities GetCapabilities() diff --git a/Ryujinx.Graphics.Texture/LayoutConverter.cs b/Ryujinx.Graphics.Texture/LayoutConverter.cs index 1b7dad2a6..970d08cb4 100644 --- a/Ryujinx.Graphics.Texture/LayoutConverter.cs +++ b/Ryujinx.Graphics.Texture/LayoutConverter.cs @@ -359,6 +359,7 @@ namespace Ryujinx.Graphics.Texture } public static ReadOnlySpan ConvertLinearToBlockLinear( + Span output, int width, int height, int depth, @@ -373,7 +374,10 @@ namespace Ryujinx.Graphics.Texture SizeInfo sizeInfo, ReadOnlySpan data) { - Span output = new byte[sizeInfo.TotalSize]; + if (output.Length == 0) + { + output = new byte[sizeInfo.TotalSize]; + } int inOffs = 0; @@ -500,6 +504,7 @@ namespace Ryujinx.Graphics.Texture } public static ReadOnlySpan ConvertLinearToLinearStrided( + Span output, int width, int height, int blockWidth, @@ -516,10 +521,21 @@ namespace Ryujinx.Graphics.Texture if (inStride == stride) { - return data; + if (output.Length != 0) + { + data.CopyTo(output); + return output; + } + else + { + return data; + } } - Span output = new byte[h * stride]; + if (output.Length == 0) + { + output = new byte[h * stride]; + } int inOffs = 0; int outOffs = 0; diff --git a/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs b/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs index 5051b2066..938c9d1d8 100644 --- a/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs +++ b/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs @@ -49,7 +49,7 @@ namespace Ryujinx.Memory.Tests throw new NotImplementedException(); } - public WritableRegion GetWritableRegion(ulong va, int size) + public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) { throw new NotImplementedException(); } diff --git a/Ryujinx.Memory/AddressSpaceManager.cs b/Ryujinx.Memory/AddressSpaceManager.cs index d8ee47467..050b5c059 100644 --- a/Ryujinx.Memory/AddressSpaceManager.cs +++ b/Ryujinx.Memory/AddressSpaceManager.cs @@ -207,9 +207,10 @@ namespace Ryujinx.Memory /// /// Virtual address of the data /// Size of the data + /// True if write tracking is triggered on the span /// A writable region of memory containing the data /// Throw for unhandled invalid or unmapped memory accesses - public unsafe WritableRegion GetWritableRegion(ulong va, int size) + public unsafe WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) { if (size == 0) { diff --git a/Ryujinx.Memory/IVirtualMemoryManager.cs b/Ryujinx.Memory/IVirtualMemoryManager.cs index 056940cc4..2448ba036 100644 --- a/Ryujinx.Memory/IVirtualMemoryManager.cs +++ b/Ryujinx.Memory/IVirtualMemoryManager.cs @@ -87,9 +87,10 @@ namespace Ryujinx.Memory /// /// Virtual address of the data /// Size of the data + /// True if write tracking is triggered on the span /// A writable region of memory containing the data /// Throw for unhandled invalid or unmapped memory accesses - WritableRegion GetWritableRegion(ulong va, int size); + WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false); /// /// Gets a reference for the given type at the specified virtual memory address. diff --git a/Ryujinx.Memory/IWritableBlock.cs b/Ryujinx.Memory/IWritableBlock.cs index c95b754de..36b9f5a6e 100644 --- a/Ryujinx.Memory/IWritableBlock.cs +++ b/Ryujinx.Memory/IWritableBlock.cs @@ -5,5 +5,7 @@ namespace Ryujinx.Memory public interface IWritableBlock { void Write(ulong va, ReadOnlySpan data); + + void WriteUntracked(ulong va, ReadOnlySpan data) => Write(va, data); } } diff --git a/Ryujinx.Memory/WritableRegion.cs b/Ryujinx.Memory/WritableRegion.cs index 467ee7f79..21565ea5c 100644 --- a/Ryujinx.Memory/WritableRegion.cs +++ b/Ryujinx.Memory/WritableRegion.cs @@ -6,15 +6,17 @@ namespace Ryujinx.Memory { private readonly IWritableBlock _block; private readonly ulong _va; + private readonly bool _tracked; private bool NeedsWriteback => _block != null; public Memory Memory { get; } - public WritableRegion(IWritableBlock block, ulong va, Memory memory) + public WritableRegion(IWritableBlock block, ulong va, Memory memory, bool tracked = false) { _block = block; _va = va; + _tracked = tracked; Memory = memory; } @@ -22,7 +24,14 @@ namespace Ryujinx.Memory { if (NeedsWriteback) { - _block.Write(_va, Memory.Span); + if (_tracked) + { + _block.Write(_va, Memory.Span); + } + else + { + _block.WriteUntracked(_va, Memory.Span); + } } } }