aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgdkchan <gab.dark.100@gmail.com>2022-09-19 16:12:56 -0300
committerGitHub <noreply@github.com>2022-09-19 16:12:56 -0300
commitda75a9a6ea89787c551b20e068a2bed8a8dc4f92 (patch)
tree812f5d63819c0b4ab2e27d1256c0d7c37ac53813
parent41790aa7434a5e6d132fad2e24844d450378205b (diff)
OpenGL: Fix blit from non-multisample to multisample texture (#3596)1.1.277
* OpenGL: Fix blit from non-multisample to multisample texture * New approach for multisample copy using compute shaders
-rw-r--r--Ryujinx.Graphics.OpenGL/Image/IntermmediatePool.cs98
-rw-r--r--Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs5
-rw-r--r--Ryujinx.Graphics.OpenGL/Image/TextureCopyMS.cs276
-rw-r--r--Ryujinx.Graphics.OpenGL/Image/TextureView.cs67
-rw-r--r--Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs3
-rw-r--r--Ryujinx.Graphics.OpenGL/Pipeline.cs36
6 files changed, 329 insertions, 156 deletions
diff --git a/Ryujinx.Graphics.OpenGL/Image/IntermmediatePool.cs b/Ryujinx.Graphics.OpenGL/Image/IntermmediatePool.cs
deleted file mode 100644
index 218a245e..00000000
--- a/Ryujinx.Graphics.OpenGL/Image/IntermmediatePool.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-using Ryujinx.Graphics.GAL;
-using System;
-using System.Collections.Generic;
-
-namespace Ryujinx.Graphics.OpenGL.Image
-{
- class IntermmediatePool : IDisposable
- {
- private readonly OpenGLRenderer _renderer;
- private readonly List<TextureView> _entries;
-
- public IntermmediatePool(OpenGLRenderer renderer)
- {
- _renderer = renderer;
- _entries = new List<TextureView>();
- }
-
- public TextureView GetOrCreateWithAtLeast(
- Target target,
- int blockWidth,
- int blockHeight,
- int bytesPerPixel,
- Format format,
- int width,
- int height,
- int depth,
- int levels)
- {
- TextureView entry;
-
- for (int i = 0; i < _entries.Count; i++)
- {
- entry = _entries[i];
-
- if (entry.Target == target && entry.Format == format)
- {
- if (entry.Width < width || entry.Height < height || entry.Info.Depth < depth || entry.Info.Levels < levels)
- {
- width = Math.Max(width, entry.Width);
- height = Math.Max(height, entry.Height);
- depth = Math.Max(depth, entry.Info.Depth);
- levels = Math.Max(levels, entry.Info.Levels);
-
- entry.Dispose();
- entry = CreateNew(target, blockWidth, blockHeight, bytesPerPixel, format, width, height, depth, levels);
- _entries[i] = entry;
- }
-
- return entry;
- }
- }
-
- entry = CreateNew(target, blockWidth, blockHeight, bytesPerPixel, format, width, height, depth, levels);
- _entries.Add(entry);
-
- return entry;
- }
-
- private TextureView CreateNew(
- Target target,
- int blockWidth,
- int blockHeight,
- int bytesPerPixel,
- Format format,
- int width,
- int height,
- int depth,
- int levels)
- {
- return (TextureView)_renderer.CreateTexture(new TextureCreateInfo(
- width,
- height,
- depth,
- levels,
- 1,
- blockWidth,
- blockHeight,
- bytesPerPixel,
- format,
- DepthStencilMode.Depth,
- target,
- SwizzleComponent.Red,
- SwizzleComponent.Green,
- SwizzleComponent.Blue,
- SwizzleComponent.Alpha), 1f);
- }
-
- public void Dispose()
- {
- foreach (TextureView entry in _entries)
- {
- entry.Dispose();
- }
-
- _entries.Clear();
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs b/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
index d7915445..75c4d870 100644
--- a/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
+++ b/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
@@ -9,8 +9,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
{
private readonly OpenGLRenderer _renderer;
- public IntermmediatePool IntermmediatePool { get; }
-
private int _srcFramebuffer;
private int _dstFramebuffer;
@@ -20,7 +18,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
public TextureCopy(OpenGLRenderer renderer)
{
_renderer = renderer;
- IntermmediatePool = new IntermmediatePool(renderer);
}
public void Copy(
@@ -517,8 +514,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
_copyPboHandle = 0;
}
-
- IntermmediatePool.Dispose();
}
}
}
diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureCopyMS.cs b/Ryujinx.Graphics.OpenGL/Image/TextureCopyMS.cs
new file mode 100644
index 00000000..9963dc66
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Image/TextureCopyMS.cs
@@ -0,0 +1,276 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.GAL;
+using System;
+using System.Numerics;
+
+namespace Ryujinx.Graphics.OpenGL.Image
+{
+ class TextureCopyMS
+ {
+ private const string ComputeShaderMSToNonMS = @"#version 450 core
+
+layout (binding = 0, $FORMAT$) uniform uimage2DMS imgIn;
+layout (binding = 1, $FORMAT$) uniform uimage2D imgOut;
+
+layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
+
+void main()
+{
+ uvec2 coords = gl_GlobalInvocationID.xy;
+ ivec2 imageSz = imageSize(imgOut);
+ if (int(coords.x) >= imageSz.x || int(coords.y) >= imageSz.y)
+ {
+ return;
+ }
+ int inSamples = imageSamples(imgIn);
+ int samplesInXLog2 = 0;
+ int samplesInYLog2 = 0;
+ switch (inSamples)
+ {
+ case 2:
+ samplesInXLog2 = 1;
+ break;
+ case 4:
+ samplesInXLog2 = 1;
+ samplesInYLog2 = 1;
+ break;
+ case 8:
+ samplesInXLog2 = 2;
+ samplesInYLog2 = 1;
+ break;
+ case 16:
+ samplesInXLog2 = 2;
+ samplesInYLog2 = 2;
+ break;
+ }
+ int samplesInX = 1 << samplesInXLog2;
+ int samplesInY = 1 << samplesInYLog2;
+ int sampleIdx = (int(coords.x) & (samplesInX - 1)) | ((int(coords.y) & (samplesInY - 1)) << samplesInXLog2);
+ uvec4 value = imageLoad(imgIn, ivec2(int(coords.x) >> samplesInXLog2, int(coords.y) >> samplesInYLog2), sampleIdx);
+ imageStore(imgOut, ivec2(coords), value);
+}";
+
+ private const string ComputeShaderNonMSToMS = @"#version 450 core
+
+layout (binding = 0, $FORMAT$) uniform uimage2D imgIn;
+layout (binding = 1, $FORMAT$) uniform uimage2DMS imgOut;
+
+layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
+
+void main()
+{
+ uvec2 coords = gl_GlobalInvocationID.xy;
+ ivec2 imageSz = imageSize(imgIn);
+ if (int(coords.x) >= imageSz.x || int(coords.y) >= imageSz.y)
+ {
+ return;
+ }
+ int outSamples = imageSamples(imgOut);
+ int samplesInXLog2 = 0;
+ int samplesInYLog2 = 0;
+ switch (outSamples)
+ {
+ case 2:
+ samplesInXLog2 = 1;
+ break;
+ case 4:
+ samplesInXLog2 = 1;
+ samplesInYLog2 = 1;
+ break;
+ case 8:
+ samplesInXLog2 = 2;
+ samplesInYLog2 = 1;
+ break;
+ case 16:
+ samplesInXLog2 = 2;
+ samplesInYLog2 = 2;
+ break;
+ }
+ int samplesInX = 1 << samplesInXLog2;
+ int samplesInY = 1 << samplesInYLog2;
+ int sampleIdx = (int(coords.x) & (samplesInX - 1)) | ((int(coords.y) & (samplesInY - 1)) << samplesInXLog2);
+ uvec4 value = imageLoad(imgIn, ivec2(coords));
+ imageStore(imgOut, ivec2(int(coords.x) >> samplesInXLog2, int(coords.y) >> samplesInYLog2), sampleIdx, value);
+}";
+
+ private readonly OpenGLRenderer _renderer;
+ private int[] _msToNonMSProgramHandles;
+ private int[] _nonMSToMSProgramHandles;
+
+ public TextureCopyMS(OpenGLRenderer renderer)
+ {
+ _renderer = renderer;
+ _msToNonMSProgramHandles = new int[5];
+ _nonMSToMSProgramHandles = new int[5];
+ }
+
+ public void CopyMSToNonMS(ITextureInfo src, ITextureInfo dst, int srcLayer, int dstLayer, int depth)
+ {
+ TextureCreateInfo srcInfo = src.Info;
+ TextureCreateInfo dstInfo = dst.Info;
+
+ int srcHandle = CreateViewIfNeeded(src);
+ int dstHandle = CreateViewIfNeeded(dst);
+
+ int dstWidth = dstInfo.Width;
+ int dstHeight = dstInfo.Height;
+
+ GL.UseProgram(GetMSToNonMSShader(srcInfo.BytesPerPixel));
+
+ for (int z = 0; z < depth; z++)
+ {
+ GL.BindImageTexture(0, srcHandle, 0, false, srcLayer + z, TextureAccess.ReadOnly, GetFormat(srcInfo.BytesPerPixel));
+ GL.BindImageTexture(1, dstHandle, 0, false, dstLayer + z, TextureAccess.WriteOnly, GetFormat(dstInfo.BytesPerPixel));
+
+ GL.DispatchCompute((dstWidth + 31) / 32, (dstHeight + 31) / 32, 1);
+ }
+
+ Pipeline pipeline = (Pipeline)_renderer.Pipeline;
+
+ pipeline.RestoreProgram();
+ pipeline.RestoreImages1And2();
+
+ DestroyViewIfNeeded(src, srcHandle);
+ DestroyViewIfNeeded(dst, dstHandle);
+ }
+
+ public void CopyNonMSToMS(ITextureInfo src, ITextureInfo dst, int srcLayer, int dstLayer, int depth)
+ {
+ TextureCreateInfo srcInfo = src.Info;
+ TextureCreateInfo dstInfo = dst.Info;
+
+ int srcHandle = CreateViewIfNeeded(src);
+ int dstHandle = CreateViewIfNeeded(dst);
+
+ int srcWidth = srcInfo.Width;
+ int srcHeight = srcInfo.Height;
+
+ GL.UseProgram(GetNonMSToMSShader(srcInfo.BytesPerPixel));
+
+ for (int z = 0; z < depth; z++)
+ {
+ GL.BindImageTexture(0, srcHandle, 0, false, srcLayer + z, TextureAccess.ReadOnly, GetFormat(srcInfo.BytesPerPixel));
+ GL.BindImageTexture(1, dstHandle, 0, false, dstLayer + z, TextureAccess.WriteOnly, GetFormat(dstInfo.BytesPerPixel));
+
+ GL.DispatchCompute((srcWidth + 31) / 32, (srcHeight + 31) / 32, 1);
+ }
+
+ Pipeline pipeline = (Pipeline)_renderer.Pipeline;
+
+ pipeline.RestoreProgram();
+ pipeline.RestoreImages1And2();
+
+ DestroyViewIfNeeded(src, srcHandle);
+ DestroyViewIfNeeded(dst, dstHandle);
+ }
+
+ private static SizedInternalFormat GetFormat(int bytesPerPixel)
+ {
+ return bytesPerPixel switch
+ {
+ 1 => SizedInternalFormat.R8ui,
+ 2 => SizedInternalFormat.R16ui,
+ 4 => SizedInternalFormat.R32ui,
+ 8 => SizedInternalFormat.Rg32ui,
+ 16 => SizedInternalFormat.Rgba32ui,
+ _ => throw new ArgumentException($"Invalid bytes per pixel {bytesPerPixel}.")
+ };
+ }
+
+ private static int CreateViewIfNeeded(ITextureInfo texture)
+ {
+ // Binding sRGB textures as images doesn't work on NVIDIA,
+ // we need to create and bind a RGBA view for it to work.
+ if (texture.Info.Format == Format.R8G8B8A8Srgb)
+ {
+ int handle = GL.GenTexture();
+
+ GL.TextureView(
+ handle,
+ texture.Info.Target.Convert(),
+ texture.Storage.Handle,
+ PixelInternalFormat.Rgba8,
+ texture.FirstLevel,
+ 1,
+ texture.FirstLayer,
+ texture.Info.GetLayers());
+
+ return handle;
+ }
+
+ return texture.Handle;
+ }
+
+ private static void DestroyViewIfNeeded(ITextureInfo info, int handle)
+ {
+ if (info.Handle != handle)
+ {
+ GL.DeleteTexture(handle);
+ }
+ }
+
+ private int GetMSToNonMSShader(int bytesPerPixel)
+ {
+ return GetShader(ComputeShaderMSToNonMS, _msToNonMSProgramHandles, bytesPerPixel);
+ }
+
+ private int GetNonMSToMSShader(int bytesPerPixel)
+ {
+ return GetShader(ComputeShaderNonMSToMS, _nonMSToMSProgramHandles, bytesPerPixel);
+ }
+
+ private int GetShader(string code, int[] programHandles, int bytesPerPixel)
+ {
+ int index = BitOperations.Log2((uint)bytesPerPixel);
+
+ if (programHandles[index] == 0)
+ {
+ int csHandle = GL.CreateShader(ShaderType.ComputeShader);
+
+ string format = new[] { "r8ui", "r16ui", "r32ui", "rg32ui", "rgba32ui" }[index];
+
+ GL.ShaderSource(csHandle, code.Replace("$FORMAT$", format));
+ GL.CompileShader(csHandle);
+
+ int programHandle = GL.CreateProgram();
+
+ GL.AttachShader(programHandle, csHandle);
+ GL.LinkProgram(programHandle);
+ GL.DetachShader(programHandle, csHandle);
+ GL.DeleteShader(csHandle);
+
+ GL.GetProgram(programHandle, GetProgramParameterName.LinkStatus, out int status);
+
+ if (status == 0)
+ {
+ throw new Exception(GL.GetProgramInfoLog(programHandle));
+ }
+
+ programHandles[index] = programHandle;
+ }
+
+ return programHandles[index];
+ }
+
+ public void Dispose()
+ {
+ for (int i = 0; i < _msToNonMSProgramHandles.Length; i++)
+ {
+ if (_msToNonMSProgramHandles[i] != 0)
+ {
+ GL.DeleteProgram(_msToNonMSProgramHandles[i]);
+ _msToNonMSProgramHandles[i] = 0;
+ }
+ }
+
+ for (int i = 0; i < _nonMSToMSProgramHandles.Length; i++)
+ {
+ if (_nonMSToMSProgramHandles[i] != 0)
+ {
+ GL.DeleteProgram(_nonMSToMSProgramHandles[i]);
+ _nonMSToMSProgramHandles[i] = 0;
+ }
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
index 243ca1b3..f17243d2 100644
--- a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
+++ b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
@@ -116,28 +116,15 @@ namespace Ryujinx.Graphics.OpenGL.Image
{
TextureView destinationView = (TextureView)destination;
- if (destinationView.Target.IsMultisample() || Target.IsMultisample())
+ if (!destinationView.Target.IsMultisample() && Target.IsMultisample())
{
- Extents2D srcRegion = new Extents2D(0, 0, Width, Height);
- Extents2D dstRegion = new Extents2D(0, 0, destinationView.Width, destinationView.Height);
-
- TextureView intermmediate = _renderer.TextureCopy.IntermmediatePool.GetOrCreateWithAtLeast(
- GetIntermmediateTarget(Target),
- Info.BlockWidth,
- Info.BlockHeight,
- Info.BytesPerPixel,
- Format,
- Width,
- Height,
- Info.Depth,
- Info.Levels);
-
- GL.Disable(EnableCap.FramebufferSrgb);
-
- _renderer.TextureCopy.Copy(this, intermmediate, srcRegion, srcRegion, true);
- _renderer.TextureCopy.Copy(intermmediate, destinationView, srcRegion, dstRegion, true, 0, firstLayer, 0, firstLevel);
-
- GL.Enable(EnableCap.FramebufferSrgb);
+ int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
+ _renderer.TextureCopyMS.CopyMSToNonMS(this, destinationView, 0, firstLayer, layers);
+ }
+ else if (destinationView.Target.IsMultisample() && !Target.IsMultisample())
+ {
+ int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
+ _renderer.TextureCopyMS.CopyNonMSToMS(this, destinationView, 0, firstLayer, layers);
}
else
{
@@ -149,28 +136,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
{
TextureView destinationView = (TextureView)destination;
- if (destinationView.Target.IsMultisample() || Target.IsMultisample())
+ if (!destinationView.Target.IsMultisample() && Target.IsMultisample())
{
- Extents2D srcRegion = new Extents2D(0, 0, Width, Height);
- Extents2D dstRegion = new Extents2D(0, 0, destinationView.Width, destinationView.Height);
-
- TextureView intermmediate = _renderer.TextureCopy.IntermmediatePool.GetOrCreateWithAtLeast(
- GetIntermmediateTarget(Target),
- Info.BlockWidth,
- Info.BlockHeight,
- Info.BytesPerPixel,
- Format,
- Math.Max(1, Width >> srcLevel),
- Math.Max(1, Height >> srcLevel),
- 1,
- 1);
-
- GL.Disable(EnableCap.FramebufferSrgb);
-
- _renderer.TextureCopy.Copy(this, intermmediate, srcRegion, srcRegion, true, srcLayer, 0, srcLevel, 0, 1, 1);
- _renderer.TextureCopy.Copy(intermmediate, destinationView, srcRegion, dstRegion, true, 0, dstLayer, 0, dstLevel, 1, 1);
-
- GL.Enable(EnableCap.FramebufferSrgb);
+ _renderer.TextureCopyMS.CopyMSToNonMS(this, destinationView, srcLayer, dstLayer,1);
+ }
+ else if (destinationView.Target.IsMultisample() && !Target.IsMultisample())
+ {
+ _renderer.TextureCopyMS.CopyNonMSToMS(this, destinationView, srcLayer, dstLayer, 1);
}
else
{
@@ -178,17 +150,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
- private static Target GetIntermmediateTarget(Target srcTarget)
- {
- return srcTarget switch
- {
- Target.Texture2D => Target.Texture2DMultisample,
- Target.Texture2DArray => Target.Texture2DMultisampleArray,
- Target.Texture2DMultisampleArray => Target.Texture2DArray,
- _ => Target.Texture2D
- };
- }
-
public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)
{
_renderer.TextureCopy.Copy(this, (TextureView)destination, srcRegion, dstRegion, linearFilter);
diff --git a/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs
index 0b9acf10..418976e6 100644
--- a/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs
+++ b/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs
@@ -24,6 +24,7 @@ namespace Ryujinx.Graphics.OpenGL
private TextureCopy _textureCopy;
private TextureCopy _backgroundTextureCopy;
internal TextureCopy TextureCopy => BackgroundContextWorker.InBackground ? _backgroundTextureCopy : _textureCopy;
+ internal TextureCopyMS TextureCopyMS { get; }
private Sync _sync;
@@ -48,6 +49,7 @@ namespace Ryujinx.Graphics.OpenGL
_window = new Window(this);
_textureCopy = new TextureCopy(this);
_backgroundTextureCopy = new TextureCopy(this);
+ TextureCopyMS = new TextureCopyMS(this);
_sync = new Sync();
PersistentBuffers = new PersistentBuffers();
ResourcePool = new ResourcePool();
@@ -211,6 +213,7 @@ namespace Ryujinx.Graphics.OpenGL
{
_textureCopy.Dispose();
_backgroundTextureCopy.Dispose();
+ TextureCopyMS.Dispose();
PersistentBuffers.Dispose();
ResourcePool.Dispose();
_pipeline.Dispose();
diff --git a/Ryujinx.Graphics.OpenGL/Pipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs
index d7cd6fbd..5f224625 100644
--- a/Ryujinx.Graphics.OpenGL/Pipeline.cs
+++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs
@@ -11,6 +11,8 @@ namespace Ryujinx.Graphics.OpenGL
{
class Pipeline : IPipeline, IDisposable
{
+ private const int SavedImages = 2;
+
private readonly DrawTextureEmulation _drawTexture;
internal ulong DrawCount { get; private set; }
@@ -46,6 +48,7 @@ namespace Ryujinx.Graphics.OpenGL
private Vector4<float>[] _renderScale = new Vector4<float>[73];
private int _fragmentScaleCount;
+ private (TextureBase, Format)[] _images;
private TextureBase _unit0Texture;
private Sampler _unit0Sampler;
@@ -78,6 +81,8 @@ namespace Ryujinx.Graphics.OpenGL
_fragmentOutputMap = uint.MaxValue;
_componentMasks = uint.MaxValue;
+ _images = new (TextureBase, Format)[SavedImages];
+
var defaultScale = new Vector4<float> { X = 1f, Y = 0f, Z = 0f, W = 0f };
new Span<Vector4<float>>(_renderScale).Fill(defaultScale);
@@ -907,6 +912,11 @@ namespace Ryujinx.Graphics.OpenGL
public void SetImage(int binding, ITexture texture, Format imageFormat)
{
+ if ((uint)binding < SavedImages)
+ {
+ _images[binding] = (texture as TextureBase, imageFormat);
+ }
+
if (texture == null)
{
return;
@@ -1608,6 +1618,32 @@ namespace Ryujinx.Graphics.OpenGL
}
}
+ public void RestoreProgram()
+ {
+ _program?.Bind();
+ }
+
+ public void RestoreImages1And2()
+ {
+ for (int i = 0; i < SavedImages; i++)
+ {
+ (TextureBase texBase, Format imageFormat) = _images[i];
+
+ if (texBase != null)
+ {
+ SizedInternalFormat format = FormatTable.GetImageFormat(imageFormat);
+
+ if (format != 0)
+ {
+ GL.BindImageTexture(i, texBase.Handle, 0, true, 0, TextureAccess.ReadWrite, format);
+ continue;
+ }
+ }
+
+ GL.BindImageTexture(i, 0, 0, true, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
+ }
+ }
+
public bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual)
{
if (value is CounterQueueEvent)