SPIR-V Intermediate Shading Language support

* Enable from Ryujinx.conf

* Adapt to use OpenTK.NetStandard

* Implement usage of UBOs for GLSL and SPIR-V

* Fix a NVidia related issue

* Use constant from UniformBinding
This commit is contained in:
ReinUsesLisp 2018-06-20 16:30:18 +00:00
parent 69697957e6
commit cc298c676a
20 changed files with 5454 additions and 154 deletions

View file

@ -173,10 +173,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL
Type = AttribTypes[Attrib.Size] + (Unsigned ? 1 : 0); Type = AttribTypes[Attrib.Size] + (Unsigned ? 1 : 0);
} }
int Location = Attrib.Index + 1;
int Size = AttribElements[Attrib.Size]; int Size = AttribElements[Attrib.Size];
int Offset = Attrib.Offset; int Offset = Attrib.Offset;
GL.VertexAttribPointer(Attrib.Index, Size, Type, Normalize, Stride, Offset); GL.VertexAttribPointer(Location, Size, Type, Normalize, Stride, Offset);
} }
GL.BindVertexArray(0); GL.BindVertexArray(0);

View file

@ -9,40 +9,33 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{ {
class OGLShader class OGLShader
{ {
private class ShaderStage : IDisposable private enum ShadingLanguage
{ {
public int Handle { get; private set; } Unknown,
GLSL,
SPIRV
}
public bool IsCompiled { get; private set; } private abstract class ShaderStage : IDisposable
{
public int Handle { get; protected set; }
public GalShaderType Type { 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> TextureUsage { get; private set; }
public IEnumerable<ShaderDeclInfo> UniformUsage { get; private set; } public IEnumerable<ShaderDeclInfo> UniformUsage { get; private set; }
public ShaderStage( public ShaderStage(
GalShaderType Type, GalShaderType Type,
string Code,
IEnumerable<ShaderDeclInfo> TextureUsage, IEnumerable<ShaderDeclInfo> TextureUsage,
IEnumerable<ShaderDeclInfo> UniformUsage) IEnumerable<ShaderDeclInfo> UniformUsage)
{ {
this.Type = Type; this.Type = Type;
this.Code = Code;
this.TextureUsage = TextureUsage; this.TextureUsage = TextureUsage;
this.UniformUsage = UniformUsage; this.UniformUsage = UniformUsage;
} }
public void Compile() public abstract void Compile();
{
if (Handle == 0)
{
Handle = GL.CreateShader(OGLEnumConverter.GetShaderType(Type));
CompileAndCheck(Handle, Code);
}
}
public void Dispose() public void Dispose()
{ {
@ -60,6 +53,69 @@ namespace Ryujinx.Graphics.Gal.OpenGL
} }
} }
private class GlslStage : ShaderStage
{
public string Code { get; private set; }
public GlslStage(
GalShaderType Type,
string Code,
IEnumerable<ShaderDeclInfo> TextureUsage,
IEnumerable<ShaderDeclInfo> UniformUsage)
: base(Type, TextureUsage, UniformUsage)
{
this.Code = Code;
}
public override void Compile()
{
if (Handle == 0)
{
Handle = GL.CreateShader(OGLEnumConverter.GetShaderType(Type));
GL.ShaderSource(Handle, Code);
GL.CompileShader(Handle);
CheckCompilation(Handle);
}
}
}
private class SpirvStage : ShaderStage
{
public byte[] Bytecode { get; private set; }
public IDictionary<string, int> Locations { get; private set; }
public SpirvStage(
GalShaderType Type,
byte[] Bytecode,
IEnumerable<ShaderDeclInfo> TextureUsage,
IEnumerable<ShaderDeclInfo> UniformUsage,
IDictionary<string, int> Locations)
: base(Type, TextureUsage, UniformUsage)
{
this.Bytecode = Bytecode;
this.Locations = Locations;
}
public override void Compile()
{
if (Handle == 0)
{
Handle = GL.CreateShader(OGLEnumConverter.GetShaderType(Type));
BinaryFormat SpirvFormat = (BinaryFormat)0x9551;
GL.ShaderBinary(1, new int[]{Handle}, SpirvFormat, Bytecode, Bytecode.Length);
GL.SpecializeShader(Handle, "main", 0, new int[]{}, new int[]{});
CheckCompilation(Handle);
}
}
}
private struct ShaderProgram private struct ShaderProgram
{ {
public ShaderStage Vertex; public ShaderStage Vertex;
@ -69,12 +125,20 @@ namespace Ryujinx.Graphics.Gal.OpenGL
public ShaderStage Fragment; public ShaderStage Fragment;
} }
private const int BuffersPerStage = Shader.UniformBinding.BuffersPerStage;
private const int BufferSize = 16 * 1024; //ARB_uniform_buffer, 16 KiB
private ShaderProgram Current; private ShaderProgram Current;
private ConcurrentDictionary<long, ShaderStage> Stages; private ConcurrentDictionary<long, ShaderStage> Stages;
private Dictionary<ShaderProgram, int> Programs; private Dictionary<ShaderProgram, int> Programs;
private ShadingLanguage Language = ShadingLanguage.Unknown;
private OGLStreamBuffer[][] Buffers;
public int CurrentProgramHandle { get; private set; } public int CurrentProgramHandle { get; private set; }
public OGLShader() public OGLShader()
@ -82,6 +146,48 @@ namespace Ryujinx.Graphics.Gal.OpenGL
Stages = new ConcurrentDictionary<long, ShaderStage>(); Stages = new ConcurrentDictionary<long, ShaderStage>();
Programs = new Dictionary<ShaderProgram, int>(); Programs = new Dictionary<ShaderProgram, int>();
Buffers = new OGLStreamBuffer[5][]; //one per stage
for (int Stage = 0; Stage < 5; Stage++)
{
Buffers[Stage] = new OGLStreamBuffer[BuffersPerStage];
}
}
public void Prepare(bool TrySPIRV)
{
Console.WriteLine(GL.GetInteger(GetPName.MaxUniformBufferBindings));
for (int Stage = 0; Stage < 5; Stage++)
{
for (int Cbuf = 0; Cbuf < BuffersPerStage; Cbuf++)
{
OGLStreamBuffer Buffer = OGLStreamBuffer.Create(BufferTarget.UniformBuffer, BufferSize);
Buffer.Allocate();
Buffers[Stage][Cbuf] = Buffer;
}
}
if (TrySPIRV)
{
if (HasSPIRV())
{
Console.WriteLine("SPIR-V Shading Language");
Language = ShadingLanguage.SPIRV;
}
else
{
Console.WriteLine("GLSL fallback (SPIR-V not available)");
Language = ShadingLanguage.GLSL;
}
}
else
{
Language = ShadingLanguage.GLSL;
}
} }
public void Create(IGalMemory Memory, long Tag, GalShaderType Type) public void Create(IGalMemory Memory, long Tag, GalShaderType Type)
@ -90,16 +196,37 @@ namespace Ryujinx.Graphics.Gal.OpenGL
} }
private ShaderStage ShaderStageFactory(IGalMemory Memory, long Position, GalShaderType Type) private ShaderStage ShaderStageFactory(IGalMemory Memory, long Position, GalShaderType Type)
{
switch (Language)
{
case ShadingLanguage.SPIRV:
{
SpirvProgram Program = GetSpirvProgram(Memory, Position, Type);
return new SpirvStage(
Type,
Program.Bytecode,
Program.Textures,
Program.Uniforms,
Program.Locations);
}
case ShadingLanguage.GLSL:
{ {
GlslProgram Program = GetGlslProgram(Memory, Position, Type); GlslProgram Program = GetGlslProgram(Memory, Position, Type);
return new ShaderStage( return new GlslStage(
Type, Type,
Program.Code, Program.Code,
Program.Textures, Program.Textures,
Program.Uniforms); Program.Uniforms);
} }
default:
throw new InvalidOperationException();
}
}
private GlslProgram GetGlslProgram(IGalMemory Memory, long Position, GalShaderType Type) private GlslProgram GetGlslProgram(IGalMemory Memory, long Position, GalShaderType Type)
{ {
GlslDecompiler Decompiler = new GlslDecompiler(); GlslDecompiler Decompiler = new GlslDecompiler();
@ -107,6 +234,13 @@ namespace Ryujinx.Graphics.Gal.OpenGL
return Decompiler.Decompile(Memory, Position + 0x50, Type); return Decompiler.Decompile(Memory, Position + 0x50, Type);
} }
private SpirvProgram GetSpirvProgram(IGalMemory Memory, long Position, GalShaderType Type)
{
SpirvDecompiler Decompiler = new SpirvDecompiler();
return Decompiler.Decompile(Memory, Position + 0x50, Type);
}
public IEnumerable<ShaderDeclInfo> GetTextureUsage(long Tag) public IEnumerable<ShaderDeclInfo> GetTextureUsage(long Tag)
{ {
if (Stages.TryGetValue(Tag, out ShaderStage Stage)) if (Stages.TryGetValue(Tag, out ShaderStage Stage))
@ -117,6 +251,73 @@ namespace Ryujinx.Graphics.Gal.OpenGL
return Enumerable.Empty<ShaderDeclInfo>(); return Enumerable.Empty<ShaderDeclInfo>();
} }
private int GetUniformLocation(string Name, ShaderStage Stage)
{
switch (Language)
{
case ShadingLanguage.SPIRV:
{
SpirvStage Spirv = (SpirvStage)Stage;
if (Spirv.Locations.TryGetValue(Name, out int Location))
{
return Location;
}
break;
}
case ShadingLanguage.GLSL:
{
return GL.GetUniformLocation(CurrentProgramHandle, Name);
}
}
throw new InvalidOperationException();
}
private bool TrySpirvStageLocation(string Name, ShaderStage Stage, out int Location)
{
if (Stage == null)
{
Location = -1;
return false;
}
SpirvStage Spirv = (SpirvStage)Stage;
return Spirv.Locations.TryGetValue(Name, out Location);
}
private int GetSpirvLocation(string Name)
{
int Location;
if (TrySpirvStageLocation(Name, Current.Vertex, out Location)
|| TrySpirvStageLocation(Name, Current.TessControl, out Location)
|| TrySpirvStageLocation(Name, Current.TessEvaluation, out Location)
|| TrySpirvStageLocation(Name, Current.Geometry, out Location)
|| TrySpirvStageLocation(Name, Current.Fragment, out Location))
{
return Location;
}
throw new InvalidOperationException();
}
private int GetUniformLocation(string Name)
{
switch (Language)
{
case ShadingLanguage.SPIRV:
return GetSpirvLocation(Name);
case ShadingLanguage.GLSL:
return GL.GetUniformLocation(CurrentProgramHandle, Name);
}
throw new InvalidOperationException();
}
public void SetConstBuffer(long Tag, int Cbuf, byte[] Data) public void SetConstBuffer(long Tag, int Cbuf, byte[] Data)
{ {
BindProgram(); BindProgram();
@ -125,21 +326,21 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{ {
foreach (ShaderDeclInfo DeclInfo in Stage.UniformUsage.Where(x => x.Cbuf == Cbuf)) foreach (ShaderDeclInfo DeclInfo in Stage.UniformUsage.Where(x => x.Cbuf == Cbuf))
{ {
int Location = GL.GetUniformLocation(CurrentProgramHandle, DeclInfo.Name); if (Cbuf >= BuffersPerStage)
int Count = Data.Length >> 2;
//The Index is the index of the last element,
//so we can add 1 to get the uniform array size.
Count = Math.Min(Count, DeclInfo.Index + 1);
unsafe
{ {
fixed (byte* Ptr = Data) string Message = $"Game tried to write constant buffer #{Cbuf} but only 0-#{BuffersPerStage-1} are supported";
{ throw new NotSupportedException(Message);
GL.Uniform1(Location, Count, (float*)Ptr);
}
} }
OGLStreamBuffer Buffer = Buffers[(int)Stage.Type][Cbuf];
int Size = Math.Min(Data.Length, BufferSize);
byte[] Destiny = Buffer.Map(Size);
Array.Copy(Data, Destiny, Size);
Buffer.Unmap(Size);
} }
} }
} }
@ -148,7 +349,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{ {
BindProgram(); BindProgram();
int Location = GL.GetUniformLocation(CurrentProgramHandle, UniformName); int Location = GetUniformLocation(UniformName);
GL.Uniform1(Location, Value); GL.Uniform1(Location, Value);
} }
@ -157,7 +358,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{ {
BindProgram(); BindProgram();
int Location = GL.GetUniformLocation(CurrentProgramHandle, UniformName); int Location = GetUniformLocation(UniformName);
GL.Uniform2(Location, X, Y); GL.Uniform2(Location, X, Y);
} }
@ -204,11 +405,33 @@ namespace Ryujinx.Graphics.Gal.OpenGL
CheckProgramLink(Handle); CheckProgramLink(Handle);
if (Language == ShadingLanguage.GLSL)
{
BindUniformBlocksIfNotNull(Handle, Current.Vertex);
BindUniformBlocksIfNotNull(Handle, Current.TessControl);
BindUniformBlocksIfNotNull(Handle, Current.TessEvaluation);
BindUniformBlocksIfNotNull(Handle, Current.Geometry);
BindUniformBlocksIfNotNull(Handle, Current.Fragment);
}
Programs.Add(Current, Handle); Programs.Add(Current, Handle);
} }
GL.UseProgram(Handle); GL.UseProgram(Handle);
//TODO: This could be done once, right?
for (int Stage = 0; Stage < 5; Stage++)
{
for (int Cbuf = 0; Cbuf < BuffersPerStage; Cbuf++)
{
OGLStreamBuffer Buffer = Buffers[Stage][Cbuf];
int Binding = Shader.UniformBinding.Get((GalShaderType)Stage, Cbuf);
GL.BindBufferBase(BufferRangeTarget.UniformBuffer, Binding, Buffer.Handle);
}
}
CurrentProgramHandle = Handle; CurrentProgramHandle = Handle;
} }
@ -222,12 +445,24 @@ namespace Ryujinx.Graphics.Gal.OpenGL
} }
} }
public static void CompileAndCheck(int Handle, string Code) private void BindUniformBlocksIfNotNull(int ProgramHandle, ShaderStage Stage)
{ {
GL.ShaderSource(Handle, Code); if (Stage != null)
GL.CompileShader(Handle); {
foreach (ShaderDeclInfo DeclInfo in Stage.UniformUsage)
{
int BlockIndex = GL.GetUniformBlockIndex(ProgramHandle, DeclInfo.Name);
CheckCompilation(Handle); if (BlockIndex < 0)
{
throw new InvalidOperationException();
}
int Binding = Shader.UniformBinding.Get(Stage.Type, DeclInfo.Cbuf);
GL.UniformBlockBinding(ProgramHandle, BlockIndex, Binding);
}
}
} }
private static void CheckCompilation(int Handle) private static void CheckCompilation(int Handle)
@ -253,5 +488,20 @@ namespace Ryujinx.Graphics.Gal.OpenGL
throw new ShaderException(GL.GetProgramInfoLog(Handle)); throw new ShaderException(GL.GetProgramInfoLog(Handle));
} }
} }
private static bool HasSPIRV()
{
int ExtensionCount = GL.GetInteger(GetPName.NumExtensions);
for (int i = 0; i < ExtensionCount; i++)
{
if (GL.GetString(StringNameIndexed.Extensions, i) == "GL_ARB_gl_spirv")
{
return true;
}
}
return false;
}
} }
} }

View file

@ -0,0 +1,125 @@
using System;
using OpenTK.Graphics.OpenGL;
namespace Ryujinx.Graphics.Gal.OpenGL
{
abstract class OGLStreamBuffer : IDisposable
{
public int Handle { get; protected set; }
protected BufferTarget Target;
protected int Size;
private bool Mapped = false;
public OGLStreamBuffer(BufferTarget Target, int MaxSize)
{
Handle = 0;
Mapped = false;
this.Target = Target;
this.Size = MaxSize;
}
public static OGLStreamBuffer Create(BufferTarget Target, int MaxSize)
{
return new SubDataBuffer(Target, MaxSize);
}
public void Allocate()
{
if (this.Handle == 0)
{
GL.CreateBuffers(1, out int Handle);
this.Handle = Handle;
InternAllocate();
}
}
public byte[] Map(int Size)
{
if (Handle == 0 || Mapped || Size > this.Size)
{
throw new InvalidOperationException();
}
byte[] Memory = InternMap(Size);
Mapped = true;
return Memory;
}
public void Unmap(int UsedSize)
{
if (Handle == 0 || !Mapped)
{
throw new InvalidOperationException();
}
InternUnmap(UsedSize);
Mapped = false;
}
protected abstract void InternAllocate();
protected abstract byte[] InternMap(int Size);
protected abstract void InternUnmap(int UsedSize);
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing && Handle != 0)
{
GL.DeleteBuffer(Handle);
Handle = 0;
}
}
}
class SubDataBuffer : OGLStreamBuffer
{
private byte[] Memory;
public SubDataBuffer(BufferTarget Target, int MaxSize)
: base(Target, MaxSize)
{
Memory = new byte[MaxSize];
}
protected override void InternAllocate()
{
GL.BindBuffer(Target, Handle);
GL.BufferData(Target, Size, IntPtr.Zero, BufferUsageHint.StreamDraw);
}
protected override byte[] InternMap(int Size)
{
return Memory;
}
protected override void InternUnmap(int UsedSize)
{
GL.BindBuffer(Target, Handle);
unsafe
{
fixed (byte* MemoryPtr = Memory)
{
GL.BufferSubData(Target, IntPtr.Zero, UsedSize, (IntPtr)MemoryPtr);
}
}
}
}
}

View file

@ -34,6 +34,14 @@ namespace Ryujinx.Graphics.Gal.OpenGL
ActionsQueue = new ConcurrentQueue<Action>(); ActionsQueue = new ConcurrentQueue<Action>();
} }
public void Initialize(bool TrySPIRV = false)
{
ActionsQueue.Enqueue(() =>
{
Shader.Prepare(TrySPIRV);
});
}
public void QueueAction(Action ActionMthd) public void QueueAction(Action ActionMthd)
{ {
ActionsQueue.Enqueue(ActionMthd); ActionsQueue.Enqueue(ActionMthd);

View file

@ -6,19 +6,12 @@ using System.Text;
namespace Ryujinx.Graphics.Gal.Shader namespace Ryujinx.Graphics.Gal.Shader
{ {
public class GlslDecompiler public class GlslDecompiler : ShaderDecompiler
{ {
private delegate string GetInstExpr(ShaderIrOp Op); private delegate string GetInstExpr(ShaderIrOp Op);
private Dictionary<ShaderIrInst, GetInstExpr> InstsExpr; private Dictionary<ShaderIrInst, GetInstExpr> InstsExpr;
private enum OperType
{
Bool,
F32,
I32
}
private const string IdentationStr = " "; private const string IdentationStr = " ";
private static string[] ElemTypes = new string[] { "float", "vec2", "vec3", "vec4" }; private static string[] ElemTypes = new string[] { "float", "vec2", "vec3", "vec4" };
@ -145,7 +138,9 @@ namespace Ryujinx.Graphics.Gal.Shader
foreach (ShaderDeclInfo DeclInfo in Decl.Uniforms.Values.OrderBy(DeclKeySelector)) foreach (ShaderDeclInfo DeclInfo in Decl.Uniforms.Values.OrderBy(DeclKeySelector))
{ {
SB.AppendLine($"uniform {GetDecl(DeclInfo)}[{DeclInfo.Index + 1}];"); SB.AppendLine($"layout (std140) uniform {DeclInfo.Name} {{");
SB.AppendLine($" vec4 {DeclInfo.Name}_data[{DeclInfo.Index / 4 + 1}];");
SB.AppendLine($"}};");
} }
if (Decl.Uniforms.Count > 0) if (Decl.Uniforms.Count > 0)
@ -158,7 +153,7 @@ namespace Ryujinx.Graphics.Gal.Shader
{ {
if (Decl.ShaderType == GalShaderType.Fragment) if (Decl.ShaderType == GalShaderType.Fragment)
{ {
SB.AppendLine("in vec4 " + GlslDecl.PositionOutAttrName + ";"); SB.AppendLine("layout (location = 0) in vec4 " + GlslDecl.PositionOutAttrName + ";");
} }
PrintDeclAttributes(Decl.InAttributes.Values, "in"); PrintDeclAttributes(Decl.InAttributes.Values, "in");
@ -168,7 +163,7 @@ namespace Ryujinx.Graphics.Gal.Shader
{ {
if (Decl.ShaderType == GalShaderType.Vertex) if (Decl.ShaderType == GalShaderType.Vertex)
{ {
SB.AppendLine("out vec4 " + GlslDecl.PositionOutAttrName + ";"); SB.AppendLine("layout (location = 0) out vec4 " + GlslDecl.PositionOutAttrName + ";");
} }
PrintDeclAttributes(Decl.OutAttributes.Values, "out"); PrintDeclAttributes(Decl.OutAttributes.Values, "out");
@ -182,7 +177,9 @@ namespace Ryujinx.Graphics.Gal.Shader
{ {
if (DeclInfo.Index >= 0) if (DeclInfo.Index >= 0)
{ {
SB.AppendLine("layout (location = " + DeclInfo.Index + ") " + InOut + " " + GetDecl(DeclInfo) + ";"); int Location = DeclInfo.Index + 1;
SB.AppendLine("layout (location = " + Location + ") " + InOut + " " + GetDecl(DeclInfo) + ";");
Count++; Count++;
} }
@ -232,11 +229,6 @@ namespace Ryujinx.Graphics.Gal.Shader
} }
} }
private int DeclKeySelector(ShaderDeclInfo DeclInfo)
{
return DeclInfo.Cbuf << 24 | DeclInfo.Index;
}
private string GetDecl(ShaderDeclInfo DeclInfo) private string GetDecl(ShaderDeclInfo DeclInfo)
{ {
return ElemTypes[DeclInfo.Size - 1] + " " + DeclInfo.Name; return ElemTypes[DeclInfo.Size - 1] + " " + DeclInfo.Name;
@ -437,20 +429,6 @@ namespace Ryujinx.Graphics.Gal.Shader
return Tail; return Tail;
} }
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) private string GetDstOperName(ShaderIrNode Node)
{ {
if (Node is ShaderIrOperAbuf Abuf) if (Node is ShaderIrOperAbuf Abuf)
@ -534,11 +512,13 @@ namespace Ryujinx.Graphics.Gal.Shader
//This may not be aways the case. //This may not be aways the case.
string Offset = "(floatBitsToInt(" + GetSrcExpr(Cbuf.Offs) + ") >> 2)"; string Offset = "(floatBitsToInt(" + GetSrcExpr(Cbuf.Offs) + ") >> 2)";
return DeclInfo.Name + "[" + Cbuf.Pos + " + " + Offset + "]"; string Index = "(" + Cbuf.Pos + " + " + Offset + ")";
return $"{DeclInfo.Name}_data[{Index} / 4][{Index} % 4]";
} }
else else
{ {
return DeclInfo.Name + "[" + Cbuf.Pos + "]"; return $"{DeclInfo.Name}_data[{Cbuf.Pos / 4}][{Cbuf.Pos % 4}]";
} }
} }
@ -950,65 +930,5 @@ namespace Ryujinx.Graphics.Gal.Shader
return Expr; return Expr;
} }
private static OperType GetDstNodeType(ShaderIrNode Node)
{
//Special case instructions with the result type different
//from the input types (like integer <-> float conversion) here.
if (Node is ShaderIrOp Op)
{
switch (Op.Inst)
{
case ShaderIrInst.Stof:
case ShaderIrInst.Txlf:
case ShaderIrInst.Utof:
return OperType.F32;
case ShaderIrInst.Ftos:
case ShaderIrInst.Ftou:
return OperType.I32;
}
}
return GetSrcNodeType(Node);
}
private static OperType GetSrcNodeType(ShaderIrNode Node)
{
switch (Node)
{
case ShaderIrOperAbuf Abuf:
return Abuf.Offs == GlslDecl.VertexIdAttr ||
Abuf.Offs == GlslDecl.InstanceIdAttr
? 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

@ -2,21 +2,17 @@ using System.Collections.Generic;
namespace Ryujinx.Graphics.Gal.Shader namespace Ryujinx.Graphics.Gal.Shader
{ {
public struct GlslProgram public class GlslProgram : ShaderProgram
{ {
public string Code { get; private set; } public string Code { get; private set; }
public IEnumerable<ShaderDeclInfo> Textures { get; private set; }
public IEnumerable<ShaderDeclInfo> Uniforms { get; private set; }
public GlslProgram( public GlslProgram(
string Code, string Code,
IEnumerable<ShaderDeclInfo> Textures, IEnumerable<ShaderDeclInfo> Textures,
IEnumerable<ShaderDeclInfo> Uniforms) IEnumerable<ShaderDeclInfo> Uniforms)
: base(Textures, Uniforms)
{ {
this.Code = Code; this.Code = Code;
this.Textures = Textures;
this.Uniforms = Uniforms;
} }
} }
} }

View file

@ -0,0 +1,70 @@
using System.IO;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gal.Shader.SPIRV
{
public class Assembler
{
private List<Instruction> Instructions;
public Assembler()
{
Instructions = new List<Instruction>();
}
public void Write(Stream Output)
{
uint Bound = DoBindings();
BinaryWriter BW = new BinaryWriter(Output);
BW.Write((uint)BinaryForm.MagicNumber);
BW.Write((uint)BinaryForm.VersionNumber);
BW.Write((uint)BinaryForm.GeneratorMagicNumber);
BW.Write((uint)Bound);
BW.Write((uint)0); // Reserved for instruction schema
foreach (Instruction Instruction in Instructions)
{
Instruction.Write(BW);
}
}
public void Add(Instruction Instruction)
{
Instructions.Add(Instruction);
}
public void Add(Instruction[] Instructions)
{
foreach (Instruction Instruction in Instructions)
{
Add(Instruction);
}
}
public void Add(List<Instruction> Instructions)
{
foreach (Instruction Instruction in Instructions)
{
Add(Instruction);
}
}
private uint DoBindings()
{
uint Bind = 1;
foreach (Instruction Instruction in Instructions)
{
if (Instruction.HoldsResultId)
{
Instruction.ResultId = Bind;
Bind++;
}
}
return Bind;
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,162 @@
using System;
using System.IO;
using System.Text;
namespace Ryujinx.Graphics.Gal.Shader.SPIRV
{
public abstract class Operand
{
public abstract int GetWordCount();
public abstract void Write(BinaryWriter BinaryWriter);
}
public class Id: Operand
{
private Instruction Instruction;
public Id(Instruction Instruction)
{
this.Instruction = Instruction;
}
public override void Write(BinaryWriter BinaryWriter)
{
BinaryWriter.Write((uint)Instruction.ResultId);
}
public override int GetWordCount()
{
return 1;
}
}
public abstract class Literal: Operand
{
}
public class LiteralString: Literal
{
public byte[] Value;
public LiteralString(string String)
{
Value = Encoding.UTF8.GetBytes(String);
}
public override void Write(BinaryWriter BinaryWriter)
{
BinaryWriter.Write(Value);
// Write remaining zero bytes
for (int i = 0; i < 4 - (Value.Length % 4); i++)
{
BinaryWriter.Write((byte)0);
}
}
public override int GetWordCount()
{
return Value.Length / 4 + 1;
}
public override bool Equals(object Object)
{
if (Object is LiteralString Other)
{
return this.Value == Other.Value;
}
return false;
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
}
public class LiteralNumber: Literal
{
public TypeCode Type;
public int Integer;
public float Float32;
public double Float64;
public LiteralNumber(int Value)
{
Integer = Value;
Type = Value.GetTypeCode();
}
public LiteralNumber(float Value)
{
Float32 = Value;
Type = Value.GetTypeCode();
}
public LiteralNumber(double Value)
{
Float64 = Value;
Type = Value.GetTypeCode();
}
public override void Write(BinaryWriter BinaryWriter)
{
switch (Type)
{
case TypeCode.Int32:
BinaryWriter.Write(Integer);
break;
case TypeCode.Single:
BinaryWriter.Write(Float32);
break;
case TypeCode.Double:
BinaryWriter.Write(Float64);
break;
default:
throw new NotImplementedException();
}
}
public override int GetWordCount()
{
switch (Type)
{
case TypeCode.Int32:
case TypeCode.Single:
return 1;
case TypeCode.Double:
return 2;
default:
throw new NotImplementedException();
}
}
public override bool Equals(object Object)
{
if (Object is LiteralNumber Other && this.Type == Other.Type)
{
return this.Integer == Other.Integer
&& this.Float32 == Other.Float32
&& this.Float64 == Other.Float64;
}
return false;
}
public override int GetHashCode()
{
return Type.GetHashCode() + Integer.GetHashCode()
+ Float32.GetHashCode() + Float64.GetHashCode();
}
}
}

View file

@ -0,0 +1,28 @@
using System;
namespace Ryujinx.Graphics.Gal.Shader
{
static class UniformBinding
{
public const int BuffersPerStage = 12; //ARB_uniform_buffer
public static int Get(GalShaderType Stage, int Cbuf)
{
return GetStageIndex(Stage) * BuffersPerStage + Cbuf;
}
private static int GetStageIndex(GalShaderType Stage)
{
switch (Stage)
{
case GalShaderType.Vertex: return 0;
case GalShaderType.Fragment: return 1;
case GalShaderType.Geometry: return 2;
case GalShaderType.TessControl: return 3;
case GalShaderType.TessEvaluation: return 4;
}
throw new ArgumentException();
}
}
}

View file

@ -0,0 +1,93 @@
using System;
namespace Ryujinx.Graphics.Gal.Shader
{
public class ShaderDecompiler
{
protected enum OperType
{
Bool,
F32,
I32
}
protected static 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;
}
protected static OperType GetDstNodeType(ShaderIrNode Node)
{
//Special case instructions with the result type different
//from the input types (like integer <-> float conversion) here.
if (Node is ShaderIrOp Op)
{
switch (Op.Inst)
{
case ShaderIrInst.Stof:
case ShaderIrInst.Txlf:
case ShaderIrInst.Utof:
return OperType.F32;
case ShaderIrInst.Ftos:
case ShaderIrInst.Ftou:
return OperType.I32;
}
}
return GetSrcNodeType(Node);
}
protected static OperType GetSrcNodeType(ShaderIrNode Node)
{
switch (Node)
{
case ShaderIrOperAbuf Abuf:
return Abuf.Offs == GlslDecl.VertexIdAttr ||
Abuf.Offs == GlslDecl.InstanceIdAttr
? 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));
}
protected static int DeclKeySelector(ShaderDeclInfo DeclInfo)
{
return DeclInfo.Cbuf << 24 | DeclInfo.Index;
}
}
}

View file

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

View file

@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gal.Shader
{
public class ShaderProgram
{
public IEnumerable<ShaderDeclInfo> Textures { get; private set; }
public IEnumerable<ShaderDeclInfo> Uniforms { get; private set; }
public ShaderProgram(
IEnumerable<ShaderDeclInfo> Textures,
IEnumerable<ShaderDeclInfo> Uniforms)
{
this.Textures = Textures;
this.Uniforms = Uniforms;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,22 @@
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gal.Shader
{
public class SpirvProgram : ShaderProgram
{
public byte[] Bytecode { get; private set; }
public IDictionary<string, int> Locations { get; private set; }
public SpirvProgram(
byte[] Bytecode,
IDictionary<string, int> Locations,
IEnumerable<ShaderDeclInfo> Textures,
IEnumerable<ShaderDeclInfo> Uniforms)
: base(Textures, Uniforms)
{
this.Bytecode = Bytecode;
this.Locations = Locations;
}
}
}

View file

@ -2,6 +2,7 @@
using Ryujinx.Graphics.Gal.Shader; using Ryujinx.Graphics.Gal.Shader;
using System; using System;
using System.IO; using System.IO;
using System.Text;
namespace Ryujinx.ShaderTools namespace Ryujinx.ShaderTools
{ {
@ -9,13 +10,11 @@ namespace Ryujinx.ShaderTools
{ {
static void Main(string[] args) static void Main(string[] args)
{ {
if (args.Length == 2) if (args.Length == 4)
{ {
GlslDecompiler Decompiler = new GlslDecompiler();
GalShaderType ShaderType = GalShaderType.Vertex; GalShaderType ShaderType = GalShaderType.Vertex;
switch (args[0].ToLower()) switch (args[1].ToLower())
{ {
case "v": ShaderType = GalShaderType.Vertex; break; case "v": ShaderType = GalShaderType.Vertex; break;
case "tc": ShaderType = GalShaderType.TessControl; break; case "tc": ShaderType = GalShaderType.TessControl; break;
@ -24,18 +23,40 @@ namespace Ryujinx.ShaderTools
case "f": ShaderType = GalShaderType.Fragment; break; case "f": ShaderType = GalShaderType.Fragment; break;
} }
using (FileStream FS = new FileStream(args[1], FileMode.Open, FileAccess.Read)) using (FileStream Output = new FileStream(args[3], FileMode.Create))
using (FileStream FS = new FileStream(args[2], FileMode.Open, FileAccess.Read))
{ {
Memory Mem = new Memory(FS); Memory Mem = new Memory(FS);
GlslProgram Program = Decompiler.Decompile(Mem, 0, ShaderType); switch (args[0].ToLower())
{
case "glsl":
{
GlslDecompiler GlslDecompiler = new GlslDecompiler();
Console.WriteLine(Program.Code); GlslProgram Program = GlslDecompiler.Decompile(Mem, 0, ShaderType);
Output.Write(System.Text.Encoding.UTF8.GetBytes(Program.Code));
break;
}
case "spirv":
{
SpirvDecompiler SpirvDecompiler = new SpirvDecompiler();
SpirvProgram Program = SpirvDecompiler.Decompile(Mem, 0, ShaderType);
Output.Write(Program.Bytecode);
break;
}
}
} }
} }
else else
{ {
Console.WriteLine("Usage: Ryujinx.ShaderTools [v|tc|te|g|f] shader.bin"); Console.WriteLine("Usage: Ryujinx.ShaderTools [spirv|glsl] [v|tc|te|g|f] shader.bin output.bin");
} }
} }
} }

View file

@ -12,6 +12,8 @@ namespace Ryujinx
{ {
public static JoyCon FakeJoyCon { get; private set; } public static JoyCon FakeJoyCon { get; private set; }
public static bool EnableSPIRV { get; private set; }
public static void Read(Logger Log) public static void Read(Logger Log)
{ {
string IniFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); string IniFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
@ -22,6 +24,8 @@ namespace Ryujinx
AOptimizations.DisableMemoryChecks = !Convert.ToBoolean(Parser.Value("Enable_Memory_Checks")); AOptimizations.DisableMemoryChecks = !Convert.ToBoolean(Parser.Value("Enable_Memory_Checks"));
EnableSPIRV = Convert.ToBoolean(Parser.Value("Enable_SPIRV"));
Log.SetEnable(LogLevel.Debug, Convert.ToBoolean(Parser.Value("Logging_Enable_Debug"))); Log.SetEnable(LogLevel.Debug, Convert.ToBoolean(Parser.Value("Logging_Enable_Debug")));
Log.SetEnable(LogLevel.Stub, Convert.ToBoolean(Parser.Value("Logging_Enable_Stub"))); Log.SetEnable(LogLevel.Stub, Convert.ToBoolean(Parser.Value("Logging_Enable_Stub")));
Log.SetEnable(LogLevel.Info, Convert.ToBoolean(Parser.Value("Logging_Enable_Info"))); Log.SetEnable(LogLevel.Info, Convert.ToBoolean(Parser.Value("Logging_Enable_Info")));

View file

@ -1,6 +1,9 @@
#Enable cpu memory checks (slow) #Enable cpu memory checks (slow)
Enable_Memory_Checks = false Enable_Memory_Checks = false
#Enable SPIR-V shading language (experimental)
Enable_SPIRV = false
#Enable print debug logs #Enable print debug logs
Logging_Enable_Debug = false Logging_Enable_Debug = false

View file

@ -2,6 +2,7 @@ using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using OpenTK.Input; using OpenTK.Input;
using Ryujinx.Graphics.Gal; using Ryujinx.Graphics.Gal;
using Ryujinx.Graphics.Gal.OpenGL;
using Ryujinx.HLE; using Ryujinx.HLE;
using Ryujinx.HLE.Input; using Ryujinx.HLE.Input;
using System; using System;
@ -42,6 +43,11 @@ namespace Ryujinx
{ {
VSync = VSyncMode.On; VSync = VSyncMode.On;
if (Renderer is OpenGLRenderer OGL)
{
OGL.Initialize(Config.EnableSPIRV);
}
Renderer.SetWindowSize(Width, Height); Renderer.SetWindowSize(Width, Height);
} }