diff --git a/Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs b/Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs index 8568e8f29..e41f03a43 100644 --- a/Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs +++ b/Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs @@ -538,9 +538,9 @@ namespace Ryujinx.Core.OsHle.Services.Nv { byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, CpuAddr, Size); - NsGpuPBEntry[] PushBuffer = NsGpuPBEntry.DecodePushBuffer(Data); + NsGpuPBEntry[] PushBuffer = NvGpuPushBuffer.Decode(Data); - Context.Ns.Gpu.ProcessPushBuffer(PushBuffer, Context.Memory); + Context.Ns.Gpu.Fifo.PushBuffer(Context.Memory, PushBuffer); } } @@ -680,8 +680,8 @@ namespace Ryujinx.Core.OsHle.Services.Nv } Map.CpuAddress = Addr; - Map.Align = Align; - Map.Kind = Kind; + Map.Align = Align; + Map.Kind = Kind; return 0; } diff --git a/Ryujinx.Core/OsHle/Services/Vi/NvFlinger.cs b/Ryujinx.Core/OsHle/Services/Vi/NvFlinger.cs index 4301c0e6d..45b99ead1 100644 --- a/Ryujinx.Core/OsHle/Services/Vi/NvFlinger.cs +++ b/Ryujinx.Core/OsHle/Services/Vi/NvFlinger.cs @@ -244,12 +244,16 @@ namespace Ryujinx.Core.OsHle.Services.Android { int Slot = ParcelReader.ReadInt32(); - int BufferCount = ParcelReader.ReadInt32(); - long BufferSize = ParcelReader.ReadInt64(); + int BufferCount = ParcelReader.ReadInt32(); - BufferQueue[Slot].State = BufferState.Free; + if (BufferCount > 0) + { + long BufferSize = ParcelReader.ReadInt64(); - BufferQueue[Slot].Data = new GbpBuffer(ParcelReader); + BufferQueue[Slot].State = BufferState.Free; + + BufferQueue[Slot].Data = new GbpBuffer(ParcelReader); + } return MakeReplyParcel(Context, 0); } diff --git a/Ryujinx.Graphics/Gal/GalBlendEquation.cs b/Ryujinx.Graphics/Gal/GalBlendEquation.cs new file mode 100644 index 000000000..d9f8e7993 --- /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 000000000..de7d45fd4 --- /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 000000000..8565051ca --- /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 000000000..7cfb171dc --- /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 000000000..71a50cdba --- /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 000000000..eb5aaf889 --- /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 000000000..fcf1f1ad2 --- /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 000000000..8e9669f00 --- /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 000000000..cf948526a --- /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 000000000..2123ec7d2 --- /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 000000000..b9e5c7658 --- /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 000000000..66e531540 --- /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 dc38c5934..563e624d5 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 aa4eac4e1..99534672d 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 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 b761811f6..7e7725d61 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 000000000..97ff8e13b --- /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 000000000..6518de5fb --- /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 000000000..f9c42ae01 --- /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 000000000..9e0d45233 --- /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 AttribElements = + new Dictionary() + { + { 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 AttribTypes = + new Dictionary() + { + { 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 000000000..6d6ac555d --- /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 TextureUsage { get; private set; } + public IEnumerable UniformUsage { get; private set; } + + public ShaderStage( + GalShaderType Type, + string Code, + IEnumerable TextureUsage, + IEnumerable 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 Stages; + + private Dictionary Programs; + + public int CurrentProgramHandle { get; private set; } + + public OGLShader() + { + Stages = new ConcurrentDictionary(); + + Programs = new Dictionary(); + } + + 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 GetTextureUsage(long Tag) + { + if (Stages.TryGetValue(Tag, out ShaderStage Stage)) + { + return Stage.TextureUsage; + } + + return Enumerable.Empty(); + } + + 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 000000000..559e0eda7 --- /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 002e54dad..0b7bf92ac 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 VertexBuffers; + private OGLShader Shader; - private Texture[] Textures; + private OGLTexture Texture; private ConcurrentQueue ActionsQueue; @@ -31,9 +23,15 @@ namespace Ryujinx.Graphics.Gal.OpenGL public OpenGLRenderer() { - VertexBuffers = new List(); + Blend = new OGLBlend(); - Textures = new Texture[8]; + FrameBuffer = new OGLFrameBuffer(); + + Rasterizer = new OGLRasterizer(); + + Shader = new OGLShader(); + + Texture = new OGLTexture(); ActionsQueue = new ConcurrentQueue(); } @@ -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]; - - Vb.PrimCount = Buffer.Length / Stride; - - VertexBuffers[Index] = Vb; - - 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); - } - - foreach (GalVertexAttrib Attrib in Attribs) - { - 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); - } - - GL.BindVertexArray(0); } - public void SendR8G8B8A8Texture(int Index, byte[] Buffer, int Width, int Height) + public void SetBlend( + GalBlendEquation Equation, + GalBlendFactor FuncSrc, + GalBlendFactor FuncDst) { - 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); + ActionsQueue.Enqueue(() => Blend.Set(Equation, FuncSrc, FuncDst)); } - public void BindTexture(int Index) + public void SetBlendSeparate( + GalBlendEquation EquationRgb, + GalBlendEquation EquationAlpha, + GalBlendFactor FuncSrcRgb, + GalBlendFactor FuncDstRgb, + GalBlendFactor FuncSrcAlpha, + GalBlendFactor FuncDstAlpha) { - GL.ActiveTexture(TextureUnit.Texture0 + Index); - - GL.BindTexture(TextureTarget.Texture2D, Textures[Index].Handle); + ActionsQueue.Enqueue(() => + { + Blend.SetSeparate( + EquationRgb, + EquationAlpha, + FuncSrcRgb, + FuncDstRgb, + FuncSrcAlpha, + FuncDstAlpha); + }); } - private void EnsureVbInitialized(int VbIndex) + public void SetFb(int FbIndex, int Width, int Height) { - while (VbIndex >= VertexBuffers.Count) - { - VertexBuffers.Add(new VertexBuffer()); - } - - VertexBuffer Vb = VertexBuffers[VbIndex]; - - if (Vb.VaoHandle == 0) - { - Vb.VaoHandle = GL.GenVertexArray(); - } - - if (Vb.VboHandle == 0) - { - Vb.VboHandle = GL.GenBuffer(); - } - - VertexBuffers[VbIndex] = Vb; + ActionsQueue.Enqueue(() => FrameBuffer.Set(FbIndex, Width, Height)); } - private void EnsureTexInitialized(int TexIndex) + public void BindFrameBuffer(int FbIndex) { - Texture Tex = Textures[TexIndex]; + ActionsQueue.Enqueue(() => FrameBuffer.Bind(FbIndex)); + } - if (Tex.Handle == 0) + public void DrawFrameBuffer(int FbIndex) + { + ActionsQueue.Enqueue(() => FrameBuffer.Draw(FbIndex)); + } + + public void ClearBuffers(int RtIndex, GalClearBufferFlags Flags) + { + ActionsQueue.Enqueue(() => Rasterizer.ClearBuffers(RtIndex, Flags)); + } + + public void SetVertexArray(int VbIndex, int Stride, byte[] Buffer, GalVertexAttrib[] Attribs) + { + if ((uint)VbIndex > 31) { - Tex.Handle = GL.GenTexture(); + throw new ArgumentOutOfRangeException(nameof(VbIndex)); } - Textures[TexIndex] = Tex; + 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) + { + throw new ArgumentNullException(nameof(Buffer)); + } + + ActionsQueue.Enqueue(() => Rasterizer.SetIndexArray(Buffer, Format)); + } + + public void DrawArrays(int VbIndex, GalPrimitiveType PrimType) + { + if ((uint)VbIndex > 31) + { + throw new ArgumentOutOfRangeException(nameof(VbIndex)); + } + + ActionsQueue.Enqueue(() => Rasterizer.DrawArrays(VbIndex, PrimType)); + } + + public void DrawElements(int VbIndex, int First, GalPrimitiveType PrimType) + { + if ((uint)VbIndex > 31) + { + throw new ArgumentOutOfRangeException(nameof(VbIndex)); + } + + ActionsQueue.Enqueue(() => Rasterizer.DrawElements(VbIndex, First, PrimType)); + } + + public void CreateShader(long Tag, GalShaderType Type, byte[] Data) + { + if (Data == null) + { + throw new ArgumentNullException(nameof(Data)); + } + + Shader.Create(Tag, Type, Data); + } + + public void SetConstBuffer(long Tag, int Cbuf, byte[] Data) + { + if (Data == null) + { + throw new ArgumentNullException(nameof(Data)); + } + + ActionsQueue.Enqueue(() => Shader.SetConstBuffer(Tag, Cbuf, Data)); + } + + public void SetUniform1(string UniformName, int Value) + { + if (UniformName == null) + { + throw new ArgumentNullException(nameof(UniformName)); + } + + ActionsQueue.Enqueue(() => Shader.SetUniform1(UniformName, Value)); + } + + public IEnumerable GetTextureUsage(long Tag) + { + return Shader.GetTextureUsage(Tag); + } + + public void BindShader(long Tag) + { + ActionsQueue.Enqueue(() => Shader.Bind(Tag)); + } + + 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 000000000..898b90b51 --- /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 m_Textures; + + private Dictionary<(int, int), ShaderDeclInfo> m_Uniforms; + + private Dictionary m_InAttributes; + private Dictionary m_OutAttributes; + + private Dictionary m_Gprs; + private Dictionary m_Preds; + + public IReadOnlyDictionary Textures => m_Textures; + + public IReadOnlyDictionary<(int, int), ShaderDeclInfo> Uniforms => m_Uniforms; + + public IReadOnlyDictionary InAttributes => m_InAttributes; + public IReadOnlyDictionary OutAttributes => m_OutAttributes; + + public IReadOnlyDictionary Gprs => m_Gprs; + public IReadOnlyDictionary 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(); + + m_InAttributes = new Dictionary(); + m_OutAttributes = new Dictionary(); + + m_Gprs = new Dictionary(); + m_Preds = new Dictionary(); + + //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 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 000000000..eda70cefa --- /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 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.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 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 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 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 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 000000000..729b6f1de --- /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 Textures { get; private set; } + public IEnumerable Uniforms { get; private set; } + + public GlslProgram( + string Code, + IEnumerable Textures, + IEnumerable 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 000000000..ef0fd78bd --- /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 000000000..5c2f493e4 --- /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 000000000..d3feb92e5 --- /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 000000000..7989570dd --- /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 000000000..fd18ce077 --- /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 000000000..50c740bf9 --- /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 000000000..779bbf923 --- /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 000000000..00f8f6a5e --- /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 000000000..1a96d3be9 --- /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 Nodes; + + public ShaderIrBlock() + { + Nodes = new List(); + } + + 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 000000000..d8c87b490 --- /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 000000000..b6f4e80b9 --- /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 000000000..2648164a1 --- /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 000000000..cd2107570 --- /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 000000000..fa612de76 --- /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 000000000..f22720563 --- /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 000000000..5c69d6a67 --- /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 000000000..ba2c2c9b2 --- /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 000000000..3c27e4836 --- /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 000000000..74cca0efe --- /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 000000000..48c3b2ee5 --- /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 000000000..7989deed1 --- /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 000000000..69457aebd --- /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 Sites; + + public RegUse() + { + Sites = new List(); + } + + 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 Nodes, GalShaderType ShaderType) + { + Dictionary Uses = new Dictionary(); + + 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 000000000..d400850c8 --- /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 000000000..9bc87ff3d --- /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/Gpu/BCn.cs b/Ryujinx.Graphics/Gal/Texture/BCn.cs similarity index 85% rename from Ryujinx.Graphics/Gpu/BCn.cs rename to Ryujinx.Graphics/Gal/Texture/BCn.cs index b1caf4675..f23a86c2c 100644 --- a/Ryujinx.Graphics/Gpu/BCn.cs +++ b/Ryujinx.Graphics/Gal/Texture/BCn.cs @@ -1,14 +1,14 @@ using System; using System.Drawing; -namespace Ryujinx.Graphics.Gpu +namespace Ryujinx.Graphics.Gal.Texture { static class BCn { - public static byte[] DecodeBC1(NsGpuTexture Tex, int Offset) + public static byte[] DecodeBC1(GalTexture Texture, int Offset) { - int W = (Tex.Width + 3) / 4; - int H = (Tex.Height + 3) / 4; + int W = (Texture.Width + 3) / 4; + int H = (Texture.Height + 3) / 4; byte[] Output = new byte[W * H * 64]; @@ -20,7 +20,7 @@ namespace Ryujinx.Graphics.Gpu { int IOffs = Offset + Swizzle.GetSwizzledAddress64(X, Y) * 8; - byte[] Tile = BCnDecodeTile(Tex.Data, IOffs, true); + byte[] Tile = BCnDecodeTile(Texture.Data, IOffs, true); int TOffset = 0; @@ -44,10 +44,10 @@ namespace Ryujinx.Graphics.Gpu return Output; } - public static byte[] DecodeBC2(NsGpuTexture Tex, int Offset) + public static byte[] DecodeBC2(GalTexture Texture, int Offset) { - int W = (Tex.Width + 3) / 4; - int H = (Tex.Height + 3) / 4; + int W = (Texture.Width + 3) / 4; + int H = (Texture.Height + 3) / 4; byte[] Output = new byte[W * H * 64]; @@ -59,10 +59,10 @@ namespace Ryujinx.Graphics.Gpu { int IOffs = Offset + Swizzle.GetSwizzledAddress128(X, Y) * 16; - byte[] Tile = BCnDecodeTile(Tex.Data, IOffs + 8, false); + byte[] Tile = BCnDecodeTile(Texture.Data, IOffs + 8, false); - int AlphaLow = Get32(Tex.Data, IOffs + 0); - int AlphaHigh = Get32(Tex.Data, IOffs + 4); + int AlphaLow = Get32(Texture.Data, IOffs + 0); + int AlphaHigh = Get32(Texture.Data, IOffs + 4); ulong AlphaCh = (uint)AlphaLow | (ulong)AlphaHigh << 32; @@ -90,10 +90,10 @@ namespace Ryujinx.Graphics.Gpu return Output; } - public static byte[] DecodeBC3(NsGpuTexture Tex, int Offset) + public static byte[] DecodeBC3(GalTexture Texture, int Offset) { - int W = (Tex.Width + 3) / 4; - int H = (Tex.Height + 3) / 4; + int W = (Texture.Width + 3) / 4; + int H = (Texture.Height + 3) / 4; byte[] Output = new byte[W * H * 64]; @@ -105,17 +105,17 @@ namespace Ryujinx.Graphics.Gpu { int IOffs = Offset + Swizzle.GetSwizzledAddress128(X, Y) * 16; - byte[] Tile = BCnDecodeTile(Tex.Data, IOffs + 8, false); + byte[] Tile = BCnDecodeTile(Texture.Data, IOffs + 8, false); byte[] Alpha = new byte[8]; - Alpha[0] = Tex.Data[IOffs + 0]; - Alpha[1] = Tex.Data[IOffs + 1]; + Alpha[0] = Texture.Data[IOffs + 0]; + Alpha[1] = Texture.Data[IOffs + 1]; CalculateBC3Alpha(Alpha); - int AlphaLow = Get32(Tex.Data, IOffs + 2); - int AlphaHigh = Get16(Tex.Data, IOffs + 6); + int AlphaLow = Get32(Texture.Data, IOffs + 2); + int AlphaHigh = Get16(Texture.Data, IOffs + 6); ulong AlphaCh = (uint)AlphaLow | (ulong)AlphaHigh << 32; @@ -143,10 +143,10 @@ namespace Ryujinx.Graphics.Gpu return Output; } - public static byte[] DecodeBC4(NsGpuTexture Tex, int Offset) + public static byte[] DecodeBC4(GalTexture Texture, int Offset) { - int W = (Tex.Width + 3) / 4; - int H = (Tex.Height + 3) / 4; + int W = (Texture.Width + 3) / 4; + int H = (Texture.Height + 3) / 4; byte[] Output = new byte[W * H * 64]; @@ -160,13 +160,13 @@ namespace Ryujinx.Graphics.Gpu byte[] Red = new byte[8]; - Red[0] = Tex.Data[IOffs + 0]; - Red[1] = Tex.Data[IOffs + 1]; + Red[0] = Texture.Data[IOffs + 0]; + Red[1] = Texture.Data[IOffs + 1]; CalculateBC3Alpha(Red); - int RedLow = Get32(Tex.Data, IOffs + 2); - int RedHigh = Get16(Tex.Data, IOffs + 6); + int RedLow = Get32(Texture.Data, IOffs + 2); + int RedHigh = Get16(Texture.Data, IOffs + 6); ulong RedCh = (uint)RedLow | (ulong)RedHigh << 32; @@ -194,10 +194,10 @@ namespace Ryujinx.Graphics.Gpu return Output; } - public static byte[] DecodeBC5(NsGpuTexture Tex, int Offset, bool SNorm) + public static byte[] DecodeBC5(GalTexture Texture, int Offset, bool SNorm) { - int W = (Tex.Width + 3) / 4; - int H = (Tex.Height + 3) / 4; + int W = (Texture.Width + 3) / 4; + int H = (Texture.Height + 3) / 4; byte[] Output = new byte[W * H * 64]; @@ -212,11 +212,11 @@ namespace Ryujinx.Graphics.Gpu byte[] Red = new byte[8]; byte[] Green = new byte[8]; - Red[0] = Tex.Data[IOffs + 0]; - Red[1] = Tex.Data[IOffs + 1]; + Red[0] = Texture.Data[IOffs + 0]; + Red[1] = Texture.Data[IOffs + 1]; - Green[0] = Tex.Data[IOffs + 8]; - Green[1] = Tex.Data[IOffs + 9]; + Green[0] = Texture.Data[IOffs + 8]; + Green[1] = Texture.Data[IOffs + 9]; if (SNorm) { @@ -229,11 +229,11 @@ namespace Ryujinx.Graphics.Gpu CalculateBC3Alpha(Green); } - int RedLow = Get32(Tex.Data, IOffs + 2); - int RedHigh = Get16(Tex.Data, IOffs + 6); + int RedLow = Get32(Texture.Data, IOffs + 2); + int RedHigh = Get16(Texture.Data, IOffs + 6); - int GreenLow = Get32(Tex.Data, IOffs + 10); - int GreenHigh = Get16(Tex.Data, IOffs + 14); + 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; diff --git a/Ryujinx.Graphics/Gpu/SwizzleAddr.cs b/Ryujinx.Graphics/Gal/Texture/SwizzleAddr.cs similarity index 98% rename from Ryujinx.Graphics/Gpu/SwizzleAddr.cs rename to Ryujinx.Graphics/Gal/Texture/SwizzleAddr.cs index 08e61eb58..b67b841bc 100644 --- a/Ryujinx.Graphics/Gpu/SwizzleAddr.cs +++ b/Ryujinx.Graphics/Gal/Texture/SwizzleAddr.cs @@ -1,6 +1,6 @@ using System; -namespace Ryujinx.Graphics.Gpu +namespace Ryujinx.Graphics.Gal.Texture { class SwizzleAddr { @@ -109,7 +109,7 @@ namespace Ryujinx.Graphics.Gpu * 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; diff --git a/Ryujinx.Graphics/Gal/Texture/TextureDecoder.cs b/Ryujinx.Graphics/Gal/Texture/TextureDecoder.cs new file mode 100644 index 000000000..4e50db51d --- /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 diff --git a/Ryujinx.Graphics/Gpu/BlockLinearSwizzle.cs b/Ryujinx.Graphics/Gpu/BlockLinearSwizzle.cs new file mode 100644 index 000000000..d2cbb1443 --- /dev/null +++ b/Ryujinx.Graphics/Gpu/BlockLinearSwizzle.cs @@ -0,0 +1,57 @@ +namespace Ryujinx.Graphics.Gpu +{ + class BlockLinearSwizzle : ISwizzle + { + private int BhShift; + private int BppShift; + private int BhMask; + + private int XShift; + private int GobStride; + + public BlockLinearSwizzle(int Width, int Bpp, int BlockHeight = 16) + { + BhMask = (BlockHeight * 8) - 1; + + BhShift = CountLsbZeros(BlockHeight * 8); + BppShift = CountLsbZeros(Bpp); + + int WidthInGobs = Width * Bpp / 64; + + GobStride = 512 * BlockHeight * WidthInGobs; + + XShift = CountLsbZeros(512 * BlockHeight); + } + + private int CountLsbZeros(int Value) + { + int Count = 0; + + while (((Value >> Count) & 1) == 0) + { + Count++; + } + + return Count; + } + + public int GetSwizzleOffset(int X, int Y) + { + X <<= BppShift; + + int Position = (Y >> BhShift) * GobStride; + + Position += (X >> 6) << XShift; + + Position += ((Y & BhMask) >> 3) << 9; + + Position += ((X & 0x3f) >> 5) << 8; + Position += ((Y & 0x07) >> 1) << 6; + Position += ((X & 0x1f) >> 4) << 5; + Position += ((Y & 0x01) >> 0) << 4; + Position += ((X & 0x0f) >> 0) << 0; + + return Position; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/INvGpuEngine.cs b/Ryujinx.Graphics/Gpu/INvGpuEngine.cs new file mode 100644 index 000000000..17e9b435c --- /dev/null +++ b/Ryujinx.Graphics/Gpu/INvGpuEngine.cs @@ -0,0 +1,11 @@ +using ChocolArm64.Memory; + +namespace Ryujinx.Graphics.Gpu +{ + interface INvGpuEngine + { + int[] Registers { get; } + + void CallMethod(AMemory Memory, NsGpuPBEntry PBEntry); + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/ISwizzle.cs b/Ryujinx.Graphics/Gpu/ISwizzle.cs new file mode 100644 index 000000000..755051d0c --- /dev/null +++ b/Ryujinx.Graphics/Gpu/ISwizzle.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.Gpu +{ + interface ISwizzle + { + int GetSwizzleOffset(int X, int Y); + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/LinearSwizzle.cs b/Ryujinx.Graphics/Gpu/LinearSwizzle.cs new file mode 100644 index 000000000..01f09f816 --- /dev/null +++ b/Ryujinx.Graphics/Gpu/LinearSwizzle.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.Gpu +{ + class LinearSwizzle : ISwizzle + { + private int Bpp; + private int Stride; + + public LinearSwizzle(int Width, int Bpp) + { + this.Bpp = Bpp; + + Stride = Width * Bpp; + } + + public int GetSwizzleOffset(int X, int Y) + { + return X * Bpp + Y * Stride; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/MacroInterpreter.cs b/Ryujinx.Graphics/Gpu/MacroInterpreter.cs new file mode 100644 index 000000000..233baac8b --- /dev/null +++ b/Ryujinx.Graphics/Gpu/MacroInterpreter.cs @@ -0,0 +1,420 @@ +using ChocolArm64.Memory; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu +{ + class MacroInterpreter + { + private enum AssignmentOperation + { + IgnoreAndFetch = 0, + Move = 1, + MoveAndSetMaddr = 2, + FetchAndSend = 3, + MoveAndSend = 4, + FetchAndSetMaddr = 5, + MoveAndSetMaddrThenFetchAndSend = 6, + MoveAndSetMaddrThenSendHigh = 7 + } + + private enum AluOperation + { + AluReg = 0, + AddImmediate = 1, + BitfieldReplace = 2, + BitfieldExtractLslImm = 3, + BitfieldExtractLslReg = 4, + ReadImmediate = 5 + } + + private enum AluRegOperation + { + Add = 0, + AddWithCarry = 1, + Subtract = 2, + SubtractWithBorrow = 3, + BitwiseExclusiveOr = 8, + BitwiseOr = 9, + BitwiseAnd = 10, + BitwiseAndNot = 11, + BitwiseNotAnd = 12 + } + + private NvGpuFifo PFifo; + private INvGpuEngine Engine; + + public Queue Fifo { get; private set; } + + private int[] Gprs; + + private int MethAddr; + private int MethIncr; + + private bool Carry; + + private int OpCode; + + private int PipeOp; + + private long Pc; + + public MacroInterpreter(NvGpuFifo PFifo, INvGpuEngine Engine) + { + this.PFifo = PFifo; + this.Engine = Engine; + + Fifo = new Queue(); + + Gprs = new int[8]; + } + + public void Execute(AMemory Memory, long Position, int Param) + { + Reset(); + + Gprs[1] = Param; + + Pc = Position; + + FetchOpCode(Memory); + + while (Step(Memory)); + + //Due to the delay slot, we still need to execute + //one more instruction before we actually exit. + Step(Memory); + } + + private void Reset() + { + for (int Index = 0; Index < Gprs.Length; Index++) + { + Gprs[Index] = 0; + } + + MethAddr = 0; + MethIncr = 0; + + Carry = false; + } + + private bool Step(AMemory Memory) + { + long BaseAddr = Pc - 4; + + FetchOpCode(Memory); + + if ((OpCode & 7) < 7) + { + //Operation produces a value. + AssignmentOperation AsgOp = (AssignmentOperation)((OpCode >> 4) & 7); + + int Result = GetAluResult(); + + switch (AsgOp) + { + //Fetch parameter and ignore result. + case AssignmentOperation.IgnoreAndFetch: + { + SetDstGpr(FetchParam()); + + break; + } + + //Move result. + case AssignmentOperation.Move: + { + SetDstGpr(Result); + + break; + } + + //Move result and use as Method Address. + case AssignmentOperation.MoveAndSetMaddr: + { + SetDstGpr(Result); + + SetMethAddr(Result); + + break; + } + + //Fetch parameter and send result. + case AssignmentOperation.FetchAndSend: + { + SetDstGpr(FetchParam()); + + Send(Memory, Result); + + break; + } + + //Move and send result. + case AssignmentOperation.MoveAndSend: + { + SetDstGpr(Result); + + Send(Memory, Result); + + break; + } + + //Fetch parameter and use result as Method Address. + case AssignmentOperation.FetchAndSetMaddr: + { + SetDstGpr(FetchParam()); + + SetMethAddr(Result); + + break; + } + + //Move result and use as Method Address, then fetch and send paramter. + case AssignmentOperation.MoveAndSetMaddrThenFetchAndSend: + { + SetDstGpr(Result); + + SetMethAddr(Result); + + Send(Memory, FetchParam()); + + break; + } + + //Move result and use as Method Address, then send bits 17:12 of result. + case AssignmentOperation.MoveAndSetMaddrThenSendHigh: + { + SetDstGpr(Result); + + SetMethAddr(Result); + + Send(Memory, (Result >> 12) & 0x3f); + + break; + } + } + } + else + { + //Branch. + bool OnNotZero = ((OpCode >> 4) & 1) != 0; + + bool Taken = OnNotZero + ? GetGprA() != 0 + : GetGprA() == 0; + + if (Taken) + { + Pc = BaseAddr + (GetImm() << 2); + + bool NoDelays = (OpCode & 0x20) != 0; + + if (NoDelays) + { + FetchOpCode(Memory); + } + + return true; + } + } + + bool Exit = (OpCode & 0x80) != 0; + + return !Exit; + } + + private void FetchOpCode(AMemory Memory) + { + OpCode = PipeOp; + + PipeOp = Memory.ReadInt32(Pc); + + Pc += 4; + } + + private int GetAluResult() + { + AluOperation Op = (AluOperation)(OpCode & 7); + + switch (Op) + { + case AluOperation.AluReg: + { + AluRegOperation AluOp = (AluRegOperation)((OpCode >> 17) & 0x1f); + + return GetAluResult(AluOp, GetGprA(), GetGprB()); + } + + case AluOperation.AddImmediate: + { + return GetGprA() + GetImm(); + } + + case AluOperation.BitfieldReplace: + case AluOperation.BitfieldExtractLslImm: + case AluOperation.BitfieldExtractLslReg: + { + int BfSrcBit = (OpCode >> 17) & 0x1f; + int BfSize = (OpCode >> 22) & 0x1f; + int BfDstBit = (OpCode >> 27) & 0x1f; + + int BfMask = (1 << BfSize) - 1; + + int Dst = GetGprA(); + int Src = GetGprB(); + + switch (Op) + { + case AluOperation.BitfieldReplace: + { + Src = (int)((uint)Src >> BfSrcBit) & BfMask; + + Dst &= ~(BfMask << BfDstBit); + + Dst |= Src << BfDstBit; + + return Dst; + } + + case AluOperation.BitfieldExtractLslImm: + { + Src = (int)((uint)Src >> Dst) & BfMask; + + return Src << BfDstBit; + } + + case AluOperation.BitfieldExtractLslReg: + { + Src = (int)((uint)Src >> BfSrcBit) & BfMask; + + return Src << Dst; + } + } + + break; + } + + case AluOperation.ReadImmediate: + { + return Read(GetGprA() + GetImm()); + } + } + + throw new ArgumentException(nameof(OpCode)); + } + + private int GetAluResult(AluRegOperation AluOp, int A, int B) + { + switch (AluOp) + { + case AluRegOperation.Add: + { + ulong Result = (ulong)A + (ulong)B; + + Carry = Result > 0xffffffff; + + return (int)Result; + } + + case AluRegOperation.AddWithCarry: + { + ulong Result = (ulong)A + (ulong)B + (Carry ? 1UL : 0UL); + + Carry = Result > 0xffffffff; + + return (int)Result; + } + + case AluRegOperation.Subtract: + { + ulong Result = (ulong)A - (ulong)B; + + Carry = Result < 0x100000000; + + return (int)Result; + } + + case AluRegOperation.SubtractWithBorrow: + { + ulong Result = (ulong)A - (ulong)B - (Carry ? 0UL : 1UL); + + Carry = Result < 0x100000000; + + return (int)Result; + } + + case AluRegOperation.BitwiseExclusiveOr: return A ^ B; + case AluRegOperation.BitwiseOr: return A | B; + case AluRegOperation.BitwiseAnd: return A & B; + case AluRegOperation.BitwiseAndNot: return A & ~B; + case AluRegOperation.BitwiseNotAnd: return ~(A & B); + } + + throw new ArgumentOutOfRangeException(nameof(AluOp)); + } + + private int GetImm() + { + //Note: The immediate is signed, the sign-extension is intended here. + return OpCode >> 14; + } + + private void SetMethAddr(int Value) + { + MethAddr = (Value >> 0) & 0xfff; + MethIncr = (Value >> 12) & 0x3f; + } + + private void SetDstGpr(int Value) + { + Gprs[(OpCode >> 8) & 7] = Value; + } + + private int GetGprA() + { + return GetGprValue((OpCode >> 11) & 7); + } + + private int GetGprB() + { + return GetGprValue((OpCode >> 14) & 7); + } + + private int GetGprValue(int Index) + { + return Index != 0 ? Gprs[Index] : 0; + } + + private int FetchParam() + { + int Value; + + //If we don't have any parameters in the FIFO, + //keep running the PFIFO engine until it writes the parameters. + while (!Fifo.TryDequeue(out Value)) + { + if (!PFifo.Step()) + { + return 0; + } + } + + return Value; + } + + private int Read(int Reg) + { + return Engine.Registers[Reg]; + } + + private void Send(AMemory Memory, int Value) + { + NsGpuPBEntry PBEntry = new NsGpuPBEntry(MethAddr, 0, Value); + + Engine.CallMethod(Memory, PBEntry); + + MethAddr += MethIncr; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NsGpu.cs b/Ryujinx.Graphics/Gpu/NsGpu.cs index 133d0af25..573805025 100644 --- a/Ryujinx.Graphics/Gpu/NsGpu.cs +++ b/Ryujinx.Graphics/Gpu/NsGpu.cs @@ -1,5 +1,5 @@ -using ChocolArm64.Memory; using Ryujinx.Graphics.Gal; +using System.Threading; namespace Ryujinx.Graphics.Gpu { @@ -9,7 +9,13 @@ namespace Ryujinx.Graphics.Gpu internal NsGpuMemoryMgr MemoryMgr { get; private set; } - internal NsGpuPGraph PGraph { get; private set; } + public NvGpuFifo Fifo; + + internal NvGpuEngine3d Engine3d; + + private Thread FifoProcessing; + + private bool KeepRunning; public NsGpu(IGalRenderer Renderer) { @@ -17,7 +23,15 @@ namespace Ryujinx.Graphics.Gpu MemoryMgr = new NsGpuMemoryMgr(); - PGraph = new NsGpuPGraph(this); + Fifo = new NvGpuFifo(this); + + Engine3d = new NvGpuEngine3d(this); + + KeepRunning = true; + + FifoProcessing = new Thread(ProcessFifo); + + FifoProcessing.Start(); } public long GetCpuAddr(long Position) @@ -35,11 +49,6 @@ namespace Ryujinx.Graphics.Gpu return MemoryMgr.Map(CpuAddr, GpuAddr, Size); } - public void ProcessPushBuffer(NsGpuPBEntry[] PushBuffer, AMemory Memory) - { - PGraph.ProcessPushBuffer(PushBuffer, Memory); - } - public long ReserveMemory(long Size, long Align) { return MemoryMgr.Reserve(Size, Align); @@ -49,5 +58,15 @@ namespace Ryujinx.Graphics.Gpu { return MemoryMgr.Reserve(GpuAddr, Size, Align); } + + private void ProcessFifo() + { + while (KeepRunning) + { + Fifo.DispatchCalls(); + + Thread.Yield(); + } + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NsGpuPBEntry.cs b/Ryujinx.Graphics/Gpu/NsGpuPBEntry.cs index 8063651aa..d405a93c6 100644 --- a/Ryujinx.Graphics/Gpu/NsGpuPBEntry.cs +++ b/Ryujinx.Graphics/Gpu/NsGpuPBEntry.cs @@ -1,13 +1,11 @@ using System; -using System.Collections.Generic; using System.Collections.ObjectModel; -using System.IO; namespace Ryujinx.Graphics.Gpu { public struct NsGpuPBEntry { - public NsGpuRegister Register { get; private set; } + public int Method { get; private set; } public int SubChannel { get; private set; } @@ -15,65 +13,11 @@ namespace Ryujinx.Graphics.Gpu public ReadOnlyCollection Arguments => Array.AsReadOnly(m_Arguments); - public NsGpuPBEntry(NsGpuRegister Register, int SubChannel, params int[] Arguments) + public NsGpuPBEntry(int Method, int SubChannel, params int[] Arguments) { - this.Register = Register; + this.Method = Method; this.SubChannel = SubChannel; this.m_Arguments = Arguments; } - - public static NsGpuPBEntry[] DecodePushBuffer(byte[] Data) - { - using (MemoryStream MS = new MemoryStream(Data)) - { - BinaryReader Reader = new BinaryReader(MS); - - List GpFifos = new List(); - - bool CanRead() => MS.Position + 4 <= MS.Length; - - while (CanRead()) - { - int Packed = Reader.ReadInt32(); - - int Reg = (Packed << 2) & 0x7ffc; - int SubC = (Packed >> 13) & 7; - int Args = (Packed >> 16) & 0x1fff; - int Mode = (Packed >> 29) & 7; - - if (Mode == 4) - { - //Inline Mode. - GpFifos.Add(new NsGpuPBEntry((NsGpuRegister)Reg, SubC, Args)); - } - else - { - //Word mode. - if (Mode == 1) - { - //Sequential Mode. - for (int Index = 0; Index < Args && CanRead(); Index++, Reg += 4) - { - GpFifos.Add(new NsGpuPBEntry((NsGpuRegister)Reg, SubC, Reader.ReadInt32())); - } - } - else - { - //Non-Sequential Mode. - int[] Arguments = new int[Args]; - - for (int Index = 0; Index < Args && CanRead(); Index++) - { - Arguments[Index] = Reader.ReadInt32(); - } - - GpFifos.Add(new NsGpuPBEntry((NsGpuRegister)Reg, SubC, Arguments)); - } - } - } - - return GpFifos.ToArray(); - } - } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NsGpuPGraph.cs b/Ryujinx.Graphics/Gpu/NsGpuPGraph.cs deleted file mode 100644 index 652f3e751..000000000 --- a/Ryujinx.Graphics/Gpu/NsGpuPGraph.cs +++ /dev/null @@ -1,305 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.Graphics.Gal; -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gpu -{ - class NsGpuPGraph - { - private NsGpu Gpu; - - private uint[] Registers; - - public NsGpuEngine[] SubChannels; - - private Dictionary CurrentVertexBuffers; - - public NsGpuPGraph(NsGpu Gpu) - { - this.Gpu = Gpu; - - Registers = new uint[0x1000]; - - SubChannels = new NsGpuEngine[8]; - - CurrentVertexBuffers = new Dictionary(); - } - - public void ProcessPushBuffer(NsGpuPBEntry[] PushBuffer, AMemory Memory) - { - bool HasQuery = false; - - foreach (NsGpuPBEntry Entry in PushBuffer) - { - if (Entry.Arguments.Count == 1) - { - SetRegister(Entry.Register, (uint)Entry.Arguments[0]); - } - - switch (Entry.Register) - { - case NsGpuRegister.BindChannel: - if (Entry.Arguments.Count > 0) - { - SubChannels[Entry.SubChannel] = (NsGpuEngine)Entry.Arguments[0]; - } - break; - - case NsGpuRegister._3dVertexArray0Fetch: - SendVertexBuffers(Memory); - break; - - case NsGpuRegister._3dCbData0: - if (GetRegister(NsGpuRegister._3dCbPos) == 0x20) - { - SendTexture(Memory); - } - break; - - case NsGpuRegister._3dQueryAddressHigh: - case NsGpuRegister._3dQueryAddressLow: - case NsGpuRegister._3dQuerySequence: - case NsGpuRegister._3dQueryGet: - HasQuery = true; - break; - - case NsGpuRegister._3dSetShader: - uint ShaderPrg = (uint)Entry.Arguments[0]; - uint ShaderId = (uint)Entry.Arguments[1]; - uint CodeAddr = (uint)Entry.Arguments[2]; - uint ShaderType = (uint)Entry.Arguments[3]; - uint CodeEnd = (uint)Entry.Arguments[4]; - - SendShader( - Memory, - ShaderPrg, - ShaderId, - CodeAddr, - ShaderType, - CodeEnd); - break; - } - } - - if (HasQuery) - { - long Position = - (long)GetRegister(NsGpuRegister._3dQueryAddressHigh) << 32 | - (long)GetRegister(NsGpuRegister._3dQueryAddressLow) << 0; - - uint Seq = GetRegister(NsGpuRegister._3dQuerySequence); - uint Get = GetRegister(NsGpuRegister._3dQueryGet); - - uint Mode = Get & 3; - - if (Mode == 0) - { - //Write - Position = Gpu.MemoryMgr.GetCpuAddr(Position); - - if (Position != -1) - { - Gpu.Renderer.QueueAction(delegate() - { - Memory.WriteUInt32(Position, Seq); - }); - } - } - } - } - - private void SendVertexBuffers(AMemory Memory) - { - long Position = - (long)GetRegister(NsGpuRegister._3dVertexArray0StartHigh) << 32 | - (long)GetRegister(NsGpuRegister._3dVertexArray0StartLow) << 0; - - long Limit = - (long)GetRegister(NsGpuRegister._3dVertexArray0LimitHigh) << 32 | - (long)GetRegister(NsGpuRegister._3dVertexArray0LimitLow) << 0; - - int VbIndex = CurrentVertexBuffers.Count; - - if (!CurrentVertexBuffers.TryAdd(Position, VbIndex)) - { - VbIndex = CurrentVertexBuffers[Position]; - } - - if (Limit != 0) - { - long Size = (Limit - Position) + 1; - - Position = Gpu.MemoryMgr.GetCpuAddr(Position); - - if (Position != -1) - { - byte[] Buffer = AMemoryHelper.ReadBytes(Memory, Position, Size); - - int Stride = (int)GetRegister(NsGpuRegister._3dVertexArray0Fetch) & 0xfff; - - List Attribs = new List(); - - for (int Attr = 0; Attr < 16; Attr++) - { - int Packed = (int)GetRegister(NsGpuRegister._3dVertexAttrib0Format + Attr * 4); - - GalVertexAttrib Attrib = new GalVertexAttrib(Attr, - (Packed >> 0) & 0x1f, - ((Packed >> 6) & 0x1) != 0, - (Packed >> 7) & 0x3fff, - (GalVertexAttribSize)((Packed >> 21) & 0x3f), - (GalVertexAttribType)((Packed >> 27) & 0x7), - ((Packed >> 31) & 0x1) != 0); - - if (Attrib.Offset < Stride) - { - Attribs.Add(Attrib); - } - } - - Gpu.Renderer.QueueAction(delegate() - { - Gpu.Renderer.SendVertexBuffer(VbIndex, Buffer, Stride, Attribs.ToArray()); - }); - } - } - } - - private void SendTexture(AMemory Memory) - { - long TicPos = (long)GetRegister(NsGpuRegister._3dTicAddressHigh) << 32 | - (long)GetRegister(NsGpuRegister._3dTicAddressLow) << 0; - - uint CbData = GetRegister(NsGpuRegister._3dCbData0); - - uint TicIndex = (CbData >> 0) & 0xfffff; - uint TscIndex = (CbData >> 20) & 0xfff; //I guess? - - TicPos = Gpu.MemoryMgr.GetCpuAddr(TicPos + TicIndex * 0x20); - - if (TicPos != -1) - { - int Word0 = Memory.ReadInt32(TicPos + 0x0); - int Word1 = Memory.ReadInt32(TicPos + 0x4); - int Word2 = Memory.ReadInt32(TicPos + 0x8); - int Word3 = Memory.ReadInt32(TicPos + 0xc); - int Word4 = Memory.ReadInt32(TicPos + 0x10); - int Word5 = Memory.ReadInt32(TicPos + 0x14); - int Word6 = Memory.ReadInt32(TicPos + 0x18); - int Word7 = Memory.ReadInt32(TicPos + 0x1c); - - long TexAddress = Word1; - - TexAddress |= (long)(Word2 & 0xff) << 32; - - TexAddress = Gpu.MemoryMgr.GetCpuAddr(TexAddress); - - if (TexAddress != -1) - { - NsGpuTextureFormat Format = (NsGpuTextureFormat)(Word0 & 0x7f); - - int Width = (Word4 & 0xffff) + 1; - int Height = (Word5 & 0xffff) + 1; - - byte[] Buffer = GetDecodedTexture(Memory, Format, TexAddress, Width, Height); - - if (Buffer != null) - { - Gpu.Renderer.QueueAction(delegate() - { - Gpu.Renderer.SendR8G8B8A8Texture(0, Buffer, Width, Height); - }); - } - } - } - } - - private void SendShader( - AMemory Memory, - uint ShaderPrg, - uint ShaderId, - uint CodeAddr, - uint ShaderType, - uint CodeEnd) - { - long CodePos = Gpu.MemoryMgr.GetCpuAddr(CodeAddr); - - byte[] Data = AMemoryHelper.ReadBytes(Memory, CodePos, 0x300); - } - - private static byte[] GetDecodedTexture( - AMemory Memory, - NsGpuTextureFormat Format, - long Position, - int Width, - int Height) - { - byte[] Data = null; - - switch (Format) - { - case NsGpuTextureFormat.BC1: - { - int Size = (Width * Height) >> 1; - - Data = AMemoryHelper.ReadBytes(Memory, Position, Size); - - Data = BCn.DecodeBC1(new NsGpuTexture() - { - Width = Width, - Height = Height, - Data = Data - }, 0); - - break; - } - - case NsGpuTextureFormat.BC2: - { - int Size = Width * Height; - - Data = AMemoryHelper.ReadBytes(Memory, Position, Size); - - Data = BCn.DecodeBC2(new NsGpuTexture() - { - Width = Width, - Height = Height, - Data = Data - }, 0); - - break; - } - - case NsGpuTextureFormat.BC3: - { - int Size = Width * Height; - - Data = AMemoryHelper.ReadBytes(Memory, Position, Size); - - Data = BCn.DecodeBC3(new NsGpuTexture() - { - Width = Width, - Height = Height, - Data = Data - }, 0); - - break; - } - - //default: throw new NotImplementedException(Format.ToString()); - } - - return Data; - } - - public uint GetRegister(NsGpuRegister Register) - { - return Registers[((int)Register >> 2) & 0xfff]; - } - - public void SetRegister(NsGpuRegister Register, uint Value) - { - Registers[((int)Register >> 2) & 0xfff] = Value; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NsGpuRegister.cs b/Ryujinx.Graphics/Gpu/NsGpuRegister.cs deleted file mode 100644 index 4642e68d6..000000000 --- a/Ryujinx.Graphics/Gpu/NsGpuRegister.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace Ryujinx.Graphics.Gpu -{ - public enum NsGpuRegister - { - BindChannel = 0, - - _2dClipEnable = 0x0290, - _2dOperation = 0x02ac, - - _3dGlobalBase = 0x02c8, - _3dRt0AddressHigh = 0x0800, - _3dRt0AddressLow = 0x0804, - _3dRt0Horiz = 0x0808, - _3dRt0Vert = 0x080c, - _3dRt0Format = 0x0810, - _3dRt0BlockDimensions = 0x0814, - _3dRt0ArrayMode = 0x0818, - _3dRt0LayerStride = 0x081c, - _3dRt0BaseLayer = 0x0820, - _3dViewportScaleX = 0x0a00, - _3dViewportScaleY = 0x0a04, - _3dViewportScaleZ = 0x0a08, - _3dViewportTranslateX = 0x0a0c, - _3dViewportTranslateY = 0x0a10, - _3dViewportTranslateZ = 0x0a14, - _3dViewportHoriz = 0x0c00, - _3dViewportVert = 0x0c04, - _3dDepthRangeNear = 0x0c08, - _3dDepthRangeFar = 0x0c0c, - _3dClearColorR = 0x0d80, - _3dClearColorG = 0x0d84, - _3dClearColorB = 0x0d88, - _3dClearColorA = 0x0d8c, - _3dScreenScissorHoriz = 0x0ff4, - _3dScreenScissorVert = 0x0ff8, - _3dVertexAttrib0Format = 0x1160, - _3dVertexAttrib1Format = 0x1164, - _3dVertexAttrib2Format = 0x1168, - _3dVertexAttrib3Format = 0x116c, - _3dVertexAttrib4Format = 0x1170, - _3dVertexAttrib5Format = 0x1174, - _3dVertexAttrib6Format = 0x1178, - _3dVertexAttrib7Format = 0x117c, - _3dVertexAttrib8Format = 0x1180, - _3dVertexAttrib9Format = 0x1184, - _3dVertexAttrib10Format = 0x1188, - _3dVertexAttrib11Format = 0x118c, - _3dVertexAttrib12Format = 0x1190, - _3dVertexAttrib13Format = 0x1194, - _3dVertexAttrib14Format = 0x1198, - _3dVertexAttrib15Format = 0x119c, - _3dScreenYControl = 0x13ac, - _3dTscAddressHigh = 0x155c, - _3dTscAddressLow = 0x1560, - _3dTscLimit = 0x1564, - _3dTicAddressHigh = 0x1574, - _3dTicAddressLow = 0x1578, - _3dTicLimit = 0x157c, - _3dMultiSampleMode = 0x15d0, - _3dVertexEndGl = 0x1614, - _3dVertexBeginGl = 0x1618, - _3dQueryAddressHigh = 0x1b00, - _3dQueryAddressLow = 0x1b04, - _3dQuerySequence = 0x1b08, - _3dQueryGet = 0x1b0c, - _3dVertexArray0Fetch = 0x1c00, - _3dVertexArray0StartHigh = 0x1c04, - _3dVertexArray0StartLow = 0x1c08, - _3dVertexArray1Fetch = 0x1c10, //todo: the rest - _3dVertexArray0LimitHigh = 0x1f00, - _3dVertexArray0LimitLow = 0x1f04, - _3dCbSize = 0x2380, - _3dCbAddressHigh = 0x2384, - _3dCbAddressLow = 0x2388, - _3dCbPos = 0x238c, - _3dCbData0 = 0x2390, - _3dCbData1 = 0x2394, - _3dCbData2 = 0x2398, - _3dCbData3 = 0x239c, - _3dCbData4 = 0x23a0, - _3dCbData5 = 0x23a4, - _3dCbData6 = 0x23a8, - _3dCbData7 = 0x23ac, - _3dCbData8 = 0x23b0, - _3dCbData9 = 0x23b4, - _3dCbData10 = 0x23b8, - _3dCbData11 = 0x23bc, - _3dCbData12 = 0x23c0, - _3dCbData13 = 0x23c4, - _3dCbData14 = 0x23c8, - _3dCbData15 = 0x23cc, - _3dSetShader = 0x3890 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NsGpuTexture.cs b/Ryujinx.Graphics/Gpu/NsGpuTexture.cs deleted file mode 100644 index aac422005..000000000 --- a/Ryujinx.Graphics/Gpu/NsGpuTexture.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ryujinx.Graphics.Gpu -{ - struct NsGpuTexture - { - public int Width; - public int Height; - - public byte[] Data; - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NsGpuTextureFormat.cs b/Ryujinx.Graphics/Gpu/NsGpuTextureFormat.cs deleted file mode 100644 index 2993840be..000000000 --- a/Ryujinx.Graphics/Gpu/NsGpuTextureFormat.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.Graphics.Gpu -{ - enum NsGpuTextureFormat - { - BC1 = 0x24, - BC2 = 0x25, - BC3 = 0x26 - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NsGpuEngine.cs b/Ryujinx.Graphics/Gpu/NvGpuEngine.cs similarity index 61% rename from Ryujinx.Graphics/Gpu/NsGpuEngine.cs rename to Ryujinx.Graphics/Gpu/NvGpuEngine.cs index 118e2b72a..624915d0d 100644 --- a/Ryujinx.Graphics/Gpu/NsGpuEngine.cs +++ b/Ryujinx.Graphics/Gpu/NvGpuEngine.cs @@ -1,13 +1,11 @@ namespace Ryujinx.Graphics.Gpu { - enum NsGpuEngine + enum NvGpuEngine { - None = 0, _2d = 0x902d, _3d = 0xb197, Compute = 0xb1c0, Kepler = 0xa140, - Dma = 0xb0b5, - GpFifo = 0xb06f + Dma = 0xb0b5 } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NvGpuEngine3d.cs b/Ryujinx.Graphics/Gpu/NvGpuEngine3d.cs new file mode 100644 index 000000000..f4486f46c --- /dev/null +++ b/Ryujinx.Graphics/Gpu/NvGpuEngine3d.cs @@ -0,0 +1,469 @@ +using ChocolArm64.Memory; +using Ryujinx.Graphics.Gal; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu +{ + class NvGpuEngine3d : INvGpuEngine + { + public int[] Registers { get; private set; } + + private NsGpu Gpu; + + private Dictionary Methods; + + private struct ConstBuffer + { + public bool Enabled; + public long Position; + public int Size; + } + + private ConstBuffer[] Cbs; + + private bool HasDataToRender; + + public NvGpuEngine3d(NsGpu Gpu) + { + this.Gpu = Gpu; + + Registers = new int[0xe00]; + + Methods = new Dictionary(); + + void AddMethod(int Meth, int Count, int Stride, NvGpuMethod Method) + { + while (Count-- > 0) + { + Methods.Add(Meth, Method); + + Meth += Stride; + } + } + + AddMethod(0x585, 1, 1, VertexEndGl); + AddMethod(0x674, 1, 1, ClearBuffers); + AddMethod(0x6c3, 1, 1, QueryControl); + AddMethod(0x8e4, 16, 1, CbData); + AddMethod(0x904, 1, 1, CbBind); + + Cbs = new ConstBuffer[18]; + } + + public void CallMethod(AMemory Memory, NsGpuPBEntry PBEntry) + { + if (Methods.TryGetValue(PBEntry.Method, out NvGpuMethod Method)) + { + Method(Memory, PBEntry); + } + else + { + WriteRegister(PBEntry); + } + } + + private void VertexEndGl(AMemory Memory, NsGpuPBEntry PBEntry) + { + SetFrameBuffer(0); + + long[] Tags = UploadShaders(Memory); + + Gpu.Renderer.BindProgram(); + + SetAlphaBlending(); + + UploadTextures(Memory, Tags); + UploadUniforms(Memory); + UploadVertexArrays(Memory); + + HasDataToRender = true; + } + + private void ClearBuffers(AMemory Memory, NsGpuPBEntry PBEntry) + { + if (HasDataToRender) + { + HasDataToRender = false; + + Gpu.Renderer.DrawFrameBuffer(0); + } + + int Arg0 = PBEntry.Arguments[0]; + + int FbIndex = (Arg0 >> 6) & 0xf; + + int Layer = (Arg0 >> 10) & 0x3ff; + + GalClearBufferFlags Flags = (GalClearBufferFlags)(Arg0 & 0x3f); + + SetFrameBuffer(0); + + Gpu.Renderer.ClearBuffers(Layer, Flags); + } + + private void SetFrameBuffer(int FbIndex) + { + int Width = ReadRegister(NvGpuEngine3dReg.FrameBufferNWidth + FbIndex * 0x10); + int Height = ReadRegister(NvGpuEngine3dReg.FrameBufferNHeight + FbIndex * 0x10); + + Gpu.Renderer.SetFb(FbIndex, Width, Height); + Gpu.Renderer.BindFrameBuffer(FbIndex); + } + + private long[] UploadShaders(AMemory Memory) + { + long[] Tags = new long[5]; + + long BasePosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress); + + for (int Index = 0; Index < 6; Index++) + { + int Control = ReadRegister(NvGpuEngine3dReg.ShaderNControl + Index * 0x10); + int Offset = ReadRegister(NvGpuEngine3dReg.ShaderNOffset + Index * 0x10); + + //Note: Vertex Program (B) is always enabled. + bool Enable = (Control & 1) != 0 || Index == 1; + + if (!Enable) + { + continue; + } + + long Tag = BasePosition + (uint)Offset; + + long Position = Gpu.GetCpuAddr(Tag); + + //TODO: Find a better way to calculate the size. + int Size = 0x20000; + + byte[] Code = AMemoryHelper.ReadBytes(Memory, Position, (uint)Size); + + GalShaderType ShaderType = GetTypeFromProgram(Index); + + Tags[(int)ShaderType] = Tag; + + Gpu.Renderer.CreateShader(Tag, ShaderType, Code); + Gpu.Renderer.BindShader(Tag); + } + + return Tags; + } + + private static GalShaderType GetTypeFromProgram(int Program) + { + switch (Program) + { + case 0: + case 1: return GalShaderType.Vertex; + case 2: return GalShaderType.TessControl; + case 3: return GalShaderType.TessEvaluation; + case 4: return GalShaderType.Geometry; + case 5: return GalShaderType.Fragment; + } + + throw new ArgumentOutOfRangeException(nameof(Program)); + } + + private void SetAlphaBlending() + { + bool BlendEnableMaster = (ReadRegister(NvGpuEngine3dReg.BlendEnableMaster) & 1) != 0; + + Gpu.Renderer.SetBlendEnable(BlendEnableMaster); + + bool BlendSeparateAlpha = (ReadRegister(NvGpuEngine3dReg.BlendSeparateAlpha) & 1) != 0; + + GalBlendEquation EquationRgb = (GalBlendEquation)ReadRegister(NvGpuEngine3dReg.BlendEquationRgb); + + GalBlendFactor FuncSrcRgb = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.BlendFuncSrcRgb); + GalBlendFactor FuncDstRgb = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.BlendFuncDstRgb); + + if (BlendSeparateAlpha) + { + GalBlendEquation EquationAlpha = (GalBlendEquation)ReadRegister(NvGpuEngine3dReg.BlendEquationAlpha); + + GalBlendFactor FuncSrcAlpha = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.BlendFuncSrcAlpha); + GalBlendFactor FuncDstAlpha = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.BlendFuncDstAlpha); + + Gpu.Renderer.SetBlendSeparate( + EquationRgb, + EquationAlpha, + FuncSrcRgb, + FuncDstRgb, + FuncSrcAlpha, + FuncDstAlpha); + } + else + { + Gpu.Renderer.SetBlend(EquationRgb, FuncSrcRgb, FuncDstRgb); + } + } + + private void UploadTextures(AMemory Memory, long[] Tags) + { + long BaseShPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress); + + int TextureCbIndex = ReadRegister(NvGpuEngine3dReg.TextureCbIndex); + + long BasePosition = Cbs[TextureCbIndex].Position; + + long Size = (uint)Cbs[TextureCbIndex].Size; + + int TexIndex = 0; + + for (int Index = 0; Index < Tags.Length; Index++) + { + foreach (ShaderDeclInfo DeclInfo in Gpu.Renderer.GetTextureUsage(Tags[Index])) + { + long Position = BasePosition + Index * Size; + + UploadTexture(Memory, Position, TexIndex, DeclInfo.Index); + + Gpu.Renderer.SetUniform1(DeclInfo.Name, TexIndex); + + TexIndex++; + } + } + } + + private void UploadTexture(AMemory Memory, long BasePosition, int TexIndex, int HndIndex) + { + long Position = BasePosition + HndIndex * 4; + + int TextureHandle = Memory.ReadInt32(Position); + + int TicIndex = (TextureHandle >> 0) & 0xfffff; + int TscIndex = (TextureHandle >> 20) & 0xfff; + + TryGetCpuAddr(NvGpuEngine3dReg.TexHeaderPoolOffset, out long TicPosition); + TryGetCpuAddr(NvGpuEngine3dReg.TexSamplerPoolOffset, out long TscPosition); + + TicPosition += TicIndex * 0x20; + TscPosition += TscIndex * 0x20; + + Gpu.Renderer.SetTexture(TexIndex, TextureFactory.MakeTexture(Gpu, Memory, TicPosition)); + Gpu.Renderer.SetSampler(TexIndex, TextureFactory.MakeSampler(Gpu, Memory, TscPosition)); + } + + private void UploadUniforms(AMemory Memory) + { + long BasePosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress); + + for (int Index = 0; Index < 5; Index++) + { + int Control = ReadRegister(NvGpuEngine3dReg.ShaderNControl + (Index + 1) * 0x10); + int Offset = ReadRegister(NvGpuEngine3dReg.ShaderNOffset + (Index + 1) * 0x10); + + //Note: Vertex Program (B) is always enabled. + bool Enable = (Control & 1) != 0 || Index == 0; + + if (!Enable) + { + continue; + } + + for (int Cbuf = 0; Cbuf < Cbs.Length; Cbuf++) + { + ConstBuffer Cb = Cbs[Cbuf]; + + if (Cb.Enabled) + { + long CbPosition = Cb.Position + Index * Cb.Size; + + byte[] Data = AMemoryHelper.ReadBytes(Memory, CbPosition, (uint)Cb.Size); + + Gpu.Renderer.SetConstBuffer(BasePosition + (uint)Offset, Cbuf, Data); + } + } + } + } + + private void UploadVertexArrays(AMemory Memory) + { + long IndexPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.IndexArrayAddress); + + int IndexSize = ReadRegister(NvGpuEngine3dReg.IndexArrayFormat); + int IndexFirst = ReadRegister(NvGpuEngine3dReg.IndexBatchFirst); + int IndexCount = ReadRegister(NvGpuEngine3dReg.IndexBatchCount); + + GalIndexFormat IndexFormat = (GalIndexFormat)IndexSize; + + IndexSize = 1 << IndexSize; + + if (IndexSize > 4) + { + throw new InvalidOperationException(); + } + + if (IndexSize != 0) + { + IndexPosition = Gpu.GetCpuAddr(IndexPosition); + + int BufferSize = IndexCount * IndexSize; + + byte[] Data = AMemoryHelper.ReadBytes(Memory, IndexPosition, BufferSize); + + Gpu.Renderer.SetIndexArray(Data, IndexFormat); + } + + List[] Attribs = new List[32]; + + for (int Attr = 0; Attr < 16; Attr++) + { + int Packed = ReadRegister(NvGpuEngine3dReg.VertexAttribNFormat + Attr); + + int ArrayIndex = Packed & 0x1f; + + if (Attribs[ArrayIndex] == null) + { + Attribs[ArrayIndex] = new List(); + } + + Attribs[ArrayIndex].Add(new GalVertexAttrib( + ((Packed >> 6) & 0x1) != 0, + (Packed >> 7) & 0x3fff, + (GalVertexAttribSize)((Packed >> 21) & 0x3f), + (GalVertexAttribType)((Packed >> 27) & 0x7), + ((Packed >> 31) & 0x1) != 0)); + } + + for (int Index = 0; Index < 32; Index++) + { + int Control = ReadRegister(NvGpuEngine3dReg.VertexArrayNControl + Index * 4); + + bool Enable = (Control & 0x1000) != 0; + + if (!Enable) + { + continue; + } + + long VertexPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNAddress + Index * 4); + long VertexEndPos = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNEndAddr + Index * 4); + + long Size = (VertexEndPos - VertexPosition) + 1; + + int Stride = Control & 0xfff; + + VertexPosition = Gpu.GetCpuAddr(VertexPosition); + + byte[] Data = AMemoryHelper.ReadBytes(Memory, VertexPosition, Size); + + GalVertexAttrib[] AttribArray = Attribs[Index]?.ToArray() ?? new GalVertexAttrib[0]; + + Gpu.Renderer.SetVertexArray(Index, Stride, Data, AttribArray); + + int PrimCtrl = ReadRegister(NvGpuEngine3dReg.VertexBeginGl); + + GalPrimitiveType PrimType = (GalPrimitiveType)(PrimCtrl & 0xffff); + + if (IndexCount != 0) + { + Gpu.Renderer.DrawElements(Index, IndexFirst, PrimType); + } + else + { + Gpu.Renderer.DrawArrays(Index, PrimType); + } + } + } + + private void QueryControl(AMemory Memory, NsGpuPBEntry PBEntry) + { + if (TryGetCpuAddr(NvGpuEngine3dReg.QueryAddress, out long Position)) + { + int Seq = Registers[(int)NvGpuEngine3dReg.QuerySequence]; + int Ctrl = Registers[(int)NvGpuEngine3dReg.QueryControl]; + + int Mode = Ctrl & 3; + + if (Mode == 0) + { + //Write. + Memory.WriteInt32(Position, Seq); + } + } + + WriteRegister(PBEntry); + } + + private void CbData(AMemory Memory, NsGpuPBEntry PBEntry) + { + if (TryGetCpuAddr(NvGpuEngine3dReg.ConstBufferNAddress, out long Position)) + { + int Offset = ReadRegister(NvGpuEngine3dReg.ConstBufferNOffset); + + foreach (int Arg in PBEntry.Arguments) + { + Memory.WriteInt32(Position + Offset, Arg); + + Offset += 4; + } + + WriteRegister(NvGpuEngine3dReg.ConstBufferNOffset, Offset); + } + } + + private void CbBind(AMemory Memory, NsGpuPBEntry PBEntry) + { + int Index = PBEntry.Arguments[0]; + + bool Enabled = (Index & 1) != 0; + + Index = (Index >> 4) & 0x1f; + + if (TryGetCpuAddr(NvGpuEngine3dReg.ConstBufferNAddress, out long Position)) + { + Cbs[Index].Position = Position; + Cbs[Index].Enabled = Enabled; + + Cbs[Index].Size = ReadRegister(NvGpuEngine3dReg.ConstBufferNSize); + } + } + + private int ReadCb(AMemory Memory, int Cbuf, int Offset) + { + long Position = Cbs[Cbuf].Position; + + int Value = Memory.ReadInt32(Position + Offset); + + return Value; + } + + private bool TryGetCpuAddr(NvGpuEngine3dReg Reg, out long Position) + { + Position = MakeInt64From2xInt32(Reg); + + Position = Gpu.GetCpuAddr(Position); + + return Position != -1; + } + + private long MakeInt64From2xInt32(NvGpuEngine3dReg Reg) + { + return + (long)Registers[(int)Reg + 0] << 32 | + (uint)Registers[(int)Reg + 1]; + } + + private void WriteRegister(NsGpuPBEntry PBEntry) + { + int ArgsCount = PBEntry.Arguments.Count; + + if (ArgsCount > 0) + { + Registers[PBEntry.Method] = PBEntry.Arguments[ArgsCount - 1]; + } + } + + private int ReadRegister(NvGpuEngine3dReg Reg) + { + return Registers[(int)Reg]; + } + + private void WriteRegister(NvGpuEngine3dReg Reg, int Value) + { + Registers[(int)Reg] = Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NvGpuEngine3dReg.cs b/Ryujinx.Graphics/Gpu/NvGpuEngine3dReg.cs new file mode 100644 index 000000000..4bba9abe0 --- /dev/null +++ b/Ryujinx.Graphics/Gpu/NvGpuEngine3dReg.cs @@ -0,0 +1,44 @@ +namespace Ryujinx.Graphics.Gpu +{ + enum NvGpuEngine3dReg + { + FrameBufferNAddress = 0x200, + FrameBufferNWidth = 0x202, + FrameBufferNHeight = 0x203, + FrameBufferNFormat = 0x204, + VertexAttribNFormat = 0x458, + BlendSeparateAlpha = 0x4cf, + BlendEquationRgb = 0x4d0, + BlendFuncSrcRgb = 0x4d1, + BlendFuncDstRgb = 0x4d2, + BlendEquationAlpha = 0x4d3, + BlendFuncSrcAlpha = 0x4d4, + BlendFuncDstAlpha = 0x4d6, + BlendEnableMaster = 0x4d7, + VertexArrayElemBase = 0x50d, + TexHeaderPoolOffset = 0x55d, + TexSamplerPoolOffset = 0x557, + ShaderAddress = 0x582, + VertexBeginGl = 0x586, + IndexArrayAddress = 0x5f2, + IndexArrayEndAddr = 0x5f4, + IndexArrayFormat = 0x5f6, + IndexBatchFirst = 0x5f7, + IndexBatchCount = 0x5f8, + QueryAddress = 0x6c0, + QuerySequence = 0x6c2, + QueryControl = 0x6c3, + VertexArrayNControl = 0x700, + VertexArrayNAddress = 0x701, + VertexArrayNDivisor = 0x703, + VertexArrayNEndAddr = 0x7c0, + ShaderNControl = 0x800, + ShaderNOffset = 0x801, + ShaderNMaxGprs = 0x803, + ShaderNType = 0x804, + ConstBufferNSize = 0x8e0, + ConstBufferNAddress = 0x8e1, + ConstBufferNOffset = 0x8e3, + TextureCbIndex = 0x982 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NvGpuFifo.cs b/Ryujinx.Graphics/Gpu/NvGpuFifo.cs new file mode 100644 index 000000000..df765895c --- /dev/null +++ b/Ryujinx.Graphics/Gpu/NvGpuFifo.cs @@ -0,0 +1,171 @@ +using ChocolArm64.Memory; +using System.Collections.Concurrent; + +namespace Ryujinx.Graphics.Gpu +{ + public class NvGpuFifo + { + private const int MacrosCount = 0x80; + private const int MacroIndexMask = MacrosCount - 1; + + private NsGpu Gpu; + + private ConcurrentQueue<(AMemory, NsGpuPBEntry)> BufferQueue; + + private NvGpuEngine[] SubChannels; + + private struct CachedMacro + { + public long Position { get; private set; } + + private MacroInterpreter Interpreter; + + public CachedMacro(NvGpuFifo PFifo, INvGpuEngine Engine, long Position) + { + this.Position = Position; + + Interpreter = new MacroInterpreter(PFifo, Engine); + } + + public void PushParam(int Param) + { + Interpreter?.Fifo.Enqueue(Param); + } + + public void Execute(AMemory Memory, int Param) + { + Interpreter?.Execute(Memory, Position, Param); + } + } + + private long CurrMacroPosition; + private int CurrMacroBindIndex; + + private CachedMacro[] Macros; + + public NvGpuFifo(NsGpu Gpu) + { + this.Gpu = Gpu; + + BufferQueue = new ConcurrentQueue<(AMemory, NsGpuPBEntry)>(); + + SubChannels = new NvGpuEngine[8]; + + Macros = new CachedMacro[MacrosCount]; + } + + public void PushBuffer(AMemory Memory, NsGpuPBEntry[] Buffer) + { + foreach (NsGpuPBEntry PBEntry in Buffer) + { + BufferQueue.Enqueue((Memory, PBEntry)); + } + } + + public void DispatchCalls() + { + while (Step()); + } + + public bool Step() + { + if (BufferQueue.TryDequeue(out (AMemory Memory, NsGpuPBEntry PBEntry) Tuple)) + { + CallMethod(Tuple.Memory, Tuple.PBEntry); + + return true; + } + + return false; + } + + private void CallMethod(AMemory Memory, NsGpuPBEntry PBEntry) + { + if (PBEntry.Method < 0x80) + { + switch ((NvGpuFifoMeth)PBEntry.Method) + { + case NvGpuFifoMeth.BindChannel: + { + NvGpuEngine Engine = (NvGpuEngine)PBEntry.Arguments[0]; + + SubChannels[PBEntry.SubChannel] = Engine; + + break; + } + + case NvGpuFifoMeth.SetMacroUploadAddress: + { + CurrMacroPosition = (long)((ulong)PBEntry.Arguments[0] << 2); + + break; + } + + case NvGpuFifoMeth.SendMacroCodeData: + { + long Position = Gpu.GetCpuAddr(CurrMacroPosition); + + foreach (int Arg in PBEntry.Arguments) + { + Memory.WriteInt32(Position, Arg); + + CurrMacroPosition += 4; + + Position += 4; + } + break; + } + + case NvGpuFifoMeth.SetMacroBindingIndex: + { + CurrMacroBindIndex = PBEntry.Arguments[0]; + + break; + } + + case NvGpuFifoMeth.BindMacro: + { + long Position = (long)((ulong)PBEntry.Arguments[0] << 2); + + Position = Gpu.GetCpuAddr(Position); + + Macros[CurrMacroBindIndex] = new CachedMacro(this, Gpu.Engine3d, Position); + + break; + } + } + } + else + { + switch (SubChannels[PBEntry.SubChannel]) + { + case NvGpuEngine._3d: Call3dMethod(Memory, PBEntry); break; + } + } + } + + private void Call3dMethod(AMemory Memory, NsGpuPBEntry PBEntry) + { + if (PBEntry.Method < 0xe00) + { + Gpu.Engine3d.CallMethod(Memory, PBEntry); + } + else + { + int MacroIndex = (PBEntry.Method >> 1) & MacroIndexMask; + + if ((PBEntry.Method & 1) != 0) + { + foreach (int Arg in PBEntry.Arguments) + { + Macros[MacroIndex].PushParam(Arg); + } + } + else + { + Macros[MacroIndex].Execute(Memory, PBEntry.Arguments[0]); + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NvGpuFifoMeth.cs b/Ryujinx.Graphics/Gpu/NvGpuFifoMeth.cs new file mode 100644 index 000000000..4287e2500 --- /dev/null +++ b/Ryujinx.Graphics/Gpu/NvGpuFifoMeth.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu +{ + enum NvGpuFifoMeth + { + BindChannel = 0, + SetMacroUploadAddress = 0x45, + SendMacroCodeData = 0x46, + SetMacroBindingIndex = 0x47, + BindMacro = 0x48 + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NvGpuMethod.cs b/Ryujinx.Graphics/Gpu/NvGpuMethod.cs new file mode 100644 index 000000000..2923ddff0 --- /dev/null +++ b/Ryujinx.Graphics/Gpu/NvGpuMethod.cs @@ -0,0 +1,6 @@ +using ChocolArm64.Memory; + +namespace Ryujinx.Graphics.Gpu +{ + delegate void NvGpuMethod(AMemory Memory, NsGpuPBEntry PBEntry); +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/NvGpuPushBuffer.cs b/Ryujinx.Graphics/Gpu/NvGpuPushBuffer.cs new file mode 100644 index 000000000..8cbb3288e --- /dev/null +++ b/Ryujinx.Graphics/Gpu/NvGpuPushBuffer.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.Graphics.Gpu +{ + public static class NvGpuPushBuffer + { + private enum SubmissionMode + { + Incrementing = 1, + NonIncrementing = 3, + Immediate = 4, + IncrementOnce = 5 + } + + public static NsGpuPBEntry[] Decode(byte[] Data) + { + using (MemoryStream MS = new MemoryStream(Data)) + { + BinaryReader Reader = new BinaryReader(MS); + + List PushBuffer = new List(); + + bool CanRead() => MS.Position + 4 <= MS.Length; + + while (CanRead()) + { + int Packed = Reader.ReadInt32(); + + int Meth = (Packed >> 0) & 0x1fff; + int SubC = (Packed >> 13) & 7; + int Args = (Packed >> 16) & 0x1fff; + int Mode = (Packed >> 29) & 7; + + switch ((SubmissionMode)Mode) + { + case SubmissionMode.Incrementing: + { + for (int Index = 0; Index < Args && CanRead(); Index++, Meth++) + { + PushBuffer.Add(new NsGpuPBEntry(Meth, SubC, Reader.ReadInt32())); + } + + break; + } + + case SubmissionMode.NonIncrementing: + { + int[] Arguments = new int[Args]; + + for (int Index = 0; Index < Arguments.Length; Index++) + { + if (!CanRead()) + { + break; + } + + Arguments[Index] = Reader.ReadInt32(); + } + + PushBuffer.Add(new NsGpuPBEntry(Meth, SubC, Arguments)); + + break; + } + + case SubmissionMode.Immediate: + { + PushBuffer.Add(new NsGpuPBEntry(Meth, SubC, Args)); + + break; + } + + case SubmissionMode.IncrementOnce: + { + if (CanRead()) + { + PushBuffer.Add(new NsGpuPBEntry(Meth, SubC, Reader.ReadInt32())); + } + + if (CanRead() && Args > 1) + { + int[] Arguments = new int[Args - 1]; + + for (int Index = 0; Index < Arguments.Length && CanRead(); Index++) + { + Arguments[Index] = Reader.ReadInt32(); + } + + PushBuffer.Add(new NsGpuPBEntry(Meth + 1, SubC, Arguments)); + } + + break; + } + } + } + + return PushBuffer.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/Texture.cs b/Ryujinx.Graphics/Gpu/Texture.cs new file mode 100644 index 000000000..c8d4e5274 --- /dev/null +++ b/Ryujinx.Graphics/Gpu/Texture.cs @@ -0,0 +1,34 @@ +using Ryujinx.Graphics.Gal; + +namespace Ryujinx.Graphics.Gpu +{ + struct Texture + { + public long Position { get; private set; } + + public int Width { get; private set; } + public int Height { get; private set; } + + public int BlockHeight { get; private set; } + + public TextureSwizzle Swizzle { get; private set; } + + public GalTextureFormat Format { get; private set; } + + public Texture( + long Position, + int Width, + int Height, + int BlockHeight, + TextureSwizzle Swizzle, + GalTextureFormat Format) + { + this.Position = Position; + this.Width = Width; + this.Height = Height; + this.BlockHeight = BlockHeight; + this.Swizzle = Swizzle; + this.Format = Format; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/TextureFactory.cs b/Ryujinx.Graphics/Gpu/TextureFactory.cs new file mode 100644 index 000000000..0a0497f3f --- /dev/null +++ b/Ryujinx.Graphics/Gpu/TextureFactory.cs @@ -0,0 +1,83 @@ +using ChocolArm64.Memory; +using Ryujinx.Graphics.Gal; +using System; + +namespace Ryujinx.Graphics.Gpu +{ + static class TextureFactory + { + public static GalTexture MakeTexture(NsGpu Gpu, AMemory Memory, long TicPosition) + { + int[] Tic = ReadWords(Memory, TicPosition, 8); + + GalTextureFormat Format = (GalTextureFormat)(Tic[0] & 0x7f); + + long TextureAddress = (uint)Tic[1]; + + TextureAddress |= (long)((ushort)Tic[2]) << 32; + + TextureAddress = Gpu.GetCpuAddr(TextureAddress); + + TextureSwizzle Swizzle = (TextureSwizzle)((Tic[2] >> 21) & 7); + + int BlockHeightLog2 = (Tic[3] >> 3) & 7; + + int BlockHeight = 1 << BlockHeightLog2; + + int Width = (Tic[4] & 0xffff) + 1; + int Height = (Tic[5] & 0xffff) + 1; + + Texture Texture = new Texture( + TextureAddress, + Width, + Height, + BlockHeight, + Swizzle, + Format); + + byte[] Data = TextureReader.Read(Memory, Texture); + + return new GalTexture(Data, Width, Height, Format); + } + + public static GalTextureSampler MakeSampler(NsGpu Gpu, AMemory Memory, long TscPosition) + { + int[] Tsc = ReadWords(Memory, TscPosition, 8); + + GalTextureWrap AddressU = (GalTextureWrap)((Tsc[0] >> 0) & 7); + GalTextureWrap AddressV = (GalTextureWrap)((Tsc[0] >> 3) & 7); + GalTextureWrap AddressP = (GalTextureWrap)((Tsc[0] >> 6) & 7); + + GalTextureFilter MagFilter = (GalTextureFilter) ((Tsc[1] >> 0) & 3); + GalTextureFilter MinFilter = (GalTextureFilter) ((Tsc[1] >> 4) & 3); + GalTextureMipFilter MipFilter = (GalTextureMipFilter)((Tsc[1] >> 6) & 3); + + GalColorF BorderColor = new GalColorF( + BitConverter.Int32BitsToSingle(Tsc[4]), + BitConverter.Int32BitsToSingle(Tsc[5]), + BitConverter.Int32BitsToSingle(Tsc[6]), + BitConverter.Int32BitsToSingle(Tsc[7])); + + return new GalTextureSampler( + AddressU, + AddressV, + AddressP, + MinFilter, + MagFilter, + MipFilter, + BorderColor); + } + + private static int[] ReadWords(AMemory Memory, long Position, int Count) + { + int[] Words = new int[Count]; + + for (int Index = 0; Index < Count; Index++, Position += 4) + { + Words[Index] = Memory.ReadInt32(Position); + } + + return Words; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/TextureReader.cs b/Ryujinx.Graphics/Gpu/TextureReader.cs new file mode 100644 index 000000000..ce66e991d --- /dev/null +++ b/Ryujinx.Graphics/Gpu/TextureReader.cs @@ -0,0 +1,127 @@ +using ChocolArm64.Memory; +using Ryujinx.Graphics.Gal; +using System; + +namespace Ryujinx.Graphics.Gpu +{ + static class TextureReader + { + public static byte[] Read(AMemory Memory, Texture Texture) + { + switch (Texture.Format) + { + case GalTextureFormat.A8B8G8R8: return Read4Bpp (Memory, Texture); + case GalTextureFormat.BC1: return Read8Bpt4x4 (Memory, Texture); + case GalTextureFormat.BC2: return Read16Bpt4x4(Memory, Texture); + case GalTextureFormat.BC3: return Read16Bpt4x4(Memory, Texture); + } + + throw new NotImplementedException(Texture.Format.ToString()); + } + + private unsafe static byte[] Read4Bpp(AMemory Memory, Texture Texture) + { + int Width = Texture.Width; + int Height = Texture.Height; + + byte[] Output = new byte[Width * Height * 4]; + + ISwizzle Swizzle = GetSwizzle(Texture.Swizzle, Width, 4, Texture.BlockHeight); + + fixed (byte* BuffPtr = Output) + { + long OutOffs = 0; + + for (int Y = 0; Y < Height; Y++) + for (int X = 0; X < Width; X++) + { + long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); + + int Pixel = Memory.ReadInt32Unchecked(Texture.Position + Offset); + + *(int*)(BuffPtr + OutOffs) = Pixel; + + OutOffs += 4; + } + } + + return Output; + } + + private unsafe static byte[] Read8Bpt4x4(AMemory Memory, Texture Texture) + { + int Width = (Texture.Width + 3) / 4; + int Height = (Texture.Height + 3) / 4; + + byte[] Output = new byte[Width * Height * 8]; + + ISwizzle Swizzle = GetSwizzle(Texture.Swizzle, Width, 8, Texture.BlockHeight); + + fixed (byte* BuffPtr = Output) + { + long OutOffs = 0; + + for (int Y = 0; Y < Height; Y++) + for (int X = 0; X < Width; X++) + { + long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); + + long Tile = Memory.ReadInt64Unchecked(Texture.Position + Offset); + + *(long*)(BuffPtr + OutOffs) = Tile; + + OutOffs += 8; + } + } + + return Output; + } + + private unsafe static byte[] Read16Bpt4x4(AMemory Memory, Texture Texture) + { + int Width = (Texture.Width + 3) / 4; + int Height = (Texture.Height + 3) / 4; + + byte[] Output = new byte[Width * Height * 16]; + + ISwizzle Swizzle = GetSwizzle(Texture.Swizzle, Width, 16, Texture.BlockHeight); + + fixed (byte* BuffPtr = Output) + { + long OutOffs = 0; + + for (int Y = 0; Y < Height; Y++) + for (int X = 0; X < Width; X++) + { + long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y); + + long Tile0 = Memory.ReadInt64Unchecked(Texture.Position + Offset + 0); + long Tile1 = Memory.ReadInt64Unchecked(Texture.Position + Offset + 8); + + *(long*)(BuffPtr + OutOffs + 0) = Tile0; + *(long*)(BuffPtr + OutOffs + 8) = Tile1; + + OutOffs += 16; + } + } + + return Output; + } + + private static ISwizzle GetSwizzle(TextureSwizzle Swizzle, int Width, int Bpp, int BlockHeight) + { + switch (Swizzle) + { + case TextureSwizzle.Pitch: + case TextureSwizzle.PitchColorKey: + return new LinearSwizzle(Width, Bpp); + + case TextureSwizzle.BlockLinear: + case TextureSwizzle.BlockLinearColorKey: + return new BlockLinearSwizzle(Width, Bpp, BlockHeight); + } + + throw new NotImplementedException(Swizzle.ToString()); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gpu/TextureSwizzle.cs b/Ryujinx.Graphics/Gpu/TextureSwizzle.cs new file mode 100644 index 000000000..2142e2c20 --- /dev/null +++ b/Ryujinx.Graphics/Gpu/TextureSwizzle.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu +{ + enum TextureSwizzle + { + _1dBuffer = 0, + PitchColorKey = 1, + Pitch = 2, + BlockLinear = 3, + BlockLinearColorKey = 4 + } +} \ No newline at end of file diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs index 3bcef9218..d6a33edbf 100644 --- a/Ryujinx/Ui/GLScreen.cs +++ b/Ryujinx/Ui/GLScreen.cs @@ -173,8 +173,6 @@ namespace Ryujinx Title = $"Ryujinx Screen - (Vsync: {VSync} - FPS: {Ns.Statistics.SystemFrameRate:0} - Guest FPS: " + $"{Ns.Statistics.GameFrameRate:0})"; - GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); - Renderer.RunActions(); Renderer.Render();