SPIR-V: Geometry shader support

This commit is contained in:
gdk 2022-02-14 15:53:35 -03:00 committed by riperiperi
parent bd56998f4d
commit e1d73cc560
12 changed files with 150 additions and 51 deletions

View file

@ -69,7 +69,7 @@ namespace Ryujinx.Graphics.Gpu
/// <summary>
/// Maximum number of vertex attributes.
/// </summary>
public const int TotalVertexAttribs = 16;
public const int TotalVertexAttribs = 16; // FIXME: Should be 32, but OpenGL only supports 16.
/// <summary>
/// Maximum number of vertex buffers.

View file

@ -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> VertexAttribState;
public fixed uint Reserved11A0[31];
public Array32<VertexAttribState> VertexAttribState;
public fixed uint Reserved11E0[15];
public RtControl RtControl;
public fixed uint Reserved1220[2];
public Size3D RtDepthStencilSize;

View file

@ -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++)
{

View file

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

View file

@ -15,6 +15,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
public ShaderConfig Config { get; }
public int InputVertices { get; }
public Dictionary<int, Instruction> UniformBuffers { get; } = new Dictionary<int, Instruction>();
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));
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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