diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index 6ca15ea1..c9bff561 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 8e8d3c78..770e99a0 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 00000000..a7cefca3
--- /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 001cac8d..41629c1d 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 fb25541b..1b7dad2a 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;