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