diff --git a/Ryujinx.Graphics.Gpu/Constants.cs b/Ryujinx.Graphics.Gpu/Constants.cs
index 455029f54..1738fddf7 100644
--- a/Ryujinx.Graphics.Gpu/Constants.cs
+++ b/Ryujinx.Graphics.Gpu/Constants.cs
@@ -69,7 +69,7 @@ namespace Ryujinx.Graphics.Gpu
///
/// Maximum number of vertex attributes.
///
- public const int TotalVertexAttribs = 16;
+ public const int TotalVertexAttribs = 16; // FIXME: Should be 32, but OpenGL only supports 16.
///
/// Maximum number of vertex buffers.
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
index 172e8c370..26260ce59 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
@@ -768,8 +768,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
public fixed uint Reserved10B0[18];
public uint ClearFlags;
public fixed uint Reserved10FC[25];
- public Array16 VertexAttribState;
- public fixed uint Reserved11A0[31];
+ public Array32 VertexAttribState;
+ public fixed uint Reserved11E0[15];
public RtControl RtControl;
public fixed uint Reserved1220[2];
public Size3D RtDepthStencilSize;
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
index 9ffece6a2..8cacd8956 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
@@ -261,7 +261,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
return gpShaders;
}
- AttributeType[] attributeTypes = new AttributeType[Constants.TotalVertexAttribs];
+ AttributeType[] attributeTypes = new AttributeType[32];
for (int location = 0; location < attributeTypes.Length; location++)
{
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
index c5d60ee16..74dfd0236 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs
@@ -275,7 +275,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
string name = builtInAttr.Name;
- if (!perPatch && IsArrayAttribute(config.Stage, isOutAttr) && IsArrayBuiltIn(value))
+ if (!perPatch && IsArrayAttribute(config.Stage, isOutAttr) && AttributeInfo.IsArrayBuiltIn(value))
{
name = isOutAttr ? $"gl_out[gl_InvocationID].{name}" : $"gl_in[{indexExpr}].{name}";
}
@@ -317,18 +317,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
}
- private static bool IsArrayBuiltIn(int attr)
- {
- if (attr <= AttributeConsts.TessLevelInner1 ||
- attr == AttributeConsts.TessCoordX ||
- attr == AttributeConsts.TessCoordY)
- {
- return false;
- }
-
- return (attr & AttributeConsts.SpecialMask) == 0;
- }
-
public static string GetUbName(ShaderStage stage, int slot, bool cbIndexable)
{
if (cbIndexable)
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs
index 84de8bd6e..28d0aeab7 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs
@@ -15,6 +15,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
public ShaderConfig Config { get; }
+ public int InputVertices { get; }
+
public Dictionary UniformBuffers { get; } = new Dictionary();
public Instruction StorageBuffersArray { get; set; }
public Instruction LocalMemory { get; set; }
@@ -65,6 +67,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
Config = config;
+ if (config.Stage == ShaderStage.Geometry)
+ {
+ InputTopology inPrimitive = config.GpuAccessor.QueryPrimitiveTopology();
+
+ InputVertices = inPrimitive switch
+ {
+ InputTopology.Points => 1,
+ InputTopology.Lines => 2,
+ InputTopology.LinesAdjacency => 2,
+ InputTopology.Triangles => 3,
+ InputTopology.TrianglesAdjacency => 3,
+ _ => throw new InvalidOperationException($"Invalid input topology \"{inPrimitive}\".")
+ };
+ }
+
AddCapability(Capability.Shader);
AddCapability(Capability.Float64);
@@ -171,7 +188,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return operand.Type switch
{
IrOperandType.Argument => GetArgument(type, operand),
- IrOperandType.Attribute => GetAttribute(type, operand, false),
+ IrOperandType.Attribute => GetAttribute(type, operand.Value & AttributeConsts.Mask, (operand.Value & AttributeConsts.LoadOutputMask) != 0),
IrOperandType.Constant => GetConstant(type, operand),
IrOperandType.ConstantBuffer => GetConstantBuffer(type, operand),
IrOperandType.LocalVariable => GetLocal(type, operand),
@@ -190,16 +207,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return isOutAttr ? Outputs[attrInfo.BaseValue] : Inputs[attrInfo.BaseValue];
}
- public Instruction GetAttributeElemPointer(AstOperand operand, bool isOutAttr, out AggregateType elemType)
+ public Instruction GetAttributeElemPointer(int attr, bool isOutAttr, Instruction index, out AggregateType elemType)
{
- var attrInfo = AttributeInfo.From(Config, operand.Value);
- if (attrInfo.BaseValue == AttributeConsts.PositionX && Config.Stage != ShaderStage.Fragment)
- {
- isOutAttr = true;
- }
+ var attrInfo = AttributeInfo.From(Config, attr);
elemType = attrInfo.Type & AggregateType.ElementTypeMask;
+ var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
+
+ var elemIndex = Constant(TypeU32(), attrInfo.GetInnermostIndex());
+
var ioVariable = isOutAttr ? Outputs[attrInfo.BaseValue] : Inputs[attrInfo.BaseValue];
if ((attrInfo.Type & (AggregateType.Array | AggregateType.Vector)) == 0)
@@ -207,15 +224,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return ioVariable;
}
- var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
-
- var elemIndex = Constant(TypeU32(), attrInfo.GetInnermostIndex());
- return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, elemIndex);
+ if (Config.Stage == ShaderStage.Geometry && !isOutAttr && (!attrInfo.IsBuiltin || AttributeInfo.IsArrayBuiltIn(attr)))
+ {
+ return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, elemIndex);
+ }
+ else
+ {
+ return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, elemIndex);
+ }
}
- public Instruction GetAttribute(AggregateType type, AstOperand operand, bool isOutAttr)
+ public Instruction GetAttribute(AggregateType type, int attr, bool isOutAttr, Instruction index = null)
{
- var elemPointer = GetAttributeElemPointer(operand, isOutAttr, out var elemType);
+ var elemPointer = GetAttributeElemPointer(attr, isOutAttr, index, out var elemType);
return BitcastIfNeeded(type, elemType, Load(GetType(elemType), elemPointer));
}
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
index b097f64b9..83f94dbf6 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
@@ -391,19 +391,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var dict = isOutAttr ? context.Outputs : context.Inputs;
var attrInfo = AttributeInfo.From(context.Config, attr);
- if (attrInfo.BaseValue == AttributeConsts.PositionX && context.Config.Stage != ShaderStage.Fragment)
- {
- isOutAttr = true;
- dict = context.Outputs;
- }
-
if (dict.ContainsKey(attrInfo.BaseValue))
{
return;
}
var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
- var spvType = context.TypePointer(storageClass, context.GetType(attrInfo.Type, attrInfo.Length));
+ var attrType = context.GetType(attrInfo.Type, attrInfo.Length);
+
+ if (context.Config.Stage == ShaderStage.Geometry && !isOutAttr && (!attrInfo.IsBuiltin || AttributeInfo.IsArrayBuiltIn(attr)))
+ {
+ attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)context.InputVertices));
+ }
+
+ var spvType = context.TypePointer(storageClass, attrType);
var spvVar = context.Variable(spvType, storageClass);
if (attrInfo.IsBuiltin)
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
index 3375e8d64..f1d72a95b 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
@@ -73,6 +73,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Add(Instruction.Ddy, GenerateDdy);
Add(Instruction.Discard, GenerateDiscard);
Add(Instruction.Divide, GenerateDivide);
+ Add(Instruction.EmitVertex, GenerateEmitVertex);
+ Add(Instruction.EndPrimitive, GenerateEndPrimitive);
Add(Instruction.ExponentB2, GenerateExponentB2);
Add(Instruction.FindLSB, GenerateFindLSB);
Add(Instruction.FindMSBS32, GenerateFindMSBS32);
@@ -500,6 +502,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return GenerateBinary(context, operation, context.FDiv, context.SDiv);
}
+ private static OperationResult GenerateEmitVertex(CodeGenContext context, AstOperation operation)
+ {
+ context.EmitVertex();
+
+ return OperationResult.Invalid;
+ }
+
+ private static OperationResult GenerateEndPrimitive(CodeGenContext context, AstOperation operation)
+ {
+ context.EndPrimitive();
+
+ return OperationResult.Invalid;
+ }
+
private static OperationResult GenerateExponentB2(CodeGenContext context, AstOperation operation)
{
return GenerateUnary(context, operation, context.GlslExp2, null);
@@ -812,13 +828,25 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
var src1 = operation.GetSource(0);
var src2 = operation.GetSource(1);
+ var src3 = operation.GetSource(2);
- if (src1 is not AstOperand oper || oper.Type != OperandType.Attribute)
+ if (!(src1 is AstOperand baseAttr) || baseAttr.Type != OperandType.Constant)
{
- throw new InvalidOperationException("First source of LoadAttribute must be a attribute.");
+ throw new InvalidOperationException($"First input of {nameof(Instruction.LoadAttribute)} must be a constant operand.");
}
- return new OperationResult(AggregateType.FP32, context.GetAttribute(AggregateType.FP32, oper, false));
+ var index = context.Get(AggregateType.S32, src3);
+ var resultType = AggregateType.FP32;
+
+ if (src2 is AstOperand operand && operand.Type == OperandType.Constant)
+ {
+ int attrOffset = baseAttr.Value + (operand.Value << 2);
+ return new OperationResult(resultType, context.GetAttribute(resultType, attrOffset, false, index));
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
}
private static OperationResult GenerateLoadConstant(CodeGenContext context, AstOperation operation)
@@ -1081,7 +1109,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask);
var indexNotSegMask = context.BitwiseAnd(context.TypeU32(), index, notSegMask);
- var threadId = context.GetAttribute(AggregateType.U32, new AstOperand(OperandType.Attribute, AttributeConsts.LaneId), false);
+ var threadId = context.GetAttribute(AggregateType.U32, AttributeConsts.LaneId, false);
var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
@@ -1111,7 +1139,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var notSegMask = context.Not(context.TypeU32(), segMask);
var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask);
- var threadId = context.GetAttribute(AggregateType.U32, new AstOperand(OperandType.Attribute, AttributeConsts.LaneId), false);
+ var threadId = context.GetAttribute(AggregateType.U32, AttributeConsts.LaneId, false);
var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
@@ -1138,7 +1166,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var segMask = context.BitwiseAnd(context.TypeU32(), context.ShiftRightLogical(context.TypeU32(), mask, const8), const31);
- var threadId = context.GetAttribute(AggregateType.U32, new AstOperand(OperandType.Attribute, AttributeConsts.LaneId), false);
+ var threadId = context.GetAttribute(AggregateType.U32, AttributeConsts.LaneId, false);
var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
var srcThreadId = context.ISub(context.TypeU32(), threadId, index);
@@ -1167,7 +1195,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var notSegMask = context.Not(context.TypeU32(), segMask);
var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask);
- var threadId = context.GetAttribute(AggregateType.U32, new AstOperand(OperandType.Attribute, AttributeConsts.LaneId), false);
+ var threadId = context.GetAttribute(AggregateType.U32, AttributeConsts.LaneId, false);
var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
index 67caa18e9..269635a0b 100644
--- a/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
+++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
@@ -29,6 +29,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.AddCapability(Capability.SampledBuffer);
context.AddCapability(Capability.SubgroupBallotKHR);
context.AddCapability(Capability.SubgroupVoteKHR);
+
+ if (config.Stage == ShaderStage.Geometry)
+ {
+ context.AddCapability(Capability.Geometry);
+ }
+
context.AddExtension("SPV_KHR_shader_ballot");
context.AddExtension("SPV_KHR_subgroup_vote");
@@ -97,7 +103,39 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
context.AddEntryPoint(context.Config.Stage.Convert(), spvFunc, "main", context.GetMainInterface());
- if (context.Config.Stage == ShaderStage.Fragment)
+ if (context.Config.Stage == ShaderStage.Geometry)
+ {
+ InputTopology inPrimitive = context.Config.GpuAccessor.QueryPrimitiveTopology();
+
+ switch (inPrimitive)
+ {
+ case InputTopology.Points:
+ context.AddExecutionMode(spvFunc, ExecutionMode.InputPoints);
+ break;
+ case InputTopology.Lines:
+ context.AddExecutionMode(spvFunc, ExecutionMode.InputLines);
+ break;
+ case InputTopology.LinesAdjacency:
+ context.AddExecutionMode(spvFunc, ExecutionMode.InputLinesAdjacency);
+ break;
+ case InputTopology.TrianglesAdjacency:
+ context.AddExecutionMode(spvFunc, ExecutionMode.InputTrianglesAdjacency);
+ break;
+ }
+
+ context.AddExecutionMode(spvFunc, ExecutionMode.Invocations, (SpvLiteralInteger)context.InputVertices);
+
+ context.AddExecutionMode(spvFunc, context.Config.OutputTopology switch
+ {
+ OutputTopology.PointList => ExecutionMode.OutputPoints,
+ OutputTopology.LineStrip => ExecutionMode.OutputLineStrip,
+ OutputTopology.TriangleStrip => ExecutionMode.OutputTriangleStrip,
+ _ => throw new InvalidOperationException($"Invalid output topology \"{context.Config.OutputTopology}\".")
+ });
+
+ context.AddExecutionMode(spvFunc, ExecutionMode.OutputVertices, (SpvLiteralInteger)context.Config.MaxOutputVertices);
+ }
+ else if (context.Config.Stage == ShaderStage.Fragment)
{
context.AddExecutionMode(spvFunc, context.Config.Options.TargetApi == TargetApi.Vulkan
? ExecutionMode.OriginUpperLeft
@@ -228,7 +266,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
else if (dest.Type == OperandType.Attribute)
{
- var elemPointer = context.GetAttributeElemPointer(dest, true, out var elemType);
+ var elemPointer = context.GetAttributeElemPointer(dest.Value, true, null, out var elemType);
context.Store(elemPointer, context.Get(elemType, assignment.Source));
}
else if (dest.Type == OperandType.Argument)
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
index ce79f3b8e..0ec56441c 100644
--- a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
+++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
@@ -89,6 +89,18 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
Instruction inst = operation.Inst;
+ if (inst == Instruction.LoadAttribute)
+ {
+ Operand src1 = operation.GetSource(0);
+ Operand src2 = operation.GetSource(1);
+
+ if (src1.Type == OperandType.Constant && src2.Type == OperandType.Constant)
+ {
+ int attrOffset = src1.Value + (src2.Value << 2);
+ context.Info.Inputs.Add(attrOffset);
+ }
+ }
+
int sourcesCount = operation.SourcesCount;
int outDestsCount = operation.DestsCount != 0 ? operation.DestsCount - 1 : 0;
diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs
index 4e3f3e28a..2c87721ee 100644
--- a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs
+++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs
@@ -279,7 +279,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
if (operand.Type == OperandType.Attribute)
{
- Info.Outputs.Add(operand.Value);
+ Info.Outputs.Add(operand.Value & AttributeConsts.Mask);
}
return GetOperand(operand);
diff --git a/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs b/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs
index c39e655e4..d0394969d 100644
--- a/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs
+++ b/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs
@@ -1,5 +1,4 @@
-using Ryujinx.Common;
-using System.Collections.Generic;
+using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation
{
@@ -98,5 +97,17 @@ namespace Ryujinx.Graphics.Shader.Translation
return new AttributeInfo(value, 0, 0, AggregateType.Invalid);
}
+
+ public static bool IsArrayBuiltIn(int attr)
+ {
+ if (attr <= AttributeConsts.TessLevelInner1 ||
+ attr == AttributeConsts.TessCoordX ||
+ attr == AttributeConsts.TessCoordY)
+ {
+ return false;
+ }
+
+ return (attr & AttributeConsts.SpecialMask) == 0;
+ }
}
}
diff --git a/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
index 237a66aa2..dbf4eb125 100644
--- a/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
+++ b/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
@@ -183,8 +183,8 @@ namespace Ryujinx.Graphics.Shader.Translation
if (Config.GpuAccessor.QueryTransformDepthMinusOneToOne())
{
- Operand z = Attribute(AttributeConsts.PositionZ);
- Operand w = Attribute(AttributeConsts.PositionW);
+ Operand z = Attribute(AttributeConsts.PositionZ | AttributeConsts.LoadOutputMask);
+ Operand w = Attribute(AttributeConsts.PositionW | AttributeConsts.LoadOutputMask);
Operand halfW = this.FPMultiply(w, ConstF(0.5f));
this.Copy(Attribute(AttributeConsts.PositionZ), this.FPFusedMultiplyAdd(z, ConstF(0.5f), halfW));