diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index 383bbb1516..399aa039c5 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -1,6 +1,7 @@
name: Feature Request
description: Suggest a new feature for Ryujinx.
title: "[Feature Request]"
+labels: enhancement
body:
- type: textarea
id: overview
diff --git a/Directory.Packages.props b/Directory.Packages.props
index ef274125a4..301024cf8a 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -8,21 +8,21 @@
-
-
+
+
-
+
-
+
-
+
-
+
@@ -39,14 +39,14 @@
-
-
-
-
-
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index f2f3cb001e..7f2294d311 100644
--- a/README.md
+++ b/README.md
@@ -36,8 +36,8 @@
## Compatibility
-As of October 2023, Ryujinx has been tested on approximately 4,200 titles;
-over 4,150 boot past menus and into gameplay, with roughly 3,500 of those being considered playable.
+As of May 2024, Ryujinx has been tested on approximately 4,300 titles;
+over 4,100 boot past menus and into gameplay, with roughly 3,550 of those being considered playable.
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues).
diff --git a/Ryujinx.sln b/Ryujinx.sln
index b8304164d5..76ebd573f3 100644
--- a/Ryujinx.sln
+++ b/Ryujinx.sln
@@ -87,6 +87,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon", "src\Ryuj
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -249,6 +251,10 @@ Global
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Ryujinx.sln.DotSettings b/Ryujinx.sln.DotSettings
index 049bdaf69d..ed7f3e9118 100644
--- a/Ryujinx.sln.DotSettings
+++ b/Ryujinx.sln.DotSettings
@@ -4,6 +4,8 @@
UseExplicitType
UseExplicitType
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy>
+ <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy></Policy>
+ True
True
True
True
diff --git a/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs b/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs
index 12ebabddd5..89b1e9e6bb 100644
--- a/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs
+++ b/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs
@@ -237,7 +237,7 @@ namespace ARMeilleure.CodeGen.Arm64
long originalPosition = _stream.Position;
_stream.Seek(0, SeekOrigin.Begin);
- _stream.Read(code, 0, code.Length);
+ _stream.ReadExactly(code, 0, code.Length);
_stream.Seek(originalPosition, SeekOrigin.Begin);
RelocInfo relocInfo;
diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs
index f156e0886b..16feeb914b 100644
--- a/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs
+++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs
@@ -251,7 +251,20 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
}
}
- int selectedReg = GetHighestValueIndex(freePositions);
+ // If this is a copy destination variable, we prefer the register used for the copy source.
+ // If the register is available, then the copy can be eliminated later as both source
+ // and destination will use the same register.
+ int selectedReg;
+
+ if (current.TryGetCopySourceRegister(out int preferredReg) && freePositions[preferredReg] >= current.GetEnd())
+ {
+ selectedReg = preferredReg;
+ }
+ else
+ {
+ selectedReg = GetHighestValueIndex(freePositions);
+ }
+
int selectedNextUse = freePositions[selectedReg];
// Intervals starts and ends at odd positions, unless they span an entire
@@ -431,7 +444,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
}
}
- private static int GetHighestValueIndex(Span span)
+ private static int GetHighestValueIndex(ReadOnlySpan span)
{
int highest = int.MinValue;
@@ -798,12 +811,12 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
// The "visited" state is stored in the MSB of the local's value.
const ulong VisitedMask = 1ul << 63;
- bool IsVisited(Operand local)
+ static bool IsVisited(Operand local)
{
return (local.GetValueUnsafe() & VisitedMask) != 0;
}
- void SetVisited(Operand local)
+ static void SetVisited(Operand local)
{
local.GetValueUnsafe() |= VisitedMask;
}
@@ -826,9 +839,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
{
dest.NumberLocal(_intervals.Count);
- _intervals.Add(new LiveInterval(dest));
+ LiveInterval interval = new LiveInterval(dest);
+ _intervals.Add(interval);
SetVisited(dest);
+
+ // If this is a copy (or copy-like operation), set the copy source interval as well.
+ // This is used for register preferencing later on, which allows the copy to be eliminated
+ // in some cases.
+ if (node.Instruction == Instruction.Copy || node.Instruction == Instruction.ZeroExtend32)
+ {
+ Operand source = node.GetSource(0);
+
+ if (source.Kind == OperandKind.LocalVariable &&
+ source.GetLocalNumber() > 0 &&
+ (node.Instruction == Instruction.Copy || source.Type == OperandType.I32))
+ {
+ interval.SetCopySource(_intervals[source.GetLocalNumber()]);
+ }
+ }
}
}
}
diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs
index 333d3951b1..cfe1bc7ca9 100644
--- a/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs
+++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs
@@ -19,6 +19,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
public LiveRange CurrRange;
public LiveInterval Parent;
+ public LiveInterval CopySource;
public UseList Uses;
public LiveIntervalList Children;
@@ -37,6 +38,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
private ref LiveRange CurrRange => ref _data->CurrRange;
private ref LiveRange PrevRange => ref _data->PrevRange;
private ref LiveInterval Parent => ref _data->Parent;
+ private ref LiveInterval CopySource => ref _data->CopySource;
private ref UseList Uses => ref _data->Uses;
private ref LiveIntervalList Children => ref _data->Children;
@@ -78,6 +80,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
Register = register;
}
+ public void SetCopySource(LiveInterval copySource)
+ {
+ CopySource = copySource;
+ }
+
+ public bool TryGetCopySourceRegister(out int copySourceRegIndex)
+ {
+ if (CopySource._data != null)
+ {
+ copySourceRegIndex = CopySource.Register.Index;
+
+ return true;
+ }
+
+ copySourceRegIndex = 0;
+
+ return false;
+ }
+
public void Reset()
{
PrevRange = default;
diff --git a/src/ARMeilleure/CodeGen/X86/Assembler.cs b/src/ARMeilleure/CodeGen/X86/Assembler.cs
index 55bf072484..96f4de049c 100644
--- a/src/ARMeilleure/CodeGen/X86/Assembler.cs
+++ b/src/ARMeilleure/CodeGen/X86/Assembler.cs
@@ -1444,7 +1444,7 @@ namespace ARMeilleure.CodeGen.X86
Span buffer = new byte[jump.JumpPosition - _stream.Position];
- _stream.Read(buffer);
+ _stream.ReadExactly(buffer);
_stream.Seek(ReservedBytesForJump, SeekOrigin.Current);
codeStream.Write(buffer);
diff --git a/src/ARMeilleure/Decoders/OpCodeTable.cs b/src/ARMeilleure/Decoders/OpCodeTable.cs
index edc004125b..8595356704 100644
--- a/src/ARMeilleure/Decoders/OpCodeTable.cs
+++ b/src/ARMeilleure/Decoders/OpCodeTable.cs
@@ -822,6 +822,10 @@ namespace ARMeilleure.Decoders
SetA32("<<<<00000100xxxxxxxxxxxx1001xxxx", InstName.Umaal, InstEmit32.Umaal, OpCode32AluUmull.Create);
SetA32("<<<<0000101xxxxxxxxxxxxx1001xxxx", InstName.Umlal, InstEmit32.Umlal, OpCode32AluUmull.Create);
SetA32("<<<<0000100xxxxxxxxxxxxx1001xxxx", InstName.Umull, InstEmit32.Umull, OpCode32AluUmull.Create);
+ SetA32("<<<<01100110xxxxxxxx11110001xxxx", InstName.Uqadd16, InstEmit32.Uqadd16, OpCode32AluReg.Create);
+ SetA32("<<<<01100110xxxxxxxx11111001xxxx", InstName.Uqadd8, InstEmit32.Uqadd8, OpCode32AluReg.Create);
+ SetA32("<<<<01100110xxxxxxxx11110111xxxx", InstName.Uqsub16, InstEmit32.Uqsub16, OpCode32AluReg.Create);
+ SetA32("<<<<01100110xxxxxxxx11111111xxxx", InstName.Uqsub8, InstEmit32.Uqsub8, OpCode32AluReg.Create);
SetA32("<<<<0110111xxxxxxxxxxxxxxx01xxxx", InstName.Usat, InstEmit32.Usat, OpCode32Sat.Create);
SetA32("<<<<01101110xxxxxxxx11110011xxxx", InstName.Usat16, InstEmit32.Usat16, OpCode32Sat16.Create);
SetA32("<<<<01100101xxxxxxxx11111111xxxx", InstName.Usub8, InstEmit32.Usub8, OpCode32AluReg.Create);
@@ -1007,6 +1011,8 @@ namespace ARMeilleure.Decoders
SetAsimd("111100100x10xxxxxxxx1011xxx0xxxx", InstName.Vqdmulh, InstEmit32.Vqdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100111x11<<10xxxx00101xx0xxx0", InstName.Vqmovn, InstEmit32.Vqmovn, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
SetAsimd("111100111x11<<10xxxx001001x0xxx0", InstName.Vqmovun, InstEmit32.Vqmovun, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
+ SetAsimd("111100110x01xxxxxxxx1011xxx0xxxx", InstName.Vqrdmulh, InstEmit32.Vqrdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
+ SetAsimd("111100110x10xxxxxxxx1011xxx0xxxx", InstName.Vqrdmulh, InstEmit32.Vqrdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("1111001x1x>>>xxxxxxx100101x1xxx0", InstName.Vqrshrn, InstEmit32.Vqrshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
SetAsimd("111100111x>>>xxxxxxx100001x1xxx0", InstName.Vqrshrun, InstEmit32.Vqrshrun, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
SetAsimd("1111001x1x>>>xxxxxxx100100x1xxx0", InstName.Vqshrn, InstEmit32.Vqshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
@@ -1030,6 +1036,7 @@ namespace ARMeilleure.Decoders
SetAsimd("1111001x1x>>>xxxxxxx101000x1xxxx", InstName.Vshll, InstEmit32.Vshll, OpCode32SimdShImmLong.Create, OpCode32SimdShImmLong.CreateT32); // A1 encoding.
SetAsimd("1111001x1x>>>xxxxxxx0000>xx1xxxx", InstName.Vshr, InstEmit32.Vshr, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
SetAsimd("111100101x>>>xxxxxxx100000x1xxx0", InstName.Vshrn, InstEmit32.Vshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
+ SetAsimd("111100111x>>>xxxxxxx0101>xx1xxxx", InstName.Vsli, InstEmit32.Vsli_I, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
SetAsimd("1111001x1x>>>xxxxxxx0001>xx1xxxx", InstName.Vsra, InstEmit32.Vsra, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
SetAsimd("111101001x00xxxxxxxx0000xxx0xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32);
SetAsimd("111101001x00xxxxxxxx0100xx0xxxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32);
@@ -1054,6 +1061,7 @@ namespace ARMeilleure.Decoders
SetAsimd("111100100x10xxxxxxxx1101xxx0xxxx", InstName.Vsub, InstEmit32.Vsub_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("1111001x1x<
+ {
+ EmitSaturateUqadd(context, d, context.Add(n, m), 16);
+ }));
+ }
+
+ public static void Uqadd8(ArmEmitterContext context)
+ {
+ OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
+
+ SetIntA32(context, op.Rd, EmitUnsigned8BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
+ {
+ EmitSaturateUqadd(context, d, context.Add(n, m), 8);
+ }));
+ }
+
+ public static void Uqsub16(ArmEmitterContext context)
+ {
+ OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
+
+ SetIntA32(context, op.Rd, EmitUnsigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
+ {
+ EmitSaturateUqsub(context, d, context.Subtract(n, m), 16);
+ }));
+ }
+
+ public static void Uqsub8(ArmEmitterContext context)
+ {
+ OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
+
+ SetIntA32(context, op.Rd, EmitUnsigned8BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
+ {
+ EmitSaturateUqsub(context, d, context.Subtract(n, m), 8);
+ }));
+ }
+
public static void Usat(ArmEmitterContext context)
{
OpCode32Sat op = (OpCode32Sat)context.CurrOp;
@@ -934,6 +976,148 @@ namespace ARMeilleure.Instructions
}
}
+ private static void EmitSaturateUqadd(ArmEmitterContext context, Operand result, Operand value, uint saturateTo)
+ {
+ Debug.Assert(saturateTo <= 32);
+
+ if (saturateTo == 32)
+ {
+ // No saturation possible for this case.
+
+ context.Copy(result, value);
+
+ return;
+ }
+ else if (saturateTo == 0)
+ {
+ // Result is always zero if we saturate 0 bits.
+
+ context.Copy(result, Const(0));
+
+ return;
+ }
+
+ // If the result is 0, the values are equal and we don't need saturation.
+ Operand lblNoSat = Label();
+ context.BranchIfFalse(lblNoSat, context.ShiftRightUI(value, Const((int)saturateTo)));
+
+ // Saturate.
+ context.Copy(result, Const(uint.MaxValue >> (32 - (int)saturateTo)));
+
+ Operand lblExit = Label();
+ context.Branch(lblExit);
+
+ context.MarkLabel(lblNoSat);
+
+ context.Copy(result, value);
+
+ context.MarkLabel(lblExit);
+ }
+
+ private static void EmitSaturateUqsub(ArmEmitterContext context, Operand result, Operand value, uint saturateTo)
+ {
+ Debug.Assert(saturateTo <= 32);
+
+ if (saturateTo == 32)
+ {
+ // No saturation possible for this case.
+
+ context.Copy(result, value);
+
+ return;
+ }
+ else if (saturateTo == 0)
+ {
+ // Result is always zero if we saturate 0 bits.
+
+ context.Copy(result, Const(0));
+
+ return;
+ }
+
+ // If the result is 0, the values are equal and we don't need saturation.
+ Operand lblNoSat = Label();
+ context.BranchIf(lblNoSat, value, Const(0), Comparison.GreaterOrEqual);
+
+ // Saturate.
+ // Assumes that the value can only underflow, since this is only used for unsigned subtraction.
+ context.Copy(result, Const(0));
+
+ Operand lblExit = Label();
+ context.Branch(lblExit);
+
+ context.MarkLabel(lblNoSat);
+
+ context.Copy(result, value);
+
+ context.MarkLabel(lblExit);
+ }
+
+ private static Operand EmitUnsigned16BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action elementAction)
+ {
+ Operand tempD = context.AllocateLocal(OperandType.I32);
+
+ Operand tempN = context.ZeroExtend16(OperandType.I32, rn);
+ Operand tempM = context.ZeroExtend16(OperandType.I32, rm);
+ elementAction(tempD, tempN, tempM);
+ Operand tempD2 = context.ZeroExtend16(OperandType.I32, tempD);
+
+ tempN = context.ShiftRightUI(rn, Const(16));
+ tempM = context.ShiftRightUI(rm, Const(16));
+ elementAction(tempD, tempN, tempM);
+ return context.BitwiseOr(tempD2, context.ShiftLeft(tempD, Const(16)));
+ }
+
+ private static Operand EmitSigned8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action elementAction)
+ {
+ return Emit8BitPair(context, rn, rm, elementAction, unsigned: false);
+ }
+
+ private static Operand EmitUnsigned8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action elementAction)
+ {
+ return Emit8BitPair(context, rn, rm, elementAction, unsigned: true);
+ }
+
+ private static Operand Emit8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action elementAction, bool unsigned)
+ {
+ Operand tempD = context.AllocateLocal(OperandType.I32);
+ Operand result = default;
+
+ for (int b = 0; b < 4; b++)
+ {
+ Operand nByte = b != 0 ? context.ShiftRightUI(rn, Const(b * 8)) : rn;
+ Operand mByte = b != 0 ? context.ShiftRightUI(rm, Const(b * 8)) : rm;
+
+ if (unsigned)
+ {
+ nByte = context.ZeroExtend8(OperandType.I32, nByte);
+ mByte = context.ZeroExtend8(OperandType.I32, mByte);
+ }
+ else
+ {
+ nByte = context.SignExtend8(OperandType.I32, nByte);
+ mByte = context.SignExtend8(OperandType.I32, mByte);
+ }
+
+ elementAction(tempD, nByte, mByte);
+
+ if (b == 0)
+ {
+ result = context.ZeroExtend8(OperandType.I32, tempD);
+ }
+ else if (b < 3)
+ {
+ result = context.BitwiseOr(result, context.ShiftLeft(context.ZeroExtend8(OperandType.I32, tempD), Const(b * 8)));
+ }
+ else
+ {
+ result = context.BitwiseOr(result, context.ShiftLeft(tempD, Const(24)));
+ }
+ }
+
+ return result;
+ }
+
private static void EmitAluStore(ArmEmitterContext context, Operand value)
{
IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;
diff --git a/src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs
index dc2646a550..c807fc8585 100644
--- a/src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs
+++ b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs
@@ -1246,6 +1246,33 @@ namespace ARMeilleure.Instructions
EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signedSrc: true, signedDst: false), signed: true);
}
+ public static void Vqrdmulh(ArmEmitterContext context)
+ {
+ OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
+ int eSize = 8 << op.Size;
+
+ EmitVectorBinaryOpI32(context, (op1, op2) =>
+ {
+ if (op.Size == 2)
+ {
+ op1 = context.SignExtend32(OperandType.I64, op1);
+ op2 = context.SignExtend32(OperandType.I64, op2);
+ }
+
+ Operand res = context.Multiply(op1, op2);
+ res = context.Add(res, Const(res.Type, 1L << (eSize - 2)));
+ res = context.ShiftRightSI(res, Const(eSize - 1));
+ res = EmitSatQ(context, res, eSize, signedSrc: true, signedDst: true);
+
+ if (op.Size == 2)
+ {
+ res = context.ConvertI64ToI32(res);
+ }
+
+ return res;
+ }, signed: true);
+ }
+
public static void Vqsub(ArmEmitterContext context)
{
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
diff --git a/src/ARMeilleure/Instructions/InstEmitSimdMove32.cs b/src/ARMeilleure/Instructions/InstEmitSimdMove32.cs
index 9fa7409979..fb2641f66e 100644
--- a/src/ARMeilleure/Instructions/InstEmitSimdMove32.cs
+++ b/src/ARMeilleure/Instructions/InstEmitSimdMove32.cs
@@ -191,6 +191,26 @@ namespace ARMeilleure.Instructions
context.Copy(GetVecA32(op.Qd), res);
}
+ public static void Vswp(ArmEmitterContext context)
+ {
+ OpCode32Simd op = (OpCode32Simd)context.CurrOp;
+
+ if (op.Q)
+ {
+ Operand temp = context.Copy(GetVecA32(op.Qd));
+
+ context.Copy(GetVecA32(op.Qd), GetVecA32(op.Qm));
+ context.Copy(GetVecA32(op.Qm), temp);
+ }
+ else
+ {
+ Operand temp = ExtractScalar(context, OperandType.I64, op.Vd);
+
+ InsertScalar(context, op.Vd, ExtractScalar(context, OperandType.I64, op.Vm));
+ InsertScalar(context, op.Vm, temp);
+ }
+ }
+
public static void Vtbl(ArmEmitterContext context)
{
OpCode32SimdTbl op = (OpCode32SimdTbl)context.CurrOp;
diff --git a/src/ARMeilleure/Instructions/InstEmitSimdShift32.cs b/src/ARMeilleure/Instructions/InstEmitSimdShift32.cs
index e40600a477..e9e3b52b90 100644
--- a/src/ARMeilleure/Instructions/InstEmitSimdShift32.cs
+++ b/src/ARMeilleure/Instructions/InstEmitSimdShift32.cs
@@ -130,6 +130,36 @@ namespace ARMeilleure.Instructions
EmitVectorUnaryNarrowOp32(context, (op1) => context.ShiftRightUI(op1, Const(shift)));
}
+ public static void Vsli_I(ArmEmitterContext context)
+ {
+ OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
+ int shift = op.Shift;
+ int eSize = 8 << op.Size;
+
+ ulong mask = shift != 0 ? ulong.MaxValue >> (64 - shift) : 0UL;
+
+ Operand res = GetVec(op.Qd);
+
+ int elems = op.GetBytesCount() >> op.Size;
+
+ for (int index = 0; index < elems; index++)
+ {
+ Operand me = EmitVectorExtractZx(context, op.Qm, op.Im + index, op.Size);
+
+ Operand neShifted = context.ShiftLeft(me, Const(shift));
+
+ Operand de = EmitVectorExtractZx(context, op.Qd, op.Id + index, op.Size);
+
+ Operand deMasked = context.BitwiseAnd(de, Const(mask));
+
+ Operand e = context.BitwiseOr(neShifted, deMasked);
+
+ res = EmitVectorInsert(context, res, e, op.Id + index, op.Size);
+ }
+
+ context.Copy(GetVec(op.Qd), res);
+ }
+
public static void Vsra(ArmEmitterContext context)
{
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
diff --git a/src/ARMeilleure/Instructions/InstName.cs b/src/ARMeilleure/Instructions/InstName.cs
index 457abbf495..ac85412d1b 100644
--- a/src/ARMeilleure/Instructions/InstName.cs
+++ b/src/ARMeilleure/Instructions/InstName.cs
@@ -571,6 +571,10 @@ namespace ARMeilleure.Instructions
Umaal,
Umlal,
Umull,
+ Uqadd16,
+ Uqadd8,
+ Uqsub16,
+ Uqsub8,
Usat,
Usat16,
Usub8,
@@ -645,6 +649,7 @@ namespace ARMeilleure.Instructions
Vqdmulh,
Vqmovn,
Vqmovun,
+ Vqrdmulh,
Vqrshrn,
Vqrshrun,
Vqshrn,
@@ -666,6 +671,7 @@ namespace ARMeilleure.Instructions
Vshll,
Vshr,
Vshrn,
+ Vsli,
Vst1,
Vst2,
Vst3,
@@ -682,6 +688,7 @@ namespace ARMeilleure.Instructions
Vsub,
Vsubl,
Vsubw,
+ Vswp,
Vtbl,
Vtrn,
Vtst,
diff --git a/src/ARMeilleure/Translation/ControlFlowGraph.cs b/src/ARMeilleure/Translation/ControlFlowGraph.cs
index 3ead49c936..45b092ec5f 100644
--- a/src/ARMeilleure/Translation/ControlFlowGraph.cs
+++ b/src/ARMeilleure/Translation/ControlFlowGraph.cs
@@ -11,7 +11,7 @@ namespace ARMeilleure.Translation
private int[] _postOrderMap;
public int LocalsCount { get; private set; }
- public BasicBlock Entry { get; }
+ public BasicBlock Entry { get; private set; }
public IntrusiveList Blocks { get; }
public BasicBlock[] PostOrderBlocks => _postOrderBlocks;
public int[] PostOrderMap => _postOrderMap;
@@ -34,6 +34,15 @@ namespace ARMeilleure.Translation
return result;
}
+ public void UpdateEntry(BasicBlock newEntry)
+ {
+ newEntry.AddSuccessor(Entry);
+
+ Entry = newEntry;
+ Blocks.AddFirst(newEntry);
+ Update();
+ }
+
public void Update()
{
RemoveUnreachableBlocks(Blocks);
diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs
index 58f065342e..c2eed7a552 100644
--- a/src/ARMeilleure/Translation/PTC/Ptc.cs
+++ b/src/ARMeilleure/Translation/PTC/Ptc.cs
@@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
- private const uint InternalVersion = 6634; //! To be incremented manually for each change to the ARMeilleure project.
+ private const uint InternalVersion = 6950; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";
@@ -857,8 +857,14 @@ namespace ARMeilleure.Translation.PTC
Stopwatch sw = Stopwatch.StartNew();
- threads.ForEach((thread) => thread.Start());
- threads.ForEach((thread) => thread.Join());
+ foreach (var thread in threads)
+ {
+ thread.Start();
+ }
+ foreach (var thread in threads)
+ {
+ thread.Join();
+ }
threads.Clear();
diff --git a/src/ARMeilleure/Translation/RegisterUsage.cs b/src/ARMeilleure/Translation/RegisterUsage.cs
index c8c2506267..472b0f67bc 100644
--- a/src/ARMeilleure/Translation/RegisterUsage.cs
+++ b/src/ARMeilleure/Translation/RegisterUsage.cs
@@ -89,6 +89,17 @@ namespace ARMeilleure.Translation
public static void RunPass(ControlFlowGraph cfg, ExecutionMode mode)
{
+ if (cfg.Entry.Predecessors.Count != 0)
+ {
+ // We expect the entry block to have no predecessors.
+ // This is required because we have a implicit context load at the start of the function,
+ // but if there is a jump to the start of the function, the context load would trash the modified values.
+ // Here we insert a new entry block that will jump to the existing entry block.
+ BasicBlock newEntry = new BasicBlock(cfg.Blocks.Count);
+
+ cfg.UpdateEntry(newEntry);
+ }
+
// Compute local register inputs and outputs used inside blocks.
RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count];
RegisterMask[] localOutputs = new RegisterMask[cfg.Blocks.Count];
@@ -201,7 +212,7 @@ namespace ARMeilleure.Translation
// The only block without any predecessor should be the entry block.
// It always needs a context load as it is the first block to run.
- if (block.Predecessors.Count == 0 || hasContextLoad)
+ if (block == cfg.Entry || hasContextLoad)
{
long vecMask = globalInputs[block.Index].VecMask;
long intMask = globalInputs[block.Index].IntMask;
diff --git a/src/ARMeilleure/Translation/TranslatorQueue.cs b/src/ARMeilleure/Translation/TranslatorQueue.cs
index cee2f9080d..831522bc14 100644
--- a/src/ARMeilleure/Translation/TranslatorQueue.cs
+++ b/src/ARMeilleure/Translation/TranslatorQueue.cs
@@ -80,7 +80,10 @@ namespace ARMeilleure.Translation
return true;
}
- Monitor.Wait(Sync);
+ if (!_disposed)
+ {
+ Monitor.Wait(Sync);
+ }
}
}
diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
index 62fe5025d6..4eb75a578b 100644
--- a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
+++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
@@ -89,9 +89,9 @@ namespace Ryujinx.Audio.Backends.SDL2
return;
}
- using IMemoryOwner samplesOwner = ByteMemoryPool.Rent(frameCount * _bytesPerFrame);
+ using SpanOwner samplesOwner = SpanOwner.Rent(frameCount * _bytesPerFrame);
- Span samples = samplesOwner.Memory.Span;
+ Span samples = samplesOwner.Span;
_ringBuffer.Read(samples, 0, samples.Length);
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
index 4011a12142..e9cc6a8e1a 100644
--- a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
+++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
@@ -122,9 +122,9 @@ namespace Ryujinx.Audio.Backends.SoundIo
int channelCount = areas.Length;
- using IMemoryOwner samplesOwner = ByteMemoryPool.Rent(frameCount * bytesPerFrame);
+ using SpanOwner samplesOwner = SpanOwner.Rent(frameCount * bytesPerFrame);
- Span samples = samplesOwner.Memory.Span;
+ Span samples = samplesOwner.Span;
_ringBuffer.Read(samples, 0, samples.Length);
diff --git a/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs b/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs
index b95e5bed14..7aefe88654 100644
--- a/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs
+++ b/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs
@@ -14,7 +14,7 @@ namespace Ryujinx.Audio.Backends.Common
private readonly object _lock = new();
- private IMemoryOwner _bufferOwner;
+ private MemoryOwner _bufferOwner;
private Memory _buffer;
private int _size;
private int _headOffset;
@@ -24,7 +24,7 @@ namespace Ryujinx.Audio.Backends.Common
public DynamicRingBuffer(int initialCapacity = RingBufferAlignment)
{
- _bufferOwner = ByteMemoryPool.RentCleared(initialCapacity);
+ _bufferOwner = MemoryOwner.RentCleared(initialCapacity);
_buffer = _bufferOwner.Memory;
}
@@ -62,7 +62,7 @@ namespace Ryujinx.Audio.Backends.Common
private void SetCapacityLocked(int capacity)
{
- IMemoryOwner newBufferOwner = ByteMemoryPool.RentCleared(capacity);
+ MemoryOwner newBufferOwner = MemoryOwner.RentCleared(capacity);
Memory newBuffer = newBufferOwner.Memory;
if (_size > 0)
diff --git a/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs b/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs
index 608381af11..7f881373f3 100644
--- a/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs
+++ b/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs
@@ -15,7 +15,6 @@ namespace Ryujinx.Audio.Renderer.Common
{
public const int Align = 0x10;
public const int BiquadStateOffset = 0x0;
- public const int BiquadStateSize = 0x10;
///
/// The state of the biquad filters of this voice.
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs
index 1a51a1fbdf..31f614d679 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs
@@ -16,10 +16,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
/// The biquad filter parameter
/// The biquad filter state
/// The output buffer to write the result
- /// The input buffer to write the result
+ /// The input buffer to read the samples from
/// The count of samples to process
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ProcessBiquadFilter(ref BiquadFilterParameter parameter, ref BiquadFilterState state, Span outputBuffer, ReadOnlySpan inputBuffer, uint sampleCount)
+ public static void ProcessBiquadFilter(
+ ref BiquadFilterParameter parameter,
+ ref BiquadFilterState state,
+ Span outputBuffer,
+ ReadOnlySpan inputBuffer,
+ uint sampleCount)
{
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
@@ -40,6 +45,96 @@ namespace Ryujinx.Audio.Renderer.Dsp
}
}
+ ///
+ /// Apply a single biquad filter and mix the result into the output buffer.
+ ///
+ /// This is implemented with a direct form 1.
+ /// The biquad filter parameter
+ /// The biquad filter state
+ /// The output buffer to write the result
+ /// The input buffer to read the samples from
+ /// The count of samples to process
+ /// Mix volume
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ProcessBiquadFilterAndMix(
+ ref BiquadFilterParameter parameter,
+ ref BiquadFilterState state,
+ Span outputBuffer,
+ ReadOnlySpan inputBuffer,
+ uint sampleCount,
+ float volume)
+ {
+ float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
+ float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
+ float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
+
+ float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter);
+ float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter);
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ float input = inputBuffer[i];
+ float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
+
+ state.State1 = state.State0;
+ state.State0 = input;
+ state.State3 = state.State2;
+ state.State2 = output;
+
+ outputBuffer[i] += FloatingPointHelper.MultiplyRoundUp(output, volume);
+ }
+ }
+
+ ///
+ /// Apply a single biquad filter and mix the result into the output buffer with volume ramp.
+ ///
+ /// This is implemented with a direct form 1.
+ /// The biquad filter parameter
+ /// The biquad filter state
+ /// The output buffer to write the result
+ /// The input buffer to read the samples from
+ /// The count of samples to process
+ /// Initial mix volume
+ /// Volume increment step
+ /// Last filtered sample value
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float ProcessBiquadFilterAndMixRamp(
+ ref BiquadFilterParameter parameter,
+ ref BiquadFilterState state,
+ Span outputBuffer,
+ ReadOnlySpan inputBuffer,
+ uint sampleCount,
+ float volume,
+ float ramp)
+ {
+ float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
+ float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
+ float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
+
+ float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter);
+ float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter);
+
+ float mixState = 0f;
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ float input = inputBuffer[i];
+ float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
+
+ state.State1 = state.State0;
+ state.State0 = input;
+ state.State3 = state.State2;
+ state.State2 = output;
+
+ mixState = FloatingPointHelper.MultiplyRoundUp(output, volume);
+
+ outputBuffer[i] += mixState;
+ volume += ramp;
+ }
+
+ return mixState;
+ }
+
///
/// Apply multiple biquad filter.
///
@@ -47,10 +142,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
/// The biquad filter parameter
/// The biquad filter state
/// The output buffer to write the result
- /// The input buffer to write the result
+ /// The input buffer to read the samples from
/// The count of samples to process
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ProcessBiquadFilter(ReadOnlySpan parameters, Span states, Span outputBuffer, ReadOnlySpan inputBuffer, uint sampleCount)
+ public static void ProcessBiquadFilter(
+ ReadOnlySpan parameters,
+ Span states,
+ Span outputBuffer,
+ ReadOnlySpan inputBuffer,
+ uint sampleCount)
{
for (int stageIndex = 0; stageIndex < parameters.Length; stageIndex++)
{
@@ -67,7 +167,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
for (int i = 0; i < sampleCount; i++)
{
- float input = inputBuffer[i];
+ float input = stageIndex != 0 ? outputBuffer[i] : inputBuffer[i];
float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
state.State1 = state.State0;
@@ -79,5 +179,129 @@ namespace Ryujinx.Audio.Renderer.Dsp
}
}
}
+
+ ///
+ /// Apply double biquad filter and mix the result into the output buffer.
+ ///
+ /// This is implemented with a direct form 1.
+ /// The biquad filter parameter
+ /// The biquad filter state
+ /// The output buffer to write the result
+ /// The input buffer to read the samples from
+ /// The count of samples to process
+ /// Mix volume
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ProcessDoubleBiquadFilterAndMix(
+ ref BiquadFilterParameter parameter0,
+ ref BiquadFilterParameter parameter1,
+ ref BiquadFilterState state0,
+ ref BiquadFilterState state1,
+ Span outputBuffer,
+ ReadOnlySpan inputBuffer,
+ uint sampleCount,
+ float volume)
+ {
+ float a00 = FixedPointHelper.ToFloat(parameter0.Numerator[0], FixedPointPrecisionForParameter);
+ float a10 = FixedPointHelper.ToFloat(parameter0.Numerator[1], FixedPointPrecisionForParameter);
+ float a20 = FixedPointHelper.ToFloat(parameter0.Numerator[2], FixedPointPrecisionForParameter);
+
+ float b10 = FixedPointHelper.ToFloat(parameter0.Denominator[0], FixedPointPrecisionForParameter);
+ float b20 = FixedPointHelper.ToFloat(parameter0.Denominator[1], FixedPointPrecisionForParameter);
+
+ float a01 = FixedPointHelper.ToFloat(parameter1.Numerator[0], FixedPointPrecisionForParameter);
+ float a11 = FixedPointHelper.ToFloat(parameter1.Numerator[1], FixedPointPrecisionForParameter);
+ float a21 = FixedPointHelper.ToFloat(parameter1.Numerator[2], FixedPointPrecisionForParameter);
+
+ float b11 = FixedPointHelper.ToFloat(parameter1.Denominator[0], FixedPointPrecisionForParameter);
+ float b21 = FixedPointHelper.ToFloat(parameter1.Denominator[1], FixedPointPrecisionForParameter);
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ float input = inputBuffer[i];
+ float output = input * a00 + state0.State0 * a10 + state0.State1 * a20 + state0.State2 * b10 + state0.State3 * b20;
+
+ state0.State1 = state0.State0;
+ state0.State0 = input;
+ state0.State3 = state0.State2;
+ state0.State2 = output;
+
+ input = output;
+ output = input * a01 + state1.State0 * a11 + state1.State1 * a21 + state1.State2 * b11 + state1.State3 * b21;
+
+ state1.State1 = state1.State0;
+ state1.State0 = input;
+ state1.State3 = state1.State2;
+ state1.State2 = output;
+
+ outputBuffer[i] += FloatingPointHelper.MultiplyRoundUp(output, volume);
+ }
+ }
+
+ ///
+ /// Apply double biquad filter and mix the result into the output buffer with volume ramp.
+ ///
+ /// This is implemented with a direct form 1.
+ /// The biquad filter parameter
+ /// The biquad filter state
+ /// The output buffer to write the result
+ /// The input buffer to read the samples from
+ /// The count of samples to process
+ /// Initial mix volume
+ /// Volume increment step
+ /// Last filtered sample value
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float ProcessDoubleBiquadFilterAndMixRamp(
+ ref BiquadFilterParameter parameter0,
+ ref BiquadFilterParameter parameter1,
+ ref BiquadFilterState state0,
+ ref BiquadFilterState state1,
+ Span outputBuffer,
+ ReadOnlySpan inputBuffer,
+ uint sampleCount,
+ float volume,
+ float ramp)
+ {
+ float a00 = FixedPointHelper.ToFloat(parameter0.Numerator[0], FixedPointPrecisionForParameter);
+ float a10 = FixedPointHelper.ToFloat(parameter0.Numerator[1], FixedPointPrecisionForParameter);
+ float a20 = FixedPointHelper.ToFloat(parameter0.Numerator[2], FixedPointPrecisionForParameter);
+
+ float b10 = FixedPointHelper.ToFloat(parameter0.Denominator[0], FixedPointPrecisionForParameter);
+ float b20 = FixedPointHelper.ToFloat(parameter0.Denominator[1], FixedPointPrecisionForParameter);
+
+ float a01 = FixedPointHelper.ToFloat(parameter1.Numerator[0], FixedPointPrecisionForParameter);
+ float a11 = FixedPointHelper.ToFloat(parameter1.Numerator[1], FixedPointPrecisionForParameter);
+ float a21 = FixedPointHelper.ToFloat(parameter1.Numerator[2], FixedPointPrecisionForParameter);
+
+ float b11 = FixedPointHelper.ToFloat(parameter1.Denominator[0], FixedPointPrecisionForParameter);
+ float b21 = FixedPointHelper.ToFloat(parameter1.Denominator[1], FixedPointPrecisionForParameter);
+
+ float mixState = 0f;
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ float input = inputBuffer[i];
+ float output = input * a00 + state0.State0 * a10 + state0.State1 * a20 + state0.State2 * b10 + state0.State3 * b20;
+
+ state0.State1 = state0.State0;
+ state0.State0 = input;
+ state0.State3 = state0.State2;
+ state0.State2 = output;
+
+ input = output;
+ output = input * a01 + state1.State0 * a11 + state1.State1 * a21 + state1.State2 * b11 + state1.State3 * b21;
+
+ state1.State1 = state1.State0;
+ state1.State0 = input;
+ state1.State3 = state1.State2;
+ state1.State2 = output;
+
+ mixState = FloatingPointHelper.MultiplyRoundUp(output, volume);
+
+ outputBuffer[i] += mixState;
+ volume += ramp;
+ }
+
+ return mixState;
+ }
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs
new file mode 100644
index 0000000000..106fc03573
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs
@@ -0,0 +1,123 @@
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class BiquadFilterAndMixCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.BiquadFilterAndMix;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ushort InputBufferIndex { get; }
+ public ushort OutputBufferIndex { get; }
+
+ private BiquadFilterParameter _parameter;
+
+ public Memory BiquadFilterState { get; }
+ public Memory PreviousBiquadFilterState { get; }
+
+ public Memory State { get; }
+
+ public int LastSampleIndex { get; }
+
+ public float Volume0 { get; }
+ public float Volume1 { get; }
+
+ public bool NeedInitialization { get; }
+ public bool HasVolumeRamp { get; }
+ public bool IsFirstMixBuffer { get; }
+
+ public BiquadFilterAndMixCommand(
+ float volume0,
+ float volume1,
+ uint inputBufferIndex,
+ uint outputBufferIndex,
+ int lastSampleIndex,
+ Memory state,
+ ref BiquadFilterParameter filter,
+ Memory biquadFilterState,
+ Memory previousBiquadFilterState,
+ bool needInitialization,
+ bool hasVolumeRamp,
+ bool isFirstMixBuffer,
+ int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ InputBufferIndex = (ushort)inputBufferIndex;
+ OutputBufferIndex = (ushort)outputBufferIndex;
+
+ _parameter = filter;
+ BiquadFilterState = biquadFilterState;
+ PreviousBiquadFilterState = previousBiquadFilterState;
+
+ State = state;
+ LastSampleIndex = lastSampleIndex;
+
+ Volume0 = volume0;
+ Volume1 = volume1;
+
+ NeedInitialization = needInitialization;
+ HasVolumeRamp = hasVolumeRamp;
+ IsFirstMixBuffer = isFirstMixBuffer;
+ }
+
+ public void Process(CommandList context)
+ {
+ ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex);
+ Span outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ if (NeedInitialization)
+ {
+ // If there is no previous state, initialize to zero.
+
+ BiquadFilterState.Span[0] = new BiquadFilterState();
+ }
+ else if (IsFirstMixBuffer)
+ {
+ // This is the first buffer, set previous state to current state.
+
+ PreviousBiquadFilterState.Span[0] = BiquadFilterState.Span[0];
+ }
+ else
+ {
+ // Rewind the current state by copying back the previous state.
+
+ BiquadFilterState.Span[0] = PreviousBiquadFilterState.Span[0];
+ }
+
+ if (HasVolumeRamp)
+ {
+ float volume = Volume0;
+ float ramp = (Volume1 - Volume0) / (int)context.SampleCount;
+
+ State.Span[0].LastSamples[LastSampleIndex] = BiquadFilterHelper.ProcessBiquadFilterAndMixRamp(
+ ref _parameter,
+ ref BiquadFilterState.Span[0],
+ outputBuffer,
+ inputBuffer,
+ context.SampleCount,
+ volume,
+ ramp);
+ }
+ else
+ {
+ BiquadFilterHelper.ProcessBiquadFilterAndMix(
+ ref _parameter,
+ ref BiquadFilterState.Span[0],
+ outputBuffer,
+ inputBuffer,
+ context.SampleCount,
+ Volume1);
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
index 098a04a041..de5c0ea2c8 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
@@ -30,8 +30,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
CopyMixBuffer,
LimiterVersion1,
LimiterVersion2,
- GroupedBiquadFilter,
+ MultiTapBiquadFilter,
CaptureBuffer,
Compressor,
+ BiquadFilterAndMix,
+ MultiTapBiquadFilterAndMix,
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
index 3c7dd63b22..41ac84c1a9 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
@@ -24,7 +24,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public Memory State { get; }
- public MixRampGroupedCommand(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span volume0, Span volume1, Memory state, int nodeId)
+ public MixRampGroupedCommand(
+ uint mixBufferCount,
+ uint inputBufferIndex,
+ uint outputBufferIndex,
+ ReadOnlySpan volume0,
+ ReadOnlySpan volume1,
+ Memory state,
+ int nodeId)
{
Enabled = true;
MixBufferCount = mixBufferCount;
@@ -48,7 +55,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static float ProcessMixRampGrouped(Span outputBuffer, ReadOnlySpan inputBuffer, float volume0, float volume1, int sampleCount)
+ private static float ProcessMixRampGrouped(
+ Span outputBuffer,
+ ReadOnlySpan inputBuffer,
+ float volume0,
+ float volume1,
+ int sampleCount)
{
float ramp = (volume1 - volume0) / sampleCount;
float volume = volume0;
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs
new file mode 100644
index 0000000000..e359371b4b
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs
@@ -0,0 +1,145 @@
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class MultiTapBiquadFilterAndMixCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.MultiTapBiquadFilterAndMix;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ushort InputBufferIndex { get; }
+ public ushort OutputBufferIndex { get; }
+
+ private BiquadFilterParameter _parameter0;
+ private BiquadFilterParameter _parameter1;
+
+ public Memory BiquadFilterState0 { get; }
+ public Memory BiquadFilterState1 { get; }
+ public Memory PreviousBiquadFilterState0 { get; }
+ public Memory PreviousBiquadFilterState1 { get; }
+
+ public Memory State { get; }
+
+ public int LastSampleIndex { get; }
+
+ public float Volume0 { get; }
+ public float Volume1 { get; }
+
+ public bool NeedInitialization0 { get; }
+ public bool NeedInitialization1 { get; }
+ public bool HasVolumeRamp { get; }
+ public bool IsFirstMixBuffer { get; }
+
+ public MultiTapBiquadFilterAndMixCommand(
+ float volume0,
+ float volume1,
+ uint inputBufferIndex,
+ uint outputBufferIndex,
+ int lastSampleIndex,
+ Memory state,
+ ref BiquadFilterParameter filter0,
+ ref BiquadFilterParameter filter1,
+ Memory biquadFilterState0,
+ Memory biquadFilterState1,
+ Memory previousBiquadFilterState0,
+ Memory previousBiquadFilterState1,
+ bool needInitialization0,
+ bool needInitialization1,
+ bool hasVolumeRamp,
+ bool isFirstMixBuffer,
+ int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ InputBufferIndex = (ushort)inputBufferIndex;
+ OutputBufferIndex = (ushort)outputBufferIndex;
+
+ _parameter0 = filter0;
+ _parameter1 = filter1;
+ BiquadFilterState0 = biquadFilterState0;
+ BiquadFilterState1 = biquadFilterState1;
+ PreviousBiquadFilterState0 = previousBiquadFilterState0;
+ PreviousBiquadFilterState1 = previousBiquadFilterState1;
+
+ State = state;
+ LastSampleIndex = lastSampleIndex;
+
+ Volume0 = volume0;
+ Volume1 = volume1;
+
+ NeedInitialization0 = needInitialization0;
+ NeedInitialization1 = needInitialization1;
+ HasVolumeRamp = hasVolumeRamp;
+ IsFirstMixBuffer = isFirstMixBuffer;
+ }
+
+ private void UpdateState(Memory state, Memory previousState, bool needInitialization)
+ {
+ if (needInitialization)
+ {
+ // If there is no previous state, initialize to zero.
+
+ state.Span[0] = new BiquadFilterState();
+ }
+ else if (IsFirstMixBuffer)
+ {
+ // This is the first buffer, set previous state to current state.
+
+ previousState.Span[0] = state.Span[0];
+ }
+ else
+ {
+ // Rewind the current state by copying back the previous state.
+
+ state.Span[0] = previousState.Span[0];
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex);
+ Span outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ UpdateState(BiquadFilterState0, PreviousBiquadFilterState0, NeedInitialization0);
+ UpdateState(BiquadFilterState1, PreviousBiquadFilterState1, NeedInitialization1);
+
+ if (HasVolumeRamp)
+ {
+ float volume = Volume0;
+ float ramp = (Volume1 - Volume0) / (int)context.SampleCount;
+
+ State.Span[0].LastSamples[LastSampleIndex] = BiquadFilterHelper.ProcessDoubleBiquadFilterAndMixRamp(
+ ref _parameter0,
+ ref _parameter1,
+ ref BiquadFilterState0.Span[0],
+ ref BiquadFilterState1.Span[0],
+ outputBuffer,
+ inputBuffer,
+ context.SampleCount,
+ volume,
+ ramp);
+ }
+ else
+ {
+ BiquadFilterHelper.ProcessDoubleBiquadFilterAndMix(
+ ref _parameter0,
+ ref _parameter1,
+ ref BiquadFilterState0.Span[0],
+ ref BiquadFilterState1.Span[0],
+ outputBuffer,
+ inputBuffer,
+ context.SampleCount,
+ Volume1);
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs
similarity index 84%
rename from src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs
rename to src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs
index 7af851bdc0..e159f8ef76 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs
@@ -4,13 +4,13 @@ using System;
namespace Ryujinx.Audio.Renderer.Dsp.Command
{
- public class GroupedBiquadFilterCommand : ICommand
+ public class MultiTapBiquadFilterCommand : ICommand
{
public bool Enabled { get; set; }
public int NodeId { get; }
- public CommandType CommandType => CommandType.GroupedBiquadFilter;
+ public CommandType CommandType => CommandType.MultiTapBiquadFilter;
public uint EstimatedProcessingTime { get; set; }
@@ -20,7 +20,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private readonly int _outputBufferIndex;
private readonly bool[] _isInitialized;
- public GroupedBiquadFilterCommand(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId)
+ public MultiTapBiquadFilterCommand(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId)
{
_parameters = filters.ToArray();
_biquadFilterStates = biquadFilterStateMemory;
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs
index f9a32b3f99..58a2d9cce1 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs
@@ -2,12 +2,16 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
- [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x20)]
public struct BiquadFilterState
{
public float State0;
public float State1;
public float State2;
public float State3;
+ public float State4;
+ public float State5;
+ public float State6;
+ public float State7;
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs
new file mode 100644
index 0000000000..807232f208
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs
@@ -0,0 +1,43 @@
+using Ryujinx.Common.Memory;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ ///
+ /// Generic interface for the splitter destination parameters.
+ ///
+ public interface ISplitterDestinationInParameter
+ {
+ ///
+ /// Target splitter destination data id.
+ ///
+ int Id { get; }
+
+ ///
+ /// The mix to output the result of the splitter.
+ ///
+ int DestinationId { get; }
+
+ ///
+ /// Biquad filter parameters.
+ ///
+ Array2 BiquadFilters { get; }
+
+ ///
+ /// Set to true if in use.
+ ///
+ bool IsUsed { get; }
+
+ ///
+ /// Mix buffer volumes.
+ ///
+ /// Used when a splitter id is specified in the mix.
+ Span MixBufferVolume { get; }
+
+ ///
+ /// Check if the magic is valid.
+ ///
+ /// Returns true if the magic is valid.
+ bool IsMagicValid();
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs
similarity index 73%
rename from src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs
rename to src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs
index b74b67be08..029c001ea9 100644
--- a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs
+++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using System;
using System.Runtime.InteropServices;
@@ -5,10 +6,10 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
///
- /// Input header for a splitter destination update.
+ /// Input header for a splitter destination version 1 update.
///
[StructLayout(LayoutKind.Sequential, Pack = 1)]
- public struct SplitterDestinationInParameter
+ public struct SplitterDestinationInParameterVersion1 : ISplitterDestinationInParameter
{
///
/// Magic of the input header.
@@ -41,7 +42,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
///
private unsafe fixed byte _reserved[3];
- [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)]
+ [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
private struct MixArray { }
///
@@ -50,6 +51,14 @@ namespace Ryujinx.Audio.Renderer.Parameter
/// Used when a splitter id is specified in the mix.
public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixBufferVolume);
+ readonly int ISplitterDestinationInParameter.Id => Id;
+
+ readonly int ISplitterDestinationInParameter.DestinationId => DestinationId;
+
+ readonly Array2 ISplitterDestinationInParameter.BiquadFilters => default;
+
+ readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
+
///
/// The expected constant of any input header.
///
diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs
new file mode 100644
index 0000000000..312be8b707
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs
@@ -0,0 +1,81 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ ///
+ /// Input header for a splitter destination version 2 update.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct SplitterDestinationInParameterVersion2 : ISplitterDestinationInParameter
+ {
+ ///
+ /// Magic of the input header.
+ ///
+ public uint Magic;
+
+ ///
+ /// Target splitter destination data id.
+ ///
+ public int Id;
+
+ ///
+ /// Mix buffer volumes storage.
+ ///
+ private MixArray _mixBufferVolume;
+
+ ///
+ /// The mix to output the result of the splitter.
+ ///
+ public int DestinationId;
+
+ ///
+ /// Biquad filter parameters.
+ ///
+ public Array2 BiquadFilters;
+
+ ///
+ /// Set to true if in use.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsUsed;
+
+ ///
+ /// Reserved/padding.
+ ///
+ private unsafe fixed byte _reserved[11];
+
+ [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
+ private struct MixArray { }
+
+ ///
+ /// Mix buffer volumes.
+ ///
+ /// Used when a splitter id is specified in the mix.
+ public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixBufferVolume);
+
+ readonly int ISplitterDestinationInParameter.Id => Id;
+
+ readonly int ISplitterDestinationInParameter.DestinationId => DestinationId;
+
+ readonly Array2 ISplitterDestinationInParameter.BiquadFilters => BiquadFilters;
+
+ readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
+
+ ///
+ /// The expected constant of any input header.
+ ///
+ private const uint ValidMagic = 0x44444E53;
+
+ ///
+ /// Check if the magic is valid.
+ ///
+ /// Returns true if the magic is valid.
+ public readonly bool IsMagicValid()
+ {
+ return Magic == ValidMagic;
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
index 9b56f5cbdf..246889c480 100644
--- a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
@@ -1,6 +1,7 @@
using Ryujinx.Audio.Integration;
using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Dsp.Command;
+using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Server.Effect;
using Ryujinx.Audio.Renderer.Server.MemoryPool;
@@ -173,6 +174,22 @@ namespace Ryujinx.Audio.Renderer.Server
return ResultCode.WorkBufferTooSmall;
}
+ Memory splitterBqfStates = Memory.Empty;
+
+ if (_behaviourContext.IsBiquadFilterParameterForSplitterEnabled() &&
+ parameter.SplitterCount > 0 &&
+ parameter.SplitterDestinationCount > 0)
+ {
+ splitterBqfStates = workBufferAllocator.Allocate(parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10);
+
+ if (splitterBqfStates.IsEmpty)
+ {
+ return ResultCode.WorkBufferTooSmall;
+ }
+
+ splitterBqfStates.Span.Clear();
+ }
+
// Invalidate DSP cache on what was currently allocated with workBuffer.
AudioProcessorMemoryManager.InvalidateDspCache(_dspMemoryPoolState.Translate(workBuffer, workBufferAllocator.Offset), workBufferAllocator.Offset);
@@ -292,7 +309,7 @@ namespace Ryujinx.Audio.Renderer.Server
state = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu);
}
- if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator))
+ if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator, splitterBqfStates))
{
return ResultCode.WorkBufferTooSmall;
}
@@ -775,6 +792,13 @@ namespace Ryujinx.Audio.Renderer.Server
// Splitter
size = SplitterContext.GetWorkBufferSize(size, ref behaviourContext, ref parameter);
+ if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled() &&
+ parameter.SplitterCount > 0 &&
+ parameter.SplitterDestinationCount > 0)
+ {
+ size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10);
+ }
+
// DSP Voice
size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceUpdateState.Align);
diff --git a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
index fe1dfc4beb..32c7de6cfb 100644
--- a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
@@ -45,7 +45,6 @@ namespace Ryujinx.Audio.Renderer.Server
/// was added to supply the count of update done sent to the DSP.
/// A new version of the command estimator was added to address timing changes caused by the voice changes.
/// Additionally, the rendering limit percent was incremented to 80%.
- ///
///
/// This was added in system update 6.0.0
public const int Revision5 = 5 << 24;
@@ -101,10 +100,18 @@ namespace Ryujinx.Audio.Renderer.Server
/// This was added in system update 14.0.0 but some changes were made in 15.0.0
public const int Revision11 = 11 << 24;
+ ///
+ /// REV12:
+ /// Two new commands were added to for biquad filtering and mixing (with optinal volume ramp) on the same command.
+ /// Splitter destinations can now specify up to two biquad filtering parameters, used for filtering the buffer before mixing.
+ ///
+ /// This was added in system update 17.0.0
+ public const int Revision12 = 12 << 24;
+
///
/// Last revision supported by the implementation.
///
- public const int LastRevision = Revision11;
+ public const int LastRevision = Revision12;
///
/// Target revision magic supported by the implementation.
@@ -354,7 +361,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// Check if the audio renderer should use an optimized Biquad Filter (Direct Form 1) in case of two biquad filters are defined on a voice.
///
/// True if the audio renderer should use the optimization.
- public bool IsBiquadFilterGroupedOptimizationSupported()
+ public bool UseMultiTapBiquadFilterProcessing()
{
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10);
}
@@ -368,6 +375,15 @@ namespace Ryujinx.Audio.Renderer.Server
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11);
}
+ ///
+ /// Check if the audio renderer should support biquad filter on splitter.
+ ///
+ /// True if the audio renderer support biquad filter on splitter
+ public bool IsBiquadFilterParameterForSplitterEnabled()
+ {
+ return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision12);
+ }
+
///
/// Get the version of the .
///
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs b/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
index f4174a913f..702f05462f 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
@@ -204,7 +204,7 @@ namespace Ryujinx.Audio.Renderer.Server
}
///
- /// Create a new .
+ /// Create a new .
///
/// The base index of the input and output buffer.
/// The biquad filter parameters.
@@ -213,9 +213,9 @@ namespace Ryujinx.Audio.Renderer.Server
/// The output buffer offset.
/// Set to true if the biquad filter state is initialized.
/// The node id associated to this command.
- public void GenerateGroupedBiquadFilter(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId)
+ public void GenerateMultiTapBiquadFilter(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId)
{
- GroupedBiquadFilterCommand command = new(baseIndex, filters, biquadFilterStatesMemory, inputBufferOffset, outputBufferOffset, isInitialized, nodeId);
+ MultiTapBiquadFilterCommand command = new(baseIndex, filters, biquadFilterStatesMemory, inputBufferOffset, outputBufferOffset, isInitialized, nodeId);
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
@@ -232,7 +232,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// The new volume.
/// The to generate the command from.
/// The node id associated to this command.
- public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span previousVolume, Span volume, Memory state, int nodeId)
+ public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, ReadOnlySpan previousVolume, ReadOnlySpan volume, Memory state, int nodeId)
{
MixRampGroupedCommand command = new(mixBufferCount, inputBufferIndex, outputBufferIndex, previousVolume, volume, state, nodeId);
@@ -260,6 +260,120 @@ namespace Ryujinx.Audio.Renderer.Server
AddCommand(command);
}
+ ///
+ /// Generate a new .
+ ///
+ /// The previous volume.
+ /// The new volume.
+ /// The input buffer index.
+ /// The output buffer index.
+ /// The index in the array to store the ramped sample.
+ /// The to generate the command from.
+ /// The biquad filter parameter.
+ /// The biquad state.
+ /// The previous biquad state.
+ /// Set to true if the biquad filter state needs to be initialized.
+ /// Set to true if the mix has volume ramp, and should be taken into account.
+ /// Set to true if the buffer is the first mix buffer.
+ /// The node id associated to this command.
+ public void GenerateBiquadFilterAndMix(
+ float previousVolume,
+ float volume,
+ uint inputBufferIndex,
+ uint outputBufferIndex,
+ int lastSampleIndex,
+ Memory state,
+ ref BiquadFilterParameter filter,
+ Memory biquadFilterState,
+ Memory previousBiquadFilterState,
+ bool needInitialization,
+ bool hasVolumeRamp,
+ bool isFirstMixBuffer,
+ int nodeId)
+ {
+ BiquadFilterAndMixCommand command = new(
+ previousVolume,
+ volume,
+ inputBufferIndex,
+ outputBufferIndex,
+ lastSampleIndex,
+ state,
+ ref filter,
+ biquadFilterState,
+ previousBiquadFilterState,
+ needInitialization,
+ hasVolumeRamp,
+ isFirstMixBuffer,
+ nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ ///
+ /// Generate a new .
+ ///
+ /// The previous volume.
+ /// The new volume.
+ /// The input buffer index.
+ /// The output buffer index.
+ /// The index in the array to store the ramped sample.
+ /// The to generate the command from.
+ /// First biquad filter parameter.
+ /// Second biquad filter parameter.
+ /// First biquad state.
+ /// Second biquad state.
+ /// First previous biquad state.
+ /// Second previous biquad state.
+ /// Set to true if the first biquad filter state needs to be initialized.
+ /// Set to true if the second biquad filter state needs to be initialized.
+ /// Set to true if the mix has volume ramp, and should be taken into account.
+ /// Set to true if the buffer is the first mix buffer.
+ /// The node id associated to this command.
+ public void GenerateMultiTapBiquadFilterAndMix(
+ float previousVolume,
+ float volume,
+ uint inputBufferIndex,
+ uint outputBufferIndex,
+ int lastSampleIndex,
+ Memory state,
+ ref BiquadFilterParameter filter0,
+ ref BiquadFilterParameter filter1,
+ Memory biquadFilterState0,
+ Memory biquadFilterState1,
+ Memory previousBiquadFilterState0,
+ Memory previousBiquadFilterState1,
+ bool needInitialization0,
+ bool needInitialization1,
+ bool hasVolumeRamp,
+ bool isFirstMixBuffer,
+ int nodeId)
+ {
+ MultiTapBiquadFilterAndMixCommand command = new(
+ previousVolume,
+ volume,
+ inputBufferIndex,
+ outputBufferIndex,
+ lastSampleIndex,
+ state,
+ ref filter0,
+ ref filter1,
+ biquadFilterState0,
+ biquadFilterState1,
+ previousBiquadFilterState0,
+ previousBiquadFilterState1,
+ needInitialization0,
+ needInitialization1,
+ hasVolumeRamp,
+ isFirstMixBuffer,
+ nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
///
/// Generate a new .
///
@@ -268,7 +382,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// The buffer count.
/// The node id associated to this command.
/// The target sample rate in use.
- public void GenerateDepopForMixBuffersCommand(Memory depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate)
+ public void GenerateDepopForMixBuffers(Memory depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate)
{
DepopForMixBuffersCommand command = new(depopBuffer, bufferOffset, bufferCount, nodeId, sampleRate);
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs b/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
index ae8f699f3a..d798230c1d 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
@@ -12,6 +12,7 @@ using Ryujinx.Audio.Renderer.Server.Voice;
using Ryujinx.Audio.Renderer.Utils;
using System;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Server
{
@@ -46,12 +47,13 @@ namespace Ryujinx.Audio.Renderer.Server
{
ref MixState mix = ref _mixContext.GetState(voiceState.MixId);
- _commandBuffer.GenerateDepopPrepare(dspState,
- _rendererContext.DepopBuffer,
- mix.BufferCount,
- mix.BufferOffset,
- voiceState.NodeId,
- voiceState.WasPlaying);
+ _commandBuffer.GenerateDepopPrepare(
+ dspState,
+ _rendererContext.DepopBuffer,
+ mix.BufferCount,
+ mix.BufferOffset,
+ voiceState.NodeId,
+ voiceState.WasPlaying);
}
else if (voiceState.SplitterId != Constants.UnusedSplitterId)
{
@@ -59,15 +61,13 @@ namespace Ryujinx.Audio.Renderer.Server
while (true)
{
- Span destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++);
+ SplitterDestination destination = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++);
- if (destinationSpan.IsEmpty)
+ if (destination.IsNull)
{
break;
}
- ref SplitterDestination destination = ref destinationSpan[0];
-
if (destination.IsConfigured())
{
int mixId = destination.DestinationId;
@@ -76,12 +76,13 @@ namespace Ryujinx.Audio.Renderer.Server
{
ref MixState mix = ref _mixContext.GetState(mixId);
- _commandBuffer.GenerateDepopPrepare(dspState,
- _rendererContext.DepopBuffer,
- mix.BufferCount,
- mix.BufferOffset,
- voiceState.NodeId,
- voiceState.WasPlaying);
+ _commandBuffer.GenerateDepopPrepare(
+ dspState,
+ _rendererContext.DepopBuffer,
+ mix.BufferCount,
+ mix.BufferOffset,
+ voiceState.NodeId,
+ voiceState.WasPlaying);
destination.MarkAsNeedToUpdateInternalState();
}
@@ -95,35 +96,39 @@ namespace Ryujinx.Audio.Renderer.Server
if (_rendererContext.BehaviourContext.IsWaveBufferVersion2Supported())
{
- _commandBuffer.GenerateDataSourceVersion2(ref voiceState,
- dspState,
- (ushort)_rendererContext.MixBufferCount,
- (ushort)channelIndex,
- voiceState.NodeId);
+ _commandBuffer.GenerateDataSourceVersion2(
+ ref voiceState,
+ dspState,
+ (ushort)_rendererContext.MixBufferCount,
+ (ushort)channelIndex,
+ voiceState.NodeId);
}
else
{
switch (voiceState.SampleFormat)
{
case SampleFormat.PcmInt16:
- _commandBuffer.GeneratePcmInt16DataSourceVersion1(ref voiceState,
- dspState,
- (ushort)_rendererContext.MixBufferCount,
- (ushort)channelIndex,
- voiceState.NodeId);
+ _commandBuffer.GeneratePcmInt16DataSourceVersion1(
+ ref voiceState,
+ dspState,
+ (ushort)_rendererContext.MixBufferCount,
+ (ushort)channelIndex,
+ voiceState.NodeId);
break;
case SampleFormat.PcmFloat:
- _commandBuffer.GeneratePcmFloatDataSourceVersion1(ref voiceState,
- dspState,
- (ushort)_rendererContext.MixBufferCount,
- (ushort)channelIndex,
- voiceState.NodeId);
+ _commandBuffer.GeneratePcmFloatDataSourceVersion1(
+ ref voiceState,
+ dspState,
+ (ushort)_rendererContext.MixBufferCount,
+ (ushort)channelIndex,
+ voiceState.NodeId);
break;
case SampleFormat.Adpcm:
- _commandBuffer.GenerateAdpcmDataSourceVersion1(ref voiceState,
- dspState,
- (ushort)_rendererContext.MixBufferCount,
- voiceState.NodeId);
+ _commandBuffer.GenerateAdpcmDataSourceVersion1(
+ ref voiceState,
+ dspState,
+ (ushort)_rendererContext.MixBufferCount,
+ voiceState.NodeId);
break;
default:
throw new NotImplementedException($"Unsupported data source {voiceState.SampleFormat}");
@@ -134,14 +139,14 @@ namespace Ryujinx.Audio.Renderer.Server
private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory state, int baseIndex, int bufferOffset, int nodeId)
{
- bool supportsOptimizedPath = _rendererContext.BehaviourContext.IsBiquadFilterGroupedOptimizationSupported();
+ bool supportsOptimizedPath = _rendererContext.BehaviourContext.UseMultiTapBiquadFilterProcessing();
if (supportsOptimizedPath && voiceState.BiquadFilters[0].Enable && voiceState.BiquadFilters[1].Enable)
{
- Memory biquadStateRawMemory = SpanMemoryManager.Cast(state)[..(VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount)];
+ Memory biquadStateRawMemory = SpanMemoryManager.Cast(state)[..(Unsafe.SizeOf() * Constants.VoiceBiquadFilterCount)];
Memory stateMemory = SpanMemoryManager.Cast(biquadStateRawMemory);
- _commandBuffer.GenerateGroupedBiquadFilter(baseIndex, voiceState.BiquadFilters.AsSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId);
+ _commandBuffer.GenerateMultiTapBiquadFilter(baseIndex, voiceState.BiquadFilters.AsSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId);
}
else
{
@@ -151,33 +156,134 @@ namespace Ryujinx.Audio.Renderer.Server
if (filter.Enable)
{
- Memory biquadStateRawMemory = SpanMemoryManager.Cast(state)[..(VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount)];
-
+ Memory biquadStateRawMemory = SpanMemoryManager.Cast(state)[..(Unsafe.SizeOf() * Constants.VoiceBiquadFilterCount)];
Memory stateMemory = SpanMemoryManager.Cast(biquadStateRawMemory);
- _commandBuffer.GenerateBiquadFilter(baseIndex,
- ref filter,
- stateMemory.Slice(i, 1),
- bufferOffset,
- bufferOffset,
- !voiceState.BiquadFilterNeedInitialization[i],
- nodeId);
+ _commandBuffer.GenerateBiquadFilter(
+ baseIndex,
+ ref filter,
+ stateMemory.Slice(i, 1),
+ bufferOffset,
+ bufferOffset,
+ !voiceState.BiquadFilterNeedInitialization[i],
+ nodeId);
}
}
}
}
- private void GenerateVoiceMix(Span mixVolumes, Span previousMixVolumes, Memory state, uint bufferOffset, uint bufferCount, uint bufferIndex, int nodeId)
+ private void GenerateVoiceMixWithSplitter(
+ SplitterDestination destination,
+ Memory state,
+ uint bufferOffset,
+ uint bufferCount,
+ uint bufferIndex,
+ int nodeId)
+ {
+ ReadOnlySpan mixVolumes = destination.MixBufferVolume;
+ ReadOnlySpan previousMixVolumes = destination.PreviousMixBufferVolume;
+
+ ref BiquadFilterParameter bqf0 = ref destination.GetBiquadFilterParameter(0);
+ ref BiquadFilterParameter bqf1 = ref destination.GetBiquadFilterParameter(1);
+
+ Memory bqfState = _splitterContext.GetBiquadFilterState(destination);
+
+ bool isFirstMixBuffer = true;
+
+ for (int i = 0; i < bufferCount; i++)
+ {
+ float previousMixVolume = previousMixVolumes[i];
+ float mixVolume = mixVolumes[i];
+
+ if (mixVolume != 0.0f || previousMixVolume != 0.0f)
+ {
+ if (bqf0.Enable && bqf1.Enable)
+ {
+ _commandBuffer.GenerateMultiTapBiquadFilterAndMix(
+ previousMixVolume,
+ mixVolume,
+ bufferIndex,
+ bufferOffset + (uint)i,
+ i,
+ state,
+ ref bqf0,
+ ref bqf1,
+ bqfState[..1],
+ bqfState.Slice(1, 1),
+ bqfState.Slice(2, 1),
+ bqfState.Slice(3, 1),
+ !destination.IsBiquadFilterEnabledPrev(),
+ !destination.IsBiquadFilterEnabledPrev(),
+ true,
+ isFirstMixBuffer,
+ nodeId);
+
+ destination.UpdateBiquadFilterEnabledPrev(0);
+ destination.UpdateBiquadFilterEnabledPrev(1);
+ }
+ else if (bqf0.Enable)
+ {
+ _commandBuffer.GenerateBiquadFilterAndMix(
+ previousMixVolume,
+ mixVolume,
+ bufferIndex,
+ bufferOffset + (uint)i,
+ i,
+ state,
+ ref bqf0,
+ bqfState[..1],
+ bqfState.Slice(1, 1),
+ !destination.IsBiquadFilterEnabledPrev(),
+ true,
+ isFirstMixBuffer,
+ nodeId);
+
+ destination.UpdateBiquadFilterEnabledPrev(0);
+ }
+ else if (bqf1.Enable)
+ {
+ _commandBuffer.GenerateBiquadFilterAndMix(
+ previousMixVolume,
+ mixVolume,
+ bufferIndex,
+ bufferOffset + (uint)i,
+ i,
+ state,
+ ref bqf1,
+ bqfState[..1],
+ bqfState.Slice(1, 1),
+ !destination.IsBiquadFilterEnabledPrev(),
+ true,
+ isFirstMixBuffer,
+ nodeId);
+
+ destination.UpdateBiquadFilterEnabledPrev(1);
+ }
+
+ isFirstMixBuffer = false;
+ }
+ }
+ }
+
+ private void GenerateVoiceMix(
+ ReadOnlySpan mixVolumes,
+ ReadOnlySpan previousMixVolumes,
+ Memory state,
+ uint bufferOffset,
+ uint bufferCount,
+ uint bufferIndex,
+ int nodeId)
{
if (bufferCount > Constants.VoiceChannelCountMax)
{
- _commandBuffer.GenerateMixRampGrouped(bufferCount,
- bufferIndex,
- bufferOffset,
- previousMixVolumes,
- mixVolumes,
- state,
- nodeId);
+ _commandBuffer.GenerateMixRampGrouped(
+ bufferCount,
+ bufferIndex,
+ bufferOffset,
+ previousMixVolumes,
+ mixVolumes,
+ state,
+ nodeId);
}
else
{
@@ -188,13 +294,14 @@ namespace Ryujinx.Audio.Renderer.Server
if (mixVolume != 0.0f || previousMixVolume != 0.0f)
{
- _commandBuffer.GenerateMixRamp(previousMixVolume,
- mixVolume,
- bufferIndex,
- bufferOffset + (uint)i,
- i,
- state,
- nodeId);
+ _commandBuffer.GenerateMixRamp(
+ previousMixVolume,
+ mixVolume,
+ bufferIndex,
+ bufferOffset + (uint)i,
+ i,
+ state,
+ nodeId);
}
}
}
@@ -271,10 +378,11 @@ namespace Ryujinx.Audio.Renderer.Server
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
}
- _commandBuffer.GenerateVolumeRamp(voiceState.PreviousVolume,
- voiceState.Volume,
- _rendererContext.MixBufferCount + (uint)channelIndex,
- nodeId);
+ _commandBuffer.GenerateVolumeRamp(
+ voiceState.PreviousVolume,
+ voiceState.Volume,
+ _rendererContext.MixBufferCount + (uint)channelIndex,
+ nodeId);
if (performanceInitialized)
{
@@ -291,15 +399,13 @@ namespace Ryujinx.Audio.Renderer.Server
while (true)
{
- Span destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId);
+ SplitterDestination destination = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId);
- if (destinationSpan.IsEmpty)
+ if (destination.IsNull)
{
break;
}
- ref SplitterDestination destination = ref destinationSpan[0];
-
destinationId += (int)channelsCount;
if (destination.IsConfigured())
@@ -310,13 +416,27 @@ namespace Ryujinx.Audio.Renderer.Server
{
ref MixState mix = ref _mixContext.GetState(mixId);
- GenerateVoiceMix(destination.MixBufferVolume,
- destination.PreviousMixBufferVolume,
- dspStateMemory,
- mix.BufferOffset,
- mix.BufferCount,
- _rendererContext.MixBufferCount + (uint)channelIndex,
- nodeId);
+ if (destination.IsBiquadFilterEnabled())
+ {
+ GenerateVoiceMixWithSplitter(
+ destination,
+ dspStateMemory,
+ mix.BufferOffset,
+ mix.BufferCount,
+ _rendererContext.MixBufferCount + (uint)channelIndex,
+ nodeId);
+ }
+ else
+ {
+ GenerateVoiceMix(
+ destination.MixBufferVolume,
+ destination.PreviousMixBufferVolume,
+ dspStateMemory,
+ mix.BufferOffset,
+ mix.BufferCount,
+ _rendererContext.MixBufferCount + (uint)channelIndex,
+ nodeId);
+ }
destination.MarkAsNeedToUpdateInternalState();
}
@@ -337,13 +457,14 @@ namespace Ryujinx.Audio.Renderer.Server
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
}
- GenerateVoiceMix(channelResource.Mix.AsSpan(),
- channelResource.PreviousMix.AsSpan(),
- dspStateMemory,
- mix.BufferOffset,
- mix.BufferCount,
- _rendererContext.MixBufferCount + (uint)channelIndex,
- nodeId);
+ GenerateVoiceMix(
+ channelResource.Mix.AsSpan(),
+ channelResource.PreviousMix.AsSpan(),
+ dspStateMemory,
+ mix.BufferOffset,
+ mix.BufferCount,
+ _rendererContext.MixBufferCount + (uint)channelIndex,
+ nodeId);
if (performanceInitialized)
{
@@ -409,10 +530,11 @@ namespace Ryujinx.Audio.Renderer.Server
{
if (effect.Parameter.Volumes[i] != 0.0f)
{
- _commandBuffer.GenerateMix((uint)bufferOffset + effect.Parameter.Input[i],
- (uint)bufferOffset + effect.Parameter.Output[i],
- nodeId,
- effect.Parameter.Volumes[i]);
+ _commandBuffer.GenerateMix(
+ (uint)bufferOffset + effect.Parameter.Input[i],
+ (uint)bufferOffset + effect.Parameter.Output[i],
+ nodeId,
+ effect.Parameter.Volumes[i]);
}
}
}
@@ -447,17 +569,18 @@ namespace Ryujinx.Audio.Renderer.Server
updateCount = newUpdateCount;
}
- _commandBuffer.GenerateAuxEffect(bufferOffset,
- effect.Parameter.Input[i],
- effect.Parameter.Output[i],
- ref effect.State,
- effect.IsEnabled,
- effect.Parameter.BufferStorageSize,
- effect.State.SendBufferInfoBase,
- effect.State.ReturnBufferInfoBase,
- updateCount,
- writeOffset,
- nodeId);
+ _commandBuffer.GenerateAuxEffect(
+ bufferOffset,
+ effect.Parameter.Input[i],
+ effect.Parameter.Output[i],
+ ref effect.State,
+ effect.IsEnabled,
+ effect.Parameter.BufferStorageSize,
+ effect.State.SendBufferInfoBase,
+ effect.State.ReturnBufferInfoBase,
+ updateCount,
+ writeOffset,
+ nodeId);
writeOffset = newUpdateCount;
@@ -500,7 +623,7 @@ namespace Ryujinx.Audio.Renderer.Server
if (effect.IsEnabled)
{
bool needInitialization = effect.Parameter.Status == UsageState.Invalid ||
- (effect.Parameter.Status == UsageState.New && !_rendererContext.BehaviourContext.IsBiquadFilterEffectStateClearBugFixed());
+ (effect.Parameter.Status == UsageState.New && !_rendererContext.BehaviourContext.IsBiquadFilterEffectStateClearBugFixed());
BiquadFilterParameter parameter = new()
{
@@ -512,11 +635,14 @@ namespace Ryujinx.Audio.Renderer.Server
for (int i = 0; i < effect.Parameter.ChannelCount; i++)
{
- _commandBuffer.GenerateBiquadFilter((int)bufferOffset, ref parameter, effect.State.Slice(i, 1),
- effect.Parameter.Input[i],
- effect.Parameter.Output[i],
- needInitialization,
- nodeId);
+ _commandBuffer.GenerateBiquadFilter(
+ (int)bufferOffset,
+ ref parameter,
+ effect.State.Slice(i, 1),
+ effect.Parameter.Input[i],
+ effect.Parameter.Output[i],
+ needInitialization,
+ nodeId);
}
}
else
@@ -591,15 +717,16 @@ namespace Ryujinx.Audio.Renderer.Server
updateCount = newUpdateCount;
}
- _commandBuffer.GenerateCaptureEffect(bufferOffset,
- effect.Parameter.Input[i],
- effect.State.SendBufferInfo,
- effect.IsEnabled,
- effect.Parameter.BufferStorageSize,
- effect.State.SendBufferInfoBase,
- updateCount,
- writeOffset,
- nodeId);
+ _commandBuffer.GenerateCaptureEffect(
+ bufferOffset,
+ effect.Parameter.Input[i],
+ effect.State.SendBufferInfo,
+ effect.IsEnabled,
+ effect.Parameter.BufferStorageSize,
+ effect.State.SendBufferInfoBase,
+ updateCount,
+ writeOffset,
+ nodeId);
writeOffset = newUpdateCount;
@@ -612,11 +739,12 @@ namespace Ryujinx.Audio.Renderer.Server
{
Debug.Assert(effect.Type == EffectType.Compressor);
- _commandBuffer.GenerateCompressorEffect(bufferOffset,
- effect.Parameter,
- effect.State,
- effect.IsEnabled,
- nodeId);
+ _commandBuffer.GenerateCompressorEffect(
+ bufferOffset,
+ effect.Parameter,
+ effect.State,
+ effect.IsEnabled,
+ nodeId);
}
private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect)
@@ -629,8 +757,11 @@ namespace Ryujinx.Audio.Renderer.Server
bool performanceInitialized = false;
- if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, effect.GetPerformanceDetailType(),
- isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix, nodeId))
+ if (_performanceManager != null && _performanceManager.GetNextEntry(
+ out performanceEntry,
+ effect.GetPerformanceDetailType(),
+ isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix,
+ nodeId))
{
performanceInitialized = true;
@@ -706,6 +837,85 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
+ private void GenerateMixWithSplitter(
+ uint inputBufferIndex,
+ uint outputBufferIndex,
+ float volume,
+ SplitterDestination destination,
+ ref bool isFirstMixBuffer,
+ int nodeId)
+ {
+ ref BiquadFilterParameter bqf0 = ref destination.GetBiquadFilterParameter(0);
+ ref BiquadFilterParameter bqf1 = ref destination.GetBiquadFilterParameter(1);
+
+ Memory bqfState = _splitterContext.GetBiquadFilterState(destination);
+
+ if (bqf0.Enable && bqf1.Enable)
+ {
+ _commandBuffer.GenerateMultiTapBiquadFilterAndMix(
+ 0f,
+ volume,
+ inputBufferIndex,
+ outputBufferIndex,
+ 0,
+ Memory.Empty,
+ ref bqf0,
+ ref bqf1,
+ bqfState[..1],
+ bqfState.Slice(1, 1),
+ bqfState.Slice(2, 1),
+ bqfState.Slice(3, 1),
+ !destination.IsBiquadFilterEnabledPrev(),
+ !destination.IsBiquadFilterEnabledPrev(),
+ false,
+ isFirstMixBuffer,
+ nodeId);
+
+ destination.UpdateBiquadFilterEnabledPrev(0);
+ destination.UpdateBiquadFilterEnabledPrev(1);
+ }
+ else if (bqf0.Enable)
+ {
+ _commandBuffer.GenerateBiquadFilterAndMix(
+ 0f,
+ volume,
+ inputBufferIndex,
+ outputBufferIndex,
+ 0,
+ Memory.Empty,
+ ref bqf0,
+ bqfState[..1],
+ bqfState.Slice(1, 1),
+ !destination.IsBiquadFilterEnabledPrev(),
+ false,
+ isFirstMixBuffer,
+ nodeId);
+
+ destination.UpdateBiquadFilterEnabledPrev(0);
+ }
+ else if (bqf1.Enable)
+ {
+ _commandBuffer.GenerateBiquadFilterAndMix(
+ 0f,
+ volume,
+ inputBufferIndex,
+ outputBufferIndex,
+ 0,
+ Memory.Empty,
+ ref bqf1,
+ bqfState[..1],
+ bqfState.Slice(1, 1),
+ !destination.IsBiquadFilterEnabledPrev(),
+ false,
+ isFirstMixBuffer,
+ nodeId);
+
+ destination.UpdateBiquadFilterEnabledPrev(1);
+ }
+
+ isFirstMixBuffer = false;
+ }
+
private void GenerateMix(ref MixState mix)
{
if (mix.HasAnyDestination())
@@ -722,15 +932,13 @@ namespace Ryujinx.Audio.Renderer.Server
{
int destinationIndex = destinationId++;
- Span destinationSpan = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex);
+ SplitterDestination destination = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex);
- if (destinationSpan.IsEmpty)
+ if (destination.IsNull)
{
break;
}
- ref SplitterDestination destination = ref destinationSpan[0];
-
if (destination.IsConfigured())
{
int mixId = destination.DestinationId;
@@ -741,16 +949,32 @@ namespace Ryujinx.Audio.Renderer.Server
uint inputBufferIndex = mix.BufferOffset + ((uint)destinationIndex % mix.BufferCount);
+ bool isFirstMixBuffer = true;
+
for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++)
{
float volume = mix.Volume * destination.GetMixVolume((int)bufferDestinationIndex);
if (volume != 0.0f)
{
- _commandBuffer.GenerateMix(inputBufferIndex,
- destinationMix.BufferOffset + bufferDestinationIndex,
- mix.NodeId,
- volume);
+ if (destination.IsBiquadFilterEnabled())
+ {
+ GenerateMixWithSplitter(
+ inputBufferIndex,
+ destinationMix.BufferOffset + bufferDestinationIndex,
+ volume,
+ destination,
+ ref isFirstMixBuffer,
+ mix.NodeId);
+ }
+ else
+ {
+ _commandBuffer.GenerateMix(
+ inputBufferIndex,
+ destinationMix.BufferOffset + bufferDestinationIndex,
+ mix.NodeId,
+ volume);
+ }
}
}
}
@@ -770,10 +994,11 @@ namespace Ryujinx.Audio.Renderer.Server
if (volume != 0.0f)
{
- _commandBuffer.GenerateMix(mix.BufferOffset + bufferIndex,
- destinationMix.BufferOffset + bufferDestinationIndex,
- mix.NodeId,
- volume);
+ _commandBuffer.GenerateMix(
+ mix.BufferOffset + bufferIndex,
+ destinationMix.BufferOffset + bufferDestinationIndex,
+ mix.NodeId,
+ volume);
}
}
}
@@ -783,11 +1008,12 @@ namespace Ryujinx.Audio.Renderer.Server
private void GenerateSubMix(ref MixState subMix)
{
- _commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer,
- subMix.BufferOffset,
- subMix.BufferCount,
- subMix.NodeId,
- subMix.SampleRate);
+ _commandBuffer.GenerateDepopForMixBuffers(
+ _rendererContext.DepopBuffer,
+ subMix.BufferOffset,
+ subMix.BufferCount,
+ subMix.NodeId,
+ subMix.SampleRate);
GenerateEffects(ref subMix);
@@ -847,11 +1073,12 @@ namespace Ryujinx.Audio.Renderer.Server
{
ref MixState finalMix = ref _mixContext.GetFinalState();
- _commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer,
- finalMix.BufferOffset,
- finalMix.BufferCount,
- finalMix.NodeId,
- finalMix.SampleRate);
+ _commandBuffer.GenerateDepopForMixBuffers(
+ _rendererContext.DepopBuffer,
+ finalMix.BufferOffset,
+ finalMix.BufferCount,
+ finalMix.NodeId,
+ finalMix.SampleRate);
GenerateEffects(ref finalMix);
@@ -882,9 +1109,10 @@ namespace Ryujinx.Audio.Renderer.Server
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
}
- _commandBuffer.GenerateVolume(finalMix.Volume,
- finalMix.BufferOffset + bufferIndex,
- nodeId);
+ _commandBuffer.GenerateVolume(
+ finalMix.Volume,
+ finalMix.BufferOffset + bufferIndex,
+ nodeId);
if (performanceSubInitialized)
{
@@ -938,41 +1166,45 @@ namespace Ryujinx.Audio.Renderer.Server
if (useCustomDownMixingCommand)
{
- _commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset,
- sink.Parameter.Input.AsSpan(),
- sink.Parameter.Input.AsSpan(),
- sink.DownMixCoefficients,
- Constants.InvalidNodeId);
+ _commandBuffer.GenerateDownMixSurroundToStereo(
+ finalMix.BufferOffset,
+ sink.Parameter.Input.AsSpan(),
+ sink.Parameter.Input.AsSpan(),
+ sink.DownMixCoefficients,
+ Constants.InvalidNodeId);
}
// NOTE: We do the downmixing at the DSP level as it's easier that way.
else if (_rendererContext.ChannelCount == 2 && sink.Parameter.InputCount == 6)
{
- _commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset,
- sink.Parameter.Input.AsSpan(),
- sink.Parameter.Input.AsSpan(),
- Constants.DefaultSurroundToStereoCoefficients,
- Constants.InvalidNodeId);
+ _commandBuffer.GenerateDownMixSurroundToStereo(
+ finalMix.BufferOffset,
+ sink.Parameter.Input.AsSpan(),
+ sink.Parameter.Input.AsSpan(),
+ Constants.DefaultSurroundToStereoCoefficients,
+ Constants.InvalidNodeId);
}
CommandList commandList = _commandBuffer.CommandList;
if (sink.UpsamplerState != null)
{
- _commandBuffer.GenerateUpsample(finalMix.BufferOffset,
- sink.UpsamplerState,
- sink.Parameter.InputCount,
- sink.Parameter.Input.AsSpan(),
- commandList.BufferCount,
- commandList.SampleCount,
- commandList.SampleRate,
- Constants.InvalidNodeId);
+ _commandBuffer.GenerateUpsample(
+ finalMix.BufferOffset,
+ sink.UpsamplerState,
+ sink.Parameter.InputCount,
+ sink.Parameter.Input.AsSpan(),
+ commandList.BufferCount,
+ commandList.SampleCount,
+ commandList.SampleRate,
+ Constants.InvalidNodeId);
}
- _commandBuffer.GenerateDeviceSink(finalMix.BufferOffset,
- sink,
- _rendererContext.SessionId,
- commandList.Buffers,
- Constants.InvalidNodeId);
+ _commandBuffer.GenerateDeviceSink(
+ finalMix.BufferOffset,
+ sink,
+ _rendererContext.SessionId,
+ commandList.Buffers,
+ Constants.InvalidNodeId);
}
private void GenerateSink(BaseSink sink, ref MixState finalMix)
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
index d95e9aa71a..cff754b825 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
@@ -170,7 +170,7 @@ namespace Ryujinx.Audio.Renderer.Server
return 0;
}
- public uint Estimate(GroupedBiquadFilterCommand command)
+ public uint Estimate(MultiTapBiquadFilterCommand command)
{
return 0;
}
@@ -184,5 +184,15 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
+
+ public uint Estimate(BiquadFilterAndMixCommand command)
+ {
+ return 0;
+ }
+
+ public uint Estimate(MultiTapBiquadFilterAndMixCommand command)
+ {
+ return 0;
+ }
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
index 929aaf383d..ef13269241 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
@@ -462,7 +462,7 @@ namespace Ryujinx.Audio.Renderer.Server
return 0;
}
- public uint Estimate(GroupedBiquadFilterCommand command)
+ public uint Estimate(MultiTapBiquadFilterCommand command)
{
return 0;
}
@@ -476,5 +476,15 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
+
+ public uint Estimate(BiquadFilterAndMixCommand command)
+ {
+ return 0;
+ }
+
+ public uint Estimate(MultiTapBiquadFilterAndMixCommand command)
+ {
+ return 0;
+ }
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
index 8ae4bc0591..31a5347b41 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
@@ -632,7 +632,7 @@ namespace Ryujinx.Audio.Renderer.Server
};
}
- public virtual uint Estimate(GroupedBiquadFilterCommand command)
+ public virtual uint Estimate(MultiTapBiquadFilterCommand command)
{
return 0;
}
@@ -646,5 +646,15 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
+
+ public virtual uint Estimate(BiquadFilterAndMixCommand command)
+ {
+ return 0;
+ }
+
+ public virtual uint Estimate(MultiTapBiquadFilterAndMixCommand command)
+ {
+ return 0;
+ }
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs
index 25bc67cd93..fb357120d3 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs
@@ -10,7 +10,7 @@ namespace Ryujinx.Audio.Renderer.Server
{
public CommandProcessingTimeEstimatorVersion4(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { }
- public override uint Estimate(GroupedBiquadFilterCommand command)
+ public override uint Estimate(MultiTapBiquadFilterCommand command)
{
Debug.Assert(SampleCount == 160 || SampleCount == 240);
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs
index 7135c1c4f0..06f135a886 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs
@@ -210,5 +210,53 @@ namespace Ryujinx.Audio.Renderer.Server
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
};
}
+
+ public override uint Estimate(BiquadFilterAndMixCommand command)
+ {
+ Debug.Assert(SampleCount == 160 || SampleCount == 240);
+
+ if (command.HasVolumeRamp)
+ {
+ if (SampleCount == 160)
+ {
+ return 5204;
+ }
+
+ return 6683;
+ }
+ else
+ {
+ if (SampleCount == 160)
+ {
+ return 3427;
+ }
+
+ return 4752;
+ }
+ }
+
+ public override uint Estimate(MultiTapBiquadFilterAndMixCommand command)
+ {
+ Debug.Assert(SampleCount == 160 || SampleCount == 240);
+
+ if (command.HasVolumeRamp)
+ {
+ if (SampleCount == 160)
+ {
+ return 7939;
+ }
+
+ return 10669;
+ }
+ else
+ {
+ if (SampleCount == 160)
+ {
+ return 6256;
+ }
+
+ return 8683;
+ }
+ }
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs b/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
index 27b22363a3..9c4312ad67 100644
--- a/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
@@ -33,8 +33,10 @@ namespace Ryujinx.Audio.Renderer.Server
uint Estimate(UpsampleCommand command);
uint Estimate(LimiterCommandVersion1 command);
uint Estimate(LimiterCommandVersion2 command);
- uint Estimate(GroupedBiquadFilterCommand command);
+ uint Estimate(MultiTapBiquadFilterCommand command);
uint Estimate(CaptureBufferCommand command);
uint Estimate(CompressorCommand command);
+ uint Estimate(BiquadFilterAndMixCommand command);
+ uint Estimate(MultiTapBiquadFilterAndMixCommand command);
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs
index b90574da92..5ba58ea5b9 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs
@@ -225,11 +225,11 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
for (int i = 0; i < splitter.DestinationCount; i++)
{
- Span destination = splitter.GetData(i);
+ SplitterDestination destination = splitter.GetData(i);
- if (!destination.IsEmpty)
+ if (!destination.IsNull)
{
- int destinationMixId = destination[0].DestinationId;
+ int destinationMixId = destination.DestinationId;
if (destinationMixId != UnusedMixId)
{
diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs
index 0a035916cb..da5a0ad455 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs
@@ -18,16 +18,12 @@ namespace Ryujinx.Audio.Renderer.Server.Performance
if (version == 2)
{
- return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter);
+ return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter);
}
if (version == 1)
{
- return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter);
+ return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter);
}
throw new NotImplementedException($"Unknown Performance metrics data format version {version}");
diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs
index 5a70a1bcff..2e5d25b9cc 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs
@@ -234,7 +234,7 @@ namespace Ryujinx.Audio.Renderer.Server.Performance
{
performanceEntry = null;
- if (_entryDetailIndex > MaxFrameDetailCount)
+ if (_entryDetailIndex >= MaxFrameDetailCount)
{
return false;
}
@@ -245,7 +245,7 @@ namespace Ryujinx.Audio.Renderer.Server.Performance
EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset(),
};
- uint baseEntryOffset = (uint)(Unsafe.SizeOf() + GetEntriesSize() + Unsafe.SizeOf() * _entryDetailIndex);
+ uint baseEntryOffset = (uint)(Unsafe.SizeOf() + GetEntriesSize() + Unsafe.SizeOf() * _entryDetailIndex);
ref TEntryDetail entryDetail = ref EntriesDetail[_entryDetailIndex];
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs
index 3efa783c37..a7b82a6bdc 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs
@@ -1,4 +1,5 @@
using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Utils;
using Ryujinx.Common;
@@ -15,15 +16,35 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
///
public class SplitterContext
{
+ ///
+ /// Amount of biquad filter states per splitter destination.
+ ///
+ public const int BqfStatesPerDestination = 4;
+
///
/// Storage for .
///
private Memory _splitters;
///
- /// Storage for .
+ /// Storage for .
///
- private Memory _splitterDestinations;
+ private Memory _splitterDestinationsV1;
+
+ ///
+ /// Storage for .
+ ///
+ private Memory _splitterDestinationsV2;
+
+ ///
+ /// Splitter biquad filtering states.
+ ///
+ private Memory _splitterBqfStates;
+
+ ///
+ /// Version of the splitter context that is being used, currently can be 1 or 2.
+ ///
+ public int Version { get; private set; }
///
/// If set to true, trust the user destination count in .
@@ -36,12 +57,17 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// The behaviour context.
/// The audio renderer configuration.
/// The .
+ /// Memory to store the biquad filtering state for splitters during processing.
/// Return true if the initialization was successful.
- public bool Initialize(ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter, WorkBufferAllocator workBufferAllocator)
+ public bool Initialize(
+ ref BehaviourContext behaviourContext,
+ ref AudioRendererConfiguration parameter,
+ WorkBufferAllocator workBufferAllocator,
+ Memory splitterBqfStates)
{
if (!behaviourContext.IsSplitterSupported() || parameter.SplitterCount <= 0 || parameter.SplitterDestinationCount <= 0)
{
- Setup(Memory.Empty, Memory.Empty, false);
+ Setup(Memory.Empty, Memory.Empty, Memory.Empty, false);
return true;
}
@@ -60,23 +86,62 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
splitter = new SplitterState(splitterId++);
}
- Memory splitterDestinations = workBufferAllocator.Allocate(parameter.SplitterDestinationCount,
- SplitterDestination.Alignment);
+ Memory splitterDestinationsV1 = Memory.Empty;
+ Memory splitterDestinationsV2 = Memory.Empty;
- if (splitterDestinations.IsEmpty)
+ if (!behaviourContext.IsBiquadFilterParameterForSplitterEnabled())
{
- return false;
+ Version = 1;
+
+ splitterDestinationsV1 = workBufferAllocator.Allocate(parameter.SplitterDestinationCount,
+ SplitterDestinationVersion1.Alignment);
+
+ if (splitterDestinationsV1.IsEmpty)
+ {
+ return false;
+ }
+
+ int splitterDestinationId = 0;
+ foreach (ref SplitterDestinationVersion1 data in splitterDestinationsV1.Span)
+ {
+ data = new SplitterDestinationVersion1(splitterDestinationId++);
+ }
}
-
- int splitterDestinationId = 0;
- foreach (ref SplitterDestination data in splitterDestinations.Span)
+ else
{
- data = new SplitterDestination(splitterDestinationId++);
+ Version = 2;
+
+ splitterDestinationsV2 = workBufferAllocator.Allocate(parameter.SplitterDestinationCount,
+ SplitterDestinationVersion2.Alignment);
+
+ if (splitterDestinationsV2.IsEmpty)
+ {
+ return false;
+ }
+
+ int splitterDestinationId = 0;
+ foreach (ref SplitterDestinationVersion2 data in splitterDestinationsV2.Span)
+ {
+ data = new SplitterDestinationVersion2(splitterDestinationId++);
+ }
+
+ if (parameter.SplitterDestinationCount > 0)
+ {
+ // Official code stores it in the SplitterDestinationVersion2 struct,
+ // but we don't to avoid using unsafe code.
+
+ splitterBqfStates.Span.Clear();
+ _splitterBqfStates = splitterBqfStates;
+ }
+ else
+ {
+ _splitterBqfStates = Memory.Empty;
+ }
}
SplitterState.InitializeSplitters(splitters.Span);
- Setup(splitters, splitterDestinations, behaviourContext.IsSplitterBugFixed());
+ Setup(splitters, splitterDestinationsV1, splitterDestinationsV2, behaviourContext.IsSplitterBugFixed());
return true;
}
@@ -93,7 +158,15 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
if (behaviourContext.IsSplitterSupported())
{
size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterCount, SplitterState.Alignment);
- size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, SplitterDestination.Alignment);
+
+ if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled())
+ {
+ size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, SplitterDestinationVersion2.Alignment);
+ }
+ else
+ {
+ size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, SplitterDestinationVersion1.Alignment);
+ }
if (behaviourContext.IsSplitterBugFixed())
{
@@ -110,12 +183,18 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// Setup the instance.
///
/// The storage.
- /// The storage.
+ /// The storage.
+ /// The storage.
/// If set to true, trust the user destination count in .
- private void Setup(Memory splitters, Memory splitterDestinations, bool isBugFixed)
+ private void Setup(
+ Memory splitters,
+ Memory splitterDestinationsV1,
+ Memory splitterDestinationsV2,
+ bool isBugFixed)
{
_splitters = splitters;
- _splitterDestinations = splitterDestinations;
+ _splitterDestinationsV1 = splitterDestinationsV1;
+ _splitterDestinationsV2 = splitterDestinationsV2;
IsBugFixed = isBugFixed;
}
@@ -141,7 +220,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
return 0;
}
- return _splitterDestinations.Length / _splitters.Length;
+ int length = _splitterDestinationsV2.IsEmpty ? _splitterDestinationsV1.Length : _splitterDestinationsV2.Length;
+
+ return length / _splitters.Length;
}
///
@@ -178,7 +259,39 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
}
///
- /// Update one or multiple from user parameters.
+ /// Update one splitter destination data from user parameters.
+ ///
+ /// The raw data after the splitter header.
+ /// True if the update was successful, false otherwise
+ private bool UpdateData(ref SequenceReader input) where T : unmanaged, ISplitterDestinationInParameter
+ {
+ ref readonly T parameter = ref input.GetRefOrRefToCopy(out _);
+
+ Debug.Assert(parameter.IsMagicValid());
+
+ if (parameter.IsMagicValid())
+ {
+ int length = _splitterDestinationsV2.IsEmpty ? _splitterDestinationsV1.Length : _splitterDestinationsV2.Length;
+
+ if (parameter.Id >= 0 && parameter.Id < length)
+ {
+ SplitterDestination destination = GetDestination(parameter.Id);
+
+ destination.Update(parameter);
+ }
+
+ return true;
+ }
+ else
+ {
+ input.Rewind(Unsafe.SizeOf());
+
+ return false;
+ }
+ }
+
+ ///
+ /// Update one or multiple splitter destination data from user parameters.
///
/// The splitter header.
/// The raw data after the splitter header.
@@ -186,23 +299,23 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{
for (int i = 0; i < inputHeader.SplitterDestinationCount; i++)
{
- ref readonly SplitterDestinationInParameter parameter = ref input.GetRefOrRefToCopy(out _);
-
- Debug.Assert(parameter.IsMagicValid());
-
- if (parameter.IsMagicValid())
+ if (Version == 1)
{
- if (parameter.Id >= 0 && parameter.Id < _splitterDestinations.Length)
+ if (!UpdateData(ref input))
{
- ref SplitterDestination destination = ref GetDestination(parameter.Id);
-
- destination.Update(parameter);
+ break;
+ }
+ }
+ else if (Version == 2)
+ {
+ if (!UpdateData(ref input))
+ {
+ break;
}
}
else
{
- input.Rewind(Unsafe.SizeOf());
- break;
+ Debug.Fail($"Invalid splitter context version {Version}.");
}
}
}
@@ -214,7 +327,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// Return true if the update was successful.
public bool Update(ref SequenceReader input)
{
- if (_splitterDestinations.IsEmpty || _splitters.IsEmpty)
+ if (!UsingSplitter())
{
return true;
}
@@ -251,45 +364,52 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
}
///
- /// Get a reference to a at the given .
+ /// Get a reference to the splitter destination data at the given .
///
/// The index to use.
- /// A reference to a at the given .
- public ref SplitterDestination GetDestination(int id)
+ /// A reference to the splitter destination data at the given .
+ public SplitterDestination GetDestination(int id)
{
- return ref SpanIOHelper.GetFromMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length);
+ if (_splitterDestinationsV2.IsEmpty)
+ {
+ return new SplitterDestination(ref SpanIOHelper.GetFromMemory(_splitterDestinationsV1, id, (uint)_splitterDestinationsV1.Length));
+ }
+ else
+ {
+ return new SplitterDestination(ref SpanIOHelper.GetFromMemory(_splitterDestinationsV2, id, (uint)_splitterDestinationsV2.Length));
+ }
}
///
- /// Get a at the given .
- ///
- /// The index to use.
- /// A at the given .
- public Memory GetDestinationMemory(int id)
- {
- return SpanIOHelper.GetMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length);
- }
-
- ///
- /// Get a in the at and pass to .
+ /// Get a in the at and pass to .
///
/// The index to use to get the .
/// The index of the .
- /// A .
- public Span GetDestination(int id, int destinationId)
+ /// A .
+ public SplitterDestination GetDestination(int id, int destinationId)
{
ref SplitterState splitter = ref GetState(id);
return splitter.GetData(destinationId);
}
+ ///
+ /// Gets the biquad filter state for a given splitter destination.
+ ///
+ /// The splitter destination.
+ /// Biquad filter state for the specified destination.
+ public Memory GetBiquadFilterState(SplitterDestination destination)
+ {
+ return _splitterBqfStates.Slice(destination.Id * BqfStatesPerDestination, BqfStatesPerDestination);
+ }
+
///
/// Return true if the audio renderer has any splitters.
///
/// True if the audio renderer has any splitters.
public bool UsingSplitter()
{
- return !_splitters.IsEmpty && !_splitterDestinations.IsEmpty;
+ return !_splitters.IsEmpty && (!_splitterDestinationsV1.IsEmpty || !_splitterDestinationsV2.IsEmpty);
}
///
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs
index 1faf7921f1..36dfa5e413 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs
@@ -1,115 +1,198 @@
using Ryujinx.Audio.Renderer.Parameter;
-using Ryujinx.Common.Utilities;
using System;
using System.Diagnostics;
-using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Server.Splitter
{
///
/// Server state for a splitter destination.
///
- [StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)]
- public struct SplitterDestination
+ public ref struct SplitterDestination
{
- public const int Alignment = 0x10;
+ private ref SplitterDestinationVersion1 _v1;
+ private ref SplitterDestinationVersion2 _v2;
///
- /// The unique id of this .
+ /// Checks if the splitter destination data reference is null.
///
- public int Id;
+ public bool IsNull => Unsafe.IsNullRef(ref _v1) && Unsafe.IsNullRef(ref _v2);
///
- /// The mix to output the result of the splitter.
+ /// The splitter unique id.
///
- public int DestinationId;
-
- ///
- /// Mix buffer volumes storage.
- ///
- private MixArray _mix;
- private MixArray _previousMix;
-
- ///
- /// Pointer to the next linked element.
- ///
- private unsafe SplitterDestination* _next;
-
- ///
- /// Set to true if in use.
- ///
- [MarshalAs(UnmanagedType.I1)]
- public bool IsUsed;
-
- ///
- /// Set to true if the internal state need to be updated.
- ///
- [MarshalAs(UnmanagedType.I1)]
- public bool NeedToUpdateInternalState;
-
- [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)]
- private struct MixArray { }
-
- ///
- /// Mix buffer volumes.
- ///
- /// Used when a splitter id is specified in the mix.
- public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mix);
-
- ///
- /// Previous mix buffer volumes.
- ///
- /// Used when a splitter id is specified in the mix.
- public Span PreviousMixBufferVolume => SpanHelpers.AsSpan(ref _previousMix);
-
- ///
- /// Get the of the next element or if not present.
- ///
- public readonly Span Next
+ public int Id
{
get
{
- unsafe
+ if (Unsafe.IsNullRef(ref _v2))
{
- return _next != null ? new Span(_next, 1) : Span.Empty;
+ if (Unsafe.IsNullRef(ref _v1))
+ {
+ return 0;
+ }
+ else
+ {
+ return _v1.Id;
+ }
+ }
+ else
+ {
+ return _v2.Id;
}
}
}
///
- /// Create a new .
+ /// The mix to output the result of the splitter.
///
- /// The unique id of this .
- public SplitterDestination(int id) : this()
+ public int DestinationId
{
- Id = id;
- DestinationId = Constants.UnusedMixId;
-
- ClearVolumes();
+ get
+ {
+ if (Unsafe.IsNullRef(ref _v2))
+ {
+ if (Unsafe.IsNullRef(ref _v1))
+ {
+ return 0;
+ }
+ else
+ {
+ return _v1.DestinationId;
+ }
+ }
+ else
+ {
+ return _v2.DestinationId;
+ }
+ }
}
///
- /// Update the from user parameter.
+ /// Mix buffer volumes.
+ ///
+ /// Used when a splitter id is specified in the mix.
+ public Span MixBufferVolume
+ {
+ get
+ {
+ if (Unsafe.IsNullRef(ref _v2))
+ {
+ if (Unsafe.IsNullRef(ref _v1))
+ {
+ return Span.Empty;
+ }
+ else
+ {
+ return _v1.MixBufferVolume;
+ }
+ }
+ else
+ {
+ return _v2.MixBufferVolume;
+ }
+ }
+ }
+
+ ///
+ /// Previous mix buffer volumes.
+ ///
+ /// Used when a splitter id is specified in the mix.
+ public Span PreviousMixBufferVolume
+ {
+ get
+ {
+ if (Unsafe.IsNullRef(ref _v2))
+ {
+ if (Unsafe.IsNullRef(ref _v1))
+ {
+ return Span.Empty;
+ }
+ else
+ {
+ return _v1.PreviousMixBufferVolume;
+ }
+ }
+ else
+ {
+ return _v2.PreviousMixBufferVolume;
+ }
+ }
+ }
+
+ ///
+ /// Get the of the next element or null if not present.
+ ///
+ public readonly SplitterDestination Next
+ {
+ get
+ {
+ unsafe
+ {
+ if (Unsafe.IsNullRef(ref _v2))
+ {
+ if (Unsafe.IsNullRef(ref _v1))
+ {
+ return new SplitterDestination();
+ }
+ else
+ {
+ return new SplitterDestination(ref _v1.Next);
+ }
+ }
+ else
+ {
+ return new SplitterDestination(ref _v2.Next);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Creates a new splitter destination wrapper for the version 1 splitter destination data.
+ ///
+ /// Version 1 splitter destination data
+ public SplitterDestination(ref SplitterDestinationVersion1 v1)
+ {
+ _v1 = ref v1;
+ _v2 = ref Unsafe.NullRef();
+ }
+
+ ///
+ /// Creates a new splitter destination wrapper for the version 2 splitter destination data.
+ ///
+ /// Version 2 splitter destination data
+ public SplitterDestination(ref SplitterDestinationVersion2 v2)
+ {
+
+ _v1 = ref Unsafe.NullRef();
+ _v2 = ref v2;
+ }
+
+ ///
+ /// Creates a new splitter destination wrapper for the splitter destination data.
+ ///
+ /// Version 1 splitter destination data
+ /// Version 2 splitter destination data
+ public unsafe SplitterDestination(SplitterDestinationVersion1* v1, SplitterDestinationVersion2* v2)
+ {
+ _v1 = ref Unsafe.AsRef(v1);
+ _v2 = ref Unsafe.AsRef(v2);
+ }
+
+ ///
+ /// Update the splitter destination data from user parameter.
///
/// The user parameter.
- public void Update(SplitterDestinationInParameter parameter)
+ public void Update(in T parameter) where T : ISplitterDestinationInParameter
{
- Debug.Assert(Id == parameter.Id);
-
- if (parameter.IsMagicValid() && Id == parameter.Id)
+ if (Unsafe.IsNullRef(ref _v2))
{
- DestinationId = parameter.DestinationId;
-
- parameter.MixBufferVolume.CopyTo(MixBufferVolume);
-
- if (!IsUsed && parameter.IsUsed)
- {
- MixBufferVolume.CopyTo(PreviousMixBufferVolume);
-
- NeedToUpdateInternalState = false;
- }
-
- IsUsed = parameter.IsUsed;
+ _v1.Update(parameter);
+ }
+ else
+ {
+ _v2.Update(parameter);
}
}
@@ -118,12 +201,14 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
///
public void UpdateInternalState()
{
- if (IsUsed && NeedToUpdateInternalState)
+ if (Unsafe.IsNullRef(ref _v2))
{
- MixBufferVolume.CopyTo(PreviousMixBufferVolume);
+ _v1.UpdateInternalState();
+ }
+ else
+ {
+ _v2.UpdateInternalState();
}
-
- NeedToUpdateInternalState = false;
}
///
@@ -131,16 +216,23 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
///
public void MarkAsNeedToUpdateInternalState()
{
- NeedToUpdateInternalState = true;
+ if (Unsafe.IsNullRef(ref _v2))
+ {
+ _v1.MarkAsNeedToUpdateInternalState();
+ }
+ else
+ {
+ _v2.MarkAsNeedToUpdateInternalState();
+ }
}
///
- /// Return true if the is used and has a destination.
+ /// Return true if the splitter destination is used and has a destination.
///
- /// True if the is used and has a destination.
+ /// True if the splitter destination is used and has a destination.
public readonly bool IsConfigured()
{
- return IsUsed && DestinationId != Constants.UnusedMixId;
+ return Unsafe.IsNullRef(ref _v2) ? _v1.IsConfigured() : _v2.IsConfigured();
}
///
@@ -150,9 +242,17 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// The volume for the given destination.
public float GetMixVolume(int destinationIndex)
{
- Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
+ return Unsafe.IsNullRef(ref _v2) ? _v1.GetMixVolume(destinationIndex) : _v2.GetMixVolume(destinationIndex);
+ }
- return MixBufferVolume[destinationIndex];
+ ///
+ /// Get the previous volume for a given destination.
+ ///
+ /// The destination index to use.
+ /// The volume for the given destination.
+ public float GetMixVolumePrev(int destinationIndex)
+ {
+ return Unsafe.IsNullRef(ref _v2) ? _v1.GetMixVolumePrev(destinationIndex) : _v2.GetMixVolumePrev(destinationIndex);
}
///
@@ -160,22 +260,33 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
///
public void ClearVolumes()
{
- MixBufferVolume.Clear();
- PreviousMixBufferVolume.Clear();
+ if (Unsafe.IsNullRef(ref _v2))
+ {
+ _v1.ClearVolumes();
+ }
+ else
+ {
+ _v2.ClearVolumes();
+ }
}
///
- /// Link the next element to the given .
+ /// Link the next element to the given splitter destination.
///
- /// The given to link.
- public void Link(ref SplitterDestination next)
+ /// The given splitter destination to link.
+ public void Link(SplitterDestination next)
{
- unsafe
+ if (Unsafe.IsNullRef(ref _v2))
{
- fixed (SplitterDestination* nextPtr = &next)
- {
- _next = nextPtr;
- }
+ Debug.Assert(!Unsafe.IsNullRef(ref next._v1));
+
+ _v1.Link(ref next._v1);
+ }
+ else
+ {
+ Debug.Assert(!Unsafe.IsNullRef(ref next._v2));
+
+ _v2.Link(ref next._v2);
}
}
@@ -184,10 +295,74 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
///
public void Unlink()
{
- unsafe
+ if (Unsafe.IsNullRef(ref _v2))
{
- _next = null;
+ _v1.Unlink();
}
+ else
+ {
+ _v2.Unlink();
+ }
+ }
+
+ ///
+ /// Checks if any biquad filter is enabled.
+ ///
+ /// True if any biquad filter is enabled.
+ public bool IsBiquadFilterEnabled()
+ {
+ return !Unsafe.IsNullRef(ref _v2) && _v2.IsBiquadFilterEnabled();
+ }
+
+ ///
+ /// Checks if any biquad filter was previously enabled.
+ ///
+ /// True if any biquad filter was previously enabled.
+ public bool IsBiquadFilterEnabledPrev()
+ {
+ return !Unsafe.IsNullRef(ref _v2) && _v2.IsBiquadFilterEnabledPrev();
+ }
+
+ ///
+ /// Gets the biquad filter parameters.
+ ///
+ /// Biquad filter index (0 or 1).
+ /// Biquad filter parameters.
+ public ref BiquadFilterParameter GetBiquadFilterParameter(int index)
+ {
+ Debug.Assert(!Unsafe.IsNullRef(ref _v2));
+
+ return ref _v2.GetBiquadFilterParameter(index);
+ }
+
+ ///
+ /// Checks if any biquad filter was previously enabled.
+ ///
+ /// Biquad filter index (0 or 1).
+ public void UpdateBiquadFilterEnabledPrev(int index)
+ {
+ if (!Unsafe.IsNullRef(ref _v2))
+ {
+ _v2.UpdateBiquadFilterEnabledPrev(index);
+ }
+ }
+
+ ///
+ /// Get the reference for the version 1 splitter destination data, or null if version 2 is being used or the destination is null.
+ ///
+ /// Reference for the version 1 splitter destination data.
+ public ref SplitterDestinationVersion1 GetV1RefOrNull()
+ {
+ return ref _v1;
+ }
+
+ ///
+ /// Get the reference for the version 2 splitter destination data, or null if version 1 is being used or the destination is null.
+ ///
+ /// Reference for the version 2 splitter destination data.
+ public ref SplitterDestinationVersion2 GetV2RefOrNull()
+ {
+ return ref _v2;
}
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs
new file mode 100644
index 0000000000..5d2b8fb0fe
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs
@@ -0,0 +1,206 @@
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Splitter
+{
+ ///
+ /// Server state for a splitter destination (version 1).
+ ///
+ [StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)]
+ public struct SplitterDestinationVersion1
+ {
+ public const int Alignment = 0x10;
+
+ ///
+ /// The unique id of this .
+ ///
+ public int Id;
+
+ ///
+ /// The mix to output the result of the splitter.
+ ///
+ public int DestinationId;
+
+ ///
+ /// Mix buffer volumes storage.
+ ///
+ private MixArray _mix;
+ private MixArray _previousMix;
+
+ ///
+ /// Pointer to the next linked element.
+ ///
+ private unsafe SplitterDestinationVersion1* _next;
+
+ ///
+ /// Set to true if in use.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsUsed;
+
+ ///
+ /// Set to true if the internal state need to be updated.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool NeedToUpdateInternalState;
+
+ [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
+ private struct MixArray { }
+
+ ///
+ /// Mix buffer volumes.
+ ///
+ /// Used when a splitter id is specified in the mix.
+ public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mix);
+
+ ///
+ /// Previous mix buffer volumes.
+ ///
+ /// Used when a splitter id is specified in the mix.
+ public Span PreviousMixBufferVolume => SpanHelpers.AsSpan(ref _previousMix);
+
+ ///
+ /// Get the reference of the next element or null if not present.
+ ///
+ public readonly ref SplitterDestinationVersion1 Next
+ {
+ get
+ {
+ unsafe
+ {
+ return ref Unsafe.AsRef(_next);
+ }
+ }
+ }
+
+ ///
+ /// Create a new .
+ ///
+ /// The unique id of this .
+ public SplitterDestinationVersion1(int id) : this()
+ {
+ Id = id;
+ DestinationId = Constants.UnusedMixId;
+
+ ClearVolumes();
+ }
+
+ ///
+ /// Update the from user parameter.
+ ///
+ /// The user parameter.
+ public void Update(in T parameter) where T : ISplitterDestinationInParameter
+ {
+ Debug.Assert(Id == parameter.Id);
+
+ if (parameter.IsMagicValid() && Id == parameter.Id)
+ {
+ DestinationId = parameter.DestinationId;
+
+ parameter.MixBufferVolume.CopyTo(MixBufferVolume);
+
+ if (!IsUsed && parameter.IsUsed)
+ {
+ MixBufferVolume.CopyTo(PreviousMixBufferVolume);
+
+ NeedToUpdateInternalState = false;
+ }
+
+ IsUsed = parameter.IsUsed;
+ }
+ }
+
+ ///
+ /// Update the internal state of the instance.
+ ///
+ public void UpdateInternalState()
+ {
+ if (IsUsed && NeedToUpdateInternalState)
+ {
+ MixBufferVolume.CopyTo(PreviousMixBufferVolume);
+ }
+
+ NeedToUpdateInternalState = false;
+ }
+
+ ///
+ /// Set the update internal state marker.
+ ///
+ public void MarkAsNeedToUpdateInternalState()
+ {
+ NeedToUpdateInternalState = true;
+ }
+
+ ///
+ /// Return true if the is used and has a destination.
+ ///
+ /// True if the is used and has a destination.
+ public readonly bool IsConfigured()
+ {
+ return IsUsed && DestinationId != Constants.UnusedMixId;
+ }
+
+ ///
+ /// Get the volume for a given destination.
+ ///
+ /// The destination index to use.
+ /// The volume for the given destination.
+ public float GetMixVolume(int destinationIndex)
+ {
+ Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
+
+ return MixBufferVolume[destinationIndex];
+ }
+
+ ///
+ /// Get the previous volume for a given destination.
+ ///
+ /// The destination index to use.
+ /// The volume for the given destination.
+ public float GetMixVolumePrev(int destinationIndex)
+ {
+ Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
+
+ return PreviousMixBufferVolume[destinationIndex];
+ }
+
+ ///
+ /// Clear the volumes.
+ ///
+ public void ClearVolumes()
+ {
+ MixBufferVolume.Clear();
+ PreviousMixBufferVolume.Clear();
+ }
+
+ ///
+ /// Link the next element to the given .
+ ///
+ /// The given to link.
+ public void Link(ref SplitterDestinationVersion1 next)
+ {
+ unsafe
+ {
+ fixed (SplitterDestinationVersion1* nextPtr = &next)
+ {
+ _next = nextPtr;
+ }
+ }
+ }
+
+ ///
+ /// Remove the link to the next element.
+ ///
+ public void Unlink()
+ {
+ unsafe
+ {
+ _next = null;
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs
new file mode 100644
index 0000000000..f9487909d4
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs
@@ -0,0 +1,250 @@
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Common.Memory;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Splitter
+{
+ ///
+ /// Server state for a splitter destination (version 2).
+ ///
+ [StructLayout(LayoutKind.Sequential, Size = 0x110, Pack = Alignment)]
+ public struct SplitterDestinationVersion2
+ {
+ public const int Alignment = 0x10;
+
+ ///
+ /// The unique id of this .
+ ///
+ public int Id;
+
+ ///
+ /// The mix to output the result of the splitter.
+ ///
+ public int DestinationId;
+
+ ///
+ /// Mix buffer volumes storage.
+ ///
+ private MixArray _mix;
+ private MixArray _previousMix;
+
+ ///
+ /// Pointer to the next linked element.
+ ///
+ private unsafe SplitterDestinationVersion2* _next;
+
+ ///
+ /// Set to true if in use.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsUsed;
+
+ ///
+ /// Set to true if the internal state need to be updated.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool NeedToUpdateInternalState;
+
+ [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
+ private struct MixArray { }
+
+ ///
+ /// Mix buffer volumes.
+ ///
+ /// Used when a splitter id is specified in the mix.
+ public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mix);
+
+ ///
+ /// Previous mix buffer volumes.
+ ///
+ /// Used when a splitter id is specified in the mix.
+ public Span PreviousMixBufferVolume => SpanHelpers.AsSpan(ref _previousMix);
+
+ ///
+ /// Get the reference of the next element or null if not present.
+ ///
+ public readonly ref SplitterDestinationVersion2 Next
+ {
+ get
+ {
+ unsafe
+ {
+ return ref Unsafe.AsRef(_next);
+ }
+ }
+ }
+
+ private Array2 _biquadFilters;
+
+ private Array2 _isPreviousBiquadFilterEnabled;
+
+ ///
+ /// Create a new .
+ ///
+ /// The unique id of this .
+ public SplitterDestinationVersion2(int id) : this()
+ {
+ Id = id;
+ DestinationId = Constants.UnusedMixId;
+
+ ClearVolumes();
+ }
+
+ ///
+ /// Update the from user parameter.
+ ///
+ /// The user parameter.
+ public void Update(in T parameter) where T : ISplitterDestinationInParameter
+ {
+ Debug.Assert(Id == parameter.Id);
+
+ if (parameter.IsMagicValid() && Id == parameter.Id)
+ {
+ DestinationId = parameter.DestinationId;
+
+ parameter.MixBufferVolume.CopyTo(MixBufferVolume);
+
+ _biquadFilters = parameter.BiquadFilters;
+
+ if (!IsUsed && parameter.IsUsed)
+ {
+ MixBufferVolume.CopyTo(PreviousMixBufferVolume);
+
+ NeedToUpdateInternalState = false;
+ }
+
+ IsUsed = parameter.IsUsed;
+ }
+ }
+
+ ///
+ /// Update the internal state of the instance.
+ ///
+ public void UpdateInternalState()
+ {
+ if (IsUsed && NeedToUpdateInternalState)
+ {
+ MixBufferVolume.CopyTo(PreviousMixBufferVolume);
+ }
+
+ NeedToUpdateInternalState = false;
+ }
+
+ ///
+ /// Set the update internal state marker.
+ ///
+ public void MarkAsNeedToUpdateInternalState()
+ {
+ NeedToUpdateInternalState = true;
+ }
+
+ ///
+ /// Return true if the is used and has a destination.
+ ///
+ /// True if the is used and has a destination.
+ public readonly bool IsConfigured()
+ {
+ return IsUsed && DestinationId != Constants.UnusedMixId;
+ }
+
+ ///
+ /// Get the volume for a given destination.
+ ///
+ /// The destination index to use.
+ /// The volume for the given destination.
+ public float GetMixVolume(int destinationIndex)
+ {
+ Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
+
+ return MixBufferVolume[destinationIndex];
+ }
+
+ ///
+ /// Get the previous volume for a given destination.
+ ///
+ /// The destination index to use.
+ /// The volume for the given destination.
+ public float GetMixVolumePrev(int destinationIndex)
+ {
+ Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
+
+ return PreviousMixBufferVolume[destinationIndex];
+ }
+
+ ///
+ /// Clear the volumes.
+ ///
+ public void ClearVolumes()
+ {
+ MixBufferVolume.Clear();
+ PreviousMixBufferVolume.Clear();
+ }
+
+ ///
+ /// Link the next element to the given .
+ ///
+ /// The given to link.
+ public void Link(ref SplitterDestinationVersion2 next)
+ {
+ unsafe
+ {
+ fixed (SplitterDestinationVersion2* nextPtr = &next)
+ {
+ _next = nextPtr;
+ }
+ }
+ }
+
+ ///
+ /// Remove the link to the next element.
+ ///
+ public void Unlink()
+ {
+ unsafe
+ {
+ _next = null;
+ }
+ }
+
+ ///
+ /// Checks if any biquad filter is enabled.
+ ///
+ /// True if any biquad filter is enabled.
+ public bool IsBiquadFilterEnabled()
+ {
+ return _biquadFilters[0].Enable || _biquadFilters[1].Enable;
+ }
+
+ ///
+ /// Checks if any biquad filter was previously enabled.
+ ///
+ /// True if any biquad filter was previously enabled.
+ public bool IsBiquadFilterEnabledPrev()
+ {
+ return _isPreviousBiquadFilterEnabled[0];
+ }
+
+ ///
+ /// Gets the biquad filter parameters.
+ ///
+ /// Biquad filter index (0 or 1).
+ /// Biquad filter parameters.
+ public ref BiquadFilterParameter GetBiquadFilterParameter(int index)
+ {
+ return ref _biquadFilters[index];
+ }
+
+ ///
+ /// Checks if any biquad filter was previously enabled.
+ ///
+ /// Biquad filter index (0 or 1).
+ public void UpdateBiquadFilterEnabledPrev(int index)
+ {
+ _isPreviousBiquadFilterEnabled[index] = _biquadFilters[index].Enable;
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs
index 944f092d2c..3e7dce5592 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs
@@ -15,6 +15,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{
public const int Alignment = 0x10;
+ private delegate void SplitterDestinationAction(SplitterDestination destination, int index);
+
///
/// The unique id of this .
///
@@ -26,7 +28,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
public uint SampleRate;
///
- /// Count of splitter destinations ().
+ /// Count of splitter destinations.
///
public int DestinationCount;
@@ -37,20 +39,25 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
public bool HasNewConnection;
///
- /// Linked list of .
+ /// Linked list of .
///
- private unsafe SplitterDestination* _destinationsData;
+ private unsafe SplitterDestinationVersion1* _destinationDataV1;
///
- /// Span to the first element of the linked list of .
+ /// Linked list of .
///
- public readonly Span Destinations
+ private unsafe SplitterDestinationVersion2* _destinationDataV2;
+
+ ///
+ /// First element of the linked list of splitter destinations data.
+ ///
+ public readonly SplitterDestination Destination
{
get
{
unsafe
{
- return (IntPtr)_destinationsData != IntPtr.Zero ? new Span(_destinationsData, 1) : Span.Empty;
+ return new SplitterDestination(_destinationDataV1, _destinationDataV2);
}
}
}
@@ -64,20 +71,20 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
Id = id;
}
- public readonly Span GetData(int index)
+ public readonly SplitterDestination GetData(int index)
{
int i = 0;
- Span result = Destinations;
+ SplitterDestination result = Destination;
while (i < index)
{
- if (result.IsEmpty)
+ if (result.IsNull)
{
break;
}
- result = result[0].Next;
+ result = result.Next;
i++;
}
@@ -93,25 +100,25 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
}
///
- /// Utility function to apply a given to all .
+ /// Utility function to apply an action to all .
///
/// The action to execute on each elements.
- private readonly void ForEachDestination(SpanAction action)
+ private readonly void ForEachDestination(SplitterDestinationAction action)
{
- Span temp = Destinations;
+ SplitterDestination temp = Destination;
int i = 0;
while (true)
{
- if (temp.IsEmpty)
+ if (temp.IsNull)
{
break;
}
- Span next = temp[0].Next;
+ SplitterDestination next = temp.Next;
- action.Invoke(temp, i++);
+ action(temp, i++);
temp = next;
}
@@ -142,9 +149,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{
input.ReadLittleEndian(out int destinationId);
- Memory destination = context.GetDestinationMemory(destinationId);
+ SplitterDestination destination = context.GetDestination(destinationId);
- SetDestination(ref destination.Span[0]);
+ SetDestination(destination);
DestinationCount = destinationCount;
@@ -152,9 +159,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{
input.ReadLittleEndian(out destinationId);
- Memory nextDestination = context.GetDestinationMemory(destinationId);
+ SplitterDestination nextDestination = context.GetDestination(destinationId);
- destination.Span[0].Link(ref nextDestination.Span[0]);
+ destination.Link(nextDestination);
destination = nextDestination;
}
}
@@ -174,16 +181,21 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
}
///
- /// Set the head of the linked list of .
+ /// Set the head of the linked list of .
///
- /// A reference to a .
- public void SetDestination(ref SplitterDestination newValue)
+ /// New destination value.
+ public void SetDestination(SplitterDestination newValue)
{
unsafe
{
- fixed (SplitterDestination* newValuePtr = &newValue)
+ fixed (SplitterDestinationVersion1* newValuePtr = &newValue.GetV1RefOrNull())
{
- _destinationsData = newValuePtr;
+ _destinationDataV1 = newValuePtr;
+ }
+
+ fixed (SplitterDestinationVersion2* newValuePtr = &newValue.GetV2RefOrNull())
+ {
+ _destinationDataV2 = newValuePtr;
}
}
}
@@ -193,19 +205,20 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
///
public readonly void UpdateInternalState()
{
- ForEachDestination((destination, _) => destination[0].UpdateInternalState());
+ ForEachDestination((destination, _) => destination.UpdateInternalState());
}
///
- /// Clear all links from the .
+ /// Clear all links from the .
///
public void ClearLinks()
{
- ForEachDestination((destination, _) => destination[0].Unlink());
+ ForEachDestination((destination, _) => destination.Unlink());
unsafe
{
- _destinationsData = (SplitterDestination*)IntPtr.Zero;
+ _destinationDataV1 = null;
+ _destinationDataV2 = null;
}
}
@@ -219,7 +232,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{
unsafe
{
- splitter._destinationsData = (SplitterDestination*)IntPtr.Zero;
+ splitter._destinationDataV1 = null;
+ splitter._destinationDataV2 = null;
}
splitter.DestinationCount = 0;
diff --git a/src/Ryujinx.Common/GraphicsDriver/DriverUtilities.cs b/src/Ryujinx.Common/GraphicsDriver/DriverUtilities.cs
index 7fe2a4f024..a9163f3485 100644
--- a/src/Ryujinx.Common/GraphicsDriver/DriverUtilities.cs
+++ b/src/Ryujinx.Common/GraphicsDriver/DriverUtilities.cs
@@ -1,13 +1,33 @@
+using Ryujinx.Common.Utilities;
using System;
namespace Ryujinx.Common.GraphicsDriver
{
public static class DriverUtilities
{
+ private static void AddMesaFlags(string envVar, string newFlags)
+ {
+ string existingFlags = Environment.GetEnvironmentVariable(envVar);
+
+ string flags = existingFlags == null ? newFlags : $"{existingFlags},{newFlags}";
+
+ OsUtils.SetEnvironmentVariableNoCaching(envVar, flags);
+ }
+
+ public static void InitDriverConfig(bool oglThreading)
+ {
+ if (OperatingSystem.IsLinux())
+ {
+ AddMesaFlags("RADV_DEBUG", "nodcc");
+ }
+
+ ToggleOGLThreading(oglThreading);
+ }
+
public static void ToggleOGLThreading(bool enabled)
{
- Environment.SetEnvironmentVariable("mesa_glthread", enabled.ToString().ToLower());
- Environment.SetEnvironmentVariable("__GL_THREADED_OPTIMIZATIONS", enabled ? "1" : "0");
+ OsUtils.SetEnvironmentVariableNoCaching("mesa_glthread", enabled.ToString().ToLower());
+ OsUtils.SetEnvironmentVariableNoCaching("__GL_THREADED_OPTIMIZATIONS", enabled ? "1" : "0");
try
{
diff --git a/src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs b/src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs
deleted file mode 100644
index 05fb29ac71..0000000000
--- a/src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using System;
-using System.Buffers;
-using System.Threading;
-
-namespace Ryujinx.Common.Memory
-{
- public partial class ByteMemoryPool
- {
- ///
- /// Represents a that wraps an array rented from
- /// and exposes it as
- /// with a length of the requested size.
- ///
- private sealed class ByteMemoryPoolBuffer : IMemoryOwner
- {
- private byte[] _array;
- private readonly int _length;
-
- public ByteMemoryPoolBuffer(int length)
- {
- _array = ArrayPool.Shared.Rent(length);
- _length = length;
- }
-
- ///
- /// Returns a belonging to this owner.
- ///
- public Memory Memory
- {
- get
- {
- byte[] array = _array;
-
- ObjectDisposedException.ThrowIf(array is null, this);
-
- return new Memory(array, 0, _length);
- }
- }
-
- public void Dispose()
- {
- var array = Interlocked.Exchange(ref _array, null);
-
- if (array != null)
- {
- ArrayPool.Shared.Return(array);
- }
- }
- }
- }
-}
diff --git a/src/Ryujinx.Common/Memory/ByteMemoryPool.cs b/src/Ryujinx.Common/Memory/ByteMemoryPool.cs
deleted file mode 100644
index 6fd6a98aa7..0000000000
--- a/src/Ryujinx.Common/Memory/ByteMemoryPool.cs
+++ /dev/null
@@ -1,106 +0,0 @@
-using System;
-using System.Buffers;
-
-namespace Ryujinx.Common.Memory
-{
- ///
- /// Provides a pool of re-usable byte array instances.
- ///
- public static partial class ByteMemoryPool
- {
- ///
- /// Returns the maximum buffer size supported by this pool.
- ///
- public static int MaxBufferSize => Array.MaxLength;
-
- ///
- /// Rents a byte memory buffer from .
- /// The buffer may contain data from a prior use.
- ///
- /// The buffer's required length in bytes
- /// A wrapping the rented memory
- ///
- public static IMemoryOwner Rent(long length)
- => RentImpl(checked((int)length));
-
- ///
- /// Rents a byte memory buffer from .
- /// The buffer may contain data from a prior use.
- ///
- /// The buffer's required length in bytes
- /// A wrapping the rented memory
- ///
- public static IMemoryOwner Rent(ulong length)
- => RentImpl(checked((int)length));
-
- ///
- /// Rents a byte memory buffer from .
- /// The buffer may contain data from a prior use.
- ///
- /// The buffer's required length in bytes
- /// A wrapping the rented memory
- ///
- public static IMemoryOwner Rent(int length)
- => RentImpl(length);
-
- ///
- /// Rents a byte memory buffer from .
- /// The buffer's contents are cleared (set to all 0s) before returning.
- ///
- /// The buffer's required length in bytes
- /// A wrapping the rented memory
- ///
- public static IMemoryOwner RentCleared(long length)
- => RentCleared(checked((int)length));
-
- ///
- /// Rents a byte memory buffer from .
- /// The buffer's contents are cleared (set to all 0s) before returning.
- ///
- /// The buffer's required length in bytes
- /// A wrapping the rented memory
- ///
- public static IMemoryOwner RentCleared(ulong length)
- => RentCleared(checked((int)length));
-
- ///
- /// Rents a byte memory buffer from .
- /// The buffer's contents are cleared (set to all 0s) before returning.
- ///
- /// The buffer's required length in bytes
- /// A wrapping the rented memory
- ///
- public static IMemoryOwner RentCleared(int length)
- {
- var buffer = RentImpl(length);
-
- buffer.Memory.Span.Clear();
-
- return buffer;
- }
-
- ///
- /// Copies into a newly rented byte memory buffer.
- ///
- /// The byte buffer to copy
- /// A wrapping the rented memory with copied to it
- public static IMemoryOwner RentCopy(ReadOnlySpan buffer)
- {
- var copy = RentImpl(buffer.Length);
-
- buffer.CopyTo(copy.Memory.Span);
-
- return copy;
- }
-
- private static ByteMemoryPoolBuffer RentImpl(int length)
- {
- if ((uint)length > Array.MaxLength)
- {
- throw new ArgumentOutOfRangeException(nameof(length), length, null);
- }
-
- return new ByteMemoryPoolBuffer(length);
- }
- }
-}
diff --git a/src/Ryujinx.Common/Memory/MemoryOwner.cs b/src/Ryujinx.Common/Memory/MemoryOwner.cs
new file mode 100644
index 0000000000..b7fe1db778
--- /dev/null
+++ b/src/Ryujinx.Common/Memory/MemoryOwner.cs
@@ -0,0 +1,140 @@
+#nullable enable
+using System;
+using System.Buffers;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace Ryujinx.Common.Memory
+{
+ ///
+ /// An implementation with an embedded length and fast
+ /// accessor, with memory allocated from .
+ ///
+ /// The type of item to store.
+ public sealed class MemoryOwner : IMemoryOwner
+ {
+ private readonly int _length;
+ private T[]? _array;
+
+ ///
+ /// Initializes a new instance of the class with the specified parameters.
+ ///
+ /// The length of the new memory buffer to use
+ private MemoryOwner(int length)
+ {
+ _length = length;
+ _array = ArrayPool.Shared.Rent(length);
+ }
+
+ ///
+ /// Creates a new instance with the specified length.
+ ///
+ /// The length of the new memory buffer to use
+ /// A instance of the requested length
+ /// Thrown when is not valid
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryOwner Rent(int length) => new(length);
+
+ ///
+ /// Creates a new instance with the specified length and the content cleared.
+ ///
+ /// The length of the new memory buffer to use
+ /// A instance of the requested length and the content cleared
+ /// Thrown when is not valid
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryOwner RentCleared(int length)
+ {
+ MemoryOwner result = new(length);
+
+ result._array.AsSpan(0, length).Clear();
+
+ return result;
+ }
+
+ ///
+ /// Creates a new instance with the content copied from the specified buffer.
+ ///
+ /// The buffer to copy
+ /// A instance with the same length and content as
+ public static MemoryOwner RentCopy(ReadOnlySpan buffer)
+ {
+ MemoryOwner result = new(buffer.Length);
+
+ buffer.CopyTo(result._array);
+
+ return result;
+ }
+
+ ///
+ /// Gets the number of items in the current instance.
+ ///
+ public int Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _length;
+ }
+
+ ///
+ public Memory Memory
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ T[]? array = _array;
+
+ if (array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ return new(array, 0, _length);
+ }
+ }
+
+ ///
+ /// Gets a wrapping the memory belonging to the current instance.
+ ///
+ ///
+ /// Uses a trick made possible by the .NET 6+ runtime array layout.
+ ///
+ public Span Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ T[]? array = _array;
+
+ if (array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(array);
+
+ return MemoryMarshal.CreateSpan(ref firstElementRef, _length);
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ T[]? array = Interlocked.Exchange(ref _array, null);
+
+ if (array is not null)
+ {
+ ArrayPool.Shared.Return(array, RuntimeHelpers.IsReferenceOrContainsReferences());
+ }
+ }
+
+ ///
+ /// Throws an when is .
+ ///
+ [DoesNotReturn]
+ private static void ThrowObjectDisposedException()
+ {
+ throw new ObjectDisposedException(nameof(MemoryOwner), "The buffer has already been disposed.");
+ }
+ }
+}
diff --git a/src/Ryujinx.Common/Memory/SpanOwner.cs b/src/Ryujinx.Common/Memory/SpanOwner.cs
new file mode 100644
index 0000000000..acb20bcad6
--- /dev/null
+++ b/src/Ryujinx.Common/Memory/SpanOwner.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Common.Memory
+{
+ ///
+ /// A stack-only type that rents a buffer of a specified length from .
+ /// It does not implement to avoid being boxed, but should still be disposed. This
+ /// is easy since C# 8, which allows use of C# `using` constructs on any type that has a public Dispose() method.
+ /// To keep this type simple, fast, and read-only, it does not check or guard against multiple disposals.
+ /// For all these reasons, all usage should be with a `using` block or statement.
+ ///
+ /// The type of item to store.
+ public readonly ref struct SpanOwner
+ {
+ private readonly int _length;
+ private readonly T[] _array;
+
+ ///
+ /// Initializes a new instance of the struct with the specified parameters.
+ ///
+ /// The length of the new memory buffer to use
+ private SpanOwner(int length)
+ {
+ _length = length;
+ _array = ArrayPool.Shared.Rent(length);
+ }
+
+ ///
+ /// Gets an empty instance.
+ ///
+ public static SpanOwner Empty
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => new(0);
+ }
+
+ ///
+ /// Creates a new instance with the specified length.
+ ///
+ /// The length of the new memory buffer to use
+ /// A instance of the requested length
+ /// Thrown when is not valid
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SpanOwner Rent(int length) => new(length);
+
+ ///
+ /// Creates a new instance with the length and the content cleared.
+ ///
+ /// The length of the new memory buffer to use
+ /// A instance of the requested length and the content cleared
+ /// Thrown when is not valid
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SpanOwner RentCleared(int length)
+ {
+ SpanOwner result = new(length);
+
+ result._array.AsSpan(0, length).Clear();
+
+ return result;
+ }
+
+ ///
+ /// Creates a new instance with the content copied from the specified buffer.
+ ///
+ /// The buffer to copy
+ /// A instance with the same length and content as
+ public static SpanOwner RentCopy(ReadOnlySpan buffer)
+ {
+ SpanOwner result = new(buffer.Length);
+
+ buffer.CopyTo(result._array);
+
+ return result;
+ }
+
+ ///
+ /// Gets the number of items in the current instance
+ ///
+ public int Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _length;
+ }
+
+ ///
+ /// Gets a wrapping the memory belonging to the current instance.
+ ///
+ ///
+ /// Uses a trick made possible by the .NET 6+ runtime array layout.
+ ///
+ public Span Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(_array);
+
+ return MemoryMarshal.CreateSpan(ref firstElementRef, _length);
+ }
+ }
+
+ ///
+ /// Implements the duck-typed method.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Dispose()
+ {
+ ArrayPool.Shared.Return(_array, RuntimeHelpers.IsReferenceOrContainsReferences());
+ }
+ }
+}
diff --git a/src/Ryujinx.Common/Utilities/EmbeddedResources.cs b/src/Ryujinx.Common/Utilities/EmbeddedResources.cs
index e22571c966..7530c012a0 100644
--- a/src/Ryujinx.Common/Utilities/EmbeddedResources.cs
+++ b/src/Ryujinx.Common/Utilities/EmbeddedResources.cs
@@ -1,6 +1,6 @@
+using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using System;
-using System.Buffers;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -42,14 +42,14 @@ namespace Ryujinx.Common
return StreamUtils.StreamToBytes(stream);
}
- public static IMemoryOwner ReadFileToRentedMemory(string filename)
+ public static MemoryOwner ReadFileToRentedMemory(string filename)
{
var (assembly, path) = ResolveManifestPath(filename);
return ReadFileToRentedMemory(assembly, path);
}
- public static IMemoryOwner ReadFileToRentedMemory(Assembly assembly, string filename)
+ public static MemoryOwner ReadFileToRentedMemory(Assembly assembly, string filename)
{
using var stream = GetStream(assembly, filename);
diff --git a/src/Ryujinx.Common/Utilities/OsUtils.cs b/src/Ryujinx.Common/Utilities/OsUtils.cs
new file mode 100644
index 0000000000..a0791b0924
--- /dev/null
+++ b/src/Ryujinx.Common/Utilities/OsUtils.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Common.Utilities
+{
+ public partial class OsUtils
+ {
+ [LibraryImport("libc", SetLastError = true)]
+ private static partial int setenv([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value, int overwrite);
+
+ public static void SetEnvironmentVariableNoCaching(string key, string value)
+ {
+ // Set the value in the cached environment variables, too.
+ Environment.SetEnvironmentVariable(key, value);
+
+ if (!OperatingSystem.IsWindows())
+ {
+ int res = setenv(key, value, 1);
+ Debug.Assert(res != -1);
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Common/Utilities/StreamUtils.cs b/src/Ryujinx.Common/Utilities/StreamUtils.cs
index 74b6af5ecf..aeb6e0d52a 100644
--- a/src/Ryujinx.Common/Utilities/StreamUtils.cs
+++ b/src/Ryujinx.Common/Utilities/StreamUtils.cs
@@ -1,6 +1,5 @@
using Microsoft.IO;
using Ryujinx.Common.Memory;
-using System.Buffers;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@@ -16,7 +15,7 @@ namespace Ryujinx.Common.Utilities
return output.ToArray();
}
- public static IMemoryOwner StreamToRentedMemory(Stream input)
+ public static MemoryOwner StreamToRentedMemory(Stream input)
{
if (input is MemoryStream inputMemoryStream)
{
@@ -26,9 +25,9 @@ namespace Ryujinx.Common.Utilities
{
long bytesExpected = input.Length;
- IMemoryOwner ownedMemory = ByteMemoryPool.Rent(bytesExpected);
+ MemoryOwner ownedMemory = MemoryOwner.Rent(checked((int)bytesExpected));
- var destSpan = ownedMemory.Memory.Span;
+ var destSpan = ownedMemory.Span;
int totalBytesRead = 0;
@@ -66,14 +65,14 @@ namespace Ryujinx.Common.Utilities
return stream.ToArray();
}
- private static IMemoryOwner MemoryStreamToRentedMemory(MemoryStream input)
+ private static MemoryOwner MemoryStreamToRentedMemory(MemoryStream input)
{
input.Position = 0;
- IMemoryOwner ownedMemory = ByteMemoryPool.Rent(input.Length);
+ MemoryOwner ownedMemory = MemoryOwner.Rent(checked((int)input.Length));
// Discard the return value because we assume reading a MemoryStream always succeeds completely.
- _ = input.Read(ownedMemory.Memory.Span);
+ _ = input.Read(ownedMemory.Span);
return ownedMemory;
}
diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs
index 663d0aeb15..501109b861 100644
--- a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs
+++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs
@@ -303,9 +303,9 @@ namespace Ryujinx.Cpu.Jit
}
else
{
- IMemoryOwner memoryOwner = ByteMemoryPool.Rent(size);
+ MemoryOwner memoryOwner = MemoryOwner.Rent(size);
- Read(va, memoryOwner.Memory.Span);
+ Read(va, memoryOwner.Span);
return new WritableRegion(this, va, memoryOwner);
}
diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMove.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMove.cs
index 88850cb33f..d57750fc10 100644
--- a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMove.cs
+++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMove.cs
@@ -1,6 +1,5 @@
using Ryujinx.Cpu.LightningJit.CodeGen;
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
-using System.Diagnostics;
namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
{
diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSaturate.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSaturate.cs
index e2354f448c..f1b6e395b6 100644
--- a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSaturate.cs
+++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSaturate.cs
@@ -114,7 +114,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
InstEmitCommon.EmitUnsigned16BitPair(context, rd, rn, rm, (d, n, m) =>
{
context.Arm64Assembler.Add(d, n, m);
- EmitSaturateUnsignedRange(context, d, 16);
+ EmitSaturateUqadd(context, d, 16);
});
}
@@ -123,7 +123,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
InstEmitCommon.EmitUnsigned8BitPair(context, rd, rn, rm, (d, n, m) =>
{
context.Arm64Assembler.Add(d, n, m);
- EmitSaturateUnsignedRange(context, d, 8);
+ EmitSaturateUqadd(context, d, 8);
});
}
@@ -140,7 +140,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
context.Arm64Assembler.Add(d, n, m);
}
- EmitSaturateUnsignedRange(context, d, 16);
+ EmitSaturateUq(context, d, 16, e == 0);
});
}
@@ -157,25 +157,25 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
context.Arm64Assembler.Sub(d, n, m);
}
- EmitSaturateUnsignedRange(context, d, 16);
+ EmitSaturateUq(context, d, 16, e != 0);
});
}
public static void Uqsub16(CodeGenContext context, uint rd, uint rn, uint rm)
{
- InstEmitCommon.EmitSigned16BitPair(context, rd, rn, rm, (d, n, m) =>
+ InstEmitCommon.EmitUnsigned16BitPair(context, rd, rn, rm, (d, n, m) =>
{
context.Arm64Assembler.Sub(d, n, m);
- EmitSaturateUnsignedRange(context, d, 16);
+ EmitSaturateUqsub(context, d, 16);
});
}
public static void Uqsub8(CodeGenContext context, uint rd, uint rn, uint rm)
{
- InstEmitCommon.EmitSigned8BitPair(context, rd, rn, rm, (d, n, m) =>
+ InstEmitCommon.EmitUnsigned8BitPair(context, rd, rn, rm, (d, n, m) =>
{
context.Arm64Assembler.Sub(d, n, m);
- EmitSaturateUnsignedRange(context, d, 8);
+ EmitSaturateUqsub(context, d, 8);
});
}
@@ -358,7 +358,17 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
}
}
- private static void EmitSaturateUnsignedRange(CodeGenContext context, Operand value, uint saturateTo)
+ private static void EmitSaturateUqadd(CodeGenContext context, Operand value, uint saturateTo)
+ {
+ EmitSaturateUq(context, value, saturateTo, isSub: false);
+ }
+
+ private static void EmitSaturateUqsub(CodeGenContext context, Operand value, uint saturateTo)
+ {
+ EmitSaturateUq(context, value, saturateTo, isSub: true);
+ }
+
+ private static void EmitSaturateUq(CodeGenContext context, Operand value, uint saturateTo, bool isSub)
{
Debug.Assert(saturateTo <= 32);
@@ -379,7 +389,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
return;
}
- context.Arm64Assembler.Lsr(tempRegister.Operand, value, InstEmitCommon.Const(32 - (int)saturateTo));
+ context.Arm64Assembler.Lsr(tempRegister.Operand, value, InstEmitCommon.Const((int)saturateTo));
int branchIndex = context.CodeWriter.InstructionPointer;
@@ -387,7 +397,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
context.Arm64Assembler.Cbz(tempRegister.Operand, 0);
// Saturate.
- context.Arm64Assembler.Mov(value, uint.MaxValue >> (32 - (int)saturateTo));
+ context.Arm64Assembler.Mov(value, isSub ? 0u : uint.MaxValue >> (32 - (int)saturateTo));
int delta = context.CodeWriter.InstructionPointer - branchIndex;
context.CodeWriter.WriteInstructionAt(branchIndex, context.CodeWriter.ReadInstructionAt(branchIndex) | (uint)((delta & 0x7ffff) << 5));
diff --git a/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs b/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs
index fc075a2643..cb1a7c3ab6 100644
--- a/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs
+++ b/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs
@@ -1,7 +1,6 @@
using Ryujinx.Common.Memory;
using Ryujinx.Memory;
using System;
-using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -145,9 +144,9 @@ namespace Ryujinx.Graphics.Device
}
else
{
- IMemoryOwner memoryOwner = ByteMemoryPool.Rent(size);
+ MemoryOwner memoryOwner = MemoryOwner.Rent(size);
- GetSpan(va, size).CopyTo(memoryOwner.Memory.Span);
+ ReadImpl(va, memoryOwner.Span);
return new WritableRegion(this, va, memoryOwner, tracked: true);
}
diff --git a/src/Ryujinx.Graphics.Device/DeviceState.cs b/src/Ryujinx.Graphics.Device/DeviceState.cs
index de8582a3b6..54178a4140 100644
--- a/src/Ryujinx.Graphics.Device/DeviceState.cs
+++ b/src/Ryujinx.Graphics.Device/DeviceState.cs
@@ -39,7 +39,10 @@ namespace Ryujinx.Graphics.Device
{
var field = fields[fieldIndex];
- int sizeOfField = SizeCalculator.SizeOf(field.FieldType);
+ var currentFieldOffset = (int)Marshal.OffsetOf(field.Name);
+ var nextFieldOffset = fieldIndex + 1 == fields.Length ? Unsafe.SizeOf() : (int)Marshal.OffsetOf(fields[fieldIndex + 1].Name);
+
+ int sizeOfField = nextFieldOffset - currentFieldOffset;
for (int i = 0; i < ((sizeOfField + 3) & ~3); i += 4)
{
diff --git a/src/Ryujinx.Graphics.Device/SizeCalculator.cs b/src/Ryujinx.Graphics.Device/SizeCalculator.cs
deleted file mode 100644
index 54820ec36f..0000000000
--- a/src/Ryujinx.Graphics.Device/SizeCalculator.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using System;
-using System.Reflection;
-
-namespace Ryujinx.Graphics.Device
-{
- public static class SizeCalculator
- {
- public static int SizeOf(Type type)
- {
- // Is type a enum type?
- if (type.IsEnum)
- {
- type = type.GetEnumUnderlyingType();
- }
-
- // Is type a pointer type?
- if (type.IsPointer || type == typeof(IntPtr) || type == typeof(UIntPtr))
- {
- return IntPtr.Size;
- }
-
- // Is type a struct type?
- if (type.IsValueType && !type.IsPrimitive)
- {
- // Check if the struct has a explicit size, if so, return that.
- if (type.StructLayoutAttribute.Size != 0)
- {
- return type.StructLayoutAttribute.Size;
- }
-
- // Otherwise we calculate the sum of the sizes of all fields.
- int size = 0;
- var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
-
- for (int fieldIndex = 0; fieldIndex < fields.Length; fieldIndex++)
- {
- size += SizeOf(fields[fieldIndex].FieldType);
- }
-
- return size;
- }
-
- // Primitive types.
- return (Type.GetTypeCode(type)) switch
- {
- TypeCode.SByte => sizeof(sbyte),
- TypeCode.Byte => sizeof(byte),
- TypeCode.Int16 => sizeof(short),
- TypeCode.UInt16 => sizeof(ushort),
- TypeCode.Int32 => sizeof(int),
- TypeCode.UInt32 => sizeof(uint),
- TypeCode.Int64 => sizeof(long),
- TypeCode.UInt64 => sizeof(ulong),
- TypeCode.Char => sizeof(char),
- TypeCode.Single => sizeof(float),
- TypeCode.Double => sizeof(double),
- TypeCode.Decimal => sizeof(decimal),
- TypeCode.Boolean => sizeof(bool),
- _ => throw new ArgumentException($"Length for type \"{type.Name}\" is unknown."),
- };
- }
- }
-}
diff --git a/src/Ryujinx.Graphics.GAL/BufferAccess.cs b/src/Ryujinx.Graphics.GAL/BufferAccess.cs
index faefa51882..1e7736f8fa 100644
--- a/src/Ryujinx.Graphics.GAL/BufferAccess.cs
+++ b/src/Ryujinx.Graphics.GAL/BufferAccess.cs
@@ -6,8 +6,13 @@ namespace Ryujinx.Graphics.GAL
public enum BufferAccess
{
Default = 0,
- FlushPersistent = 1 << 0,
- Stream = 1 << 1,
- SparseCompatible = 1 << 2,
+ HostMemory = 1,
+ DeviceMemory = 2,
+ DeviceMemoryMapped = 3,
+
+ MemoryTypeMask = 0xf,
+
+ Stream = 1 << 4,
+ SparseCompatible = 1 << 5,
}
}
diff --git a/src/Ryujinx.Graphics.GAL/Capabilities.cs b/src/Ryujinx.Graphics.GAL/Capabilities.cs
index 70736fbd61..a5c6eb5c8e 100644
--- a/src/Ryujinx.Graphics.GAL/Capabilities.cs
+++ b/src/Ryujinx.Graphics.GAL/Capabilities.cs
@@ -6,6 +6,7 @@ namespace Ryujinx.Graphics.GAL
{
public readonly TargetApi Api;
public readonly string VendorName;
+ public readonly SystemMemoryType MemoryType;
public readonly bool HasFrontFacingBug;
public readonly bool HasVectorIndexingBug;
@@ -36,6 +37,7 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsMismatchingViewFormat;
public readonly bool SupportsCubemapView;
public readonly bool SupportsNonConstantTextureOffset;
+ public readonly bool SupportsQuads;
public readonly bool SupportsSeparateSampler;
public readonly bool SupportsShaderBallot;
public readonly bool SupportsShaderBarrierDivergence;
@@ -49,6 +51,13 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsIndirectParameters;
public readonly bool SupportsDepthClipControl;
+ public readonly int UniformBufferSetIndex;
+ public readonly int StorageBufferSetIndex;
+ public readonly int TextureSetIndex;
+ public readonly int ImageSetIndex;
+ public readonly int ExtraSetBaseIndex;
+ public readonly int MaximumExtraSets;
+
public readonly uint MaximumUniformBuffersPerStage;
public readonly uint MaximumStorageBuffersPerStage;
public readonly uint MaximumTexturesPerStage;
@@ -65,6 +74,7 @@ namespace Ryujinx.Graphics.GAL
public Capabilities(
TargetApi api,
string vendorName,
+ SystemMemoryType memoryType,
bool hasFrontFacingBug,
bool hasVectorIndexingBug,
bool needsFragmentOutputSpecialization,
@@ -93,6 +103,7 @@ namespace Ryujinx.Graphics.GAL
bool supportsMismatchingViewFormat,
bool supportsCubemapView,
bool supportsNonConstantTextureOffset,
+ bool supportsQuads,
bool supportsSeparateSampler,
bool supportsShaderBallot,
bool supportsShaderBarrierDivergence,
@@ -105,6 +116,12 @@ namespace Ryujinx.Graphics.GAL
bool supportsViewportSwizzle,
bool supportsIndirectParameters,
bool supportsDepthClipControl,
+ int uniformBufferSetIndex,
+ int storageBufferSetIndex,
+ int textureSetIndex,
+ int imageSetIndex,
+ int extraSetBaseIndex,
+ int maximumExtraSets,
uint maximumUniformBuffersPerStage,
uint maximumStorageBuffersPerStage,
uint maximumTexturesPerStage,
@@ -118,6 +135,7 @@ namespace Ryujinx.Graphics.GAL
{
Api = api;
VendorName = vendorName;
+ MemoryType = memoryType;
HasFrontFacingBug = hasFrontFacingBug;
HasVectorIndexingBug = hasVectorIndexingBug;
NeedsFragmentOutputSpecialization = needsFragmentOutputSpecialization;
@@ -146,6 +164,7 @@ namespace Ryujinx.Graphics.GAL
SupportsMismatchingViewFormat = supportsMismatchingViewFormat;
SupportsCubemapView = supportsCubemapView;
SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
+ SupportsQuads = supportsQuads;
SupportsSeparateSampler = supportsSeparateSampler;
SupportsShaderBallot = supportsShaderBallot;
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
@@ -158,6 +177,12 @@ namespace Ryujinx.Graphics.GAL
SupportsViewportSwizzle = supportsViewportSwizzle;
SupportsIndirectParameters = supportsIndirectParameters;
SupportsDepthClipControl = supportsDepthClipControl;
+ UniformBufferSetIndex = uniformBufferSetIndex;
+ StorageBufferSetIndex = storageBufferSetIndex;
+ TextureSetIndex = textureSetIndex;
+ ImageSetIndex = imageSetIndex;
+ ExtraSetBaseIndex = extraSetBaseIndex;
+ MaximumExtraSets = maximumExtraSets;
MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage;
MaximumStorageBuffersPerStage = maximumStorageBuffersPerStage;
MaximumTexturesPerStage = maximumTexturesPerStage;
diff --git a/src/Ryujinx.Graphics.GAL/Format.cs b/src/Ryujinx.Graphics.GAL/Format.cs
index bd3b38a9ac..17c42d2d41 100644
--- a/src/Ryujinx.Graphics.GAL/Format.cs
+++ b/src/Ryujinx.Graphics.GAL/Format.cs
@@ -711,5 +711,36 @@ namespace Ryujinx.Graphics.GAL
{
return format.IsUint() || format.IsSint();
}
+
+ ///
+ /// Checks if the texture format is a float or sRGB color format.
+ ///
+ ///
+ /// Does not include normalized, compressed or depth formats.
+ /// Float and sRGB formats do not participate in logical operations.
+ ///
+ /// Texture format
+ /// True if the format is a float or sRGB color format, false otherwise
+ public static bool IsFloatOrSrgb(this Format format)
+ {
+ switch (format)
+ {
+ case Format.R8G8B8A8Srgb:
+ case Format.B8G8R8A8Srgb:
+ case Format.R16Float:
+ case Format.R16G16Float:
+ case Format.R16G16B16Float:
+ case Format.R16G16B16A16Float:
+ case Format.R32Float:
+ case Format.R32G32Float:
+ case Format.R32G32B32Float:
+ case Format.R32G32B32A32Float:
+ case Format.R11G11B10Float:
+ case Format.R9G9B9E5Float:
+ return true;
+ }
+
+ return false;
+ }
}
}
diff --git a/src/Ryujinx.Graphics.GAL/IImageArray.cs b/src/Ryujinx.Graphics.GAL/IImageArray.cs
index 30cff50b15..d119aa9fbd 100644
--- a/src/Ryujinx.Graphics.GAL/IImageArray.cs
+++ b/src/Ryujinx.Graphics.GAL/IImageArray.cs
@@ -1,6 +1,8 @@
+using System;
+
namespace Ryujinx.Graphics.GAL
{
- public interface IImageArray
+ public interface IImageArray : IDisposable
{
void SetFormats(int index, Format[] imageFormats);
void SetImages(int index, ITexture[] images);
diff --git a/src/Ryujinx.Graphics.GAL/IPipeline.cs b/src/Ryujinx.Graphics.GAL/IPipeline.cs
index 9efb9e3e8f..cbf1bc3a22 100644
--- a/src/Ryujinx.Graphics.GAL/IPipeline.cs
+++ b/src/Ryujinx.Graphics.GAL/IPipeline.cs
@@ -60,6 +60,7 @@ namespace Ryujinx.Graphics.GAL
void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat);
void SetImageArray(ShaderStage stage, int binding, IImageArray array);
+ void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array);
void SetLineParameters(float width, bool smooth);
@@ -91,6 +92,7 @@ namespace Ryujinx.Graphics.GAL
void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler);
void SetTextureArray(ShaderStage stage, int binding, ITextureArray array);
+ void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array);
void SetTransformFeedbackBuffers(ReadOnlySpan buffers);
void SetUniformBuffers(ReadOnlySpan buffers);
diff --git a/src/Ryujinx.Graphics.GAL/IRenderer.cs b/src/Ryujinx.Graphics.GAL/IRenderer.cs
index a3466e3966..85d0bd729f 100644
--- a/src/Ryujinx.Graphics.GAL/IRenderer.cs
+++ b/src/Ryujinx.Graphics.GAL/IRenderer.cs
@@ -17,7 +17,6 @@ namespace Ryujinx.Graphics.GAL
void BackgroundContextAction(Action action, bool alwaysBackground = false);
BufferHandle CreateBuffer(int size, BufferAccess access = BufferAccess.Default);
- BufferHandle CreateBuffer(int size, BufferAccess access, BufferHandle storageHint);
BufferHandle CreateBuffer(nint pointer, int size);
BufferHandle CreateBufferSparse(ReadOnlySpan storageBuffers);
diff --git a/src/Ryujinx.Graphics.GAL/ITextureArray.cs b/src/Ryujinx.Graphics.GAL/ITextureArray.cs
index 35c2116b54..9ee79dacbb 100644
--- a/src/Ryujinx.Graphics.GAL/ITextureArray.cs
+++ b/src/Ryujinx.Graphics.GAL/ITextureArray.cs
@@ -1,6 +1,8 @@
+using System;
+
namespace Ryujinx.Graphics.GAL
{
- public interface ITextureArray
+ public interface ITextureArray : IDisposable
{
void SetSamplers(int index, ISampler[] samplers);
void SetTextures(int index, ITexture[] textures);
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
index fd2919be4d..ef227d4a54 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
@@ -44,7 +44,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
}
Register(CommandType.Action);
- Register(CommandType.CreateBuffer);
Register(CommandType.CreateBufferAccess);
Register(CommandType.CreateBufferSparse);
Register(CommandType.CreateHostBuffer);
@@ -67,6 +66,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register(CommandType.CounterEventDispose);
Register(CommandType.CounterEventFlush);
+ Register(CommandType.ImageArrayDispose);
Register(CommandType.ImageArraySetFormats);
Register(CommandType.ImageArraySetImages);
@@ -89,6 +89,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register(CommandType.TextureSetDataSliceRegion);
Register(CommandType.TextureSetStorage);
+ Register(CommandType.TextureArrayDispose);
Register(CommandType.TextureArraySetSamplers);
Register(CommandType.TextureArraySetTextures);
@@ -125,6 +126,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register(CommandType.SetUniformBuffers);
Register(CommandType.SetImage);
Register(CommandType.SetImageArray);
+ Register(CommandType.SetImageArraySeparate);
Register(CommandType.SetIndexBuffer);
Register(CommandType.SetLineParameters);
Register(CommandType.SetLogicOpState);
@@ -142,6 +144,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register(CommandType.SetStencilTest);
Register(CommandType.SetTextureAndSampler);
Register(CommandType.SetTextureArray);
+ Register(CommandType.SetTextureArraySeparate);
Register(CommandType.SetUserClipDistance);
Register(CommandType.SetVertexAttribs);
Register(CommandType.SetVertexBuffers);
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
index a5e7336cdf..cf3f5d6c14 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
@@ -3,7 +3,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
enum CommandType : byte
{
Action,
- CreateBuffer,
CreateBufferAccess,
CreateBufferSparse,
CreateHostBuffer,
@@ -27,6 +26,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
CounterEventDispose,
CounterEventFlush,
+ ImageArrayDispose,
ImageArraySetFormats,
ImageArraySetImages,
@@ -49,6 +49,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
TextureSetDataSliceRegion,
TextureSetStorage,
+ TextureArrayDispose,
TextureArraySetSamplers,
TextureArraySetTextures,
@@ -85,6 +86,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
SetUniformBuffers,
SetImage,
SetImageArray,
+ SetImageArraySeparate,
SetIndexBuffer,
SetLineParameters,
SetLogicOpState,
@@ -102,6 +104,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
SetStencilTest,
SetTextureAndSampler,
SetTextureArray,
+ SetTextureArraySeparate,
SetUserClipDistance,
SetVertexAttribs,
SetVertexBuffers,
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArrayDisposeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArrayDisposeCommand.cs
new file mode 100644
index 0000000000..ac2ac933b7
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArrayDisposeCommand.cs
@@ -0,0 +1,21 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using Ryujinx.Graphics.GAL.Multithreading.Resources;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray
+{
+ struct ImageArrayDisposeCommand : IGALCommand, IGALCommand
+ {
+ public readonly CommandType CommandType => CommandType.ImageArrayDispose;
+ private TableRef _imageArray;
+
+ public void Set(TableRef imageArray)
+ {
+ _imageArray = imageArray;
+ }
+
+ public static void Run(ref ImageArrayDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ command._imageArray.Get(threaded).Base.Dispose();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs
deleted file mode 100644
index 60a6e4bf45..0000000000
--- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
-{
- struct CreateBufferCommand : IGALCommand, IGALCommand
- {
- public readonly CommandType CommandType => CommandType.CreateBuffer;
- private BufferHandle _threadedHandle;
- private int _size;
- private BufferAccess _access;
- private BufferHandle _storageHint;
-
- public void Set(BufferHandle threadedHandle, int size, BufferAccess access, BufferHandle storageHint)
- {
- _threadedHandle = threadedHandle;
- _size = size;
- _access = access;
- _storageHint = storageHint;
- }
-
- public static void Run(ref CreateBufferCommand command, ThreadedRenderer threaded, IRenderer renderer)
- {
- BufferHandle hint = BufferHandle.Null;
-
- if (command._storageHint != BufferHandle.Null)
- {
- hint = threaded.Buffers.MapBuffer(command._storageHint);
- }
-
- threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size, command._access, hint));
- }
- }
-}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArraySeparateCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArraySeparateCommand.cs
new file mode 100644
index 0000000000..abeb58a06f
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArraySeparateCommand.cs
@@ -0,0 +1,26 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using Ryujinx.Graphics.GAL.Multithreading.Resources;
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetImageArraySeparateCommand : IGALCommand, IGALCommand
+ {
+ public readonly CommandType CommandType => CommandType.SetImageArraySeparate;
+ private ShaderStage _stage;
+ private int _setIndex;
+ private TableRef _array;
+
+ public void Set(ShaderStage stage, int setIndex, TableRef array)
+ {
+ _stage = stage;
+ _setIndex = setIndex;
+ _array = array;
+ }
+
+ public static void Run(ref SetImageArraySeparateCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.SetImageArraySeparate(command._stage, command._setIndex, command._array.GetAs(threaded)?.Base);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArraySeparateCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArraySeparateCommand.cs
new file mode 100644
index 0000000000..b179f2e70a
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArraySeparateCommand.cs
@@ -0,0 +1,26 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using Ryujinx.Graphics.GAL.Multithreading.Resources;
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetTextureArraySeparateCommand : IGALCommand, IGALCommand
+ {
+ public readonly CommandType CommandType => CommandType.SetTextureArraySeparate;
+ private ShaderStage _stage;
+ private int _setIndex;
+ private TableRef _array;
+
+ public void Set(ShaderStage stage, int setIndex, TableRef array)
+ {
+ _stage = stage;
+ _setIndex = setIndex;
+ _array = array;
+ }
+
+ public static void Run(ref SetTextureArraySeparateCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.SetTextureArraySeparate(command._stage, command._setIndex, command._array.GetAs(threaded)?.Base);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArrayDisposeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArrayDisposeCommand.cs
new file mode 100644
index 0000000000..fec1c48f0d
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArrayDisposeCommand.cs
@@ -0,0 +1,21 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using Ryujinx.Graphics.GAL.Multithreading.Resources;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray
+{
+ struct TextureArrayDisposeCommand : IGALCommand, IGALCommand
+ {
+ public readonly CommandType CommandType => CommandType.TextureArrayDispose;
+ private TableRef _textureArray;
+
+ public void Set(TableRef textureArray)
+ {
+ _textureArray = textureArray;
+ }
+
+ public static void Run(ref TextureArrayDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ command._textureArray.Get(threaded).Base.Dispose();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetSamplersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetSamplersCommand.cs
similarity index 100%
rename from src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetSamplersCommand.cs
rename to src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetSamplersCommand.cs
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetTexturesCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetTexturesCommand.cs
similarity index 100%
rename from src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetTexturesCommand.cs
rename to src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetTexturesCommand.cs
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs
index d26ee1fbd5..19bc6f233a 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs
@@ -21,6 +21,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
return new TableRef(_renderer, reference);
}
+ public void Dispose()
+ {
+ _renderer.New().Set(Ref(this));
+ _renderer.QueueCommand();
+ }
+
public void SetFormats(int index, Format[] imageFormats)
{
_renderer.New().Set(Ref(this), index, Ref(imageFormats));
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs
index 82405a1f69..4334c70484 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs
@@ -22,6 +22,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
return new TableRef(_renderer, reference);
}
+ public void Dispose()
+ {
+ _renderer.New().Set(Ref(this));
+ _renderer.QueueCommand();
+ }
+
public void SetSamplers(int index, ISampler[] samplers)
{
_renderer.New().Set(Ref(this), index, Ref(samplers.ToArray()));
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
index 697894eb52..edd79d8a07 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
@@ -189,6 +189,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
+ public void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array)
+ {
+ _renderer.New().Set(stage, setIndex, Ref(array));
+ _renderer.QueueCommand();
+ }
+
public void SetIndexBuffer(BufferRange buffer, IndexType type)
{
_renderer.New().Set(buffer, type);
@@ -297,6 +303,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
+ public void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array)
+ {
+ _renderer.New().Set(stage, setIndex, Ref(array));
+ _renderer.QueueCommand();
+ }
+
public void SetTransformFeedbackBuffers(ReadOnlySpan buffers)
{
_renderer.New().Set(_renderer.CopySpan(buffers));
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
index 5e17bcd2c1..cc3d2e5c11 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
@@ -272,15 +272,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return handle;
}
- public BufferHandle CreateBuffer(int size, BufferAccess access, BufferHandle storageHint)
- {
- BufferHandle handle = Buffers.CreateBufferHandle();
- New().Set(handle, size, access, storageHint);
- QueueCommand();
-
- return handle;
- }
-
public BufferHandle CreateBuffer(nint pointer, int size)
{
BufferHandle handle = Buffers.CreateBufferHandle();
diff --git a/src/Ryujinx.Graphics.GAL/ResourceLayout.cs b/src/Ryujinx.Graphics.GAL/ResourceLayout.cs
index 998c046f19..b7464ee12e 100644
--- a/src/Ryujinx.Graphics.GAL/ResourceLayout.cs
+++ b/src/Ryujinx.Graphics.GAL/ResourceLayout.cs
@@ -74,13 +74,15 @@ namespace Ryujinx.Graphics.GAL
public int ArrayLength { get; }
public ResourceType Type { get; }
public ResourceStages Stages { get; }
+ public bool Write { get; }
- public ResourceUsage(int binding, int arrayLength, ResourceType type, ResourceStages stages)
+ public ResourceUsage(int binding, int arrayLength, ResourceType type, ResourceStages stages, bool write)
{
Binding = binding;
ArrayLength = arrayLength;
Type = type;
Stages = stages;
+ Write = write;
}
public override int GetHashCode()
diff --git a/src/Ryujinx.Graphics.GAL/SystemMemoryType.cs b/src/Ryujinx.Graphics.GAL/SystemMemoryType.cs
new file mode 100644
index 0000000000..532921298a
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/SystemMemoryType.cs
@@ -0,0 +1,29 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum SystemMemoryType
+ {
+ ///
+ /// The backend manages the ownership of memory. This mode never supports host imported memory.
+ ///
+ BackendManaged,
+
+ ///
+ /// Device memory has similar performance to host memory, usually because it's shared between CPU/GPU.
+ /// Use host memory whenever possible.
+ ///
+ UnifiedMemory,
+
+ ///
+ /// GPU storage to host memory goes though a slow interconnect, but it would still be preferable to use it if the data is flushed back often.
+ /// Assumes constant buffer access to host memory is rather fast.
+ ///
+ DedicatedMemory,
+
+ ///
+ /// GPU storage to host memory goes though a slow interconnect, that is very slow when doing access from storage.
+ /// When frequently accessed, copy buffers to host memory using DMA.
+ /// Assumes constant buffer access to host memory is rather fast.
+ ///
+ DedicatedMemorySlowStorage
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs b/src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs
index 44090291dd..79c84db016 100644
--- a/src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs
+++ b/src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs
@@ -1,6 +1,5 @@
using Ryujinx.Common;
using System;
-using System.Numerics;
namespace Ryujinx.Graphics.GAL
{
@@ -113,25 +112,6 @@ namespace Ryujinx.Graphics.GAL
return 1;
}
- public int GetLevelsClamped()
- {
- int maxSize = Width;
-
- if (Target != Target.Texture1D &&
- Target != Target.Texture1DArray)
- {
- maxSize = Math.Max(maxSize, Height);
- }
-
- if (Target == Target.Texture3D)
- {
- maxSize = Math.Max(maxSize, Depth);
- }
-
- int maxLevels = BitOperations.Log2((uint)maxSize) + 1;
- return Math.Min(Levels, maxLevels);
- }
-
private static int GetLevelSize(int size, int level)
{
return Math.Max(1, size >> level);
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs
index 93e43ce3c5..78099f74a0 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs
@@ -199,7 +199,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
if (target != null)
{
target.SynchronizeMemory();
- var dataCopy = ByteMemoryPool.RentCopy(data);
+ var dataCopy = MemoryOwner.RentCopy(data);
target.SetData(dataCopy, 0, 0, new GAL.Rectangle(_dstX, _dstY, _lineLengthIn / target.Info.FormatInfo.BytesPerPixel, _lineCount));
target.SignalModified();
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs
index 7f3772f44b..475d1ee4e5 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs
@@ -5,6 +5,7 @@ using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Engine.Types;
+using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Memory.Range;
using System;
using System.Collections.Generic;
@@ -495,8 +496,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
ulong indirectBufferSize = (ulong)maxDrawCount * (ulong)stride;
- MultiRange indirectBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize);
- MultiRange parameterBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4);
+ MultiRange indirectBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize, BufferStage.Indirect);
+ MultiRange parameterBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4, BufferStage.Indirect);
_processor.ThreedClass.DrawIndirect(
topology,
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs
index f9cb40b0de..6de50fb2e7 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs
@@ -438,7 +438,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
ReadOnlySpan dataBytes = MemoryMarshal.Cast(data);
- BufferHandle buffer = _context.Renderer.CreateBuffer(dataBytes.Length);
+ BufferHandle buffer = _context.Renderer.CreateBuffer(dataBytes.Length, BufferAccess.DeviceMemory);
_context.Renderer.SetBufferData(buffer, 0, dataBytes);
return new IndexBuffer(buffer, count, dataBytes.Length);
@@ -529,7 +529,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
{
if (_dummyBuffer == BufferHandle.Null)
{
- _dummyBuffer = _context.Renderer.CreateBuffer(DummyBufferSize);
+ _dummyBuffer = _context.Renderer.CreateBuffer(DummyBufferSize, BufferAccess.DeviceMemory);
_context.Renderer.Pipeline.ClearBuffer(_dummyBuffer, 0, DummyBufferSize, 0);
}
@@ -550,7 +550,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
_context.Renderer.DeleteBuffer(_sequentialIndexBuffer);
}
- _sequentialIndexBuffer = _context.Renderer.CreateBuffer(count * sizeof(uint));
+ _sequentialIndexBuffer = _context.Renderer.CreateBuffer(count * sizeof(uint), BufferAccess.DeviceMemory);
_sequentialIndexBufferCount = count;
Span data = new int[count];
@@ -583,7 +583,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
_context.Renderer.DeleteBuffer(buffer.Handle);
}
- buffer.Handle = _context.Renderer.CreateBuffer(newSize);
+ buffer.Handle = _context.Renderer.CreateBuffer(newSize, BufferAccess.DeviceMemory);
buffer.Size = newSize;
}
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs
index 6324e6a15a..73682866ba 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs
@@ -3,6 +3,7 @@ using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
@@ -370,7 +371,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
{
var memoryManager = _channel.MemoryManager;
- BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address, size));
+ BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address, size), BufferStage.VertexBuffer);
ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format);
bufferTexture.SetStorage(range);
@@ -412,7 +413,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
var memoryManager = _channel.MemoryManager;
ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1);
- BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address + indexOffset - misalign, size + misalign));
+ BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(
+ memoryManager.GetPhysicalRegions(address + indexOffset - misalign, size + misalign),
+ BufferStage.IndexBuffer);
misalignedOffset = (int)misalign >> shift;
SetIndexBufferTexture(reservations, range, format);
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
index d8de14de09..56ef64c6ec 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
@@ -684,8 +684,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
if (hasCount)
{
- var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange);
- var parameterBuffer = memory.BufferCache.GetBufferRange(parameterBufferRange);
+ var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
+ var parameterBuffer = memory.BufferCache.GetBufferRange(parameterBufferRange, BufferStage.Indirect);
if (indexed)
{
@@ -698,7 +698,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
}
else
{
- var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange);
+ var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
if (indexed)
{
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs
index e54855a8ff..effcb7bbb7 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs
@@ -79,7 +79,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
var field = fields[fieldIndex];
- int sizeOfField = SizeCalculator.SizeOf(field.FieldType);
+ var currentFieldOffset = (int)Marshal.OffsetOf(field.Name);
+ var nextFieldOffset = fieldIndex + 1 == fields.Length ? Unsafe.SizeOf() : (int)Marshal.OffsetOf(fields[fieldIndex + 1].Name);
+
+ int sizeOfField = nextFieldOffset - currentFieldOffset;
if (fieldToDelegate.TryGetValue(field.Name, out int entryIndex))
{
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
index dd55e7d1d6..35051c6e03 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
@@ -415,7 +415,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
#pragma warning disable CS0649 // Field is never assigned to
public int Width;
public int Height;
- public int Depth;
+ public ushort Depth;
+ public ushort Flags;
+
+ public readonly bool UnpackIsLayered()
+ {
+ return (Flags & 1) == 0;
+ }
#pragma warning restore CS0649
}
diff --git a/src/Ryujinx.Graphics.Gpu/GpuContext.cs b/src/Ryujinx.Graphics.Gpu/GpuContext.cs
index aa0084fdc9..048d32fb79 100644
--- a/src/Ryujinx.Graphics.Gpu/GpuContext.cs
+++ b/src/Ryujinx.Graphics.Gpu/GpuContext.cs
@@ -393,10 +393,17 @@ namespace Ryujinx.Graphics.Gpu
if (force || _pendingSync || (syncpoint && SyncpointActions.Count > 0))
{
- Renderer.CreateSync(SyncNumber, strict);
+ foreach (var action in SyncActions)
+ {
+ action.SyncPreAction(syncpoint);
+ }
- SyncActions.ForEach(action => action.SyncPreAction(syncpoint));
- SyncpointActions.ForEach(action => action.SyncPreAction(syncpoint));
+ foreach (var action in SyncpointActions)
+ {
+ action.SyncPreAction(syncpoint);
+ }
+
+ Renderer.CreateSync(SyncNumber, strict);
SyncNumber++;
diff --git a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs
index e67caea81f..3b6c407cc2 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -1,4 +1,5 @@
using Ryujinx.Common.Logging;
+using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture;
@@ -390,7 +391,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
_views.Remove(texture);
- Group.RemoveView(texture);
+ Group.RemoveView(_views, texture);
texture._viewStorage = texture;
@@ -805,7 +806,7 @@ namespace Ryujinx.Graphics.Gpu.Image
sliceDepth,
levels,
layers,
- out IMemoryOwner decoded))
+ out MemoryOwner decoded))
{
string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}";
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
index ba895c60a8..31abc21e8a 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
@@ -19,6 +19,11 @@ namespace Ryujinx.Graphics.Gpu.Image
///
public Format Format { get; }
+ ///
+ /// Shader texture host set index.
+ ///
+ public int Set { get; }
+
///
/// Shader texture host binding point.
///
@@ -54,15 +59,17 @@ namespace Ryujinx.Graphics.Gpu.Image
///
/// The shader sampler target type
/// Format of the image as declared on the shader
+ /// Shader texture host set index
/// The shader texture binding point
/// For array of textures, this indicates the length of the array. A value of one indicates it is not an array
/// Constant buffer slot where the texture handle is located
/// The shader texture handle (read index into the texture constant buffer)
/// The texture's usage flags, indicating how it is used in the shader
- public TextureBindingInfo(Target target, Format format, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags)
+ public TextureBindingInfo(Target target, Format format, int set, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags)
{
Target = target;
Format = format;
+ Set = set;
Binding = binding;
ArrayLength = arrayLength;
CbufSlot = cbufSlot;
@@ -74,6 +81,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Constructs the texture binding information structure.
///
/// The shader sampler target type
+ /// Shader texture host set index
/// The shader texture binding point
/// For array of textures, this indicates the length of the array. A value of one indicates it is not an array
/// Constant buffer slot where the texture handle is located
@@ -82,12 +90,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Indicates that the binding is for a sampler
public TextureBindingInfo(
Target target,
+ int set,
int binding,
int arrayLength,
int cbufSlot,
int handle,
TextureUsageFlags flags,
- bool isSamplerOnly) : this(target, 0, binding, arrayLength, cbufSlot, handle, flags)
+ bool isSamplerOnly) : this(target, 0, set, binding, arrayLength, cbufSlot, handle, flags)
{
IsSamplerOnly = isSamplerOnly;
}
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs
index 7e486e0a84..8b9243b1ec 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs
@@ -340,7 +340,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// True if any used entries of the pool might have been modified, false otherwise
public bool SamplerPoolModified()
{
- return SamplerPool.WasModified(ref _samplerPoolSequence);
+ return SamplerPool != null && SamplerPool.WasModified(ref _samplerPoolSequence);
}
}
@@ -516,12 +516,15 @@ namespace Ryujinx.Graphics.Gpu.Image
}
// Check if any of our cached samplers changed on the pool.
- foreach ((int samplerId, (Sampler sampler, SamplerDescriptor descriptor)) in SamplerIds)
+ if (SamplerPool != null)
{
- if (SamplerPool.GetCachedItem(samplerId) != sampler ||
- (sampler == null && SamplerPool.IsValidId(samplerId) && !SamplerPool.GetDescriptorRef(samplerId).Equals(descriptor)))
+ foreach ((int samplerId, (Sampler sampler, SamplerDescriptor descriptor)) in SamplerIds)
{
- return true;
+ if (SamplerPool.GetCachedItem(samplerId) != sampler ||
+ (sampler == null && SamplerPool.IsValidId(samplerId) && !SamplerPool.GetDescriptorRef(samplerId).Equals(descriptor)))
+ {
+ return true;
+ }
}
}
@@ -566,7 +569,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int stageIndex,
int textureBufferIndex,
SamplerIndex samplerIndex,
- TextureBindingInfo bindingInfo)
+ in TextureBindingInfo bindingInfo)
{
Update(texturePool, samplerPool, stage, stageIndex, textureBufferIndex, isImage: false, samplerIndex, bindingInfo);
}
@@ -579,7 +582,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Shader stage index where the array is used
/// Texture constant buffer index
/// Array binding information
- public void UpdateImageArray(TexturePool texturePool, ShaderStage stage, int stageIndex, int textureBufferIndex, TextureBindingInfo bindingInfo)
+ public void UpdateImageArray(TexturePool texturePool, ShaderStage stage, int stageIndex, int textureBufferIndex, in TextureBindingInfo bindingInfo)
{
Update(texturePool, null, stage, stageIndex, textureBufferIndex, isImage: true, SamplerIndex.ViaHeaderIndex, bindingInfo);
}
@@ -603,7 +606,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int textureBufferIndex,
bool isImage,
SamplerIndex samplerIndex,
- TextureBindingInfo bindingInfo)
+ in TextureBindingInfo bindingInfo)
{
if (IsDirectHandleType(bindingInfo.Handle))
{
@@ -623,7 +626,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Shader stage where the array is used
/// Whether the array is a image or texture array
/// Array binding information
- private void UpdateFromPool(TexturePool texturePool, SamplerPool samplerPool, ShaderStage stage, bool isImage, TextureBindingInfo bindingInfo)
+ private void UpdateFromPool(TexturePool texturePool, SamplerPool samplerPool, ShaderStage stage, bool isImage, in TextureBindingInfo bindingInfo)
{
CacheEntry entry = GetOrAddEntry(texturePool, samplerPool, bindingInfo, isImage, out bool isNewEntry);
@@ -638,11 +641,11 @@ namespace Ryujinx.Graphics.Gpu.Image
if (isImage)
{
- _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
+ SetImageArray(stage, bindingInfo, entry.ImageArray);
}
else
{
- _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
+ SetTextureArray(stage, bindingInfo, entry.TextureArray);
}
return;
@@ -708,11 +711,11 @@ namespace Ryujinx.Graphics.Gpu.Image
format = texture.Format;
}
- _channel.BufferManager.SetBufferTextureStorage(entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
+ _channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
}
else
{
- _channel.BufferManager.SetBufferTextureStorage(entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format);
+ _channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format);
}
}
else if (isImage)
@@ -737,14 +740,14 @@ namespace Ryujinx.Graphics.Gpu.Image
entry.ImageArray.SetFormats(0, formats);
entry.ImageArray.SetImages(0, textures);
- _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
+ SetImageArray(stage, bindingInfo, entry.ImageArray);
}
else
{
entry.TextureArray.SetSamplers(0, samplers);
entry.TextureArray.SetTextures(0, textures);
- _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
+ SetTextureArray(stage, bindingInfo, entry.TextureArray);
}
}
@@ -767,7 +770,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int textureBufferIndex,
bool isImage,
SamplerIndex samplerIndex,
- TextureBindingInfo bindingInfo)
+ in TextureBindingInfo bindingInfo)
{
(textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, textureBufferIndex);
@@ -800,11 +803,11 @@ namespace Ryujinx.Graphics.Gpu.Image
if (isImage)
{
- _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
+ SetImageArray(stage, bindingInfo, entry.ImageArray);
}
else
{
- _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
+ SetTextureArray(stage, bindingInfo, entry.TextureArray);
}
return;
@@ -829,11 +832,11 @@ namespace Ryujinx.Graphics.Gpu.Image
if (isImage)
{
- _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
+ SetImageArray(stage, bindingInfo, entry.ImageArray);
}
else
{
- _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
+ SetTextureArray(stage, bindingInfo, entry.TextureArray);
}
return;
@@ -899,13 +902,19 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
- Sampler sampler = samplerPool?.Get(samplerId);
-
entry.TextureIds[textureId] = (texture, descriptor);
- entry.SamplerIds[samplerId] = (sampler, samplerPool?.GetDescriptorRef(samplerId) ?? default);
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
- ISampler hostSampler = sampler?.GetHostSampler(texture);
+ ISampler hostSampler = null;
+
+ if (!isImage && bindingInfo.Target != Target.TextureBuffer)
+ {
+ Sampler sampler = samplerPool?.Get(samplerId);
+
+ entry.SamplerIds[samplerId] = (sampler, samplerPool?.GetDescriptorRef(samplerId) ?? default);
+
+ hostSampler = sampler?.GetHostSampler(texture);
+ }
Format format = bindingInfo.Format;
@@ -921,11 +930,11 @@ namespace Ryujinx.Graphics.Gpu.Image
format = texture.Format;
}
- _channel.BufferManager.SetBufferTextureStorage(entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
+ _channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
}
else
{
- _channel.BufferManager.SetBufferTextureStorage(entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format);
+ _channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format);
}
}
else if (isImage)
@@ -950,14 +959,50 @@ namespace Ryujinx.Graphics.Gpu.Image
entry.ImageArray.SetFormats(0, formats);
entry.ImageArray.SetImages(0, textures);
- _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
+ SetImageArray(stage, bindingInfo, entry.ImageArray);
}
else
{
entry.TextureArray.SetSamplers(0, samplers);
entry.TextureArray.SetTextures(0, textures);
- _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
+ SetTextureArray(stage, bindingInfo, entry.TextureArray);
+ }
+ }
+
+ ///
+ /// Updates a texture array binding on the host.
+ ///
+ /// Shader stage where the array is used
+ /// Array binding information
+ /// Texture array
+ private void SetTextureArray(ShaderStage stage, in TextureBindingInfo bindingInfo, ITextureArray array)
+ {
+ if (bindingInfo.Set >= _context.Capabilities.ExtraSetBaseIndex && _context.Capabilities.MaximumExtraSets != 0)
+ {
+ _context.Renderer.Pipeline.SetTextureArraySeparate(stage, bindingInfo.Set, array);
+ }
+ else
+ {
+ _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, array);
+ }
+ }
+
+ ///
+ /// Updates a image array binding on the host.
+ ///
+ /// Shader stage where the array is used
+ /// Array binding information
+ /// Image array
+ private void SetImageArray(ShaderStage stage, in TextureBindingInfo bindingInfo, IImageArray array)
+ {
+ if (bindingInfo.Set >= _context.Capabilities.ExtraSetBaseIndex && _context.Capabilities.MaximumExtraSets != 0)
+ {
+ _context.Renderer.Pipeline.SetImageArraySeparate(stage, bindingInfo.Set, array);
+ }
+ else
+ {
+ _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, array);
}
}
@@ -973,7 +1018,7 @@ namespace Ryujinx.Graphics.Gpu.Image
private CacheEntry GetOrAddEntry(
TexturePool texturePool,
SamplerPool samplerPool,
- TextureBindingInfo bindingInfo,
+ in TextureBindingInfo bindingInfo,
bool isImage,
out bool isNew)
{
@@ -1015,7 +1060,7 @@ namespace Ryujinx.Graphics.Gpu.Image
private CacheEntryFromBuffer GetOrAddEntry(
TexturePool texturePool,
SamplerPool samplerPool,
- TextureBindingInfo bindingInfo,
+ in TextureBindingInfo bindingInfo,
bool isImage,
ref BufferBounds textureBufferBounds,
out bool isNew)
@@ -1077,6 +1122,15 @@ namespace Ryujinx.Graphics.Gpu.Image
nextNode = nextNode.Next;
_cacheFromBuffer.Remove(toRemove.Value.Key);
_lruCache.Remove(toRemove);
+
+ if (toRemove.Value.Key.IsImage)
+ {
+ toRemove.Value.ImageArray.Dispose();
+ }
+ else
+ {
+ toRemove.Value.TextureArray.Dispose();
+ }
}
}
@@ -1088,11 +1142,20 @@ namespace Ryujinx.Graphics.Gpu.Image
{
List keysToRemove = null;
- foreach (CacheEntryFromPoolKey key in _cacheFromPool.Keys)
+ foreach ((CacheEntryFromPoolKey key, CacheEntry entry) in _cacheFromPool)
{
if (key.MatchesPool(pool))
{
(keysToRemove ??= new()).Add(key);
+
+ if (key.IsImage)
+ {
+ entry.ImageArray.Dispose();
+ }
+ else
+ {
+ entry.TextureArray.Dispose();
+ }
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
index 5743c89c0d..b6fa842e35 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
@@ -8,6 +8,7 @@ using Ryujinx.Graphics.Texture;
using Ryujinx.Memory.Range;
using System;
using System.Collections.Generic;
+using System.Threading;
namespace Ryujinx.Graphics.Gpu.Image
{
@@ -39,6 +40,8 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly MultiRangeList _textures;
private readonly HashSet _partiallyMappedTextures;
+ private readonly ReaderWriterLockSlim _texturesLock;
+
private Texture[] _textureOverlaps;
private OverlapInfo[] _overlapInfo;
@@ -57,6 +60,8 @@ namespace Ryujinx.Graphics.Gpu.Image
_textures = new MultiRangeList();
_partiallyMappedTextures = new HashSet();
+ _texturesLock = new ReaderWriterLockSlim();
+
_textureOverlaps = new Texture[OverlapsBufferInitialCapacity];
_overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity];
@@ -75,10 +80,16 @@ namespace Ryujinx.Graphics.Gpu.Image
MultiRange unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size);
- lock (_textures)
+ _texturesLock.EnterReadLock();
+
+ try
{
overlapCount = _textures.FindOverlaps(unmapped, ref overlaps);
}
+ finally
+ {
+ _texturesLock.ExitReadLock();
+ }
if (overlapCount > 0)
{
@@ -217,7 +228,18 @@ namespace Ryujinx.Graphics.Gpu.Image
public bool UpdateMapping(Texture texture, MultiRange range)
{
// There cannot be an existing texture compatible with this mapping in the texture cache already.
- int overlapCount = _textures.FindOverlaps(range, ref _textureOverlaps);
+ int overlapCount;
+
+ _texturesLock.EnterReadLock();
+
+ try
+ {
+ overlapCount = _textures.FindOverlaps(range, ref _textureOverlaps);
+ }
+ finally
+ {
+ _texturesLock.ExitReadLock();
+ }
for (int i = 0; i < overlapCount; i++)
{
@@ -231,11 +253,20 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
- _textures.Remove(texture);
+ _texturesLock.EnterWriteLock();
- texture.ReplaceRange(range);
+ try
+ {
+ _textures.Remove(texture);
- _textures.Add(texture);
+ texture.ReplaceRange(range);
+
+ _textures.Add(texture);
+ }
+ finally
+ {
+ _texturesLock.ExitWriteLock();
+ }
return true;
}
@@ -437,13 +468,11 @@ namespace Ryujinx.Graphics.Gpu.Image
int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY();
int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ();
+ layered &= size.UnpackIsLayered();
+
Target target;
- if (dsState.MemoryLayout.UnpackIsTarget3D())
- {
- target = Target.Texture3D;
- }
- else if ((samplesInX | samplesInY) != 1)
+ if ((samplesInX | samplesInY) != 1)
{
target = size.Depth > 1 && layered
? Target.Texture2DMultisampleArray
@@ -611,11 +640,17 @@ namespace Ryujinx.Graphics.Gpu.Image
int sameAddressOverlapsCount;
- lock (_textures)
+ _texturesLock.EnterReadLock();
+
+ try
{
// Try to find a perfect texture match, with the same address and parameters.
sameAddressOverlapsCount = _textures.FindOverlaps(address, ref _textureOverlaps);
}
+ finally
+ {
+ _texturesLock.ExitReadLock();
+ }
Texture texture = null;
@@ -698,10 +733,16 @@ namespace Ryujinx.Graphics.Gpu.Image
if (info.Target != Target.TextureBuffer)
{
- lock (_textures)
+ _texturesLock.EnterReadLock();
+
+ try
{
overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps);
}
+ finally
+ {
+ _texturesLock.ExitReadLock();
+ }
}
if (_overlapInfo.Length != _textureOverlaps.Length)
@@ -1025,10 +1066,16 @@ namespace Ryujinx.Graphics.Gpu.Image
_cache.Add(texture);
}
- lock (_textures)
+ _texturesLock.EnterWriteLock();
+
+ try
{
_textures.Add(texture);
}
+ finally
+ {
+ _texturesLock.ExitWriteLock();
+ }
if (partiallyMapped)
{
@@ -1091,7 +1138,19 @@ namespace Ryujinx.Graphics.Gpu.Image
return null;
}
- int addressMatches = _textures.FindOverlaps(address, ref _textureOverlaps);
+ int addressMatches;
+
+ _texturesLock.EnterReadLock();
+
+ try
+ {
+ addressMatches = _textures.FindOverlaps(address, ref _textureOverlaps);
+ }
+ finally
+ {
+ _texturesLock.ExitReadLock();
+ }
+
Texture textureMatch = null;
for (int i = 0; i < addressMatches; i++)
@@ -1232,10 +1291,16 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The texture to be removed
public void RemoveTextureFromCache(Texture texture)
{
- lock (_textures)
+ _texturesLock.EnterWriteLock();
+
+ try
{
_textures.Remove(texture);
}
+ finally
+ {
+ _texturesLock.ExitWriteLock();
+ }
lock (_partiallyMappedTextures)
{
@@ -1324,13 +1389,19 @@ namespace Ryujinx.Graphics.Gpu.Image
///
public void Dispose()
{
- lock (_textures)
+ _texturesLock.EnterReadLock();
+
+ try
{
foreach (Texture texture in _textures)
{
texture.Dispose();
}
}
+ finally
+ {
+ _texturesLock.ExitReadLock();
+ }
}
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
index de9c47c976..06ca2c5997 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
@@ -88,9 +88,9 @@ namespace Ryujinx.Graphics.Gpu.Image
private MultiRange TextureRange => Storage.Range;
///
- /// The views list from the storage texture.
+ /// The views array from the storage texture.
///
- private List _views;
+ private Texture[] _views;
private TextureGroupHandle[] _handles;
private bool[] _loadNeeded;
@@ -645,7 +645,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
else
{
- _flushBuffer = _context.Renderer.CreateBuffer((int)Storage.Size, BufferAccess.FlushPersistent);
+ _flushBuffer = _context.Renderer.CreateBuffer((int)Storage.Size, BufferAccess.HostMemory);
_flushBufferImported = false;
}
@@ -1074,7 +1074,7 @@ namespace Ryujinx.Graphics.Gpu.Image
public void UpdateViews(List views, Texture texture)
{
// This is saved to calculate overlapping views for each handle.
- _views = views;
+ _views = views.ToArray();
bool layerViews = _hasLayerViews;
bool mipViews = _hasMipViews;
@@ -1136,9 +1136,13 @@ namespace Ryujinx.Graphics.Gpu.Image
///
/// Removes a view from the group, removing it from all overlap lists.
///
+ /// The views list of the storage texture
/// View to remove from the group
- public void RemoveView(Texture view)
+ public void RemoveView(List views, Texture view)
{
+ // This is saved to calculate overlapping views for each handle.
+ _views = views.ToArray();
+
int offset = FindOffset(view);
foreach (TextureGroupHandle handle in _handles)
@@ -1605,9 +1609,11 @@ namespace Ryujinx.Graphics.Gpu.Image
Storage.SignalModifiedDirty();
- if (_views != null)
+ Texture[] views = _views;
+
+ if (views != null)
{
- foreach (Texture texture in _views)
+ foreach (Texture texture in views)
{
texture.SignalModifiedDirty();
}
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs
index 0af6b7ca83..860922d592 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs
@@ -121,7 +121,7 @@ namespace Ryujinx.Graphics.Gpu.Image
public TextureGroupHandle(TextureGroup group,
int offset,
ulong size,
- List views,
+ IEnumerable views,
int firstLayer,
int firstLevel,
int baseSlice,
@@ -201,8 +201,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Calculate a list of which views overlap this handle.
///
/// The parent texture group, used to find a view's base CPU VA offset
- /// The list of views to search for overlaps
- public void RecalculateOverlaps(TextureGroup group, List views)
+ /// The views to search for overlaps
+ public void RecalculateOverlaps(TextureGroup group, IEnumerable views)
{
// Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
lock (Overlaps)
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
index a4035577d3..4ed0a93c17 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
@@ -6,6 +6,7 @@ using Ryujinx.Memory.Range;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Numerics;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Image
@@ -490,6 +491,8 @@ namespace Ryujinx.Graphics.Gpu.Image
levels = (maxLod - minLod) + 1;
}
+ levels = ClampLevels(target, width, height, depthOrLayers, levels);
+
SwizzleComponent swizzleR = descriptor.UnpackSwizzleR().Convert();
SwizzleComponent swizzleG = descriptor.UnpackSwizzleG().Convert();
SwizzleComponent swizzleB = descriptor.UnpackSwizzleB().Convert();
@@ -540,6 +543,34 @@ namespace Ryujinx.Graphics.Gpu.Image
swizzleA);
}
+ ///
+ /// Clamps the amount of mipmap levels to the maximum allowed for the given texture dimensions.
+ ///
+ /// Number of texture dimensions (1D, 2D, 3D, Cube, etc)
+ /// Width of the texture
+ /// Height of the texture, ignored for 1D textures
+ /// Depth of the texture for 3D textures, otherwise ignored
+ /// Original amount of mipmap levels
+ /// Clamped mipmap levels
+ private static int ClampLevels(Target target, int width, int height, int depthOrLayers, int levels)
+ {
+ int maxSize = width;
+
+ if (target != Target.Texture1D &&
+ target != Target.Texture1DArray)
+ {
+ maxSize = Math.Max(maxSize, height);
+ }
+
+ if (target == Target.Texture3D)
+ {
+ maxSize = Math.Max(maxSize, depthOrLayers);
+ }
+
+ int maxLevels = BitOperations.Log2((uint)maxSize) + 1;
+ return Math.Min(levels, maxLevels);
+ }
+
///
/// Gets the texture depth-stencil mode, based on the swizzle components of each color channel.
/// The depth-stencil mode is determined based on how the driver sets those parameters.
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
index d293060b5a..e060e0b4f8 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
@@ -10,6 +10,8 @@ using System.Threading;
namespace Ryujinx.Graphics.Gpu.Memory
{
+ delegate void BufferFlushAction(ulong address, ulong size, ulong syncNumber);
+
///
/// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
///
@@ -23,7 +25,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Host buffer handle.
///
- public BufferHandle Handle { get; }
+ public BufferHandle Handle { get; private set; }
///
/// Start address of the buffer in guest memory.
@@ -60,6 +62,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
private BufferModifiedRangeList _modifiedRanges = null;
+ ///
+ /// A structure that is used to flush buffer data back to a host mapped buffer for cached readback.
+ /// Only used if the buffer data is explicitly owned by device local memory.
+ ///
+ private BufferPreFlush _preFlush = null;
+
+ ///
+ /// Usage tracking state that determines what type of backing the buffer should use.
+ ///
+ public BufferBackingState BackingState;
+
private readonly MultiRegionHandle _memoryTrackingGranular;
private readonly RegionHandle _memoryTracking;
@@ -87,6 +100,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Physical memory where the buffer is mapped
/// Start address of the buffer
/// Size of the buffer in bytes
+ /// The type of usage that created the buffer
/// Indicates if the buffer can be used in a sparse buffer mapping
/// Buffers which this buffer contains, and will inherit tracking handles from
public Buffer(
@@ -94,6 +108,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
PhysicalMemory physicalMemory,
ulong address,
ulong size,
+ BufferStage stage,
bool sparseCompatible,
IEnumerable baseBuffers = null)
{
@@ -103,9 +118,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
Size = size;
SparseCompatible = sparseCompatible;
- BufferAccess access = sparseCompatible ? BufferAccess.SparseCompatible : BufferAccess.Default;
+ BackingState = new BufferBackingState(_context, this, stage, baseBuffers);
- Handle = context.Renderer.CreateBuffer((int)size, access, baseBuffers?.MaxBy(x => x.Size).Handle ?? BufferHandle.Null);
+ BufferAccess access = BackingState.SwitchAccess(this);
+
+ Handle = context.Renderer.CreateBuffer((int)size, access);
_useGranular = size > GranularBufferThreshold;
@@ -161,6 +178,29 @@ namespace Ryujinx.Graphics.Gpu.Memory
_virtualDependenciesLock = new ReaderWriterLockSlim();
}
+ ///
+ /// Recreates the backing buffer based on the desired access type
+ /// reported by the backing state struct.
+ ///
+ private void ChangeBacking()
+ {
+ BufferAccess access = BackingState.SwitchAccess(this);
+
+ BufferHandle newHandle = _context.Renderer.CreateBuffer((int)Size, access);
+
+ _context.Renderer.Pipeline.CopyBuffer(Handle, newHandle, 0, 0, (int)Size);
+
+ _modifiedRanges?.SelfMigration();
+
+ // If swtiching from device local to host mapped, pre-flushing data no longer makes sense.
+ // This is set to null and disposed when the migration fully completes.
+ _preFlush = null;
+
+ Handle = newHandle;
+
+ _physicalMemory.BufferCache.BufferBackingChanged(this);
+ }
+
///
/// Gets a sub-range from the buffer, from a start address til a page boundary after the given size.
///
@@ -246,6 +286,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
else
{
+ BackingState.RecordSet();
_context.Renderer.SetBufferData(Handle, 0, _physicalMemory.GetSpan(Address, (int)Size));
CopyToDependantVirtualBuffers();
}
@@ -283,15 +324,35 @@ namespace Ryujinx.Graphics.Gpu.Memory
_modifiedRanges ??= new BufferModifiedRangeList(_context, this, Flush);
}
+ ///
+ /// Checks if a backing change is deemed necessary from the given usage.
+ /// If it is, queues a backing change to happen on the next sync action.
+ ///
+ /// Buffer stage that can change backing type
+ private void TryQueueBackingChange(BufferStage stage)
+ {
+ if (BackingState.ShouldChangeBacking(stage))
+ {
+ if (!_syncActionRegistered)
+ {
+ _context.RegisterSyncAction(this);
+ _syncActionRegistered = true;
+ }
+ }
+ }
+
///
/// Signal that the given region of the buffer has been modified.
///
/// The start address of the modified region
/// The size of the modified region
- public void SignalModified(ulong address, ulong size)
+ /// Buffer stage that triggered the modification
+ public void SignalModified(ulong address, ulong size, BufferStage stage)
{
EnsureRangeList();
+ TryQueueBackingChange(stage);
+
_modifiedRanges.SignalModified(address, size);
if (!_syncActionRegistered)
@@ -311,6 +372,37 @@ namespace Ryujinx.Graphics.Gpu.Memory
_modifiedRanges?.Clear(address, size);
}
+ ///
+ /// Action to be performed immediately before sync is created.
+ /// This will copy any buffer ranges designated for pre-flushing.
+ ///
+ /// True if the action is a guest syncpoint
+ public void SyncPreAction(bool syncpoint)
+ {
+ if (_referenceCount == 0)
+ {
+ return;
+ }
+
+ if (BackingState.ShouldChangeBacking())
+ {
+ ChangeBacking();
+ }
+
+ if (BackingState.IsDeviceLocal)
+ {
+ _preFlush ??= new BufferPreFlush(_context, this, FlushImpl);
+
+ if (_preFlush.ShouldCopy)
+ {
+ _modifiedRanges?.GetRangesAtSync(Address, Size, _context.SyncNumber, (address, size) =>
+ {
+ _preFlush.CopyModified(address, size);
+ });
+ }
+ }
+ }
+
///
/// Action to be performed when a syncpoint is reached after modification.
/// This will register read/write tracking to flush the buffer from GPU when its memory is used.
@@ -466,6 +558,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Size of the modified region
private void LoadRegion(ulong mAddress, ulong mSize)
{
+ BackingState.RecordSet();
+
int offset = (int)(mAddress - Address);
_context.Renderer.SetBufferData(Handle, offset, _physicalMemory.GetSpan(mAddress, (int)mSize));
@@ -539,18 +633,84 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Flushes a range of the buffer.
/// This writes the range data back into guest memory.
///
+ /// Buffer handle to flush data from
/// Start address of the range
/// Size in bytes of the range
- public void Flush(ulong address, ulong size)
+ private void FlushImpl(BufferHandle handle, ulong address, ulong size)
{
int offset = (int)(address - Address);
- using PinnedSpan data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
+ using PinnedSpan data = _context.Renderer.GetBufferData(handle, offset, (int)size);
// TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
_physicalMemory.WriteUntracked(address, CopyFromDependantVirtualBuffers(data.Get(), address, size));
}
+ ///
+ /// Flushes a range of the buffer.
+ /// This writes the range data back into guest memory.
+ ///
+ /// Start address of the range
+ /// Size in bytes of the range
+ private void FlushImpl(ulong address, ulong size)
+ {
+ FlushImpl(Handle, address, size);
+ }
+
+ ///
+ /// Flushes a range of the buffer from the most optimal source.
+ /// This writes the range data back into guest memory.
+ ///
+ /// Start address of the range
+ /// Size in bytes of the range
+ /// Sync number waited for before flushing the data
+ public void Flush(ulong address, ulong size, ulong syncNumber)
+ {
+ BackingState.RecordFlush();
+
+ BufferPreFlush preFlush = _preFlush;
+
+ if (preFlush != null)
+ {
+ preFlush.FlushWithAction(address, size, syncNumber);
+ }
+ else
+ {
+ FlushImpl(address, size);
+ }
+ }
+ ///
+ /// Gets an action that disposes the backing buffer using its current handle.
+ /// Useful for deleting an old copy of the buffer after the handle changes.
+ ///
+ /// An action that flushes data from the specified range, using the buffer handle at the time the method is generated
+ public Action GetSnapshotDisposeAction()
+ {
+ BufferHandle handle = Handle;
+ BufferPreFlush preFlush = _preFlush;
+
+ return () =>
+ {
+ _context.Renderer.DeleteBuffer(handle);
+ preFlush?.Dispose();
+ };
+ }
+
+ ///
+ /// Gets an action that flushes a range of the buffer using its current handle.
+ /// Useful for flushing data from old copies of the buffer after the handle changes.
+ ///
+ /// An action that flushes data from the specified range, using the buffer handle at the time the method is generated
+ public BufferFlushAction GetSnapshotFlushAction()
+ {
+ BufferHandle handle = Handle;
+
+ return (ulong address, ulong size, ulong _) =>
+ {
+ FlushImpl(handle, address, size);
+ };
+ }
+
///
/// Align a given address and size region to page boundaries.
///
@@ -857,6 +1017,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
_modifiedRanges?.Clear();
_context.Renderer.DeleteBuffer(Handle);
+ _preFlush?.Dispose();
+ _preFlush = null;
UnmappedSequence++;
}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs
new file mode 100644
index 0000000000..3f65131e6b
--- /dev/null
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs
@@ -0,0 +1,294 @@
+using Ryujinx.Graphics.GAL;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ ///
+ /// Type of backing memory.
+ /// In ascending order of priority when merging multiple buffer backing states.
+ ///
+ internal enum BufferBackingType
+ {
+ HostMemory,
+ DeviceMemory,
+ DeviceMemoryWithFlush
+ }
+
+ ///
+ /// Keeps track of buffer usage to decide what memory heap that buffer memory is placed on.
+ /// Dedicated GPUs prefer certain types of resources to be device local,
+ /// and if we need data to be read back, we might prefer that they're in host memory.
+ ///
+ /// The measurements recorded here compare to a set of heruristics (thresholds and conditions)
+ /// that appear to produce good performance in most software.
+ ///
+ internal struct BufferBackingState
+ {
+ private const int DeviceLocalSizeThreshold = 256 * 1024; // 256kb
+
+ private const int SetCountThreshold = 100;
+ private const int WriteCountThreshold = 50;
+ private const int FlushCountThreshold = 5;
+ private const int DeviceLocalForceExpiry = 100;
+
+ public readonly bool IsDeviceLocal => _activeType != BufferBackingType.HostMemory;
+
+ private readonly SystemMemoryType _systemMemoryType;
+ private BufferBackingType _activeType;
+ private BufferBackingType _desiredType;
+
+ private bool _canSwap;
+
+ private int _setCount;
+ private int _writeCount;
+ private int _flushCount;
+ private int _flushTemp;
+ private int _lastFlushWrite;
+ private int _deviceLocalForceCount;
+
+ private readonly int _size;
+
+ ///
+ /// Initialize the buffer backing state for a given parent buffer.
+ ///
+ /// GPU context
+ /// Parent buffer
+ /// Initial buffer stage
+ /// Buffers to inherit state from
+ public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, IEnumerable baseBuffers = null)
+ {
+ _size = (int)parent.Size;
+ _systemMemoryType = context.Capabilities.MemoryType;
+
+ // Backend managed is always auto, unified memory is always host.
+ _desiredType = BufferBackingType.HostMemory;
+ _canSwap = _systemMemoryType != SystemMemoryType.BackendManaged && _systemMemoryType != SystemMemoryType.UnifiedMemory;
+
+ if (_canSwap)
+ {
+ // Might want to start certain buffers as being device local,
+ // and the usage might also lock those buffers into being device local.
+
+ BufferStage storageFlags = stage & BufferStage.StorageMask;
+
+ if (parent.Size > DeviceLocalSizeThreshold && baseBuffers == null)
+ {
+ _desiredType = BufferBackingType.DeviceMemory;
+ }
+
+ if (storageFlags != 0)
+ {
+ // Storage buffer bindings may require special treatment.
+
+ var rawStage = stage & BufferStage.StageMask;
+
+ if (rawStage == BufferStage.Fragment)
+ {
+ // Fragment read should start device local.
+
+ _desiredType = BufferBackingType.DeviceMemory;
+
+ if (storageFlags != BufferStage.StorageRead)
+ {
+ // Fragment write should stay device local until the use doesn't happen anymore.
+
+ _deviceLocalForceCount = DeviceLocalForceExpiry;
+ }
+ }
+
+ // TODO: Might be nice to force atomic access to be device local for any stage.
+ }
+
+ if (baseBuffers != null)
+ {
+ foreach (Buffer buffer in baseBuffers)
+ {
+ CombineState(buffer.BackingState);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Combine buffer backing types, selecting the one with highest priority.
+ ///
+ /// First buffer backing type
+ /// Second buffer backing type
+ /// Combined buffer backing type
+ private static BufferBackingType CombineTypes(BufferBackingType left, BufferBackingType right)
+ {
+ return (BufferBackingType)Math.Max((int)left, (int)right);
+ }
+
+ ///
+ /// Combine the state from the given buffer backing state with this one,
+ /// so that the state isn't lost when migrating buffers.
+ ///
+ /// Buffer state to combine into this state
+ private void CombineState(BufferBackingState oldState)
+ {
+ _setCount += oldState._setCount;
+ _writeCount += oldState._writeCount;
+ _flushCount += oldState._flushCount;
+ _flushTemp += oldState._flushTemp;
+ _lastFlushWrite = -1;
+ _deviceLocalForceCount = Math.Max(_deviceLocalForceCount, oldState._deviceLocalForceCount);
+
+ _canSwap &= oldState._canSwap;
+
+ _desiredType = CombineTypes(_desiredType, oldState._desiredType);
+ }
+
+ ///
+ /// Get the buffer access for the desired backing type, and record that type as now being active.
+ ///
+ /// Parent buffer
+ /// Buffer access
+ public BufferAccess SwitchAccess(Buffer parent)
+ {
+ BufferAccess access = parent.SparseCompatible ? BufferAccess.SparseCompatible : BufferAccess.Default;
+
+ bool isBackendManaged = _systemMemoryType == SystemMemoryType.BackendManaged;
+
+ if (!isBackendManaged)
+ {
+ switch (_desiredType)
+ {
+ case BufferBackingType.HostMemory:
+ access |= BufferAccess.HostMemory;
+ break;
+ case BufferBackingType.DeviceMemory:
+ access |= BufferAccess.DeviceMemory;
+ break;
+ case BufferBackingType.DeviceMemoryWithFlush:
+ access |= BufferAccess.DeviceMemoryMapped;
+ break;
+ }
+ }
+
+ _activeType = _desiredType;
+
+ return access;
+ }
+
+ ///
+ /// Record when data has been uploaded to the buffer.
+ ///
+ public void RecordSet()
+ {
+ _setCount++;
+
+ ConsiderUseCounts();
+ }
+
+ ///
+ /// Record when data has been flushed from the buffer.
+ ///
+ public void RecordFlush()
+ {
+ if (_lastFlushWrite != _writeCount)
+ {
+ // If it's on the same page as the last flush, ignore it.
+ _lastFlushWrite = _writeCount;
+ _flushCount++;
+ }
+ }
+
+ ///
+ /// Determine if the buffer backing should be changed.
+ ///
+ /// True if the desired backing type is different from the current type
+ public readonly bool ShouldChangeBacking()
+ {
+ return _desiredType != _activeType;
+ }
+
+ ///
+ /// Determine if the buffer backing should be changed, considering a new use with the given buffer stage.
+ ///
+ /// Buffer stage for the use
+ /// True if the desired backing type is different from the current type
+ public bool ShouldChangeBacking(BufferStage stage)
+ {
+ if (!_canSwap)
+ {
+ return false;
+ }
+
+ BufferStage storageFlags = stage & BufferStage.StorageMask;
+
+ if (storageFlags != 0)
+ {
+ if (storageFlags != BufferStage.StorageRead)
+ {
+ // Storage write.
+ _writeCount++;
+
+ var rawStage = stage & BufferStage.StageMask;
+
+ if (rawStage == BufferStage.Fragment)
+ {
+ // Switch to device memory, swap back only if this use disappears.
+
+ _desiredType = CombineTypes(_desiredType, BufferBackingType.DeviceMemory);
+ _deviceLocalForceCount = DeviceLocalForceExpiry;
+
+ // TODO: Might be nice to force atomic access to be device local for any stage.
+ }
+ }
+
+ ConsiderUseCounts();
+ }
+
+ return _desiredType != _activeType;
+ }
+
+ ///
+ /// Evaluate the current counts to determine what the buffer's desired backing type is.
+ /// This method depends on heuristics devised by testing a variety of software.
+ ///
+ private void ConsiderUseCounts()
+ {
+ if (_canSwap)
+ {
+ if (_writeCount >= WriteCountThreshold || _setCount >= SetCountThreshold || _flushCount >= FlushCountThreshold)
+ {
+ if (_deviceLocalForceCount > 0 && --_deviceLocalForceCount != 0)
+ {
+ // Some buffer usage demanded that the buffer stay device local.
+ // The desired type was selected when this counter was set.
+ }
+ else if (_flushCount > 0 || _flushTemp-- > 0)
+ {
+ // Buffers that flush should ideally be mapped in host address space for easy copies.
+ // If the buffer is large it will do better on GPU memory, as there will be more writes than data flushes (typically individual pages).
+ // If it is small, then it's likely most of the buffer will be flushed so we want it on host memory, as access is cached.
+ _desiredType = _size > DeviceLocalSizeThreshold ? BufferBackingType.DeviceMemoryWithFlush : BufferBackingType.HostMemory;
+ }
+ else if (_writeCount >= WriteCountThreshold)
+ {
+ // Buffers that are written often should ideally be in the device local heap. (Storage buffers)
+ _desiredType = BufferBackingType.DeviceMemory;
+ }
+ else if (_setCount > SetCountThreshold)
+ {
+ // Buffers that have their data set often should ideally be host mapped. (Constant buffers)
+ _desiredType = BufferBackingType.HostMemory;
+ }
+
+ // It's harder for a buffer that is flushed to revert to another type of mapping.
+ if (_flushCount > 0)
+ {
+ _flushTemp = 1000;
+ }
+
+ _lastFlushWrite = -1;
+ _flushCount = 0;
+ _writeCount = 0;
+ _setCount = 0;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs
index c6284780d3..66d2cdb626 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs
@@ -107,8 +107,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// GPU memory manager where the buffer is mapped
/// Start GPU virtual address of the buffer
/// Size in bytes of the buffer
+ /// The type of usage that created the buffer
/// Contiguous physical range of the buffer, after address translation
- public MultiRange TranslateAndCreateBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size)
+ public MultiRange TranslateAndCreateBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage)
{
if (gpuVa == 0)
{
@@ -119,7 +120,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (address != MemoryManager.PteUnmapped)
{
- CreateBuffer(address, size);
+ CreateBuffer(address, size, stage);
}
return new MultiRange(address, size);
@@ -132,8 +133,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// GPU memory manager where the buffer is mapped
/// Start GPU virtual address of the buffer
/// Size in bytes of the buffer
+ /// The type of usage that created the buffer
/// Physical ranges of the buffer, after address translation
- public MultiRange TranslateAndCreateMultiBuffers(MemoryManager memoryManager, ulong gpuVa, ulong size)
+ public MultiRange TranslateAndCreateMultiBuffers(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage)
{
if (gpuVa == 0)
{
@@ -149,7 +151,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
return range;
}
- CreateBuffer(range);
+ CreateBuffer(range, stage);
return range;
}
@@ -161,8 +163,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// GPU memory manager where the buffer is mapped
/// Start GPU virtual address of the buffer
/// Size in bytes of the buffer
+ /// The type of usage that created the buffer
/// Physical ranges of the buffer, after address translation
- public MultiRange TranslateAndCreateMultiBuffersPhysicalOnly(MemoryManager memoryManager, ulong gpuVa, ulong size)
+ public MultiRange TranslateAndCreateMultiBuffersPhysicalOnly(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage)
{
if (gpuVa == 0)
{
@@ -186,11 +189,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
if (range.Count > 1)
{
- CreateBuffer(subRange.Address, subRange.Size, SparseBufferAlignmentSize);
+ CreateBuffer(subRange.Address, subRange.Size, stage, SparseBufferAlignmentSize);
}
else
{
- CreateBuffer(subRange.Address, subRange.Size);
+ CreateBuffer(subRange.Address, subRange.Size, stage);
}
}
}
@@ -203,11 +206,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// This can be used to ensure the existance of a buffer.
///
/// Physical ranges of memory where the buffer data is located
- public void CreateBuffer(MultiRange range)
+ /// The type of usage that created the buffer
+ public void CreateBuffer(MultiRange range, BufferStage stage)
{
if (range.Count > 1)
{
- CreateMultiRangeBuffer(range);
+ CreateMultiRangeBuffer(range, stage);
}
else
{
@@ -215,7 +219,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (subRange.Address != MemoryManager.PteUnmapped)
{
- CreateBuffer(subRange.Address, subRange.Size);
+ CreateBuffer(subRange.Address, subRange.Size, stage);
}
}
}
@@ -226,7 +230,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Address of the buffer in memory
/// Size of the buffer in bytes
- public void CreateBuffer(ulong address, ulong size)
+ /// The type of usage that created the buffer
+ public void CreateBuffer(ulong address, ulong size, BufferStage stage)
{
ulong endAddress = address + size;
@@ -239,7 +244,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
alignedEndAddress += BufferAlignmentSize;
}
- CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress);
+ CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, stage);
}
///
@@ -248,8 +253,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Address of the buffer in memory
/// Size of the buffer in bytes
+ /// The type of usage that created the buffer
/// Alignment of the start address of the buffer in bytes
- public void CreateBuffer(ulong address, ulong size, ulong alignment)
+ public void CreateBuffer(ulong address, ulong size, BufferStage stage, ulong alignment)
{
ulong alignmentMask = alignment - 1;
ulong pageAlignmentMask = BufferAlignmentMask;
@@ -264,7 +270,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
alignedEndAddress += pageAlignmentMask;
}
- CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, alignment);
+ CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, stage, alignment);
}
///
@@ -272,7 +278,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// if it does not exist yet.
///
/// Physical ranges of memory
- private void CreateMultiRangeBuffer(MultiRange range)
+ /// The type of usage that created the buffer
+ private void CreateMultiRangeBuffer(MultiRange range, BufferStage stage)
{
// Ensure all non-contiguous buffer we might use are sparse aligned.
for (int i = 0; i < range.Count; i++)
@@ -281,7 +288,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (subRange.Address != MemoryManager.PteUnmapped)
{
- CreateBuffer(subRange.Address, subRange.Size, SparseBufferAlignmentSize);
+ CreateBuffer(subRange.Address, subRange.Size, stage, SparseBufferAlignmentSize);
}
}
@@ -431,9 +438,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
result.EndGpuAddress < gpuVa + size ||
result.UnmappedSequence != result.Buffer.UnmappedSequence)
{
- MultiRange range = TranslateAndCreateBuffer(memoryManager, gpuVa, size);
+ MultiRange range = TranslateAndCreateBuffer(memoryManager, gpuVa, size, BufferStage.Internal);
ulong address = range.GetSubRange(0).Address;
- result = new BufferCacheEntry(address, gpuVa, GetBuffer(address, size));
+ result = new BufferCacheEntry(address, gpuVa, GetBuffer(address, size, BufferStage.Internal));
_dirtyCache[gpuVa] = result;
}
@@ -466,9 +473,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
result.EndGpuAddress < alignedEndGpuVa ||
result.UnmappedSequence != result.Buffer.UnmappedSequence)
{
- MultiRange range = TranslateAndCreateBuffer(memoryManager, alignedGpuVa, size);
+ MultiRange range = TranslateAndCreateBuffer(memoryManager, alignedGpuVa, size, BufferStage.None);
ulong address = range.GetSubRange(0).Address;
- result = new BufferCacheEntry(address, alignedGpuVa, GetBuffer(address, size));
+ result = new BufferCacheEntry(address, alignedGpuVa, GetBuffer(address, size, BufferStage.None));
_modifiedCache[alignedGpuVa] = result;
}
@@ -485,7 +492,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Address of the buffer in guest memory
/// Size in bytes of the buffer
- private void CreateBufferAligned(ulong address, ulong size)
+ /// The type of usage that created the buffer
+ private void CreateBufferAligned(ulong address, ulong size, BufferStage stage)
{
Buffer[] overlaps = _bufferOverlaps;
int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps);
@@ -546,13 +554,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong newSize = endAddress - address;
- CreateBufferAligned(address, newSize, anySparseCompatible, overlaps, overlapsCount);
+ CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlaps, overlapsCount);
}
}
else
{
// No overlap, just create a new buffer.
- Buffer buffer = new(_context, _physicalMemory, address, size, sparseCompatible: false);
+ Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false);
lock (_buffers)
{
@@ -570,8 +578,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Address of the buffer in guest memory
/// Size in bytes of the buffer
+ /// The type of usage that created the buffer
/// Alignment of the start address of the buffer
- private void CreateBufferAligned(ulong address, ulong size, ulong alignment)
+ private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment)
{
Buffer[] overlaps = _bufferOverlaps;
int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps);
@@ -624,13 +633,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong newSize = endAddress - address;
- CreateBufferAligned(address, newSize, sparseAligned, overlaps, overlapsCount);
+ CreateBufferAligned(address, newSize, stage, sparseAligned, overlaps, overlapsCount);
}
}
else
{
// No overlap, just create a new buffer.
- Buffer buffer = new(_context, _physicalMemory, address, size, sparseAligned);
+ Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseAligned);
lock (_buffers)
{
@@ -648,12 +657,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Address of the buffer in guest memory
/// Size in bytes of the buffer
+ /// The type of usage that created the buffer
/// Indicates if the buffer can be used in a sparse buffer mapping
/// Buffers overlapping the range
/// Total of overlaps
- private void CreateBufferAligned(ulong address, ulong size, bool sparseCompatible, Buffer[] overlaps, int overlapsCount)
+ private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, Buffer[] overlaps, int overlapsCount)
{
- Buffer newBuffer = new Buffer(_context, _physicalMemory, address, size, sparseCompatible, overlaps.Take(overlapsCount));
+ Buffer newBuffer = new Buffer(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps.Take(overlapsCount));
lock (_buffers)
{
@@ -704,7 +714,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int index = 0; index < overlapCount; index++)
{
- CreateMultiRangeBuffer(overlaps[index].Range);
+ CreateMultiRangeBuffer(overlaps[index].Range, BufferStage.None);
}
}
@@ -731,8 +741,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Size in bytes of the copy
public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size)
{
- MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size);
- MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size);
+ MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size, BufferStage.Copy);
+ MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size, BufferStage.Copy);
if (srcRange.Count == 1 && dstRange.Count == 1)
{
@@ -788,8 +798,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Size in bytes of the copy
private void CopyBufferSingleRange(MemoryManager memoryManager, ulong srcAddress, ulong dstAddress, ulong size)
{
- Buffer srcBuffer = GetBuffer(srcAddress, size);
- Buffer dstBuffer = GetBuffer(dstAddress, size);
+ Buffer srcBuffer = GetBuffer(srcAddress, size, BufferStage.Copy);
+ Buffer dstBuffer = GetBuffer(dstAddress, size, BufferStage.Copy);
int srcOffset = (int)(srcAddress - srcBuffer.Address);
int dstOffset = (int)(dstAddress - dstBuffer.Address);
@@ -803,7 +813,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (srcBuffer.IsModified(srcAddress, size))
{
- dstBuffer.SignalModified(dstAddress, size);
+ dstBuffer.SignalModified(dstAddress, size, BufferStage.Copy);
}
else
{
@@ -828,12 +838,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Value to be written into the buffer
public void ClearBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, uint value)
{
- MultiRange range = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, gpuVa, size);
+ MultiRange range = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, gpuVa, size, BufferStage.Copy);
for (int index = 0; index < range.Count; index++)
{
MemoryRange subRange = range.GetSubRange(index);
- Buffer buffer = GetBuffer(subRange.Address, subRange.Size);
+ Buffer buffer = GetBuffer(subRange.Address, subRange.Size, BufferStage.Copy);
int offset = (int)(subRange.Address - buffer.Address);
@@ -849,18 +859,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Gets a buffer sub-range starting at a given memory address, aligned to the next page boundary.
///
/// Physical regions of memory where the buffer is mapped
+ /// Buffer stage that triggered the access
/// Whether the buffer will be written to by this use
/// The buffer sub-range starting at the given memory address
- public BufferRange GetBufferRangeAligned(MultiRange range, bool write = false)
+ public BufferRange GetBufferRangeAligned(MultiRange range, BufferStage stage, bool write = false)
{
if (range.Count > 1)
{
- return GetBuffer(range, write).GetRange(range);
+ return GetBuffer(range, stage, write).GetRange(range);
}
else
{
MemoryRange subRange = range.GetSubRange(0);
- return GetBuffer(subRange.Address, subRange.Size, write).GetRangeAligned(subRange.Address, subRange.Size, write);
+ return GetBuffer(subRange.Address, subRange.Size, stage, write).GetRangeAligned(subRange.Address, subRange.Size, write);
}
}
@@ -868,18 +879,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Gets a buffer sub-range for a given memory range.
///
/// Physical regions of memory where the buffer is mapped
+ /// Buffer stage that triggered the access
/// Whether the buffer will be written to by this use
/// The buffer sub-range for the given range
- public BufferRange GetBufferRange(MultiRange range, bool write = false)
+ public BufferRange GetBufferRange(MultiRange range, BufferStage stage, bool write = false)
{
if (range.Count > 1)
{
- return GetBuffer(range, write).GetRange(range);
+ return GetBuffer(range, stage, write).GetRange(range);
}
else
{
MemoryRange subRange = range.GetSubRange(0);
- return GetBuffer(subRange.Address, subRange.Size, write).GetRange(subRange.Address, subRange.Size, write);
+ return GetBuffer(subRange.Address, subRange.Size, stage, write).GetRange(subRange.Address, subRange.Size, write);
}
}
@@ -888,9 +900,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// A buffer overlapping with the specified range is assumed to already exist on the cache.
///
/// Physical regions of memory where the buffer is mapped
+ /// Buffer stage that triggered the access
/// Whether the buffer will be written to by this use
/// The buffer where the range is fully contained
- private MultiRangeBuffer GetBuffer(MultiRange range, bool write = false)
+ private MultiRangeBuffer GetBuffer(MultiRange range, BufferStage stage, bool write = false)
{
for (int i = 0; i < range.Count; i++)
{
@@ -902,7 +915,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (write)
{
- subBuffer.SignalModified(subRange.Address, subRange.Size);
+ subBuffer.SignalModified(subRange.Address, subRange.Size, stage);
}
}
@@ -935,9 +948,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Start address of the memory range
/// Size in bytes of the memory range
+ /// Buffer stage that triggered the access
/// Whether the buffer will be written to by this use
/// The buffer where the range is fully contained
- private Buffer GetBuffer(ulong address, ulong size, bool write = false)
+ private Buffer GetBuffer(ulong address, ulong size, BufferStage stage, bool write = false)
{
Buffer buffer;
@@ -950,7 +964,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (write)
{
- buffer.SignalModified(address, size);
+ buffer.SignalModified(address, size, stage);
}
}
else
@@ -1004,6 +1018,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
+ ///
+ /// Signal that the given buffer's handle has changed,
+ /// forcing rebind and any overlapping multi-range buffers to be recreated.
+ ///
+ /// The buffer that has changed handle
+ public void BufferBackingChanged(Buffer buffer)
+ {
+ NotifyBuffersModified?.Invoke();
+
+ RecreateMultiRangeBuffers(buffer.Address, buffer.Size);
+ }
+
///
/// Prune any invalid entries from a quick access dictionary.
///
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
index 8f2201e0aa..26d9501c68 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
@@ -156,7 +156,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Type of each index buffer element
public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type)
{
- MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size);
+ MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.IndexBuffer);
_indexBuffer.Range = range;
_indexBuffer.Type = type;
@@ -186,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Vertex divisor of the buffer, for instanced draws
public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int divisor)
{
- MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size);
+ MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.VertexBuffer);
_vertexBuffers[index].Range = range;
_vertexBuffers[index].Stride = stride;
@@ -213,7 +213,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Size in bytes of the transform feedback buffer
public void SetTransformFeedbackBuffer(int index, ulong gpuVa, ulong size)
{
- MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size);
+ MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStage.TransformFeedback);
_transformFeedbackBuffers[index] = new BufferBounds(range);
_transformFeedbackBuffersDirty = true;
@@ -260,7 +260,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
gpuVa = BitUtils.AlignDown(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
- MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size);
+ MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.ComputeStorage(flags));
_cpStorageBuffers.SetBounds(index, range, flags);
}
@@ -284,7 +284,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
gpuVa = BitUtils.AlignDown(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
- MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size);
+ MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.GraphicsStorage(stage, flags));
if (!buffers.Buffers[index].Range.Equals(range))
{
@@ -303,7 +303,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Size in bytes of the storage buffer
public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size)
{
- MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size);
+ MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.Compute);
_cpUniformBuffers.SetBounds(index, range);
}
@@ -318,7 +318,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Size in bytes of the storage buffer
public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size)
{
- MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size);
+ MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStageUtils.FromShaderStage(stage));
_gpUniformBuffers[stage].SetBounds(index, range);
_gpUniformBuffersDirty = true;
@@ -502,7 +502,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
foreach (var binding in _bufferTextures)
{
var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
- var range = bufferCache.GetBufferRange(binding.Range, isStore);
+ var range = bufferCache.GetBufferRange(binding.Range, BufferStageUtils.TextureBuffer(binding.Stage, binding.BindingInfo.Flags), isStore);
binding.Texture.SetStorage(range);
// The texture must be rebound to use the new storage if it was updated.
@@ -526,7 +526,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
foreach (var binding in _bufferTextureArrays)
{
- var range = bufferCache.GetBufferRange(binding.Range);
+ var range = bufferCache.GetBufferRange(binding.Range, BufferStage.None);
binding.Texture.SetStorage(range);
textureArray[0] = binding.Texture;
@@ -536,7 +536,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
foreach (var binding in _bufferImageArrays)
{
var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
- var range = bufferCache.GetBufferRange(binding.Range, isStore);
+ var range = bufferCache.GetBufferRange(binding.Range, BufferStage.None, isStore);
binding.Texture.SetStorage(range);
textureArray[0] = binding.Texture;
@@ -565,7 +565,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (!_indexBuffer.Range.IsUnmapped)
{
- BufferRange buffer = bufferCache.GetBufferRange(_indexBuffer.Range);
+ BufferRange buffer = bufferCache.GetBufferRange(_indexBuffer.Range, BufferStage.IndexBuffer);
_context.Renderer.Pipeline.SetIndexBuffer(buffer, _indexBuffer.Type);
}
@@ -597,7 +597,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
continue;
}
- BufferRange buffer = bufferCache.GetBufferRange(vb.Range);
+ BufferRange buffer = bufferCache.GetBufferRange(vb.Range, BufferStage.VertexBuffer);
vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor);
}
@@ -637,7 +637,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
continue;
}
- tfbs[index] = bufferCache.GetBufferRange(tfb.Range, write: true);
+ tfbs[index] = bufferCache.GetBufferRange(tfb.Range, BufferStage.TransformFeedback, write: true);
}
_context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs);
@@ -684,7 +684,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
_context.SupportBufferUpdater.SetTfeOffset(index, tfeOffset);
- buffers[index] = new BufferAssignment(index, bufferCache.GetBufferRange(range, write: true));
+ buffers[index] = new BufferAssignment(index, bufferCache.GetBufferRange(range, BufferStage.TransformFeedback, write: true));
}
}
@@ -751,6 +751,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
{
ref var buffers = ref bindings[(int)stage - 1];
+ BufferStage bufferStage = BufferStageUtils.FromShaderStage(stage);
for (int index = 0; index < buffers.Count; index++)
{
@@ -762,8 +763,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
var range = isStorage
- ? bufferCache.GetBufferRangeAligned(bounds.Range, isWrite)
- : bufferCache.GetBufferRange(bounds.Range);
+ ? bufferCache.GetBufferRangeAligned(bounds.Range, bufferStage | BufferStageUtils.FromUsage(bounds.Flags), isWrite)
+ : bufferCache.GetBufferRange(bounds.Range, bufferStage);
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
}
@@ -799,8 +800,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
var range = isStorage
- ? bufferCache.GetBufferRangeAligned(bounds.Range, isWrite)
- : bufferCache.GetBufferRange(bounds.Range);
+ ? bufferCache.GetBufferRangeAligned(bounds.Range, BufferStageUtils.ComputeStorage(bounds.Flags), isWrite)
+ : bufferCache.GetBufferRange(bounds.Range, BufferStage.Compute);
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
}
@@ -875,7 +876,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
Format format,
bool isImage)
{
- _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range);
+ _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
_bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, format, isImage));
}
@@ -883,6 +884,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Sets the buffer storage of a buffer texture array element. This will be bound when the buffer manager commits bindings.
///
+ /// Shader stage accessing the texture
/// Texture array where the element will be inserted
/// Buffer texture
/// Physical ranges of memory where the buffer texture data is located
@@ -890,6 +892,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Index of the binding on the array
/// Format of the buffer texture
public void SetBufferTextureStorage(
+ ShaderStage stage,
ITextureArray array,
ITexture texture,
MultiRange range,
@@ -897,7 +900,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
int index,
Format format)
{
- _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range);
+ _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
_bufferTextureArrays.Add(new BufferTextureArrayBinding(array, texture, range, bindingInfo, index, format));
}
@@ -905,6 +908,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Sets the buffer storage of a buffer image array element. This will be bound when the buffer manager commits bindings.
///
+ /// Shader stage accessing the texture
/// Image array where the element will be inserted
/// Buffer texture
/// Physical ranges of memory where the buffer texture data is located
@@ -912,6 +916,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Index of the binding on the array
/// Format of the buffer texture
public void SetBufferTextureStorage(
+ ShaderStage stage,
IImageArray array,
ITexture texture,
MultiRange range,
@@ -919,7 +924,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
int index,
Format format)
{
- _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range);
+ _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
_bufferImageArrays.Add(new BufferTextureArrayBinding(array, texture, range, bindingInfo, index, format));
}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs
index 0a52680316..ce99853188 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs
@@ -1,37 +1,21 @@
using System;
+using System.Threading;
namespace Ryujinx.Graphics.Gpu.Memory
{
///
- /// A record of when buffer data was copied from one buffer to another, along with the SyncNumber when the migration will be complete.
- /// Keeps the source buffer alive for data flushes until the migration is complete.
+ /// A record of when buffer data was copied from multiple buffers to one migration target,
+ /// along with the SyncNumber when the migration will be complete.
+ /// Keeps the source buffers alive for data flushes until the migration is complete.
+ /// All spans cover the full range of the "destination" buffer.
///
internal class BufferMigration : IDisposable
{
///
- /// The offset for the migrated region.
+ /// Ranges from source buffers that were copied as part of this migration.
+ /// Ordered by increasing base address.
///
- private readonly ulong _offset;
-
- ///
- /// The size for the migrated region.
- ///
- private readonly ulong _size;
-
- ///
- /// The buffer that was migrated from.
- ///
- private readonly Buffer _buffer;
-
- ///
- /// The source range action, to be called on overlap with an unreached sync number.
- ///
- private readonly Action _sourceRangeAction;
-
- ///
- /// The source range list.
- ///
- private readonly BufferModifiedRangeList _source;
+ public BufferMigrationSpan[] Spans { get; private set; }
///
/// The destination range list. This range list must be updated when flushing the source.
@@ -43,55 +27,193 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
public readonly ulong SyncNumber;
+ ///
+ /// Number of active users there are traversing this migration's spans.
+ ///
+ private int _refCount;
+
+ ///
+ /// Create a new buffer migration.
+ ///
+ /// Source spans for the migration
+ /// Destination buffer range list
+ /// Sync number where this migration will be complete
+ public BufferMigration(BufferMigrationSpan[] spans, BufferModifiedRangeList destination, ulong syncNumber)
+ {
+ Spans = spans;
+ Destination = destination;
+ SyncNumber = syncNumber;
+ }
+
+ ///
+ /// Add a span to the migration. Allocates a new array with the target size, and replaces it.
+ ///
+ ///
+ /// The base address for the span is assumed to be higher than all other spans in the migration,
+ /// to keep the span array ordered.
+ ///
+ public void AddSpanToEnd(BufferMigrationSpan span)
+ {
+ BufferMigrationSpan[] oldSpans = Spans;
+
+ BufferMigrationSpan[] newSpans = new BufferMigrationSpan[oldSpans.Length + 1];
+
+ oldSpans.CopyTo(newSpans, 0);
+
+ newSpans[oldSpans.Length] = span;
+
+ Spans = newSpans;
+ }
+
+ ///
+ /// Performs the given range action, or one from a migration that overlaps and has not synced yet.
+ ///
+ /// The offset to pass to the action
+ /// The size to pass to the action
+ /// The sync number that has been reached
+ /// The action to perform
+ public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferFlushAction rangeAction)
+ {
+ long syncDiff = (long)(syncNumber - SyncNumber);
+
+ if (syncDiff >= 0)
+ {
+ // The migration has completed. Run the parent action.
+ rangeAction(offset, size, syncNumber);
+ }
+ else
+ {
+ Interlocked.Increment(ref _refCount);
+
+ ulong prevAddress = offset;
+ ulong endAddress = offset + size;
+
+ foreach (BufferMigrationSpan span in Spans)
+ {
+ if (!span.Overlaps(offset, size))
+ {
+ continue;
+ }
+
+ if (span.Address > prevAddress)
+ {
+ // There's a gap between this span and the last (or the start address). Flush the range using the parent action.
+
+ rangeAction(prevAddress, span.Address - prevAddress, syncNumber);
+ }
+
+ span.RangeActionWithMigration(offset, size, syncNumber);
+
+ prevAddress = span.Address + span.Size;
+ }
+
+ if (endAddress > prevAddress)
+ {
+ // There's a gap at the end of the range with no migration. Flush the range using the parent action.
+ rangeAction(prevAddress, endAddress - prevAddress, syncNumber);
+ }
+
+ Interlocked.Decrement(ref _refCount);
+ }
+ }
+
+ ///
+ /// Dispose the buffer migration. This removes the reference from the destination range list,
+ /// and runs all the dispose buffers for the migration spans. (typically disposes the source buffer)
+ ///
+ public void Dispose()
+ {
+ while (Volatile.Read(ref _refCount) > 0)
+ {
+ // Coming into this method, the sync for the migration will be met, so nothing can increment the ref count.
+ // However, an existing traversal of the spans for data flush could still be in progress.
+ // Spin if this is ever the case, so they don't get disposed before the operation is complete.
+ }
+
+ Destination.RemoveMigration(this);
+
+ foreach (BufferMigrationSpan span in Spans)
+ {
+ span.Dispose();
+ }
+ }
+ }
+
+ ///
+ /// A record of when buffer data was copied from one buffer to another, for a specific range in a source buffer.
+ /// Keeps the source buffer alive for data flushes until the migration is complete.
+ ///
+ internal readonly struct BufferMigrationSpan : IDisposable
+ {
+ ///
+ /// The offset for the migrated region.
+ ///
+ public readonly ulong Address;
+
+ ///
+ /// The size for the migrated region.
+ ///
+ public readonly ulong Size;
+
+ ///
+ /// The action to perform when the migration isn't needed anymore.
+ ///
+ private readonly Action _disposeAction;
+
+ ///
+ /// The source range action, to be called on overlap with an unreached sync number.
+ ///
+ private readonly BufferFlushAction _sourceRangeAction;
+
+ ///
+ /// Optional migration for the source data. Can chain together if many migrations happen in a short time.
+ /// If this is null, then _sourceRangeAction will always provide up to date data.
+ ///
+ private readonly BufferMigration _source;
+
///
/// Creates a record for a buffer migration.
///
/// The source buffer for this migration
+ /// The action to perform when the migration isn't needed anymore
/// The flush action for the source buffer
- /// The modified range list for the source buffer
- /// The modified range list for the destination buffer
- /// The sync number for when the migration is complete
- public BufferMigration(
+ /// Pending migration for the source buffer
+ public BufferMigrationSpan(
Buffer buffer,
- Action sourceRangeAction,
- BufferModifiedRangeList source,
- BufferModifiedRangeList dest,
- ulong syncNumber)
+ Action disposeAction,
+ BufferFlushAction sourceRangeAction,
+ BufferMigration source)
{
- _offset = buffer.Address;
- _size = buffer.Size;
- _buffer = buffer;
+ Address = buffer.Address;
+ Size = buffer.Size;
+ _disposeAction = disposeAction;
_sourceRangeAction = sourceRangeAction;
_source = source;
- Destination = dest;
- SyncNumber = syncNumber;
}
+ ///
+ /// Creates a record for a buffer migration, using the default buffer dispose action.
+ ///
+ /// The source buffer for this migration
+ /// The flush action for the source buffer
+ /// Pending migration for the source buffer
+ public BufferMigrationSpan(
+ Buffer buffer,
+ BufferFlushAction sourceRangeAction,
+ BufferMigration source) : this(buffer, buffer.DecrementReferenceCount, sourceRangeAction, source) { }
+
///
/// Determine if the given range overlaps this migration, and has not been completed yet.
///
/// Start offset
/// Range size
- /// The sync number that was waited on
/// True if overlapping and in progress, false otherwise
- public bool Overlaps(ulong offset, ulong size, ulong syncNumber)
+ public bool Overlaps(ulong offset, ulong size)
{
ulong end = offset + size;
- ulong destEnd = _offset + _size;
- long syncDiff = (long)(syncNumber - SyncNumber); // syncNumber is less if the copy has not completed.
+ ulong destEnd = Address + Size;
- return !(end <= _offset || offset >= destEnd) && syncDiff < 0;
- }
-
- ///
- /// Determine if the given range matches this migration.
- ///
- /// Start offset
- /// Range size
- /// True if the range exactly matches, false otherwise
- public bool FullyMatches(ulong offset, ulong size)
- {
- return _offset == offset && _size == size;
+ return !(end <= Address || offset >= destEnd);
}
///
@@ -100,26 +222,30 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Start offset
/// Range size
/// Current sync number
- /// The modified range list that originally owned this range
- public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferModifiedRangeList parent)
+ public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber)
{
ulong end = offset + size;
- end = Math.Min(_offset + _size, end);
- offset = Math.Max(_offset, offset);
+ end = Math.Min(Address + Size, end);
+ offset = Math.Max(Address, offset);
size = end - offset;
- _source.RangeActionWithMigration(offset, size, syncNumber, parent, _sourceRangeAction);
+ if (_source != null)
+ {
+ _source.RangeActionWithMigration(offset, size, syncNumber, _sourceRangeAction);
+ }
+ else
+ {
+ _sourceRangeAction(offset, size, syncNumber);
+ }
}
///
- /// Removes this reference to the range list, potentially allowing for the source buffer to be disposed.
+ /// Removes this migration span, potentially allowing for the source buffer to be disposed.
///
public void Dispose()
{
- Destination.RemoveMigration(this);
-
- _buffer.DecrementReferenceCount();
+ _disposeAction();
}
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs
index 6ada8a4b25..d330de638a 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs
@@ -1,7 +1,6 @@
using Ryujinx.Common.Pools;
using Ryujinx.Memory.Range;
using System;
-using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Graphics.Gpu.Memory
@@ -72,10 +71,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly GpuContext _context;
private readonly Buffer _parent;
- private readonly Action _flushAction;
+ private readonly BufferFlushAction _flushAction;
- private List _sources;
- private BufferMigration _migrationTarget;
+ private BufferMigration _source;
+ private BufferModifiedRangeList _migrationTarget;
private readonly object _lock = new();
@@ -99,7 +98,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// GPU context that the buffer range list belongs to
/// The parent buffer that owns this range list
/// The flush action for the parent buffer
- public BufferModifiedRangeList(GpuContext context, Buffer parent, Action flushAction) : base(BackingInitialSize)
+ public BufferModifiedRangeList(GpuContext context, Buffer parent, BufferFlushAction flushAction) : base(BackingInitialSize)
{
_context = context;
_parent = parent;
@@ -199,6 +198,36 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
+ ///
+ /// Gets modified ranges within the specified region, and then fires the given action for each range individually.
+ ///
+ /// Start address to query
+ /// Size to query
+ /// Sync number required for a range to be signalled
+ /// The action to call for each modified range
+ public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action rangeAction)
+ {
+ int count = 0;
+
+ ref var overlaps = ref ThreadStaticArray.Get();
+
+ // Range list must be consistent for this operation.
+ lock (_lock)
+ {
+ count = FindOverlapsNonOverlapping(address, size, ref overlaps);
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ BufferModifiedRange overlap = overlaps[i];
+
+ if (overlap.SyncNumber == syncNumber)
+ {
+ rangeAction(overlap.Address, overlap.Size);
+ }
+ }
+ }
+
///
/// Gets modified ranges within the specified region, and then fires the given action for each range individually.
///
@@ -245,41 +274,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// The offset to pass to the action
/// The size to pass to the action
/// The sync number that has been reached
- /// The modified range list that originally owned this range
/// The action to perform
- public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferModifiedRangeList parent, Action rangeAction)
+ public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferFlushAction rangeAction)
{
- bool firstSource = true;
-
- if (parent != this)
+ if (_source != null)
{
- lock (_lock)
- {
- if (_sources != null)
- {
- foreach (BufferMigration source in _sources)
- {
- if (source.Overlaps(offset, size, syncNumber))
- {
- if (firstSource && !source.FullyMatches(offset, size))
- {
- // Perform this buffer's action first. The migrations will run after.
- rangeAction(offset, size);
- }
-
- source.RangeActionWithMigration(offset, size, syncNumber, parent);
-
- firstSource = false;
- }
- }
- }
- }
+ _source.RangeActionWithMigration(offset, size, syncNumber, rangeAction);
}
-
- if (firstSource)
+ else
{
- // No overlapping migrations, or they are not meant for this range, flush the data using the given action.
- rangeAction(offset, size);
+ rangeAction(offset, size, syncNumber);
}
}
@@ -319,7 +323,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ClearPart(overlap, clampAddress, clampEnd);
- RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, overlap.Parent, _flushAction);
+ RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, _flushAction);
}
}
@@ -329,7 +333,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
// There is a migration target to call instead. This can't be changed after set so accessing it outside the lock is fine.
- _migrationTarget.Destination.RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
+ _migrationTarget.RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
}
///
@@ -367,7 +371,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (rangeCount == -1)
{
- _migrationTarget.Destination.WaitForAndFlushRanges(address, size);
+ _migrationTarget.WaitForAndFlushRanges(address, size);
return;
}
@@ -407,6 +411,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Inherit ranges from another modified range list.
///
+ ///
+ /// Assumes that ranges will be inherited in address ascending order.
+ ///
/// The range list to inherit from
/// The action to call for each modified range
public void InheritRanges(BufferModifiedRangeList ranges, Action registerRangeAction)
@@ -415,18 +422,31 @@ namespace Ryujinx.Graphics.Gpu.Memory
lock (ranges._lock)
{
- BufferMigration migration = new(ranges._parent, ranges._flushAction, ranges, this, _context.SyncNumber);
-
- ranges._parent.IncrementReferenceCount();
- ranges._migrationTarget = migration;
-
- _context.RegisterBufferMigration(migration);
-
inheritRanges = ranges.ToArray();
lock (_lock)
{
- (_sources ??= new List()).Add(migration);
+ // Copy over the migration from the previous range list
+
+ BufferMigration oldMigration = ranges._source;
+
+ BufferMigrationSpan span = new BufferMigrationSpan(ranges._parent, ranges._flushAction, oldMigration);
+ ranges._parent.IncrementReferenceCount();
+
+ if (_source == null)
+ {
+ // Create a new migration.
+ _source = new BufferMigration(new BufferMigrationSpan[] { span }, this, _context.SyncNumber);
+
+ _context.RegisterBufferMigration(_source);
+ }
+ else
+ {
+ // Extend the migration
+ _source.AddSpanToEnd(span);
+ }
+
+ ranges._migrationTarget = this;
foreach (BufferModifiedRange range in inheritRanges)
{
@@ -445,6 +465,27 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
+ ///
+ /// Register a migration from previous buffer storage. This migration is from a snapshot of the buffer's
+ /// current handle to its handle in the future, and is assumed to be complete when the sync action completes.
+ /// When the migration completes, the handle is disposed.
+ ///
+ public void SelfMigration()
+ {
+ lock (_lock)
+ {
+ BufferMigrationSpan span = new(_parent, _parent.GetSnapshotDisposeAction(), _parent.GetSnapshotFlushAction(), _source);
+ BufferMigration migration = new(new BufferMigrationSpan[] { span }, this, _context.SyncNumber);
+
+ // Migration target is used to redirect flush actions to the latest range list,
+ // so we don't need to set it here. (this range list is still the latest)
+
+ _context.RegisterBufferMigration(migration);
+
+ _source = migration;
+ }
+ }
+
///
/// Removes a source buffer migration, indicating its copy has completed.
///
@@ -453,7 +494,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
lock (_lock)
{
- _sources.Remove(migration);
+ if (_source == migration)
+ {
+ _source = null;
+ }
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferPreFlush.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferPreFlush.cs
new file mode 100644
index 0000000000..d58b9ea66d
--- /dev/null
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferPreFlush.cs
@@ -0,0 +1,295 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ ///
+ /// Manages flushing ranges from buffers in advance for easy access, if they are flushed often.
+ /// Typically, from device local memory to a host mapped target for cached access.
+ ///
+ internal class BufferPreFlush : IDisposable
+ {
+ private const ulong PageSize = MemoryManager.PageSize;
+
+ ///
+ /// Threshold for the number of copies without a flush required to disable preflush on a page.
+ ///
+ private const int DeactivateCopyThreshold = 200;
+
+ ///
+ /// Value that indicates whether a page has been flushed or copied before.
+ ///
+ private enum PreFlushState
+ {
+ None,
+ HasFlushed,
+ HasCopied
+ }
+
+ ///
+ /// Flush state for each page of the buffer.
+ /// Controls whether data should be copied to the flush buffer, what sync is expected
+ /// and unflushed copy counting for stopping copies that are no longer needed.
+ ///
+ private struct PreFlushPage
+ {
+ public PreFlushState State;
+ public ulong FirstActivatedSync;
+ public ulong LastCopiedSync;
+ public int CopyCount;
+ }
+
+ ///
+ /// True if there are ranges that should copy to the flush buffer, false otherwise.
+ ///
+ public bool ShouldCopy { get; private set; }
+
+ private readonly GpuContext _context;
+ private readonly Buffer _buffer;
+ private readonly PreFlushPage[] _pages;
+ private readonly ulong _address;
+ private readonly ulong _size;
+ private readonly ulong _misalignment;
+ private readonly Action _flushAction;
+
+ private BufferHandle _flushBuffer;
+
+ public BufferPreFlush(GpuContext context, Buffer parent, Action flushAction)
+ {
+ _context = context;
+ _buffer = parent;
+ _address = parent.Address;
+ _size = parent.Size;
+ _pages = new PreFlushPage[BitUtils.DivRoundUp(_size, PageSize)];
+ _misalignment = _address & (PageSize - 1);
+
+ _flushAction = flushAction;
+ }
+
+ ///
+ /// Ensure that the flush buffer exists.
+ ///
+ private void EnsureFlushBuffer()
+ {
+ if (_flushBuffer == BufferHandle.Null)
+ {
+ _flushBuffer = _context.Renderer.CreateBuffer((int)_size, BufferAccess.HostMemory);
+ }
+ }
+
+ ///
+ /// Gets a page range from an address and size byte range.
+ ///
+ /// Range address
+ /// Range size
+ /// A page index and count
+ private (int index, int count) GetPageRange(ulong address, ulong size)
+ {
+ ulong offset = address - _address;
+ ulong endOffset = offset + size;
+
+ int basePage = (int)(offset / PageSize);
+ int endPage = (int)((endOffset - 1) / PageSize);
+
+ return (basePage, 1 + endPage - basePage);
+ }
+
+ ///
+ /// Gets an offset and size range in the parent buffer from a page index and count.
+ ///
+ /// Range start page
+ /// Range page count
+ /// Offset and size range
+ private (int offset, int size) GetOffset(int startPage, int count)
+ {
+ int offset = (int)((ulong)startPage * PageSize - _misalignment);
+ int endOffset = (int)((ulong)(startPage + count) * PageSize - _misalignment);
+
+ offset = Math.Max(0, offset);
+ endOffset = Math.Min((int)_size, endOffset);
+
+ return (offset, endOffset - offset);
+ }
+
+ ///
+ /// Copy a range of pages from the parent buffer into the flush buffer.
+ ///
+ /// Range start page
+ /// Range page count
+ private void CopyPageRange(int startPage, int count)
+ {
+ (int offset, int size) = GetOffset(startPage, count);
+
+ EnsureFlushBuffer();
+
+ _context.Renderer.Pipeline.CopyBuffer(_buffer.Handle, _flushBuffer, offset, offset, size);
+ }
+
+ ///
+ /// Copy a modified range into the flush buffer if it's marked as flushed.
+ /// Any pages the range overlaps are copied, and copies aren't repeated in the same sync number.
+ ///
+ /// Range address
+ /// Range size
+ public void CopyModified(ulong address, ulong size)
+ {
+ (int baseIndex, int count) = GetPageRange(address, size);
+ ulong syncNumber = _context.SyncNumber;
+
+ int startPage = -1;
+
+ for (int i = 0; i < count; i++)
+ {
+ int pageIndex = baseIndex + i;
+ ref PreFlushPage page = ref _pages[pageIndex];
+
+ if (page.State > PreFlushState.None)
+ {
+ // Perform the copy, and update the state of each page.
+ if (startPage == -1)
+ {
+ startPage = pageIndex;
+ }
+
+ if (page.State != PreFlushState.HasCopied)
+ {
+ page.FirstActivatedSync = syncNumber;
+ page.State = PreFlushState.HasCopied;
+ }
+ else if (page.CopyCount++ >= DeactivateCopyThreshold)
+ {
+ page.CopyCount = 0;
+ page.State = PreFlushState.None;
+ }
+
+ if (page.LastCopiedSync != syncNumber)
+ {
+ page.LastCopiedSync = syncNumber;
+ }
+ }
+ else if (startPage != -1)
+ {
+ CopyPageRange(startPage, pageIndex - startPage);
+
+ startPage = -1;
+ }
+ }
+
+ if (startPage != -1)
+ {
+ CopyPageRange(startPage, (baseIndex + count) - startPage);
+ }
+ }
+
+ ///
+ /// Flush the given page range back into guest memory, optionally using data from the flush buffer.
+ /// The actual flushed range is an intersection of the page range and the address range.
+ ///
+ /// Address range start
+ /// Address range size
+ /// Page range start
+ /// Page range count
+ /// True if the data should come from the flush buffer
+ private void FlushPageRange(ulong address, ulong size, int startPage, int count, bool preFlush)
+ {
+ (int pageOffset, int pageSize) = GetOffset(startPage, count);
+
+ int offset = (int)(address - _address);
+ int end = offset + (int)size;
+
+ offset = Math.Max(offset, pageOffset);
+ end = Math.Min(end, pageOffset + pageSize);
+
+ if (end >= offset)
+ {
+ BufferHandle handle = preFlush ? _flushBuffer : _buffer.Handle;
+ _flushAction(handle, _address + (ulong)offset, (ulong)(end - offset));
+ }
+ }
+
+ ///
+ /// Flush the given address range back into guest memory, optionally using data from the flush buffer.
+ /// When a copy has been performed on or before the waited sync number, the data can come from the flush buffer.
+ /// Otherwise, it flushes the parent buffer directly.
+ ///
+ /// Range address
+ /// Range size
+ /// Sync number that has been waited for
+ public void FlushWithAction(ulong address, ulong size, ulong syncNumber)
+ {
+ // Copy the parts of the range that have pre-flush copies that have been completed.
+ // Run the flush action for ranges that don't have pre-flush copies.
+
+ // If a range doesn't have a pre-flush copy, consider adding one.
+
+ (int baseIndex, int count) = GetPageRange(address, size);
+
+ bool rangePreFlushed = false;
+ int startPage = -1;
+
+ for (int i = 0; i < count; i++)
+ {
+ int pageIndex = baseIndex + i;
+ ref PreFlushPage page = ref _pages[pageIndex];
+
+ bool flushPage = false;
+ page.CopyCount = 0;
+
+ if (page.State == PreFlushState.HasCopied)
+ {
+ if (syncNumber >= page.FirstActivatedSync)
+ {
+ // After the range is first activated, its data will always be copied to the preflush buffer on each sync.
+ flushPage = true;
+ }
+ }
+ else if (page.State == PreFlushState.None)
+ {
+ page.State = PreFlushState.HasFlushed;
+ ShouldCopy = true;
+ }
+
+ if (flushPage)
+ {
+ if (!rangePreFlushed || startPage == -1)
+ {
+ if (startPage != -1)
+ {
+ FlushPageRange(address, size, startPage, pageIndex - startPage, false);
+ }
+
+ rangePreFlushed = true;
+ startPage = pageIndex;
+ }
+ }
+ else if (rangePreFlushed || startPage == -1)
+ {
+ if (startPage != -1)
+ {
+ FlushPageRange(address, size, startPage, pageIndex - startPage, true);
+ }
+
+ rangePreFlushed = false;
+ startPage = pageIndex;
+ }
+ }
+
+ if (startPage != -1)
+ {
+ FlushPageRange(address, size, startPage, (baseIndex + count) - startPage, rangePreFlushed);
+ }
+ }
+
+ ///
+ /// Dispose the flush buffer, if present.
+ ///
+ public void Dispose()
+ {
+ if (_flushBuffer != BufferHandle.Null)
+ {
+ _context.Renderer.DeleteBuffer(_flushBuffer);
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferStage.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferStage.cs
new file mode 100644
index 0000000000..d56abda284
--- /dev/null
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferStage.cs
@@ -0,0 +1,99 @@
+using Ryujinx.Graphics.Shader;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ ///
+ /// Pipeline stages that can modify buffer data, as well as flags indicating storage usage.
+ /// Must match ShaderStage for the shader stages, though anything after that can be in any order.
+ ///
+ internal enum BufferStage : byte
+ {
+ Compute,
+ Vertex,
+ TessellationControl,
+ TessellationEvaluation,
+ Geometry,
+ Fragment,
+
+ Indirect,
+ VertexBuffer,
+ IndexBuffer,
+ Copy,
+ TransformFeedback,
+ Internal,
+ None,
+
+ StageMask = 0x3f,
+ StorageMask = 0xc0,
+
+ StorageRead = 0x40,
+ StorageWrite = 0x80,
+
+#pragma warning disable CA1069 // Enums values should not be duplicated
+ StorageAtomic = 0xc0
+#pragma warning restore CA1069 // Enums values should not be duplicated
+ }
+
+ ///
+ /// Utility methods to convert shader stages and binding flags into buffer stages.
+ ///
+ internal static class BufferStageUtils
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static BufferStage FromShaderStage(ShaderStage stage)
+ {
+ return (BufferStage)stage;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static BufferStage FromShaderStage(int stageIndex)
+ {
+ return (BufferStage)(stageIndex + 1);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static BufferStage FromUsage(BufferUsageFlags flags)
+ {
+ if (flags.HasFlag(BufferUsageFlags.Write))
+ {
+ return BufferStage.StorageWrite;
+ }
+ else
+ {
+ return BufferStage.StorageRead;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static BufferStage FromUsage(TextureUsageFlags flags)
+ {
+ if (flags.HasFlag(TextureUsageFlags.ImageStore))
+ {
+ return BufferStage.StorageWrite;
+ }
+ else
+ {
+ return BufferStage.StorageRead;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static BufferStage TextureBuffer(ShaderStage shaderStage, TextureUsageFlags flags)
+ {
+ return FromShaderStage(shaderStage) | FromUsage(flags);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static BufferStage GraphicsStorage(int stageIndex, BufferUsageFlags flags)
+ {
+ return FromShaderStage(stageIndex) | FromUsage(flags);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static BufferStage ComputeStorage(BufferUsageFlags flags)
+ {
+ return BufferStage.Compute | FromUsage(flags);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
index 0b6c78fac3..59a940a4f9 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
@@ -2,7 +2,6 @@ using Ryujinx.Common.Memory;
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using System;
-using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -242,9 +241,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
else
{
- IMemoryOwner memoryOwner = ByteMemoryPool.Rent(size);
+ MemoryOwner memoryOwner = MemoryOwner.Rent(size);
- GetSpan(va, size).CopyTo(memoryOwner.Memory.Span);
+ ReadImpl(va, memoryOwner.Span, tracked);
return new WritableRegion(this, va, memoryOwner, tracked);
}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
index 4d09c3aabd..b22cc01b87 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
@@ -192,9 +192,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
else
{
- IMemoryOwner memoryOwner = ByteMemoryPool.Rent(range.GetSize());
+ MemoryOwner memoryOwner = MemoryOwner.Rent(checked((int)range.GetSize()));
- Memory memory = memoryOwner.Memory;
+ Span memorySpan = memoryOwner.Span;
int offset = 0;
for (int i = 0; i < range.Count; i++)
@@ -203,7 +203,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
int size = (int)currentRange.Size;
if (currentRange.Address != MemoryManager.PteUnmapped)
{
- GetSpan(currentRange.Address, size).CopyTo(memory.Span.Slice(offset, size));
+ GetSpan(currentRange.Address, size).CopyTo(memorySpan.Slice(offset, size));
}
offset += size;
}
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs
index a80dcbc875..51be00b6e7 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs
@@ -62,6 +62,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
var result = new TextureBindingInfo(
target,
+ descriptor.Set,
descriptor.Binding,
descriptor.ArrayLength,
descriptor.CbufSlot,
@@ -90,6 +91,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
var result = new TextureBindingInfo(
target,
format,
+ descriptor.Set,
descriptor.Binding,
descriptor.ArrayLength,
descriptor.CbufSlot,
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs
index b08c44d671..3837092c94 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs
@@ -125,9 +125,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
CompressionAlgorithm algorithm = CompressionAlgorithm.None;
Read(ref algorithm);
- if (algorithm == CompressionAlgorithm.Deflate)
+ switch (algorithm)
{
- _activeStream = new DeflateStream(_stream, CompressionMode.Decompress, true);
+ case CompressionAlgorithm.None:
+ break;
+ case CompressionAlgorithm.Deflate:
+ _activeStream = new DeflateStream(_stream, CompressionMode.Decompress, true);
+ break;
+ case CompressionAlgorithm.Brotli:
+ _activeStream = new BrotliStream(_stream, CompressionMode.Decompress, true);
+ break;
+ default:
+ throw new ArgumentException($"Invalid compression algorithm \"{algorithm}\"");
}
}
@@ -139,9 +148,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
{
Write(ref algorithm);
- if (algorithm == CompressionAlgorithm.Deflate)
+ switch (algorithm)
{
- _activeStream = new DeflateStream(_stream, CompressionLevel.SmallestSize, true);
+ case CompressionAlgorithm.None:
+ break;
+ case CompressionAlgorithm.Deflate:
+ _activeStream = new DeflateStream(_stream, CompressionLevel.Fastest, true);
+ break;
+ case CompressionAlgorithm.Brotli:
+ _activeStream = new BrotliStream(_stream, CompressionLevel.Fastest, true);
+ break;
+ default:
+ throw new ArgumentException($"Invalid compression algorithm \"{algorithm}\"");
}
}
@@ -177,7 +195,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
switch (algorithm)
{
case CompressionAlgorithm.None:
- stream.Read(data);
+ stream.ReadExactly(data);
break;
case CompressionAlgorithm.Deflate:
stream = new DeflateStream(stream, CompressionMode.Decompress, true);
@@ -187,6 +205,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
}
stream.Dispose();
break;
+ case CompressionAlgorithm.Brotli:
+ stream = new BrotliStream(stream, CompressionMode.Decompress, true);
+ for (int offset = 0; offset < data.Length;)
+ {
+ offset += stream.Read(data[offset..]);
+ }
+ stream.Dispose();
+ break;
}
}
@@ -206,7 +232,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
stream.Write(data);
break;
case CompressionAlgorithm.Deflate:
- stream = new DeflateStream(stream, CompressionLevel.SmallestSize, true);
+ stream = new DeflateStream(stream, CompressionLevel.Fastest, true);
+ stream.Write(data);
+ stream.Dispose();
+ break;
+ case CompressionAlgorithm.Brotli:
+ stream = new BrotliStream(stream, CompressionLevel.Fastest, true);
stream.Write(data);
stream.Dispose();
break;
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/CompressionAlgorithm.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/CompressionAlgorithm.cs
index 96ddbb513e..86d3de07de 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/CompressionAlgorithm.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/CompressionAlgorithm.cs
@@ -14,5 +14,10 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// Deflate compression (RFC 1951).
///
Deflate,
+
+ ///
+ /// Brotli compression (RFC 7932).
+ ///
+ Brotli,
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs
index c4ce0b8702..cecfe9acf8 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs
@@ -51,7 +51,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// Compression algorithm
public static CompressionAlgorithm GetCompressionAlgorithm()
{
- return CompressionAlgorithm.Deflate;
+ return CompressionAlgorithm.Brotli;
}
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
index 45f32e2d39..3c7664b77a 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
@@ -18,6 +18,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private readonly ShaderSpecializationState _newSpecState;
private readonly int _stageIndex;
private readonly bool _isVulkan;
+ private readonly bool _hasGeometryShader;
+ private readonly bool _supportsQuads;
///
/// Creates a new instance of the cached GPU state accessor for shader translation.
@@ -29,6 +31,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// Shader specialization state of the recompiled shader
/// Resource counts shared across all shader stages
/// Shader stage index
+ /// Indicates if a geometry shader is present
public DiskCacheGpuAccessor(
GpuContext context,
ReadOnlyMemory data,
@@ -36,7 +39,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
ShaderSpecializationState oldSpecState,
ShaderSpecializationState newSpecState,
ResourceCounts counts,
- int stageIndex) : base(context, counts, stageIndex)
+ int stageIndex,
+ bool hasGeometryShader) : base(context, counts, stageIndex)
{
_data = data;
_cb1Data = cb1Data;
@@ -44,6 +48,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
_newSpecState = newSpecState;
_stageIndex = stageIndex;
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
+ _hasGeometryShader = hasGeometryShader;
+ _supportsQuads = context.Capabilities.SupportsQuads;
if (stageIndex == (int)ShaderStage.Geometry - 1)
{
@@ -100,7 +106,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
///
public GpuGraphicsState QueryGraphicsState()
{
- return _oldSpecState.GraphicsState.CreateShaderGraphicsState(!_isVulkan, _isVulkan || _oldSpecState.GraphicsState.YNegateEnabled);
+ return _oldSpecState.GraphicsState.CreateShaderGraphicsState(
+ !_isVulkan,
+ _supportsQuads,
+ _hasGeometryShader,
+ _isVulkan || _oldSpecState.GraphicsState.YNegateEnabled);
}
///
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs
index 59d2cfb3fe..08cd3bb02e 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs
@@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
}
dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
- dataFileStream.Read(cb1Data);
+ dataFileStream.ReadExactly(cb1Data);
BinarySerializer.ReadCompressed(dataFileStream, guestCode);
_cache[index] = (guestCode, cb1Data);
@@ -279,7 +279,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
byte[] cachedCode = new byte[entry.CodeSize];
byte[] cachedCb1Data = new byte[entry.Cb1DataSize];
- dataFileStream.Read(cachedCb1Data);
+ dataFileStream.ReadExactly(cachedCb1Data);
BinarySerializer.ReadCompressed(dataFileStream, cachedCode);
if (data.SequenceEqual(cachedCode) && cb1Data.SequenceEqual(cachedCb1Data))
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
index 2c19cc4b98..c1f5920116 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
- private const uint CodeGenVersion = 6577;
+ private const uint CodeGenVersion = 7131;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs
index 153fc44276..20f96462ea 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs
@@ -601,6 +601,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
TargetApi api = _context.Capabilities.Api;
+ bool hasCachedGs = guestShaders[4].HasValue;
+
for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
{
if (guestShaders[stageIndex + 1].HasValue)
@@ -610,7 +612,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
byte[] guestCode = shader.Code;
byte[] cb1Data = shader.Cb1Data;
- DiskCacheGpuAccessor gpuAccessor = new(_context, guestCode, cb1Data, specState, newSpecState, counts, stageIndex);
+ DiskCacheGpuAccessor gpuAccessor = new(_context, guestCode, cb1Data, specState, newSpecState, counts, stageIndex, hasCachedGs);
TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, 0);
if (nextStage != null)
@@ -623,7 +625,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
byte[] guestCodeA = guestShaders[0].Value.Code;
byte[] cb1DataA = guestShaders[0].Value.Cb1Data;
- DiskCacheGpuAccessor gpuAccessorA = new(_context, guestCodeA, cb1DataA, specState, newSpecState, counts, 0);
+ DiskCacheGpuAccessor gpuAccessorA = new(_context, guestCodeA, cb1DataA, specState, newSpecState, counts, 0, hasCachedGs);
translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, api, DefaultFlags | TranslationFlags.VertexA, 0);
}
@@ -711,7 +713,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
GuestCodeAndCbData shader = guestShaders[0].Value;
ResourceCounts counts = new();
ShaderSpecializationState newSpecState = new(ref specState.ComputeState);
- DiskCacheGpuAccessor gpuAccessor = new(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0);
+ DiskCacheGpuAccessor gpuAccessor = new(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0, false);
gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false);
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, 0);
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
index 04949690a2..1be75f242b 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
@@ -17,6 +17,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
private readonly int _stageIndex;
private readonly bool _compute;
private readonly bool _isVulkan;
+ private readonly bool _hasGeometryShader;
+ private readonly bool _supportsQuads;
///
/// Creates a new instance of the GPU state accessor for graphics shader translation.
@@ -25,12 +27,20 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// GPU channel
/// Current GPU state
/// Graphics shader stage index (0 = Vertex, 4 = Fragment)
- public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state, int stageIndex) : base(context, state.ResourceCounts, stageIndex)
+ /// Indicates if a geometry shader is present
+ public GpuAccessor(
+ GpuContext context,
+ GpuChannel channel,
+ GpuAccessorState state,
+ int stageIndex,
+ bool hasGeometryShader) : base(context, state.ResourceCounts, stageIndex)
{
- _isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
_channel = channel;
_state = state;
_stageIndex = stageIndex;
+ _isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
+ _hasGeometryShader = hasGeometryShader;
+ _supportsQuads = context.Capabilities.SupportsQuads;
if (stageIndex == (int)ShaderStage.Geometry - 1)
{
@@ -105,7 +115,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
///
public GpuGraphicsState QueryGraphicsState()
{
- return _state.GraphicsState.CreateShaderGraphicsState(!_isVulkan, _isVulkan || _state.GraphicsState.YNegateEnabled);
+ return _state.GraphicsState.CreateShaderGraphicsState(
+ !_isVulkan,
+ _supportsQuads,
+ _hasGeometryShader,
+ _isVulkan || _state.GraphicsState.YNegateEnabled);
}
///
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
index 0d562b0da2..d89eebabfd 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
@@ -51,7 +51,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
_reservedImages = rrc.ReservedImages;
}
- public int CreateConstantBufferBinding(int index)
+ public SetBindingPair CreateConstantBufferBinding(int index)
{
int binding;
@@ -64,10 +64,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
binding = _resourceCounts.UniformBuffersCount++;
}
- return binding + _reservedConstantBuffers;
+ return new SetBindingPair(_context.Capabilities.UniformBufferSetIndex, binding + _reservedConstantBuffers);
}
- public int CreateImageBinding(int count, bool isBuffer)
+ public SetBindingPair CreateImageBinding(int count, bool isBuffer)
{
int binding;
@@ -96,10 +96,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
_resourceCounts.ImagesCount += count;
}
- return binding + _reservedImages;
+ return new SetBindingPair(_context.Capabilities.ImageSetIndex, binding + _reservedImages);
}
- public int CreateStorageBufferBinding(int index)
+ public SetBindingPair CreateStorageBufferBinding(int index)
{
int binding;
@@ -112,10 +112,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
binding = _resourceCounts.StorageBuffersCount++;
}
- return binding + _reservedStorageBuffers;
+ return new SetBindingPair(_context.Capabilities.StorageBufferSetIndex, binding + _reservedStorageBuffers);
}
- public int CreateTextureBinding(int count, bool isBuffer)
+ public SetBindingPair CreateTextureBinding(int count, bool isBuffer)
{
int binding;
@@ -144,7 +144,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
_resourceCounts.TexturesCount += count;
}
- return binding + _reservedTextures;
+ return new SetBindingPair(_context.Capabilities.TextureSetIndex, binding + _reservedTextures);
}
private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName)
@@ -183,6 +183,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
return maxPerStage * Constants.ShaderStages;
}
+ public int CreateExtraSet()
+ {
+ if (_resourceCounts.SetsCount >= _context.Capabilities.MaximumExtraSets)
+ {
+ return -1;
+ }
+
+ return _context.Capabilities.ExtraSetBaseIndex + _resourceCounts.SetsCount++;
+ }
+
public int QueryHostGatherBiasPrecision() => _context.Capabilities.GatherBiasPrecision;
public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision;
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
index b5bc4df3c0..765bef7d4a 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
@@ -106,8 +106,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Creates a new graphics state from this state that can be used for shader generation.
///
/// Indicates if the host API supports alpha test operations
+ /// Indicates if the host API supports quad primitives
+ /// Indicates if a geometry shader is used
+ /// If true, indicates that the fragment origin is the upper left corner of the viewport, otherwise it is the lower left corner
/// GPU graphics state that can be used for shader translation
- public readonly GpuGraphicsState CreateShaderGraphicsState(bool hostSupportsAlphaTest, bool originUpperLeft)
+ public readonly GpuGraphicsState CreateShaderGraphicsState(bool hostSupportsAlphaTest, bool hostSupportsQuads, bool hasGeometryShader, bool originUpperLeft)
{
AlphaTestOp alphaTestOp;
@@ -130,6 +133,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
};
}
+ bool isQuad = Topology == PrimitiveTopology.Quads || Topology == PrimitiveTopology.QuadStrip;
+ bool halvePrimitiveId = !hostSupportsQuads && !hasGeometryShader && isQuad;
+
return new GpuGraphicsState(
EarlyZForce,
ConvertToInputTopology(Topology, TessellationMode),
@@ -149,7 +155,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
in FragmentOutputTypes,
DualSourceBlendEnable,
YNegateEnabled,
- originUpperLeft);
+ originUpperLeft,
+ halvePrimitiveId);
}
///
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs b/src/Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs
index 126e3249c0..59ab378cf8 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs
@@ -24,5 +24,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Total of images used by the shaders.
///
public int ImagesCount;
+
+ ///
+ /// Total of extra sets used by the shaders.
+ ///
+ public int SetsCount;
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
index 31cc94a25c..4fc66c4c06 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
@@ -339,7 +339,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (gpuVa != 0)
{
- GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState, stageIndex);
+ GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState, stageIndex, addresses.Geometry != 0);
TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, gpuVa);
if (nextStage != null)
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs
index ed56db3b30..49823562f2 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs
@@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
+using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Shader
@@ -9,13 +10,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
///
class ShaderInfoBuilder
{
- private const int TotalSets = 4;
-
- private const int UniformSetIndex = 0;
- private const int StorageSetIndex = 1;
- private const int TextureSetIndex = 2;
- private const int ImageSetIndex = 3;
-
private const ResourceStages SupportBufferStages =
ResourceStages.Compute |
ResourceStages.Vertex |
@@ -36,8 +30,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
private readonly int _reservedTextures;
private readonly int _reservedImages;
- private readonly List[] _resourceDescriptors;
- private readonly List[] _resourceUsages;
+ private List[] _resourceDescriptors;
+ private List[] _resourceUsages;
///
/// Creates a new shader info builder.
@@ -51,17 +45,27 @@ namespace Ryujinx.Graphics.Gpu.Shader
_fragmentOutputMap = -1;
- _resourceDescriptors = new List[TotalSets];
- _resourceUsages = new List[TotalSets];
+ int uniformSetIndex = context.Capabilities.UniformBufferSetIndex;
+ int storageSetIndex = context.Capabilities.StorageBufferSetIndex;
+ int textureSetIndex = context.Capabilities.TextureSetIndex;
+ int imageSetIndex = context.Capabilities.ImageSetIndex;
- for (int index = 0; index < TotalSets; index++)
+ int totalSets = Math.Max(uniformSetIndex, storageSetIndex);
+ totalSets = Math.Max(totalSets, textureSetIndex);
+ totalSets = Math.Max(totalSets, imageSetIndex);
+ totalSets++;
+
+ _resourceDescriptors = new List[totalSets];
+ _resourceUsages = new List[totalSets];
+
+ for (int index = 0; index < totalSets; index++)
{
_resourceDescriptors[index] = new();
_resourceUsages[index] = new();
}
- AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1);
- AddUsage(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1);
+ AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, uniformSetIndex, 0, 1);
+ AddUsage(SupportBufferStages, ResourceType.UniformBuffer, uniformSetIndex, 0, 1);
ResourceReservationCounts rrc = new(!context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute);
@@ -73,16 +77,25 @@ namespace Ryujinx.Graphics.Gpu.Shader
// TODO: Handle that better? Maybe we should only set the binding that are really needed on each shader.
ResourceStages stages = vertexAsCompute ? ResourceStages.Compute | ResourceStages.Vertex : VtgStages;
- PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, UniformSetIndex, 1, rrc.ReservedConstantBuffers - 1);
- PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, StorageSetIndex, 0, rrc.ReservedStorageBuffers);
- PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, TextureSetIndex, 0, rrc.ReservedTextures);
- PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, ImageSetIndex, 0, rrc.ReservedImages);
+ PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, uniformSetIndex, 1, rrc.ReservedConstantBuffers - 1);
+ PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, storageSetIndex, 0, rrc.ReservedStorageBuffers, true);
+ PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, textureSetIndex, 0, rrc.ReservedTextures);
+ PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, imageSetIndex, 0, rrc.ReservedImages, true);
}
- private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count)
+ ///
+ /// Populates descriptors and usages for vertex as compute and transform feedback emulation reserved resources.
+ ///
+ /// Shader stages where the resources are used
+ /// Resource type
+ /// Resource set index where the resources are used
+ /// First binding number
+ /// Amount of bindings
+ /// True if the binding is written from the shader, false otherwise
+ private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count, bool write = false)
{
AddDescriptor(stages, type, setIndex, start, count);
- AddUsage(stages, type, setIndex, start, count);
+ AddUsage(stages, type, setIndex, start, count, write);
}
///
@@ -127,18 +140,23 @@ namespace Ryujinx.Graphics.Gpu.Shader
int textureBinding = _reservedTextures + stageIndex * texturesPerStage * 2;
int imageBinding = _reservedImages + stageIndex * imagesPerStage * 2;
- AddDescriptor(stages, ResourceType.UniformBuffer, UniformSetIndex, uniformBinding, uniformsPerStage);
- AddDescriptor(stages, ResourceType.StorageBuffer, StorageSetIndex, storageBinding, storagesPerStage);
- AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage);
- AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage);
+ int uniformSetIndex = _context.Capabilities.UniformBufferSetIndex;
+ int storageSetIndex = _context.Capabilities.StorageBufferSetIndex;
+ int textureSetIndex = _context.Capabilities.TextureSetIndex;
+ int imageSetIndex = _context.Capabilities.ImageSetIndex;
- AddArrayDescriptors(info.Textures, stages, TextureSetIndex, isImage: false);
- AddArrayDescriptors(info.Images, stages, TextureSetIndex, isImage: true);
+ AddDescriptor(stages, ResourceType.UniformBuffer, uniformSetIndex, uniformBinding, uniformsPerStage);
+ AddDescriptor(stages, ResourceType.StorageBuffer, storageSetIndex, storageBinding, storagesPerStage);
+ AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, textureSetIndex, textureBinding, texturesPerStage);
+ AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, imageSetIndex, imageBinding, imagesPerStage);
- AddUsage(info.CBuffers, stages, UniformSetIndex, isStorage: false);
- AddUsage(info.SBuffers, stages, StorageSetIndex, isStorage: true);
- AddUsage(info.Textures, stages, TextureSetIndex, isImage: false);
- AddUsage(info.Images, stages, ImageSetIndex, isImage: true);
+ AddArrayDescriptors(info.Textures, stages, isImage: false);
+ AddArrayDescriptors(info.Images, stages, isImage: true);
+
+ AddUsage(info.CBuffers, stages, isStorage: false);
+ AddUsage(info.SBuffers, stages, isStorage: true);
+ AddUsage(info.Textures, stages, isImage: false);
+ AddUsage(info.Images, stages, isImage: true);
}
///
@@ -177,9 +195,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
///
/// Textures to be added
/// Stages where the textures are used
- /// Descriptor set index where the textures will be bound
/// True for images, false for textures
- private void AddArrayDescriptors(IEnumerable textures, ResourceStages stages, int setIndex, bool isImage)
+ private void AddArrayDescriptors(IEnumerable textures, ResourceStages stages, bool isImage)
{
foreach (TextureDescriptor texture in textures)
{
@@ -187,7 +204,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
ResourceType type = GetTextureResourceType(texture, isImage);
- _resourceDescriptors[setIndex].Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages));
+ GetDescriptors(texture.Set).Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages));
}
}
}
@@ -200,11 +217,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Descriptor set number where the resource will be bound
/// Binding number where the resource will be bound
/// Number of resources bound at the binding location
- private void AddUsage(ResourceStages stages, ResourceType type, int setIndex, int binding, int count)
+ /// True if the binding is written from the shader, false otherwise
+ private void AddUsage(ResourceStages stages, ResourceType type, int setIndex, int binding, int count, bool write = false)
{
for (int index = 0; index < count; index++)
{
- _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, 1, type, stages));
+ _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, 1, type, stages, write));
}
}
@@ -213,17 +231,17 @@ namespace Ryujinx.Graphics.Gpu.Shader
///
/// Buffers to be added
/// Stages where the buffers are used
- /// Descriptor set index where the buffers will be bound
/// True for storage buffers, false for uniform buffers
- private void AddUsage(IEnumerable buffers, ResourceStages stages, int setIndex, bool isStorage)
+ private void AddUsage(IEnumerable buffers, ResourceStages stages, bool isStorage)
{
foreach (BufferDescriptor buffer in buffers)
{
- _resourceUsages[setIndex].Add(new ResourceUsage(
+ GetUsages(buffer.Set).Add(new ResourceUsage(
buffer.Binding,
1,
isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
- stages));
+ stages,
+ buffer.Flags.HasFlag(BufferUsageFlags.Write)));
}
}
@@ -232,18 +250,70 @@ namespace Ryujinx.Graphics.Gpu.Shader
///
/// Textures to be added
/// Stages where the textures are used
- /// Descriptor set index where the textures will be bound
/// True for images, false for textures
- private void AddUsage(IEnumerable textures, ResourceStages stages, int setIndex, bool isImage)
+ private void AddUsage(IEnumerable textures, ResourceStages stages, bool isImage)
{
foreach (TextureDescriptor texture in textures)
{
ResourceType type = GetTextureResourceType(texture, isImage);
- _resourceUsages[setIndex].Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages));
+ GetUsages(texture.Set).Add(new ResourceUsage(
+ texture.Binding,
+ texture.ArrayLength,
+ type,
+ stages,
+ texture.Flags.HasFlag(TextureUsageFlags.ImageStore)));
}
}
+ ///
+ /// Gets the list of resource descriptors for a given set index. A new list will be created if needed.
+ ///
+ /// Resource set index
+ /// List of resource descriptors
+ private List GetDescriptors(int setIndex)
+ {
+ if (_resourceDescriptors.Length <= setIndex)
+ {
+ int oldLength = _resourceDescriptors.Length;
+ Array.Resize(ref _resourceDescriptors, setIndex + 1);
+
+ for (int index = oldLength; index <= setIndex; index++)
+ {
+ _resourceDescriptors[index] = new();
+ }
+ }
+
+ return _resourceDescriptors[setIndex];
+ }
+
+ ///
+ /// Gets the list of resource usages for a given set index. A new list will be created if needed.
+ ///
+ /// Resource set index
+ /// List of resource usages
+ private List GetUsages(int setIndex)
+ {
+ if (_resourceUsages.Length <= setIndex)
+ {
+ int oldLength = _resourceUsages.Length;
+ Array.Resize(ref _resourceUsages, setIndex + 1);
+
+ for (int index = oldLength; index <= setIndex; index++)
+ {
+ _resourceUsages[index] = new();
+ }
+ }
+
+ return _resourceUsages[setIndex];
+ }
+
+ ///
+ /// Gets a resource type from a texture descriptor.
+ ///
+ /// Texture descriptor
+ /// Whether the texture is a image texture (writable) or not (sampled)
+ /// Resource type
private static ResourceType GetTextureResourceType(TextureDescriptor texture, bool isImage)
{
bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
@@ -278,10 +348,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Shader information
public ShaderInfo Build(ProgramPipelineState? pipeline, bool fromCache = false)
{
- var descriptors = new ResourceDescriptorCollection[TotalSets];
- var usages = new ResourceUsageCollection[TotalSets];
+ int totalSets = _resourceDescriptors.Length;
- for (int index = 0; index < TotalSets; index++)
+ var descriptors = new ResourceDescriptorCollection[totalSets];
+ var usages = new ResourceUsageCollection[totalSets];
+
+ for (int index = 0; index < totalSets; index++)
{
descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly());
usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly());
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs b/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs
index 434f25900c..490c0c585c 100644
--- a/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs
@@ -1,6 +1,5 @@
using Ryujinx.Common.Memory;
using System;
-using System.Buffers;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
@@ -10,11 +9,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
{
static class FormatConverter
{
- public unsafe static IMemoryOwner ConvertS8D24ToD24S8(ReadOnlySpan data)
+ public unsafe static MemoryOwner ConvertS8D24ToD24S8(ReadOnlySpan data)
{
- IMemoryOwner outputMemory = ByteMemoryPool.Rent(data.Length);
+ MemoryOwner outputMemory = MemoryOwner.Rent(data.Length);
- Span output = outputMemory.Memory.Span;
+ Span output = outputMemory.Span;
int start = 0;
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs b/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs
index 1c5acedf3a..6198823d9d 100644
--- a/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs
@@ -63,5 +63,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
}
+
+ public void Dispose()
+ {
+ }
}
}
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs
index d70b0a0081..41ac058c1c 100644
--- a/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs
@@ -48,5 +48,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
}
+
+ public void Dispose()
+ {
+ }
}
}
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs
index 79c6cb685b..0ebafb04e9 100644
--- a/src/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs
@@ -48,7 +48,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
internalFormat = (SizedInternalFormat)format.PixelInternalFormat;
}
- int levels = Info.GetLevelsClamped();
+ int levels = Info.Levels;
switch (Info.Target)
{
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
index 8a18e6132a..946eb755cc 100644
--- a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
@@ -51,7 +51,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
pixelInternalFormat = format.PixelInternalFormat;
}
- int levels = Info.GetLevelsClamped();
+ int levels = Info.Levels;
GL.TextureView(
Handle,
@@ -267,7 +267,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
public unsafe PinnedSpan GetData()
{
int size = 0;
- int levels = Info.GetLevelsClamped();
+ int levels = Info.Levels;
for (int level = 0; level < levels; level++)
{
@@ -426,7 +426,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
faces = 6;
}
- int levels = Info.GetLevelsClamped();
+ int levels = Info.Levels;
for (int level = 0; level < levels; level++)
{
@@ -716,7 +716,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
int width = Info.Width;
int height = Info.Height;
int depth = Info.Depth;
- int levels = Info.GetLevelsClamped();
+ int levels = Info.Levels;
int offset = 0;
diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs
index d56c40af46..ba9cd45c67 100644
--- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs
+++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs
@@ -61,7 +61,9 @@ namespace Ryujinx.Graphics.OpenGL
{
BufferCount++;
- if (access.HasFlag(GAL.BufferAccess.FlushPersistent))
+ var memType = access & GAL.BufferAccess.MemoryTypeMask;
+
+ if (memType == GAL.BufferAccess.HostMemory)
{
BufferHandle handle = Buffer.CreatePersistent(size);
@@ -75,11 +77,6 @@ namespace Ryujinx.Graphics.OpenGL
}
}
- public BufferHandle CreateBuffer(int size, GAL.BufferAccess access, BufferHandle storageHint)
- {
- return CreateBuffer(size, access);
- }
-
public BufferHandle CreateBuffer(nint pointer, int size)
{
throw new NotSupportedException();
@@ -148,6 +145,7 @@ namespace Ryujinx.Graphics.OpenGL
return new Capabilities(
api: TargetApi.OpenGL,
vendorName: GpuVendor,
+ memoryType: SystemMemoryType.BackendManaged,
hasFrontFacingBug: intelWindows,
hasVectorIndexingBug: amdWindows,
needsFragmentOutputSpecialization: false,
@@ -161,6 +159,7 @@ namespace Ryujinx.Graphics.OpenGL
supportsBgraFormat: false,
supportsR4G4Format: false,
supportsR4G4B4A4Format: true,
+ supportsScaledVertexFormats: true,
supportsSnormBufferTextureFormat: false,
supports5BitComponentFormat: true,
supportsSparseBuffer: false,
@@ -175,7 +174,7 @@ namespace Ryujinx.Graphics.OpenGL
supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat,
supportsCubemapView: true,
supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset,
- supportsScaledVertexFormats: true,
+ supportsQuads: HwCapabilities.SupportsQuads,
supportsSeparateSampler: false,
supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
supportsShaderBarrierDivergence: !(intelWindows || intelUnix),
@@ -188,6 +187,12 @@ namespace Ryujinx.Graphics.OpenGL
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
supportsDepthClipControl: true,
+ uniformBufferSetIndex: 0,
+ storageBufferSetIndex: 1,
+ textureSetIndex: 2,
+ imageSetIndex: 3,
+ extraSetBaseIndex: 0,
+ maximumExtraSets: 0,
maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver?
maximumStorageBuffersPerStage: 16,
maximumTexturesPerStage: 32,
diff --git a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs
index 6d066bb676..54f6b3f7b2 100644
--- a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs
@@ -963,6 +963,11 @@ namespace Ryujinx.Graphics.OpenGL
(array as ImageArray).Bind(binding);
}
+ public void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array)
+ {
+ throw new NotSupportedException("OpenGL does not support descriptor sets.");
+ }
+
public void SetIndexBuffer(BufferRange buffer, IndexType type)
{
_elementsType = type.Convert();
@@ -1312,6 +1317,11 @@ namespace Ryujinx.Graphics.OpenGL
(array as TextureArray).Bind(binding);
}
+ public void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array)
+ {
+ throw new NotSupportedException("OpenGL does not support descriptor sets.");
+ }
+
public void SetTransformFeedbackBuffers(ReadOnlySpan buffers)
{
if (_tfEnabled)
diff --git a/src/Ryujinx.Graphics.Shader/BufferDescriptor.cs b/src/Ryujinx.Graphics.Shader/BufferDescriptor.cs
index ead1c5e67b..11d4e3c116 100644
--- a/src/Ryujinx.Graphics.Shader/BufferDescriptor.cs
+++ b/src/Ryujinx.Graphics.Shader/BufferDescriptor.cs
@@ -4,14 +4,16 @@ namespace Ryujinx.Graphics.Shader
{
// New fields should be added to the end of the struct to keep disk shader cache compatibility.
+ public readonly int Set;
public readonly int Binding;
public readonly byte Slot;
public readonly byte SbCbSlot;
public readonly ushort SbCbOffset;
public readonly BufferUsageFlags Flags;
- public BufferDescriptor(int binding, int slot)
+ public BufferDescriptor(int set, int binding, int slot)
{
+ Set = set;
Binding = binding;
Slot = (byte)slot;
SbCbSlot = 0;
@@ -19,8 +21,9 @@ namespace Ryujinx.Graphics.Shader
Flags = BufferUsageFlags.None;
}
- public BufferDescriptor(int binding, int slot, int sbCbSlot, int sbCbOffset, BufferUsageFlags flags)
+ public BufferDescriptor(int set, int binding, int slot, int sbCbSlot, int sbCbOffset, BufferUsageFlags flags)
{
+ Set = set;
Binding = binding;
Slot = (byte)slot;
SbCbSlot = (byte)sbCbSlot;
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
index f0e57b534b..4308b08f81 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
@@ -462,7 +462,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
}
else
{
- context.Properties.Textures.TryGetValue(texOp.Binding, out TextureDefinition definition);
+ context.Properties.Textures.TryGetValue(texOp.GetTextureSetAndBinding(), out TextureDefinition definition);
bool hasLod = !definition.Type.HasFlag(SamplerType.Multisample) && (definition.Type & SamplerType.Mask) != SamplerType.TextureBuffer;
string texCall;
@@ -639,7 +639,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
private static string GetSamplerName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex)
{
- TextureDefinition textureDefinition = context.Properties.Textures[texOp.Binding];
+ TextureDefinition textureDefinition = context.Properties.Textures[texOp.GetTextureSetAndBinding()];
string name = textureDefinition.Name;
if (textureDefinition.ArrayLength != 1)
@@ -649,7 +649,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (texOp.IsSeparate)
{
- TextureDefinition samplerDefinition = context.Properties.Textures[texOp.SamplerBinding];
+ TextureDefinition samplerDefinition = context.Properties.Textures[texOp.GetSamplerSetAndBinding()];
string samplerName = samplerDefinition.Name;
if (samplerDefinition.ArrayLength != 1)
@@ -665,7 +665,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
private static string GetImageName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex)
{
- TextureDefinition definition = context.Properties.Images[texOp.Binding];
+ TextureDefinition definition = context.Properties.Images[texOp.GetTextureSetAndBinding()];
string name = definition.Name;
if (definition.ArrayLength != 1)
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs
index 2b1fdf44c3..cc7977f848 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs
@@ -33,9 +33,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public Dictionary LocalMemories { get; } = new();
public Dictionary SharedMemories { get; } = new();
- public Dictionary SamplersTypes { get; } = new();
- public Dictionary Samplers { get; } = new();
- public Dictionary Images { get; } = new();
+ public Dictionary SamplersTypes { get; } = new();
+ public Dictionary Samplers { get; } = new();
+ public Dictionary Images { get; } = new();
public Dictionary Inputs { get; } = new();
public Dictionary Outputs { get; } = new();
@@ -98,11 +98,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Logger = parameters.Logger;
TargetApi = parameters.TargetApi;
- AddCapability(Capability.Shader);
- AddCapability(Capability.Float64);
-
- SetMemoryModel(AddressingModel.Logical, MemoryModel.GLSL450);
-
Delegates = new SpirvDelegates(this);
}
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
index 37df4df802..55d35bf0d1 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
@@ -208,13 +208,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var sampledImageVariable = context.Variable(sampledImageArrayPointerType, StorageClass.UniformConstant);
- context.Samplers.Add(sampler.Binding, new SamplerDeclaration(
+ context.Samplers.Add(new(sampler.Set, sampler.Binding), new SamplerDeclaration(
imageType,
sampledImageType,
sampledImagePointerType,
sampledImageVariable,
sampler.ArrayLength != 1));
- context.SamplersTypes.Add(sampler.Binding, sampler.Type);
+ context.SamplersTypes.Add(new(sampler.Set, sampler.Binding), sampler.Type);
context.Name(sampledImageVariable, sampler.Name);
context.Decorate(sampledImageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
@@ -256,7 +256,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var imageVariable = context.Variable(imageArrayPointerType, StorageClass.UniformConstant);
- context.Images.Add(image.Binding, new ImageDeclaration(imageType, imagePointerType, imageVariable, image.ArrayLength != 1));
+ context.Images.Add(new(image.Set, image.Binding), new ImageDeclaration(imageType, imagePointerType, imageVariable, image.ArrayLength != 1));
context.Name(imageVariable, image.Name);
context.Decorate(imageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
index 34f8532a65..6206985d85 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
@@ -602,7 +602,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return context.Get(type, texOp.GetSource(srcIndex++));
}
- ImageDeclaration declaration = context.Images[texOp.Binding];
+ ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()];
SpvInstruction image = declaration.Image;
SpvInstruction resultType = context.GetType(componentType);
@@ -681,7 +681,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return context.Get(type, texOp.GetSource(srcIndex++));
}
- ImageDeclaration declaration = context.Images[texOp.Binding];
+ ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()];
SpvInstruction image = declaration.Image;
if (declaration.IsIndexed)
@@ -738,7 +738,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return context.Get(type, texOp.GetSource(srcIndex++));
}
- ImageDeclaration declaration = context.Images[texOp.Binding];
+ ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()];
SpvInstruction image = declaration.Image;
if (declaration.IsIndexed)
@@ -837,7 +837,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return context.Get(type, texOp.GetSource(srcIndex++));
}
- SamplerDeclaration declaration = context.Samplers[texOp.Binding];
+ SamplerDeclaration declaration = context.Samplers[texOp.GetTextureSetAndBinding()];
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
int pCount = texOp.Type.GetDimensions();
@@ -1161,7 +1161,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return context.Get(type, texOp.GetSource(srcIndex++));
}
- SamplerDeclaration declaration = context.Samplers[texOp.Binding];
+ SamplerDeclaration declaration = context.Samplers[texOp.GetTextureSetAndBinding()];
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
int coordsCount = texOp.Type.GetDimensions();
@@ -1433,7 +1433,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
int srcIndex = 0;
- SamplerDeclaration declaration = context.Samplers[texOp.Binding];
+ SamplerDeclaration declaration = context.Samplers[texOp.GetTextureSetAndBinding()];
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
image = context.Image(declaration.ImageType, image);
@@ -1449,7 +1449,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
int srcIndex = 0;
- SamplerDeclaration declaration = context.Samplers[texOp.Binding];
+ SamplerDeclaration declaration = context.Samplers[texOp.GetTextureSetAndBinding()];
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
image = context.Image(declaration.ImageType, image);
@@ -1460,7 +1460,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
else
{
- var type = context.SamplersTypes[texOp.Binding];
+ var type = context.SamplersTypes[texOp.GetTextureSetAndBinding()];
bool hasLod = !type.HasFlag(SamplerType.Multisample) && type != SamplerType.TextureBuffer;
int dimensions = (type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : type.GetDimensions();
@@ -1889,7 +1889,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
image = context.Load(declaration.ImageType, image);
- SamplerDeclaration samplerDeclaration = context.Samplers[texOp.SamplerBinding];
+ SamplerDeclaration samplerDeclaration = context.Samplers[texOp.GetSamplerSetAndBinding()];
SpvInstruction sampler = samplerDeclaration.Image;
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
index ccfdc46d09..b259dde28c 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
@@ -43,6 +43,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
CodeGenContext context = new(info, parameters, instPool, integerPool);
+ context.AddCapability(Capability.Shader);
+
+ context.SetMemoryModel(AddressingModel.Logical, MemoryModel.GLSL450);
+
context.AddCapability(Capability.GroupNonUniformBallot);
context.AddCapability(Capability.GroupNonUniformShuffle);
context.AddCapability(Capability.GroupNonUniformVote);
@@ -51,6 +55,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.AddCapability(Capability.ImageQuery);
context.AddCapability(Capability.SampledBuffer);
+ if (parameters.HostCapabilities.SupportsShaderFloat64)
+ {
+ context.AddCapability(Capability.Float64);
+ }
+
if (parameters.Definitions.TransformFeedbackEnabled && parameters.Definitions.LastInVertexPipeline)
{
context.AddCapability(Capability.TransformFeedback);
@@ -58,7 +67,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (parameters.Definitions.Stage == ShaderStage.Fragment)
{
- if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.Layer)))
+ if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.Layer)) ||
+ context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.PrimitiveId)))
{
context.AddCapability(Capability.Geometry);
}
diff --git a/src/Ryujinx.Graphics.Shader/GpuGraphicsState.cs b/src/Ryujinx.Graphics.Shader/GpuGraphicsState.cs
index f16c71d559..38684002c2 100644
--- a/src/Ryujinx.Graphics.Shader/GpuGraphicsState.cs
+++ b/src/Ryujinx.Graphics.Shader/GpuGraphicsState.cs
@@ -102,6 +102,11 @@ namespace Ryujinx.Graphics.Shader
///
public readonly bool OriginUpperLeft;
+ ///
+ /// Indicates that the primitive ID values on the shader should be halved due to quad to triangles conversion.
+ ///
+ public readonly bool HalvePrimitiveId;
+
///
/// Creates a new GPU graphics state.
///
@@ -124,6 +129,7 @@ namespace Ryujinx.Graphics.Shader
/// Indicates whether dual source blend is enabled
/// Indicates if negation of the viewport Y axis is enabled
/// If true, indicates that the fragment origin is the upper left corner of the viewport, otherwise it is the lower left corner
+ /// Indicates that the primitive ID values on the shader should be halved due to quad to triangles conversion
public GpuGraphicsState(
bool earlyZForce,
InputTopology topology,
@@ -143,7 +149,8 @@ namespace Ryujinx.Graphics.Shader
in Array8 fragmentOutputTypes,
bool dualSourceBlendEnable,
bool yNegateEnabled,
- bool originUpperLeft)
+ bool originUpperLeft,
+ bool halvePrimitiveId)
{
EarlyZForce = earlyZForce;
Topology = topology;
@@ -164,6 +171,7 @@ namespace Ryujinx.Graphics.Shader
DualSourceBlendEnable = dualSourceBlendEnable;
YNegateEnabled = yNegateEnabled;
OriginUpperLeft = originUpperLeft;
+ HalvePrimitiveId = halvePrimitiveId;
}
}
}
diff --git a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs
index b1a9f9f842..4e6d6edf9b 100644
--- a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs
+++ b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs
@@ -27,34 +27,43 @@ namespace Ryujinx.Graphics.Shader
ReadOnlySpan GetCode(ulong address, int minimumSize);
///
- /// Queries the binding number of a constant buffer.
+ /// Gets the binding number of a constant buffer.
///
/// Constant buffer index
/// Binding number
- int CreateConstantBufferBinding(int index);
+ SetBindingPair CreateConstantBufferBinding(int index);
///
- /// Queries the binding number of an image.
+ /// Gets the binding number of an image.
///
/// For array of images, the number of elements of the array, otherwise it should be 1
/// Indicates if the image is a buffer image
/// Binding number
- int CreateImageBinding(int count, bool isBuffer);
+ SetBindingPair CreateImageBinding(int count, bool isBuffer);
///
- /// Queries the binding number of a storage buffer.
+ /// Gets the binding number of a storage buffer.
///
/// Storage buffer index
/// Binding number
- int CreateStorageBufferBinding(int index);
+ SetBindingPair CreateStorageBufferBinding(int index);
///
- /// Queries the binding number of a texture.
+ /// Gets the binding number of a texture.
///
/// For array of textures, the number of elements of the array, otherwise it should be 1
/// Indicates if the texture is a buffer texture
/// Binding number
- int CreateTextureBinding(int count, bool isBuffer);
+ SetBindingPair CreateTextureBinding(int count, bool isBuffer);
+
+ ///
+ /// Gets the set index for an additional set, or -1 if there's no extra set available.
+ ///
+ /// Extra set index, or -1 if not available
+ int CreateExtraSet()
+ {
+ return -1;
+ }
///
/// Queries Local Size X for compute shaders.
@@ -135,6 +144,7 @@ namespace Ryujinx.Graphics.Shader
default,
false,
false,
+ false,
false);
}
diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs
index 63ce38e251..c704156bcb 100644
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs
@@ -84,6 +84,10 @@ namespace Ryujinx.Graphics.Shader.Instructions
value = context.IConvertU32ToFP32(value);
}
}
+ else if (offset == AttributeConsts.PrimitiveId && context.TranslatorContext.Definitions.HalvePrimitiveId)
+ {
+ value = context.ShiftRightS32(value, Const(1));
+ }
context.Copy(Register(rd), value);
}
@@ -187,6 +191,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
}
}
+ else if (op.Imm10 == AttributeConsts.PrimitiveId && context.TranslatorContext.Definitions.HalvePrimitiveId)
+ {
+ // If quads are used, but the host does not support them, they need to be converted to triangles.
+ // Since each quad becomes 2 triangles, we need to compensate here and divide primitive ID by 2.
+ res = context.ShiftRightS32(res, Const(1));
+ }
else if (op.Imm10 == AttributeConsts.FrontFacing && context.TranslatorContext.GpuAccessor.QueryHostHasFrontFacingBug())
{
// gl_FrontFacing sometimes has incorrect (flipped) values depending how it is accessed on Intel GPUs.
diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs
index 630162ade5..1d8651254f 100644
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs
@@ -24,7 +24,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (op.BVal)
{
- context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0)));
+ context.Copy(dest, context.ConditionalSelect(res, ConstF(1), ConstF(0)));
}
else
{
diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitSurface.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitSurface.cs
index 0aac0ffa8c..383e82c697 100644
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitSurface.cs
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitSurface.cs
@@ -278,7 +278,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
flags |= TextureFlags.Bindless;
}
- int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageAtomic,
type,
format,
@@ -286,7 +286,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureOperation.DefaultCbufSlot,
imm);
- Operand res = context.ImageAtomic(type, format, flags, binding, sources);
+ Operand res = context.ImageAtomic(type, format, flags, setAndBinding, sources);
context.Copy(d, res);
}
@@ -389,7 +389,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureFormat format = isBindless ? TextureFormat.Unknown : ShaderProperties.GetTextureFormat(context.TranslatorContext.GpuAccessor, handle);
- int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageLoad,
type,
format,
@@ -397,7 +397,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureOperation.DefaultCbufSlot,
handle);
- context.ImageLoad(type, format, flags, binding, (int)componentMask, dests, sources);
+ context.ImageLoad(type, format, flags, setAndBinding, (int)componentMask, dests, sources);
}
else
{
@@ -432,7 +432,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureFormat format = GetTextureFormat(size);
- int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageLoad,
type,
format,
@@ -440,7 +440,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureOperation.DefaultCbufSlot,
handle);
- context.ImageLoad(type, format, flags, binding, compMask, dests, sources);
+ context.ImageLoad(type, format, flags, setAndBinding, compMask, dests, sources);
switch (size)
{
@@ -552,7 +552,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
flags |= TextureFlags.Bindless;
}
- int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageAtomic,
type,
format,
@@ -560,7 +560,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureOperation.DefaultCbufSlot,
imm);
- context.ImageAtomic(type, format, flags, binding, sources);
+ context.ImageAtomic(type, format, flags, setAndBinding, sources);
}
private static void EmitSust(
@@ -679,7 +679,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
flags |= TextureFlags.Coherent;
}
- int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageStore,
type,
format,
@@ -687,7 +687,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureOperation.DefaultCbufSlot,
handle);
- context.ImageStore(type, format, flags, binding, sources);
+ context.ImageStore(type, format, flags, setAndBinding, sources);
}
private static int GetComponentSizeInBytesLog2(SuatomSize size)
diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
index 06daa26a0a..2076262daf 100644
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
@@ -885,7 +885,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
return Register(dest++, RegisterType.Gpr);
}
- int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
Instruction.Lod,
type,
TextureFormat.Unknown,
@@ -913,7 +913,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
else
{
// The instruction component order is the inverse of GLSL's.
- Operand res = context.Lod(type, flags, binding, compIndex ^ 1, sources);
+ Operand res = context.Lod(type, flags, setAndBinding, compIndex ^ 1, sources);
res = context.FPMultiply(res, ConstF(256.0f));
@@ -1116,12 +1116,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
TextureFlags flags = isBindless ? TextureFlags.Bindless : TextureFlags.None;
- int binding;
+ SetBindingPair setAndBinding;
switch (query)
{
case TexQuery.TexHeaderDimension:
- binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
+ setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
Instruction.TextureQuerySize,
type,
TextureFormat.Unknown,
@@ -1140,13 +1140,13 @@ namespace Ryujinx.Graphics.Shader.Instructions
break;
}
- context.Copy(d, context.TextureQuerySize(type, flags, binding, compIndex, sources));
+ context.Copy(d, context.TextureQuerySize(type, flags, setAndBinding, compIndex, sources));
}
}
break;
case TexQuery.TexHeaderTextureType:
- binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
+ setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
Instruction.TextureQuerySamples,
type,
TextureFormat.Unknown,
@@ -1171,7 +1171,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (d != null)
{
- context.Copy(d, context.TextureQuerySamples(type, flags, binding, sources));
+ context.Copy(d, context.TextureQuerySamples(type, flags, setAndBinding, sources));
}
}
break;
@@ -1191,7 +1191,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
Operand[] dests,
Operand[] sources)
{
- int binding = flags.HasFlag(TextureFlags.Bindless) ? 0 : context.ResourceManager.GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = flags.HasFlag(TextureFlags.Bindless) ? default : context.ResourceManager.GetTextureOrImageBinding(
Instruction.TextureSample,
type,
TextureFormat.Unknown,
@@ -1199,7 +1199,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureOperation.DefaultCbufSlot,
handle);
- context.TextureSample(type, flags, binding, componentMask, dests, sources);
+ context.TextureSample(type, flags, setAndBinding, componentMask, dests, sources);
}
private static SamplerType ConvertSamplerType(TexDim dimensions)
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs
index 8703e660e0..273a38a5b6 100644
--- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs
@@ -156,6 +156,26 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
return false;
}
+ public static bool IsComparison(this Instruction inst)
+ {
+ switch (inst & Instruction.Mask)
+ {
+ case Instruction.CompareEqual:
+ case Instruction.CompareGreater:
+ case Instruction.CompareGreaterOrEqual:
+ case Instruction.CompareGreaterOrEqualU32:
+ case Instruction.CompareGreaterU32:
+ case Instruction.CompareLess:
+ case Instruction.CompareLessOrEqual:
+ case Instruction.CompareLessOrEqualU32:
+ case Instruction.CompareLessU32:
+ case Instruction.CompareNotEqual:
+ return true;
+ }
+
+ return false;
+ }
+
public static bool IsTextureQuery(this Instruction inst)
{
inst &= Instruction.Mask;
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs
index 74ec5ca611..7eee8f2e9c 100644
--- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs
@@ -8,7 +8,9 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
public TextureFormat Format { get; set; }
public TextureFlags Flags { get; private set; }
+ public int Set { get; private set; }
public int Binding { get; private set; }
+ public int SamplerSet { get; private set; }
public int SamplerBinding { get; private set; }
public TextureOperation(
@@ -16,6 +18,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
SamplerType type,
TextureFormat format,
TextureFlags flags,
+ int set,
int binding,
int compIndex,
Operand[] dests,
@@ -24,24 +27,28 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
Type = type;
Format = format;
Flags = flags;
+ Set = set;
Binding = binding;
+ SamplerSet = -1;
SamplerBinding = -1;
}
- public void TurnIntoArray(int binding)
+ public void TurnIntoArray(SetBindingPair setAndBinding)
{
Flags &= ~TextureFlags.Bindless;
- Binding = binding;
+ Set = setAndBinding.SetIndex;
+ Binding = setAndBinding.Binding;
}
- public void TurnIntoArray(int textureBinding, int samplerBinding)
+ public void TurnIntoArray(SetBindingPair textureSetAndBinding, SetBindingPair samplerSetAndBinding)
{
- TurnIntoArray(textureBinding);
+ TurnIntoArray(textureSetAndBinding);
- SamplerBinding = samplerBinding;
+ SamplerSet = samplerSetAndBinding.SetIndex;
+ SamplerBinding = samplerSetAndBinding.Binding;
}
- public void SetBinding(int binding)
+ public void SetBinding(SetBindingPair setAndBinding)
{
if ((Flags & TextureFlags.Bindless) != 0)
{
@@ -50,7 +57,8 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
RemoveSource(0);
}
- Binding = binding;
+ Set = setAndBinding.SetIndex;
+ Binding = setAndBinding.Binding;
}
public void SetLodLevelFlag()
diff --git a/src/Ryujinx.Graphics.Shader/SetBindingPair.cs b/src/Ryujinx.Graphics.Shader/SetBindingPair.cs
new file mode 100644
index 0000000000..1e8a4f9c61
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/SetBindingPair.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.Graphics.Shader
+{
+ public readonly record struct SetBindingPair(int SetIndex, int Binding);
+}
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
index 4068c41271..867cae8536 100644
--- a/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
@@ -8,7 +8,9 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public TextureFormat Format { get; }
public TextureFlags Flags { get; }
+ public int Set { get; }
public int Binding { get; }
+ public int SamplerSet { get; }
public int SamplerBinding { get; }
public bool IsSeparate => SamplerBinding >= 0;
@@ -18,7 +20,9 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
SamplerType type,
TextureFormat format,
TextureFlags flags,
+ int set,
int binding,
+ int samplerSet,
int samplerBinding,
int index,
params IAstNode[] sources) : base(inst, StorageKind.None, false, index, sources, sources.Length)
@@ -26,8 +30,20 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Type = type;
Format = format;
Flags = flags;
+ Set = set;
Binding = binding;
+ SamplerSet = samplerSet;
SamplerBinding = samplerBinding;
}
+
+ public SetBindingPair GetTextureSetAndBinding()
+ {
+ return new SetBindingPair(Set, Binding);
+ }
+
+ public SetBindingPair GetSamplerSetAndBinding()
+ {
+ return new SetBindingPair(SamplerSet, SamplerBinding);
+ }
}
}
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs
index 8c12c2aaf5..53ed6bfccd 100644
--- a/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs
@@ -6,15 +6,15 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
private readonly Dictionary _constantBuffers;
private readonly Dictionary _storageBuffers;
- private readonly Dictionary _textures;
- private readonly Dictionary _images;
+ private readonly Dictionary _textures;
+ private readonly Dictionary _images;
private readonly Dictionary _localMemories;
private readonly Dictionary _sharedMemories;
public IReadOnlyDictionary ConstantBuffers => _constantBuffers;
public IReadOnlyDictionary StorageBuffers => _storageBuffers;
- public IReadOnlyDictionary Textures => _textures;
- public IReadOnlyDictionary Images => _images;
+ public IReadOnlyDictionary Textures => _textures;
+ public IReadOnlyDictionary Images => _images;
public IReadOnlyDictionary LocalMemories => _localMemories;
public IReadOnlyDictionary SharedMemories => _sharedMemories;
@@ -22,8 +22,8 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
_constantBuffers = new Dictionary();
_storageBuffers = new Dictionary();
- _textures = new Dictionary();
- _images = new Dictionary();
+ _textures = new Dictionary();
+ _images = new Dictionary();
_localMemories = new Dictionary();
_sharedMemories = new Dictionary();
}
@@ -40,12 +40,12 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public void AddOrUpdateTexture(TextureDefinition definition)
{
- _textures[definition.Binding] = definition;
+ _textures[new(definition.Set, definition.Binding)] = definition;
}
public void AddOrUpdateImage(TextureDefinition definition)
{
- _images[definition.Binding] = definition;
+ _images[new(definition.Set, definition.Binding)] = definition;
}
public int AddLocalMemory(MemoryDefinition definition)
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
index c4ebaee73f..88053658dd 100644
--- a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
@@ -169,7 +169,17 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
AstTextureOperation GetAstTextureOperation(TextureOperation texOp)
{
- return new AstTextureOperation(inst, texOp.Type, texOp.Format, texOp.Flags, texOp.Binding, texOp.SamplerBinding, texOp.Index, sources);
+ return new AstTextureOperation(
+ inst,
+ texOp.Type,
+ texOp.Format,
+ texOp.Flags,
+ texOp.Set,
+ texOp.Binding,
+ texOp.SamplerSet,
+ texOp.SamplerBinding,
+ texOp.Index,
+ sources);
}
int componentsCount = BitOperations.PopCount((uint)operation.Index);
diff --git a/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs b/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs
index d287a1aa7b..1e387407d8 100644
--- a/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs
+++ b/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs
@@ -4,6 +4,7 @@ namespace Ryujinx.Graphics.Shader
{
// New fields should be added to the end of the struct to keep disk shader cache compatibility.
+ public readonly int Set;
public readonly int Binding;
public readonly SamplerType Type;
@@ -18,6 +19,7 @@ namespace Ryujinx.Graphics.Shader
public readonly TextureUsageFlags Flags;
public TextureDescriptor(
+ int set,
int binding,
SamplerType type,
TextureFormat format,
@@ -27,6 +29,7 @@ namespace Ryujinx.Graphics.Shader
bool separate,
TextureUsageFlags flags)
{
+ Set = set;
Binding = binding;
Type = type;
Format = format;
diff --git a/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs b/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
index e1157eea41..5e07b39f12 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
@@ -124,7 +124,7 @@ namespace Ryujinx.Graphics.Shader.Translation
this.TextureSample(
SamplerType.TextureBuffer,
TextureFlags.IntCoords,
- ResourceManager.Reservations.IndexBufferTextureBinding,
+ ResourceManager.Reservations.GetIndexBufferTextureSetAndBinding(),
1,
new[] { vertexIndexVr },
new[] { this.IAdd(ibBaseOffset, outputVertexOffset) });
@@ -145,7 +145,7 @@ namespace Ryujinx.Graphics.Shader.Translation
this.TextureSample(
SamplerType.TextureBuffer,
TextureFlags.IntCoords,
- ResourceManager.Reservations.TopologyRemapBufferTextureBinding,
+ ResourceManager.Reservations.GetTopologyRemapBufferTextureSetAndBinding(),
1,
new[] { vertexIndex },
new[] { this.IAdd(baseVertex, Const(index)) });
diff --git a/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs b/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs
index 9e314c6204..5bdbb0025a 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs
@@ -618,12 +618,21 @@ namespace Ryujinx.Graphics.Shader.Translation
SamplerType type,
TextureFormat format,
TextureFlags flags,
- int binding,
+ SetBindingPair setAndBinding,
Operand[] sources)
{
Operand dest = Local();
- context.Add(new TextureOperation(Instruction.ImageAtomic, type, format, flags, binding, 0, new[] { dest }, sources));
+ context.Add(new TextureOperation(
+ Instruction.ImageAtomic,
+ type,
+ format,
+ flags,
+ setAndBinding.SetIndex,
+ setAndBinding.Binding,
+ 0,
+ new[] { dest },
+ sources));
return dest;
}
@@ -633,12 +642,21 @@ namespace Ryujinx.Graphics.Shader.Translation
SamplerType type,
TextureFormat format,
TextureFlags flags,
- int binding,
+ SetBindingPair setAndBinding,
int compMask,
Operand[] dests,
Operand[] sources)
{
- context.Add(new TextureOperation(Instruction.ImageLoad, type, format, flags, binding, compMask, dests, sources));
+ context.Add(new TextureOperation(
+ Instruction.ImageLoad,
+ type,
+ format,
+ flags,
+ setAndBinding.SetIndex,
+ setAndBinding.Binding,
+ compMask,
+ dests,
+ sources));
}
public static void ImageStore(
@@ -646,10 +664,19 @@ namespace Ryujinx.Graphics.Shader.Translation
SamplerType type,
TextureFormat format,
TextureFlags flags,
- int binding,
+ SetBindingPair setAndBinding,
Operand[] sources)
{
- context.Add(new TextureOperation(Instruction.ImageStore, type, format, flags, binding, 0, null, sources));
+ context.Add(new TextureOperation(
+ Instruction.ImageStore,
+ type,
+ format,
+ flags,
+ setAndBinding.SetIndex,
+ setAndBinding.Binding,
+ 0,
+ null,
+ sources));
}
public static Operand IsNan(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32)
@@ -718,13 +745,22 @@ namespace Ryujinx.Graphics.Shader.Translation
this EmitterContext context,
SamplerType type,
TextureFlags flags,
- int binding,
+ SetBindingPair setAndBinding,
int compIndex,
Operand[] sources)
{
Operand dest = Local();
- context.Add(new TextureOperation(Instruction.Lod, type, TextureFormat.Unknown, flags, binding, compIndex, new[] { dest }, sources));
+ context.Add(new TextureOperation(
+ Instruction.Lod,
+ type,
+ TextureFormat.Unknown,
+ flags,
+ setAndBinding.SetIndex,
+ setAndBinding.Binding,
+ compIndex,
+ new[] { dest },
+ sources));
return dest;
}
@@ -889,24 +925,42 @@ namespace Ryujinx.Graphics.Shader.Translation
this EmitterContext context,
SamplerType type,
TextureFlags flags,
- int binding,
+ SetBindingPair setAndBinding,
int compMask,
Operand[] dests,
Operand[] sources)
{
- context.Add(new TextureOperation(Instruction.TextureSample, type, TextureFormat.Unknown, flags, binding, compMask, dests, sources));
+ context.Add(new TextureOperation(
+ Instruction.TextureSample,
+ type,
+ TextureFormat.Unknown,
+ flags,
+ setAndBinding.SetIndex,
+ setAndBinding.Binding,
+ compMask,
+ dests,
+ sources));
}
public static Operand TextureQuerySamples(
this EmitterContext context,
SamplerType type,
TextureFlags flags,
- int binding,
+ SetBindingPair setAndBinding,
Operand[] sources)
{
Operand dest = Local();
- context.Add(new TextureOperation(Instruction.TextureQuerySamples, type, TextureFormat.Unknown, flags, binding, 0, new[] { dest }, sources));
+ context.Add(new TextureOperation(
+ Instruction.TextureQuerySamples,
+ type,
+ TextureFormat.Unknown,
+ flags,
+ setAndBinding.SetIndex,
+ setAndBinding.Binding,
+ 0,
+ new[] { dest },
+ sources));
return dest;
}
@@ -915,13 +969,22 @@ namespace Ryujinx.Graphics.Shader.Translation
this EmitterContext context,
SamplerType type,
TextureFlags flags,
- int binding,
+ SetBindingPair setAndBinding,
int compIndex,
Operand[] sources)
{
Operand dest = Local();
- context.Add(new TextureOperation(Instruction.TextureQuerySize, type, TextureFormat.Unknown, flags, binding, compIndex, new[] { dest }, sources));
+ context.Add(new TextureOperation(
+ Instruction.TextureQuerySize,
+ type,
+ TextureFormat.Unknown,
+ flags,
+ setAndBinding.SetIndex,
+ setAndBinding.Binding,
+ compIndex,
+ new[] { dest },
+ sources));
return dest;
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs b/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs
index 2523272b0c..11fe6599db 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs
@@ -8,6 +8,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public readonly bool SupportsGeometryShaderPassthrough;
public readonly bool SupportsShaderBallot;
public readonly bool SupportsShaderBarrierDivergence;
+ public readonly bool SupportsShaderFloat64;
public readonly bool SupportsTextureShadowLod;
public readonly bool SupportsViewportMask;
@@ -18,6 +19,7 @@ namespace Ryujinx.Graphics.Shader.Translation
bool supportsGeometryShaderPassthrough,
bool supportsShaderBallot,
bool supportsShaderBarrierDivergence,
+ bool supportsShaderFloat64,
bool supportsTextureShadowLod,
bool supportsViewportMask)
{
@@ -27,6 +29,7 @@ namespace Ryujinx.Graphics.Shader.Translation
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
SupportsShaderBallot = supportsShaderBallot;
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
+ SupportsShaderFloat64 = supportsShaderFloat64;
SupportsTextureShadowLod = supportsTextureShadowLod;
SupportsViewportMask = supportsViewportMask;
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
index 223215439f..1f2f79a2dd 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
@@ -38,6 +38,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// If we can't do bindless elimination, remove the texture operation.
// Set any destination variables to zero.
+ string typeName = texOp.Inst.IsImage()
+ ? texOp.Type.ToGlslImageType(texOp.Format.GetComponentType())
+ : texOp.Type.ToGlslTextureType();
+
+ gpuAccessor.Log($"Failed to find handle source for bindless access of type \"{typeName}\".");
+
for (int destIndex = 0; destIndex < texOp.DestsCount; destIndex++)
{
block.Operations.AddBefore(node, new Operation(Instruction.Copy, texOp.GetDest(destIndex), OperandHelper.Const(0)));
@@ -62,17 +68,22 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return false;
}
- Operand nvHandle = texOp.GetSource(0);
+ Operand bindlessHandle = texOp.GetSource(0);
- if (nvHandle.AsgOp is not Operation handleOp ||
- handleOp.Inst != Instruction.Load ||
- handleOp.StorageKind != StorageKind.Input)
+ if (bindlessHandle.AsgOp is PhiNode phi)
{
- // Right now, we only allow bindless access when the handle comes from a shader input.
- // This is an artificial limitation to prevent it from being used in cases where it
- // would have a large performance impact of loading all textures in the pool.
- // It might be removed in the future, if we can mitigate the performance impact.
+ for (int srcIndex = 0; srcIndex < phi.SourcesCount; srcIndex++)
+ {
+ Operand phiSource = phi.GetSource(srcIndex);
+ if (phiSource.AsgOp is not PhiNode && !IsBindlessAccessAllowed(phiSource))
+ {
+ return false;
+ }
+ }
+ }
+ else if (!IsBindlessAccessAllowed(bindlessHandle))
+ {
return false;
}
@@ -80,8 +91,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
Operand samplerHandle = OperandHelper.Local();
Operand textureIndex = OperandHelper.Local();
- block.Operations.AddBefore(node, new Operation(Instruction.BitwiseAnd, textureHandle, nvHandle, OperandHelper.Const(0xfffff)));
- block.Operations.AddBefore(node, new Operation(Instruction.ShiftRightU32, samplerHandle, nvHandle, OperandHelper.Const(20)));
+ block.Operations.AddBefore(node, new Operation(Instruction.BitwiseAnd, textureHandle, bindlessHandle, OperandHelper.Const(0xfffff)));
+ block.Operations.AddBefore(node, new Operation(Instruction.ShiftRightU32, samplerHandle, bindlessHandle, OperandHelper.Const(20)));
int texturePoolLength = Math.Max(BindlessToArray.MinimumArrayLength, gpuAccessor.QueryTextureArrayLengthFromPool());
@@ -91,7 +102,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
bool hasSampler = !texOp.Inst.IsImage();
- int textureBinding = resourceManager.GetTextureOrImageBinding(
+ SetBindingPair textureSetAndBinding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type,
texOp.Format,
@@ -111,7 +122,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
texOp.InsertSource(1, samplerIndex);
- int samplerBinding = resourceManager.GetTextureOrImageBinding(
+ SetBindingPair samplerSetAndBinding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
SamplerType.None,
texOp.Format,
@@ -120,11 +131,35 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
TextureHandle.PackOffsets(0, 0, TextureHandleType.Direct),
samplerPoolLength);
- texOp.TurnIntoArray(textureBinding, samplerBinding);
+ texOp.TurnIntoArray(textureSetAndBinding, samplerSetAndBinding);
}
else
{
- texOp.TurnIntoArray(textureBinding);
+ texOp.TurnIntoArray(textureSetAndBinding);
+ }
+
+ return true;
+ }
+
+ private static bool IsBindlessAccessAllowed(Operand bindlessHandle)
+ {
+ if (bindlessHandle.Type == OperandType.ConstantBuffer)
+ {
+ // Bindless access with handles from constant buffer is allowed.
+
+ return true;
+ }
+
+ if (bindlessHandle.AsgOp is not Operation handleOp ||
+ handleOp.Inst != Instruction.Load ||
+ (handleOp.StorageKind != StorageKind.Input && handleOp.StorageKind != StorageKind.StorageBuffer))
+ {
+ // Right now, we only allow bindless access when the handle comes from a shader input or storage buffer.
+ // This is an artificial limitation to prevent it from being used in cases where it
+ // would have a large performance impact of loading all textures in the pool.
+ // It might be removed in the future, if we can mitigate the performance impact.
+
+ return false;
}
return true;
@@ -265,7 +300,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
resourceManager,
gpuAccessor,
texOp,
- TextureHandle.PackOffsets(src0.GetCbufOffset(), ((src1.Value >> 20) & 0xfff), handleType),
+ TextureHandle.PackOffsets(src0.GetCbufOffset(), (src1.Value >> 20) & 0xfff, handleType),
TextureHandle.PackSlots(src0.GetCbufSlot(), 0),
rewriteSamplerType,
isImage: false);
@@ -445,7 +480,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
}
- int binding = resourceManager.GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type,
texOp.Format,
@@ -453,7 +488,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
cbufSlot,
cbufOffset);
- texOp.SetBinding(binding);
+ texOp.SetBinding(setAndBinding);
}
}
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs
index f2be7975da..1e0b3b645a 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs
@@ -126,7 +126,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue;
}
- if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp)
+ Operand bindlessHandle = Utils.FindLastOperation(texOp.GetSource(0), block);
+
+ if (bindlessHandle.AsgOp is not Operation handleAsgOp)
{
continue;
}
@@ -137,8 +139,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (handleAsgOp.Inst == Instruction.BitwiseOr)
{
- Operand src0 = handleAsgOp.GetSource(0);
- Operand src1 = handleAsgOp.GetSource(1);
+ Operand src0 = Utils.FindLastOperation(handleAsgOp.GetSource(0), block);
+ Operand src1 = Utils.FindLastOperation(handleAsgOp.GetSource(1), block);
if (src0.Type == OperandType.ConstantBuffer && src1.AsgOp is Operation)
{
@@ -221,7 +223,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
private static void TurnIntoArray(ResourceManager resourceManager, TextureOperation texOp, int cbufSlot, int handleIndex, int length)
{
- int binding = resourceManager.GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type,
texOp.Format,
@@ -230,7 +232,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
handleIndex,
length);
- texOp.TurnIntoArray(binding);
+ texOp.TurnIntoArray(setAndBinding);
}
}
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs
index 49eb3a89b3..1be7c5c520 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs
@@ -152,18 +152,14 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
// If all phi sources are the same, we can propagate it and remove the phi.
- Operand firstSrc = phi.GetSource(0);
-
- for (int index = 1; index < phi.SourcesCount; index++)
+ if (!Utils.AreAllSourcesTheSameOperand(phi))
{
- if (!IsSameOperand(firstSrc, phi.GetSource(index)))
- {
- return false;
- }
+ return false;
}
// All sources are equal, we can propagate the value.
+ Operand firstSrc = phi.GetSource(0);
Operand dest = phi.Dest;
INode[] uses = dest.UseOps.ToArray();
@@ -182,17 +178,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return true;
}
- private static bool IsSameOperand(Operand x, Operand y)
- {
- if (x.Type != y.Type || x.Value != y.Value)
- {
- return false;
- }
-
- // TODO: Handle Load operations with the same storage and the same constant parameters.
- return x.Type == OperandType.Constant || x.Type == OperandType.ConstantBuffer;
- }
-
private static bool PropagatePack(Operation packOp)
{
// Propagate pack source operands to uses by unpack
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs
index a509fcb425..097c8aa88c 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs
@@ -31,6 +31,10 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
TryEliminateBitwiseOr(operation);
break;
+ case Instruction.CompareNotEqual:
+ TryEliminateCompareNotEqual(operation);
+ break;
+
case Instruction.ConditionalSelect:
TryEliminateConditionalSelect(operation);
break;
@@ -174,6 +178,32 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
}
+ private static void TryEliminateCompareNotEqual(Operation operation)
+ {
+ // Comparison instruction returns 0 if the result is false, and -1 if true.
+ // Doing a not equal zero comparison on the result is redundant, so we can just copy the first result in this case.
+
+ Operand lhs = operation.GetSource(0);
+ Operand rhs = operation.GetSource(1);
+
+ if (lhs.Type == OperandType.Constant)
+ {
+ (lhs, rhs) = (rhs, lhs);
+ }
+
+ if (rhs.Type != OperandType.Constant || rhs.Value != 0)
+ {
+ return;
+ }
+
+ if (lhs.AsgOp is not Operation compareOp || !compareOp.Inst.IsComparison())
+ {
+ return;
+ }
+
+ operation.TurnIntoCopy(lhs);
+ }
+
private static void TryEliminateConditionalSelect(Operation operation)
{
Operand cond = operation.GetSource(0);
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs
index 74a6d5a1e7..23180ff825 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs
@@ -34,6 +34,50 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return elemIndexSrc.Type == OperandType.Constant && elemIndexSrc.Value == elemIndex;
}
+ private static bool IsSameOperand(Operand x, Operand y)
+ {
+ if (x.Type != y.Type || x.Value != y.Value)
+ {
+ return false;
+ }
+
+ // TODO: Handle Load operations with the same storage and the same constant parameters.
+ return x == y || x.Type == OperandType.Constant || x.Type == OperandType.ConstantBuffer;
+ }
+
+ private static bool AreAllSourcesEqual(INode node, INode otherNode)
+ {
+ if (node.SourcesCount != otherNode.SourcesCount)
+ {
+ return false;
+ }
+
+ for (int index = 0; index < node.SourcesCount; index++)
+ {
+ if (!IsSameOperand(node.GetSource(index), otherNode.GetSource(index)))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static bool AreAllSourcesTheSameOperand(INode node)
+ {
+ Operand firstSrc = node.GetSource(0);
+
+ for (int index = 1; index < node.SourcesCount; index++)
+ {
+ if (!IsSameOperand(firstSrc, node.GetSource(index)))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
private static Operation FindBranchSource(BasicBlock block)
{
foreach (BasicBlock sourceBlock in block.Predecessors)
@@ -55,6 +99,19 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return inst == Instruction.BranchIfFalse || inst == Instruction.BranchIfTrue;
}
+ private static bool IsSameCondition(Operand currentCondition, Operand queryCondition)
+ {
+ if (currentCondition == queryCondition)
+ {
+ return true;
+ }
+
+ return currentCondition.AsgOp is Operation currentOperation &&
+ queryCondition.AsgOp is Operation queryOperation &&
+ currentOperation.Inst == queryOperation.Inst &&
+ AreAllSourcesEqual(currentOperation, queryOperation);
+ }
+
private static bool BlockConditionsMatch(BasicBlock currentBlock, BasicBlock queryBlock)
{
// Check if all the conditions for the query block are satisfied by the current block.
@@ -70,10 +127,10 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return currentBranch != null && queryBranch != null &&
currentBranch.Inst == queryBranch.Inst &&
- currentCondition == queryCondition;
+ IsSameCondition(currentCondition, queryCondition);
}
- public static Operand FindLastOperation(Operand source, BasicBlock block)
+ public static Operand FindLastOperation(Operand source, BasicBlock block, bool recurse = true)
{
if (source.AsgOp is PhiNode phiNode)
{
@@ -84,10 +141,23 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
for (int i = phiNode.SourcesCount - 1; i >= 0; i--)
{
BasicBlock phiBlock = phiNode.GetBlock(i);
+ Operand phiSource = phiNode.GetSource(i);
if (BlockConditionsMatch(block, phiBlock))
{
- return phiNode.GetSource(i);
+ return phiSource;
+ }
+ else if (recurse && phiSource.AsgOp is PhiNode)
+ {
+ // Phi source is another phi.
+ // Let's check if that phi has a block that matches our condition.
+
+ Operand match = FindLastOperation(phiSource, block, false);
+
+ if (match != phiSource)
+ {
+ return match;
+ }
}
}
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/RegisterUsage.cs b/src/Ryujinx.Graphics.Shader/Translation/RegisterUsage.cs
index e27e47070a..1c724223c4 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/RegisterUsage.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/RegisterUsage.cs
@@ -155,9 +155,14 @@ namespace Ryujinx.Graphics.Shader.Translation
localInputs[block.Index] |= GetMask(register) & ~localOutputs[block.Index];
}
- if (operation.Dest != null && operation.Dest.Type == OperandType.Register)
+ for (int dstIndex = 0; dstIndex < operation.DestsCount; dstIndex++)
{
- localOutputs[block.Index] |= GetMask(operation.Dest.GetRegister());
+ Operand dest = operation.GetDest(dstIndex);
+
+ if (dest != null && dest.Type == OperandType.Register)
+ {
+ localOutputs[block.Index] |= GetMask(dest.GetRegister());
+ }
}
}
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs
index 890501c919..94691a5b4c 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs
@@ -20,8 +20,8 @@ namespace Ryujinx.Graphics.Shader.Translation
private readonly ShaderStage _stage;
private readonly string _stagePrefix;
- private readonly int[] _cbSlotToBindingMap;
- private readonly int[] _sbSlotToBindingMap;
+ private readonly SetBindingPair[] _cbSlotToBindingMap;
+ private readonly SetBindingPair[] _sbSlotToBindingMap;
private uint _sbSlotWritten;
private readonly Dictionary _sbSlots;
@@ -33,6 +33,7 @@ namespace Ryujinx.Graphics.Shader.Translation
private struct TextureMeta
{
+ public int Set;
public int Binding;
public bool AccurateType;
public SamplerType Type;
@@ -64,10 +65,10 @@ namespace Ryujinx.Graphics.Shader.Translation
_stage = stage;
_stagePrefix = GetShaderStagePrefix(stage);
- _cbSlotToBindingMap = new int[18];
- _sbSlotToBindingMap = new int[16];
- _cbSlotToBindingMap.AsSpan().Fill(-1);
- _sbSlotToBindingMap.AsSpan().Fill(-1);
+ _cbSlotToBindingMap = new SetBindingPair[18];
+ _sbSlotToBindingMap = new SetBindingPair[16];
+ _cbSlotToBindingMap.AsSpan().Fill(new(-1, -1));
+ _sbSlotToBindingMap.AsSpan().Fill(new(-1, -1));
_sbSlots = new();
_sbSlotsReverse = new();
@@ -146,16 +147,16 @@ namespace Ryujinx.Graphics.Shader.Translation
public int GetConstantBufferBinding(int slot)
{
- int binding = _cbSlotToBindingMap[slot];
- if (binding < 0)
+ SetBindingPair setAndBinding = _cbSlotToBindingMap[slot];
+ if (setAndBinding.Binding < 0)
{
- binding = _gpuAccessor.CreateConstantBufferBinding(slot);
- _cbSlotToBindingMap[slot] = binding;
+ setAndBinding = _gpuAccessor.CreateConstantBufferBinding(slot);
+ _cbSlotToBindingMap[slot] = setAndBinding;
string slotNumber = slot.ToString(CultureInfo.InvariantCulture);
- AddNewConstantBuffer(binding, $"{_stagePrefix}_c{slotNumber}");
+ AddNewConstantBuffer(setAndBinding.SetIndex, setAndBinding.Binding, $"{_stagePrefix}_c{slotNumber}");
}
- return binding;
+ return setAndBinding.Binding;
}
public bool TryGetStorageBufferBinding(int sbCbSlot, int sbCbOffset, bool write, out int binding)
@@ -166,14 +167,14 @@ namespace Ryujinx.Graphics.Shader.Translation
return false;
}
- binding = _sbSlotToBindingMap[slot];
+ SetBindingPair setAndBinding = _sbSlotToBindingMap[slot];
- if (binding < 0)
+ if (setAndBinding.Binding < 0)
{
- binding = _gpuAccessor.CreateStorageBufferBinding(slot);
- _sbSlotToBindingMap[slot] = binding;
+ setAndBinding = _gpuAccessor.CreateStorageBufferBinding(slot);
+ _sbSlotToBindingMap[slot] = setAndBinding;
string slotNumber = slot.ToString(CultureInfo.InvariantCulture);
- AddNewStorageBuffer(binding, $"{_stagePrefix}_s{slotNumber}");
+ AddNewStorageBuffer(setAndBinding.SetIndex, setAndBinding.Binding, $"{_stagePrefix}_s{slotNumber}");
}
if (write)
@@ -181,6 +182,7 @@ namespace Ryujinx.Graphics.Shader.Translation
_sbSlotWritten |= 1u << slot;
}
+ binding = setAndBinding.Binding;
return true;
}
@@ -208,7 +210,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
for (slot = 0; slot < _cbSlotToBindingMap.Length; slot++)
{
- if (_cbSlotToBindingMap[slot] == binding)
+ if (_cbSlotToBindingMap[slot].Binding == binding)
{
return true;
}
@@ -218,7 +220,7 @@ namespace Ryujinx.Graphics.Shader.Translation
return false;
}
- public int GetTextureOrImageBinding(
+ public SetBindingPair GetTextureOrImageBinding(
Instruction inst,
SamplerType type,
TextureFormat format,
@@ -240,7 +242,7 @@ namespace Ryujinx.Graphics.Shader.Translation
format = TextureFormat.Unknown;
}
- int binding = GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = GetTextureOrImageBinding(
cbufSlot,
handle,
arrayLength,
@@ -255,10 +257,10 @@ namespace Ryujinx.Graphics.Shader.Translation
_gpuAccessor.RegisterTexture(handle, cbufSlot);
- return binding;
+ return setAndBinding;
}
- private int GetTextureOrImageBinding(
+ private SetBindingPair GetTextureOrImageBinding(
int cbufSlot,
int handle,
int arrayLength,
@@ -311,21 +313,38 @@ namespace Ryujinx.Graphics.Shader.Translation
UsageFlags = usageFlags,
};
+ int setIndex;
int binding;
if (dict.TryGetValue(info, out var existingMeta))
{
dict[info] = MergeTextureMeta(meta, existingMeta);
+ setIndex = existingMeta.Set;
binding = existingMeta.Binding;
}
else
{
- bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer;
+ if (arrayLength > 1 && (setIndex = _gpuAccessor.CreateExtraSet()) >= 0)
+ {
+ // We reserved an "extra set" for the array.
+ // In this case the binding is always the first one (0).
+ // Using separate sets for array is better as we need to do less descriptor set updates.
- binding = isImage
- ? _gpuAccessor.CreateImageBinding(arrayLength, isBuffer)
- : _gpuAccessor.CreateTextureBinding(arrayLength, isBuffer);
+ binding = 0;
+ }
+ else
+ {
+ bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer;
+ SetBindingPair setAndBinding = isImage
+ ? _gpuAccessor.CreateImageBinding(arrayLength, isBuffer)
+ : _gpuAccessor.CreateTextureBinding(arrayLength, isBuffer);
+
+ setIndex = setAndBinding.SetIndex;
+ binding = setAndBinding.Binding;
+ }
+
+ meta.Set = setIndex;
meta.Binding = binding;
dict.Add(info, meta);
@@ -355,7 +374,7 @@ namespace Ryujinx.Graphics.Shader.Translation
}
var definition = new TextureDefinition(
- isImage ? 3 : 2,
+ setIndex,
binding,
arrayLength,
separate,
@@ -373,11 +392,12 @@ namespace Ryujinx.Graphics.Shader.Translation
Properties.AddOrUpdateTexture(definition);
}
- return binding;
+ return new SetBindingPair(setIndex, binding);
}
private static TextureMeta MergeTextureMeta(TextureMeta meta, TextureMeta existingMeta)
{
+ meta.Set = existingMeta.Set;
meta.Binding = existingMeta.Binding;
meta.UsageFlags |= existingMeta.UsageFlags;
@@ -440,11 +460,11 @@ namespace Ryujinx.Graphics.Shader.Translation
for (int slot = 0; slot < _cbSlotToBindingMap.Length; slot++)
{
- int binding = _cbSlotToBindingMap[slot];
+ SetBindingPair setAndBinding = _cbSlotToBindingMap[slot];
- if (binding >= 0 && _usedConstantBufferBindings.Contains(binding))
+ if (setAndBinding.Binding >= 0 && _usedConstantBufferBindings.Contains(setAndBinding.Binding))
{
- descriptors[descriptorIndex++] = new BufferDescriptor(binding, slot);
+ descriptors[descriptorIndex++] = new BufferDescriptor(setAndBinding.SetIndex, setAndBinding.Binding, slot);
}
}
@@ -464,13 +484,13 @@ namespace Ryujinx.Graphics.Shader.Translation
foreach ((int key, int slot) in _sbSlots)
{
- int binding = _sbSlotToBindingMap[slot];
+ SetBindingPair setAndBinding = _sbSlotToBindingMap[slot];
- if (binding >= 0)
+ if (setAndBinding.Binding >= 0)
{
(int sbCbSlot, int sbCbOffset) = UnpackSbCbInfo(key);
BufferUsageFlags flags = (_sbSlotWritten & (1u << slot)) != 0 ? BufferUsageFlags.Write : BufferUsageFlags.None;
- descriptors[descriptorIndex++] = new BufferDescriptor(binding, slot, sbCbSlot, sbCbOffset, flags);
+ descriptors[descriptorIndex++] = new BufferDescriptor(setAndBinding.SetIndex, setAndBinding.Binding, slot, sbCbSlot, sbCbOffset, flags);
}
}
@@ -507,6 +527,7 @@ namespace Ryujinx.Graphics.Shader.Translation
}
descriptors.Add(new TextureDescriptor(
+ meta.Set,
meta.Binding,
meta.Type,
info.Format,
@@ -527,6 +548,7 @@ namespace Ryujinx.Graphics.Shader.Translation
}
descriptors.Add(new TextureDescriptor(
+ meta.Set,
meta.Binding,
meta.Type,
info.Format,
@@ -587,24 +609,24 @@ namespace Ryujinx.Graphics.Shader.Translation
return false;
}
- private void AddNewConstantBuffer(int binding, string name)
+ private void AddNewConstantBuffer(int setIndex, int binding, string name)
{
StructureType type = new(new[]
{
new StructureField(AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32, "data", Constants.ConstantBufferSize / 16),
});
- Properties.AddOrUpdateConstantBuffer(new(BufferLayout.Std140, 0, binding, name, type));
+ Properties.AddOrUpdateConstantBuffer(new(BufferLayout.Std140, setIndex, binding, name, type));
}
- private void AddNewStorageBuffer(int binding, string name)
+ private void AddNewStorageBuffer(int setIndex, int binding, string name)
{
StructureType type = new(new[]
{
new StructureField(AggregateType.Array | AggregateType.U32, "data", 0),
});
- Properties.AddOrUpdateStorageBuffer(new(BufferLayout.Std430, 1, binding, name, type));
+ Properties.AddOrUpdateStorageBuffer(new(BufferLayout.Std430, setIndex, binding, name, type));
}
public static string GetShaderStagePrefix(ShaderStage stage)
diff --git a/src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs b/src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs
index d559f66990..c89c4d0b66 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs
@@ -11,6 +11,8 @@ namespace Ryujinx.Graphics.Shader.Translation
public const int MaxVertexBufferTextures = 32;
+ private const int TextureSetIndex = 2; // TODO: Get from GPU accessor.
+
public int VertexInfoConstantBufferBinding { get; }
public int VertexOutputStorageBufferBinding { get; }
public int GeometryVertexOutputStorageBufferBinding { get; }
@@ -163,6 +165,21 @@ namespace Ryujinx.Graphics.Shader.Translation
return _vertexBufferTextureBaseBinding + vaLocation;
}
+ public SetBindingPair GetVertexBufferTextureSetAndBinding(int vaLocation)
+ {
+ return new SetBindingPair(TextureSetIndex, GetVertexBufferTextureBinding(vaLocation));
+ }
+
+ public SetBindingPair GetIndexBufferTextureSetAndBinding()
+ {
+ return new SetBindingPair(TextureSetIndex, IndexBufferTextureBinding);
+ }
+
+ public SetBindingPair GetTopologyRemapBufferTextureSetAndBinding()
+ {
+ return new SetBindingPair(TextureSetIndex, TopologyRemapBufferTextureBinding);
+ }
+
internal bool TryGetOffset(StorageKind storageKind, int location, int component, out int offset)
{
return _offsets.TryGetValue(new IoDefinition(storageKind, IoVariable.UserDefined, location, component), out offset);
diff --git a/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs b/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs
index 3246e25940..f831ec940c 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs
@@ -45,6 +45,8 @@ namespace Ryujinx.Graphics.Shader.Translation
public bool YNegateEnabled => _graphicsState.YNegateEnabled;
public bool OriginUpperLeft => _graphicsState.OriginUpperLeft;
+ public bool HalvePrimitiveId => _graphicsState.HalvePrimitiveId;
+
public ImapPixelType[] ImapTypes { get; }
public bool IaIndexing { get; private set; }
public bool OaIndexing { get; private set; }
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs
index 072b456955..6ba8cb44ad 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs
@@ -182,6 +182,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
texOp.Type,
texOp.Format,
texOp.Flags,
+ texOp.Set,
texOp.Binding,
index,
new[] { coordSize },
@@ -251,6 +252,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
texOp.Type,
texOp.Format,
texOp.Flags,
+ texOp.Set,
texOp.Binding,
index,
new[] { coordSize },
@@ -471,6 +473,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
texOp.Type,
texOp.Format,
texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets),
+ texOp.Set,
texOp.Binding,
1 << 3, // W component: i=0, j=0
new[] { dests[destIndex++] },
@@ -527,6 +530,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
texOp.Type,
texOp.Format,
texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets),
+ texOp.Set,
texOp.Binding,
componentIndex,
dests,
@@ -573,6 +577,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
texOp.Type,
texOp.Format,
texOp.Flags,
+ texOp.Set,
texOp.Binding,
index,
new[] { texSizes[index] },
@@ -603,6 +608,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
texOp.Type,
texOp.Format,
texOp.Flags,
+ texOp.Set,
texOp.Binding,
0,
new[] { lod },
@@ -633,6 +639,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
texOp.Type,
texOp.Format,
texOp.Flags,
+ texOp.Set,
texOp.Binding,
index,
new[] { texSizes[index] },
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/VertexToCompute.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/VertexToCompute.cs
index d71ada8658..ddd2134d2e 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Transforms/VertexToCompute.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/VertexToCompute.cs
@@ -54,6 +54,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
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);
@@ -62,7 +63,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
SamplerType.TextureBuffer,
TextureFormat.Unknown,
TextureFlags.IntCoords,
- context.ResourceManager.Reservations.GetVertexBufferTextureBinding(location),
+ setAndBinding.SetIndex,
+ setAndBinding.Binding,
1 << component,
new[] { temp },
new[] { vertexElemOffset }));
@@ -75,6 +77,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
}
else
{
+ SetBindingPair setAndBinding = context.ResourceManager.Reservations.GetVertexBufferTextureSetAndBinding(location);
Operand temp = component > 0 ? Local() : dest;
Operand vertexElemOffset = GenerateVertexOffset(context.ResourceManager, node, location, component);
@@ -83,7 +86,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
SamplerType.TextureBuffer,
TextureFormat.Unknown,
TextureFlags.IntCoords,
- context.ResourceManager.Reservations.GetVertexBufferTextureBinding(location),
+ setAndBinding.SetIndex,
+ setAndBinding.Binding,
1,
new[] { temp },
new[] { vertexElemOffset }));
diff --git a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
index 1065355886..a579433f92 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
@@ -363,6 +363,7 @@ namespace Ryujinx.Graphics.Shader.Translation
GpuAccessor.QueryHostSupportsGeometryShaderPassthrough(),
GpuAccessor.QueryHostSupportsShaderBallot(),
GpuAccessor.QueryHostSupportsShaderBarrierDivergence(),
+ GpuAccessor.QueryHostSupportsShaderFloat64(),
GpuAccessor.QueryHostSupportsTextureShadowLod(),
GpuAccessor.QueryHostSupportsViewportMask());
@@ -412,8 +413,8 @@ namespace Ryujinx.Graphics.Shader.Translation
if (Stage == ShaderStage.Vertex)
{
- int ibBinding = resourceManager.Reservations.IndexBufferTextureBinding;
- TextureDefinition indexBuffer = new(2, ibBinding, "ib_data", SamplerType.TextureBuffer);
+ SetBindingPair ibSetAndBinding = resourceManager.Reservations.GetIndexBufferTextureSetAndBinding();
+ TextureDefinition indexBuffer = new(ibSetAndBinding.SetIndex, ibSetAndBinding.Binding, "ib_data", SamplerType.TextureBuffer);
resourceManager.Properties.AddOrUpdateTexture(indexBuffer);
int inputMap = _program.AttributeUsage.UsedInputAttributes;
@@ -421,8 +422,8 @@ namespace Ryujinx.Graphics.Shader.Translation
while (inputMap != 0)
{
int location = BitOperations.TrailingZeroCount(inputMap);
- int binding = resourceManager.Reservations.GetVertexBufferTextureBinding(location);
- TextureDefinition vaBuffer = new(2, binding, $"vb_data{location}", SamplerType.TextureBuffer);
+ SetBindingPair setAndBinding = resourceManager.Reservations.GetVertexBufferTextureSetAndBinding(location);
+ TextureDefinition vaBuffer = new(setAndBinding.SetIndex, setAndBinding.Binding, $"vb_data{location}", SamplerType.TextureBuffer);
resourceManager.Properties.AddOrUpdateTexture(vaBuffer);
inputMap &= ~(1 << location);
@@ -430,8 +431,8 @@ namespace Ryujinx.Graphics.Shader.Translation
}
else if (Stage == ShaderStage.Geometry)
{
- int trbBinding = resourceManager.Reservations.TopologyRemapBufferTextureBinding;
- TextureDefinition remapBuffer = new(2, trbBinding, "trb_data", SamplerType.TextureBuffer);
+ SetBindingPair trbSetAndBinding = resourceManager.Reservations.GetTopologyRemapBufferTextureSetAndBinding();
+ TextureDefinition remapBuffer = new(trbSetAndBinding.SetIndex, trbSetAndBinding.Binding, "trb_data", SamplerType.TextureBuffer);
resourceManager.Properties.AddOrUpdateTexture(remapBuffer);
int geometryVbOutputSbBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding;
diff --git a/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs b/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs
index 3f65e1225b..92e39d2e06 100644
--- a/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs
+++ b/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs
@@ -1,7 +1,6 @@
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using System;
-using System.Buffers;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
@@ -293,9 +292,9 @@ namespace Ryujinx.Graphics.Texture.Astc
int depth,
int levels,
int layers,
- out IMemoryOwner decoded)
+ out MemoryOwner decoded)
{
- decoded = ByteMemoryPool.Rent(QueryDecompressedSize(width, height, depth, levels, layers));
+ decoded = MemoryOwner.Rent(QueryDecompressedSize(width, height, depth, levels, layers));
AstcDecoder decoder = new(data, decoded.Memory, blockWidth, blockHeight, width, height, depth, levels, layers);
diff --git a/src/Ryujinx.Graphics.Texture/BCnDecoder.cs b/src/Ryujinx.Graphics.Texture/BCnDecoder.cs
index eb85334a21..d7b1f0fa9d 100644
--- a/src/Ryujinx.Graphics.Texture/BCnDecoder.cs
+++ b/src/Ryujinx.Graphics.Texture/BCnDecoder.cs
@@ -1,7 +1,6 @@
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using System;
-using System.Buffers;
using System.Buffers.Binary;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
@@ -14,7 +13,7 @@ namespace Ryujinx.Graphics.Texture
private const int BlockWidth = 4;
private const int BlockHeight = 4;
- public static IMemoryOwner DecodeBC1(ReadOnlySpan data, int width, int height, int depth, int levels, int layers)
+ public static MemoryOwner DecodeBC1(ReadOnlySpan data, int width, int height, int depth, int levels, int layers)
{
int size = 0;
@@ -23,12 +22,12 @@ namespace Ryujinx.Graphics.Texture
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4;
}
- IMemoryOwner output = ByteMemoryPool.Rent(size);
+ MemoryOwner output = MemoryOwner.Rent(size);
Span tile = stackalloc byte[BlockWidth * BlockHeight * 4];
Span tileAsUint = MemoryMarshal.Cast(tile);
- Span outputAsUint = MemoryMarshal.Cast(output.Memory.Span);
+ Span outputAsUint = MemoryMarshal.Cast(output.Span);
Span> tileAsVector128 = MemoryMarshal.Cast>(tile);
@@ -102,7 +101,7 @@ namespace Ryujinx.Graphics.Texture
return output;
}
- public static IMemoryOwner DecodeBC2(ReadOnlySpan data, int width, int height, int depth, int levels, int layers)
+ public static MemoryOwner DecodeBC2(ReadOnlySpan data, int width, int height, int depth, int levels, int layers)
{
int size = 0;
@@ -111,12 +110,12 @@ namespace Ryujinx.Graphics.Texture
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4;
}
- IMemoryOwner output = ByteMemoryPool.Rent(size);
+ MemoryOwner output = MemoryOwner.Rent(size);
Span tile = stackalloc byte[BlockWidth * BlockHeight * 4];
Span tileAsUint = MemoryMarshal.Cast(tile);
- Span outputAsUint = MemoryMarshal.Cast(output.Memory.Span);
+ Span outputAsUint = MemoryMarshal.Cast(output.Span);
Span> tileAsVector128 = MemoryMarshal.Cast>(tile);
@@ -197,7 +196,7 @@ namespace Ryujinx.Graphics.Texture
return output;
}
- public static IMemoryOwner DecodeBC3(ReadOnlySpan data, int width, int height, int depth, int levels, int layers)
+ public static MemoryOwner DecodeBC3(ReadOnlySpan data, int width, int height, int depth, int levels, int layers)
{
int size = 0;
@@ -206,13 +205,13 @@ namespace Ryujinx.Graphics.Texture
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4;
}
- IMemoryOwner output = ByteMemoryPool.Rent(size);
+ MemoryOwner