using Ryujinx.Graphics.Shader.IntermediateRepresentation; using Ryujinx.Graphics.Shader.Translation.Optimizations; using System.Collections.Generic; using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; namespace Ryujinx.Graphics.Shader.Translation.Transforms { class VertexToCompute : ITransformPass { public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures) { return usedFeatures.HasFlag(FeatureFlags.VtgAsCompute); } public static LinkedListNode RunPass(TransformContext context, LinkedListNode node) { if (context.Definitions.Stage != ShaderStage.Vertex) { return node; } Operation operation = (Operation)node.Value; LinkedListNode newNode = node; if (operation.Inst == Instruction.Load && operation.StorageKind == StorageKind.Input) { Operand dest = operation.Dest; switch ((IoVariable)operation.GetSource(0).Value) { case IoVariable.BaseInstance: newNode = GenerateBaseInstanceLoad(context.ResourceManager, node, dest); break; case IoVariable.BaseVertex: newNode = GenerateBaseVertexLoad(context.ResourceManager, node, dest); break; case IoVariable.InstanceId: newNode = GenerateInstanceIdLoad(node, dest); break; case IoVariable.InstanceIndex: newNode = GenerateInstanceIndexLoad(context.ResourceManager, node, dest); break; case IoVariable.VertexId: case IoVariable.VertexIndex: newNode = GenerateVertexIndexLoad(context.ResourceManager, node, dest); break; case IoVariable.UserDefined: int location = operation.GetSource(1).Value; int component = operation.GetSource(2).Value; if (context.Definitions.IsAttributePacked(location)) { bool needsSextNorm = context.Definitions.IsAttributePackedRgb10A2Signed(location); SetBindingPair setAndBinding = context.ResourceManager.Reservations.GetVertexBufferTextureSetAndBinding(location); Operand temp = needsSextNorm ? Local() : dest; Operand vertexElemOffset = GenerateVertexOffset(context.ResourceManager, node, location, 0); newNode = node.List.AddBefore(node, new TextureOperation( Instruction.TextureSample, SamplerType.TextureBuffer, TextureFormat.Unknown, TextureFlags.IntCoords, setAndBinding.SetIndex, setAndBinding.Binding, 1 << component, [temp], [vertexElemOffset])); if (needsSextNorm) { bool sint = context.Definitions.IsAttributeSint(location); CopySignExtendedNormalized(node, component == 3 ? 2 : 10, !sint, dest, temp); } } else { SetBindingPair setAndBinding = context.ResourceManager.Reservations.GetVertexBufferTextureSetAndBinding(location); Operand temp = component > 0 ? Local() : dest; Operand vertexElemOffset = GenerateVertexOffset(context.ResourceManager, node, location, component); newNode = node.List.AddBefore(node, new TextureOperation( Instruction.TextureSample, SamplerType.TextureBuffer, TextureFormat.Unknown, TextureFlags.IntCoords, setAndBinding.SetIndex, setAndBinding.Binding, 1, [temp], [vertexElemOffset])); if (component > 0) { newNode = CopyMasked(context.ResourceManager, newNode, location, component, dest, temp); } } break; case IoVariable.GlobalId: case IoVariable.SubgroupEqMask: case IoVariable.SubgroupGeMask: case IoVariable.SubgroupGtMask: case IoVariable.SubgroupLaneId: case IoVariable.SubgroupLeMask: case IoVariable.SubgroupLtMask: // Those are valid or expected for vertex shaders. break; default: context.GpuAccessor.Log($"Invalid input \"{(IoVariable)operation.GetSource(0).Value}\"."); break; } } else if (operation.Inst == Instruction.Load && operation.StorageKind == StorageKind.Output) { if (TryGetOutputOffset(context.ResourceManager, operation, out int outputOffset)) { newNode = node.List.AddBefore(node, new Operation( Instruction.Load, StorageKind.LocalMemory, operation.Dest, [Const(context.ResourceManager.LocalVertexDataMemoryId), Const(outputOffset)])); } else { context.GpuAccessor.Log($"Invalid output \"{(IoVariable)operation.GetSource(0).Value}\"."); } } else if (operation.Inst == Instruction.Store && operation.StorageKind == StorageKind.Output) { if (TryGetOutputOffset(context.ResourceManager, operation, out int outputOffset)) { Operand value = operation.GetSource(operation.SourcesCount - 1); newNode = node.List.AddBefore(node, new Operation( Instruction.Store, StorageKind.LocalMemory, (Operand)null, [Const(context.ResourceManager.LocalVertexDataMemoryId), Const(outputOffset), value])); } else { context.GpuAccessor.Log($"Invalid output \"{(IoVariable)operation.GetSource(0).Value}\"."); } } if (newNode != node) { Utils.DeleteNode(node, operation); } return newNode; } private static Operand GenerateVertexOffset(ResourceManager resourceManager, LinkedListNode node, int location, int component) { int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding; Operand vertexIdVr = Local(); GenerateVertexIdVertexRateLoad(resourceManager, node, vertexIdVr); Operand vertexIdIr = Local(); GenerateVertexIdInstanceRateLoad(resourceManager, node, vertexIdIr); Operand attributeOffset = Local(); node.List.AddBefore(node, new Operation( Instruction.Load, StorageKind.ConstantBuffer, attributeOffset, [Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexOffsets), Const(location), Const(0)])); Operand isInstanceRate = Local(); node.List.AddBefore(node, new Operation( Instruction.Load, StorageKind.ConstantBuffer, isInstanceRate, [Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexOffsets), Const(location), Const(1)])); Operand vertexId = Local(); node.List.AddBefore(node, new Operation( Instruction.ConditionalSelect, vertexId, [isInstanceRate, vertexIdIr, vertexIdVr])); Operand vertexStride = Local(); node.List.AddBefore(node, new Operation( Instruction.Load, StorageKind.ConstantBuffer, vertexStride, [Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexStrides), Const(location), Const(0)])); Operand vertexBaseOffset = Local(); node.List.AddBefore(node, new Operation(Instruction.Multiply, vertexBaseOffset, [vertexId, vertexStride])); Operand vertexOffset = Local(); node.List.AddBefore(node, new Operation(Instruction.Add, vertexOffset, [attributeOffset, vertexBaseOffset])); Operand vertexElemOffset; if (component != 0) { vertexElemOffset = Local(); node.List.AddBefore(node, new Operation(Instruction.Add, vertexElemOffset, [vertexOffset, Const(component)])); } else { vertexElemOffset = vertexOffset; } return vertexElemOffset; } private static LinkedListNode CopySignExtendedNormalized(LinkedListNode node, int bits, bool normalize, Operand dest, Operand src) { Operand leftShifted = Local(); node = node.List.AddAfter(node, new Operation( Instruction.ShiftLeft, leftShifted, [src, Const(32 - bits)])); Operand rightShifted = normalize ? Local() : dest; node = node.List.AddAfter(node, new Operation( Instruction.ShiftRightS32, rightShifted, [leftShifted, Const(32 - bits)])); if (normalize) { Operand asFloat = Local(); node = node.List.AddAfter(node, new Operation(Instruction.ConvertS32ToFP32, asFloat, [rightShifted])); node = node.List.AddAfter(node, new Operation( Instruction.FP32 | Instruction.Multiply, dest, [asFloat, ConstF(1f / (1 << (bits - 1)))])); } return node; } private static LinkedListNode CopyMasked( ResourceManager resourceManager, LinkedListNode node, int location, int component, Operand dest, Operand src) { Operand componentExists = Local(); int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding; node = node.List.AddAfter(node, new Operation( Instruction.Load, StorageKind.ConstantBuffer, componentExists, [Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexStrides), Const(location), Const(component)])); return node.List.AddAfter(node, new Operation( Instruction.ConditionalSelect, dest, [componentExists, src, ConstF(component == 3 ? 1f : 0f)])); } private static LinkedListNode GenerateBaseVertexLoad(ResourceManager resourceManager, LinkedListNode node, Operand dest) { int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding; return node.List.AddBefore(node, new Operation( Instruction.Load, StorageKind.ConstantBuffer, dest, [Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexCounts), Const(2)])); } private static LinkedListNode GenerateBaseInstanceLoad(ResourceManager resourceManager, LinkedListNode node, Operand dest) { int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding; return node.List.AddBefore(node, new Operation( Instruction.Load, StorageKind.ConstantBuffer, dest, [Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexCounts), Const(3)])); } private static LinkedListNode GenerateVertexIndexLoad(ResourceManager resourceManager, LinkedListNode node, Operand dest) { Operand baseVertex = Local(); Operand vertexId = Local(); GenerateBaseVertexLoad(resourceManager, node, baseVertex); GenerateVertexIdVertexRateLoad(resourceManager, node, vertexId); return node.List.AddBefore(node, new Operation(Instruction.Add, dest, [baseVertex, vertexId])); } private static LinkedListNode GenerateInstanceIndexLoad(ResourceManager resourceManager, LinkedListNode node, Operand dest) { Operand baseInstance = Local(); Operand instanceId = Local(); GenerateBaseInstanceLoad(resourceManager, node, baseInstance); node.List.AddBefore(node, new Operation( Instruction.Load, StorageKind.Input, instanceId, [Const((int)IoVariable.GlobalId), Const(1)])); return node.List.AddBefore(node, new Operation(Instruction.Add, dest, [baseInstance, instanceId])); } private static LinkedListNode GenerateVertexIdVertexRateLoad(ResourceManager resourceManager, LinkedListNode node, Operand dest) { Operand[] sources = [Const(resourceManager.LocalVertexIndexVertexRateMemoryId)]; return node.List.AddBefore(node, new Operation(Instruction.Load, StorageKind.LocalMemory, dest, sources)); } private static LinkedListNode GenerateVertexIdInstanceRateLoad(ResourceManager resourceManager, LinkedListNode node, Operand dest) { Operand[] sources = [Const(resourceManager.LocalVertexIndexInstanceRateMemoryId)]; return node.List.AddBefore(node, new Operation(Instruction.Load, StorageKind.LocalMemory, dest, sources)); } private static LinkedListNode GenerateInstanceIdLoad(LinkedListNode node, Operand dest) { Operand[] sources = [Const((int)IoVariable.GlobalId), Const(1)]; return node.List.AddBefore(node, new Operation(Instruction.Load, StorageKind.Input, dest, sources)); } private static bool TryGetOutputOffset(ResourceManager resourceManager, Operation operation, out int outputOffset) { bool isStore = operation.Inst == Instruction.Store; IoVariable ioVariable = (IoVariable)operation.GetSource(0).Value; bool isValidOutput; if (ioVariable == IoVariable.UserDefined) { int lastIndex = operation.SourcesCount - (isStore ? 2 : 1); int location = operation.GetSource(1).Value; int component = operation.GetSource(lastIndex).Value; isValidOutput = resourceManager.Reservations.TryGetOffset(StorageKind.Output, location, component, out outputOffset); } else { if (ResourceReservations.IsVectorOrArrayVariable(ioVariable)) { int component = operation.GetSource(operation.SourcesCount - (isStore ? 2 : 1)).Value; isValidOutput = resourceManager.Reservations.TryGetOffset(StorageKind.Output, ioVariable, component, out outputOffset); } else { isValidOutput = resourceManager.Reservations.TryGetOffset(StorageKind.Output, ioVariable, out outputOffset); } } return isValidOutput; } } }