diff options
Diffstat (limited to 'src/Ryujinx.Graphics.OpenGL/Image/TextureCopyMS.cs')
-rw-r--r-- | src/Ryujinx.Graphics.OpenGL/Image/TextureCopyMS.cs | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureCopyMS.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureCopyMS.cs new file mode 100644 index 00000000..9963dc66 --- /dev/null +++ b/src/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; + } + } + } + } +} |