diff options
| author | gdkchan <gab.dark.100@gmail.com> | 2018-04-08 16:17:35 -0300 |
|---|---|---|
| committer | gdkchan <gab.dark.100@gmail.com> | 2018-04-08 16:41:38 -0300 |
| commit | b9aa3966c00b4bb3ff0292dc28ed53ad26cf284b (patch) | |
| tree | cd2ab3d65c61ac6c6ceb312116e5d138868a3e18 /Ryujinx.Graphics/Gal | |
| parent | 7acd0e01226d64d05b2675f6ae07507039a31835 (diff) | |
Merge shader branch, adding support for GLSL decompilation, a macro
interpreter, and a rewrite of the GPU code.
Diffstat (limited to 'Ryujinx.Graphics/Gal')
52 files changed, 4236 insertions, 206 deletions
diff --git a/Ryujinx.Graphics/Gal/GalBlendEquation.cs b/Ryujinx.Graphics/Gal/GalBlendEquation.cs new file mode 100644 index 00000000..d9f8e799 --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalBlendEquation.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalBlendEquation + { + FuncAdd = 0x8006, + Min = 0x8007, + Max = 0x8008, + FuncSubtract = 0x800a, + FuncReverseSubtract = 0x800b + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalBlendFactor.cs b/Ryujinx.Graphics/Gal/GalBlendFactor.cs new file mode 100644 index 00000000..de7d45fd --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalBlendFactor.cs @@ -0,0 +1,25 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalBlendFactor + { + Zero = 0x4000, + One = 0x4001, + SrcColor = 0x4300, + OneMinusSrcColor = 0x4301, + SrcAlpha = 0x4302, + OneMinusSrcAlpha = 0x4303, + DstAlpha = 0x4304, + OneMinusDstAlpha = 0x4305, + DstColor = 0x4306, + OneMinusDstColor = 0x4307, + SrcAlphaSaturate = 0x4308, + ConstantColor = 0xc001, + OneMinusConstantColor = 0xc002, + ConstantAlpha = 0xc003, + OneMinusConstantAlpha = 0xc004, + Src1Color = 0xc900, + OneMinusSrc1Color = 0xc901, + Src1Alpha = 0xc902, + OneMinusSrc1Alpha = 0xc903 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalClearBufferFlags.cs b/Ryujinx.Graphics/Gal/GalClearBufferFlags.cs new file mode 100644 index 00000000..8565051c --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalClearBufferFlags.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.Graphics.Gal +{ + [Flags] + public enum GalClearBufferFlags + { + Depth = 1 << 0, + Stencil = 1 << 1, + ColorRed = 1 << 2, + ColorGreen = 1 << 3, + ColorBlue = 1 << 4, + ColorAlpha = 1 << 5 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalColorF.cs b/Ryujinx.Graphics/Gal/GalColorF.cs new file mode 100644 index 00000000..7cfb171d --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalColorF.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.Gal +{ + public struct GalColorF + { + public float Red { get; private set; } + public float Green { get; private set; } + public float Blue { get; private set; } + public float Alpha { get; private set; } + + public GalColorF( + float Red, + float Green, + float Blue, + float Alpha) + { + this.Red = Red; + this.Green = Green; + this.Blue = Blue; + this.Alpha = Alpha; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalIndexFormat.cs b/Ryujinx.Graphics/Gal/GalIndexFormat.cs new file mode 100644 index 00000000..71a50cdb --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalIndexFormat.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalIndexFormat + { + Byte = 0, + Int16 = 1, + Int32 = 2 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalShaderType.cs b/Ryujinx.Graphics/Gal/GalShaderType.cs new file mode 100644 index 00000000..eb5aaf88 --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalShaderType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalShaderType + { + Vertex = 0, + TessControl = 1, + TessEvaluation = 2, + Geometry = 3, + Fragment = 4 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTexture.cs b/Ryujinx.Graphics/Gal/GalTexture.cs new file mode 100644 index 00000000..fcf1f1ad --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalTexture.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.Gal +{ + public struct GalTexture + { + public byte[] Data; + + public int Width; + public int Height; + + public GalTextureFormat Format; + + public GalTexture(byte[] Data, int Width, int Height, GalTextureFormat Format) + { + this.Data = Data; + this.Width = Width; + this.Height = Height; + this.Format = Format; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureFilter.cs b/Ryujinx.Graphics/Gal/GalTextureFilter.cs new file mode 100644 index 00000000..8e9669f0 --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalTextureFilter.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalTextureFilter + { + Nearest = 1, + Linear = 2 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureFormat.cs b/Ryujinx.Graphics/Gal/GalTextureFormat.cs new file mode 100644 index 00000000..cf948526 --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalTextureFormat.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalTextureFormat + { + A8B8G8R8 = 0x8, + BC1 = 0x24, + BC2 = 0x25, + BC3 = 0x26 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureMipFilter.cs b/Ryujinx.Graphics/Gal/GalTextureMipFilter.cs new file mode 100644 index 00000000..2123ec7d --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalTextureMipFilter.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalTextureMipFilter + { + None = 1, + Nearest = 2, + Linear = 3 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureSampler.cs b/Ryujinx.Graphics/Gal/GalTextureSampler.cs new file mode 100644 index 00000000..b9e5c765 --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalTextureSampler.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.Graphics.Gal +{ + public struct GalTextureSampler + { + public GalTextureWrap AddressU { get; private set; } + public GalTextureWrap AddressV { get; private set; } + public GalTextureWrap AddressP { get; private set; } + + public GalTextureFilter MinFilter { get; private set; } + public GalTextureFilter MagFilter { get; private set; } + public GalTextureMipFilter MipFilter { get; private set; } + + public GalColorF BorderColor { get; private set; } + + public GalTextureSampler( + GalTextureWrap AddressU, + GalTextureWrap AddressV, + GalTextureWrap AddressP, + GalTextureFilter MinFilter, + GalTextureFilter MagFilter, + GalTextureMipFilter MipFilter, + GalColorF BorderColor) + { + this.AddressU = AddressU; + this.AddressV = AddressV; + this.AddressP = AddressP; + this.MinFilter = MinFilter; + this.MagFilter = MagFilter; + this.MipFilter = MipFilter; + this.BorderColor = BorderColor; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalTextureWrap.cs b/Ryujinx.Graphics/Gal/GalTextureWrap.cs new file mode 100644 index 00000000..66e53154 --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalTextureWrap.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Gal +{ + public enum GalTextureWrap + { + Repeat = 0, + MirroredRepeat = 1, + ClampToEdge = 2, + ClampToBorder = 3, + Clamp = 4, + MirrorClampToEdge = 5, + MirrorClampToBorder = 6, + MirrorClamp = 7 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalVertexAttrib.cs b/Ryujinx.Graphics/Gal/GalVertexAttrib.cs index dc38c593..563e624d 100644 --- a/Ryujinx.Graphics/Gal/GalVertexAttrib.cs +++ b/Ryujinx.Graphics/Gal/GalVertexAttrib.cs @@ -2,8 +2,6 @@ namespace Ryujinx.Graphics.Gal { public struct GalVertexAttrib { - public int Index { get; private set; } - public int Buffer { get; private set; } public bool IsConst { get; private set; } public int Offset { get; private set; } @@ -13,16 +11,12 @@ namespace Ryujinx.Graphics.Gal public bool IsBgra { get; private set; } public GalVertexAttrib( - int Index, - int Buffer, bool IsConst, int Offset, GalVertexAttribSize Size, GalVertexAttribType Type, bool IsBgra) { - this.Index = Index; - this.Buffer = Buffer; this.IsConst = IsConst; this.Offset = Offset; this.Size = Size; diff --git a/Ryujinx.Graphics/Gal/IGalRenderer.cs b/Ryujinx.Graphics/Gal/IGalRenderer.cs index aa4eac4e..99534672 100644 --- a/Ryujinx.Graphics/Gal/IGalRenderer.cs +++ b/Ryujinx.Graphics/Gal/IGalRenderer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace Ryujinx.Graphics.Gal { @@ -21,10 +22,56 @@ namespace Ryujinx.Graphics.Gal float OffsY, float Rotate); - void SendVertexBuffer(int Index, byte[] Buffer, int Stride, GalVertexAttrib[] Attribs); + //Blend + void SetBlendEnable(bool Enable); - void SendR8G8B8A8Texture(int Index, byte[] Buffer, int Width, int Height); + void SetBlend( + GalBlendEquation Equation, + GalBlendFactor FuncSrc, + GalBlendFactor FuncDst); - void BindTexture(int Index); + void SetBlendSeparate( + GalBlendEquation EquationRgb, + GalBlendEquation EquationAlpha, + GalBlendFactor FuncSrcRgb, + GalBlendFactor FuncDstRgb, + GalBlendFactor FuncSrcAlpha, + GalBlendFactor FuncDstAlpha); + + //Frame Buffer + void SetFb(int FbIndex, int Width, int Height); + + void BindFrameBuffer(int FbIndex); + + void DrawFrameBuffer(int FbIndex); + + //Rasterizer + void ClearBuffers(int RtIndex, GalClearBufferFlags Flags); + + void SetVertexArray(int VbIndex, int Stride, byte[] Buffer, GalVertexAttrib[] Attribs); + + void SetIndexArray(byte[] Buffer, GalIndexFormat Format); + + void DrawArrays(int VbIndex, GalPrimitiveType PrimType); + + void DrawElements(int VbIndex, int First, GalPrimitiveType PrimType); + + //Shader + void CreateShader(long Tag, GalShaderType Type, byte[] Data); + + IEnumerable<ShaderDeclInfo> GetTextureUsage(long Tag); + + void SetConstBuffer(long Tag, int Cbuf, byte[] Data); + + void SetUniform1(string UniformName, int Value); + + void BindShader(long Tag); + + void BindProgram(); + + //Texture + void SetTexture(int Index, GalTexture Tex); + + void SetSampler(int Index, GalTextureSampler Sampler); } }
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/FrameBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/FrameBuffer.cs index b761811f..7e7725d6 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/FrameBuffer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/FrameBuffer.cs @@ -219,7 +219,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL PixelFormat.Rgba, PixelType.UnsignedByte, Pixels); - + GL.ActiveTexture(TextureUnit.Texture0); GL.BindVertexArray(VaoHandle); diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLBlend.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLBlend.cs new file mode 100644 index 00000000..97ff8e13 --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLBlend.cs @@ -0,0 +1,49 @@ +using OpenTK.Graphics.OpenGL; + +namespace Ryujinx.Graphics.Gal.OpenGL +{ + class OGLBlend + { + public void Enable() + { + GL.Enable(EnableCap.Blend); + } + + public void Disable() + { + GL.Disable(EnableCap.Blend); + } + + public void Set( + GalBlendEquation Equation, + GalBlendFactor FuncSrc, + GalBlendFactor FuncDst) + { + GL.BlendEquation( + OGLEnumConverter.GetBlendEquation(Equation)); + + GL.BlendFunc( + OGLEnumConverter.GetBlendFactorSrc(FuncSrc), + OGLEnumConverter.GetBlendFactorDst(FuncDst)); + } + + public void SetSeparate( + GalBlendEquation EquationRgb, + GalBlendEquation EquationAlpha, + GalBlendFactor FuncSrcRgb, + GalBlendFactor FuncDstRgb, + GalBlendFactor FuncSrcAlpha, + GalBlendFactor FuncDstAlpha) + { + GL.BlendEquationSeparate( + OGLEnumConverter.GetBlendEquation(EquationRgb), + OGLEnumConverter.GetBlendEquation(EquationAlpha)); + + GL.BlendFuncSeparate( + OGLEnumConverter.GetBlendFactorSrc(FuncSrcRgb), + OGLEnumConverter.GetBlendFactorDst(FuncDstRgb), + OGLEnumConverter.GetBlendFactorSrc(FuncSrcAlpha), + OGLEnumConverter.GetBlendFactorDst(FuncDstAlpha)); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs new file mode 100644 index 00000000..6518de5f --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs @@ -0,0 +1,129 @@ +using OpenTK.Graphics.OpenGL; +using System; + +namespace Ryujinx.Graphics.Gal.OpenGL +{ + static class OGLEnumConverter + { + public static DrawElementsType GetDrawElementsType(GalIndexFormat Format) + { + switch (Format) + { + case GalIndexFormat.Byte: return DrawElementsType.UnsignedByte; + case GalIndexFormat.Int16: return DrawElementsType.UnsignedShort; + case GalIndexFormat.Int32: return DrawElementsType.UnsignedInt; + } + + throw new ArgumentException(nameof(Format)); + } + + public static PrimitiveType GetPrimitiveType(GalPrimitiveType Type) + { + switch (Type) + { + case GalPrimitiveType.Points: return PrimitiveType.Points; + case GalPrimitiveType.Lines: return PrimitiveType.Lines; + case GalPrimitiveType.LineLoop: return PrimitiveType.LineLoop; + case GalPrimitiveType.LineStrip: return PrimitiveType.LineStrip; + case GalPrimitiveType.Triangles: return PrimitiveType.Triangles; + case GalPrimitiveType.TriangleStrip: return PrimitiveType.TriangleStrip; + case GalPrimitiveType.TriangleFan: return PrimitiveType.TriangleFan; + case GalPrimitiveType.Quads: return PrimitiveType.Quads; + case GalPrimitiveType.QuadStrip: return PrimitiveType.QuadStrip; + case GalPrimitiveType.Polygon: return PrimitiveType.Polygon; + case GalPrimitiveType.LinesAdjacency: return PrimitiveType.LinesAdjacency; + case GalPrimitiveType.LineStripAdjacency: return PrimitiveType.LineStripAdjacency; + case GalPrimitiveType.TrianglesAdjacency: return PrimitiveType.TrianglesAdjacency; + case GalPrimitiveType.TriangleStripAdjacency: return PrimitiveType.TriangleStripAdjacency; + case GalPrimitiveType.Patches: return PrimitiveType.Patches; + } + + throw new ArgumentException(nameof(Type)); + } + + public static ShaderType GetShaderType(GalShaderType Type) + { + switch (Type) + { + case GalShaderType.Vertex: return ShaderType.VertexShader; + case GalShaderType.TessControl: return ShaderType.TessControlShader; + case GalShaderType.TessEvaluation: return ShaderType.TessEvaluationShader; + case GalShaderType.Geometry: return ShaderType.GeometryShader; + case GalShaderType.Fragment: return ShaderType.FragmentShader; + } + + throw new ArgumentException(nameof(Type)); + } + + public static PixelInternalFormat GetCompressedTextureFormat(GalTextureFormat Format) + { + switch (Format) + { + case GalTextureFormat.BC1: return PixelInternalFormat.CompressedRgbaS3tcDxt1Ext; + case GalTextureFormat.BC2: return PixelInternalFormat.CompressedRgbaS3tcDxt3Ext; + case GalTextureFormat.BC3: return PixelInternalFormat.CompressedRgbaS3tcDxt5Ext; + } + + throw new NotImplementedException(Format.ToString()); + } + + public static TextureWrapMode GetTextureWrapMode(GalTextureWrap Wrap) + { + switch (Wrap) + { + case GalTextureWrap.Repeat: return TextureWrapMode.Repeat; + case GalTextureWrap.MirroredRepeat: return TextureWrapMode.MirroredRepeat; + case GalTextureWrap.ClampToEdge: return TextureWrapMode.ClampToEdge; + case GalTextureWrap.ClampToBorder: return TextureWrapMode.ClampToBorder; + case GalTextureWrap.Clamp: return TextureWrapMode.Clamp; + + //TODO: Those needs extensions (and are currently wrong). + case GalTextureWrap.MirrorClampToEdge: return TextureWrapMode.ClampToEdge; + case GalTextureWrap.MirrorClampToBorder: return TextureWrapMode.ClampToBorder; + case GalTextureWrap.MirrorClamp: return TextureWrapMode.Clamp; + } + + throw new ArgumentException(nameof(Wrap)); + } + + public static TextureMinFilter GetTextureMinFilter( + GalTextureFilter MinFilter, + GalTextureMipFilter MipFilter) + { + //TODO: Mip (needs mipmap support first). + switch (MinFilter) + { + case GalTextureFilter.Nearest: return TextureMinFilter.Nearest; + case GalTextureFilter.Linear: return TextureMinFilter.Linear; + } + + throw new ArgumentException(nameof(MinFilter)); + } + + public static TextureMagFilter GetTextureMagFilter(GalTextureFilter Filter) + { + switch (Filter) + { + case GalTextureFilter.Nearest: return TextureMagFilter.Nearest; + case GalTextureFilter.Linear: return TextureMagFilter.Linear; + } + + throw new ArgumentException(nameof(Filter)); + } + + public static BlendEquationMode GetBlendEquation(GalBlendEquation BlendEquation) + { + return (BlendEquationMode)BlendEquation; + } + + public static BlendingFactorSrc GetBlendFactorSrc(GalBlendFactor BlendFactor) + { + return (BlendingFactorSrc)(BlendFactor - 0x4000); + } + + public static BlendingFactorDest GetBlendFactorDst(GalBlendFactor BlendFactor) + { + return (BlendingFactorDest)(BlendFactor - 0x4000); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs new file mode 100644 index 00000000..f9c42ae0 --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs @@ -0,0 +1,182 @@ +using OpenTK; +using OpenTK.Graphics.OpenGL; +using System; + +namespace Ryujinx.Graphics.Gal.OpenGL +{ + class OGLFrameBuffer + { + private struct FrameBuffer + { + public int FbHandle; + public int RbHandle; + public int TexHandle; + } + + private struct ShaderProgram + { + public int Handle; + public int VpHandle; + public int FpHandle; + } + + private FrameBuffer[] Fbs; + + private ShaderProgram Shader; + + private bool IsInitialized; + + private int VaoHandle; + private int VboHandle; + + public OGLFrameBuffer() + { + Fbs = new FrameBuffer[16]; + + Shader = new ShaderProgram(); + } + + public void Set(int Index, int Width, int Height) + { + if (Fbs[Index].FbHandle != 0) + { + return; + } + + Fbs[Index].FbHandle = GL.GenFramebuffer(); + Fbs[Index].RbHandle = GL.GenRenderbuffer(); + Fbs[Index].TexHandle = GL.GenTexture(); + + GL.BindFramebuffer(FramebufferTarget.Framebuffer, Fbs[Index].FbHandle); + + GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, Fbs[Index].RbHandle); + + GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferStorage.Depth24Stencil8, 1280, 720); + + GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthStencilAttachment, RenderbufferTarget.Renderbuffer, Fbs[Index].RbHandle); + + GL.BindTexture(TextureTarget.Texture2D, Fbs[Index].TexHandle); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, 1280, 720, 0, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero); + + GL.FramebufferTexture(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, Fbs[Index].TexHandle, 0); + + GL.DrawBuffer(DrawBufferMode.ColorAttachment0); + } + + public void Bind(int Index) + { + if (Fbs[Index].FbHandle == 0) + { + return; + } + + GL.BindFramebuffer(FramebufferTarget.Framebuffer, Fbs[Index].FbHandle); + } + + public void Draw(int Index) + { + if (Fbs[Index].FbHandle == 0) + { + return; + } + + EnsureInitialized(); + + GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); + + GL.BindTexture(TextureTarget.Texture2D, Fbs[Index].TexHandle); + + GL.ActiveTexture(TextureUnit.Texture0); + + GL.BindVertexArray(VaoHandle); + + GL.UseProgram(Shader.Handle); + + GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4); + } + + private void EnsureInitialized() + { + if (!IsInitialized) + { + IsInitialized = true; + + SetupShader(); + SetupVertex(); + } + } + + private void SetupShader() + { + Shader.VpHandle = GL.CreateShader(ShaderType.VertexShader); + Shader.FpHandle = GL.CreateShader(ShaderType.FragmentShader); + + string VpSource = EmbeddedResource.GetString("GlFbVtxShader"); + string FpSource = EmbeddedResource.GetString("GlFbFragShader"); + + GL.ShaderSource(Shader.VpHandle, VpSource); + GL.ShaderSource(Shader.FpHandle, FpSource); + GL.CompileShader(Shader.VpHandle); + GL.CompileShader(Shader.FpHandle); + + Shader.Handle = GL.CreateProgram(); + + GL.AttachShader(Shader.Handle, Shader.VpHandle); + GL.AttachShader(Shader.Handle, Shader.FpHandle); + GL.LinkProgram(Shader.Handle); + GL.UseProgram(Shader.Handle); + + Matrix2 Transform = Matrix2.CreateScale(1, -1); + + int TexUniformLocation = GL.GetUniformLocation(Shader.Handle, "tex"); + + GL.Uniform1(TexUniformLocation, 0); + + int WindowSizeUniformLocation = GL.GetUniformLocation(Shader.Handle, "window_size"); + + GL.Uniform2(WindowSizeUniformLocation, new Vector2(1280.0f, 720.0f)); + + int TransformUniformLocation = GL.GetUniformLocation(Shader.Handle, "transform"); + + GL.UniformMatrix2(TransformUniformLocation, false, ref Transform); + } + + private void SetupVertex() + { + VaoHandle = GL.GenVertexArray(); + VboHandle = GL.GenBuffer(); + + float[] Buffer = new float[] + { + -1, 1, 0, 0, + 1, 1, 1, 0, + -1, -1, 0, 1, + 1, -1, 1, 1 + }; + + IntPtr Length = new IntPtr(Buffer.Length * 4); + + GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); + GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); + GL.BindBuffer(BufferTarget.ArrayBuffer, 0); + + GL.BindVertexArray(VaoHandle); + + GL.EnableVertexAttribArray(0); + + GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); + + GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 16, 0); + + GL.EnableVertexAttribArray(1); + + GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); + + GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 16, 8); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs new file mode 100644 index 00000000..9e0d4523 --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs @@ -0,0 +1,231 @@ +using OpenTK.Graphics.OpenGL; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gal.OpenGL +{ + class OGLRasterizer + { + private static Dictionary<GalVertexAttribSize, int> AttribElements = + new Dictionary<GalVertexAttribSize, int>() + { + { GalVertexAttribSize._32_32_32_32, 4 }, + { GalVertexAttribSize._32_32_32, 3 }, + { GalVertexAttribSize._16_16_16_16, 4 }, + { GalVertexAttribSize._32_32, 2 }, + { GalVertexAttribSize._16_16_16, 3 }, + { GalVertexAttribSize._8_8_8_8, 4 }, + { GalVertexAttribSize._16_16, 2 }, + { GalVertexAttribSize._32, 1 }, + { GalVertexAttribSize._8_8_8, 3 }, + { GalVertexAttribSize._8_8, 2 }, + { GalVertexAttribSize._16, 1 }, + { GalVertexAttribSize._8, 1 }, + { GalVertexAttribSize._10_10_10_2, 4 }, + { GalVertexAttribSize._11_11_10, 3 } + }; + + private static Dictionary<GalVertexAttribSize, VertexAttribPointerType> AttribTypes = + new Dictionary<GalVertexAttribSize, VertexAttribPointerType>() + { + { GalVertexAttribSize._32_32_32_32, VertexAttribPointerType.Int }, + { GalVertexAttribSize._32_32_32, VertexAttribPointerType.Int }, + { GalVertexAttribSize._16_16_16_16, VertexAttribPointerType.Short }, + { GalVertexAttribSize._32_32, VertexAttribPointerType.Int }, + { GalVertexAttribSize._16_16_16, VertexAttribPointerType.Short }, + { GalVertexAttribSize._8_8_8_8, VertexAttribPointerType.Byte }, + { GalVertexAttribSize._16_16, VertexAttribPointerType.Short }, + { GalVertexAttribSize._32, VertexAttribPointerType.Int }, + { GalVertexAttribSize._8_8_8, VertexAttribPointerType.Byte }, + { GalVertexAttribSize._8_8, VertexAttribPointerType.Byte }, + { GalVertexAttribSize._16, VertexAttribPointerType.Short }, + { GalVertexAttribSize._8, VertexAttribPointerType.Byte }, + { GalVertexAttribSize._10_10_10_2, VertexAttribPointerType.Int }, //? + { GalVertexAttribSize._11_11_10, VertexAttribPointerType.Int } //? + }; + + private struct VbInfo + { + public int VaoHandle; + public int VboHandle; + + public int PrimCount; + } + + private struct IbInfo + { + public int IboHandle; + public int Count; + + public DrawElementsType Type; + } + + private VbInfo[] VertexBuffers; + + private IbInfo IndexBuffer; + + public OGLRasterizer() + { + VertexBuffers = new VbInfo[32]; + + IndexBuffer = new IbInfo(); + } + + public void ClearBuffers(int RtIndex, GalClearBufferFlags Flags) + { + ClearBufferMask Mask = 0; + + //OpenGL doesn't support clearing just a single color channel, + //so we can't just clear all channels... + if (Flags.HasFlag(GalClearBufferFlags.ColorRed) && + Flags.HasFlag(GalClearBufferFlags.ColorGreen) && + Flags.HasFlag(GalClearBufferFlags.ColorBlue) && + Flags.HasFlag(GalClearBufferFlags.ColorAlpha)) + { + Mask = ClearBufferMask.ColorBufferBit; + } + + if (Flags.HasFlag(GalClearBufferFlags.Depth)) + { + Mask |= ClearBufferMask.DepthBufferBit; + } + + if (Flags.HasFlag(GalClearBufferFlags.Stencil)) + { + Mask |= ClearBufferMask.StencilBufferBit; + } + + GL.Clear(Mask); + } + + public void SetVertexArray(int VbIndex, int Stride, byte[] Buffer, GalVertexAttrib[] Attribs) + { + EnsureVbInitialized(VbIndex); + + VertexBuffers[VbIndex].PrimCount = Buffer.Length / Stride; + + VbInfo Vb = VertexBuffers[VbIndex]; + + IntPtr Length = new IntPtr(Buffer.Length); + + GL.BindBuffer(BufferTarget.ArrayBuffer, Vb.VboHandle); + GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); + GL.BindBuffer(BufferTarget.ArrayBuffer, 0); + + GL.BindVertexArray(Vb.VaoHandle); + + for (int Attr = 0; Attr < 16; Attr++) + { + GL.DisableVertexAttribArray(Attr); + } + + for (int Index = 0; Index < Attribs.Length; Index++) + { + GalVertexAttrib Attrib = Attribs[Index]; + + GL.EnableVertexAttribArray(Index); + + GL.BindBuffer(BufferTarget.ArrayBuffer, Vb.VboHandle); + + bool Unsigned = + Attrib.Type == GalVertexAttribType.Unorm || + Attrib.Type == GalVertexAttribType.Uint || + Attrib.Type == GalVertexAttribType.Uscaled; + + bool Normalize = + Attrib.Type == GalVertexAttribType.Snorm || + Attrib.Type == GalVertexAttribType.Unorm; + + VertexAttribPointerType Type = 0; + + if (Attrib.Type == GalVertexAttribType.Float) + { + Type = VertexAttribPointerType.Float; + } + else + { + Type = AttribTypes[Attrib.Size] + (Unsigned ? 1 : 0); + } + + int Size = AttribElements[Attrib.Size]; + int Offset = Attrib.Offset; + + GL.VertexAttribPointer(Index, Size, Type, Normalize, Stride, Offset); + } + + GL.BindVertexArray(0); + } + + public void SetIndexArray(byte[] Buffer, GalIndexFormat Format) + { + EnsureIbInitialized(); + + IndexBuffer.Type = OGLEnumConverter.GetDrawElementsType(Format); + + IndexBuffer.Count = Buffer.Length >> (int)Format; + + IntPtr Length = new IntPtr(Buffer.Length); + + GL.BindBuffer(BufferTarget.ElementArrayBuffer, IndexBuffer.IboHandle); + GL.BufferData(BufferTarget.ElementArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); + GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0); + } + + public void DrawArrays(int VbIndex, GalPrimitiveType PrimType) + { + VbInfo Vb = VertexBuffers[VbIndex]; + + if (Vb.PrimCount == 0) + { + return; + } + + GL.BindVertexArray(Vb.VaoHandle); + + GL.DrawArrays(OGLEnumConverter.GetPrimitiveType(PrimType), 0, Vb.PrimCount); + } + + public void DrawElements(int VbIndex, int First, GalPrimitiveType PrimType) + { + VbInfo Vb = VertexBuffers[VbIndex]; + + if (Vb.PrimCount == 0) + { + return; + } + + PrimitiveType Mode = OGLEnumConverter.GetPrimitiveType(PrimType); + + GL.BindVertexArray(Vb.VaoHandle); + + GL.BindBuffer(BufferTarget.ElementArrayBuffer, IndexBuffer.IboHandle); + + GL.DrawElements(Mode, IndexBuffer.Count, IndexBuffer.Type, First); + } + + private void EnsureVbInitialized(int VbIndex) + { + VbInfo Vb = VertexBuffers[VbIndex]; + + if (Vb.VaoHandle == 0) + { + Vb.VaoHandle = GL.GenVertexArray(); + } + + if (Vb.VboHandle == 0) + { + Vb.VboHandle = GL.GenBuffer(); + } + + VertexBuffers[VbIndex] = Vb; + } + + private void EnsureIbInitialized() + { + if (IndexBuffer.IboHandle == 0) + { + IndexBuffer.IboHandle = GL.GenBuffer(); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs new file mode 100644 index 00000000..6d6ac555 --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs @@ -0,0 +1,253 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.Gal.Shader; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Ryujinx.Graphics.Gal.OpenGL +{ + class OGLShader + { + private class ShaderStage : IDisposable + { + public int Handle { get; private set; } + + public bool IsCompiled { get; private set; } + + public GalShaderType Type { get; private set; } + + public string Code { get; private set; } + + public IEnumerable<ShaderDeclInfo> TextureUsage { get; private set; } + public IEnumerable<ShaderDeclInfo> UniformUsage { get; private set; } + + public ShaderStage( + GalShaderType Type, + string Code, + IEnumerable<ShaderDeclInfo> TextureUsage, + IEnumerable<ShaderDeclInfo> UniformUsage) + { + this.Type = Type; + this.Code = Code; + this.TextureUsage = TextureUsage; + this.UniformUsage = UniformUsage; + } + + public void Compile() + { + if (Handle == 0) + { + Handle = GL.CreateShader(OGLEnumConverter.GetShaderType(Type)); + + CompileAndCheck(Handle, Code); + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing && Handle != 0) + { + GL.DeleteShader(Handle); + + Handle = 0; + } + } + } + + private struct ShaderProgram + { + public ShaderStage Vertex; + public ShaderStage TessControl; + public ShaderStage TessEvaluation; + public ShaderStage Geometry; + public ShaderStage Fragment; + } + + private ShaderProgram Current; + + private ConcurrentDictionary<long, ShaderStage> Stages; + + private Dictionary<ShaderProgram, int> Programs; + + public int CurrentProgramHandle { get; private set; } + + public OGLShader() + { + Stages = new ConcurrentDictionary<long, ShaderStage>(); + + Programs = new Dictionary<ShaderProgram, int>(); + } + + public void Create(long Tag, GalShaderType Type, byte[] Data) + { + Stages.GetOrAdd(Tag, (Key) => ShaderStageFactory(Type, Data)); + } + + private ShaderStage ShaderStageFactory(GalShaderType Type, byte[] Data) + { + GlslProgram Program = GetGlslProgram(Data, Type); + + return new ShaderStage( + Type, + Program.Code, + Program.Textures, + Program.Uniforms); + } + + private GlslProgram GetGlslProgram(byte[] Data, GalShaderType Type) + { + int[] Code = new int[(Data.Length - 0x50) >> 2]; + + using (MemoryStream MS = new MemoryStream(Data)) + { + MS.Seek(0x50, SeekOrigin.Begin); + + BinaryReader Reader = new BinaryReader(MS); + + for (int Index = 0; Index < Code.Length; Index++) + { + Code[Index] = Reader.ReadInt32(); + } + } + + GlslDecompiler Decompiler = new GlslDecompiler(); + + return Decompiler.Decompile(Code, Type); + } + + public IEnumerable<ShaderDeclInfo> GetTextureUsage(long Tag) + { + if (Stages.TryGetValue(Tag, out ShaderStage Stage)) + { + return Stage.TextureUsage; + } + + return Enumerable.Empty<ShaderDeclInfo>(); + } + + public void SetConstBuffer(long Tag, int Cbuf, byte[] Data) + { + BindProgram(); + + if (Stages.TryGetValue(Tag, out ShaderStage Stage)) + { + foreach (ShaderDeclInfo DeclInfo in Stage.UniformUsage.Where(x => x.Cbuf == Cbuf)) + { + float Value = BitConverter.ToSingle(Data, DeclInfo.Index * 4); + + int Location = GL.GetUniformLocation(CurrentProgramHandle, DeclInfo.Name); + + GL.Uniform1(Location, Value); + } + } + } + + public void SetUniform1(string UniformName, int Value) + { + BindProgram(); + + int Location = GL.GetUniformLocation(CurrentProgramHandle, UniformName); + + GL.Uniform1(Location, Value); + } + + public void Bind(long Tag) + { + if (Stages.TryGetValue(Tag, out ShaderStage Stage)) + { + Bind(Stage); + } + } + + private void Bind(ShaderStage Stage) + { + switch (Stage.Type) + { + case GalShaderType.Vertex: Current.Vertex = Stage; break; + case GalShaderType.TessControl: Current.TessControl = Stage; break; + case GalShaderType.TessEvaluation: Current.TessEvaluation = Stage; break; + case GalShaderType.Geometry: Current.Geometry = Stage; break; + case GalShaderType.Fragment: Current.Fragment = Stage; break; + } + } + + public void BindProgram() + { + if (Current.Vertex == null || + Current.Fragment == null) + { + return; + } + + if (!Programs.TryGetValue(Current, out int Handle)) + { + Handle = GL.CreateProgram(); + + AttachIfNotNull(Handle, Current.Vertex); + AttachIfNotNull(Handle, Current.TessControl); + AttachIfNotNull(Handle, Current.TessEvaluation); + AttachIfNotNull(Handle, Current.Geometry); + AttachIfNotNull(Handle, Current.Fragment); + + GL.LinkProgram(Handle); + + CheckProgramLink(Handle); + + Programs.Add(Current, Handle); + } + + GL.UseProgram(Handle); + + CurrentProgramHandle = Handle; + } + + private void AttachIfNotNull(int ProgramHandle, ShaderStage Stage) + { + if (Stage != null) + { + Stage.Compile(); + + GL.AttachShader(ProgramHandle, Stage.Handle); + } + } + + public static void CompileAndCheck(int Handle, string Code) + { + GL.ShaderSource(Handle, Code); + GL.CompileShader(Handle); + + CheckCompilation(Handle); + } + + private static void CheckCompilation(int Handle) + { + int Status = 0; + + GL.GetShader(Handle, ShaderParameter.CompileStatus, out Status); + + if (Status == 0) + { + throw new ShaderException(GL.GetShaderInfoLog(Handle)); + } + } + + private static void CheckProgramLink(int Handle) + { + int Status = 0; + + GL.GetProgram(Handle, GetProgramParameterName.LinkStatus, out Status); + + if (Status == 0) + { + throw new ShaderException(GL.GetProgramInfoLog(Handle)); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs new file mode 100644 index 00000000..559e0eda --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs @@ -0,0 +1,96 @@ +using OpenTK.Graphics.OpenGL; + +namespace Ryujinx.Graphics.Gal.OpenGL +{ + class OGLTexture + { + private int[] Textures; + + public OGLTexture() + { + Textures = new int[80]; + } + + public void Set(int Index, GalTexture Tex) + { + GL.ActiveTexture(TextureUnit.Texture0 + Index); + + int Handle = EnsureTextureInitialized(Index); + + GL.BindTexture(TextureTarget.Texture2D, Handle); + + int W = Tex.Width; + int H = Tex.Height; + + byte[] Data = Tex.Data; + + int Length = Data.Length; + + if (IsCompressedTextureFormat(Tex.Format)) + { + PixelInternalFormat Pif = OGLEnumConverter.GetCompressedTextureFormat(Tex.Format); + + GL.CompressedTexImage2D(TextureTarget.Texture2D, 0, Pif, W, H, 0, Length, Data); + } + else + { + //TODO: Get those from Texture format. + const PixelInternalFormat Pif = PixelInternalFormat.Rgba; + + const PixelFormat Pf = PixelFormat.Rgba; + + const PixelType Pt = PixelType.UnsignedByte; + + GL.TexImage2D(TextureTarget.Texture2D, 0, Pif, W, H, 0, Pf, Pt, Data); + } + } + + public void Set(int Index, GalTextureSampler Sampler) + { + int Handle = EnsureTextureInitialized(Index); + + GL.BindTexture(TextureTarget.Texture2D, Handle); + + int WrapS = (int)OGLEnumConverter.GetTextureWrapMode(Sampler.AddressU); + int WrapT = (int)OGLEnumConverter.GetTextureWrapMode(Sampler.AddressV); + + int MinFilter = (int)OGLEnumConverter.GetTextureMinFilter(Sampler.MinFilter, Sampler.MipFilter); + int MagFilter = (int)OGLEnumConverter.GetTextureMagFilter(Sampler.MagFilter); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, WrapS); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, WrapT); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, MinFilter); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, MagFilter); + + float[] Color = new float[] + { + Sampler.BorderColor.Red, + Sampler.BorderColor.Green, + Sampler.BorderColor.Blue, + Sampler.BorderColor.Alpha + }; + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBorderColor, Color); + } + + private static bool IsCompressedTextureFormat(GalTextureFormat Format) + { + return Format == GalTextureFormat.BC1 || + Format == GalTextureFormat.BC2 || + Format == GalTextureFormat.BC3; + } + + private int EnsureTextureInitialized(int TexIndex) + { + int Handle = Textures[TexIndex]; + + if (Handle == 0) + { + Handle = Textures[TexIndex] = GL.GenTexture(); + } + + return Handle; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs b/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs index 002e54da..0b7bf92a 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs @@ -1,5 +1,4 @@ using OpenTK; -using OpenTK.Graphics.OpenGL; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -8,22 +7,15 @@ namespace Ryujinx.Graphics.Gal.OpenGL { public class OpenGLRenderer : IGalRenderer { - private struct VertexBuffer - { - public int VaoHandle; - public int VboHandle; + private OGLBlend Blend; - public int PrimCount; - } + private OGLFrameBuffer FrameBuffer; - private struct Texture - { - public int Handle; - } + private OGLRasterizer Rasterizer; - private List<VertexBuffer> VertexBuffers; + private OGLShader Shader; - private Texture[] Textures; + private OGLTexture Texture; private ConcurrentQueue<Action> ActionsQueue; @@ -31,9 +23,15 @@ namespace Ryujinx.Graphics.Gal.OpenGL public OpenGLRenderer() { - VertexBuffers = new List<VertexBuffer>(); + Blend = new OGLBlend(); + + FrameBuffer = new OGLFrameBuffer(); + + Rasterizer = new OGLRasterizer(); + + Shader = new OGLShader(); - Textures = new Texture[8]; + Texture = new OGLTexture(); ActionsQueue = new ConcurrentQueue<Action>(); } @@ -66,18 +64,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL public void Render() { FbRenderer.Render(); - - for (int Index = 0; Index < VertexBuffers.Count; Index++) - { - VertexBuffer Vb = VertexBuffers[Index]; - - if (Vb.VaoHandle != 0 && - Vb.PrimCount != 0) - { - GL.BindVertexArray(Vb.VaoHandle); - GL.DrawArrays(PrimitiveType.TriangleStrip, 0, Vb.PrimCount); - } - } } public void SetWindowSize(int Width, int Height) @@ -106,218 +92,161 @@ namespace Ryujinx.Graphics.Gal.OpenGL FbRenderer.Set(Fb, Width, Height, Transform, Offs); } - public void SendVertexBuffer(int Index, byte[] Buffer, int Stride, GalVertexAttrib[] Attribs) + public void SetBlendEnable(bool Enable) { - if (Index < 0) + if (Enable) { - throw new ArgumentOutOfRangeException(nameof(Index)); + ActionsQueue.Enqueue(() => Blend.Enable()); } - - if (Buffer.Length == 0 || Stride == 0) + else { - return; + ActionsQueue.Enqueue(() => Blend.Disable()); } + } - EnsureVbInitialized(Index); - - VertexBuffer Vb = VertexBuffers[Index]; + public void SetBlend( + GalBlendEquation Equation, + GalBlendFactor FuncSrc, + GalBlendFactor FuncDst) + { + ActionsQueue.Enqueue(() => Blend.Set(Equation, FuncSrc, FuncDst)); + } - Vb.PrimCount = Buffer.Length / Stride; + public void SetBlendSeparate( + GalBlendEquation EquationRgb, + GalBlendEquation EquationAlpha, + GalBlendFactor FuncSrcRgb, + GalBlendFactor FuncDstRgb, + GalBlendFactor FuncSrcAlpha, + GalBlendFactor FuncDstAlpha) + { + ActionsQueue.Enqueue(() => + { + Blend.SetSeparate( + EquationRgb, + EquationAlpha, + FuncSrcRgb, + FuncDstRgb, + FuncSrcAlpha, + FuncDstAlpha); + }); + } - VertexBuffers[Index] = Vb; + public void SetFb(int FbIndex, int Width, int Height) + { + ActionsQueue.Enqueue(() => FrameBuffer.Set(FbIndex, Width, Height)); + } - IntPtr Length = new IntPtr(Buffer.Length); + public void BindFrameBuffer(int FbIndex) + { + ActionsQueue.Enqueue(() => FrameBuffer.Bind(FbIndex)); + } - GL.BindBuffer(BufferTarget.ArrayBuffer, Vb.VboHandle); - GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); - GL.BindBuffer(BufferTarget.ArrayBuffer, 0); + public void DrawFrameBuffer(int FbIndex) + { + ActionsQueue.Enqueue(() => FrameBuffer.Draw(FbIndex)); + } - GL.BindVertexArray(Vb.VaoHandle); + public void ClearBuffers(int RtIndex, GalClearBufferFlags Flags) + { + ActionsQueue.Enqueue(() => Rasterizer.ClearBuffers(RtIndex, Flags)); + } - for (int Attr = 0; Attr < 16; Attr++) + public void SetVertexArray(int VbIndex, int Stride, byte[] Buffer, GalVertexAttrib[] Attribs) + { + if ((uint)VbIndex > 31) { - GL.DisableVertexAttribArray(Attr); + throw new ArgumentOutOfRangeException(nameof(VbIndex)); } - foreach (GalVertexAttrib Attrib in Attribs) + ActionsQueue.Enqueue(() => Rasterizer.SetVertexArray(VbIndex, Stride, + Buffer ?? throw new ArgumentNullException(nameof(Buffer)), + Attribs ?? throw new ArgumentNullException(nameof(Attribs)))); + } + + public void SetIndexArray(byte[] Buffer, GalIndexFormat Format) + { + if (Buffer == null) { - if (Attrib.Index >= 3) break; - - GL.EnableVertexAttribArray(Attrib.Index); - - GL.BindBuffer(BufferTarget.ArrayBuffer, Vb.VboHandle); - - int Size = 0; - - switch (Attrib.Size) - { - case GalVertexAttribSize._8: - case GalVertexAttribSize._16: - case GalVertexAttribSize._32: - Size = 1; - break; - case GalVertexAttribSize._8_8: - case GalVertexAttribSize._16_16: - case GalVertexAttribSize._32_32: - Size = 2; - break; - case GalVertexAttribSize._8_8_8: - case GalVertexAttribSize._11_11_10: - case GalVertexAttribSize._16_16_16: - case GalVertexAttribSize._32_32_32: - Size = 3; - break; - case GalVertexAttribSize._8_8_8_8: - case GalVertexAttribSize._10_10_10_2: - case GalVertexAttribSize._16_16_16_16: - case GalVertexAttribSize._32_32_32_32: - Size = 4; - break; - } - - bool Signed = - Attrib.Type == GalVertexAttribType.Snorm || - Attrib.Type == GalVertexAttribType.Sint || - Attrib.Type == GalVertexAttribType.Sscaled; - - bool Normalize = - Attrib.Type == GalVertexAttribType.Snorm || - Attrib.Type == GalVertexAttribType.Unorm; - - VertexAttribPointerType Type = 0; - - switch (Attrib.Type) - { - case GalVertexAttribType.Snorm: - case GalVertexAttribType.Unorm: - case GalVertexAttribType.Sint: - case GalVertexAttribType.Uint: - case GalVertexAttribType.Uscaled: - case GalVertexAttribType.Sscaled: - { - switch (Attrib.Size) - { - case GalVertexAttribSize._8: - case GalVertexAttribSize._8_8: - case GalVertexAttribSize._8_8_8: - case GalVertexAttribSize._8_8_8_8: - { - Type = Signed - ? VertexAttribPointerType.Byte - : VertexAttribPointerType.UnsignedByte; - - break; - } - - case GalVertexAttribSize._16: - case GalVertexAttribSize._16_16: - case GalVertexAttribSize._16_16_16: - case GalVertexAttribSize._16_16_16_16: - { - Type = Signed - ? VertexAttribPointerType.Short - : VertexAttribPointerType.UnsignedShort; - - break; - } - - case GalVertexAttribSize._10_10_10_2: - case GalVertexAttribSize._11_11_10: - case GalVertexAttribSize._32: - case GalVertexAttribSize._32_32: - case GalVertexAttribSize._32_32_32: - case GalVertexAttribSize._32_32_32_32: - { - Type = Signed - ? VertexAttribPointerType.Int - : VertexAttribPointerType.UnsignedInt; - - break; - } - } - - break; - } - - case GalVertexAttribType.Float: - { - Type = VertexAttribPointerType.Float; - - break; - } - } - - GL.VertexAttribPointer( - Attrib.Index, - Size, - Type, - Normalize, - Stride, - Attrib.Offset); + throw new ArgumentNullException(nameof(Buffer)); } - GL.BindVertexArray(0); + ActionsQueue.Enqueue(() => Rasterizer.SetIndexArray(Buffer, Format)); } - public void SendR8G8B8A8Texture(int Index, byte[] Buffer, int Width, int Height) + public void DrawArrays(int VbIndex, GalPrimitiveType PrimType) { - EnsureTexInitialized(Index); - - GL.BindTexture(TextureTarget.Texture2D, Textures[Index].Handle); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); - GL.TexImage2D(TextureTarget.Texture2D, - 0, - PixelInternalFormat.Rgba, - Width, - Height, - 0, - PixelFormat.Rgba, - PixelType.UnsignedByte, - Buffer); + if ((uint)VbIndex > 31) + { + throw new ArgumentOutOfRangeException(nameof(VbIndex)); + } + + ActionsQueue.Enqueue(() => Rasterizer.DrawArrays(VbIndex, PrimType)); } - public void BindTexture(int Index) + public void DrawElements(int VbIndex, int First, GalPrimitiveType PrimType) { - GL.ActiveTexture(TextureUnit.Texture0 + Index); + if ((uint)VbIndex > 31) + { + throw new ArgumentOutOfRangeException(nameof(VbIndex)); + } - GL.BindTexture(TextureTarget.Texture2D, Textures[Index].Handle); + ActionsQueue.Enqueue(() => Rasterizer.DrawElements(VbIndex, First, PrimType)); } - private void EnsureVbInitialized(int VbIndex) + public void CreateShader(long Tag, GalShaderType Type, byte[] Data) { - while (VbIndex >= VertexBuffers.Count) + if (Data == null) { - VertexBuffers.Add(new VertexBuffer()); + throw new ArgumentNullException(nameof(Data)); } - VertexBuffer Vb = VertexBuffers[VbIndex]; + Shader.Create(Tag, Type, Data); + } - if (Vb.VaoHandle == 0) + public void SetConstBuffer(long Tag, int Cbuf, byte[] Data) + { + if (Data == null) { - Vb.VaoHandle = GL.GenVertexArray(); + throw new ArgumentNullException(nameof(Data)); } - if (Vb.VboHandle == 0) + ActionsQueue.Enqueue(() => Shader.SetConstBuffer(Tag, Cbuf, Data)); + } + + public void SetUniform1(string UniformName, int Value) + { + if (UniformName == null) { - Vb.VboHandle = GL.GenBuffer(); + throw new ArgumentNullException(nameof(UniformName)); } - VertexBuffers[VbIndex] = Vb; + ActionsQueue.Enqueue(() => Shader.SetUniform1(UniformName, Value)); } - private void EnsureTexInitialized(int TexIndex) + public IEnumerable<ShaderDeclInfo> GetTextureUsage(long Tag) { - Texture Tex = Textures[TexIndex]; + return Shader.GetTextureUsage(Tag); + } - if (Tex.Handle == 0) - { - Tex.Handle = GL.GenTexture(); - } + public void BindShader(long Tag) + { + ActionsQueue.Enqueue(() => Shader.Bind(Tag)); + } - Textures[TexIndex] = Tex; + public void BindProgram() + { + ActionsQueue.Enqueue(() => Shader.BindProgram()); + } + + public void SetTexture(int Index, GalTexture Tex) + { + ActionsQueue.Enqueue(() => Texture.Set(Index, Tex)); + } + + public void SetSampler(int Index, GalTextureSampler Sampler) + { + ActionsQueue.Enqueue(() => Texture.Set(Index, Sampler)); } } }
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs new file mode 100644 index 00000000..898b90b5 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs @@ -0,0 +1,212 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gal.Shader +{ + class GlslDecl + { + public const int VertexIdAttr = 0x2fc; + public const int GlPositionWAttr = 0x7c; + + private const int AttrStartIndex = 8; + private const int TexStartIndex = 8; + + private const string InAttrName = "in_attr"; + private const string OutAttrName = "out_attr"; + private const string UniformName = "c"; + + private const string GprName = "gpr"; + private const string PredName = "pred"; + private const string TextureName = "tex"; + + public const string FragmentOutputName = "FragColor"; + + private string[] StagePrefixes = new string[] { "vp", "tcp", "tep", "gp", "fp" }; + + private string StagePrefix; + + private Dictionary<int, ShaderDeclInfo> m_Textures; + + private Dictionary<(int, int), ShaderDeclInfo> m_Uniforms; + + private Dictionary<int, ShaderDeclInfo> m_InAttributes; + private Dictionary<int, ShaderDeclInfo> m_OutAttributes; + + private Dictionary<int, ShaderDeclInfo> m_Gprs; + private Dictionary<int, ShaderDeclInfo> m_Preds; + + public IReadOnlyDictionary<int, ShaderDeclInfo> Textures => m_Textures; + + public IReadOnlyDictionary<(int, int), ShaderDeclInfo> Uniforms => m_Uniforms; + + public IReadOnlyDictionary<int, ShaderDeclInfo> InAttributes => m_InAttributes; + public IReadOnlyDictionary<int, ShaderDeclInfo> OutAttributes => m_OutAttributes; + + public IReadOnlyDictionary<int, ShaderDeclInfo> Gprs => m_Gprs; + public IReadOnlyDictionary<int, ShaderDeclInfo> Preds => m_Preds; + + public GalShaderType ShaderType { get; private set; } + + public GlslDecl(ShaderIrNode[] Nodes, GalShaderType ShaderType) + { + this.ShaderType = ShaderType; + + StagePrefix = StagePrefixes[(int)ShaderType] + "_"; + + m_Uniforms = new Dictionary<(int, int), ShaderDeclInfo>(); + + m_Textures = new Dictionary<int, ShaderDeclInfo>(); + + m_InAttributes = new Dictionary<int, ShaderDeclInfo>(); + m_OutAttributes = new Dictionary<int, ShaderDeclInfo>(); + + m_Gprs = new Dictionary<int, ShaderDeclInfo>(); + m_Preds = new Dictionary<int, ShaderDeclInfo>(); + + //FIXME: Only valid for vertex shaders. + if (ShaderType == GalShaderType.Fragment) + { + m_Gprs.Add(0, new ShaderDeclInfo(FragmentOutputName, 0, 0, 4)); + } + else + { + m_OutAttributes.Add(7, new ShaderDeclInfo("gl_Position", -1, 0, 4)); + } + + foreach (ShaderIrNode Node in Nodes) + { + Traverse(null, Node); + } + } + + private void Traverse(ShaderIrNode Parent, ShaderIrNode Node) + { + switch (Node) + { + case ShaderIrAsg Asg: + { + Traverse(Asg, Asg.Dst); + Traverse(Asg, Asg.Src); + + break; + } + + case ShaderIrCond Cond: + { + Traverse(Cond, Cond.Pred); + Traverse(Cond, Cond.Child); + + break; + } + + case ShaderIrOp Op: + { + Traverse(Op, Op.OperandA); + Traverse(Op, Op.OperandB); + Traverse(Op, Op.OperandC); + + if (Op.Inst == ShaderIrInst.Texr || + Op.Inst == ShaderIrInst.Texg || + Op.Inst == ShaderIrInst.Texb || + Op.Inst == ShaderIrInst.Texa) + { + int Handle = ((ShaderIrOperImm)Op.OperandC).Value; + + int Index = Handle - TexStartIndex; + + string Name = StagePrefix + TextureName + Index; + + m_Textures.TryAdd(Handle, new ShaderDeclInfo(Name, Handle)); + } + break; + } + + case ShaderIrOperCbuf Cbuf: + { + string Name = StagePrefix + UniformName + Cbuf.Index + "_" + Cbuf.Offs; + + ShaderDeclInfo DeclInfo = new ShaderDeclInfo(Name, Cbuf.Offs, Cbuf.Index); + + m_Uniforms.TryAdd((Cbuf.Index, Cbuf.Offs), DeclInfo); + + break; + } + + case ShaderIrOperAbuf Abuf: + { + //This is a built-in input variable. + if (Abuf.Offs == VertexIdAttr) + { + break; + } + + int Index = Abuf.Offs >> 4; + int Elem = (Abuf.Offs >> 2) & 3; + + int GlslIndex = Index - AttrStartIndex; + + ShaderDeclInfo DeclInfo; + + if (Parent is ShaderIrAsg Asg && Asg.Dst == Node) + { + if (!m_OutAttributes.TryGetValue(Index, out DeclInfo)) + { + DeclInfo = new ShaderDeclInfo(OutAttrName + GlslIndex, GlslIndex); + + m_OutAttributes.Add(Index, DeclInfo); + } + } + else + { + if (!m_InAttributes.TryGetValue(Index, out DeclInfo)) + { + DeclInfo = new ShaderDeclInfo(InAttrName + GlslIndex, GlslIndex); + + m_InAttributes.Add(Index, DeclInfo); + } + } + + DeclInfo.Enlarge(Elem + 1); + + break; + } + + case ShaderIrOperGpr Gpr: + { + if (!Gpr.IsConst && !HasName(m_Gprs, Gpr.Index)) + { + string Name = GprName + Gpr.Index; + + m_Gprs.TryAdd(Gpr.Index, new ShaderDeclInfo(Name, Gpr.Index)); + } + break; + } + + case ShaderIrOperPred Pred: + { + if (!Pred.IsConst && !HasName(m_Preds, Pred.Index)) + { + string Name = PredName + Pred.Index; + + m_Preds.TryAdd(Pred.Index, new ShaderDeclInfo(Name, Pred.Index)); + } + break; + } + } + } + + private bool HasName(Dictionary<int, ShaderDeclInfo> Decls, int Index) + { + int VecIndex = Index >> 2; + + if (Decls.TryGetValue(VecIndex, out ShaderDeclInfo DeclInfo)) + { + if (DeclInfo.Size > 1 && Index < VecIndex + DeclInfo.Size) + { + return true; + } + } + + return Decls.ContainsKey(Index); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs new file mode 100644 index 00000000..eda70cef --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs @@ -0,0 +1,644 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace Ryujinx.Graphics.Gal.Shader +{ + class GlslDecompiler + { + private delegate string GetInstExpr(ShaderIrOp Op); + + private Dictionary<ShaderIrInst, GetInstExpr> InstsExpr; + + private enum OperType + { + Bool, + F32, + I32 + } + + private const string IdentationStr = " "; + + private static string[] ElemTypes = new string[] { "float", "vec2", "vec3", "vec4" }; + + private GlslDecl Decl; + + private StringBuilder SB; + + public GlslDecompiler() + { + InstsExpr = new Dictionary<ShaderIrInst, GetInstExpr>() + { + { ShaderIrInst.And, GetAndExpr }, + { ShaderIrInst.Asr, GetAsrExpr }, + { ShaderIrInst.Band, GetBandExpr }, + { ShaderIrInst.Bnot, GetBnotExpr }, + { ShaderIrInst.Clt, GetCltExpr }, + { ShaderIrInst.Ceq, GetCeqExpr }, + { ShaderIrInst.Cle, GetCleExpr }, + { ShaderIrInst.Cgt, GetCgtExpr }, + { ShaderIrInst.Cne, GetCneExpr }, + { ShaderIrInst.Cge, GetCgeExpr }, + { ShaderIrInst.Exit, GetExitExpr }, + { ShaderIrInst.Fabs, GetFabsExpr }, + { ShaderIrInst.Fadd, GetFaddExpr }, + { ShaderIrInst.Fcos, GetFcosExpr }, + { ShaderIrInst.Fex2, GetFex2Expr }, + { ShaderIrInst.Ffma, GetFfmaExpr }, + { ShaderIrInst.Flg2, GetFlg2Expr }, + { ShaderIrInst.Fmul, GetFmulExpr }, + { ShaderIrInst.Fneg, GetFnegExpr }, + { ShaderIrInst.Frcp, GetFrcpExpr }, + { ShaderIrInst.Frsq, GetFrsqExpr }, + { ShaderIrInst.Fsin, GetFsinExpr }, + { ShaderIrInst.Ipa, GetIpaExpr }, + { ShaderIrInst.Kil, GetKilExpr }, + { ShaderIrInst.Lsr, GetLsrExpr }, + { ShaderIrInst.Not, GetNotExpr }, + { ShaderIrInst.Or, GetOrExpr }, + { ShaderIrInst.Stof, GetStofExpr }, + { ShaderIrInst.Utof, GetUtofExpr }, + { ShaderIrInst.Texr, GetTexrExpr }, + { ShaderIrInst.Texg, GetTexgExpr }, + { ShaderIrInst.Texb, GetTexbExpr }, + { ShaderIrInst.Texa, GetTexaExpr }, + { ShaderIrInst.Xor, GetXorExpr }, + }; + } + + public GlslProgram Decompile(int[] Code, GalShaderType ShaderType) + { + ShaderIrBlock Block = ShaderDecoder.DecodeBasicBlock(Code, 0, ShaderType); + + ShaderIrNode[] Nodes = Block.GetNodes(); + + Decl = new GlslDecl(Nodes, ShaderType); + + SB = new StringBuilder(); + + SB.AppendLine("#version 330 core"); + + PrintDeclTextures(); + PrintDeclUniforms(); + PrintDeclInAttributes(); + PrintDeclOutAttributes(); + PrintDeclGprs(); + PrintDeclPreds(); + + PrintBlockScope("void main()", 1, Nodes); + + string GlslCode = SB.ToString(); + + return new GlslProgram( + GlslCode, + Decl.Textures.Values, + Decl.Uniforms.Values); + } + + private void PrintDeclTextures() + { + PrintDecls(Decl.Textures, "uniform sampler2D"); + } + + private void PrintDeclUniforms() + { + foreach (ShaderDeclInfo DeclInfo in Decl.Uniforms.Values.OrderBy(DeclKeySelector)) + { + SB.AppendLine($"uniform {GetDecl(DeclInfo)};"); + } + + if (Decl.Uniforms.Count > 0) + { + SB.AppendLine(); + } + } + + private void PrintDeclInAttributes() + { + PrintDeclAttributes(Decl.InAttributes.Values, "in"); + } + + private void PrintDeclOutAttributes() + { + PrintDeclAttributes(Decl.OutAttributes.Values, "out"); + } + + private void PrintDeclAttributes(IEnumerable<ShaderDeclInfo> Decls, string InOut) + { + int Count = 0; + + foreach (ShaderDeclInfo DeclInfo in Decls.OrderBy(DeclKeySelector)) + { + if (DeclInfo.Index >= 0) + { + SB.AppendLine($"layout (location = {DeclInfo.Index}) {InOut} {GetDecl(DeclInfo)};"); + + Count++; + } + } + + if (Count > 0) + { + SB.AppendLine(); + } + } + + private void PrintDeclGprs() + { + PrintDecls(Decl.Gprs); + } + + private void PrintDeclPreds() + { + PrintDecls(Decl.Preds, "bool"); + } + + private void PrintDecls(IReadOnlyDictionary<int, ShaderDeclInfo> Dict, string CustomType = null) + { + foreach (ShaderDeclInfo DeclInfo in Dict.Values.OrderBy(DeclKeySelector)) + { + string Name; + + if (CustomType != null) + { + Name = CustomType + " " + DeclInfo.Name + ";"; + } + else if (DeclInfo.Name == GlslDecl.FragmentOutputName) + { + Name = "layout (location = 0) out " + GetDecl(DeclInfo) + ";"; + } + else + { + Name = GetDecl(DeclInfo) + ";"; + } + + SB.AppendLine(Name); + } + + if (Dict.Count > 0) + { + SB.AppendLine(); + } + } + + private int DeclKeySelector(ShaderDeclInfo DeclInfo) + { + return DeclInfo.Cbuf << 24 | DeclInfo.Index; + } + + private string GetDecl(ShaderDeclInfo DeclInfo) + { + return ElemTypes[DeclInfo.Size - 1] + " " + DeclInfo.Name; + } + + private void PrintBlockScope(string ScopeName, int IdentationLevel, params ShaderIrNode[] Nodes) + { + string Identation = string.Empty; + + for (int Index = 0; Index < IdentationLevel - 1; Index++) + { + Identation += IdentationStr; + } + + if (ScopeName != string.Empty) + { + ScopeName += " "; + } + + SB.AppendLine(Identation + ScopeName + "{"); + + string LastLine = Identation + "}"; + + if (IdentationLevel > 0) + { + Identation += IdentationStr; + } + + for (int Index = 0; Index < Nodes.Length; Index++) + { + ShaderIrNode Node = Nodes[Index]; + + if (Node is ShaderIrCond Cond) + { + string SubScopeName = "if (" + GetSrcExpr(Cond.Pred, true) + ")"; + + PrintBlockScope(SubScopeName, IdentationLevel + 1, Cond.Child); + } + else if (Node is ShaderIrAsg Asg && IsValidOutOper(Asg.Dst)) + { + string Expr = GetSrcExpr(Asg.Src, true); + + Expr = GetExprWithCast(Asg.Dst, Asg.Src, Expr); + + SB.AppendLine(Identation + GetDstOperName(Asg.Dst) + " = " + Expr + ";"); + } + else if (Node is ShaderIrOp Op) + { + SB.AppendLine(Identation + GetSrcExpr(Op, true) + ";"); + } + else + { + throw new InvalidOperationException(); + } + } + + SB.AppendLine(LastLine); + } + + private bool IsValidOutOper(ShaderIrNode Node) + { + if (Node is ShaderIrOperGpr Gpr && Gpr.IsConst) + { + return false; + } + else if (Node is ShaderIrOperPred Pred && Pred.IsConst) + { + return false; + } + + return true; + } + + private string GetDstOperName(ShaderIrNode Node) + { + if (Node is ShaderIrOperAbuf Abuf) + { + return GetOutAbufName(Abuf); + } + else if (Node is ShaderIrOperGpr Gpr) + { + return GetName(Gpr); + } + else if (Node is ShaderIrOperPred Pred) + { + return GetName(Pred); + } + + throw new ArgumentException(nameof(Node)); + } + + private string GetSrcExpr(ShaderIrNode Node, bool Entry = false) + { + switch (Node) + { + case ShaderIrOperAbuf Abuf: return GetName (Abuf); + case ShaderIrOperCbuf Cbuf: return GetName (Cbuf); + case ShaderIrOperGpr Gpr: return GetName (Gpr); + case ShaderIrOperImm Imm: return GetValue(Imm); + case ShaderIrOperImmf Immf: return GetValue(Immf); + case ShaderIrOperPred Pred: return GetName (Pred); + + case ShaderIrOp Op: + string Expr; + + if (InstsExpr.TryGetValue(Op.Inst, out GetInstExpr GetExpr)) + { + Expr = GetExpr(Op); + } + else + { + throw new NotImplementedException(Op.Inst.ToString()); + } + + if (!Entry && NeedsParentheses(Op)) + { + Expr = "(" + Expr + ")"; + } + + return Expr; + + default: throw new ArgumentException(nameof(Node)); + } + } + + private static bool NeedsParentheses(ShaderIrOp Op) + { + switch (Op.Inst) + { + case ShaderIrInst.Frcp: + return true; + + case ShaderIrInst.Ipa: + case ShaderIrInst.Texr: + case ShaderIrInst.Texg: + case ShaderIrInst.Texb: + case ShaderIrInst.Texa: + return false; + } + + return Op.OperandB != null || + Op.OperandC != null; + } + + private string GetName(ShaderIrOperCbuf Cbuf) + { + if (!Decl.Uniforms.TryGetValue((Cbuf.Index, Cbuf.Offs), out ShaderDeclInfo DeclInfo)) + { + throw new InvalidOperationException(); + } + + return DeclInfo.Name; + } + + private string GetOutAbufName(ShaderIrOperAbuf Abuf) + { + return GetName(Decl.OutAttributes, Abuf); + } + + private string GetName(ShaderIrOperAbuf Abuf) + { + if (Abuf.Offs == GlslDecl.GlPositionWAttr && Decl.ShaderType == GalShaderType.Fragment) + { + return "(1f / gl_FragCoord.w)"; + } + + if (Abuf.Offs == GlslDecl.VertexIdAttr) + { + return "gl_VertexID"; + } + + return GetName(Decl.InAttributes, Abuf); + } + + private string GetName(IReadOnlyDictionary<int, ShaderDeclInfo> Dict, ShaderIrOperAbuf Abuf) + { + int Index = Abuf.Offs >> 4; + int Elem = (Abuf.Offs >> 2) & 3; + + if (!Dict.TryGetValue(Index, out ShaderDeclInfo DeclInfo)) + { + throw new InvalidOperationException(); + } + + return DeclInfo.Size > 1 ? DeclInfo.Name + "." + GetAttrSwizzle(Elem) : DeclInfo.Name; + } + + private string GetName(ShaderIrOperGpr Gpr) + { + return Gpr.IsConst ? "0" : GetNameWithSwizzle(Decl.Gprs, Gpr.Index); + } + + private string GetValue(ShaderIrOperImm Imm) + { + //Only use hex is the value is too big and would likely be hard to read as int. + if (Imm.Value > 0xfff || + Imm.Value < -0xfff) + { + return "0x" + Imm.Value.ToString("x8", CultureInfo.InvariantCulture); + } + else + { + return Imm.Value.ToString(CultureInfo.InvariantCulture); + } + } + + private string GetValue(ShaderIrOperImmf Immf) + { + return Immf.Value.ToString(CultureInfo.InvariantCulture) + "f"; + } + + private string GetName(ShaderIrOperPred Pred) + { + return Pred.IsConst ? "true" : GetNameWithSwizzle(Decl.Preds, Pred.Index); + } + + private string GetNameWithSwizzle(IReadOnlyDictionary<int, ShaderDeclInfo> Dict, int Index) + { + int VecIndex = Index >> 2; + + if (Dict.TryGetValue(VecIndex, out ShaderDeclInfo DeclInfo)) + { + if (DeclInfo.Size > 1 && Index < VecIndex + DeclInfo.Size) + { + return DeclInfo.Name + "." + GetAttrSwizzle(Index & 3); + } + } + + if (!Dict.TryGetValue(Index, out DeclInfo)) + { + throw new InvalidOperationException(); + } + + return DeclInfo.Name; + } + + private string GetAttrSwizzle(int Elem) + { + return "xyzw".Substring(Elem, 1); + } + + private string GetAndExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "&"); + + private string GetAsrExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">>"); + + private string GetBandExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "&&"); + + private string GetBnotExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "!"); + + private string GetCltExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "<"); + private string GetCeqExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "=="); + private string GetCleExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "<="); + private string GetCgtExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">"); + private string GetCneExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "!="); + private string GetCgeExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">="); + + private string GetExitExpr(ShaderIrOp Op) => "return"; + + private string GetFabsExpr(ShaderIrOp Op) => GetUnaryCall(Op, "abs"); + + private string GetFaddExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "+"); + + private string GetFcosExpr(ShaderIrOp Op) => GetUnaryCall(Op, "cos"); + + private string GetFex2Expr(ShaderIrOp Op) => GetUnaryCall(Op, "exp2"); + + private string GetFfmaExpr(ShaderIrOp Op) => GetTernaryExpr(Op, "*", "+"); + + private string GetFlg2Expr(ShaderIrOp Op) => GetUnaryCall(Op, "log2"); + + private string GetFmulExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "*"); + + private string GetFnegExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "-"); + + private string GetFrcpExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "1f / "); + + private string GetFrsqExpr(ShaderIrOp Op) => GetUnaryCall(Op, "inversesqrt"); + + private string GetFsinExpr(ShaderIrOp Op) => GetUnaryCall(Op, "sin"); + + private string GetIpaExpr(ShaderIrOp Op) => GetSrcExpr(Op.OperandA); + + private string GetKilExpr(ShaderIrOp Op) => "discard"; + + private string GetLsrExpr(ShaderIrOp Op) + { + return "int(uint(" + GetOperExpr(Op, Op.OperandA) + ") >> " + + GetOperExpr(Op, Op.OperandB) + ")"; + } + + private string GetNotExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "~"); + + private string GetOrExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "|"); + + private string GetStofExpr(ShaderIrOp Op) + { + return "float(" + GetOperExpr(Op, Op.OperandA) + ")"; + } + + private string GetUtofExpr(ShaderIrOp Op) + { + return "float(uint(" + GetOperExpr(Op, Op.OperandA) + "))"; + } + + private string GetXorExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "^"); + + private string GetUnaryCall(ShaderIrOp Op, string FuncName) + { + return FuncName + "(" + GetOperExpr(Op, Op.OperandA) + ")"; + } + + private string GetUnaryExpr(ShaderIrOp Op, string Opr) + { + return Opr + GetOperExpr(Op, Op.OperandA); + } + + private string GetBinaryExpr(ShaderIrOp Op, string Opr) + { + return GetOperExpr(Op, Op.OperandA) + " " + Opr + " " + + GetOperExpr(Op, Op.OperandB); + } + + private string GetTernaryExpr(ShaderIrOp Op, string Opr1, string Opr2) + { + return GetOperExpr(Op, Op.OperandA) + " " + Opr1 + " " + + GetOperExpr(Op, Op.OperandB) + " " + Opr2 + " " + + GetOperExpr(Op, Op.OperandC); + } + + private string GetTexrExpr(ShaderIrOp Op) => GetTexExpr(Op, 'r'); + private string GetTexgExpr(ShaderIrOp Op) => GetTexExpr(Op, 'g'); + private string GetTexbExpr(ShaderIrOp Op) => GetTexExpr(Op, 'b'); + private string GetTexaExpr(ShaderIrOp Op) => GetTexExpr(Op, 'a'); + + private string GetTexExpr(ShaderIrOp Op, char Ch) + { + return $"texture({GetTexSamplerName(Op)}, {GetTexSamplerCoords(Op)}).{Ch}"; + } + + private string GetTexSamplerName(ShaderIrOp Op) + { + ShaderIrOperImm Node = (ShaderIrOperImm)Op.OperandC; + + int Handle = ((ShaderIrOperImm)Op.OperandC).Value; + + if (!Decl.Textures.TryGetValue(Handle, out ShaderDeclInfo DeclInfo)) + { + throw new InvalidOperationException(); + } + + return DeclInfo.Name; + } + + private string GetTexSamplerCoords(ShaderIrOp Op) + { + return "vec2(" + GetOperExpr(Op, Op.OperandA) + ", " + + GetOperExpr(Op, Op.OperandB) + ")"; + } + + private string GetOperExpr(ShaderIrOp Op, ShaderIrNode Oper) + { + return GetExprWithCast(Op, Oper, GetSrcExpr(Oper)); + } + + private static string GetExprWithCast(ShaderIrNode Dst, ShaderIrNode Src, string Expr) + { + //Note: The "DstType" (of the cast) is the type that the operation + //uses on the source operands, while the "SrcType" is the destination + //type of the operand result (if it is a operation) or just the type + //of the variable for registers/uniforms/attributes. + OperType DstType = GetSrcNodeType(Dst); + OperType SrcType = GetDstNodeType(Src); + + if (DstType != SrcType) + { + //Check for invalid casts + //(like bool to int/float and others). + if (SrcType != OperType.F32 && + SrcType != OperType.I32) + { + throw new InvalidOperationException(); + } + + //For integer immediates being used as float, + //it's better (for readability) to just return the float value. + if (Src is ShaderIrOperImm Imm && DstType == OperType.F32) + { + float Value = BitConverter.Int32BitsToSingle(Imm.Value); + + return Value.ToString(CultureInfo.InvariantCulture) + "f"; + } + + switch (DstType) + { + case OperType.F32: Expr = "intBitsToFloat(" + Expr + ")"; break; + case OperType.I32: Expr = "floatBitsToInt(" + Expr + ")"; break; + } + } + + return Expr; + } + + private static OperType GetDstNodeType(ShaderIrNode Node) + { + if (Node is ShaderIrOp Op) + { + switch (Op.Inst) + { + case ShaderIrInst.Stof: return OperType.F32; + case ShaderIrInst.Utof: return OperType.F32; + } + } + + return GetSrcNodeType(Node); + } + + private static OperType GetSrcNodeType(ShaderIrNode Node) + { + switch (Node) + { + case ShaderIrOperAbuf Abuf: + return Abuf.Offs == GlslDecl.VertexIdAttr + ? OperType.I32 + : OperType.F32; + + case ShaderIrOperCbuf Cbuf: return OperType.F32; + case ShaderIrOperGpr Gpr: return OperType.F32; + case ShaderIrOperImm Imm: return OperType.I32; + case ShaderIrOperImmf Immf: return OperType.F32; + case ShaderIrOperPred Pred: return OperType.Bool; + + case ShaderIrOp Op: + if (Op.Inst > ShaderIrInst.B_Start && + Op.Inst < ShaderIrInst.B_End) + { + return OperType.Bool; + } + else if (Op.Inst > ShaderIrInst.F_Start && + Op.Inst < ShaderIrInst.F_End) + { + return OperType.F32; + } + else if (Op.Inst > ShaderIrInst.I_Start && + Op.Inst < ShaderIrInst.I_End) + { + return OperType.I32; + } + break; + } + + throw new ArgumentException(nameof(Node)); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs b/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs new file mode 100644 index 00000000..729b6f1d --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gal.Shader +{ + struct GlslProgram + { + public string Code { get; private set; } + + public IEnumerable<ShaderDeclInfo> Textures { get; private set; } + public IEnumerable<ShaderDeclInfo> Uniforms { get; private set; } + + public GlslProgram( + string Code, + IEnumerable<ShaderDeclInfo> Textures, + IEnumerable<ShaderDeclInfo> Uniforms) + { + this.Code = Code; + this.Textures = Textures; + this.Uniforms = Uniforms; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs new file mode 100644 index 00000000..ef0fd78b --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + delegate void ShaderDecodeFunc(ShaderIrBlock Block, long OpCode); +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs new file mode 100644 index 00000000..5c2f493e --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs @@ -0,0 +1,315 @@ +using System; + +using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper; + +namespace Ryujinx.Graphics.Gal.Shader +{ + static partial class ShaderDecode + { + public static void Fadd_C(ShaderIrBlock Block, long OpCode) + { + EmitAluBinaryF(Block, OpCode, ShaderOper.CR, ShaderIrInst.Fadd); + } + + public static void Fadd_I(ShaderIrBlock Block, long OpCode) + { + EmitAluBinaryF(Block, OpCode, ShaderOper.Immf, ShaderIrInst.Fadd); + } + + public static void Fadd_R(ShaderIrBlock Block, long OpCode) + { + EmitAluBinaryF(Block, OpCode, ShaderOper.RR, ShaderIrInst.Fadd); + } + + public static void Ffma_CR(ShaderIrBlock Block, long OpCode) + { + EmitAluFfma(Block, OpCode, ShaderOper.CR); + } + + public static void Ffma_I(ShaderIrBlock Block, long OpCode) + { + EmitAluFfma(Block, OpCode, ShaderOper.Immf); + } + + public static void Ffma_RC(ShaderIrBlock Block, long OpCode) + { + EmitAluFfma(Block, OpCode, ShaderOper.RC); + } + + public static void Ffma_RR(ShaderIrBlock Block, long OpCode) + { + EmitAluFfma(Block, OpCode, ShaderOper.RR); + } + + public static void Fmul_C(ShaderIrBlock Block, long OpCode) + { + EmitAluBinaryF(Block, OpCode, ShaderOper.CR, ShaderIrInst.Fmul); + } + + public static void Fmul_I(ShaderIrBlock Block, long OpCode) + { + EmitAluBinaryF(Block, OpCode, ShaderOper.Immf, ShaderIrInst.Fmul); + } + + public static void Fmul_R(ShaderIrBlock Block, long OpCode) + { + EmitAluBinaryF(Block, OpCode, ShaderOper.RR, ShaderIrInst.Fmul); + } + + public static void Fsetp_C(ShaderIrBlock Block, long OpCode) + { + EmitFsetp(Block, OpCode, ShaderOper.CR); + } + + public static void Fsetp_I(ShaderIrBlock Block, long OpCode) + { + EmitFsetp(Block, OpCode, ShaderOper.Immf); + } + + public static void Fsetp_R(ShaderIrBlock Block, long OpCode) + { + EmitFsetp(Block, OpCode, ShaderOper.RR); + } + + public static void Ipa(ShaderIrBlock Block, long OpCode) + { + ShaderIrNode OperA = GetOperAbuf28(OpCode); + ShaderIrNode OperB = GetOperGpr20 (OpCode); + + ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Ipa, OperA, OperB); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); + } + + public static void Lop32i(ShaderIrBlock Block, long OpCode) + { + int SubOp = (int)(OpCode >> 53) & 3; + + bool Ia = ((OpCode >> 55) & 1) != 0; + bool Ib = ((OpCode >> 56) & 1) != 0; + + ShaderIrInst Inst = 0; + + switch (SubOp) + { + case 0: Inst = ShaderIrInst.And; break; + case 1: Inst = ShaderIrInst.Or; break; + case 2: Inst = ShaderIrInst.Xor; break; + } + + ShaderIrNode OperA = GetAluNot(GetOperGpr8(OpCode), Ia); + + //SubOp == 3 is pass, used by the not instruction + //which just moves the inverted register value. + if (SubOp < 3) + { + ShaderIrNode OperB = GetAluNot(GetOperImm32_20(OpCode), Ib); + + ShaderIrOp Op = new ShaderIrOp(Inst, OperA, OperB); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); + } + else + { + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), OperA), OpCode)); + } + } + + public static void Mufu(ShaderIrBlock Block, long OpCode) + { + int SubOp = (int)(OpCode >> 20) & 7; + + bool Aa = ((OpCode >> 46) & 1) != 0; + bool Na = ((OpCode >> 48) & 1) != 0; + + ShaderIrInst Inst = 0; + + switch (SubOp) + { + case 0: Inst = ShaderIrInst.Fcos; break; + case 1: Inst = ShaderIrInst.Fsin; break; + case 2: Inst = ShaderIrInst.Fex2; break; + case 3: Inst = ShaderIrInst.Flg2; break; + case 4: Inst = ShaderIrInst.Frcp; break; + case 5: Inst = ShaderIrInst.Frsq; break; + + default: throw new NotImplementedException(SubOp.ToString()); + } + + ShaderIrNode OperA = GetOperGpr8(OpCode); + + ShaderIrOp Op = new ShaderIrOp(Inst, GetAluAbsNeg(OperA, Aa, Na)); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); + } + + public static void Shr_C(ShaderIrBlock Block, long OpCode) + { + EmitAluBinary(Block, OpCode, ShaderOper.CR, GetShrInst(OpCode)); + } + + public static void Shr_I(ShaderIrBlock Block, long OpCode) + { + EmitAluBinary(Block, OpCode, ShaderOper.Imm, GetShrInst(OpCode)); + } + + public static void Shr_R(ShaderIrBlock Block, long OpCode) + { + EmitAluBinary(Block, OpCode, ShaderOper.RR, GetShrInst(OpCode)); + } + + private static ShaderIrInst GetShrInst(long OpCode) + { + bool Signed = ((OpCode >> 48) & 1) != 0; + + return Signed ? ShaderIrInst.Asr : ShaderIrInst.Lsr; + } + + private static void EmitAluBinary( + ShaderIrBlock Block, + long OpCode, + ShaderOper Oper, + ShaderIrInst Inst) + { + ShaderIrNode OperA = GetOperGpr8(OpCode), OperB; + + switch (Oper) + { + case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break; + case ShaderOper.Imm: OperB = GetOperImm19_20(OpCode); break; + case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break; + + default: throw new ArgumentException(nameof(Oper)); + } + + ShaderIrNode Op = new ShaderIrOp(Inst, OperA, OperB); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); + } + + private static void EmitAluBinaryF( + ShaderIrBlock Block, + long OpCode, + ShaderOper Oper, + ShaderIrInst Inst) + { + bool Nb = ((OpCode >> 45) & 1) != 0; + bool Aa = ((OpCode >> 46) & 1) != 0; + bool Na = ((OpCode >> 48) & 1) != 0; + bool Ab = ((OpCode >> 49) & 1) != 0; + bool Ad = ((OpCode >> 50) & 1) != 0; + + ShaderIrNode OperA = GetOperGpr8(OpCode), OperB; + + if (Inst == ShaderIrInst.Fadd) + { + OperA = GetAluAbsNeg(OperA, Aa, Na); + } + + switch (Oper) + { + case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break; + case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break; + case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break; + + default: throw new ArgumentException(nameof(Oper)); + } + + OperB = GetAluAbsNeg(OperB, Ab, Nb); + + ShaderIrNode Op = new ShaderIrOp(Inst, OperA, OperB); + + Op = GetAluAbs(Op, Ad); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); + } + + private static void EmitAluFfma(ShaderIrBlock Block, long OpCode, ShaderOper Oper) + { + bool Nb = ((OpCode >> 48) & 1) != 0; + bool Nc = ((OpCode >> 49) & 1) != 0; + + ShaderIrNode OperA = GetOperGpr8(OpCode), OperB, OperC; + + switch (Oper) + { + case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break; + case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break; + case ShaderOper.RC: OperB = GetOperGpr39 (OpCode); break; + case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break; + + default: throw new ArgumentException(nameof(Oper)); + } + + OperB = GetAluNeg(OperB, Nb); + + if (Oper == ShaderOper.RC) + { + OperC = GetAluNeg(GetOperCbuf34(OpCode), Nc); + } + else + { + OperC = GetAluNeg(GetOperGpr39(OpCode), Nc); + } + + ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Ffma, OperA, OperB, OperC); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); + } + + private static void EmitFsetp(ShaderIrBlock Block, long OpCode, ShaderOper Oper) + { + bool Aa = ((OpCode >> 7) & 1) != 0; + bool Np = ((OpCode >> 42) & 1) != 0; + bool Na = ((OpCode >> 43) & 1) != 0; + bool Ab = ((OpCode >> 44) & 1) != 0; + + ShaderIrNode OperA = GetOperGpr8(OpCode), OperB; + + switch (Oper) + { + case ShaderOper.CR: OperB = GetOperCbuf34 (OpCode); break; + case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break; + case ShaderOper.RR: OperB = GetOperGpr20 (OpCode); break; + + default: throw new ArgumentException(nameof(Oper)); + } + + ShaderIrInst CmpInst = GetCmp(OpCode); + + ShaderIrOp Op = new ShaderIrOp(CmpInst, + GetAluAbsNeg(OperA, Aa, Na), + GetAluAbs (OperB, Ab)); + + ShaderIrOperPred P0Node = GetOperPred3 (OpCode); + ShaderIrOperPred P1Node = GetOperPred0 (OpCode); + ShaderIrOperPred P2Node = GetOperPred39(OpCode); + + Block.AddNode(GetPredNode(new ShaderIrAsg(P0Node, Op), OpCode)); + + ShaderIrInst LopInst = GetBLop(OpCode); + + if (LopInst == ShaderIrInst.Band && P1Node.IsConst && P2Node.IsConst) + { + return; + } + + ShaderIrNode P2NNode = P2Node; + + if (Np) + { + P2NNode = new ShaderIrOp(ShaderIrInst.Bnot, P2NNode); + } + + Op = new ShaderIrOp(ShaderIrInst.Bnot, P0Node); + + Op = new ShaderIrOp(LopInst, Op, P2NNode); + + Block.AddNode(GetPredNode(new ShaderIrAsg(P1Node, Op), OpCode)); + + Op = new ShaderIrOp(LopInst, P0Node, P2NNode); + + Block.AddNode(GetPredNode(new ShaderIrAsg(P0Node, Op), OpCode)); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs new file mode 100644 index 00000000..d3feb92e --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs @@ -0,0 +1,17 @@ +using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper; + +namespace Ryujinx.Graphics.Gal.Shader +{ + static partial class ShaderDecode + { + public static void Exit(ShaderIrBlock Block, long OpCode) + { + Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Exit), OpCode)); + } + + public static void Kil(ShaderIrBlock Block, long OpCode) + { + Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Kil), OpCode)); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs new file mode 100644 index 00000000..7989570d --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs @@ -0,0 +1,211 @@ +using System; + +namespace Ryujinx.Graphics.Gal.Shader +{ + static class ShaderDecodeHelper + { + public static ShaderIrOperAbuf[] GetOperAbuf20(long OpCode) + { + int Abuf = (int)(OpCode >> 20) & 0x3ff; + int Reg = (int)(OpCode >> 39) & 0xff; + int Size = (int)(OpCode >> 47) & 3; + + ShaderIrOperAbuf[] Opers = new ShaderIrOperAbuf[Size + 1]; + + for (int Index = 0; Index <= Size; Index++) + { + Opers[Index] = new ShaderIrOperAbuf(Abuf, Reg); + } + + return Opers; + } + + public static ShaderIrOperAbuf GetOperAbuf28(long OpCode) + { + int Abuf = (int)(OpCode >> 28) & 0x3ff; + int Reg = (int)(OpCode >> 39) & 0xff; + + return new ShaderIrOperAbuf(Abuf, Reg); + } + + public static ShaderIrOperCbuf GetOperCbuf34(long OpCode) + { + return new ShaderIrOperCbuf( + (int)(OpCode >> 34) & 0x1f, + (int)(OpCode >> 20) & 0x3fff); + } + + public static ShaderIrOperGpr GetOperGpr8(long OpCode) + { + return new ShaderIrOperGpr((int)(OpCode >> 8) & 0xff); + } + + public static ShaderIrOperGpr GetOperGpr20(long OpCode) + { + return new ShaderIrOperGpr((int)(OpCode >> 20) & 0xff); + } + + public static ShaderIrOperGpr GetOperGpr39(long OpCode) + { + return new ShaderIrOperGpr((int)(OpCode >> 39) & 0xff); + } + + public static ShaderIrOperGpr GetOperGpr0(long OpCode) + { + return new ShaderIrOperGpr((int)(OpCode >> 0) & 0xff); + } + + public static ShaderIrOperGpr GetOperGpr28(long OpCode) + { + return new ShaderIrOperGpr((int)(OpCode >> 28) & 0xff); + } + + public static ShaderIrNode GetOperImm19_20(long OpCode) + { + int Value = (int)(OpCode >> 20) & 0x7ffff; + + bool Neg = ((OpCode >> 56) & 1) != 0; + + if (Neg) + { + Value = -Value; + } + + return new ShaderIrOperImm((int)Value); + } + + public static ShaderIrNode GetOperImmf19_20(long OpCode) + { + uint Imm = (uint)(OpCode >> 20) & 0x7ffff; + + bool Neg = ((OpCode >> 56) & 1) != 0; + + Imm <<= 12; + + if (Neg) + { + Imm |= 0x80000000; + } + + float Value = BitConverter.Int32BitsToSingle((int)Imm); + + return new ShaderIrOperImmf(Value); + } + + public static ShaderIrOperImm GetOperImm13_36(long OpCode) + { + return new ShaderIrOperImm((int)(OpCode >> 36) & 0x1fff); + } + + public static ShaderIrOperImm GetOperImm32_20(long OpCode) + { + return new ShaderIrOperImm((int)(OpCode >> 20)); + } + + public static ShaderIrOperPred GetOperPred3(long OpCode) + { + return new ShaderIrOperPred((int)(OpCode >> 3) & 7); + } + + public static ShaderIrOperPred GetOperPred0(long OpCode) + { + return new ShaderIrOperPred((int)(OpCode >> 0) & 7); + } + + public static ShaderIrNode GetOperPred39N(long OpCode) + { + ShaderIrNode Node = GetOperPred39(OpCode); + + if (((OpCode >> 42) & 1) != 0) + { + Node = new ShaderIrOp(ShaderIrInst.Bnot, Node); + } + + return Node; + } + + public static ShaderIrOperPred GetOperPred39(long OpCode) + { + return new ShaderIrOperPred((int)(OpCode >> 39) & 7); + } + + public static ShaderIrInst GetCmp(long OpCode) + { + switch ((int)(OpCode >> 48) & 0xf) + { + case 0x1: return ShaderIrInst.Clt; + case 0x2: return ShaderIrInst.Ceq; + case 0x3: return ShaderIrInst.Cle; + case 0x4: return ShaderIrInst.Cgt; + case 0x5: return ShaderIrInst.Cne; + case 0x6: return ShaderIrInst.Cge; + case 0x7: return ShaderIrInst.Cnum; + case 0x8: return ShaderIrInst.Cnan; + case 0x9: return ShaderIrInst.Cltu; + case 0xa: return ShaderIrInst.Cequ; + case 0xb: return ShaderIrInst.Cleu; + case 0xc: return ShaderIrInst.Cgtu; + case 0xd: return ShaderIrInst.Cneu; + case 0xe: return ShaderIrInst.Cgeu; + } + + throw new ArgumentException(nameof(OpCode)); + } + + public static ShaderIrInst GetBLop(long OpCode) + { + switch ((int)(OpCode >> 45) & 3) + { + case 0: return ShaderIrInst.Band; + case 1: return ShaderIrInst.Bor; + case 2: return ShaderIrInst.Bxor; + } + + throw new ArgumentException(nameof(OpCode)); + } + + public static ShaderIrNode GetPredNode(ShaderIrNode Node, long OpCode) + { + ShaderIrOperPred Pred = GetPredNode(OpCode); + + if (Pred.Index != ShaderIrOperPred.UnusedIndex) + { + Node = new ShaderIrCond(Pred, Node); + } + + return Node; + } + + private static ShaderIrOperPred GetPredNode(long OpCode) + { + int Pred = (int)(OpCode >> 16) & 0xf; + + if (Pred != 0xf) + { + Pred &= 7; + } + + return new ShaderIrOperPred(Pred); + } + + public static ShaderIrNode GetAluAbsNeg(ShaderIrNode Node, bool Abs, bool Neg) + { + return GetAluNeg(GetAluAbs(Node, Abs), Neg); + } + + public static ShaderIrNode GetAluAbs(ShaderIrNode Node, bool Abs) + { + return Abs ? new ShaderIrOp(ShaderIrInst.Fabs, Node) : Node; + } + + public static ShaderIrNode GetAluNeg(ShaderIrNode Node, bool Neg) + { + return Neg ? new ShaderIrOp(ShaderIrInst.Fneg, Node) : Node; + } + + public static ShaderIrNode GetAluNot(ShaderIrNode Node, bool Not) + { + return Not ? new ShaderIrOp(ShaderIrInst.Not, Node) : Node; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs new file mode 100644 index 00000000..fd18ce07 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs @@ -0,0 +1,59 @@ +using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper; + +namespace Ryujinx.Graphics.Gal.Shader +{ + static partial class ShaderDecode + { + public static void Ld_A(ShaderIrBlock Block, long OpCode) + { + ShaderIrNode[] Opers = GetOperAbuf20(OpCode); + + int Index = 0; + + foreach (ShaderIrNode OperA in Opers) + { + ShaderIrOperGpr OperD = GetOperGpr0(OpCode); + + OperD.Index += Index++; + + Block.AddNode(GetPredNode(new ShaderIrAsg(OperD, OperA), OpCode)); + } + } + + public static void St_A(ShaderIrBlock Block, long OpCode) + { + ShaderIrNode[] Opers = GetOperAbuf20(OpCode); + + int Index = 0; + + foreach (ShaderIrNode OperA in Opers) + { + ShaderIrOperGpr OperD = GetOperGpr0(OpCode); + + OperD.Index += Index++; + + Block.AddNode(GetPredNode(new ShaderIrAsg(OperA, OperD), OpCode)); + } + } + + public static void Texs(ShaderIrBlock Block, long OpCode) + { + //TODO: Support other formats. + ShaderIrNode OperA = GetOperGpr8 (OpCode); + ShaderIrNode OperB = GetOperGpr20 (OpCode); + ShaderIrNode OperC = GetOperGpr28 (OpCode); + ShaderIrNode OperD = GetOperImm13_36(OpCode); + + for (int Ch = 0; Ch < 4; Ch++) + { + ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Texr + Ch, OperA, OperB, OperD); + + ShaderIrOperGpr Dst = GetOperGpr0(OpCode); + + Dst.Index += Ch; + + Block.AddNode(new ShaderIrAsg(Dst, Op)); + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs new file mode 100644 index 00000000..50c740bf --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs @@ -0,0 +1,128 @@ +using System; + +using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper; + +namespace Ryujinx.Graphics.Gal.Shader +{ + static partial class ShaderDecode + { + private enum IntType + { + U8 = 0, + U16 = 1, + U32 = 2, + U64 = 3, + S8 = 4, + S16 = 5, + S32 = 6, + S64 = 7 + } + + private enum FloatType + { + F16 = 1, + F32 = 2, + F64 = 3 + } + + public static void I2f_C(ShaderIrBlock Block, long OpCode) + { + EmitI2f(Block, OpCode, ShaderOper.CR); + } + + public static void I2f_I(ShaderIrBlock Block, long OpCode) + { + EmitI2f(Block, OpCode, ShaderOper.Imm); + } + + public static void I2f_R(ShaderIrBlock Block, long OpCode) + { + EmitI2f(Block, OpCode, ShaderOper.RR); + } + + private static void EmitI2f(ShaderIrBlock Block, long OpCode, ShaderOper Oper) + { + IntType Type = GetIntType(OpCode); + + if (Type == IntType.U64 || + Type == IntType.S64) + { + //TODO: 64-bits support. + //Note: GLSL doesn't support 64-bits integers. + throw new NotImplementedException(); + } + + int Sel = (int)(OpCode >> 41) & 3; + + bool Na = ((OpCode >> 45) & 1) != 0; + bool Aa = ((OpCode >> 49) & 1) != 0; + + ShaderIrNode OperA; + + switch (Oper) + { + case ShaderOper.CR: OperA = GetOperCbuf34 (OpCode); break; + case ShaderOper.Imm: OperA = GetOperImm19_20(OpCode); break; + case ShaderOper.RR: OperA = GetOperGpr20 (OpCode); break; + + default: throw new ArgumentException(nameof(Oper)); + } + + OperA = GetAluAbsNeg(OperA, Aa, Na); + + bool Signed = Type >= IntType.S8; + + int Shift = Sel * 8; + + int Size = 8 << ((int)Type & 3); + + ulong Mask = ulong.MaxValue >> (64 - Size); + + int Mask32 = (int)Mask; + + if (Shift != 0) + { + OperA = new ShaderIrOp(ShaderIrInst.Asr, OperA, new ShaderIrOperImm(Shift)); + } + + if (Mask != uint.MaxValue) + { + OperA = new ShaderIrOp(ShaderIrInst.And, OperA, new ShaderIrOperImm(Mask32)); + } + + ShaderIrInst Inst = Signed + ? ShaderIrInst.Stof + : ShaderIrInst.Utof; + + ShaderIrNode Op = new ShaderIrOp(Inst, OperA); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode)); + } + + public static void Mov32i(ShaderIrBlock Block, long OpCode) + { + ShaderIrOperImm Imm = GetOperImm32_20(OpCode); + + Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Imm), OpCode)); + } + + private static IntType GetIntType(long OpCode) + { + bool Signed = ((OpCode >> 13) & 1) != 0; + + IntType Type = (IntType)((OpCode >> 10) & 3); + + if (Signed) + { + Type += (int)IntType.S8; + } + + return Type; + } + + private static FloatType GetFloatType(long OpCode) + { + return (FloatType)((OpCode >> 8) & 3); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs new file mode 100644 index 00000000..779bbf92 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs @@ -0,0 +1,41 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + static class ShaderDecoder + { + public static ShaderIrBlock DecodeBasicBlock(int[] Code, int Offset, GalShaderType ShaderType) + { + ShaderIrBlock Block = new ShaderIrBlock(); + + while (Offset + 2 <= Code.Length) + { + uint Word0 = (uint)Code[Offset++]; + uint Word1 = (uint)Code[Offset++]; + + long OpCode = Word0 | (long)Word1 << 32; + + ShaderDecodeFunc Decode = ShaderOpCodeTable.GetDecoder(OpCode); + + if (Decode == null) + { + continue; + } + + Decode(Block, OpCode); + + if (Block.GetLastNode() is ShaderIrOp Op && IsFlowChange(Op.Inst)) + { + break; + } + } + + Block.RunOptimizationPasses(ShaderType); + + return Block; + } + + private static bool IsFlowChange(ShaderIrInst Inst) + { + return Inst == ShaderIrInst.Exit; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs new file mode 100644 index 00000000..00f8f6a5 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrAsg : ShaderIrNode + { + public ShaderIrNode Dst { get; set; } + public ShaderIrNode Src { get; set; } + + public ShaderIrAsg(ShaderIrNode Dst, ShaderIrNode Src) + { + this.Dst = Dst; + this.Src = Src; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs new file mode 100644 index 00000000..1a96d3be --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrBlock + { + private List<ShaderIrNode> Nodes; + + public ShaderIrBlock() + { + Nodes = new List<ShaderIrNode>(); + } + + public void AddNode(ShaderIrNode Node) + { + Nodes.Add(Node); + } + + public void RunOptimizationPasses(GalShaderType ShaderType) + { + ShaderOptExprProp.Optimize(Nodes, ShaderType); + } + + public ShaderIrNode[] GetNodes() + { + return Nodes.ToArray(); + } + + public ShaderIrNode GetLastNode() + { + if (Nodes.Count > 0) + { + return Nodes[Nodes.Count - 1]; + } + + return null; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs new file mode 100644 index 00000000..d8c87b49 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrCond : ShaderIrNode + { + public ShaderIrNode Pred { get; set; } + public ShaderIrNode Child { get; set; } + + public ShaderIrCond(ShaderIrNode Pred, ShaderIrNode Child) + { + this.Pred = Pred; + this.Child = Child; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs new file mode 100644 index 00000000..b6f4e80b --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs @@ -0,0 +1,59 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + enum ShaderIrInst + { + B_Start, + Band, + Bnot, + Bor, + Bxor, + Clt, + Ceq, + Cle, + Cgt, + Cne, + Cge, + Cnum, + Cnan, + Cltu, + Cequ, + Cleu, + Cgtu, + Cneu, + Cgeu, + B_End, + + F_Start, + Fabs, + Fadd, + Fcos, + Fex2, + Ffma, + Flg2, + Fmul, + Fneg, + Frcp, + Frsq, + Fsin, + Ipa, + Texr, + Texg, + Texb, + Texa, + F_End, + + I_Start, + And, + Asr, + Lsr, + Not, + Or, + Stof, + Utof, + Xor, + I_End, + + Exit, + Kil + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs new file mode 100644 index 00000000..2648164a --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrNode { } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs new file mode 100644 index 00000000..cd210757 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrOp : ShaderIrNode + { + public ShaderIrInst Inst { get; private set; } + public ShaderIrNode OperandA { get; set; } + public ShaderIrNode OperandB { get; set; } + public ShaderIrNode OperandC { get; set; } + + public ShaderIrOp( + ShaderIrInst Inst, + ShaderIrNode OperandA = null, + ShaderIrNode OperandB = null, + ShaderIrNode OperandC = null) + { + this.Inst = Inst; + this.OperandA = OperandA; + this.OperandB = OperandB; + this.OperandC = OperandC; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs new file mode 100644 index 00000000..fa612de7 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrOperAbuf : ShaderIrNode + { + public int Offs { get; private set; } + public int GprIndex { get; private set; } + + public ShaderIrOperAbuf(int Offs, int GprIndex) + { + this.Offs = Offs; + this.GprIndex = GprIndex; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs new file mode 100644 index 00000000..f2272056 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrOperCbuf : ShaderIrNode + { + public int Index { get; private set; } + public int Offs { get; private set; } + + public ShaderIrOperCbuf(int Index, int Offs) + { + this.Index = Index; + this.Offs = Offs; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs new file mode 100644 index 00000000..5c69d6a6 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrOperGpr : ShaderIrNode + { + public const int ZRIndex = 0xff; + + public bool IsConst => Index == ZRIndex; + + public int Index { get; set; } + + public ShaderIrOperGpr(int Index) + { + this.Index = Index; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs new file mode 100644 index 00000000..ba2c2c9b --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrOperImm : ShaderIrNode + { + public int Value { get; private set; } + + public ShaderIrOperImm(int Value) + { + this.Value = Value; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs new file mode 100644 index 00000000..3c27e483 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrOperImmf : ShaderIrNode + { + public float Value { get; private set; } + + public ShaderIrOperImmf(float Value) + { + this.Value = Value; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs new file mode 100644 index 00000000..74cca0ef --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + class ShaderIrOperPred : ShaderIrNode + { + public const int UnusedIndex = 0x7; + public const int NeverExecute = 0xf; + + public bool IsConst => Index >= UnusedIndex; + + public int Index { get; set; } + + public ShaderIrOperPred(int Index) + { + this.Index = Index; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs new file mode 100644 index 00000000..48c3b2ee --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs @@ -0,0 +1,97 @@ +using System; + +namespace Ryujinx.Graphics.Gal.Shader +{ + static class ShaderOpCodeTable + { + private const int EncodingBits = 14; + + private static ShaderDecodeFunc[] OpCodes; + + static ShaderOpCodeTable() + { + OpCodes = new ShaderDecodeFunc[1 << EncodingBits]; + +#region Instructions + Set("111000110000xx", ShaderDecode.Exit); + Set("0100110001011x", ShaderDecode.Fadd_C); + Set("0011100x01011x", ShaderDecode.Fadd_I); + Set("0101110001011x", ShaderDecode.Fadd_R); + Set("010010011xxxxx", ShaderDecode.Ffma_CR); + Set("001100101xxxxx", ShaderDecode.Ffma_I); + Set("010100011xxxxx", ShaderDecode.Ffma_RC); + Set("010110011xxxxx", ShaderDecode.Ffma_RR); + Set("0100110001101x", ShaderDecode.Fmul_C); + Set("0011100x01101x", ShaderDecode.Fmul_I); + Set("0101110001101x", ShaderDecode.Fmul_R); + Set("010010111011xx", ShaderDecode.Fsetp_C); + Set("0011011x1011xx", ShaderDecode.Fsetp_I); + Set("010110111011xx", ShaderDecode.Fsetp_R); + Set("0100110010111x", ShaderDecode.I2f_C); + Set("0011100x10111x", ShaderDecode.I2f_I); + Set("0101110010111x", ShaderDecode.I2f_R); + Set("11100000xxxxxx", ShaderDecode.Ipa); + Set("111000110011xx", ShaderDecode.Kil); + Set("1110111111011x", ShaderDecode.Ld_A); + Set("000001xxxxxxxx", ShaderDecode.Lop32i); + Set("000000010000xx", ShaderDecode.Mov32i); + Set("0101000010000x", ShaderDecode.Mufu); + Set("0100110000101x", ShaderDecode.Shr_C); + Set("0011100x00101x", ShaderDecode.Shr_I); + Set("0101110000101x", ShaderDecode.Shr_R); + Set("1110111111110x", ShaderDecode.St_A); + Set("1101100xxxxxxx", ShaderDecode.Texs); +#endregion + } + + private static void Set(string Encoding, ShaderDecodeFunc Func) + { + if (Encoding.Length != EncodingBits) + { + throw new ArgumentException(nameof(Encoding)); + } + + int Bit = Encoding.Length - 1; + int Value = 0; + int XMask = 0; + int XBits = 0; + + int[] XPos = new int[Encoding.Length]; + + for (int Index = 0; Index < Encoding.Length; Index++, Bit--) + { + char Chr = Encoding[Index]; + + if (Chr == '1') + { + Value |= 1 << Bit; + } + else if (Chr == 'x') + { + XMask |= 1 << Bit; + + XPos[XBits++] = Bit; + } + } + + XMask = ~XMask; + + for (int Index = 0; Index < (1 << XBits); Index++) + { + Value &= XMask; + + for (int X = 0; X < XBits; X++) + { + Value |= ((Index >> X) & 1) << XPos[X]; + } + + OpCodes[Value] = Func; + } + } + + public static ShaderDecodeFunc GetDecoder(long OpCode) + { + return OpCodes[(ulong)OpCode >> (64 - EncodingBits)]; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs new file mode 100644 index 00000000..7989deed --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gal.Shader +{ + enum ShaderOper + { + CR, + RC, + RR, + Imm, + Immf + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOptExprProp.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOptExprProp.cs new file mode 100644 index 00000000..69457aeb --- /dev/null +++ b/Ryujinx.Graphics/Gal/Shader/ShaderOptExprProp.cs @@ -0,0 +1,266 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gal.Shader +{ + static class ShaderOptExprProp + { + private struct UseSite + { + public object Parent; + + public int OperIndex; + + public UseSite(object Parent, int OperIndex) + { + this.Parent = Parent; + this.OperIndex = OperIndex; + } + } + + private class RegUse + { + public ShaderIrAsg Asg { get; private set; } + + public int AsgIndex { get; private set; } + + private bool Propagate; + + private List<UseSite> Sites; + + public RegUse() + { + Sites = new List<UseSite>(); + } + + public void AddUseSite(UseSite Site) + { + Sites.Add(Site); + } + + public bool TryPropagate() + { + //This happens when a untiliazied register is used, + //this usually indicates a decoding error, but may also + //be cased by bogus programs (?). In any case, we just + //keep the unitialized access and avoid trying to propagate + //the expression (since we can't propagate what doesn't yet exist). + if (Asg == null || !Propagate) + { + return false; + } + + if (Sites.Count > 0) + { + foreach (UseSite Site in Sites) + { + if (Site.Parent is ShaderIrCond Cond) + { + switch (Site.OperIndex) + { + case 0: Cond.Pred = Asg.Src; break; + case 1: Cond.Child = Asg.Src; break; + + default: throw new InvalidOperationException(); + } + } + else if (Site.Parent is ShaderIrOp Op) + { + switch (Site.OperIndex) + { + case 0: Op.OperandA = Asg.Src; break; + case 1: Op.OperandB = Asg.Src; break; + case 2: Op.OperandC = Asg.Src; break; + + default: throw new InvalidOperationException(); + } + } + else if (Site.Parent is ShaderIrAsg SiteAsg) + { + SiteAsg.Src = Asg.Src; + } + else + { + throw new InvalidOperationException(); + } + } + } + + return true; + } + + public void SetNewAsg(ShaderIrAsg Asg, int AsgIndex, bool Propagate) + { + this.Asg = Asg; + this.AsgIndex = AsgIndex; + this.Propagate = Propagate; + + Sites.Clear(); + } + } + + public static void Optimize(List<ShaderIrNode> Nodes, GalShaderType ShaderType) + { + Dictionary<int, RegUse> Uses = new Dictionary<int, RegUse>(); + + RegUse GetUse(int Key) + { + RegUse Use; + + if (!Uses.TryGetValue(Key, out Use)) + { + Use = new RegUse(); + + Uses.Add(Key, Use); + } + + return Use; + } + + int GetGprKey(int GprIndex) + { + return GprIndex; + } + + int GetPredKey(int PredIndex) + { + return PredIndex | 0x10000000; + } + + RegUse GetGprUse(int GprIndex) + { + return GetUse(GetGprKey(GprIndex)); + } + + RegUse GetPredUse(int PredIndex) + { + return GetUse(GetPredKey(PredIndex)); + } + + void FindRegUses(List<(int, UseSite)> UseList, object Parent, ShaderIrNode Node, int OperIndex = 0) + { + if (Node is ShaderIrAsg Asg) + { + FindRegUses(UseList, Asg, Asg.Src); + } + else if (Node is ShaderIrCond Cond) + { + FindRegUses(UseList, Cond, Cond.Pred, 0); + FindRegUses(UseList, Cond, Cond.Child, 1); + } + else if (Node is ShaderIrOp Op) + { + FindRegUses(UseList, Op, Op.OperandA, 0); + FindRegUses(UseList, Op, Op.OperandB, 1); + FindRegUses(UseList, Op, Op.OperandC, 2); + } + else if (Node is ShaderIrOperGpr Gpr && Gpr.Index != ShaderIrOperGpr.ZRIndex) + { + UseList.Add((GetGprKey(Gpr.Index), new UseSite(Parent, OperIndex))); + } + else if (Node is ShaderIrOperPred Pred) + { + UseList.Add((GetPredKey(Pred.Index), new UseSite(Parent, OperIndex))); + } + } + + void TryAddRegUseSite(ShaderIrNode Node) + { + List<(int, UseSite)> UseList = new List<(int, UseSite)>(); + + FindRegUses(UseList, null, Node); + + foreach ((int Key, UseSite Site) in UseList) + { + GetUse(Key).AddUseSite(Site); + } + } + + bool TryPropagate(RegUse Use) + { + //We can only propagate if the registers that the expression depends + //on weren't assigned after the original expression assignment + //to a register took place. We traverse the expression tree to find + //all registers being used, if any of those registers was assigned + //after the assignment to be propagated, then we can't propagate. + if (Use?.Asg == null) + { + return false; + } + + List<(int, UseSite)> UseList = new List<(int, UseSite)>(); + + FindRegUses(UseList, Use.Asg, Use.Asg.Src); + + foreach ((int Key, UseSite Site) in UseList) + { + if (GetUse(Key).AsgIndex >= Use.AsgIndex) + { + return false; + } + } + + return Use.TryPropagate(); + } + + for (int Index = 0, AsgIndex = 0; Index < Nodes.Count; Index++, AsgIndex++) + { + ShaderIrNode Node = Nodes[Index]; + + bool IsConditional = Node is ShaderIrCond; + + TryAddRegUseSite(Node); + + while (Node is ShaderIrCond Cond) + { + Node = Cond.Child; + } + + if (!(Node is ShaderIrAsg Asg)) + { + continue; + } + + RegUse Use = null; + + if (Asg.Dst is ShaderIrOperGpr Gpr && Gpr.Index != ShaderIrOperGpr.ZRIndex) + { + Use = GetGprUse(Gpr.Index); + } + else if (Asg.Dst is ShaderIrOperPred Pred) + { + Use = GetPredUse(Pred.Index); + } + + if (!IsConditional && TryPropagate(Use)) + { + Nodes.Remove(Use.Asg); + + Index--; + } + + //All nodes inside conditional nodes can't be propagated, + //as we don't even know if they will be executed to begin with. + Use?.SetNewAsg(Asg, AsgIndex, !IsConditional); + } + + foreach (RegUse Use in Uses.Values) + { + //Gprs 0-3 are the color output on fragment shaders, + //so we can't remove the last assignments to those registers. + if (ShaderType == GalShaderType.Fragment) + { + if (Use.Asg?.Dst is ShaderIrOperGpr Gpr && Gpr.Index < 4) + { + continue; + } + } + + if (TryPropagate(Use)) + { + Nodes.Remove(Use.Asg); + } + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs b/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs new file mode 100644 index 00000000..d400850c --- /dev/null +++ b/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.Graphics.Gal +{ + public class ShaderDeclInfo + { + public string Name { get; private set; } + + public int Index { get; private set; } + public int Cbuf { get; private set; } + public int Size { get; private set; } + + public ShaderDeclInfo(string Name, int Index, int Cbuf = 0, int Size = 1) + { + this.Name = Name; + this.Index = Index; + this.Cbuf = Cbuf; + this.Size = Size; + } + + internal void Enlarge(int NewSize) + { + if (Size < NewSize) + { + Size = NewSize; + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/ShaderException.cs b/Ryujinx.Graphics/Gal/ShaderException.cs new file mode 100644 index 00000000..9bc87ff3 --- /dev/null +++ b/Ryujinx.Graphics/Gal/ShaderException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.Graphics.Gal +{ + class ShaderException : Exception + { + public ShaderException() : base() { } + + public ShaderException(string Message) : base(Message) { } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Texture/BCn.cs b/Ryujinx.Graphics/Gal/Texture/BCn.cs new file mode 100644 index 00000000..f23a86c2 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Texture/BCn.cs @@ -0,0 +1,468 @@ +using System; +using System.Drawing; + +namespace Ryujinx.Graphics.Gal.Texture +{ + static class BCn + { + public static byte[] DecodeBC1(GalTexture Texture, int Offset) + { + int W = (Texture.Width + 3) / 4; + int H = (Texture.Height + 3) / 4; + + byte[] Output = new byte[W * H * 64]; + + SwizzleAddr Swizzle = new SwizzleAddr(W, H, 8); + + for (int Y = 0; Y < H; Y++) + { + for (int X = 0; X < W; X++) + { + int IOffs = Offset + Swizzle.GetSwizzledAddress64(X, Y) * 8; + + byte[] Tile = BCnDecodeTile(Texture.Data, IOffs, true); + + int TOffset = 0; + + for (int TY = 0; TY < 4; TY++) + { + for (int TX = 0; TX < 4; TX++) + { + int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4; + + Output[OOffset + 0] = Tile[TOffset + 0]; + Output[OOffset + 1] = Tile[TOffset + 1]; + Output[OOffset + 2] = Tile[TOffset + 2]; + Output[OOffset + 3] = Tile[TOffset + 3]; + + TOffset += 4; + } + } + } + } + + return Output; + } + + public static byte[] DecodeBC2(GalTexture Texture, int Offset) + { + int W = (Texture.Width + 3) / 4; + int H = (Texture.Height + 3) / 4; + + byte[] Output = new byte[W * H * 64]; + + SwizzleAddr Swizzle = new SwizzleAddr(W, H, 4); + + for (int Y = 0; Y < H; Y++) + { + for (int X = 0; X < W; X++) + { + int IOffs = Offset + Swizzle.GetSwizzledAddress128(X, Y) * 16; + + byte[] Tile = BCnDecodeTile(Texture.Data, IOffs + 8, false); + + int AlphaLow = Get32(Texture.Data, IOffs + 0); + int AlphaHigh = Get32(Texture.Data, IOffs + 4); + + ulong AlphaCh = (uint)AlphaLow | (ulong)AlphaHigh << 32; + + int TOffset = 0; + + for (int TY = 0; TY < 4; TY++) + { + for (int TX = 0; TX < 4; TX++) + { + ulong Alpha = (AlphaCh >> (TY * 16 + TX * 4)) & 0xf; + + int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4; + + Output[OOffset + 0] = Tile[TOffset + 0]; + Output[OOffset + 1] = Tile[TOffset + 1]; + Output[OOffset + 2] = Tile[TOffset + 2]; + Output[OOffset + 3] = (byte)(Alpha | (Alpha << 4)); + + TOffset += 4; + } + } + } + } + + return Output; + } + + public static byte[] DecodeBC3(GalTexture Texture, int Offset) + { + int W = (Texture.Width + 3) / 4; + int H = (Texture.Height + 3) / 4; + + byte[] Output = new byte[W * H * 64]; + + SwizzleAddr Swizzle = new SwizzleAddr(W, H, 4); + + for (int Y = 0; Y < H; Y++) + { + for (int X = 0; X < W; X++) + { + int IOffs = Offset + Swizzle.GetSwizzledAddress128(X, Y) * 16; + + byte[] Tile = BCnDecodeTile(Texture.Data, IOffs + 8, false); + + byte[] Alpha = new byte[8]; + + Alpha[0] = Texture.Data[IOffs + 0]; + Alpha[1] = Texture.Data[IOffs + 1]; + + CalculateBC3Alpha(Alpha); + + int AlphaLow = Get32(Texture.Data, IOffs + 2); + int AlphaHigh = Get16(Texture.Data, IOffs + 6); + + ulong AlphaCh = (uint)AlphaLow | (ulong)AlphaHigh << 32; + + int TOffset = 0; + + for (int TY = 0; TY < 4; TY++) + { + for (int TX = 0; TX < 4; TX++) + { + int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4; + + byte AlphaPx = Alpha[(AlphaCh >> (TY * 12 + TX * 3)) & 7]; + + Output[OOffset + 0] = Tile[TOffset + 0]; + Output[OOffset + 1] = Tile[TOffset + 1]; + Output[OOffset + 2] = Tile[TOffset + 2]; + Output[OOffset + 3] = AlphaPx; + + TOffset += 4; + } + } + } + } + + return Output; + } + + public static byte[] DecodeBC4(GalTexture Texture, int Offset) + { + int W = (Texture.Width + 3) / 4; + int H = (Texture.Height + 3) / 4; + + byte[] Output = new byte[W * H * 64]; + + SwizzleAddr Swizzle = new SwizzleAddr(W, H, 8); + + for (int Y = 0; Y < H; Y++) + { + for (int X = 0; X < W; X++) + { + int IOffs = Swizzle.GetSwizzledAddress64(X, Y) * 8; + + byte[] Red = new byte[8]; + + Red[0] = Texture.Data[IOffs + 0]; + Red[1] = Texture.Data[IOffs + 1]; + + CalculateBC3Alpha(Red); + + int RedLow = Get32(Texture.Data, IOffs + 2); + int RedHigh = Get16(Texture.Data, IOffs + 6); + + ulong RedCh = (uint)RedLow | (ulong)RedHigh << 32; + + int TOffset = 0; + + for (int TY = 0; TY < 4; TY++) + { + for (int TX = 0; TX < 4; TX++) + { + int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4; + + byte RedPx = Red[(RedCh >> (TY * 12 + TX * 3)) & 7]; + + Output[OOffset + 0] = RedPx; + Output[OOffset + 1] = RedPx; + Output[OOffset + 2] = RedPx; + Output[OOffset + 3] = 0xff; + + TOffset += 4; + } + } + } + } + + return Output; + } + + public static byte[] DecodeBC5(GalTexture Texture, int Offset, bool SNorm) + { + int W = (Texture.Width + 3) / 4; + int H = (Texture.Height + 3) / 4; + + byte[] Output = new byte[W * H * 64]; + + SwizzleAddr Swizzle = new SwizzleAddr(W, H, 4); + + for (int Y = 0; Y < H; Y++) + { + for (int X = 0; X < W; X++) + { + int IOffs = Swizzle.GetSwizzledAddress128(X, Y) * 16; + + byte[] Red = new byte[8]; + byte[] Green = new byte[8]; + + Red[0] = Texture.Data[IOffs + 0]; + Red[1] = Texture.Data[IOffs + 1]; + + Green[0] = Texture.Data[IOffs + 8]; + Green[1] = Texture.Data[IOffs + 9]; + + if (SNorm) + { + CalculateBC3AlphaS(Red); + CalculateBC3AlphaS(Green); + } + else + { + CalculateBC3Alpha(Red); + CalculateBC3Alpha(Green); + } + + int RedLow = Get32(Texture.Data, IOffs + 2); + int RedHigh = Get16(Texture.Data, IOffs + 6); + + int GreenLow = Get32(Texture.Data, IOffs + 10); + int GreenHigh = Get16(Texture.Data, IOffs + 14); + + ulong RedCh = (uint)RedLow | (ulong)RedHigh << 32; + ulong GreenCh = (uint)GreenLow | (ulong)GreenHigh << 32; + + int TOffset = 0; + + if (SNorm) + { + for (int TY = 0; TY < 4; TY++) + { + for (int TX = 0; TX < 4; TX++) + { + int Shift = TY * 12 + TX * 3; + + int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4; + + byte RedPx = Red [(RedCh >> Shift) & 7]; + byte GreenPx = Green[(GreenCh >> Shift) & 7]; + + RedPx += 0x80; + GreenPx += 0x80; + + float NX = (RedPx / 255f) * 2 - 1; + float NY = (GreenPx / 255f) * 2 - 1; + + float NZ = (float)Math.Sqrt(1 - (NX * NX + NY * NY)); + + Output[OOffset + 0] = Clamp((NZ + 1) * 0.5f); + Output[OOffset + 1] = Clamp((NY + 1) * 0.5f); + Output[OOffset + 2] = Clamp((NX + 1) * 0.5f); + Output[OOffset + 3] = 0xff; + + TOffset += 4; + } + } + } + else + { + for (int TY = 0; TY < 4; TY++) + { + for (int TX = 0; TX < 4; TX++) + { + int Shift = TY * 12 + TX * 3; + + int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4; + + byte RedPx = Red [(RedCh >> Shift) & 7]; + byte GreenPx = Green[(GreenCh >> Shift) & 7]; + + Output[OOffset + 0] = RedPx; + Output[OOffset + 1] = RedPx; + Output[OOffset + 2] = RedPx; + Output[OOffset + 3] = GreenPx; + + TOffset += 4; + } + } + } + } + } + + return Output; + } + + private static byte Clamp(float Value) + { + if (Value > 1) + { + return 0xff; + } + else if (Value < 0) + { + return 0; + } + else + { + return (byte)(Value * 0xff); + } + } + + private static void CalculateBC3Alpha(byte[] Alpha) + { + for (int i = 2; i < 8; i++) + { + if (Alpha[0] > Alpha[1]) + { + Alpha[i] = (byte)(((8 - i) * Alpha[0] + (i - 1) * Alpha[1]) / 7); + } + else if (i < 6) + { + Alpha[i] = (byte)(((6 - i) * Alpha[0] + (i - 1) * Alpha[1]) / 7); + } + else if (i == 6) + { + Alpha[i] = 0; + } + else /* i == 7 */ + { + Alpha[i] = 0xff; + } + } + } + + private static void CalculateBC3AlphaS(byte[] Alpha) + { + for (int i = 2; i < 8; i++) + { + if ((sbyte)Alpha[0] > (sbyte)Alpha[1]) + { + Alpha[i] = (byte)(((8 - i) * (sbyte)Alpha[0] + (i - 1) * (sbyte)Alpha[1]) / 7); + } + else if (i < 6) + { + Alpha[i] = (byte)(((6 - i) * (sbyte)Alpha[0] + (i - 1) * (sbyte)Alpha[1]) / 7); + } + else if (i == 6) + { + Alpha[i] = 0x80; + } + else /* i == 7 */ + { + Alpha[i] = 0x7f; + } + } + } + + private static byte[] BCnDecodeTile( + byte[] Input, + int Offset, + bool IsBC1) + { + Color[] CLUT = new Color[4]; + + int c0 = Get16(Input, Offset + 0); + int c1 = Get16(Input, Offset + 2); + + CLUT[0] = DecodeRGB565(c0); + CLUT[1] = DecodeRGB565(c1); + CLUT[2] = CalculateCLUT2(CLUT[0], CLUT[1], c0, c1, IsBC1); + CLUT[3] = CalculateCLUT3(CLUT[0], CLUT[1], c0, c1, IsBC1); + + int Indices = Get32(Input, Offset + 4); + + int IdxShift = 0; + + byte[] Output = new byte[4 * 4 * 4]; + + int OOffset = 0; + + for (int TY = 0; TY < 4; TY++) + { + for (int TX = 0; TX < 4; TX++) + { + int Idx = (Indices >> IdxShift) & 3; + + IdxShift += 2; + + Color Pixel = CLUT[Idx]; + + Output[OOffset + 0] = Pixel.R; + Output[OOffset + 1] = Pixel.G; + Output[OOffset + 2] = Pixel.B; + Output[OOffset + 3] = Pixel.A; + + OOffset += 4; + } + } + + return Output; + } + + private static Color CalculateCLUT2(Color C0, Color C1, int c0, int c1, bool IsBC1) + { + if (c0 > c1 || !IsBC1) + { + return Color.FromArgb( + (2 * C0.R + C1.R) / 3, + (2 * C0.G + C1.G) / 3, + (2 * C0.B + C1.B) / 3); + } + else + { + return Color.FromArgb( + (C0.R + C1.R) / 2, + (C0.G + C1.G) / 2, + (C0.B + C1.B) / 2); + } + } + + private static Color CalculateCLUT3(Color C0, Color C1, int c0, int c1, bool IsBC1) + { + if (c0 > c1 || !IsBC1) + { + return + Color.FromArgb( + (2 * C1.R + C0.R) / 3, + (2 * C1.G + C0.G) / 3, + (2 * C1.B + C0.B) / 3); + } + + return Color.Transparent; + } + + private static Color DecodeRGB565(int Value) + { + int B = ((Value >> 0) & 0x1f) << 3; + int G = ((Value >> 5) & 0x3f) << 2; + int R = ((Value >> 11) & 0x1f) << 3; + + return Color.FromArgb( + R | (R >> 5), + G | (G >> 6), + B | (B >> 5)); + } + + private static int Get16(byte[] Data, int Address) + { + return + Data[Address + 0] << 0 | + Data[Address + 1] << 8; + } + + private static int Get32(byte[] Data, int Address) + { + return + Data[Address + 0] << 0 | + Data[Address + 1] << 8 | + Data[Address + 2] << 16 | + Data[Address + 3] << 24; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Texture/SwizzleAddr.cs b/Ryujinx.Graphics/Gal/Texture/SwizzleAddr.cs new file mode 100644 index 00000000..b67b841b --- /dev/null +++ b/Ryujinx.Graphics/Gal/Texture/SwizzleAddr.cs @@ -0,0 +1,144 @@ +using System; + +namespace Ryujinx.Graphics.Gal.Texture +{ + class SwizzleAddr + { + private int Width; + + private int XB; + private int YB; + + public SwizzleAddr(int Width, int Height, int Pad) + { + int W = Pow2RoundUp(Width); + int H = Pow2RoundUp(Height); + + XB = CountZeros(W); + YB = CountZeros(H); + + int HH = H >> 1; + + if (!IsPow2(Height) && Height <= HH + HH / 3 && YB > 3) + { + YB--; + } + + this.Width = RoundSize(Width, Pad); + } + + private static int Pow2RoundUp(int Value) + { + Value--; + + Value |= (Value >> 1); + Value |= (Value >> 2); + Value |= (Value >> 4); + Value |= (Value >> 8); + Value |= (Value >> 16); + + return ++Value; + } + + private static bool IsPow2(int Value) + { + return Value != 0 && (Value & (Value - 1)) == 0; + } + + private static int CountZeros(int Value) + { + int Count = 0; + + for (int i = 0; i < 32; i++) + { + if ((Value & (1 << i)) != 0) + { + break; + } + + Count++; + } + + return Count; + } + + private static int RoundSize(int Size, int Pad) + { + int Mask = Pad - 1; + + if ((Size & Mask) != 0) + { + Size &= ~Mask; + Size += Pad; + } + + return Size; + } + + public int GetSwizzledAddress8(int X, int Y) + { + return GetSwizzledAddress(X, Y, 4); + } + + public int GetSwizzledAddress16(int X, int Y) + { + return GetSwizzledAddress(X, Y, 3); + } + + public int GetSwizzledAddress32(int X, int Y) + { + return GetSwizzledAddress(X, Y, 2); + } + + public int GetSwizzledAddress64(int X, int Y) + { + return GetSwizzledAddress(X, Y, 1); + } + + public int GetSwizzledAddress128(int X, int Y) + { + return GetSwizzledAddress(X, Y, 0); + } + + private int GetSwizzledAddress(int X, int Y, int XBase) + { + /* + * Examples of patterns: + * x x y x y y x y 0 0 0 0 64 x 64 dxt5 + * x x x x x y y y y x y y x y 0 0 0 0 512 x 512 dxt5 + * y x x x x x x y y y y x y y x y 0 0 0 0 1024 x 1024 dxt5 + * y y x x x x x x y y y y x y y x y x 0 0 0 2048 x 2048 dxt1 + * y y y x x x x x x y y y y x y y x y x x 0 0 1024 x 1024 rgba8888 + * + * Read from right to left, LSB first. + */ + int XCnt = XBase; + int YCnt = 1; + int XUsed = 0; + int YUsed = 0; + int Address = 0; + + while (XUsed < XBase + 2 && XUsed + XCnt < XB) + { + int XMask = (1 << XCnt) - 1; + int YMask = (1 << YCnt) - 1; + + Address |= (X & XMask) << XUsed + YUsed; + Address |= (Y & YMask) << XUsed + YUsed + XCnt; + + X >>= XCnt; + Y >>= YCnt; + + XUsed += XCnt; + YUsed += YCnt; + + XCnt = Math.Min(XB - XUsed, 1); + YCnt = Math.Min(YB - YUsed, YCnt << 1); + } + + Address |= (X + Y * (Width >> XUsed)) << (XUsed + YUsed); + + return Address; + } + } +} diff --git a/Ryujinx.Graphics/Gal/Texture/TextureDecoder.cs b/Ryujinx.Graphics/Gal/Texture/TextureDecoder.cs new file mode 100644 index 00000000..4e50db51 --- /dev/null +++ b/Ryujinx.Graphics/Gal/Texture/TextureDecoder.cs @@ -0,0 +1,19 @@ +using System; + +namespace Ryujinx.Graphics.Gal.Texture +{ + static class TextureDecoder + { + public static byte[] Decode(GalTexture Texture) + { + switch (Texture.Format) + { + case GalTextureFormat.BC1: return BCn.DecodeBC1(Texture, 0); + case GalTextureFormat.BC2: return BCn.DecodeBC2(Texture, 0); + case GalTextureFormat.BC3: return BCn.DecodeBC3(Texture, 0); + } + + throw new NotImplementedException(Texture.Format.ToString()); + } + } +}
\ No newline at end of file |
