Merge shader branch, adding support for GLSL decompilation, a macro

interpreter, and a rewrite of the GPU code.
This commit is contained in:
gdkchan 2018-04-08 16:17:35 -03:00
parent 7acd0e0122
commit b9aa3966c0
77 changed files with 5301 additions and 766 deletions

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Graphics.Gal
{
public enum GalBlendEquation
{
FuncAdd = 0x8006,
Min = 0x8007,
Max = 0x8008,
FuncSubtract = 0x800a,
FuncReverseSubtract = 0x800b
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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;
}
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Graphics.Gal
{
public enum GalIndexFormat
{
Byte = 0,
Int16 = 1,
Int32 = 2
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Graphics.Gal
{
public enum GalShaderType
{
Vertex = 0,
TessControl = 1,
TessEvaluation = 2,
Geometry = 3,
Fragment = 4
}
}

View file

@ -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;
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.Gal
{
public enum GalTextureFilter
{
Nearest = 1,
Linear = 2
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.Graphics.Gal
{
public enum GalTextureFormat
{
A8B8G8R8 = 0x8,
BC1 = 0x24,
BC2 = 0x25,
BC3 = 0x26
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Graphics.Gal
{
public enum GalTextureMipFilter
{
None = 1,
Nearest = 2,
Linear = 3
}
}

View file

@ -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;
}
}
}

View file

@ -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
}
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -219,7 +219,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
PixelFormat.Rgba,
PixelType.UnsignedByte,
Pixels);
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindVertexArray(VaoHandle);

View file

@ -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));
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}
}
}

View file

@ -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));
}
}
}
}

View file

@ -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;
}
}
}

View file

@ -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();
Textures = new Texture[8];
FrameBuffer = new OGLFrameBuffer();
Rasterizer = new OGLRasterizer();
Shader = new OGLShader();
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];
Vb.PrimCount = Buffer.Length / Stride;
VertexBuffers[Index] = Vb;
IntPtr Length = new IntPtr(Buffer.Length);
GL.BindBuffer(BufferTarget.ArrayBuffer, Vb.VboHandle);
GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw);
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
GL.BindVertexArray(Vb.VaoHandle);
for (int Attr = 0; Attr < 16; Attr++)
{
GL.DisableVertexAttribArray(Attr);
}
foreach (GalVertexAttrib Attrib in Attribs)
{
if (Attrib.Index >= 3) break;
GL.EnableVertexAttribArray(Attrib.Index);
GL.BindBuffer(BufferTarget.ArrayBuffer, Vb.VboHandle);
int Size = 0;
switch (Attrib.Size)
{
case GalVertexAttribSize._8:
case GalVertexAttribSize._16:
case GalVertexAttribSize._32:
Size = 1;
break;
case GalVertexAttribSize._8_8:
case GalVertexAttribSize._16_16:
case GalVertexAttribSize._32_32:
Size = 2;
break;
case GalVertexAttribSize._8_8_8:
case GalVertexAttribSize._11_11_10:
case GalVertexAttribSize._16_16_16:
case GalVertexAttribSize._32_32_32:
Size = 3;
break;
case GalVertexAttribSize._8_8_8_8:
case GalVertexAttribSize._10_10_10_2:
case GalVertexAttribSize._16_16_16_16:
case GalVertexAttribSize._32_32_32_32:
Size = 4;
break;
}
bool Signed =
Attrib.Type == GalVertexAttribType.Snorm ||
Attrib.Type == GalVertexAttribType.Sint ||
Attrib.Type == GalVertexAttribType.Sscaled;
bool Normalize =
Attrib.Type == GalVertexAttribType.Snorm ||
Attrib.Type == GalVertexAttribType.Unorm;
VertexAttribPointerType Type = 0;
switch (Attrib.Type)
{
case GalVertexAttribType.Snorm:
case GalVertexAttribType.Unorm:
case GalVertexAttribType.Sint:
case GalVertexAttribType.Uint:
case GalVertexAttribType.Uscaled:
case GalVertexAttribType.Sscaled:
{
switch (Attrib.Size)
{
case GalVertexAttribSize._8:
case GalVertexAttribSize._8_8:
case GalVertexAttribSize._8_8_8:
case GalVertexAttribSize._8_8_8_8:
{
Type = Signed
? VertexAttribPointerType.Byte
: VertexAttribPointerType.UnsignedByte;
break;
}
case GalVertexAttribSize._16:
case GalVertexAttribSize._16_16:
case GalVertexAttribSize._16_16_16:
case GalVertexAttribSize._16_16_16_16:
{
Type = Signed
? VertexAttribPointerType.Short
: VertexAttribPointerType.UnsignedShort;
break;
}
case GalVertexAttribSize._10_10_10_2:
case GalVertexAttribSize._11_11_10:
case GalVertexAttribSize._32:
case GalVertexAttribSize._32_32:
case GalVertexAttribSize._32_32_32:
case GalVertexAttribSize._32_32_32_32:
{
Type = Signed
? VertexAttribPointerType.Int
: VertexAttribPointerType.UnsignedInt;
break;
}
}
break;
}
case GalVertexAttribType.Float:
{
Type = VertexAttribPointerType.Float;
break;
}
}
GL.VertexAttribPointer(
Attrib.Index,
Size,
Type,
Normalize,
Stride,
Attrib.Offset);
}
GL.BindVertexArray(0);
}
public void SendR8G8B8A8Texture(int Index, byte[] Buffer, int Width, int Height)
public void SetBlend(
GalBlendEquation Equation,
GalBlendFactor FuncSrc,
GalBlendFactor FuncDst)
{
EnsureTexInitialized(Index);
GL.BindTexture(TextureTarget.Texture2D, Textures[Index].Handle);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
GL.TexImage2D(TextureTarget.Texture2D,
0,
PixelInternalFormat.Rgba,
Width,
Height,
0,
PixelFormat.Rgba,
PixelType.UnsignedByte,
Buffer);
ActionsQueue.Enqueue(() => Blend.Set(Equation, FuncSrc, FuncDst));
}
public void BindTexture(int Index)
public void SetBlendSeparate(
GalBlendEquation EquationRgb,
GalBlendEquation EquationAlpha,
GalBlendFactor FuncSrcRgb,
GalBlendFactor FuncDstRgb,
GalBlendFactor FuncSrcAlpha,
GalBlendFactor FuncDstAlpha)
{
GL.ActiveTexture(TextureUnit.Texture0 + Index);
GL.BindTexture(TextureTarget.Texture2D, Textures[Index].Handle);
ActionsQueue.Enqueue(() =>
{
Blend.SetSeparate(
EquationRgb,
EquationAlpha,
FuncSrcRgb,
FuncDstRgb,
FuncSrcAlpha,
FuncDstAlpha);
});
}
private void EnsureVbInitialized(int VbIndex)
public void SetFb(int FbIndex, int Width, int Height)
{
while (VbIndex >= VertexBuffers.Count)
{
VertexBuffers.Add(new VertexBuffer());
}
VertexBuffer Vb = VertexBuffers[VbIndex];
if (Vb.VaoHandle == 0)
{
Vb.VaoHandle = GL.GenVertexArray();
}
if (Vb.VboHandle == 0)
{
Vb.VboHandle = GL.GenBuffer();
}
VertexBuffers[VbIndex] = Vb;
ActionsQueue.Enqueue(() => FrameBuffer.Set(FbIndex, Width, Height));
}
private void EnsureTexInitialized(int TexIndex)
public void BindFrameBuffer(int FbIndex)
{
Texture Tex = Textures[TexIndex];
ActionsQueue.Enqueue(() => FrameBuffer.Bind(FbIndex));
}
if (Tex.Handle == 0)
public void DrawFrameBuffer(int FbIndex)
{
ActionsQueue.Enqueue(() => FrameBuffer.Draw(FbIndex));
}
public void ClearBuffers(int RtIndex, GalClearBufferFlags Flags)
{
ActionsQueue.Enqueue(() => Rasterizer.ClearBuffers(RtIndex, Flags));
}
public void SetVertexArray(int VbIndex, int Stride, byte[] Buffer, GalVertexAttrib[] Attribs)
{
if ((uint)VbIndex > 31)
{
Tex.Handle = GL.GenTexture();
throw new ArgumentOutOfRangeException(nameof(VbIndex));
}
Textures[TexIndex] = Tex;
ActionsQueue.Enqueue(() => Rasterizer.SetVertexArray(VbIndex, Stride,
Buffer ?? throw new ArgumentNullException(nameof(Buffer)),
Attribs ?? throw new ArgumentNullException(nameof(Attribs))));
}
public void SetIndexArray(byte[] Buffer, GalIndexFormat Format)
{
if (Buffer == null)
{
throw new ArgumentNullException(nameof(Buffer));
}
ActionsQueue.Enqueue(() => Rasterizer.SetIndexArray(Buffer, Format));
}
public void DrawArrays(int VbIndex, GalPrimitiveType PrimType)
{
if ((uint)VbIndex > 31)
{
throw new ArgumentOutOfRangeException(nameof(VbIndex));
}
ActionsQueue.Enqueue(() => Rasterizer.DrawArrays(VbIndex, PrimType));
}
public void DrawElements(int VbIndex, int First, GalPrimitiveType PrimType)
{
if ((uint)VbIndex > 31)
{
throw new ArgumentOutOfRangeException(nameof(VbIndex));
}
ActionsQueue.Enqueue(() => Rasterizer.DrawElements(VbIndex, First, PrimType));
}
public void CreateShader(long Tag, GalShaderType Type, byte[] Data)
{
if (Data == null)
{
throw new ArgumentNullException(nameof(Data));
}
Shader.Create(Tag, Type, Data);
}
public void SetConstBuffer(long Tag, int Cbuf, byte[] Data)
{
if (Data == null)
{
throw new ArgumentNullException(nameof(Data));
}
ActionsQueue.Enqueue(() => Shader.SetConstBuffer(Tag, Cbuf, Data));
}
public void SetUniform1(string UniformName, int Value)
{
if (UniformName == null)
{
throw new ArgumentNullException(nameof(UniformName));
}
ActionsQueue.Enqueue(() => Shader.SetUniform1(UniformName, Value));
}
public IEnumerable<ShaderDeclInfo> GetTextureUsage(long Tag)
{
return Shader.GetTextureUsage(Tag);
}
public void BindShader(long Tag)
{
ActionsQueue.Enqueue(() => Shader.Bind(Tag));
}
public void BindProgram()
{
ActionsQueue.Enqueue(() => Shader.BindProgram());
}
public void SetTexture(int Index, GalTexture Tex)
{
ActionsQueue.Enqueue(() => Texture.Set(Index, Tex));
}
public void SetSampler(int Index, GalTextureSampler Sampler)
{
ActionsQueue.Enqueue(() => Texture.Set(Index, Sampler));
}
}
}

View file

@ -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);
}
}
}

View file

@ -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));
}
}
}

View file

@ -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;
}
}
}

View file

@ -0,0 +1,4 @@
namespace Ryujinx.Graphics.Gal.Shader
{
delegate void ShaderDecodeFunc(ShaderIrBlock Block, long OpCode);
}

View file

@ -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));
}
}
}

View file

@ -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));
}
}
}

View file

@ -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;
}
}
}

View file

@ -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));
}
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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
}
}

View file

@ -0,0 +1,4 @@
namespace Ryujinx.Graphics.Gal.Shader
{
class ShaderIrNode { }
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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)];
}
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.Graphics.Gal.Shader
{
enum ShaderOper
{
CR,
RC,
RR,
Imm,
Immf
}
}

View file

@ -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);
}
}
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -0,0 +1,11 @@
using System;
namespace Ryujinx.Graphics.Gal
{
class ShaderException : Exception
{
public ShaderException() : base() { }
public ShaderException(string Message) : base(Message) { }
}
}

View file

@ -0,0 +1,468 @@
using System;
using System.Drawing;
namespace Ryujinx.Graphics.Gal.Texture
{
static class BCn
{
public static byte[] DecodeBC1(GalTexture Texture, int Offset)
{
int W = (Texture.Width + 3) / 4;
int H = (Texture.Height + 3) / 4;
byte[] Output = new byte[W * H * 64];
SwizzleAddr Swizzle = new SwizzleAddr(W, H, 8);
for (int Y = 0; Y < H; Y++)
{
for (int X = 0; X < W; X++)
{
int IOffs = Offset + Swizzle.GetSwizzledAddress64(X, Y) * 8;
byte[] Tile = BCnDecodeTile(Texture.Data, IOffs, true);
int TOffset = 0;
for (int TY = 0; TY < 4; TY++)
{
for (int TX = 0; TX < 4; TX++)
{
int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4;
Output[OOffset + 0] = Tile[TOffset + 0];
Output[OOffset + 1] = Tile[TOffset + 1];
Output[OOffset + 2] = Tile[TOffset + 2];
Output[OOffset + 3] = Tile[TOffset + 3];
TOffset += 4;
}
}
}
}
return Output;
}
public static byte[] DecodeBC2(GalTexture Texture, int Offset)
{
int W = (Texture.Width + 3) / 4;
int H = (Texture.Height + 3) / 4;
byte[] Output = new byte[W * H * 64];
SwizzleAddr Swizzle = new SwizzleAddr(W, H, 4);
for (int Y = 0; Y < H; Y++)
{
for (int X = 0; X < W; X++)
{
int IOffs = Offset + Swizzle.GetSwizzledAddress128(X, Y) * 16;
byte[] Tile = BCnDecodeTile(Texture.Data, IOffs + 8, false);
int AlphaLow = Get32(Texture.Data, IOffs + 0);
int AlphaHigh = Get32(Texture.Data, IOffs + 4);
ulong AlphaCh = (uint)AlphaLow | (ulong)AlphaHigh << 32;
int TOffset = 0;
for (int TY = 0; TY < 4; TY++)
{
for (int TX = 0; TX < 4; TX++)
{
ulong Alpha = (AlphaCh >> (TY * 16 + TX * 4)) & 0xf;
int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4;
Output[OOffset + 0] = Tile[TOffset + 0];
Output[OOffset + 1] = Tile[TOffset + 1];
Output[OOffset + 2] = Tile[TOffset + 2];
Output[OOffset + 3] = (byte)(Alpha | (Alpha << 4));
TOffset += 4;
}
}
}
}
return Output;
}
public static byte[] DecodeBC3(GalTexture Texture, int Offset)
{
int W = (Texture.Width + 3) / 4;
int H = (Texture.Height + 3) / 4;
byte[] Output = new byte[W * H * 64];
SwizzleAddr Swizzle = new SwizzleAddr(W, H, 4);
for (int Y = 0; Y < H; Y++)
{
for (int X = 0; X < W; X++)
{
int IOffs = Offset + Swizzle.GetSwizzledAddress128(X, Y) * 16;
byte[] Tile = BCnDecodeTile(Texture.Data, IOffs + 8, false);
byte[] Alpha = new byte[8];
Alpha[0] = Texture.Data[IOffs + 0];
Alpha[1] = Texture.Data[IOffs + 1];
CalculateBC3Alpha(Alpha);
int AlphaLow = Get32(Texture.Data, IOffs + 2);
int AlphaHigh = Get16(Texture.Data, IOffs + 6);
ulong AlphaCh = (uint)AlphaLow | (ulong)AlphaHigh << 32;
int TOffset = 0;
for (int TY = 0; TY < 4; TY++)
{
for (int TX = 0; TX < 4; TX++)
{
int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4;
byte AlphaPx = Alpha[(AlphaCh >> (TY * 12 + TX * 3)) & 7];
Output[OOffset + 0] = Tile[TOffset + 0];
Output[OOffset + 1] = Tile[TOffset + 1];
Output[OOffset + 2] = Tile[TOffset + 2];
Output[OOffset + 3] = AlphaPx;
TOffset += 4;
}
}
}
}
return Output;
}
public static byte[] DecodeBC4(GalTexture Texture, int Offset)
{
int W = (Texture.Width + 3) / 4;
int H = (Texture.Height + 3) / 4;
byte[] Output = new byte[W * H * 64];
SwizzleAddr Swizzle = new SwizzleAddr(W, H, 8);
for (int Y = 0; Y < H; Y++)
{
for (int X = 0; X < W; X++)
{
int IOffs = Swizzle.GetSwizzledAddress64(X, Y) * 8;
byte[] Red = new byte[8];
Red[0] = Texture.Data[IOffs + 0];
Red[1] = Texture.Data[IOffs + 1];
CalculateBC3Alpha(Red);
int RedLow = Get32(Texture.Data, IOffs + 2);
int RedHigh = Get16(Texture.Data, IOffs + 6);
ulong RedCh = (uint)RedLow | (ulong)RedHigh << 32;
int TOffset = 0;
for (int TY = 0; TY < 4; TY++)
{
for (int TX = 0; TX < 4; TX++)
{
int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4;
byte RedPx = Red[(RedCh >> (TY * 12 + TX * 3)) & 7];
Output[OOffset + 0] = RedPx;
Output[OOffset + 1] = RedPx;
Output[OOffset + 2] = RedPx;
Output[OOffset + 3] = 0xff;
TOffset += 4;
}
}
}
}
return Output;
}
public static byte[] DecodeBC5(GalTexture Texture, int Offset, bool SNorm)
{
int W = (Texture.Width + 3) / 4;
int H = (Texture.Height + 3) / 4;
byte[] Output = new byte[W * H * 64];
SwizzleAddr Swizzle = new SwizzleAddr(W, H, 4);
for (int Y = 0; Y < H; Y++)
{
for (int X = 0; X < W; X++)
{
int IOffs = Swizzle.GetSwizzledAddress128(X, Y) * 16;
byte[] Red = new byte[8];
byte[] Green = new byte[8];
Red[0] = Texture.Data[IOffs + 0];
Red[1] = Texture.Data[IOffs + 1];
Green[0] = Texture.Data[IOffs + 8];
Green[1] = Texture.Data[IOffs + 9];
if (SNorm)
{
CalculateBC3AlphaS(Red);
CalculateBC3AlphaS(Green);
}
else
{
CalculateBC3Alpha(Red);
CalculateBC3Alpha(Green);
}
int RedLow = Get32(Texture.Data, IOffs + 2);
int RedHigh = Get16(Texture.Data, IOffs + 6);
int GreenLow = Get32(Texture.Data, IOffs + 10);
int GreenHigh = Get16(Texture.Data, IOffs + 14);
ulong RedCh = (uint)RedLow | (ulong)RedHigh << 32;
ulong GreenCh = (uint)GreenLow | (ulong)GreenHigh << 32;
int TOffset = 0;
if (SNorm)
{
for (int TY = 0; TY < 4; TY++)
{
for (int TX = 0; TX < 4; TX++)
{
int Shift = TY * 12 + TX * 3;
int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4;
byte RedPx = Red [(RedCh >> Shift) & 7];
byte GreenPx = Green[(GreenCh >> Shift) & 7];
RedPx += 0x80;
GreenPx += 0x80;
float NX = (RedPx / 255f) * 2 - 1;
float NY = (GreenPx / 255f) * 2 - 1;
float NZ = (float)Math.Sqrt(1 - (NX * NX + NY * NY));
Output[OOffset + 0] = Clamp((NZ + 1) * 0.5f);
Output[OOffset + 1] = Clamp((NY + 1) * 0.5f);
Output[OOffset + 2] = Clamp((NX + 1) * 0.5f);
Output[OOffset + 3] = 0xff;
TOffset += 4;
}
}
}
else
{
for (int TY = 0; TY < 4; TY++)
{
for (int TX = 0; TX < 4; TX++)
{
int Shift = TY * 12 + TX * 3;
int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4;
byte RedPx = Red [(RedCh >> Shift) & 7];
byte GreenPx = Green[(GreenCh >> Shift) & 7];
Output[OOffset + 0] = RedPx;
Output[OOffset + 1] = RedPx;
Output[OOffset + 2] = RedPx;
Output[OOffset + 3] = GreenPx;
TOffset += 4;
}
}
}
}
}
return Output;
}
private static byte Clamp(float Value)
{
if (Value > 1)
{
return 0xff;
}
else if (Value < 0)
{
return 0;
}
else
{
return (byte)(Value * 0xff);
}
}
private static void CalculateBC3Alpha(byte[] Alpha)
{
for (int i = 2; i < 8; i++)
{
if (Alpha[0] > Alpha[1])
{
Alpha[i] = (byte)(((8 - i) * Alpha[0] + (i - 1) * Alpha[1]) / 7);
}
else if (i < 6)
{
Alpha[i] = (byte)(((6 - i) * Alpha[0] + (i - 1) * Alpha[1]) / 7);
}
else if (i == 6)
{
Alpha[i] = 0;
}
else /* i == 7 */
{
Alpha[i] = 0xff;
}
}
}
private static void CalculateBC3AlphaS(byte[] Alpha)
{
for (int i = 2; i < 8; i++)
{
if ((sbyte)Alpha[0] > (sbyte)Alpha[1])
{
Alpha[i] = (byte)(((8 - i) * (sbyte)Alpha[0] + (i - 1) * (sbyte)Alpha[1]) / 7);
}
else if (i < 6)
{
Alpha[i] = (byte)(((6 - i) * (sbyte)Alpha[0] + (i - 1) * (sbyte)Alpha[1]) / 7);
}
else if (i == 6)
{
Alpha[i] = 0x80;
}
else /* i == 7 */
{
Alpha[i] = 0x7f;
}
}
}
private static byte[] BCnDecodeTile(
byte[] Input,
int Offset,
bool IsBC1)
{
Color[] CLUT = new Color[4];
int c0 = Get16(Input, Offset + 0);
int c1 = Get16(Input, Offset + 2);
CLUT[0] = DecodeRGB565(c0);
CLUT[1] = DecodeRGB565(c1);
CLUT[2] = CalculateCLUT2(CLUT[0], CLUT[1], c0, c1, IsBC1);
CLUT[3] = CalculateCLUT3(CLUT[0], CLUT[1], c0, c1, IsBC1);
int Indices = Get32(Input, Offset + 4);
int IdxShift = 0;
byte[] Output = new byte[4 * 4 * 4];
int OOffset = 0;
for (int TY = 0; TY < 4; TY++)
{
for (int TX = 0; TX < 4; TX++)
{
int Idx = (Indices >> IdxShift) & 3;
IdxShift += 2;
Color Pixel = CLUT[Idx];
Output[OOffset + 0] = Pixel.R;
Output[OOffset + 1] = Pixel.G;
Output[OOffset + 2] = Pixel.B;
Output[OOffset + 3] = Pixel.A;
OOffset += 4;
}
}
return Output;
}
private static Color CalculateCLUT2(Color C0, Color C1, int c0, int c1, bool IsBC1)
{
if (c0 > c1 || !IsBC1)
{
return Color.FromArgb(
(2 * C0.R + C1.R) / 3,
(2 * C0.G + C1.G) / 3,
(2 * C0.B + C1.B) / 3);
}
else
{
return Color.FromArgb(
(C0.R + C1.R) / 2,
(C0.G + C1.G) / 2,
(C0.B + C1.B) / 2);
}
}
private static Color CalculateCLUT3(Color C0, Color C1, int c0, int c1, bool IsBC1)
{
if (c0 > c1 || !IsBC1)
{
return
Color.FromArgb(
(2 * C1.R + C0.R) / 3,
(2 * C1.G + C0.G) / 3,
(2 * C1.B + C0.B) / 3);
}
return Color.Transparent;
}
private static Color DecodeRGB565(int Value)
{
int B = ((Value >> 0) & 0x1f) << 3;
int G = ((Value >> 5) & 0x3f) << 2;
int R = ((Value >> 11) & 0x1f) << 3;
return Color.FromArgb(
R | (R >> 5),
G | (G >> 6),
B | (B >> 5));
}
private static int Get16(byte[] Data, int Address)
{
return
Data[Address + 0] << 0 |
Data[Address + 1] << 8;
}
private static int Get32(byte[] Data, int Address)
{
return
Data[Address + 0] << 0 |
Data[Address + 1] << 8 |
Data[Address + 2] << 16 |
Data[Address + 3] << 24;
}
}
}

View file

@ -0,0 +1,144 @@
using System;
namespace Ryujinx.Graphics.Gal.Texture
{
class SwizzleAddr
{
private int Width;
private int XB;
private int YB;
public SwizzleAddr(int Width, int Height, int Pad)
{
int W = Pow2RoundUp(Width);
int H = Pow2RoundUp(Height);
XB = CountZeros(W);
YB = CountZeros(H);
int HH = H >> 1;
if (!IsPow2(Height) && Height <= HH + HH / 3 && YB > 3)
{
YB--;
}
this.Width = RoundSize(Width, Pad);
}
private static int Pow2RoundUp(int Value)
{
Value--;
Value |= (Value >> 1);
Value |= (Value >> 2);
Value |= (Value >> 4);
Value |= (Value >> 8);
Value |= (Value >> 16);
return ++Value;
}
private static bool IsPow2(int Value)
{
return Value != 0 && (Value & (Value - 1)) == 0;
}
private static int CountZeros(int Value)
{
int Count = 0;
for (int i = 0; i < 32; i++)
{
if ((Value & (1 << i)) != 0)
{
break;
}
Count++;
}
return Count;
}
private static int RoundSize(int Size, int Pad)
{
int Mask = Pad - 1;
if ((Size & Mask) != 0)
{
Size &= ~Mask;
Size += Pad;
}
return Size;
}
public int GetSwizzledAddress8(int X, int Y)
{
return GetSwizzledAddress(X, Y, 4);
}
public int GetSwizzledAddress16(int X, int Y)
{
return GetSwizzledAddress(X, Y, 3);
}
public int GetSwizzledAddress32(int X, int Y)
{
return GetSwizzledAddress(X, Y, 2);
}
public int GetSwizzledAddress64(int X, int Y)
{
return GetSwizzledAddress(X, Y, 1);
}
public int GetSwizzledAddress128(int X, int Y)
{
return GetSwizzledAddress(X, Y, 0);
}
private int GetSwizzledAddress(int X, int Y, int XBase)
{
/*
* Examples of patterns:
* x x y x y y x y 0 0 0 0 64 x 64 dxt5
* x x x x x y y y y x y y x y 0 0 0 0 512 x 512 dxt5
* y x x x x x x y y y y x y y x y 0 0 0 0 1024 x 1024 dxt5
* y y x x x x x x y y y y x y y x y x 0 0 0 2048 x 2048 dxt1
* y y y x x x x x x y y y y x y y x y x x 0 0 1024 x 1024 rgba8888
*
* Read from right to left, LSB first.
*/
int XCnt = XBase;
int YCnt = 1;
int XUsed = 0;
int YUsed = 0;
int Address = 0;
while (XUsed < XBase + 2 && XUsed + XCnt < XB)
{
int XMask = (1 << XCnt) - 1;
int YMask = (1 << YCnt) - 1;
Address |= (X & XMask) << XUsed + YUsed;
Address |= (Y & YMask) << XUsed + YUsed + XCnt;
X >>= XCnt;
Y >>= YCnt;
XUsed += XCnt;
YUsed += YCnt;
XCnt = Math.Min(XB - XUsed, 1);
YCnt = Math.Min(YB - YUsed, YCnt << 1);
}
Address |= (X + Y * (Width >> XUsed)) << (XUsed + YUsed);
return Address;
}
}
}

View file

@ -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());
}
}
}