From ca5ac37cd638222e7475ac8f632b878126f3462d Mon Sep 17 00:00:00 2001 From: riperiperi Date: Fri, 16 Jul 2021 22:10:20 +0100 Subject: [PATCH] Flush buffers and texture data through a persistent mapped buffer. (#2481) * Use persistent buffers to flush texture data * Flush buffers via copy to persistent buffers. * Log error when timing out, small refactoring. --- Ryujinx.Graphics.Gpu/Image/Texture.cs | 4 +- Ryujinx.Graphics.OpenGL/Image/TextureView.cs | 19 +-- Ryujinx.Graphics.OpenGL/PersistentBuffers.cs | 120 +++++++++++++++++++ Ryujinx.Graphics.OpenGL/Renderer.cs | 6 +- Ryujinx.Graphics.Texture/LayoutConverter.cs | 9 +- 5 files changed, 135 insertions(+), 23 deletions(-) create mode 100644 Ryujinx.Graphics.OpenGL/PersistentBuffers.cs diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index 6ca15ea107..c9bff5611d 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -876,9 +876,9 @@ namespace Ryujinx.Graphics.Gpu.Image /// This is not cheap, avoid doing that unless strictly needed. /// /// Host texture data - private Span GetTextureDataFromGpu(bool blacklist, ITexture texture = null) + private ReadOnlySpan GetTextureDataFromGpu(bool blacklist, ITexture texture = null) { - Span data; + ReadOnlySpan data; if (texture != null) { diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs index 8e8d3c78ad..770e99a091 100644 --- a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs +++ b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs @@ -121,24 +121,7 @@ namespace Ryujinx.Graphics.OpenGL.Image public byte[] GetData() { - int size = 0; - - for (int level = 0; level < Info.Levels; level++) - { - size += Info.GetMipSize(level); - } - - byte[] data = new byte[size]; - - unsafe - { - fixed (byte* ptr = data) - { - WriteTo((IntPtr)ptr); - } - } - - return data; + return _renderer.PersistentBuffers.Default.GetTextureData(this); } public void WriteToPbo(int offset, bool forceBgra) diff --git a/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs b/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs new file mode 100644 index 0000000000..a7cefca3dd --- /dev/null +++ b/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs @@ -0,0 +1,120 @@ +using System; +using System.Runtime.InteropServices; +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.OpenGL.Image; + +namespace Ryujinx.Graphics.OpenGL +{ + class PersistentBuffers : IDisposable + { + private PersistentBuffer _main = new PersistentBuffer(); + private PersistentBuffer _background = new PersistentBuffer(); + + public PersistentBuffer Default => BackgroundContextWorker.InBackground ? _background : _main; + + public void Dispose() + { + _main?.Dispose(); + _background?.Dispose(); + } + } + + class PersistentBuffer : IDisposable + { + private IntPtr _bufferMap; + private int _copyBufferHandle; + private int _copyBufferSize; + + private void EnsureBuffer(int requiredSize) + { + if (_copyBufferSize < requiredSize && _copyBufferHandle != 0) + { + GL.DeleteBuffer(_copyBufferHandle); + + _copyBufferHandle = 0; + } + + if (_copyBufferHandle == 0) + { + _copyBufferHandle = GL.GenBuffer(); + _copyBufferSize = requiredSize; + + GL.BindBuffer(BufferTarget.CopyWriteBuffer, _copyBufferHandle); + GL.BufferStorage(BufferTarget.CopyWriteBuffer, requiredSize, IntPtr.Zero, BufferStorageFlags.MapReadBit | BufferStorageFlags.MapPersistentBit); + + _bufferMap = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, IntPtr.Zero, requiredSize, BufferAccessMask.MapReadBit | BufferAccessMask.MapPersistentBit); + } + } + + private void Sync() + { + GL.MemoryBarrier(MemoryBarrierFlags.ClientMappedBufferBarrierBit); + + IntPtr sync = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None); + WaitSyncStatus syncResult = GL.ClientWaitSync(sync, ClientWaitSyncFlags.SyncFlushCommandsBit, 1000000000); + + if (syncResult == WaitSyncStatus.TimeoutExpired) + { + Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to sync persistent buffer state within 1000ms. Continuing..."); + } + + GL.DeleteSync(sync); + } + + public byte[] GetTextureData(TextureView view) + { + int size = 0; + + for (int level = 0; level < view.Info.Levels; level++) + { + size += view.Info.GetMipSize(level); + } + + EnsureBuffer(size); + + GL.BindBuffer(BufferTarget.PixelPackBuffer, _copyBufferHandle); + + view.WriteToPbo(0, false); + + GL.BindBuffer(BufferTarget.PixelPackBuffer, 0); + + byte[] data = new byte[size]; + + Sync(); + + Marshal.Copy(_bufferMap, data, 0, size); + + return data; + } + + public byte[] GetBufferData(BufferHandle buffer, int offset, int size) + { + EnsureBuffer(size); + + GL.BindBuffer(BufferTarget.CopyReadBuffer, buffer.ToInt32()); + GL.BindBuffer(BufferTarget.CopyWriteBuffer, _copyBufferHandle); + + GL.CopyBufferSubData(BufferTarget.CopyReadBuffer, BufferTarget.CopyWriteBuffer, (IntPtr)offset, IntPtr.Zero, size); + + GL.BindBuffer(BufferTarget.CopyWriteBuffer, 0); + + byte[] data = new byte[size]; + + Sync(); + + Marshal.Copy(_bufferMap, data, 0, size); + + return data; + } + + public void Dispose() + { + if (_copyBufferHandle != 0) + { + GL.DeleteBuffer(_copyBufferHandle); + } + } + } +} diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs index 001cac8d62..41629c1d29 100644 --- a/Ryujinx.Graphics.OpenGL/Renderer.cs +++ b/Ryujinx.Graphics.OpenGL/Renderer.cs @@ -30,6 +30,8 @@ namespace Ryujinx.Graphics.OpenGL public event EventHandler ScreenCaptured; + internal PersistentBuffers PersistentBuffers { get; } + internal ResourcePool ResourcePool { get; } internal int BufferCount { get; private set; } @@ -46,6 +48,7 @@ namespace Ryujinx.Graphics.OpenGL _textureCopy = new TextureCopy(this); _backgroundTextureCopy = new TextureCopy(this); _sync = new Sync(); + PersistentBuffers = new PersistentBuffers(); ResourcePool = new ResourcePool(); } @@ -90,7 +93,7 @@ namespace Ryujinx.Graphics.OpenGL public byte[] GetBufferData(BufferHandle buffer, int offset, int size) { - return Buffer.GetData(buffer, offset, size); + return PersistentBuffers.Default.GetBufferData(buffer, offset, size); } public Capabilities GetCapabilities() @@ -177,6 +180,7 @@ namespace Ryujinx.Graphics.OpenGL { _textureCopy.Dispose(); _backgroundTextureCopy.Dispose(); + PersistentBuffers.Dispose(); ResourcePool.Dispose(); _pipeline.Dispose(); _window.Dispose(); diff --git a/Ryujinx.Graphics.Texture/LayoutConverter.cs b/Ryujinx.Graphics.Texture/LayoutConverter.cs index fb25541bef..1b7dad2a67 100644 --- a/Ryujinx.Graphics.Texture/LayoutConverter.cs +++ b/Ryujinx.Graphics.Texture/LayoutConverter.cs @@ -358,7 +358,7 @@ namespace Ryujinx.Graphics.Texture }; } - public static Span ConvertLinearToBlockLinear( + public static ReadOnlySpan ConvertLinearToBlockLinear( int width, int height, int depth, @@ -499,7 +499,7 @@ namespace Ryujinx.Graphics.Texture return output; } - public static Span ConvertLinearToLinearStrided( + public static ReadOnlySpan ConvertLinearToLinearStrided( int width, int height, int blockWidth, @@ -514,6 +514,11 @@ namespace Ryujinx.Graphics.Texture int inStride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); int lineSize = width * bytesPerPixel; + if (inStride == stride) + { + return data; + } + Span output = new byte[h * stride]; int inOffs = 0;