From cacc9c7da4fe484f9788fcb48f806e28cd1e3b4d Mon Sep 17 00:00:00 2001 From: gdk Date: Thu, 7 Apr 2022 16:45:22 -0300 Subject: [PATCH] SPIR-V: Implement tessellation support --- .../CodeGen/Spirv/CodeGenContext.cs | 42 ++++++++++++- .../CodeGen/Spirv/Declarations.cs | 54 +++++++++++----- .../CodeGen/Spirv/SpirvGenerator.cs | 62 ++++++++++++++++--- .../StructuredIr/StructuredProgramContext.cs | 8 +++ .../StructuredIr/StructuredProgramInfo.cs | 4 ++ .../Translation/AttributeInfo.cs | 50 ++++++++------- 6 files changed, 173 insertions(+), 47 deletions(-) diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs index 0f3ae2548..5ab22d755 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs @@ -31,6 +31,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv public Dictionary Images { get; } = new Dictionary(); public Dictionary Inputs { get; } = new Dictionary(); public Dictionary Outputs { get; } = new Dictionary(); + public Dictionary InputsPerPatch { get; } = new Dictionary(); + public Dictionary OutputsPerPatch { get; } = new Dictionary(); private readonly Dictionary _locals = new Dictionary(); private readonly Dictionary _localForArgs = new Dictionary(); @@ -148,7 +150,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv public Instruction[] GetMainInterface() { - var mainInterface = Inputs.Values.Concat(Outputs.Values); + var mainInterface = Inputs.Values + .Concat(Outputs.Values) + .Concat(InputsPerPatch.Values) + .Concat(OutputsPerPatch.Values); if (InputsArray != null) { @@ -216,6 +221,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { IrOperandType.Argument => GetArgument(type, operand), IrOperandType.Attribute => GetAttribute(type, operand.Value & AttributeConsts.Mask, (operand.Value & AttributeConsts.LoadOutputMask) != 0), + IrOperandType.AttributePerPatch => GetAttributePerPatch(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), @@ -317,6 +323,40 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv return BitcastIfNeeded(type, elemType, Load(GetType(elemType), elemPointer)); } + public Instruction GetAttributePerPatchElemPointer(int attr, bool isOutAttr, out AggregateType elemType) + { + var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input; + var attrInfo = AttributeInfo.From(Config, attr, isOutAttr); + + int attrOffset = attrInfo.BaseValue; + Instruction ioVariable; + + bool isUserAttr = attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd; + + elemType = attrInfo.Type & AggregateType.ElementTypeMask; + + ioVariable = isOutAttr ? OutputsPerPatch[attrOffset] : InputsPerPatch[attrOffset]; + + if ((attrInfo.Type & (AggregateType.Array | AggregateType.Vector)) == 0) + { + return ioVariable; + } + + var elemIndex = Constant(TypeU32(), attrInfo.GetInnermostIndex()); + return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, elemIndex); + } + + public Instruction GetAttributePerPatch(AggregateType type, int attr, bool isOutAttr) + { + if (!AttributeInfo.Validate(Config, attr, isOutAttr: false)) + { + return GetConstant(type, new AstOperand(IrOperandType.Constant, 0)); + } + + var elemPointer = GetAttributePerPatchElemPointer(attr, isOutAttr, out var elemType); + return BitcastIfNeeded(type, elemType, Load(GetType(elemType), elemPointer)); + } + public Instruction GetAttribute(AggregateType type, Instruction attr, bool isOutAttr, Instruction index = null) { var elemPointer = GetAttributeElemPointer(attr, isOutAttr, index, out var elemType); diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs index 9baf292e8..349a376f4 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs @@ -98,8 +98,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv DeclareStorageBuffers(context, context.Config.GetStorageBufferDescriptors()); DeclareSamplers(context, context.Config.GetTextureDescriptors()); DeclareImages(context, context.Config.GetImageDescriptors()); - DeclareInputAttributes(context, info); - DeclareOutputAttributes(context, info); + DeclareInputAttributes(context, info, perPatch: false); + DeclareOutputAttributes(context, info, perPatch: false); + DeclareInputAttributes(context, info, perPatch: true); + DeclareOutputAttributes(context, info, perPatch: true); } private static void DeclareLocalMemory(CodeGenContext context, int size) @@ -354,11 +356,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv }; } - private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo info) + private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo info, bool perPatch) { bool iaIndexing = context.Config.UsedFeatures.HasFlag(FeatureFlags.IaIndexing); + var inputs = perPatch ? info.InputsPerPatch : info.Inputs; - foreach (int attr in info.Inputs) + foreach (int attr in inputs) { if (!AttributeInfo.Validate(context.Config, attr, isOutAttr: false)) { @@ -367,7 +370,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv bool isUserAttr = attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd; - if (iaIndexing && isUserAttr) + if (iaIndexing && isUserAttr && !perPatch) { if (context.InputsArray == null) { @@ -399,16 +402,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv iq = context.Config.ImapTypes[(attr - AttributeConsts.UserAttributeBase) / 16].GetFirstUsedType(); } - DeclareInputOrOutput(context, attr, false, iq); + DeclareInputOrOutput(context, attr, perPatch, isOutAttr: false, iq); } } } - private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo info) + private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo info, bool perPatch) { bool oaIndexing = context.Config.UsedFeatures.HasFlag(FeatureFlags.OaIndexing); + var outputs = perPatch ? info.OutputsPerPatch : info.Outputs; - foreach (int attr in info.Outputs) + foreach (int attr in outputs) { if (!AttributeInfo.Validate(context.Config, attr, isOutAttr: true)) { @@ -417,7 +421,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv bool isUserAttr = attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd; - if (oaIndexing && isUserAttr) + if (oaIndexing && isUserAttr && !perPatch) { if (context.OutputsArray == null) { @@ -435,30 +439,30 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv } else { - DeclareOutputAttribute(context, attr); + DeclareOutputAttribute(context, attr, perPatch); } } if (context.Config.Stage == ShaderStage.Vertex) { - DeclareOutputAttribute(context, AttributeConsts.PositionX); + DeclareOutputAttribute(context, AttributeConsts.PositionX, perPatch: false); } } - private static void DeclareOutputAttribute(CodeGenContext context, int attr) + private static void DeclareOutputAttribute(CodeGenContext context, int attr, bool perPatch) { - DeclareInputOrOutput(context, attr, true); + DeclareInputOrOutput(context, attr, perPatch, isOutAttr: true); } public static void DeclareInvocationId(CodeGenContext context) { - DeclareInputOrOutput(context, AttributeConsts.LaneId, false); + DeclareInputOrOutput(context, AttributeConsts.LaneId, perPatch: false, isOutAttr: false); } - private static void DeclareInputOrOutput(CodeGenContext context, int attr, bool isOutAttr, PixelImap iq = PixelImap.Unused) + private static void DeclareInputOrOutput(CodeGenContext context, int attr, bool perPatch, bool isOutAttr, PixelImap iq = PixelImap.Unused) { bool isUserAttr = attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd; - if (isUserAttr && context.Config.TransformFeedbackEnabled && + if (isUserAttr && context.Config.TransformFeedbackEnabled && !perPatch && ((isOutAttr && context.Config.Stage != ShaderStage.Fragment) || (!isOutAttr && context.Config.Stage != ShaderStage.Vertex))) { @@ -466,7 +470,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv return; } - var dict = isOutAttr ? context.Outputs : context.Inputs; + var dict = perPatch + ? (isOutAttr ? context.OutputsPerPatch : context.InputsPerPatch) + : (isOutAttr ? context.Outputs : context.Inputs); + var attrInfo = AttributeInfo.From(context.Config, attr, isOutAttr); if (dict.ContainsKey(attrInfo.BaseValue)) @@ -485,6 +492,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv var spvType = context.TypePointer(storageClass, attrType); var spvVar = context.Variable(spvType, storageClass); + if (perPatch) + { + context.Decorate(spvVar, Decoration.Patch); + } + if (attrInfo.IsBuiltin) { context.Decorate(spvVar, Decoration.BuiltIn, (LiteralInteger)GetBuiltIn(context, attrInfo.BaseValue)); @@ -503,6 +515,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv else if (isUserAttr) { int location = (attr - AttributeConsts.UserAttributeBase) / 16; + + if (perPatch) + { + location += 32; + } + context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location); if (!isOutAttr) @@ -586,6 +604,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { return attr switch { + AttributeConsts.TessLevelOuter0 => BuiltIn.TessLevelOuter, + AttributeConsts.TessLevelInner0 => BuiltIn.TessLevelInner, AttributeConsts.Layer => BuiltIn.Layer, AttributeConsts.ViewportIndex => BuiltIn.ViewportIndex, AttributeConsts.PointSize => BuiltIn.PointSize, diff --git a/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs b/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs index 7dd4a12c0..352e1aa65 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs @@ -66,11 +66,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv context.AddCapability(Capability.FragmentShaderPixelInterlockEXT); context.AddExtension("SPV_EXT_fragment_shader_interlock"); } - - if (config.Stage == ShaderStage.Geometry) + else if (config.Stage == ShaderStage.Geometry) { context.AddCapability(Capability.Geometry); } + else if (config.Stage == ShaderStage.TessellationControl || config.Stage == ShaderStage.TessellationEvaluation) + { + context.AddCapability(Capability.Tessellation); + } context.AddExtension("SPV_KHR_shader_ballot"); context.AddExtension("SPV_KHR_subgroup_vote"); @@ -149,11 +152,50 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv { context.AddEntryPoint(context.Config.Stage.Convert(), spvFunc, "main", context.GetMainInterface()); - if (context.Config.Stage == ShaderStage.Geometry) + if (context.Config.Stage == ShaderStage.TessellationControl) { - InputTopology inPrimitive = context.Config.GpuAccessor.QueryPrimitiveTopology(); + context.AddExecutionMode(spvFunc, ExecutionMode.OutputVertices, (SpvLiteralInteger)context.Config.ThreadsPerInputPrimitive); + } + else if (context.Config.Stage == ShaderStage.TessellationEvaluation) + { + switch (context.Config.GpuAccessor.QueryTessPatchType()) + { + case TessPatchType.Isolines: + context.AddExecutionMode(spvFunc, ExecutionMode.Isolines); + break; + case TessPatchType.Triangles: + context.AddExecutionMode(spvFunc, ExecutionMode.Triangles); + break; + case TessPatchType.Quads: + context.AddExecutionMode(spvFunc, ExecutionMode.Quads); + break; + } - switch (inPrimitive) + switch (context.Config.GpuAccessor.QueryTessSpacing()) + { + case TessSpacing.EqualSpacing: + context.AddExecutionMode(spvFunc, ExecutionMode.SpacingEqual); + break; + case TessSpacing.FractionalEventSpacing: + context.AddExecutionMode(spvFunc, ExecutionMode.SpacingFractionalEven); + break; + case TessSpacing.FractionalOddSpacing: + context.AddExecutionMode(spvFunc, ExecutionMode.SpacingFractionalOdd); + break; + } + + if (context.Config.GpuAccessor.QueryTessCw()) + { + context.AddExecutionMode(spvFunc, ExecutionMode.VertexOrderCw); + } + else + { + context.AddExecutionMode(spvFunc, ExecutionMode.VertexOrderCcw); + } + } + else if (context.Config.Stage == ShaderStage.Geometry) + { + switch (context.Config.GpuAccessor.QueryPrimitiveTopology()) { case InputTopology.Points: context.AddExecutionMode(spvFunc, ExecutionMode.InputPoints); @@ -329,11 +371,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv var source = context.Get(dest.VarType.Convert(), assignment.Source); context.Store(context.GetLocalPointer(dest), source); } - else if (dest.Type == OperandType.Attribute) + else if (dest.Type == OperandType.Attribute || dest.Type == OperandType.AttributePerPatch) { if (AttributeInfo.Validate(context.Config, dest.Value, isOutAttr: true)) { - var elemPointer = context.GetAttributeElemPointer(dest.Value, true, null, out var elemType); + bool perPatch = dest.Type == OperandType.AttributePerPatch; + AggregateType elemType; + + var elemPointer = perPatch + ? context.GetAttributePerPatchElemPointer(dest.Value, true, out elemType) + : context.GetAttributeElemPointer(dest.Value, true, null, out elemType); + context.Store(elemPointer, context.Get(elemType, assignment.Source)); } } diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs index 2c87721ee..19c4ad461 100644 --- a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs +++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs @@ -281,6 +281,10 @@ namespace Ryujinx.Graphics.Shader.StructuredIr { Info.Outputs.Add(operand.Value & AttributeConsts.Mask); } + else if (operand.Type == OperandType.AttributePerPatch) + { + Info.OutputsPerPatch.Add(operand.Value & AttributeConsts.Mask); + } return GetOperand(operand); } @@ -297,6 +301,10 @@ namespace Ryujinx.Graphics.Shader.StructuredIr { Info.Inputs.Add(operand.Value); } + else if (operand.Type == OperandType.AttributePerPatch) + { + Info.InputsPerPatch.Add(operand.Value); + } return GetOperand(operand); } diff --git a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs index 650349b6f..43bdfaba5 100644 --- a/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs +++ b/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs @@ -24,6 +24,8 @@ namespace Ryujinx.Graphics.Shader.StructuredIr public HashSet Inputs { get; } public HashSet Outputs { get; } + public HashSet InputsPerPatch { get; } + public HashSet OutputsPerPatch { get; } public HelperFunctionsMask HelperFunctionsMask { get; set; } @@ -35,6 +37,8 @@ namespace Ryujinx.Graphics.Shader.StructuredIr Inputs = new HashSet(); Outputs = new HashSet(); + InputsPerPatch = new HashSet(); + OutputsPerPatch = new HashSet(); TransformFeedbackOutputs = new TransformFeedbackOutput[0xc0]; } diff --git a/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs b/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs index f69548507..8bf98963d 100644 --- a/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs +++ b/Ryujinx.Graphics.Shader/Translation/AttributeInfo.cs @@ -6,28 +6,34 @@ namespace Ryujinx.Graphics.Shader.Translation { private static readonly Dictionary BuiltInAttributes = new Dictionary() { - { AttributeConsts.Layer, new AttributeInfo(AttributeConsts.Layer, 0, 1, AggregateType.S32) }, - { AttributeConsts.ViewportIndex, new AttributeInfo(AttributeConsts.ViewportIndex, 0, 1, AggregateType.S32) }, - { AttributeConsts.PointSize, new AttributeInfo(AttributeConsts.PointSize, 0, 1, AggregateType.FP32) }, - { AttributeConsts.PositionX, new AttributeInfo(AttributeConsts.PositionX, 0, 4, AggregateType.Vector | AggregateType.FP32) }, - { AttributeConsts.PositionY, new AttributeInfo(AttributeConsts.PositionX, 1, 4, AggregateType.Vector | AggregateType.FP32) }, - { AttributeConsts.PositionZ, new AttributeInfo(AttributeConsts.PositionX, 2, 4, AggregateType.Vector | AggregateType.FP32) }, - { AttributeConsts.PositionW, new AttributeInfo(AttributeConsts.PositionX, 3, 4, AggregateType.Vector | AggregateType.FP32) }, - { AttributeConsts.ClipDistance0, new AttributeInfo(AttributeConsts.ClipDistance0, 0, 8, AggregateType.Array | AggregateType.FP32) }, - { AttributeConsts.ClipDistance1, new AttributeInfo(AttributeConsts.ClipDistance0, 1, 8, AggregateType.Array | AggregateType.FP32) }, - { AttributeConsts.ClipDistance2, new AttributeInfo(AttributeConsts.ClipDistance0, 2, 8, AggregateType.Array | AggregateType.FP32) }, - { AttributeConsts.ClipDistance3, new AttributeInfo(AttributeConsts.ClipDistance0, 3, 8, AggregateType.Array | AggregateType.FP32) }, - { AttributeConsts.ClipDistance4, new AttributeInfo(AttributeConsts.ClipDistance0, 4, 8, AggregateType.Array | AggregateType.FP32) }, - { AttributeConsts.ClipDistance5, new AttributeInfo(AttributeConsts.ClipDistance0, 5, 8, AggregateType.Array | AggregateType.FP32) }, - { AttributeConsts.ClipDistance6, new AttributeInfo(AttributeConsts.ClipDistance0, 6, 8, AggregateType.Array | AggregateType.FP32) }, - { AttributeConsts.ClipDistance7, new AttributeInfo(AttributeConsts.ClipDistance0, 7, 8, AggregateType.Array | AggregateType.FP32) }, - { AttributeConsts.PointCoordX, new AttributeInfo(AttributeConsts.PointCoordX, 0, 2, AggregateType.Vector | AggregateType.FP32) }, - { AttributeConsts.PointCoordY, new AttributeInfo(AttributeConsts.PointCoordX, 1, 2, AggregateType.Vector | AggregateType.FP32) }, - { AttributeConsts.TessCoordX, new AttributeInfo(AttributeConsts.TessCoordX, 0, 2, AggregateType.Vector | AggregateType.FP32) }, - { AttributeConsts.TessCoordY, new AttributeInfo(AttributeConsts.TessCoordX, 1, 2, AggregateType.Vector | AggregateType.FP32) }, - { AttributeConsts.InstanceId, new AttributeInfo(AttributeConsts.InstanceId, 0, 1, AggregateType.S32) }, - { AttributeConsts.VertexId, new AttributeInfo(AttributeConsts.VertexId, 0, 1, AggregateType.S32) }, - { AttributeConsts.FrontFacing, new AttributeInfo(AttributeConsts.FrontFacing, 0, 1, AggregateType.Bool) }, + { AttributeConsts.TessLevelOuter0, new AttributeInfo(AttributeConsts.TessLevelOuter0, 0, 4, AggregateType.Array | AggregateType.FP32) }, + { AttributeConsts.TessLevelOuter1, new AttributeInfo(AttributeConsts.TessLevelOuter0, 1, 4, AggregateType.Array | AggregateType.FP32) }, + { AttributeConsts.TessLevelOuter2, new AttributeInfo(AttributeConsts.TessLevelOuter0, 2, 4, AggregateType.Array | AggregateType.FP32) }, + { AttributeConsts.TessLevelOuter3, new AttributeInfo(AttributeConsts.TessLevelOuter0, 3, 4, AggregateType.Array | AggregateType.FP32) }, + { AttributeConsts.TessLevelInner0, new AttributeInfo(AttributeConsts.TessLevelInner0, 0, 2, AggregateType.Array | AggregateType.FP32) }, + { AttributeConsts.TessLevelInner1, new AttributeInfo(AttributeConsts.TessLevelInner0, 1, 2, AggregateType.Array | AggregateType.FP32) }, + { AttributeConsts.Layer, new AttributeInfo(AttributeConsts.Layer, 0, 1, AggregateType.S32) }, + { AttributeConsts.ViewportIndex, new AttributeInfo(AttributeConsts.ViewportIndex, 0, 1, AggregateType.S32) }, + { AttributeConsts.PointSize, new AttributeInfo(AttributeConsts.PointSize, 0, 1, AggregateType.FP32) }, + { AttributeConsts.PositionX, new AttributeInfo(AttributeConsts.PositionX, 0, 4, AggregateType.Vector | AggregateType.FP32) }, + { AttributeConsts.PositionY, new AttributeInfo(AttributeConsts.PositionX, 1, 4, AggregateType.Vector | AggregateType.FP32) }, + { AttributeConsts.PositionZ, new AttributeInfo(AttributeConsts.PositionX, 2, 4, AggregateType.Vector | AggregateType.FP32) }, + { AttributeConsts.PositionW, new AttributeInfo(AttributeConsts.PositionX, 3, 4, AggregateType.Vector | AggregateType.FP32) }, + { AttributeConsts.ClipDistance0, new AttributeInfo(AttributeConsts.ClipDistance0, 0, 8, AggregateType.Array | AggregateType.FP32) }, + { AttributeConsts.ClipDistance1, new AttributeInfo(AttributeConsts.ClipDistance0, 1, 8, AggregateType.Array | AggregateType.FP32) }, + { AttributeConsts.ClipDistance2, new AttributeInfo(AttributeConsts.ClipDistance0, 2, 8, AggregateType.Array | AggregateType.FP32) }, + { AttributeConsts.ClipDistance3, new AttributeInfo(AttributeConsts.ClipDistance0, 3, 8, AggregateType.Array | AggregateType.FP32) }, + { AttributeConsts.ClipDistance4, new AttributeInfo(AttributeConsts.ClipDistance0, 4, 8, AggregateType.Array | AggregateType.FP32) }, + { AttributeConsts.ClipDistance5, new AttributeInfo(AttributeConsts.ClipDistance0, 5, 8, AggregateType.Array | AggregateType.FP32) }, + { AttributeConsts.ClipDistance6, new AttributeInfo(AttributeConsts.ClipDistance0, 6, 8, AggregateType.Array | AggregateType.FP32) }, + { AttributeConsts.ClipDistance7, new AttributeInfo(AttributeConsts.ClipDistance0, 7, 8, AggregateType.Array | AggregateType.FP32) }, + { AttributeConsts.PointCoordX, new AttributeInfo(AttributeConsts.PointCoordX, 0, 2, AggregateType.Vector | AggregateType.FP32) }, + { AttributeConsts.PointCoordY, new AttributeInfo(AttributeConsts.PointCoordX, 1, 2, AggregateType.Vector | AggregateType.FP32) }, + { AttributeConsts.TessCoordX, new AttributeInfo(AttributeConsts.TessCoordX, 0, 2, AggregateType.Vector | AggregateType.FP32) }, + { AttributeConsts.TessCoordY, new AttributeInfo(AttributeConsts.TessCoordX, 1, 2, AggregateType.Vector | AggregateType.FP32) }, + { AttributeConsts.InstanceId, new AttributeInfo(AttributeConsts.InstanceId, 0, 1, AggregateType.S32) }, + { AttributeConsts.VertexId, new AttributeInfo(AttributeConsts.VertexId, 0, 1, AggregateType.S32) }, + { AttributeConsts.FrontFacing, new AttributeInfo(AttributeConsts.FrontFacing, 0, 1, AggregateType.Bool) }, // Special. { AttributeConsts.FragmentOutputDepth, new AttributeInfo(AttributeConsts.FragmentOutputDepth, 0, 1, AggregateType.FP32) },