From da75a9a6ea89787c551b20e068a2bed8a8dc4f92 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Mon, 19 Sep 2022 16:12:56 -0300 Subject: [PATCH] OpenGL: Fix blit from non-multisample to multisample texture (#3596) * OpenGL: Fix blit from non-multisample to multisample texture * New approach for multisample copy using compute shaders --- .../Image/IntermmediatePool.cs | 98 ------- Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs | 5 - .../Image/TextureCopyMS.cs | 276 ++++++++++++++++++ Ryujinx.Graphics.OpenGL/Image/TextureView.cs | 67 +---- Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs | 3 + Ryujinx.Graphics.OpenGL/Pipeline.cs | 36 +++ 6 files changed, 329 insertions(+), 156 deletions(-) delete mode 100644 Ryujinx.Graphics.OpenGL/Image/IntermmediatePool.cs create mode 100644 Ryujinx.Graphics.OpenGL/Image/TextureCopyMS.cs diff --git a/Ryujinx.Graphics.OpenGL/Image/IntermmediatePool.cs b/Ryujinx.Graphics.OpenGL/Image/IntermmediatePool.cs deleted file mode 100644 index 218a245e1..000000000 --- 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 _entries; - - public IntermmediatePool(OpenGLRenderer renderer) - { - _renderer = renderer; - _entries = new List(); - } - - 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 d79154452..75c4d8708 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 000000000..9963dc661 --- /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 243ca1b3e..f17243d2b 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 0b9acf10b..418976e66 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 d7cd6fbdd..5f2246253 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[] _renderScale = new Vector4[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 { X = 1f, Y = 0f, Z = 0f, W = 0f }; new Span>(_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)