Compare commits

..

2 Commits

Author SHA1 Message Date
17354d59d1 Declare and use gl_PerVertex block for VTG per-vertex built-ins (#5576)
* Declare and use gl_PerVertex block for VTG per-vertex built-ins

* Shader cache version bump
2023-08-16 23:16:25 +02:00
0c445184c1 Vulkan: Periodically free regions of the staging buffer (#5572)
* Vulkan: Periodically free regions of the staging buffer

There was an edge case where a game could submit tens of thousands of small copies over the course of over half a minute to unique fences. This could result in a large stutter when the staging buffer became full and it tried to check and free thousands of completed fences.

This became visible with some games and mirrors on Windows, as they don't submit any buffer data via the staging buffer, but may submit copies of the support buffer.

This change makes the Vulkan backend check for staging buffer completion on each command buffer submit, so it can't get backed up with 1000s of copies to check.

* Add comment
2023-08-16 23:06:46 +02:00
8 changed files with 180 additions and 4 deletions

View File

@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2; private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 5529; private const uint CodeGenVersion = 5576;
private const string SharedTocFileName = "shared.toc"; private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data"; private const string SharedDataFileName = "shared.data";

View File

@ -348,12 +348,98 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
} }
} }
} }
else if (IoMap.IsPerVertexBuiltIn(ioDefinition.IoVariable))
{
continue;
}
bool isOutput = ioDefinition.StorageKind.IsOutput(); bool isOutput = ioDefinition.StorageKind.IsOutput();
bool isPerPatch = ioDefinition.StorageKind.IsPerPatch(); bool isPerPatch = ioDefinition.StorageKind.IsPerPatch();
DeclareInputOrOutput(context, ioDefinition, isOutput, isPerPatch, iq, firstLocation); DeclareInputOrOutput(context, ioDefinition, isOutput, isPerPatch, iq, firstLocation);
} }
DeclarePerVertexBlock(context);
}
private static void DeclarePerVertexBlock(CodeGenContext context)
{
if (context.Definitions.Stage.IsVtg())
{
if (context.Definitions.Stage != ShaderStage.Vertex)
{
var perVertexInputStructType = CreatePerVertexStructType(context);
int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.InputVertices : 32;
var perVertexInputArrayType = context.TypeArray(perVertexInputStructType, context.Constant(context.TypeU32(), arraySize));
var perVertexInputPointerType = context.TypePointer(StorageClass.Input, perVertexInputArrayType);
var perVertexInputVariable = context.Variable(perVertexInputPointerType, StorageClass.Input);
context.Name(perVertexInputVariable, "gl_in");
context.AddGlobalVariable(perVertexInputVariable);
context.Inputs.Add(new IoDefinition(StorageKind.Input, IoVariable.Position), perVertexInputVariable);
}
var perVertexOutputStructType = CreatePerVertexStructType(context);
void DecorateTfo(IoVariable ioVariable, int fieldIndex)
{
if (context.Definitions.TryGetTransformFeedbackOutput(ioVariable, 0, 0, out var transformFeedbackOutput))
{
context.MemberDecorate(perVertexOutputStructType, fieldIndex, Decoration.XfbBuffer, (LiteralInteger)transformFeedbackOutput.Buffer);
context.MemberDecorate(perVertexOutputStructType, fieldIndex, Decoration.XfbStride, (LiteralInteger)transformFeedbackOutput.Stride);
context.MemberDecorate(perVertexOutputStructType, fieldIndex, Decoration.Offset, (LiteralInteger)transformFeedbackOutput.Offset);
}
}
DecorateTfo(IoVariable.Position, 0);
DecorateTfo(IoVariable.PointSize, 1);
DecorateTfo(IoVariable.ClipDistance, 2);
SpvInstruction perVertexOutputArrayType;
if (context.Definitions.Stage == ShaderStage.TessellationControl)
{
int arraySize = context.Definitions.ThreadsPerInputPrimitive;
perVertexOutputArrayType = context.TypeArray(perVertexOutputStructType, context.Constant(context.TypeU32(), arraySize));
}
else
{
perVertexOutputArrayType = perVertexOutputStructType;
}
var perVertexOutputPointerType = context.TypePointer(StorageClass.Output, perVertexOutputArrayType);
var perVertexOutputVariable = context.Variable(perVertexOutputPointerType, StorageClass.Output);
context.AddGlobalVariable(perVertexOutputVariable);
context.Outputs.Add(new IoDefinition(StorageKind.Output, IoVariable.Position), perVertexOutputVariable);
}
}
private static SpvInstruction CreatePerVertexStructType(CodeGenContext context)
{
var vec4FloatType = context.TypeVector(context.TypeFP32(), 4);
var floatType = context.TypeFP32();
var array8FloatType = context.TypeArray(context.TypeFP32(), context.Constant(context.TypeU32(), 8));
var array1FloatType = context.TypeArray(context.TypeFP32(), context.Constant(context.TypeU32(), 1));
var perVertexStructType = context.TypeStruct(true, vec4FloatType, floatType, array8FloatType, array1FloatType);
context.Name(perVertexStructType, "gl_PerVertex");
context.MemberName(perVertexStructType, 0, "gl_Position");
context.MemberName(perVertexStructType, 1, "gl_PointSize");
context.MemberName(perVertexStructType, 2, "gl_ClipDistance");
context.MemberName(perVertexStructType, 3, "gl_CullDistance");
context.Decorate(perVertexStructType, Decoration.Block);
context.MemberDecorate(perVertexStructType, 0, Decoration.BuiltIn, (LiteralInteger)BuiltIn.Position);
context.MemberDecorate(perVertexStructType, 1, Decoration.BuiltIn, (LiteralInteger)BuiltIn.PointSize);
context.MemberDecorate(perVertexStructType, 2, Decoration.BuiltIn, (LiteralInteger)BuiltIn.ClipDistance);
context.MemberDecorate(perVertexStructType, 3, Decoration.BuiltIn, (LiteralInteger)BuiltIn.CullDistance);
return perVertexStructType;
} }
private static void DeclareInputOrOutput( private static void DeclareInputOrOutput(

View File

@ -1788,6 +1788,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
StorageClass storageClass; StorageClass storageClass;
SpvInstruction baseObj; SpvInstruction baseObj;
int srcIndex = 0; int srcIndex = 0;
IoVariable? perVertexBuiltIn = null;
switch (storageKind) switch (storageKind)
{ {
@ -1881,6 +1882,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
else else
{ {
(_, varType) = IoMap.GetSpirvBuiltIn(ioVariable); (_, varType) = IoMap.GetSpirvBuiltIn(ioVariable);
if (IoMap.IsPerVertexBuiltIn(ioVariable))
{
perVertexBuiltIn = ioVariable;
ioVariable = IoVariable.Position;
}
} }
varType &= AggregateType.ElementTypeMask; varType &= AggregateType.ElementTypeMask;
@ -1902,6 +1909,31 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
bool isStoreOrAtomic = operation.Inst == Instruction.Store || operation.Inst.IsAtomic(); bool isStoreOrAtomic = operation.Inst == Instruction.Store || operation.Inst.IsAtomic();
int inputsCount = (isStoreOrAtomic ? operation.SourcesCount - 1 : operation.SourcesCount) - srcIndex; int inputsCount = (isStoreOrAtomic ? operation.SourcesCount - 1 : operation.SourcesCount) - srcIndex;
if (perVertexBuiltIn.HasValue)
{
int fieldIndex = IoMap.GetPerVertexStructFieldIndex(perVertexBuiltIn.Value);
var indexes = new SpvInstruction[inputsCount + 1];
int index = 0;
if (IoMap.IsPerVertexArrayBuiltIn(storageKind, context.Definitions.Stage))
{
indexes[index++] = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
indexes[index++] = context.Constant(context.TypeS32(), fieldIndex);
}
else
{
indexes[index++] = context.Constant(context.TypeS32(), fieldIndex);
}
for (; index < inputsCount + 1; srcIndex++, index++)
{
indexes[index] = context.Get(AggregateType.S32, operation.GetSource(srcIndex));
}
return context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, indexes);
}
if (operation.Inst == Instruction.AtomicCompareAndSwap) if (operation.Inst == Instruction.AtomicCompareAndSwap)
{ {
inputsCount--; inputsCount--;

View File

@ -1,5 +1,6 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation; using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation; using Ryujinx.Graphics.Shader.Translation;
using System;
using static Spv.Specification; using static Spv.Specification;
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
@ -80,5 +81,43 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return false; return false;
} }
public static bool IsPerVertexBuiltIn(IoVariable ioVariable)
{
switch (ioVariable)
{
case IoVariable.Position:
case IoVariable.PointSize:
case IoVariable.ClipDistance:
return true;
}
return false;
}
public static bool IsPerVertexArrayBuiltIn(StorageKind storageKind, ShaderStage stage)
{
if (storageKind == StorageKind.Output)
{
return stage == ShaderStage.TessellationControl;
}
else
{
return stage == ShaderStage.TessellationControl ||
stage == ShaderStage.TessellationEvaluation ||
stage == ShaderStage.Geometry;
}
}
public static int GetPerVertexStructFieldIndex(IoVariable ioVariable)
{
return ioVariable switch
{
IoVariable.Position => 0,
IoVariable.PointSize => 1,
IoVariable.ClipDistance => 2,
_ => throw new ArgumentException($"Invalid built-in variable {ioVariable}.")
};
}
} }
} }

View File

@ -23,5 +23,18 @@ namespace Ryujinx.Graphics.Shader
{ {
return stage == ShaderStage.Vertex || stage == ShaderStage.Fragment || stage == ShaderStage.Compute; return stage == ShaderStage.Vertex || stage == ShaderStage.Fragment || stage == ShaderStage.Compute;
} }
/// <summary>
/// Checks if the shader stage is vertex, tessellation or geometry.
/// </summary>
/// <param name="stage">Shader stage</param>
/// <returns>True if the shader stage is vertex, tessellation or geometry, false otherwise</returns>
public static bool IsVtg(this ShaderStage stage)
{
return stage == ShaderStage.Vertex ||
stage == ShaderStage.TessellationControl ||
stage == ShaderStage.TessellationEvaluation ||
stage == ShaderStage.Geometry;
}
} }
} }

View File

@ -246,7 +246,7 @@ namespace Ryujinx.Graphics.Vulkan
return true; return true;
} }
private void FreeCompleted() public void FreeCompleted()
{ {
FenceHolder signalledFence = null; FenceHolder signalledFence = null;
while (_pendingCopies.TryPeek(out var pc) && (pc.Fence == signalledFence || pc.Fence.IsSignaled())) while (_pendingCopies.TryPeek(out var pc) && (pc.Fence == signalledFence || pc.Fence.IsSignaled()))

View File

@ -475,6 +475,9 @@ namespace Ryujinx.Graphics.Vulkan
internal void RegisterFlush() internal void RegisterFlush()
{ {
SyncManager.RegisterFlush(); SyncManager.RegisterFlush();
// Periodically free unused regions of the staging buffer to avoid doing it all at once.
BufferManager.StagingBuffer.FreeCompleted();
} }
public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size) public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)

View File

@ -28,6 +28,7 @@ namespace Spv.Generator
// In the declaration block. // In the declaration block.
private readonly Dictionary<TypeDeclarationKey, Instruction> _typeDeclarations; private readonly Dictionary<TypeDeclarationKey, Instruction> _typeDeclarations;
private readonly List<Instruction> _typeDeclarationsList;
// In the declaration block. // In the declaration block.
private readonly List<Instruction> _globals; private readonly List<Instruction> _globals;
// In the declaration block. // In the declaration block.
@ -54,6 +55,7 @@ namespace Spv.Generator
_debug = new List<Instruction>(); _debug = new List<Instruction>();
_annotations = new List<Instruction>(); _annotations = new List<Instruction>();
_typeDeclarations = new Dictionary<TypeDeclarationKey, Instruction>(); _typeDeclarations = new Dictionary<TypeDeclarationKey, Instruction>();
_typeDeclarationsList = new List<Instruction>();
_constants = new Dictionary<ConstantKey, Instruction>(); _constants = new Dictionary<ConstantKey, Instruction>();
_globals = new List<Instruction>(); _globals = new List<Instruction>();
_functionsDeclarations = new List<Instruction>(); _functionsDeclarations = new List<Instruction>();
@ -126,7 +128,8 @@ namespace Spv.Generator
instruction.SetId(GetNewId()); instruction.SetId(GetNewId());
_typeDeclarations.Add(key, instruction); _typeDeclarations[key] = instruction;
_typeDeclarationsList.Add(instruction);
} }
public void AddEntryPoint(ExecutionModel executionModel, Instruction function, string name, params Instruction[] interfaces) public void AddEntryPoint(ExecutionModel executionModel, Instruction function, string name, params Instruction[] interfaces)
@ -330,7 +333,7 @@ namespace Spv.Generator
// Ensure that everything is in the right order in the declarations section. // Ensure that everything is in the right order in the declarations section.
List<Instruction> declarations = new(); List<Instruction> declarations = new();
declarations.AddRange(_typeDeclarations.Values); declarations.AddRange(_typeDeclarationsList);
declarations.AddRange(_globals); declarations.AddRange(_globals);
declarations.AddRange(_constants.Values); declarations.AddRange(_constants.Values);
declarations.Sort((Instruction x, Instruction y) => x.Id.CompareTo(y.Id)); declarations.Sort((Instruction x, Instruction y) => x.Id.CompareTo(y.Id));