Compare commits
43 Commits
1.1.1343
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
363a1ab68e | |||
ca59c3f499 | |||
fdd7ee791c | |||
398fa1c238 | |||
2c5c0392f9 | |||
e0acde04bb | |||
3c61d560c3 | |||
b45a81458a | |||
460f9faf4e | |||
552c15739c | |||
0137c9e635 | |||
23fa5f4c9c | |||
4f75e26ec7 | |||
8d8983049e | |||
7969fb6bba | |||
4a4b11871e | |||
e85ee673b1 | |||
42f22fe5d7 | |||
263eb97f79 | |||
3004902257 | |||
59ddb26628 | |||
83fda10f6e | |||
d97e995e59 | |||
56b2f84702 | |||
698e36bbd2 | |||
6ce49a2dc7 | |||
ccd330ba0f | |||
95d252b7b8 | |||
add681144b | |||
c6dc00815a | |||
99f04ac1a6 | |||
ce09450743 | |||
2cb80f37d4 | |||
827069e784 | |||
1a919e99b2 | |||
f77bebac80 | |||
6fbf279fac | |||
344f4f52c1 | |||
eb212aa91b | |||
a6dbb2ad2b | |||
595e514f18 | |||
07435ad844 | |||
1668ba913f |
@ -13,15 +13,15 @@
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="Concentus" Version="2.2.0" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageVersion Include="DynamicData" Version="8.4.1" />
|
||||
<PackageVersion Include="DynamicData" Version="9.0.4" />
|
||||
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||
<PackageVersion Include="LibHac" Version="0.19.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.6.2" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
|
||||
@ -39,11 +39,11 @@
|
||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.8" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.21.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.21.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.21.0" />
|
||||
<PackageVersion Include="SkiaSharp" Version="2.88.7" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.7" />
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build32" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Management" Version="8.0.0" />
|
||||
|
@ -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
|
||||
|
@ -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<<xxxxxxx00010x0x0xxxx", InstName.Vsubl, InstEmit32.Vsubl_I, OpCode32SimdRegLong.Create, OpCode32SimdRegLong.CreateT32);
|
||||
SetAsimd("1111001x1x<<xxxxxxx00011x0x0xxxx", InstName.Vsubw, InstEmit32.Vsubw_I, OpCode32SimdRegWide.Create, OpCode32SimdRegWide.CreateT32);
|
||||
SetAsimd("111100111x110010xxxx00000xx0xxxx", InstName.Vswp, InstEmit32.Vswp, OpCode32Simd.Create, OpCode32Simd.CreateT32);
|
||||
SetAsimd("111100111x11xxxxxxxx10xxxxx0xxxx", InstName.Vtbl, InstEmit32.Vtbl, OpCode32SimdTbl.Create, OpCode32SimdTbl.CreateT32);
|
||||
SetAsimd("111100111x11<<10xxxx00001xx0xxxx", InstName.Vtrn, InstEmit32.Vtrn, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
|
||||
SetAsimd("111100100x<<xxxxxxxx1000xxx1xxxx", InstName.Vtst, InstEmit32.Vtst, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||
|
@ -2,6 +2,8 @@ using ARMeilleure.Decoders;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.State;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using static ARMeilleure.Instructions.InstEmitAluHelper;
|
||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||
@ -558,6 +560,46 @@ namespace ARMeilleure.Instructions
|
||||
EmitHsub8(context, unsigned: true);
|
||||
}
|
||||
|
||||
public static void Uqadd16(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
|
||||
|
||||
SetIntA32(context, op.Rd, EmitUnsigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
|
||||
{
|
||||
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<Operand, Operand, Operand> 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<Operand, Operand, Operand> elementAction)
|
||||
{
|
||||
return Emit8BitPair(context, rn, rm, elementAction, unsigned: false);
|
||||
}
|
||||
|
||||
private static Operand EmitUnsigned8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
|
||||
{
|
||||
return Emit8BitPair(context, rn, rm, elementAction, unsigned: true);
|
||||
}
|
||||
|
||||
private static Operand Emit8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> 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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -80,7 +80,10 @@ namespace ARMeilleure.Translation
|
||||
return true;
|
||||
}
|
||||
|
||||
Monitor.Wait(Sync);
|
||||
if (!_disposed)
|
||||
{
|
||||
Monitor.Wait(Sync);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,16 +18,12 @@ namespace Ryujinx.Audio.Renderer.Server.Performance
|
||||
|
||||
if (version == 2)
|
||||
{
|
||||
return (ulong)PerformanceManagerGeneric<PerformanceFrameHeaderVersion2,
|
||||
PerformanceEntryVersion2,
|
||||
PerformanceDetailVersion2>.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter);
|
||||
return (ulong)PerformanceManagerGeneric<PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, PerformanceDetailVersion2>.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter);
|
||||
}
|
||||
|
||||
if (version == 1)
|
||||
{
|
||||
return (ulong)PerformanceManagerGeneric<PerformanceFrameHeaderVersion1,
|
||||
PerformanceEntryVersion1,
|
||||
PerformanceDetailVersion1>.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter);
|
||||
return (ulong)PerformanceManagerGeneric<PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, PerformanceDetailVersion1>.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter);
|
||||
}
|
||||
|
||||
throw new NotImplementedException($"Unknown Performance metrics data format version {version}");
|
||||
|
@ -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<THeader>() + GetEntriesSize() + Unsafe.SizeOf<IPerformanceDetailEntry>() * _entryDetailIndex);
|
||||
uint baseEntryOffset = (uint)(Unsafe.SizeOf<THeader>() + GetEntriesSize() + Unsafe.SizeOf<TEntryDetail>() * _entryDetailIndex);
|
||||
|
||||
ref TEntryDetail entryDetail = ref EntriesDetail[_entryDetailIndex];
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -1,51 +0,0 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
public partial class ByteMemoryPool
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a <see cref="IMemoryOwner{Byte}"/> that wraps an array rented from
|
||||
/// <see cref="ArrayPool{Byte}.Shared"/> and exposes it as <see cref="Memory{Byte}"/>
|
||||
/// with a length of the requested size.
|
||||
/// </summary>
|
||||
private sealed class ByteMemoryPoolBuffer : IMemoryOwner<byte>
|
||||
{
|
||||
private byte[] _array;
|
||||
private readonly int _length;
|
||||
|
||||
public ByteMemoryPoolBuffer(int length)
|
||||
{
|
||||
_array = ArrayPool<byte>.Shared.Rent(length);
|
||||
_length = length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Memory{Byte}"/> belonging to this owner.
|
||||
/// </summary>
|
||||
public Memory<byte> Memory
|
||||
{
|
||||
get
|
||||
{
|
||||
byte[] array = _array;
|
||||
|
||||
ObjectDisposedException.ThrowIf(array is null, this);
|
||||
|
||||
return new Memory<byte>(array, 0, _length);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var array = Interlocked.Exchange(ref _array, null);
|
||||
|
||||
if (array != null)
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(array);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a pool of re-usable byte array instances.
|
||||
/// </summary>
|
||||
public static partial class ByteMemoryPool
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the maximum buffer size supported by this pool.
|
||||
/// </summary>
|
||||
public static int MaxBufferSize => Array.MaxLength;
|
||||
|
||||
/// <summary>
|
||||
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
|
||||
/// The buffer may contain data from a prior use.
|
||||
/// </summary>
|
||||
/// <param name="length">The buffer's required length in bytes</param>
|
||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public static IMemoryOwner<byte> Rent(long length)
|
||||
=> RentImpl(checked((int)length));
|
||||
|
||||
/// <summary>
|
||||
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
|
||||
/// The buffer may contain data from a prior use.
|
||||
/// </summary>
|
||||
/// <param name="length">The buffer's required length in bytes</param>
|
||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public static IMemoryOwner<byte> Rent(ulong length)
|
||||
=> RentImpl(checked((int)length));
|
||||
|
||||
/// <summary>
|
||||
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
|
||||
/// The buffer may contain data from a prior use.
|
||||
/// </summary>
|
||||
/// <param name="length">The buffer's required length in bytes</param>
|
||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public static IMemoryOwner<byte> Rent(int length)
|
||||
=> RentImpl(length);
|
||||
|
||||
/// <summary>
|
||||
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
|
||||
/// The buffer's contents are cleared (set to all 0s) before returning.
|
||||
/// </summary>
|
||||
/// <param name="length">The buffer's required length in bytes</param>
|
||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public static IMemoryOwner<byte> RentCleared(long length)
|
||||
=> RentCleared(checked((int)length));
|
||||
|
||||
/// <summary>
|
||||
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
|
||||
/// The buffer's contents are cleared (set to all 0s) before returning.
|
||||
/// </summary>
|
||||
/// <param name="length">The buffer's required length in bytes</param>
|
||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public static IMemoryOwner<byte> RentCleared(ulong length)
|
||||
=> RentCleared(checked((int)length));
|
||||
|
||||
/// <summary>
|
||||
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
|
||||
/// The buffer's contents are cleared (set to all 0s) before returning.
|
||||
/// </summary>
|
||||
/// <param name="length">The buffer's required length in bytes</param>
|
||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public static IMemoryOwner<byte> RentCleared(int length)
|
||||
{
|
||||
var buffer = RentImpl(length);
|
||||
|
||||
buffer.Memory.Span.Clear();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies <paramref name="buffer"/> into a newly rented byte memory buffer.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The byte buffer to copy</param>
|
||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory with <paramref name="buffer"/> copied to it</returns>
|
||||
public static IMemoryOwner<byte> RentCopy(ReadOnlySpan<byte> 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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<byte> ReadFileToRentedMemory(string filename)
|
||||
public static MemoryOwner<byte> ReadFileToRentedMemory(string filename)
|
||||
{
|
||||
var (assembly, path) = ResolveManifestPath(filename);
|
||||
|
||||
return ReadFileToRentedMemory(assembly, path);
|
||||
}
|
||||
|
||||
public static IMemoryOwner<byte> ReadFileToRentedMemory(Assembly assembly, string filename)
|
||||
public static MemoryOwner<byte> ReadFileToRentedMemory(Assembly assembly, string filename)
|
||||
{
|
||||
using var stream = GetStream(assembly, filename);
|
||||
|
||||
|
24
src/Ryujinx.Common/Utilities/OsUtils.cs
Normal file
24
src/Ryujinx.Common/Utilities/OsUtils.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<byte> StreamToRentedMemory(Stream input)
|
||||
public static MemoryOwner<byte> StreamToRentedMemory(Stream input)
|
||||
{
|
||||
if (input is MemoryStream inputMemoryStream)
|
||||
{
|
||||
@ -26,9 +25,9 @@ namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
long bytesExpected = input.Length;
|
||||
|
||||
IMemoryOwner<byte> ownedMemory = ByteMemoryPool.Rent(bytesExpected);
|
||||
MemoryOwner<byte> ownedMemory = MemoryOwner<byte>.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<byte> MemoryStreamToRentedMemory(MemoryStream input)
|
||||
private static MemoryOwner<byte> MemoryStreamToRentedMemory(MemoryStream input)
|
||||
{
|
||||
input.Position = 0;
|
||||
|
||||
IMemoryOwner<byte> ownedMemory = ByteMemoryPool.Rent(input.Length);
|
||||
MemoryOwner<byte> ownedMemory = MemoryOwner<byte>.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;
|
||||
}
|
||||
|
@ -303,9 +303,9 @@ namespace Ryujinx.Cpu.Jit
|
||||
}
|
||||
else
|
||||
{
|
||||
IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(size);
|
||||
MemoryOwner<byte> memoryOwner = MemoryOwner<byte>.Rent(size);
|
||||
|
||||
Read(va, memoryOwner.Memory.Span);
|
||||
Read(va, memoryOwner.Span);
|
||||
|
||||
return new WritableRegion(this, va, memoryOwner);
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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));
|
||||
|
@ -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<byte> memoryOwner = ByteMemoryPool.Rent(size);
|
||||
MemoryOwner<byte> memoryOwner = MemoryOwner<byte>.Rent(size);
|
||||
|
||||
GetSpan(va, size).CopyTo(memoryOwner.Memory.Span);
|
||||
ReadImpl(va, memoryOwner.Span);
|
||||
|
||||
return new WritableRegion(this, va, memoryOwner, tracked: true);
|
||||
}
|
||||
|
@ -39,7 +39,10 @@ namespace Ryujinx.Graphics.Device
|
||||
{
|
||||
var field = fields[fieldIndex];
|
||||
|
||||
int sizeOfField = SizeCalculator.SizeOf(field.FieldType);
|
||||
var currentFieldOffset = (int)Marshal.OffsetOf<TState>(field.Name);
|
||||
var nextFieldOffset = fieldIndex + 1 == fields.Length ? Unsafe.SizeOf<TState>() : (int)Marshal.OffsetOf<TState>(fields[fieldIndex + 1].Name);
|
||||
|
||||
int sizeOfField = nextFieldOffset - currentFieldOffset;
|
||||
|
||||
for (int i = 0; i < ((sizeOfField + 3) & ~3); i += 4)
|
||||
{
|
||||
|
@ -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."),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -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);
|
||||
|
@ -199,7 +199,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
|
||||
if (target != null)
|
||||
{
|
||||
target.SynchronizeMemory();
|
||||
var dataCopy = ByteMemoryPool.RentCopy(data);
|
||||
var dataCopy = MemoryOwner<byte>.RentCopy(data);
|
||||
target.SetData(dataCopy, 0, 0, new GAL.Rectangle<int>(_dstX, _dstY, _lineLengthIn / target.Info.FormatInfo.BytesPerPixel, _lineCount));
|
||||
target.SignalModified();
|
||||
|
||||
|
@ -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<TState>(field.Name);
|
||||
var nextFieldOffset = fieldIndex + 1 == fields.Length ? Unsafe.SizeOf<TState>() : (int)Marshal.OffsetOf<TState>(fields[fieldIndex + 1].Name);
|
||||
|
||||
int sizeOfField = nextFieldOffset - currentFieldOffset;
|
||||
|
||||
if (fieldToDelegate.TryGetValue(field.Name, out int entryIndex))
|
||||
{
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
@ -805,7 +806,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
sliceDepth,
|
||||
levels,
|
||||
layers,
|
||||
out IMemoryOwner<byte> decoded))
|
||||
out MemoryOwner<byte> decoded))
|
||||
{
|
||||
string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}";
|
||||
|
||||
|
@ -340,7 +340,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <returns>True if any used entries of the pool might have been modified, false otherwise</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
@ -468,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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps the amount of mipmap levels to the maximum allowed for the given texture dimensions.
|
||||
/// </summary>
|
||||
/// <param name="target">Number of texture dimensions (1D, 2D, 3D, Cube, etc)</param>
|
||||
/// <param name="width">Width of the texture</param>
|
||||
/// <param name="height">Height of the texture, ignored for 1D textures</param>
|
||||
/// <param name="depthOrLayers">Depth of the texture for 3D textures, otherwise ignored</param>
|
||||
/// <param name="levels">Original amount of mipmap levels</param>
|
||||
/// <returns>Clamped mipmap levels</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
|
@ -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<byte> memoryOwner = ByteMemoryPool.Rent(size);
|
||||
MemoryOwner<byte> memoryOwner = MemoryOwner<byte>.Rent(size);
|
||||
|
||||
GetSpan(va, size).CopyTo(memoryOwner.Memory.Span);
|
||||
ReadImpl(va, memoryOwner.Span, tracked);
|
||||
|
||||
return new WritableRegion(this, va, memoryOwner, tracked);
|
||||
}
|
||||
|
@ -192,9 +192,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
else
|
||||
{
|
||||
IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(range.GetSize());
|
||||
MemoryOwner<byte> memoryOwner = MemoryOwner<byte>.Rent(checked((int)range.GetSize()));
|
||||
|
||||
Memory<byte> memory = memoryOwner.Memory;
|
||||
Span<byte> 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;
|
||||
}
|
||||
|
@ -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 = 6921;
|
||||
private const uint CodeGenVersion = 7131;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
|
@ -78,9 +78,9 @@ namespace Ryujinx.Graphics.Gpu.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.StorageBuffer, storageSetIndex, 0, rrc.ReservedStorageBuffers, true);
|
||||
PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, textureSetIndex, 0, rrc.ReservedTextures);
|
||||
PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, imageSetIndex, 0, rrc.ReservedImages);
|
||||
PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, imageSetIndex, 0, rrc.ReservedImages, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -91,10 +91,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="setIndex">Resource set index where the resources are used</param>
|
||||
/// <param name="start">First binding number</param>
|
||||
/// <param name="count">Amount of bindings</param>
|
||||
private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count)
|
||||
/// <param name="write">True if the binding is written from the shader, false otherwise</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -216,11 +217,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="setIndex">Descriptor set number where the resource will be bound</param>
|
||||
/// <param name="binding">Binding number where the resource will be bound</param>
|
||||
/// <param name="count">Number of resources bound at the binding location</param>
|
||||
private void AddUsage(ResourceStages stages, ResourceType type, int setIndex, int binding, int count)
|
||||
/// <param name="write">True if the binding is written from the shader, false otherwise</param>
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,7 +240,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
buffer.Binding,
|
||||
1,
|
||||
isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
|
||||
stages));
|
||||
stages,
|
||||
buffer.Flags.HasFlag(BufferUsageFlags.Write)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,7 +257,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
ResourceType type = GetTextureResourceType(texture, isImage);
|
||||
|
||||
GetUsages(texture.Set).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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<byte> ConvertS8D24ToD24S8(ReadOnlySpan<byte> data)
|
||||
public unsafe static MemoryOwner<byte> ConvertS8D24ToD24S8(ReadOnlySpan<byte> data)
|
||||
{
|
||||
IMemoryOwner<byte> outputMemory = ByteMemoryPool.Rent(data.Length);
|
||||
MemoryOwner<byte> outputMemory = MemoryOwner<byte>.Rent(data.Length);
|
||||
|
||||
Span<byte> output = outputMemory.Memory.Span;
|
||||
Span<byte> output = outputMemory.Span;
|
||||
|
||||
int start = 0;
|
||||
|
||||
|
@ -48,7 +48,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
internalFormat = (SizedInternalFormat)format.PixelInternalFormat;
|
||||
}
|
||||
|
||||
int levels = Info.GetLevelsClamped();
|
||||
int levels = Info.Levels;
|
||||
|
||||
switch (Info.Target)
|
||||
{
|
||||
|
@ -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<byte> 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;
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<byte> decoded)
|
||||
out MemoryOwner<byte> decoded)
|
||||
{
|
||||
decoded = ByteMemoryPool.Rent(QueryDecompressedSize(width, height, depth, levels, layers));
|
||||
decoded = MemoryOwner<byte>.Rent(QueryDecompressedSize(width, height, depth, levels, layers));
|
||||
|
||||
AstcDecoder decoder = new(data, decoded.Memory, blockWidth, blockHeight, width, height, depth, levels, layers);
|
||||
|
||||
|
@ -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<byte> DecodeBC1(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
|
||||
public static MemoryOwner<byte> DecodeBC1(ReadOnlySpan<byte> 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<byte> output = ByteMemoryPool.Rent(size);
|
||||
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(size);
|
||||
|
||||
Span<byte> tile = stackalloc byte[BlockWidth * BlockHeight * 4];
|
||||
|
||||
Span<uint> tileAsUint = MemoryMarshal.Cast<byte, uint>(tile);
|
||||
Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
|
||||
Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output.Span);
|
||||
|
||||
Span<Vector128<byte>> tileAsVector128 = MemoryMarshal.Cast<byte, Vector128<byte>>(tile);
|
||||
|
||||
@ -102,7 +101,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
return output;
|
||||
}
|
||||
|
||||
public static IMemoryOwner<byte> DecodeBC2(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
|
||||
public static MemoryOwner<byte> DecodeBC2(ReadOnlySpan<byte> 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<byte> output = ByteMemoryPool.Rent(size);
|
||||
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(size);
|
||||
|
||||
Span<byte> tile = stackalloc byte[BlockWidth * BlockHeight * 4];
|
||||
|
||||
Span<uint> tileAsUint = MemoryMarshal.Cast<byte, uint>(tile);
|
||||
Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
|
||||
Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output.Span);
|
||||
|
||||
Span<Vector128<byte>> tileAsVector128 = MemoryMarshal.Cast<byte, Vector128<byte>>(tile);
|
||||
|
||||
@ -197,7 +196,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
return output;
|
||||
}
|
||||
|
||||
public static IMemoryOwner<byte> DecodeBC3(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
|
||||
public static MemoryOwner<byte> DecodeBC3(ReadOnlySpan<byte> 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<byte> output = ByteMemoryPool.Rent(size);
|
||||
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(size);
|
||||
|
||||
Span<byte> tile = stackalloc byte[BlockWidth * BlockHeight * 4];
|
||||
Span<byte> rPal = stackalloc byte[8];
|
||||
|
||||
Span<uint> tileAsUint = MemoryMarshal.Cast<byte, uint>(tile);
|
||||
Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
|
||||
Span<uint> outputAsUint = MemoryMarshal.Cast<byte, uint>(output.Span);
|
||||
|
||||
Span<Vector128<byte>> tileAsVector128 = MemoryMarshal.Cast<byte, Vector128<byte>>(tile);
|
||||
|
||||
@ -294,7 +293,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
return output;
|
||||
}
|
||||
|
||||
public static IMemoryOwner<byte> DecodeBC4(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed)
|
||||
public static MemoryOwner<byte> DecodeBC4(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed)
|
||||
{
|
||||
int size = 0;
|
||||
|
||||
@ -306,8 +305,8 @@ namespace Ryujinx.Graphics.Texture
|
||||
// Backends currently expect a stride alignment of 4 bytes, so output width must be aligned.
|
||||
int alignedWidth = BitUtils.AlignUp(width, 4);
|
||||
|
||||
IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
|
||||
Span<byte> outputSpan = output.Memory.Span;
|
||||
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(size);
|
||||
Span<byte> outputSpan = output.Span;
|
||||
|
||||
ReadOnlySpan<ulong> data64 = MemoryMarshal.Cast<byte, ulong>(data);
|
||||
|
||||
@ -402,7 +401,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
return output;
|
||||
}
|
||||
|
||||
public static IMemoryOwner<byte> DecodeBC5(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed)
|
||||
public static MemoryOwner<byte> DecodeBC5(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed)
|
||||
{
|
||||
int size = 0;
|
||||
|
||||
@ -414,7 +413,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
// Backends currently expect a stride alignment of 4 bytes, so output width must be aligned.
|
||||
int alignedWidth = BitUtils.AlignUp(width, 2);
|
||||
|
||||
IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
|
||||
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(size);
|
||||
|
||||
ReadOnlySpan<ulong> data64 = MemoryMarshal.Cast<byte, ulong>(data);
|
||||
|
||||
@ -423,7 +422,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
Span<byte> rPal = stackalloc byte[8];
|
||||
Span<byte> gPal = stackalloc byte[8];
|
||||
|
||||
Span<ushort> outputAsUshort = MemoryMarshal.Cast<byte, ushort>(output.Memory.Span);
|
||||
Span<ushort> outputAsUshort = MemoryMarshal.Cast<byte, ushort>(output.Span);
|
||||
|
||||
Span<uint> rTileAsUint = MemoryMarshal.Cast<byte, uint>(rTile);
|
||||
Span<uint> gTileAsUint = MemoryMarshal.Cast<byte, uint>(gTile);
|
||||
@ -527,7 +526,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
return output;
|
||||
}
|
||||
|
||||
public static IMemoryOwner<byte> DecodeBC6(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed)
|
||||
public static MemoryOwner<byte> DecodeBC6(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers, bool signed)
|
||||
{
|
||||
int size = 0;
|
||||
|
||||
@ -536,8 +535,8 @@ namespace Ryujinx.Graphics.Texture
|
||||
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 8;
|
||||
}
|
||||
|
||||
IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
|
||||
Span<byte> outputSpan = output.Memory.Span;
|
||||
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(size);
|
||||
Span<byte> outputSpan = output.Span;
|
||||
|
||||
int inputOffset = 0;
|
||||
int outputOffset = 0;
|
||||
@ -566,7 +565,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
return output;
|
||||
}
|
||||
|
||||
public static IMemoryOwner<byte> DecodeBC7(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
|
||||
public static MemoryOwner<byte> DecodeBC7(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
|
||||
{
|
||||
int size = 0;
|
||||
|
||||
@ -575,8 +574,8 @@ namespace Ryujinx.Graphics.Texture
|
||||
size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4;
|
||||
}
|
||||
|
||||
IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
|
||||
Span<byte> outputSpan = output.Memory.Span;
|
||||
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(size);
|
||||
Span<byte> outputSpan = output.Span;
|
||||
|
||||
int inputOffset = 0;
|
||||
int outputOffset = 0;
|
||||
|
@ -2,7 +2,6 @@ using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.Texture.Encoders;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Graphics.Texture
|
||||
{
|
||||
@ -11,7 +10,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
private const int BlockWidth = 4;
|
||||
private const int BlockHeight = 4;
|
||||
|
||||
public static IMemoryOwner<byte> EncodeBC7(Memory<byte> data, int width, int height, int depth, int levels, int layers)
|
||||
public static MemoryOwner<byte> EncodeBC7(Memory<byte> data, int width, int height, int depth, int levels, int layers)
|
||||
{
|
||||
int size = 0;
|
||||
|
||||
@ -23,7 +22,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
size += w * h * 16 * Math.Max(1, depth >> l) * layers;
|
||||
}
|
||||
|
||||
IMemoryOwner<byte> output = ByteMemoryPool.Rent(size);
|
||||
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(size);
|
||||
Memory<byte> outputMemory = output.Memory;
|
||||
|
||||
int imageBaseIOffs = 0;
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@ -51,15 +50,15 @@ namespace Ryujinx.Graphics.Texture
|
||||
new int[] { -3, -5, -7, -9, 2, 4, 6, 8 },
|
||||
};
|
||||
|
||||
public static IMemoryOwner<byte> DecodeRgb(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
|
||||
public static MemoryOwner<byte> DecodeRgb(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
|
||||
{
|
||||
ReadOnlySpan<ulong> dataUlong = MemoryMarshal.Cast<byte, ulong>(data);
|
||||
|
||||
int inputOffset = 0;
|
||||
|
||||
IMemoryOwner<byte> output = ByteMemoryPool.Rent(CalculateOutputSize(width, height, depth, levels, layers));
|
||||
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(CalculateOutputSize(width, height, depth, levels, layers));
|
||||
|
||||
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
|
||||
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output.Span);
|
||||
Span<uint> tile = stackalloc uint[BlockWidth * BlockHeight];
|
||||
|
||||
int imageBaseOOffs = 0;
|
||||
@ -113,15 +112,15 @@ namespace Ryujinx.Graphics.Texture
|
||||
return output;
|
||||
}
|
||||
|
||||
public static IMemoryOwner<byte> DecodePta(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
|
||||
public static MemoryOwner<byte> DecodePta(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
|
||||
{
|
||||
ReadOnlySpan<ulong> dataUlong = MemoryMarshal.Cast<byte, ulong>(data);
|
||||
|
||||
int inputOffset = 0;
|
||||
|
||||
IMemoryOwner<byte> output = ByteMemoryPool.Rent(CalculateOutputSize(width, height, depth, levels, layers));
|
||||
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(CalculateOutputSize(width, height, depth, levels, layers));
|
||||
|
||||
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
|
||||
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output.Span);
|
||||
Span<uint> tile = stackalloc uint[BlockWidth * BlockHeight];
|
||||
|
||||
int imageBaseOOffs = 0;
|
||||
@ -170,15 +169,15 @@ namespace Ryujinx.Graphics.Texture
|
||||
return output;
|
||||
}
|
||||
|
||||
public static IMemoryOwner<byte> DecodeRgba(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
|
||||
public static MemoryOwner<byte> DecodeRgba(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers)
|
||||
{
|
||||
ReadOnlySpan<ulong> dataUlong = MemoryMarshal.Cast<byte, ulong>(data);
|
||||
|
||||
int inputOffset = 0;
|
||||
|
||||
IMemoryOwner<byte> output = ByteMemoryPool.Rent(CalculateOutputSize(width, height, depth, levels, layers));
|
||||
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(CalculateOutputSize(width, height, depth, levels, layers));
|
||||
|
||||
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
|
||||
Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output.Span);
|
||||
Span<uint> tile = stackalloc uint[BlockWidth * BlockHeight];
|
||||
|
||||
int imageBaseOOffs = 0;
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.Intrinsics;
|
||||
using static Ryujinx.Graphics.Texture.BlockLinearConstants;
|
||||
|
||||
@ -95,7 +94,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
};
|
||||
}
|
||||
|
||||
public static IMemoryOwner<byte> ConvertBlockLinearToLinear(
|
||||
public static MemoryOwner<byte> ConvertBlockLinearToLinear(
|
||||
int width,
|
||||
int height,
|
||||
int depth,
|
||||
@ -121,8 +120,8 @@ namespace Ryujinx.Graphics.Texture
|
||||
blockHeight,
|
||||
bytesPerPixel);
|
||||
|
||||
IMemoryOwner<byte> outputOwner = ByteMemoryPool.Rent(outSize);
|
||||
Span<byte> output = outputOwner.Memory.Span;
|
||||
MemoryOwner<byte> outputOwner = MemoryOwner<byte>.Rent(outSize);
|
||||
Span<byte> output = outputOwner.Span;
|
||||
|
||||
int outOffs = 0;
|
||||
|
||||
@ -249,7 +248,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
return outputOwner;
|
||||
}
|
||||
|
||||
public static IMemoryOwner<byte> ConvertLinearStridedToLinear(
|
||||
public static MemoryOwner<byte> ConvertLinearStridedToLinear(
|
||||
int width,
|
||||
int height,
|
||||
int blockWidth,
|
||||
@ -265,8 +264,8 @@ namespace Ryujinx.Graphics.Texture
|
||||
int outStride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment);
|
||||
lineSize = Math.Min(lineSize, outStride);
|
||||
|
||||
IMemoryOwner<byte> output = ByteMemoryPool.Rent(h * outStride);
|
||||
Span<byte> outSpan = output.Memory.Span;
|
||||
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(h * outStride);
|
||||
Span<byte> outSpan = output.Span;
|
||||
|
||||
int outOffs = 0;
|
||||
int inOffs = 0;
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
@ -21,13 +20,14 @@ namespace Ryujinx.Graphics.Texture
|
||||
return (remainder, outRemainder, length / stride);
|
||||
}
|
||||
|
||||
public unsafe static IMemoryOwner<byte> ConvertR4G4ToR4G4B4A4(ReadOnlySpan<byte> data, int width)
|
||||
public unsafe static MemoryOwner<byte> ConvertR4G4ToR4G4B4A4(ReadOnlySpan<byte> data, int width)
|
||||
{
|
||||
IMemoryOwner<byte> output = ByteMemoryPool.Rent(data.Length * 2);
|
||||
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(data.Length * 2);
|
||||
Span<byte> outputSpan = output.Span;
|
||||
|
||||
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 1, 2);
|
||||
|
||||
Span<ushort> outputSpan = MemoryMarshal.Cast<byte, ushort>(output.Memory.Span);
|
||||
Span<ushort> outputSpanUInt16 = MemoryMarshal.Cast<byte, ushort>(outputSpan);
|
||||
|
||||
if (remainder == 0)
|
||||
{
|
||||
@ -38,7 +38,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
int sizeTrunc = data.Length & ~7;
|
||||
start = sizeTrunc;
|
||||
|
||||
fixed (byte* inputPtr = data, outputPtr = output.Memory.Span)
|
||||
fixed (byte* inputPtr = data, outputPtr = outputSpan)
|
||||
{
|
||||
for (ulong offset = 0; offset < (ulong)sizeTrunc; offset += 8)
|
||||
{
|
||||
@ -49,7 +49,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
|
||||
for (int i = start; i < data.Length; i++)
|
||||
{
|
||||
outputSpan[i] = data[i];
|
||||
outputSpanUInt16[i] = data[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -61,7 +61,7 @@ namespace Ryujinx.Graphics.Texture
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
outputSpan[outOffset++] = data[offset++];
|
||||
outputSpanUInt16[outOffset++] = data[offset++];
|
||||
}
|
||||
|
||||
offset += remainder;
|
||||
@ -72,16 +72,16 @@ namespace Ryujinx.Graphics.Texture
|
||||
return output;
|
||||
}
|
||||
|
||||
public static IMemoryOwner<byte> ConvertR5G6B5ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
|
||||
public static MemoryOwner<byte> ConvertR5G6B5ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
|
||||
{
|
||||
IMemoryOwner<byte> output = ByteMemoryPool.Rent(data.Length * 2);
|
||||
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(data.Length * 2);
|
||||
int offset = 0;
|
||||
int outOffset = 0;
|
||||
|
||||
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
|
||||
|
||||
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
|
||||
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
|
||||
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Span);
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
@ -109,16 +109,16 @@ namespace Ryujinx.Graphics.Texture
|
||||
return output;
|
||||
}
|
||||
|
||||
public static IMemoryOwner<byte> ConvertR5G5B5ToR8G8B8A8(ReadOnlySpan<byte> data, int width, bool forceAlpha)
|
||||
public static MemoryOwner<byte> ConvertR5G5B5ToR8G8B8A8(ReadOnlySpan<byte> data, int width, bool forceAlpha)
|
||||
{
|
||||
IMemoryOwner<byte> output = ByteMemoryPool.Rent(data.Length * 2);
|
||||
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(data.Length * 2);
|
||||
int offset = 0;
|
||||
int outOffset = 0;
|
||||
|
||||
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
|
||||
|
||||
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
|
||||
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
|
||||
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Span);
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
@ -146,16 +146,16 @@ namespace Ryujinx.Graphics.Texture
|
||||
return output;
|
||||
}
|
||||
|
||||
public static IMemoryOwner<byte> ConvertA1B5G5R5ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
|
||||
public static MemoryOwner<byte> ConvertA1B5G5R5ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
|
||||
{
|
||||
IMemoryOwner<byte> output = ByteMemoryPool.Rent(data.Length * 2);
|
||||
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(data.Length * 2);
|
||||
int offset = 0;
|
||||
int outOffset = 0;
|
||||
|
||||
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
|
||||
|
||||
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
|
||||
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
|
||||
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Span);
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
@ -183,16 +183,16 @@ namespace Ryujinx.Graphics.Texture
|
||||
return output;
|
||||
}
|
||||
|
||||
public static IMemoryOwner<byte> ConvertR4G4B4A4ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
|
||||
public static MemoryOwner<byte> ConvertR4G4B4A4ToR8G8B8A8(ReadOnlySpan<byte> data, int width)
|
||||
{
|
||||
IMemoryOwner<byte> output = ByteMemoryPool.Rent(data.Length * 2);
|
||||
MemoryOwner<byte> output = MemoryOwner<byte>.Rent(data.Length * 2);
|
||||
int offset = 0;
|
||||
int outOffset = 0;
|
||||
|
||||
(int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4);
|
||||
|
||||
ReadOnlySpan<ushort> inputSpan = MemoryMarshal.Cast<byte, ushort>(data);
|
||||
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Memory.Span);
|
||||
Span<uint> outputSpan = MemoryMarshal.Cast<byte, uint>(output.Span);
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
|
@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
queue,
|
||||
queueLock,
|
||||
_gd.QueueFamilyIndex,
|
||||
_gd.IsConcurrentFenceWaitUnsupported,
|
||||
_gd.IsQualcommProprietary,
|
||||
isLight: true);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
@ -8,22 +9,55 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
private const int MaxBarriersPerCall = 16;
|
||||
|
||||
private const AccessFlags BaseAccess = AccessFlags.ShaderReadBit | AccessFlags.ShaderWriteBit;
|
||||
private const AccessFlags BufferAccess = AccessFlags.IndexReadBit | AccessFlags.VertexAttributeReadBit | AccessFlags.UniformReadBit;
|
||||
private const AccessFlags CommandBufferAccess = AccessFlags.IndirectCommandReadBit;
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
|
||||
private readonly NativeArray<MemoryBarrier> _memoryBarrierBatch = new(MaxBarriersPerCall);
|
||||
private readonly NativeArray<BufferMemoryBarrier> _bufferBarrierBatch = new(MaxBarriersPerCall);
|
||||
private readonly NativeArray<ImageMemoryBarrier> _imageBarrierBatch = new(MaxBarriersPerCall);
|
||||
|
||||
private readonly List<BarrierWithStageFlags<MemoryBarrier>> _memoryBarriers = new();
|
||||
private readonly List<BarrierWithStageFlags<BufferMemoryBarrier>> _bufferBarriers = new();
|
||||
private readonly List<BarrierWithStageFlags<ImageMemoryBarrier>> _imageBarriers = new();
|
||||
private readonly List<BarrierWithStageFlags<MemoryBarrier, int>> _memoryBarriers = new();
|
||||
private readonly List<BarrierWithStageFlags<BufferMemoryBarrier, int>> _bufferBarriers = new();
|
||||
private readonly List<BarrierWithStageFlags<ImageMemoryBarrier, TextureStorage>> _imageBarriers = new();
|
||||
private int _queuedBarrierCount;
|
||||
|
||||
private enum IncoherentBarrierType
|
||||
{
|
||||
None,
|
||||
Texture,
|
||||
All,
|
||||
CommandBuffer
|
||||
}
|
||||
|
||||
private bool _feedbackLoopActive;
|
||||
private PipelineStageFlags _incoherentBufferWriteStages;
|
||||
private PipelineStageFlags _incoherentTextureWriteStages;
|
||||
private PipelineStageFlags _extraStages;
|
||||
private IncoherentBarrierType _queuedIncoherentBarrier;
|
||||
private bool _queuedFeedbackLoopBarrier;
|
||||
|
||||
public BarrierBatch(VulkanRenderer gd)
|
||||
{
|
||||
_gd = gd;
|
||||
}
|
||||
|
||||
public static (AccessFlags Access, PipelineStageFlags Stages) GetSubpassAccessSuperset(VulkanRenderer gd)
|
||||
{
|
||||
AccessFlags access = BufferAccess;
|
||||
PipelineStageFlags stages = PipelineStageFlags.AllGraphicsBit;
|
||||
|
||||
if (gd.TransformFeedbackApi != null)
|
||||
{
|
||||
access |= AccessFlags.TransformFeedbackWriteBitExt;
|
||||
stages |= PipelineStageFlags.TransformFeedbackBitExt;
|
||||
}
|
||||
|
||||
return (access, stages);
|
||||
}
|
||||
|
||||
private readonly record struct StageFlags : IEquatable<StageFlags>
|
||||
{
|
||||
public readonly PipelineStageFlags Source;
|
||||
@ -36,47 +70,150 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct BarrierWithStageFlags<T> where T : unmanaged
|
||||
private readonly struct BarrierWithStageFlags<T, T2> where T : unmanaged
|
||||
{
|
||||
public readonly StageFlags Flags;
|
||||
public readonly T Barrier;
|
||||
public readonly T2 Resource;
|
||||
|
||||
public BarrierWithStageFlags(StageFlags flags, T barrier)
|
||||
{
|
||||
Flags = flags;
|
||||
Barrier = barrier;
|
||||
Resource = default;
|
||||
}
|
||||
|
||||
public BarrierWithStageFlags(PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags, T barrier)
|
||||
public BarrierWithStageFlags(PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags, T barrier, T2 resource)
|
||||
{
|
||||
Flags = new StageFlags(srcStageFlags, dstStageFlags);
|
||||
Barrier = barrier;
|
||||
Resource = resource;
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueBarrier<T>(List<BarrierWithStageFlags<T>> list, T barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) where T : unmanaged
|
||||
private void QueueBarrier<T, T2>(List<BarrierWithStageFlags<T, T2>> list, T barrier, T2 resource, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) where T : unmanaged
|
||||
{
|
||||
list.Add(new BarrierWithStageFlags<T>(srcStageFlags, dstStageFlags, barrier));
|
||||
list.Add(new BarrierWithStageFlags<T, T2>(srcStageFlags, dstStageFlags, barrier, resource));
|
||||
_queuedBarrierCount++;
|
||||
}
|
||||
|
||||
public void QueueBarrier(MemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||
{
|
||||
QueueBarrier(_memoryBarriers, barrier, srcStageFlags, dstStageFlags);
|
||||
QueueBarrier(_memoryBarriers, barrier, default, srcStageFlags, dstStageFlags);
|
||||
}
|
||||
|
||||
public void QueueBarrier(BufferMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||
{
|
||||
QueueBarrier(_bufferBarriers, barrier, srcStageFlags, dstStageFlags);
|
||||
QueueBarrier(_bufferBarriers, barrier, default, srcStageFlags, dstStageFlags);
|
||||
}
|
||||
|
||||
public void QueueBarrier(ImageMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||
public void QueueBarrier(ImageMemoryBarrier barrier, TextureStorage resource, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||
{
|
||||
QueueBarrier(_imageBarriers, barrier, srcStageFlags, dstStageFlags);
|
||||
QueueBarrier(_imageBarriers, barrier, resource, srcStageFlags, dstStageFlags);
|
||||
}
|
||||
|
||||
public unsafe void Flush(CommandBuffer cb, bool insideRenderPass, Action endRenderPass)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void FlushMemoryBarrier(ShaderCollection program, bool inRenderPass)
|
||||
{
|
||||
if (_queuedIncoherentBarrier > IncoherentBarrierType.None)
|
||||
{
|
||||
// We should emit a memory barrier if there's a write access in the program (current program, or program since last barrier)
|
||||
bool hasTextureWrite = _incoherentTextureWriteStages != PipelineStageFlags.None;
|
||||
bool hasBufferWrite = _incoherentBufferWriteStages != PipelineStageFlags.None;
|
||||
bool hasBufferBarrier = _queuedIncoherentBarrier > IncoherentBarrierType.Texture;
|
||||
|
||||
if (hasTextureWrite || (hasBufferBarrier && hasBufferWrite))
|
||||
{
|
||||
AccessFlags access = BaseAccess;
|
||||
|
||||
PipelineStageFlags stages = inRenderPass ? PipelineStageFlags.AllGraphicsBit : PipelineStageFlags.AllCommandsBit;
|
||||
|
||||
if (hasBufferBarrier && hasBufferWrite)
|
||||
{
|
||||
access |= BufferAccess;
|
||||
|
||||
if (_gd.TransformFeedbackApi != null)
|
||||
{
|
||||
access |= AccessFlags.TransformFeedbackWriteBitExt;
|
||||
stages |= PipelineStageFlags.TransformFeedbackBitExt;
|
||||
}
|
||||
}
|
||||
|
||||
if (_queuedIncoherentBarrier == IncoherentBarrierType.CommandBuffer)
|
||||
{
|
||||
access |= CommandBufferAccess;
|
||||
stages |= PipelineStageFlags.DrawIndirectBit;
|
||||
}
|
||||
|
||||
MemoryBarrier barrier = new MemoryBarrier()
|
||||
{
|
||||
SType = StructureType.MemoryBarrier,
|
||||
SrcAccessMask = access,
|
||||
DstAccessMask = access
|
||||
};
|
||||
|
||||
QueueBarrier(barrier, stages, stages);
|
||||
|
||||
_incoherentTextureWriteStages = program?.IncoherentTextureWriteStages ?? PipelineStageFlags.None;
|
||||
|
||||
if (_queuedIncoherentBarrier > IncoherentBarrierType.Texture)
|
||||
{
|
||||
if (program != null)
|
||||
{
|
||||
_incoherentBufferWriteStages = program.IncoherentBufferWriteStages | _extraStages;
|
||||
}
|
||||
else
|
||||
{
|
||||
_incoherentBufferWriteStages = PipelineStageFlags.None;
|
||||
}
|
||||
}
|
||||
|
||||
_queuedIncoherentBarrier = IncoherentBarrierType.None;
|
||||
_queuedFeedbackLoopBarrier = false;
|
||||
}
|
||||
else if (_feedbackLoopActive && _queuedFeedbackLoopBarrier)
|
||||
{
|
||||
// Feedback loop barrier.
|
||||
|
||||
MemoryBarrier barrier = new MemoryBarrier()
|
||||
{
|
||||
SType = StructureType.MemoryBarrier,
|
||||
SrcAccessMask = AccessFlags.ShaderWriteBit,
|
||||
DstAccessMask = AccessFlags.ShaderReadBit
|
||||
};
|
||||
|
||||
QueueBarrier(barrier, PipelineStageFlags.FragmentShaderBit, PipelineStageFlags.AllGraphicsBit);
|
||||
|
||||
_queuedFeedbackLoopBarrier = false;
|
||||
}
|
||||
|
||||
_feedbackLoopActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void Flush(CommandBufferScoped cbs, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
|
||||
{
|
||||
Flush(cbs, null, false, inRenderPass, rpHolder, endRenderPass);
|
||||
}
|
||||
|
||||
public unsafe void Flush(CommandBufferScoped cbs, ShaderCollection program, bool feedbackLoopActive, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
|
||||
{
|
||||
if (program != null)
|
||||
{
|
||||
_incoherentBufferWriteStages |= program.IncoherentBufferWriteStages | _extraStages;
|
||||
_incoherentTextureWriteStages |= program.IncoherentTextureWriteStages;
|
||||
}
|
||||
|
||||
_feedbackLoopActive |= feedbackLoopActive;
|
||||
|
||||
FlushMemoryBarrier(program, inRenderPass);
|
||||
|
||||
if (!inRenderPass && rpHolder != null)
|
||||
{
|
||||
// Render pass is about to begin. Queue any fences that normally interrupt the pass.
|
||||
rpHolder.InsertForcedFences(cbs);
|
||||
}
|
||||
|
||||
while (_queuedBarrierCount > 0)
|
||||
{
|
||||
int memoryCount = 0;
|
||||
@ -86,20 +223,20 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
bool hasBarrier = false;
|
||||
StageFlags flags = default;
|
||||
|
||||
static void AddBarriers<T>(
|
||||
static void AddBarriers<T, T2>(
|
||||
Span<T> target,
|
||||
ref int queuedBarrierCount,
|
||||
ref bool hasBarrier,
|
||||
ref StageFlags flags,
|
||||
ref int count,
|
||||
List<BarrierWithStageFlags<T>> list) where T : unmanaged
|
||||
List<BarrierWithStageFlags<T, T2>> list) where T : unmanaged
|
||||
{
|
||||
int firstMatch = -1;
|
||||
int end = list.Count;
|
||||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
BarrierWithStageFlags<T> barrier = list[i];
|
||||
BarrierWithStageFlags<T, T2> barrier = list[i];
|
||||
|
||||
if (!hasBarrier)
|
||||
{
|
||||
@ -162,21 +299,60 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
if (insideRenderPass)
|
||||
if (inRenderPass && _imageBarriers.Count > 0)
|
||||
{
|
||||
// Image barriers queued in the batch are meant to be globally scoped,
|
||||
// but inside a render pass they're scoped to just the range of the render pass.
|
||||
|
||||
// On MoltenVK, we just break the rules and always use image barrier.
|
||||
// On desktop GPUs, all barriers are globally scoped, so we just replace it with a generic memory barrier.
|
||||
// TODO: On certain GPUs, we need to split render pass so the barrier scope is global. When this is done,
|
||||
// notify the resource that it should add a barrier as soon as a render pass ends to avoid this in future.
|
||||
// Generally, we want to avoid this from happening in the future, so flag the texture to immediately
|
||||
// emit a barrier whenever the current render pass is bound again.
|
||||
|
||||
if (!_gd.IsMoltenVk)
|
||||
bool anyIsNonAttachment = false;
|
||||
|
||||
foreach (BarrierWithStageFlags<ImageMemoryBarrier, TextureStorage> barrier in _imageBarriers)
|
||||
{
|
||||
// If the binding is an attachment, don't add it as a forced fence.
|
||||
bool isAttachment = rpHolder.ContainsAttachment(barrier.Resource);
|
||||
|
||||
if (!isAttachment)
|
||||
{
|
||||
rpHolder.AddForcedFence(barrier.Resource, barrier.Flags.Dest);
|
||||
anyIsNonAttachment = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_gd.IsTBDR)
|
||||
{
|
||||
if (!_gd.IsMoltenVk)
|
||||
{
|
||||
if (!anyIsNonAttachment)
|
||||
{
|
||||
// This case is a feedback loop. To prevent this from causing an absolute performance disaster,
|
||||
// remove the barriers entirely.
|
||||
// If this is not here, there will be a lot of single draw render passes.
|
||||
// TODO: explicit handling for feedback loops, likely outside this class.
|
||||
|
||||
_queuedBarrierCount -= _imageBarriers.Count;
|
||||
_imageBarriers.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// TBDR GPUs are sensitive to barriers, so we need to end the pass to ensure the data is available.
|
||||
// Metal already has hazard tracking so MVK doesn't need this.
|
||||
endRenderPass();
|
||||
inRenderPass = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Generic pipeline memory barriers will work for desktop GPUs.
|
||||
// They do require a few more access flags on the subpass dependency, though.
|
||||
foreach (var barrier in _imageBarriers)
|
||||
{
|
||||
_memoryBarriers.Add(new BarrierWithStageFlags<MemoryBarrier>(
|
||||
_memoryBarriers.Add(new BarrierWithStageFlags<MemoryBarrier, int>(
|
||||
barrier.Flags,
|
||||
new MemoryBarrier()
|
||||
{
|
||||
@ -190,6 +366,22 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
if (inRenderPass && _memoryBarriers.Count > 0)
|
||||
{
|
||||
PipelineStageFlags allFlags = PipelineStageFlags.None;
|
||||
|
||||
foreach (var barrier in _memoryBarriers)
|
||||
{
|
||||
allFlags |= barrier.Flags.Dest;
|
||||
}
|
||||
|
||||
if (allFlags.HasFlag(PipelineStageFlags.DrawIndirectBit) || !_gd.SupportsRenderPassBarrier(allFlags))
|
||||
{
|
||||
endRenderPass();
|
||||
inRenderPass = false;
|
||||
}
|
||||
}
|
||||
|
||||
AddBarriers(_memoryBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref memoryCount, _memoryBarriers);
|
||||
AddBarriers(_bufferBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref bufferCount, _bufferBarriers);
|
||||
AddBarriers(_imageBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref imageCount, _imageBarriers);
|
||||
@ -198,14 +390,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
PipelineStageFlags srcStageFlags = flags.Source;
|
||||
|
||||
if (insideRenderPass)
|
||||
if (inRenderPass)
|
||||
{
|
||||
// Inside a render pass, barrier stages can only be from rasterization.
|
||||
srcStageFlags &= ~PipelineStageFlags.ComputeShaderBit;
|
||||
}
|
||||
|
||||
_gd.Api.CmdPipelineBarrier(
|
||||
cb,
|
||||
cbs.CommandBuffer,
|
||||
srcStageFlags,
|
||||
flags.Dest,
|
||||
0,
|
||||
@ -219,6 +411,43 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueIncoherentBarrier(IncoherentBarrierType type)
|
||||
{
|
||||
if (type > _queuedIncoherentBarrier)
|
||||
{
|
||||
_queuedIncoherentBarrier = type;
|
||||
}
|
||||
|
||||
_queuedFeedbackLoopBarrier = true;
|
||||
}
|
||||
|
||||
public void QueueTextureBarrier()
|
||||
{
|
||||
QueueIncoherentBarrier(IncoherentBarrierType.Texture);
|
||||
}
|
||||
|
||||
public void QueueMemoryBarrier()
|
||||
{
|
||||
QueueIncoherentBarrier(IncoherentBarrierType.All);
|
||||
}
|
||||
|
||||
public void QueueCommandBufferBarrier()
|
||||
{
|
||||
QueueIncoherentBarrier(IncoherentBarrierType.CommandBuffer);
|
||||
}
|
||||
|
||||
public void EnableTfbBarriers(bool enable)
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
_extraStages |= PipelineStageFlags.TransformFeedbackBitExt;
|
||||
}
|
||||
else
|
||||
{
|
||||
_extraStages &= ~PipelineStageFlags.TransformFeedbackBitExt;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_memoryBarrierBatch.Dispose();
|
||||
|
@ -122,7 +122,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Range = (uint)size,
|
||||
};
|
||||
|
||||
_gd.Api.CreateBufferView(_device, bufferViewCreateInfo, null, out var bufferView).ThrowOnError();
|
||||
_gd.Api.CreateBufferView(_device, in bufferViewCreateInfo, null, out var bufferView).ThrowOnError();
|
||||
|
||||
return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), this, _waitable, _buffer);
|
||||
}
|
||||
@ -153,7 +153,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
DependencyFlags.DeviceGroupBit,
|
||||
1,
|
||||
memoryBarrier,
|
||||
in memoryBarrier,
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
@ -770,7 +770,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
0,
|
||||
null,
|
||||
1,
|
||||
memoryBarrier,
|
||||
in memoryBarrier,
|
||||
0,
|
||||
null);
|
||||
}
|
||||
|
@ -221,7 +221,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PBufferBinds = &bufferBind
|
||||
};
|
||||
|
||||
gd.Api.QueueBindSparse(gd.Queue, 1, bindSparseInfo, default).ThrowOnError();
|
||||
gd.Api.QueueBindSparse(gd.Queue, 1, in bindSparseInfo, default).ThrowOnError();
|
||||
}
|
||||
|
||||
var holder = new BufferHolder(gd, _device, buffer, (int)size, storageAllocations);
|
||||
|
@ -25,7 +25,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
var buffer = _buffer.Get(cbs, _offset, _size, true).Value;
|
||||
|
||||
gd.TransformFeedbackApi.CmdBindTransformFeedbackBuffers(cbs.CommandBuffer, binding, 1, buffer, (ulong)_offset, (ulong)_size);
|
||||
ulong offset = (ulong)_offset;
|
||||
ulong size = (ulong)_size;
|
||||
|
||||
gd.TransformFeedbackApi.CmdBindTransformFeedbackBuffers(cbs.CommandBuffer, binding, 1, in buffer, in offset, in size);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,11 +31,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public int SubmissionCount;
|
||||
public CommandBuffer CommandBuffer;
|
||||
public FenceHolder Fence;
|
||||
public SemaphoreHolder Semaphore;
|
||||
|
||||
public List<IAuto> Dependants;
|
||||
public List<MultiFenceHolder> Waitables;
|
||||
public HashSet<SemaphoreHolder> Dependencies;
|
||||
|
||||
public void Initialize(Vk api, Device device, CommandPool pool)
|
||||
{
|
||||
@ -47,11 +45,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Level = CommandBufferLevel.Primary,
|
||||
};
|
||||
|
||||
api.AllocateCommandBuffers(device, allocateInfo, out CommandBuffer);
|
||||
api.AllocateCommandBuffers(device, in allocateInfo, out CommandBuffer);
|
||||
|
||||
Dependants = new List<IAuto>();
|
||||
Waitables = new List<MultiFenceHolder>();
|
||||
Dependencies = new HashSet<SemaphoreHolder>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +83,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
CommandPoolCreateFlags.ResetCommandBufferBit,
|
||||
};
|
||||
|
||||
api.CreateCommandPool(device, commandPoolCreateInfo, null, out _pool).ThrowOnError();
|
||||
api.CreateCommandPool(device, in commandPoolCreateInfo, null, out _pool).ThrowOnError();
|
||||
|
||||
// We need at least 2 command buffers to get texture data in some cases.
|
||||
_totalCommandBuffers = isLight ? 2 : MaxCommandBuffers;
|
||||
@ -143,14 +140,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDependency(int cbIndex, CommandBufferScoped dependencyCbs)
|
||||
{
|
||||
Debug.Assert(_commandBuffers[cbIndex].InUse);
|
||||
var semaphoreHolder = _commandBuffers[dependencyCbs.CommandBufferIndex].Semaphore;
|
||||
semaphoreHolder.Get();
|
||||
_commandBuffers[cbIndex].Dependencies.Add(semaphoreHolder);
|
||||
}
|
||||
|
||||
public void AddWaitable(int cbIndex, MultiFenceHolder waitable)
|
||||
{
|
||||
ref var entry = ref _commandBuffers[cbIndex];
|
||||
@ -264,7 +253,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SType = StructureType.CommandBufferBeginInfo,
|
||||
};
|
||||
|
||||
_api.BeginCommandBuffer(entry.CommandBuffer, commandBufferBeginInfo).ThrowOnError();
|
||||
_api.BeginCommandBuffer(entry.CommandBuffer, in commandBufferBeginInfo).ThrowOnError();
|
||||
|
||||
return new CommandBufferScoped(this, entry.CommandBuffer, cursor);
|
||||
}
|
||||
@ -322,7 +311,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
lock (_queueLock)
|
||||
{
|
||||
_api.QueueSubmit(_queue, 1, sInfo, entry.Fence.GetUnsafe()).ThrowOnError();
|
||||
_api.QueueSubmit(_queue, 1, in sInfo, entry.Fence.GetUnsafe()).ThrowOnError();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -354,14 +343,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
waitable.RemoveBufferUses(cbIndex);
|
||||
}
|
||||
|
||||
foreach (var dependency in entry.Dependencies)
|
||||
{
|
||||
dependency.Put();
|
||||
}
|
||||
|
||||
entry.Dependants.Clear();
|
||||
entry.Waitables.Clear();
|
||||
entry.Dependencies.Clear();
|
||||
entry.Fence?.Dispose();
|
||||
|
||||
if (refreshFence)
|
||||
|
@ -26,11 +26,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_pool.AddWaitable(CommandBufferIndex, waitable);
|
||||
}
|
||||
|
||||
public void AddDependency(CommandBufferScoped dependencyCbs)
|
||||
{
|
||||
_pool.AddDependency(CommandBufferIndex, dependencyCbs);
|
||||
}
|
||||
|
||||
public FenceHolder GetFence()
|
||||
{
|
||||
return _pool.GetFence(CommandBufferIndex);
|
||||
|
@ -43,7 +43,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PBufferInfo = &bufferInfo,
|
||||
};
|
||||
|
||||
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
|
||||
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PBufferInfo = pBufferInfo,
|
||||
};
|
||||
|
||||
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
|
||||
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PImageInfo = &imageInfo,
|
||||
};
|
||||
|
||||
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
|
||||
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,7 +107,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PImageInfo = pImageInfo,
|
||||
};
|
||||
|
||||
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
|
||||
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,7 +144,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PImageInfo = pImageInfo,
|
||||
};
|
||||
|
||||
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
|
||||
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
|
||||
|
||||
i += count - 1;
|
||||
}
|
||||
@ -166,7 +166,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PTexelBufferView = &texelBufferView,
|
||||
};
|
||||
|
||||
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
|
||||
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,7 +200,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PTexelBufferView = pTexelBufferView + i,
|
||||
};
|
||||
|
||||
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
|
||||
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null);
|
||||
}
|
||||
|
||||
i += count;
|
||||
|
@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PPoolSizes = pPoolsSize,
|
||||
};
|
||||
|
||||
Api.CreateDescriptorPool(device, descriptorPoolCreateInfo, null, out _pool).ThrowOnError();
|
||||
Api.CreateDescriptorPool(device, in descriptorPoolCreateInfo, null, out _pool).ThrowOnError();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ using Ryujinx.Graphics.Shader;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using CompareOp = Ryujinx.Graphics.GAL.CompareOp;
|
||||
@ -42,15 +43,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private record struct TextureRef
|
||||
{
|
||||
public ShaderStage Stage;
|
||||
public TextureStorage Storage;
|
||||
public Auto<DisposableImageView> View;
|
||||
public TextureView View;
|
||||
public Auto<DisposableImageView> ImageView;
|
||||
public Auto<DisposableSampler> Sampler;
|
||||
|
||||
public TextureRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view, Auto<DisposableSampler> sampler)
|
||||
public TextureRef(ShaderStage stage, TextureView view, Auto<DisposableImageView> imageView, Auto<DisposableSampler> sampler)
|
||||
{
|
||||
Stage = stage;
|
||||
Storage = storage;
|
||||
View = view;
|
||||
ImageView = imageView;
|
||||
Sampler = sampler;
|
||||
}
|
||||
}
|
||||
@ -58,14 +59,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private record struct ImageRef
|
||||
{
|
||||
public ShaderStage Stage;
|
||||
public TextureStorage Storage;
|
||||
public Auto<DisposableImageView> View;
|
||||
public TextureView View;
|
||||
public Auto<DisposableImageView> ImageView;
|
||||
|
||||
public ImageRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view)
|
||||
public ImageRef(ShaderStage stage, TextureView view, Auto<DisposableImageView> imageView)
|
||||
{
|
||||
Stage = stage;
|
||||
Storage = storage;
|
||||
View = view;
|
||||
ImageView = imageView;
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,6 +125,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private readonly TextureView _dummyTexture;
|
||||
private readonly SamplerHolder _dummySampler;
|
||||
|
||||
public List<TextureView> FeedbackLoopHazards { get; private set; }
|
||||
|
||||
public DescriptorSetUpdater(VulkanRenderer gd, Device device)
|
||||
{
|
||||
_gd = gd;
|
||||
@ -209,10 +212,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_templateUpdater = new();
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
public void Initialize(bool isMainPipeline)
|
||||
{
|
||||
IMemoryOwner<byte> dummyTextureData = ByteMemoryPool.RentCleared(4);
|
||||
MemoryOwner<byte> dummyTextureData = MemoryOwner<byte>.RentCleared(4);
|
||||
_dummyTexture.SetData(dummyTextureData);
|
||||
|
||||
if (isMainPipeline)
|
||||
{
|
||||
FeedbackLoopHazards = new();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool BindingOverlaps(ref DescriptorBufferInfo info, int bindingOffset, int offset, int size)
|
||||
@ -275,6 +283,18 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void InsertBindingBarriers(CommandBufferScoped cbs)
|
||||
{
|
||||
if ((FeedbackLoopHazards?.Count ?? 0) > 0)
|
||||
{
|
||||
// Clear existing hazards - they will be rebuilt.
|
||||
|
||||
foreach (TextureView hazard in FeedbackLoopHazards)
|
||||
{
|
||||
hazard.DecrementHazardUses();
|
||||
}
|
||||
|
||||
FeedbackLoopHazards.Clear();
|
||||
}
|
||||
|
||||
foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.TextureSetIndex])
|
||||
{
|
||||
if (segment.Type == ResourceType.TextureAndSampler)
|
||||
@ -284,7 +304,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
for (int i = 0; i < segment.Count; i++)
|
||||
{
|
||||
ref var texture = ref _textureRefs[segment.Binding + i];
|
||||
texture.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, texture.Stage.ConvertToPipelineStageFlags());
|
||||
texture.View?.PrepareForUsage(cbs, texture.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -305,7 +325,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
for (int i = 0; i < segment.Count; i++)
|
||||
{
|
||||
ref var image = ref _imageRefs[segment.Binding + i];
|
||||
image.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, image.Stage.ConvertToPipelineStageFlags());
|
||||
image.View?.PrepareForUsage(cbs, image.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -385,9 +405,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
else if (image is TextureView view)
|
||||
{
|
||||
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
ref ImageRef iRef = ref _imageRefs[binding];
|
||||
|
||||
_imageRefs[binding] = new(stage, view.Storage, view.GetView(imageFormat).GetIdentityImageView());
|
||||
iRef.View?.ClearUsage(FeedbackLoopHazards);
|
||||
view?.PrepareForUsage(cbs, stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
|
||||
|
||||
iRef = new(stage, view, view.GetView(imageFormat).GetIdentityImageView());
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -486,9 +509,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
else if (texture is TextureView view)
|
||||
{
|
||||
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
ref TextureRef iRef = ref _textureRefs[binding];
|
||||
|
||||
_textureRefs[binding] = new(stage, view.Storage, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler());
|
||||
iRef.View?.ClearUsage(FeedbackLoopHazards);
|
||||
view?.PrepareForUsage(cbs, stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
|
||||
|
||||
iRef = new(stage, view, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler());
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -510,7 +536,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
|
||||
_textureRefs[binding] = new(stage, view.Storage, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler());
|
||||
_textureRefs[binding] = new(stage, view, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler());
|
||||
|
||||
SignalDirty(DirtyFlags.Texture);
|
||||
}
|
||||
@ -836,7 +862,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ref var texture = ref textures[i];
|
||||
ref var refs = ref _textureRefs[binding + i];
|
||||
|
||||
texture.ImageView = refs.View?.Get(cbs).Value ?? default;
|
||||
texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default;
|
||||
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
|
||||
|
||||
if (texture.ImageView.Handle == 0)
|
||||
@ -886,7 +912,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
images[i].ImageView = _imageRefs[binding + i].View?.Get(cbs).Value ?? default;
|
||||
images[i].ImageView = _imageRefs[binding + i].ImageView?.Get(cbs).Value ?? default;
|
||||
}
|
||||
|
||||
tu.Push<DescriptorImageInfo>(images[..count]);
|
||||
@ -957,7 +983,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ref var texture = ref textures[i];
|
||||
ref var refs = ref _textureRefs[binding + i];
|
||||
|
||||
texture.ImageView = refs.View?.Get(cbs).Value ?? default;
|
||||
texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default;
|
||||
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
|
||||
|
||||
if (texture.ImageView.Handle == 0)
|
||||
|
@ -59,14 +59,14 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
var scalingResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||
|
||||
var sharpeningResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 3)
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 4)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||
|
||||
_sampler = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||
|
||||
|
@ -42,7 +42,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
var resourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||
|
||||
_samplerLinear = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||
|
||||
|
@ -81,20 +81,20 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
var edgeResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||
|
||||
var blendResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 4)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||
|
||||
var neighbourResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||
|
||||
_samplerLinear = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||
|
||||
|
12
src/Ryujinx.Graphics.Vulkan/FeedbackLoopAspects.cs
Normal file
12
src/Ryujinx.Graphics.Vulkan/FeedbackLoopAspects.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
[Flags]
|
||||
internal enum FeedbackLoopAspects
|
||||
{
|
||||
None = 0,
|
||||
Color = 1 << 0,
|
||||
Depth = 1 << 1,
|
||||
}
|
||||
}
|
@ -250,7 +250,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Layers = Layers,
|
||||
};
|
||||
|
||||
api.CreateFramebuffer(_device, framebufferCreateInfo, null, out var framebuffer).ThrowOnError();
|
||||
api.CreateFramebuffer(_device, in framebufferCreateInfo, null, out var framebuffer).ThrowOnError();
|
||||
return new Auto<DisposableFramebuffer>(new DisposableFramebuffer(api, _device, framebuffer), null, _attachments);
|
||||
}
|
||||
|
||||
@ -286,10 +286,44 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_depthStencil?.Storage?.QueueLoadOpBarrier(cbs, true);
|
||||
|
||||
gd.Barriers.Flush(cbs.CommandBuffer, false, null);
|
||||
gd.Barriers.Flush(cbs, false, null, null);
|
||||
}
|
||||
|
||||
public (Auto<DisposableRenderPass> renderPass, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
||||
public void AddStoreOpUsage()
|
||||
{
|
||||
if (_colors != null)
|
||||
{
|
||||
foreach (var color in _colors)
|
||||
{
|
||||
color.Storage?.AddStoreOpUsage(false);
|
||||
}
|
||||
}
|
||||
|
||||
_depthStencil?.Storage?.AddStoreOpUsage(true);
|
||||
}
|
||||
|
||||
public void ClearBindings()
|
||||
{
|
||||
_depthStencil?.Storage.ClearBindings();
|
||||
|
||||
for (int i = 0; i < _colorsCanonical.Length; i++)
|
||||
{
|
||||
_colorsCanonical[i]?.Storage.ClearBindings();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddBindings()
|
||||
{
|
||||
_depthStencil?.Storage.AddBinding(_depthStencil);
|
||||
|
||||
for (int i = 0; i < _colorsCanonical.Length; i++)
|
||||
{
|
||||
TextureView color = _colorsCanonical[i];
|
||||
color?.Storage.AddBinding(color);
|
||||
}
|
||||
}
|
||||
|
||||
public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
CommandBufferScoped cbs)
|
||||
|
@ -46,6 +46,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public readonly bool SupportsViewportArray2;
|
||||
public readonly bool SupportsHostImportedMemory;
|
||||
public readonly bool SupportsDepthClipControl;
|
||||
public readonly bool SupportsAttachmentFeedbackLoop;
|
||||
public readonly bool SupportsDynamicAttachmentFeedbackLoop;
|
||||
public readonly uint SubgroupSize;
|
||||
public readonly SampleCountFlags SupportedSampleCounts;
|
||||
public readonly PortabilitySubsetFlags PortabilitySubset;
|
||||
@ -84,6 +86,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
bool supportsViewportArray2,
|
||||
bool supportsHostImportedMemory,
|
||||
bool supportsDepthClipControl,
|
||||
bool supportsAttachmentFeedbackLoop,
|
||||
bool supportsDynamicAttachmentFeedbackLoop,
|
||||
uint subgroupSize,
|
||||
SampleCountFlags supportedSampleCounts,
|
||||
PortabilitySubsetFlags portabilitySubset,
|
||||
@ -121,6 +125,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SupportsViewportArray2 = supportsViewportArray2;
|
||||
SupportsHostImportedMemory = supportsHostImportedMemory;
|
||||
SupportsDepthClipControl = supportsDepthClipControl;
|
||||
SupportsAttachmentFeedbackLoop = supportsAttachmentFeedbackLoop;
|
||||
SupportsDynamicAttachmentFeedbackLoop = supportsDynamicAttachmentFeedbackLoop;
|
||||
SubgroupSize = subgroupSize;
|
||||
SupportedSampleCounts = supportedSampleCounts;
|
||||
PortabilitySubset = portabilitySubset;
|
||||
|
@ -115,7 +115,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var strideChangeResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2).Build();
|
||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build();
|
||||
|
||||
_programStrideChange = gd.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
@ -125,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var colorCopyResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 0)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||
|
||||
_programColorCopyShortening = gd.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
@ -155,7 +155,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var convertD32S8ToD24S8ResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2).Build();
|
||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build();
|
||||
|
||||
_programConvertD32S8ToD24S8 = gd.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
@ -165,7 +165,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var convertIndexBufferResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2).Build();
|
||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build();
|
||||
|
||||
_programConvertIndexBuffer = gd.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
@ -175,7 +175,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var convertIndirectDataResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true)
|
||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 3).Build();
|
||||
|
||||
_programConvertIndirectData = gd.CreateProgramWithMinimalLayout(new[]
|
||||
|
@ -115,7 +115,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PNext = &importInfo,
|
||||
};
|
||||
|
||||
Result result = _api.AllocateMemory(_device, memoryAllocateInfo, null, out var deviceMemory);
|
||||
Result result = _api.AllocateMemory(_device, in memoryAllocateInfo, null, out var deviceMemory);
|
||||
|
||||
if (result < Result.Success)
|
||||
{
|
||||
|
@ -95,7 +95,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
_cachedCommandBufferIndex = -1;
|
||||
_storages = null;
|
||||
SetDirty(_gd);
|
||||
SetDirty(_gd, isImage: true);
|
||||
}
|
||||
|
||||
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)
|
||||
|
@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
MemoryTypeIndex = (uint)MemoryTypeIndex,
|
||||
};
|
||||
|
||||
_api.AllocateMemory(_device, memoryAllocateInfo, null, out var deviceMemory).ThrowOnError();
|
||||
_api.AllocateMemory(_device, in memoryAllocateInfo, null, out var deviceMemory).ThrowOnError();
|
||||
|
||||
IntPtr hostPointer = IntPtr.Zero;
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
using Silk.NET.Core.Loader;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
@ -8,6 +9,8 @@ namespace Ryujinx.Graphics.Vulkan.MoltenVK
|
||||
[SupportedOSPlatform("macos")]
|
||||
public static partial class MVKInitialization
|
||||
{
|
||||
private const string VulkanLib = "libvulkan.dylib";
|
||||
|
||||
[LibraryImport("libMoltenVK.dylib")]
|
||||
private static partial Result vkGetMoltenVKConfigurationMVK(IntPtr unusedInstance, out MVKConfiguration config, in IntPtr configSize);
|
||||
|
||||
@ -29,5 +32,20 @@ namespace Ryujinx.Graphics.Vulkan.MoltenVK
|
||||
|
||||
vkSetMoltenVKConfigurationMVK(IntPtr.Zero, config, configSize);
|
||||
}
|
||||
|
||||
private static string[] Resolver(string path)
|
||||
{
|
||||
if (path.EndsWith(VulkanLib))
|
||||
{
|
||||
path = path[..^VulkanLib.Length] + "libMoltenVK.dylib";
|
||||
return [path];
|
||||
}
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
public static void InitializeResolver()
|
||||
{
|
||||
((DefaultPathResolver)PathResolver.Default).Resolvers.Insert(0, Resolver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
@ -33,6 +34,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public readonly Action EndRenderPassDelegate;
|
||||
|
||||
protected PipelineDynamicState DynamicState;
|
||||
protected bool IsMainPipeline;
|
||||
private PipelineState _newState;
|
||||
private bool _graphicsStateDirty;
|
||||
private bool _computeStateDirty;
|
||||
@ -55,6 +57,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
protected FramebufferParams FramebufferParams;
|
||||
private Auto<DisposableFramebuffer> _framebuffer;
|
||||
private RenderPassHolder _rpHolder;
|
||||
private Auto<DisposableRenderPass> _renderPass;
|
||||
private RenderPassHolder _nullRenderPass;
|
||||
private int _writtenAttachmentCount;
|
||||
@ -84,9 +87,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private bool _tfEnabled;
|
||||
private bool _tfActive;
|
||||
|
||||
private readonly PipelineColorBlendAttachmentState[] _storedBlend;
|
||||
private FeedbackLoopAspects _feedbackLoop;
|
||||
private bool _passWritesDepthStencil;
|
||||
|
||||
private ulong _drawCountSinceBarrier;
|
||||
private readonly PipelineColorBlendAttachmentState[] _storedBlend;
|
||||
public ulong DrawCount { get; private set; }
|
||||
public bool RenderPassActive { get; private set; }
|
||||
|
||||
@ -103,7 +107,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SType = StructureType.PipelineCacheCreateInfo,
|
||||
};
|
||||
|
||||
gd.Api.CreatePipelineCache(device, pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError();
|
||||
gd.Api.CreatePipelineCache(device, in pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError();
|
||||
|
||||
_descriptorSetUpdater = new DescriptorSetUpdater(gd, device);
|
||||
_vertexBufferUpdater = new VertexBufferUpdater(gd);
|
||||
@ -127,7 +131,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_descriptorSetUpdater.Initialize();
|
||||
_descriptorSetUpdater.Initialize(IsMainPipeline);
|
||||
|
||||
QuadsToTrisPattern = new IndexBufferPattern(Gd, 4, 6, 0, new[] { 0, 1, 2, 0, 2, 3 }, 4, false);
|
||||
TriFanToTrisPattern = new IndexBufferPattern(Gd, 3, 3, 2, new[] { int.MinValue, -1, 0 }, 1, true);
|
||||
@ -135,48 +139,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public unsafe void Barrier()
|
||||
{
|
||||
if (_drawCountSinceBarrier != DrawCount)
|
||||
{
|
||||
_drawCountSinceBarrier = DrawCount;
|
||||
|
||||
// Barriers are not supported inside a render pass on Apple GPUs.
|
||||
// As a workaround, end the render pass.
|
||||
if (Gd.Vendor == Vendor.Apple)
|
||||
{
|
||||
EndRenderPass();
|
||||
}
|
||||
}
|
||||
|
||||
MemoryBarrier memoryBarrier = new()
|
||||
{
|
||||
SType = StructureType.MemoryBarrier,
|
||||
SrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
|
||||
DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
|
||||
};
|
||||
|
||||
PipelineStageFlags pipelineStageFlags = PipelineStageFlags.VertexShaderBit | PipelineStageFlags.FragmentShaderBit;
|
||||
|
||||
if (Gd.Capabilities.SupportsGeometryShader)
|
||||
{
|
||||
pipelineStageFlags |= PipelineStageFlags.GeometryShaderBit;
|
||||
}
|
||||
|
||||
if (Gd.Capabilities.SupportsTessellationShader)
|
||||
{
|
||||
pipelineStageFlags |= PipelineStageFlags.TessellationControlShaderBit | PipelineStageFlags.TessellationEvaluationShaderBit;
|
||||
}
|
||||
|
||||
Gd.Api.CmdPipelineBarrier(
|
||||
CommandBuffer,
|
||||
pipelineStageFlags,
|
||||
pipelineStageFlags,
|
||||
0,
|
||||
1,
|
||||
memoryBarrier,
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
null);
|
||||
Gd.Barriers.QueueMemoryBarrier();
|
||||
}
|
||||
|
||||
public void ComputeBarrier()
|
||||
@ -203,6 +166,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void BeginTransformFeedback(PrimitiveTopology topology)
|
||||
{
|
||||
Gd.Barriers.EnableTfbBarriers(true);
|
||||
_tfEnabled = true;
|
||||
}
|
||||
|
||||
@ -249,7 +213,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
CreateRenderPass();
|
||||
}
|
||||
|
||||
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
|
||||
Gd.Barriers.Flush(Cbs, RenderPassActive, _rpHolder, EndRenderPassDelegate);
|
||||
|
||||
BeginRenderPass();
|
||||
|
||||
@ -287,7 +251,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
CreateRenderPass();
|
||||
}
|
||||
|
||||
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
|
||||
Gd.Barriers.Flush(Cbs, RenderPassActive, _rpHolder, EndRenderPassDelegate);
|
||||
|
||||
BeginRenderPass();
|
||||
|
||||
@ -299,24 +263,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public unsafe void CommandBufferBarrier()
|
||||
{
|
||||
MemoryBarrier memoryBarrier = new()
|
||||
{
|
||||
SType = StructureType.MemoryBarrier,
|
||||
SrcAccessMask = BufferHolder.DefaultAccessFlags,
|
||||
DstAccessMask = AccessFlags.IndirectCommandReadBit,
|
||||
};
|
||||
|
||||
Gd.Api.CmdPipelineBarrier(
|
||||
CommandBuffer,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.DrawIndirectBit,
|
||||
0,
|
||||
1,
|
||||
memoryBarrier,
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
null);
|
||||
Gd.Barriers.QueueCommandBufferBarrier();
|
||||
}
|
||||
|
||||
public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
|
||||
@ -722,6 +669,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void EndTransformFeedback()
|
||||
{
|
||||
Gd.Barriers.EnableTfbBarriers(false);
|
||||
PauseTransformFeedbackInternal();
|
||||
_tfEnabled = false;
|
||||
}
|
||||
@ -871,6 +819,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_newState.DepthTestEnable = depthTest.TestEnable;
|
||||
_newState.DepthWriteEnable = depthTest.WriteEnable;
|
||||
_newState.DepthCompareOp = depthTest.Func.Convert();
|
||||
|
||||
UpdatePassDepthStencil();
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
@ -1020,6 +970,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
_newState.RasterizerDiscardEnable = discard;
|
||||
SignalStateChange();
|
||||
|
||||
if (!discard && Gd.IsQualcommProprietary)
|
||||
{
|
||||
// On Adreno, enabling rasterizer discard somehow corrupts the viewport state.
|
||||
// Force it to be updated on next use to work around this bug.
|
||||
DynamicState.ForceAllDirty();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask)
|
||||
@ -1129,6 +1086,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert();
|
||||
_newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert();
|
||||
_newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert();
|
||||
|
||||
UpdatePassDepthStencil();
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
@ -1401,24 +1360,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public unsafe void TextureBarrier()
|
||||
{
|
||||
MemoryBarrier memoryBarrier = new()
|
||||
{
|
||||
SType = StructureType.MemoryBarrier,
|
||||
SrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
|
||||
DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
|
||||
};
|
||||
|
||||
Gd.Api.CmdPipelineBarrier(
|
||||
CommandBuffer,
|
||||
PipelineStageFlags.FragmentShaderBit,
|
||||
PipelineStageFlags.FragmentShaderBit,
|
||||
0,
|
||||
1,
|
||||
memoryBarrier,
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
null);
|
||||
Gd.Barriers.QueueTextureBarrier();
|
||||
}
|
||||
|
||||
public void TextureBarrierTiled()
|
||||
@ -1493,7 +1435,23 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
if (IsMainPipeline)
|
||||
{
|
||||
FramebufferParams?.ClearBindings();
|
||||
}
|
||||
|
||||
FramebufferParams = new FramebufferParams(Device, colors, depthStencil);
|
||||
|
||||
if (IsMainPipeline)
|
||||
{
|
||||
FramebufferParams.AddBindings();
|
||||
|
||||
_newState.FeedbackLoopAspects = FeedbackLoopAspects.None;
|
||||
_bindingBarriersDirty = true;
|
||||
}
|
||||
|
||||
_passWritesDepthStencil = false;
|
||||
UpdatePassDepthStencil();
|
||||
UpdatePipelineAttachmentFormats();
|
||||
}
|
||||
|
||||
@ -1525,12 +1483,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
// Use the null framebuffer.
|
||||
_nullRenderPass ??= new RenderPassHolder(Gd, Device, new RenderPassCacheKey(), FramebufferParams);
|
||||
|
||||
_rpHolder = _nullRenderPass;
|
||||
_renderPass = _nullRenderPass.GetRenderPass();
|
||||
_framebuffer = _nullRenderPass.GetFramebuffer(Gd, Cbs, FramebufferParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
(_renderPass, _framebuffer) = FramebufferParams.GetPassAndFramebuffer(Gd, Device, Cbs);
|
||||
(_rpHolder, _framebuffer) = FramebufferParams.GetPassAndFramebuffer(Gd, Device, Cbs);
|
||||
|
||||
_renderPass = _rpHolder.GetRenderPass();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1557,11 +1518,82 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
|
||||
Gd.Barriers.Flush(Cbs, _program, _feedbackLoop != 0, RenderPassActive, _rpHolder, EndRenderPassDelegate);
|
||||
|
||||
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute);
|
||||
}
|
||||
|
||||
private bool ChangeFeedbackLoop(FeedbackLoopAspects aspects)
|
||||
{
|
||||
if (_feedbackLoop != aspects)
|
||||
{
|
||||
if (Gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
|
||||
{
|
||||
DynamicState.SetFeedbackLoop(aspects);
|
||||
}
|
||||
else
|
||||
{
|
||||
_newState.FeedbackLoopAspects = aspects;
|
||||
}
|
||||
|
||||
_feedbackLoop = aspects;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool UpdateFeedbackLoop()
|
||||
{
|
||||
List<TextureView> hazards = _descriptorSetUpdater.FeedbackLoopHazards;
|
||||
|
||||
if ((hazards?.Count ?? 0) > 0)
|
||||
{
|
||||
FeedbackLoopAspects aspects = 0;
|
||||
|
||||
foreach (TextureView view in hazards)
|
||||
{
|
||||
// May need to enforce feedback loop layout here in the future.
|
||||
// Though technically, it should always work with the general layout.
|
||||
|
||||
if (view.Info.Format.IsDepthOrStencil())
|
||||
{
|
||||
if (_passWritesDepthStencil)
|
||||
{
|
||||
// If depth/stencil isn't written in the pass, it doesn't count as a feedback loop.
|
||||
|
||||
aspects |= FeedbackLoopAspects.Depth;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
aspects |= FeedbackLoopAspects.Color;
|
||||
}
|
||||
}
|
||||
|
||||
return ChangeFeedbackLoop(aspects);
|
||||
}
|
||||
else if (_feedbackLoop != 0)
|
||||
{
|
||||
return ChangeFeedbackLoop(FeedbackLoopAspects.None);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void UpdatePassDepthStencil()
|
||||
{
|
||||
if (!RenderPassActive)
|
||||
{
|
||||
_passWritesDepthStencil = false;
|
||||
}
|
||||
|
||||
// Stencil test being enabled doesn't necessarily mean a write, but it's not critical to check.
|
||||
_passWritesDepthStencil |= (_newState.DepthTestEnable && _newState.DepthWriteEnable) || _newState.StencilTestEnable;
|
||||
}
|
||||
|
||||
private bool RecreateGraphicsPipelineIfNeeded()
|
||||
{
|
||||
if (AutoFlush.ShouldFlushDraw(DrawCount))
|
||||
@ -1569,7 +1601,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Gd.FlushAllCommands();
|
||||
}
|
||||
|
||||
DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
|
||||
DynamicState.ReplayIfDirty(Gd, CommandBuffer);
|
||||
|
||||
if (_needsIndexBufferRebind && _indexBufferPattern == null)
|
||||
{
|
||||
@ -1603,7 +1635,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_vertexBufferUpdater.Commit(Cbs);
|
||||
}
|
||||
|
||||
if (_graphicsStateDirty || Pbp != PipelineBindPoint.Graphics)
|
||||
if (_bindingBarriersDirty)
|
||||
{
|
||||
// Stale barriers may have been activated by switching program. Emit any that are relevant.
|
||||
_descriptorSetUpdater.InsertBindingBarriers(Cbs);
|
||||
|
||||
_bindingBarriersDirty = false;
|
||||
}
|
||||
|
||||
if (UpdateFeedbackLoop() || _graphicsStateDirty || Pbp != PipelineBindPoint.Graphics)
|
||||
{
|
||||
if (!CreatePipeline(PipelineBindPoint.Graphics))
|
||||
{
|
||||
@ -1612,17 +1652,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_graphicsStateDirty = false;
|
||||
Pbp = PipelineBindPoint.Graphics;
|
||||
|
||||
if (_bindingBarriersDirty)
|
||||
{
|
||||
// Stale barriers may have been activated by switching program. Emit any that are relevant.
|
||||
_descriptorSetUpdater.InsertBindingBarriers(Cbs);
|
||||
|
||||
_bindingBarriersDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
|
||||
Gd.Barriers.Flush(Cbs, _program, _feedbackLoop != 0, RenderPassActive, _rpHolder, EndRenderPassDelegate);
|
||||
|
||||
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics);
|
||||
|
||||
@ -1692,7 +1724,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ClearValueCount = 1,
|
||||
};
|
||||
|
||||
Gd.Api.CmdBeginRenderPass(CommandBuffer, renderPassBeginInfo, SubpassContents.Inline);
|
||||
Gd.Api.CmdBeginRenderPass(CommandBuffer, in renderPassBeginInfo, SubpassContents.Inline);
|
||||
RenderPassActive = true;
|
||||
}
|
||||
}
|
||||
@ -1701,6 +1733,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
if (RenderPassActive)
|
||||
{
|
||||
FramebufferParams.AddStoreOpUsage();
|
||||
|
||||
PauseTransformFeedbackInternal();
|
||||
Gd.Api.CmdEndRenderPass(CommandBuffer);
|
||||
SignalRenderPassEnd();
|
||||
|
@ -9,13 +9,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
static class PipelineConverter
|
||||
{
|
||||
private const AccessFlags SubpassAccessMask =
|
||||
AccessFlags.MemoryReadBit |
|
||||
AccessFlags.MemoryWriteBit |
|
||||
AccessFlags.ShaderReadBit |
|
||||
AccessFlags.ColorAttachmentWriteBit |
|
||||
AccessFlags.DepthStencilAttachmentWriteBit;
|
||||
|
||||
public static unsafe DisposableRenderPass ToRenderPass(this ProgramPipelineState state, VulkanRenderer gd, Device device)
|
||||
{
|
||||
const int MaxAttachments = Constants.MaxRenderTargets + 1;
|
||||
@ -108,7 +101,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
var subpassDependency = CreateSubpassDependency();
|
||||
var subpassDependency = CreateSubpassDependency(gd);
|
||||
|
||||
fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
|
||||
{
|
||||
@ -123,35 +116,39 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
DependencyCount = 1,
|
||||
};
|
||||
|
||||
gd.Api.CreateRenderPass(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError();
|
||||
gd.Api.CreateRenderPass(device, in renderPassCreateInfo, null, out var renderPass).ThrowOnError();
|
||||
|
||||
return new DisposableRenderPass(gd.Api, device, renderPass);
|
||||
}
|
||||
}
|
||||
|
||||
public static SubpassDependency CreateSubpassDependency()
|
||||
public static SubpassDependency CreateSubpassDependency(VulkanRenderer gd)
|
||||
{
|
||||
var (access, stages) = BarrierBatch.GetSubpassAccessSuperset(gd);
|
||||
|
||||
return new SubpassDependency(
|
||||
0,
|
||||
0,
|
||||
PipelineStageFlags.AllGraphicsBit,
|
||||
PipelineStageFlags.AllGraphicsBit,
|
||||
SubpassAccessMask,
|
||||
SubpassAccessMask,
|
||||
stages,
|
||||
stages,
|
||||
access,
|
||||
access,
|
||||
0);
|
||||
}
|
||||
|
||||
public unsafe static SubpassDependency2 CreateSubpassDependency2()
|
||||
public unsafe static SubpassDependency2 CreateSubpassDependency2(VulkanRenderer gd)
|
||||
{
|
||||
var (access, stages) = BarrierBatch.GetSubpassAccessSuperset(gd);
|
||||
|
||||
return new SubpassDependency2(
|
||||
StructureType.SubpassDependency2,
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
PipelineStageFlags.AllGraphicsBit,
|
||||
PipelineStageFlags.AllGraphicsBit,
|
||||
SubpassAccessMask,
|
||||
SubpassAccessMask,
|
||||
stages,
|
||||
stages,
|
||||
access,
|
||||
access,
|
||||
0);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Silk.NET.Vulkan;
|
||||
using Silk.NET.Vulkan.Extensions.EXT;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
@ -21,6 +22,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private Array4<float> _blendConstants;
|
||||
|
||||
private FeedbackLoopAspects _feedbackLoopAspects;
|
||||
|
||||
public uint ViewportsCount;
|
||||
public Array16<Viewport> Viewports;
|
||||
|
||||
@ -32,7 +35,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Scissor = 1 << 2,
|
||||
Stencil = 1 << 3,
|
||||
Viewport = 1 << 4,
|
||||
All = Blend | DepthBias | Scissor | Stencil | Viewport,
|
||||
FeedbackLoop = 1 << 5,
|
||||
All = Blend | DepthBias | Scissor | Stencil | Viewport | FeedbackLoop,
|
||||
}
|
||||
|
||||
private DirtyFlags _dirty;
|
||||
@ -99,13 +103,22 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFeedbackLoop(FeedbackLoopAspects aspects)
|
||||
{
|
||||
_feedbackLoopAspects = aspects;
|
||||
|
||||
_dirty |= DirtyFlags.FeedbackLoop;
|
||||
}
|
||||
|
||||
public void ForceAllDirty()
|
||||
{
|
||||
_dirty = DirtyFlags.All;
|
||||
}
|
||||
|
||||
public void ReplayIfDirty(Vk api, CommandBuffer commandBuffer)
|
||||
public void ReplayIfDirty(VulkanRenderer gd, CommandBuffer commandBuffer)
|
||||
{
|
||||
Vk api = gd.Api;
|
||||
|
||||
if (_dirty.HasFlag(DirtyFlags.Blend))
|
||||
{
|
||||
RecordBlend(api, commandBuffer);
|
||||
@ -131,6 +144,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
RecordViewport(api, commandBuffer);
|
||||
}
|
||||
|
||||
if (_dirty.HasFlag(DirtyFlags.FeedbackLoop) && gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
|
||||
{
|
||||
RecordFeedbackLoop(gd.DynamicFeedbackLoopApi, commandBuffer);
|
||||
}
|
||||
|
||||
_dirty = DirtyFlags.None;
|
||||
}
|
||||
|
||||
@ -169,5 +187,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
api.CmdSetViewport(commandBuffer, 0, ViewportsCount, Viewports.AsSpan());
|
||||
}
|
||||
}
|
||||
|
||||
private readonly void RecordFeedbackLoop(ExtAttachmentFeedbackLoopDynamicState api, CommandBuffer commandBuffer)
|
||||
{
|
||||
ImageAspectFlags aspects = (_feedbackLoopAspects & FeedbackLoopAspects.Color) != 0 ? ImageAspectFlags.ColorBit : 0;
|
||||
|
||||
if ((_feedbackLoopAspects & FeedbackLoopAspects.Depth) != 0)
|
||||
{
|
||||
aspects |= ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit;
|
||||
}
|
||||
|
||||
api.CmdSetAttachmentFeedbackLoopEnable(commandBuffer, aspects);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_activeBufferMirrors = new();
|
||||
|
||||
CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer;
|
||||
|
||||
IsMainPipeline = true;
|
||||
}
|
||||
|
||||
private void CopyPendingQuery()
|
||||
@ -47,10 +49,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return;
|
||||
}
|
||||
|
||||
if (componentMask != 0xf)
|
||||
if (componentMask != 0xf || Gd.IsQualcommProprietary)
|
||||
{
|
||||
// We can't use CmdClearAttachments if not writing all components,
|
||||
// because on Vulkan, the pipeline state does not affect clears.
|
||||
// On proprietary Adreno drivers, CmdClearAttachments appears to execute out of order, so it's better to not use it at all.
|
||||
var dstTexture = FramebufferParams.GetColorView(index);
|
||||
if (dstTexture == null)
|
||||
{
|
||||
@ -87,10 +90,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return;
|
||||
}
|
||||
|
||||
if (stencilMask != 0 && stencilMask != 0xff)
|
||||
if ((stencilMask != 0 && stencilMask != 0xff) || Gd.IsQualcommProprietary)
|
||||
{
|
||||
// We can't use CmdClearAttachments if not clearing all (mask is all ones, 0xFF) or none (mask is 0) of the stencil bits,
|
||||
// because on Vulkan, the pipeline state does not affect clears.
|
||||
// On proprietary Adreno drivers, CmdClearAttachments appears to execute out of order, so it's better to not use it at all.
|
||||
var dstTexture = FramebufferParams.GetDepthStencilView();
|
||||
if (dstTexture == null)
|
||||
{
|
||||
@ -233,7 +237,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (Pipeline != null && Pbp == PipelineBindPoint.Graphics)
|
||||
{
|
||||
DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
|
||||
DynamicState.ReplayIfDirty(Gd, CommandBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -255,7 +259,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PreloadCbs = null;
|
||||
}
|
||||
|
||||
Gd.Barriers.Flush(Cbs.CommandBuffer, false, null);
|
||||
Gd.Barriers.Flush(Cbs, false, null, null);
|
||||
CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
|
||||
Gd.RegisterFlush();
|
||||
|
||||
|
@ -91,7 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Flags = flags,
|
||||
};
|
||||
|
||||
gd.Api.CreateDescriptorSetLayout(device, descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError();
|
||||
gd.Api.CreateDescriptorSetLayout(device, in descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
struct PipelineState : IDisposable
|
||||
{
|
||||
private const int RequiredSubgroupSize = 32;
|
||||
private const int MaxDynamicStatesCount = 9;
|
||||
|
||||
public PipelineUid Internal;
|
||||
|
||||
@ -299,6 +300,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6);
|
||||
}
|
||||
|
||||
public FeedbackLoopAspects FeedbackLoopAspects
|
||||
{
|
||||
readonly get => (FeedbackLoopAspects)((Internal.Id8 >> 7) & 0x3);
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFE7F) | (((ulong)value) << 7);
|
||||
}
|
||||
|
||||
public bool HasTessellationControlShader;
|
||||
public NativeArray<PipelineShaderStageCreateInfo> Stages;
|
||||
public PipelineLayout PipelineLayout;
|
||||
@ -439,7 +446,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
SType = StructureType.PipelineInputAssemblyStateCreateInfo,
|
||||
PrimitiveRestartEnable = primitiveRestartEnable,
|
||||
Topology = Topology,
|
||||
Topology = HasTessellationControlShader ? PrimitiveTopology.PatchList : Topology,
|
||||
};
|
||||
|
||||
var tessellationState = new PipelineTessellationStateCreateInfo
|
||||
@ -564,9 +571,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
|
||||
bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
|
||||
int dynamicStatesCount = supportsExtDynamicState ? 8 : 7;
|
||||
bool supportsFeedbackLoopDynamicState = gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop;
|
||||
|
||||
DynamicState* dynamicStates = stackalloc DynamicState[dynamicStatesCount];
|
||||
DynamicState* dynamicStates = stackalloc DynamicState[MaxDynamicStatesCount];
|
||||
|
||||
int dynamicStatesCount = 7;
|
||||
|
||||
dynamicStates[0] = DynamicState.Viewport;
|
||||
dynamicStates[1] = DynamicState.Scissor;
|
||||
@ -578,7 +587,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (supportsExtDynamicState)
|
||||
{
|
||||
dynamicStates[7] = DynamicState.VertexInputBindingStrideExt;
|
||||
dynamicStates[dynamicStatesCount++] = DynamicState.VertexInputBindingStrideExt;
|
||||
}
|
||||
|
||||
if (supportsFeedbackLoopDynamicState)
|
||||
{
|
||||
dynamicStates[dynamicStatesCount++] = DynamicState.AttachmentFeedbackLoopEnableExt;
|
||||
}
|
||||
|
||||
var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo
|
||||
@ -588,9 +602,27 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PDynamicStates = dynamicStates,
|
||||
};
|
||||
|
||||
PipelineCreateFlags flags = 0;
|
||||
|
||||
if (gd.Capabilities.SupportsAttachmentFeedbackLoop)
|
||||
{
|
||||
FeedbackLoopAspects aspects = FeedbackLoopAspects;
|
||||
|
||||
if ((aspects & FeedbackLoopAspects.Color) != 0)
|
||||
{
|
||||
flags |= PipelineCreateFlags.CreateColorAttachmentFeedbackLoopBitExt;
|
||||
}
|
||||
|
||||
if ((aspects & FeedbackLoopAspects.Depth) != 0)
|
||||
{
|
||||
flags |= PipelineCreateFlags.CreateDepthStencilAttachmentFeedbackLoopBitExt;
|
||||
}
|
||||
}
|
||||
|
||||
var pipelineCreateInfo = new GraphicsPipelineCreateInfo
|
||||
{
|
||||
SType = StructureType.GraphicsPipelineCreateInfo,
|
||||
Flags = flags,
|
||||
StageCount = StagesCount,
|
||||
PStages = Stages.Pointer,
|
||||
PVertexInputState = &vertexInputState,
|
||||
|
@ -52,7 +52,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
||||
PipelineStatistics = flags,
|
||||
};
|
||||
|
||||
gd.Api.CreateQueryPool(device, queryPoolCreateInfo, null, out _queryPool).ThrowOnError();
|
||||
gd.Api.CreateQueryPool(device, in queryPoolCreateInfo, null, out _queryPool).ThrowOnError();
|
||||
}
|
||||
|
||||
var buffer = gd.BufferManager.Create(gd, sizeof(long), forConditionalRendering: true);
|
||||
|
@ -1,5 +1,7 @@
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
@ -29,10 +31,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
private readonly record struct ForcedFence(TextureStorage Texture, PipelineStageFlags StageFlags);
|
||||
|
||||
private readonly TextureView[] _textures;
|
||||
private readonly Auto<DisposableRenderPass> _renderPass;
|
||||
private readonly HashTableSlim<FramebufferCacheKey, Auto<DisposableFramebuffer>> _framebuffers;
|
||||
private readonly RenderPassCacheKey _key;
|
||||
private readonly List<ForcedFence> _forcedFences;
|
||||
|
||||
public unsafe RenderPassHolder(VulkanRenderer gd, Device device, RenderPassCacheKey key, FramebufferParams fb)
|
||||
{
|
||||
@ -105,7 +110,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
var subpassDependency = PipelineConverter.CreateSubpassDependency();
|
||||
var subpassDependency = PipelineConverter.CreateSubpassDependency(gd);
|
||||
|
||||
fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
|
||||
{
|
||||
@ -120,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
DependencyCount = 1,
|
||||
};
|
||||
|
||||
gd.Api.CreateRenderPass(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError();
|
||||
gd.Api.CreateRenderPass(device, in renderPassCreateInfo, null, out var renderPass).ThrowOnError();
|
||||
|
||||
_renderPass = new Auto<DisposableRenderPass>(new DisposableRenderPass(gd.Api, device, renderPass));
|
||||
}
|
||||
@ -138,6 +143,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_textures = textures;
|
||||
_key = key;
|
||||
|
||||
_forcedFences = new List<ForcedFence>();
|
||||
}
|
||||
|
||||
public Auto<DisposableFramebuffer> GetFramebuffer(VulkanRenderer gd, CommandBufferScoped cbs, FramebufferParams fb)
|
||||
@ -159,6 +166,37 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return _renderPass;
|
||||
}
|
||||
|
||||
public void AddForcedFence(TextureStorage storage, PipelineStageFlags stageFlags)
|
||||
{
|
||||
if (!_forcedFences.Any(fence => fence.Texture == storage))
|
||||
{
|
||||
_forcedFences.Add(new ForcedFence(storage, stageFlags));
|
||||
}
|
||||
}
|
||||
|
||||
public void InsertForcedFences(CommandBufferScoped cbs)
|
||||
{
|
||||
if (_forcedFences.Count > 0)
|
||||
{
|
||||
_forcedFences.RemoveAll((entry) =>
|
||||
{
|
||||
if (entry.Texture.Disposed)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
entry.Texture.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, entry.StageFlags);
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsAttachment(TextureStorage storage)
|
||||
{
|
||||
return _textures.Any(view => view.Storage == storage);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Dispose all framebuffers.
|
||||
|
@ -14,13 +14,20 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private int _bindCount;
|
||||
|
||||
protected void SetDirty(VulkanRenderer gd)
|
||||
protected void SetDirty(VulkanRenderer gd, bool isImage)
|
||||
{
|
||||
ReleaseDescriptorSet();
|
||||
|
||||
if (_bindCount != 0)
|
||||
{
|
||||
gd.PipelineInternal.ForceTextureDirty();
|
||||
if (isImage)
|
||||
{
|
||||
gd.PipelineInternal.ForceImageDirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
gd.PipelineInternal.ForceTextureDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
public ResourceLayoutBuilder Add(ResourceStages stages, ResourceType type, int binding)
|
||||
public ResourceLayoutBuilder Add(ResourceStages stages, ResourceType type, int binding, bool write = false)
|
||||
{
|
||||
int setIndex = type switch
|
||||
{
|
||||
@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
};
|
||||
|
||||
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, 1, type, stages));
|
||||
_resourceUsages[setIndex].Add(new ResourceUsage(binding, 1, type, stages));
|
||||
_resourceUsages[setIndex].Add(new ResourceUsage(binding, 1, type, stages, write));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
samplerCreateInfo.BorderColor = BorderColor.FloatCustomExt;
|
||||
}
|
||||
|
||||
gd.Api.CreateSampler(device, samplerCreateInfo, null, out var sampler).ThrowOnError();
|
||||
gd.Api.CreateSampler(device, in samplerCreateInfo, null, out var sampler).ThrowOnError();
|
||||
|
||||
_sampler = new Auto<DisposableSampler>(new DisposableSampler(gd.Api, device, sampler));
|
||||
}
|
||||
|
@ -1,60 +0,0 @@
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using VkSemaphore = Silk.NET.Vulkan.Semaphore;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
class SemaphoreHolder : IDisposable
|
||||
{
|
||||
private readonly Vk _api;
|
||||
private readonly Device _device;
|
||||
private VkSemaphore _semaphore;
|
||||
private int _referenceCount;
|
||||
private bool _disposed;
|
||||
|
||||
public unsafe SemaphoreHolder(Vk api, Device device)
|
||||
{
|
||||
_api = api;
|
||||
_device = device;
|
||||
|
||||
var semaphoreCreateInfo = new SemaphoreCreateInfo
|
||||
{
|
||||
SType = StructureType.SemaphoreCreateInfo,
|
||||
};
|
||||
|
||||
api.CreateSemaphore(device, in semaphoreCreateInfo, null, out _semaphore).ThrowOnError();
|
||||
|
||||
_referenceCount = 1;
|
||||
}
|
||||
|
||||
public VkSemaphore GetUnsafe()
|
||||
{
|
||||
return _semaphore;
|
||||
}
|
||||
|
||||
public VkSemaphore Get()
|
||||
{
|
||||
Interlocked.Increment(ref _referenceCount);
|
||||
return _semaphore;
|
||||
}
|
||||
|
||||
public unsafe void Put()
|
||||
{
|
||||
if (Interlocked.Decrement(ref _referenceCount) == 0)
|
||||
{
|
||||
_api.DestroySemaphore(_device, _semaphore, null);
|
||||
_semaphore = default;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
Put();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -64,7 +64,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PCode = (uint*)pCode,
|
||||
};
|
||||
|
||||
api.CreateShaderModule(device, shaderModuleCreateInfo, null, out _module).ThrowOnError();
|
||||
api.CreateShaderModule(device, in shaderModuleCreateInfo, null, out _module).ThrowOnError();
|
||||
}
|
||||
|
||||
CompileStatus = ProgramLinkStatus.Success;
|
||||
|
@ -27,6 +27,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public uint Stages { get; }
|
||||
|
||||
public PipelineStageFlags IncoherentBufferWriteStages { get; }
|
||||
public PipelineStageFlags IncoherentTextureWriteStages { get; }
|
||||
|
||||
public ResourceBindingSegment[][] ClearSegments { get; }
|
||||
public ResourceBindingSegment[][] BindingSegments { get; }
|
||||
public DescriptorSetTemplate[] Templates { get; }
|
||||
@ -131,9 +134,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ClearSegments = BuildClearSegments(sets);
|
||||
BindingSegments = BuildBindingSegments(resourceLayout.SetUsages, out bool usesBufferTextures);
|
||||
Templates = BuildTemplates(usePushDescriptors);
|
||||
(IncoherentBufferWriteStages, IncoherentTextureWriteStages) = BuildIncoherentStages(resourceLayout.SetUsages);
|
||||
|
||||
// Updating buffer texture bindings using template updates crashes the Adreno driver on Windows.
|
||||
UpdateTexturesWithoutTemplate = gd.Vendor == Vendor.Qualcomm && usesBufferTextures;
|
||||
UpdateTexturesWithoutTemplate = gd.IsQualcommProprietary && usesBufferTextures;
|
||||
|
||||
_compileTask = Task.CompletedTask;
|
||||
_firstBackgroundUse = false;
|
||||
@ -377,6 +381,73 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return templates;
|
||||
}
|
||||
|
||||
private PipelineStageFlags GetPipelineStages(ResourceStages stages)
|
||||
{
|
||||
PipelineStageFlags result = 0;
|
||||
|
||||
if ((stages & ResourceStages.Compute) != 0)
|
||||
{
|
||||
result |= PipelineStageFlags.ComputeShaderBit;
|
||||
}
|
||||
|
||||
if ((stages & ResourceStages.Vertex) != 0)
|
||||
{
|
||||
result |= PipelineStageFlags.VertexShaderBit;
|
||||
}
|
||||
|
||||
if ((stages & ResourceStages.Fragment) != 0)
|
||||
{
|
||||
result |= PipelineStageFlags.FragmentShaderBit;
|
||||
}
|
||||
|
||||
if ((stages & ResourceStages.Geometry) != 0)
|
||||
{
|
||||
result |= PipelineStageFlags.GeometryShaderBit;
|
||||
}
|
||||
|
||||
if ((stages & ResourceStages.TessellationControl) != 0)
|
||||
{
|
||||
result |= PipelineStageFlags.TessellationControlShaderBit;
|
||||
}
|
||||
|
||||
if ((stages & ResourceStages.TessellationEvaluation) != 0)
|
||||
{
|
||||
result |= PipelineStageFlags.TessellationEvaluationShaderBit;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private (PipelineStageFlags Buffer, PipelineStageFlags Texture) BuildIncoherentStages(ReadOnlyCollection<ResourceUsageCollection> setUsages)
|
||||
{
|
||||
PipelineStageFlags buffer = PipelineStageFlags.None;
|
||||
PipelineStageFlags texture = PipelineStageFlags.None;
|
||||
|
||||
foreach (var set in setUsages)
|
||||
{
|
||||
foreach (var range in set.Usages)
|
||||
{
|
||||
if (range.Write)
|
||||
{
|
||||
PipelineStageFlags stages = GetPipelineStages(range.Stages);
|
||||
|
||||
switch (range.Type)
|
||||
{
|
||||
case ResourceType.Image:
|
||||
texture |= stages;
|
||||
break;
|
||||
case ResourceType.StorageBuffer:
|
||||
case ResourceType.BufferImage:
|
||||
buffer |= stages;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (buffer, texture);
|
||||
}
|
||||
|
||||
private async Task BackgroundCompilation()
|
||||
{
|
||||
await Task.WhenAll(_shaders.Select(shader => shader.CompileTask));
|
||||
|
@ -104,7 +104,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
_cachedCommandBufferIndex = -1;
|
||||
_storages = null;
|
||||
SetDirty(_gd);
|
||||
SetDirty(_gd, isImage: false);
|
||||
}
|
||||
|
||||
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)
|
||||
|
@ -88,7 +88,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
DstOffsets = dstOffsets,
|
||||
};
|
||||
|
||||
api.CmdBlitImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, region, filter);
|
||||
api.CmdBlitImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, in region, filter);
|
||||
|
||||
copySrcLevel++;
|
||||
copyDstLevel++;
|
||||
@ -320,13 +320,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
var region = new ImageResolve(srcSl, new Offset3D(0, 0, srcZ), dstSl, new Offset3D(0, 0, dstZ), extent);
|
||||
|
||||
api.CmdResolveImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, region);
|
||||
api.CmdResolveImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, in region);
|
||||
}
|
||||
else
|
||||
{
|
||||
var region = new ImageCopy(srcSl, new Offset3D(0, 0, srcZ), dstSl, new Offset3D(0, 0, dstZ), extent);
|
||||
|
||||
api.CmdCopyImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, region);
|
||||
api.CmdCopyImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, in region);
|
||||
}
|
||||
|
||||
width = Math.Max(1, width >> 1);
|
||||
@ -407,7 +407,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ImageLayout.General,
|
||||
ImageLayout.General);
|
||||
|
||||
var subpassDependency = PipelineConverter.CreateSubpassDependency2();
|
||||
var subpassDependency = PipelineConverter.CreateSubpassDependency2(gd);
|
||||
|
||||
fixed (AttachmentDescription2* pAttachmentDescs = attachmentDescs)
|
||||
{
|
||||
@ -422,7 +422,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
DependencyCount = 1,
|
||||
};
|
||||
|
||||
gd.Api.CreateRenderPass2(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError();
|
||||
gd.Api.CreateRenderPass2(device, in renderPassCreateInfo, null, out var renderPass).ThrowOnError();
|
||||
|
||||
using var rp = new Auto<DisposableRenderPass>(new DisposableRenderPass(gd.Api, device, renderPass));
|
||||
|
||||
@ -445,7 +445,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Layers = (uint)src.Layers,
|
||||
};
|
||||
|
||||
gd.Api.CreateFramebuffer(device, framebufferCreateInfo, null, out var framebuffer).ThrowOnError();
|
||||
gd.Api.CreateFramebuffer(device, in framebufferCreateInfo, null, out var framebuffer).ThrowOnError();
|
||||
using var fb = new Auto<DisposableFramebuffer>(new DisposableFramebuffer(gd.Api, device, framebuffer), null, srcView, dstView);
|
||||
|
||||
var renderArea = new Rect2D(null, new Extent2D((uint)src.Info.Width, (uint)src.Info.Height));
|
||||
@ -465,7 +465,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
// to resolve the depth-stencil texture.
|
||||
// TODO: Do speculative resolve and part of the same render pass as the draw to avoid
|
||||
// ending the current render pass?
|
||||
gd.Api.CmdBeginRenderPass(cbs.CommandBuffer, renderPassBeginInfo, SubpassContents.Inline);
|
||||
gd.Api.CmdBeginRenderPass(cbs.CommandBuffer, in renderPassBeginInfo, SubpassContents.Inline);
|
||||
gd.Api.CmdEndRenderPass(cbs.CommandBuffer);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Format = Ryujinx.Graphics.GAL.Format;
|
||||
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||
using VkFormat = Silk.NET.Vulkan.Format;
|
||||
@ -12,6 +13,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
class TextureStorage : IDisposable
|
||||
{
|
||||
private struct TextureSliceInfo
|
||||
{
|
||||
public int BindCount;
|
||||
}
|
||||
|
||||
private const MemoryPropertyFlags DefaultImageMemoryFlags =
|
||||
MemoryPropertyFlags.DeviceLocalBit;
|
||||
|
||||
@ -38,9 +44,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public TextureCreateInfo Info => _info;
|
||||
|
||||
public bool Disposed { get; private set; }
|
||||
|
||||
private readonly Image _image;
|
||||
private readonly Auto<DisposableImage> _imageAuto;
|
||||
private readonly Auto<MemoryAllocation> _allocationAuto;
|
||||
private readonly int _depthOrLayers;
|
||||
private Auto<MemoryAllocation> _foreignAllocationAuto;
|
||||
|
||||
private Dictionary<Format, TextureStorage> _aliasedStorages;
|
||||
@ -53,6 +62,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private int _viewsCount;
|
||||
private readonly ulong _size;
|
||||
|
||||
private int _bindCount;
|
||||
private readonly TextureSliceInfo[] _slices;
|
||||
|
||||
public VkFormat VkFormat { get; }
|
||||
|
||||
public unsafe TextureStorage(
|
||||
@ -71,6 +83,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var depth = (uint)(info.Target == Target.Texture3D ? info.Depth : 1);
|
||||
|
||||
VkFormat = format;
|
||||
_depthOrLayers = info.GetDepthOrLayers();
|
||||
|
||||
var type = info.Target.Convert();
|
||||
|
||||
@ -78,7 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
var sampleCountFlags = ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)info.Samples);
|
||||
|
||||
var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample);
|
||||
var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities);
|
||||
|
||||
var flags = ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit;
|
||||
|
||||
@ -112,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Flags = flags,
|
||||
};
|
||||
|
||||
gd.Api.CreateImage(device, imageCreateInfo, null, out _image).ThrowOnError();
|
||||
gd.Api.CreateImage(device, in imageCreateInfo, null, out _image).ThrowOnError();
|
||||
|
||||
if (foreignAllocation == null)
|
||||
{
|
||||
@ -146,6 +159,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
InitialTransition(ImageLayout.Preinitialized, ImageLayout.General);
|
||||
}
|
||||
|
||||
_slices = new TextureSliceInfo[levels * _depthOrLayers];
|
||||
}
|
||||
|
||||
public TextureStorage CreateAliasedColorForDepthStorageUnsafe(Format format)
|
||||
@ -282,7 +297,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
0,
|
||||
null,
|
||||
1,
|
||||
barrier);
|
||||
in barrier);
|
||||
|
||||
if (useTempCbs)
|
||||
{
|
||||
@ -290,7 +305,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
public static ImageUsageFlags GetImageUsage(Format format, Target target, bool supportsMsStorage)
|
||||
public static ImageUsageFlags GetImageUsage(Format format, Target target, in HardwareCapabilities capabilities)
|
||||
{
|
||||
var usage = DefaultUsageFlags;
|
||||
|
||||
@ -303,11 +318,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
usage |= ImageUsageFlags.ColorAttachmentBit;
|
||||
}
|
||||
|
||||
bool supportsMsStorage = capabilities.SupportsShaderStorageImageMultisample;
|
||||
|
||||
if (format.IsImageCompatible() && (supportsMsStorage || !target.IsMultisample()))
|
||||
{
|
||||
usage |= ImageUsageFlags.StorageBit;
|
||||
}
|
||||
|
||||
if (capabilities.SupportsAttachmentFeedbackLoop &&
|
||||
(usage & (ImageUsageFlags.DepthStencilAttachmentBit | ImageUsageFlags.ColorAttachmentBit)) != 0)
|
||||
{
|
||||
usage |= ImageUsageFlags.AttachmentFeedbackLoopBitExt;
|
||||
}
|
||||
|
||||
return usage;
|
||||
}
|
||||
|
||||
@ -399,11 +422,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (to)
|
||||
{
|
||||
_gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region);
|
||||
_gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, in region);
|
||||
}
|
||||
else
|
||||
{
|
||||
_gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region);
|
||||
_gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, in region);
|
||||
}
|
||||
|
||||
offset += mipSize;
|
||||
@ -433,6 +456,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint;
|
||||
}
|
||||
|
||||
public void AddStoreOpUsage(bool depthStencil)
|
||||
{
|
||||
_lastModificationStage = depthStencil ?
|
||||
PipelineStageFlags.LateFragmentTestsBit :
|
||||
PipelineStageFlags.ColorAttachmentOutputBit;
|
||||
|
||||
_lastModificationAccess = depthStencil ?
|
||||
AccessFlags.DepthStencilAttachmentWriteBit :
|
||||
AccessFlags.ColorAttachmentWriteBit;
|
||||
}
|
||||
|
||||
public void QueueLoadOpBarrier(CommandBufferScoped cbs, bool depthStencil)
|
||||
{
|
||||
PipelineStageFlags srcStageFlags = _lastReadStage | _lastModificationStage;
|
||||
@ -458,7 +492,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_info.GetLayers(),
|
||||
_info.Levels);
|
||||
|
||||
_gd.Barriers.QueueBarrier(barrier, srcStageFlags, dstStageFlags);
|
||||
_gd.Barriers.QueueBarrier(barrier, this, srcStageFlags, dstStageFlags);
|
||||
|
||||
_lastReadStage = PipelineStageFlags.None;
|
||||
_lastReadAccess = AccessFlags.None;
|
||||
@ -491,12 +525,61 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_info.GetLayers(),
|
||||
_info.Levels);
|
||||
|
||||
_gd.Barriers.QueueBarrier(barrier, _lastModificationStage, dstStageFlags);
|
||||
_gd.Barriers.QueueBarrier(barrier, this, _lastModificationStage, dstStageFlags);
|
||||
|
||||
_lastModificationAccess = AccessFlags.None;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddBinding(TextureView view)
|
||||
{
|
||||
// Assumes a view only has a first level.
|
||||
|
||||
int index = view.FirstLevel * _depthOrLayers + view.FirstLayer;
|
||||
int layers = view.Layers;
|
||||
|
||||
for (int i = 0; i < layers; i++)
|
||||
{
|
||||
ref TextureSliceInfo info = ref _slices[index++];
|
||||
|
||||
info.BindCount++;
|
||||
}
|
||||
|
||||
_bindCount++;
|
||||
}
|
||||
|
||||
public void ClearBindings()
|
||||
{
|
||||
if (_bindCount != 0)
|
||||
{
|
||||
Array.Clear(_slices, 0, _slices.Length);
|
||||
|
||||
_bindCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsBound(TextureView view)
|
||||
{
|
||||
if (_bindCount != 0)
|
||||
{
|
||||
int index = view.FirstLevel * _depthOrLayers + view.FirstLayer;
|
||||
int layers = view.Layers;
|
||||
|
||||
for (int i = 0; i < layers; i++)
|
||||
{
|
||||
ref TextureSliceInfo info = ref _slices[index++];
|
||||
|
||||
if (info.BindCount != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void IncrementViewsCount()
|
||||
{
|
||||
_viewsCount++;
|
||||
@ -514,6 +597,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposed = true;
|
||||
|
||||
if (_aliasedStorages != null)
|
||||
{
|
||||
foreach (var storage in _aliasedStorages.Values)
|
||||
|
@ -23,6 +23,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private readonly Auto<DisposableImageView> _imageView2dArray;
|
||||
private Dictionary<Format, TextureView> _selfManagedViews;
|
||||
|
||||
private int _hazardUses;
|
||||
|
||||
private readonly TextureCreateInfo _info;
|
||||
|
||||
private HashTableSlim<RenderPassCacheKey, RenderPassHolder> _renderPasses;
|
||||
@ -60,7 +62,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
gd.Textures.Add(this);
|
||||
|
||||
var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format);
|
||||
var usage = TextureStorage.GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample);
|
||||
var usage = TextureStorage.GetImageUsage(info.Format, info.Target, gd.Capabilities);
|
||||
var levels = (uint)info.Levels;
|
||||
var layers = (uint)info.GetLayers();
|
||||
|
||||
@ -117,7 +119,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PNext = &imageViewUsage,
|
||||
};
|
||||
|
||||
gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError();
|
||||
gd.Api.CreateImageView(device, in imageCreateInfo, null, out var imageView).ThrowOnError();
|
||||
return new Auto<DisposableImageView>(new DisposableImageView(gd.Api, device, imageView), null, storage.GetImage());
|
||||
}
|
||||
|
||||
@ -492,7 +494,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
dstStageMask,
|
||||
DependencyFlags.None,
|
||||
1,
|
||||
memoryBarrier,
|
||||
in memoryBarrier,
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
@ -557,7 +559,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
0,
|
||||
null,
|
||||
1,
|
||||
memoryBarrier);
|
||||
in memoryBarrier);
|
||||
}
|
||||
|
||||
public TextureView GetView(Format format)
|
||||
@ -667,8 +669,36 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (PrepareOutputBuffer(cbs, hostSize, buffer, out VkBuffer copyToBuffer, out BufferHolder tempCopyHolder))
|
||||
{
|
||||
// No barrier necessary, as this is a temporary copy buffer.
|
||||
offset = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferHolder.InsertBufferBarrier(
|
||||
_gd,
|
||||
cbs.CommandBuffer,
|
||||
copyToBuffer,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
AccessFlags.TransferWriteBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
offset,
|
||||
outSize);
|
||||
}
|
||||
|
||||
InsertImageBarrier(
|
||||
_gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
image,
|
||||
TextureStorage.DefaultAccessMask,
|
||||
AccessFlags.TransferReadBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
Info.Format.ConvertAspectFlags(),
|
||||
FirstLayer + layer,
|
||||
FirstLevel + level,
|
||||
1,
|
||||
1);
|
||||
|
||||
CopyFromOrToBuffer(cbs.CommandBuffer, copyToBuffer, image, hostSize, true, layer, level, 1, 1, singleSlice: true, offset, stride);
|
||||
|
||||
@ -677,6 +707,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
CopyDataToOutputBuffer(cbs, tempCopyHolder, autoBuffer, hostSize, range.Offset);
|
||||
tempCopyHolder.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferHolder.InsertBufferBarrier(
|
||||
_gd,
|
||||
cbs.CommandBuffer,
|
||||
copyToBuffer,
|
||||
AccessFlags.TransferWriteBit,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
offset,
|
||||
outSize);
|
||||
}
|
||||
}
|
||||
|
||||
private ReadOnlySpan<byte> GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer)
|
||||
@ -908,11 +951,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (to)
|
||||
{
|
||||
_gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region);
|
||||
_gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, in region);
|
||||
}
|
||||
else
|
||||
{
|
||||
_gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region);
|
||||
_gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, in region);
|
||||
}
|
||||
|
||||
offset += mipSize;
|
||||
@ -969,11 +1012,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (to)
|
||||
{
|
||||
_gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region);
|
||||
_gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, in region);
|
||||
}
|
||||
else
|
||||
{
|
||||
_gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region);
|
||||
_gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, in region);
|
||||
}
|
||||
}
|
||||
|
||||
@ -993,7 +1036,35 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public (Auto<DisposableRenderPass> renderPass, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
||||
public void PrepareForUsage(CommandBufferScoped cbs, PipelineStageFlags flags, List<TextureView> feedbackLoopHazards)
|
||||
{
|
||||
Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, flags);
|
||||
|
||||
if (feedbackLoopHazards != null && Storage.IsBound(this))
|
||||
{
|
||||
feedbackLoopHazards.Add(this);
|
||||
_hazardUses++;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearUsage(List<TextureView> feedbackLoopHazards)
|
||||
{
|
||||
if (_hazardUses != 0 && feedbackLoopHazards != null)
|
||||
{
|
||||
feedbackLoopHazards.Remove(this);
|
||||
_hazardUses--;
|
||||
}
|
||||
}
|
||||
|
||||
public void DecrementHazardUses()
|
||||
{
|
||||
if (_hazardUses != 0)
|
||||
{
|
||||
_hazardUses--;
|
||||
}
|
||||
}
|
||||
|
||||
public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
CommandBufferScoped cbs,
|
||||
@ -1006,7 +1077,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
rpHolder = new RenderPassHolder(gd, device, key, fb);
|
||||
}
|
||||
|
||||
return (rpHolder.GetRenderPass(), rpHolder.GetFramebuffer(gd, cbs, fb));
|
||||
return (rpHolder, rpHolder.GetFramebuffer(gd, cbs, fb));
|
||||
}
|
||||
|
||||
public void AddRenderPass(RenderPassCacheKey key, RenderPassHolder renderPass)
|
||||
|
@ -69,27 +69,30 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
DriverId.AmdProprietary => "AMD",
|
||||
DriverId.AmdOpenSource => "AMD (Open)",
|
||||
DriverId.ArmProprietary => "ARM",
|
||||
DriverId.BroadcomProprietary => "Broadcom",
|
||||
DriverId.CoreaviProprietary => "CoreAVI",
|
||||
DriverId.GgpProprietary => "GGP",
|
||||
DriverId.GoogleSwiftshader => "SwiftShader",
|
||||
DriverId.ImaginationProprietary => "Imagination",
|
||||
DriverId.IntelOpenSourceMesa => "Intel (Open)",
|
||||
DriverId.IntelProprietaryWindows => "Intel",
|
||||
DriverId.JuiceProprietary => "Juice",
|
||||
DriverId.MesaDozen => "Dozen",
|
||||
DriverId.MesaLlvmpipe => "LLVMpipe",
|
||||
DriverId.MesaPanvk => "PanVK",
|
||||
DriverId.MesaRadv => "RADV",
|
||||
DriverId.NvidiaProprietary => "NVIDIA",
|
||||
DriverId.IntelProprietaryWindows => "Intel",
|
||||
DriverId.IntelOpenSourceMesa => "Intel (Open)",
|
||||
DriverId.ImaginationProprietary => "Imagination",
|
||||
DriverId.QualcommProprietary => "Qualcomm",
|
||||
DriverId.ArmProprietary => "ARM",
|
||||
DriverId.GoogleSwiftshader => "SwiftShader",
|
||||
DriverId.GgpProprietary => "GGP",
|
||||
DriverId.BroadcomProprietary => "Broadcom",
|
||||
DriverId.MesaLlvmpipe => "LLVMpipe",
|
||||
DriverId.Moltenvk => "MoltenVK",
|
||||
DriverId.CoreaviProprietary => "CoreAVI",
|
||||
DriverId.JuiceProprietary => "Juice",
|
||||
DriverId.VerisiliconProprietary => "Verisilicon",
|
||||
DriverId.MesaTurnip => "Turnip",
|
||||
DriverId.MesaV3DV => "V3DV",
|
||||
DriverId.MesaVenus => "Venus",
|
||||
DriverId.Moltenvk => "MoltenVK",
|
||||
DriverId.NvidiaProprietary => "NVIDIA",
|
||||
DriverId.QualcommProprietary => "Qualcomm",
|
||||
DriverId.MesaPanvk => "PanVK",
|
||||
DriverId.SamsungProprietary => "Samsung",
|
||||
DriverId.VerisiliconProprietary => "Verisilicon",
|
||||
DriverId.MesaVenus => "Venus",
|
||||
DriverId.MesaDozen => "Dozen",
|
||||
DriverId.MesaNvk => "NVK",
|
||||
DriverId.ImaginationOpenSourceMesa => "Imagination (Open)",
|
||||
DriverId.MesaAgxv => "Honeykrisp",
|
||||
_ => id.ToString(),
|
||||
};
|
||||
}
|
||||
|
@ -44,6 +44,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
"VK_EXT_4444_formats",
|
||||
"VK_KHR_8bit_storage",
|
||||
"VK_KHR_maintenance2",
|
||||
"VK_EXT_attachment_feedback_loop_layout",
|
||||
"VK_EXT_attachment_feedback_loop_dynamic_state",
|
||||
};
|
||||
|
||||
private static readonly string[] _requiredExtensions = {
|
||||
@ -357,6 +359,28 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
features2.PNext = &supportedFeaturesDepthClipControl;
|
||||
}
|
||||
|
||||
PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT supportedFeaturesAttachmentFeedbackLoopLayout = new()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt,
|
||||
PNext = features2.PNext,
|
||||
};
|
||||
|
||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout"))
|
||||
{
|
||||
features2.PNext = &supportedFeaturesAttachmentFeedbackLoopLayout;
|
||||
}
|
||||
|
||||
PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT supportedFeaturesDynamicAttachmentFeedbackLoopLayout = new()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt,
|
||||
PNext = features2.PNext,
|
||||
};
|
||||
|
||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state"))
|
||||
{
|
||||
features2.PNext = &supportedFeaturesDynamicAttachmentFeedbackLoopLayout;
|
||||
}
|
||||
|
||||
PhysicalDeviceVulkan12Features supportedPhysicalDeviceVulkan12Features = new()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceVulkan12Features,
|
||||
@ -531,6 +555,36 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
pExtendedFeatures = &featuresDepthClipControl;
|
||||
}
|
||||
|
||||
PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT featuresAttachmentFeedbackLoopLayout;
|
||||
|
||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout") &&
|
||||
supportedFeaturesAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopLayout)
|
||||
{
|
||||
featuresAttachmentFeedbackLoopLayout = new()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt,
|
||||
PNext = pExtendedFeatures,
|
||||
AttachmentFeedbackLoopLayout = true,
|
||||
};
|
||||
|
||||
pExtendedFeatures = &featuresAttachmentFeedbackLoopLayout;
|
||||
}
|
||||
|
||||
PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT featuresDynamicAttachmentFeedbackLoopLayout;
|
||||
|
||||
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state") &&
|
||||
supportedFeaturesDynamicAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopDynamicState)
|
||||
{
|
||||
featuresDynamicAttachmentFeedbackLoopLayout = new()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt,
|
||||
PNext = pExtendedFeatures,
|
||||
AttachmentFeedbackLoopDynamicState = true,
|
||||
};
|
||||
|
||||
pExtendedFeatures = &featuresDynamicAttachmentFeedbackLoopLayout;
|
||||
}
|
||||
|
||||
var enabledExtensions = _requiredExtensions.Union(_desirableExtensions.Intersect(physicalDevice.DeviceExtensions)).ToArray();
|
||||
|
||||
IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
|
||||
|
@ -38,6 +38,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal KhrPushDescriptor PushDescriptorApi { get; private set; }
|
||||
internal ExtTransformFeedback TransformFeedbackApi { get; private set; }
|
||||
internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; }
|
||||
internal ExtAttachmentFeedbackLoopDynamicState DynamicFeedbackLoopApi { get; private set; }
|
||||
|
||||
internal uint QueueFamilyIndex { get; private set; }
|
||||
internal Queue Queue { get; private set; }
|
||||
@ -87,10 +88,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal bool IsAmdGcn { get; private set; }
|
||||
internal bool IsNvidiaPreTuring { get; private set; }
|
||||
internal bool IsIntelArc { get; private set; }
|
||||
internal bool IsQualcommProprietary { get; private set; }
|
||||
internal bool IsMoltenVk { get; private set; }
|
||||
internal bool IsTBDR { get; private set; }
|
||||
internal bool IsSharedMemory { get; private set; }
|
||||
internal bool IsConcurrentFenceWaitUnsupported { get; private set; }
|
||||
|
||||
public string GpuVendor { get; private set; }
|
||||
public string GpuDriver { get; private set; }
|
||||
@ -149,6 +150,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
DrawIndirectCountApi = drawIndirectCountApi;
|
||||
}
|
||||
|
||||
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtAttachmentFeedbackLoopDynamicState dynamicFeedbackLoopApi))
|
||||
{
|
||||
DynamicFeedbackLoopApi = dynamicFeedbackLoopApi;
|
||||
}
|
||||
|
||||
if (maxQueueCount >= 2)
|
||||
{
|
||||
Api.GetDeviceQueue(_device, queueFamilyIndex, 1, out var backgroundQueue);
|
||||
@ -243,6 +249,16 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt,
|
||||
};
|
||||
|
||||
PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT featuresAttachmentFeedbackLoop = new()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt,
|
||||
};
|
||||
|
||||
PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT featuresDynamicAttachmentFeedbackLoop = new()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt,
|
||||
};
|
||||
|
||||
PhysicalDevicePortabilitySubsetFeaturesKHR featuresPortabilitySubset = new()
|
||||
{
|
||||
SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr,
|
||||
@ -279,6 +295,22 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
features2.PNext = &featuresDepthClipControl;
|
||||
}
|
||||
|
||||
bool supportsAttachmentFeedbackLoop = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout");
|
||||
|
||||
if (supportsAttachmentFeedbackLoop)
|
||||
{
|
||||
featuresAttachmentFeedbackLoop.PNext = features2.PNext;
|
||||
features2.PNext = &featuresAttachmentFeedbackLoop;
|
||||
}
|
||||
|
||||
bool supportsDynamicAttachmentFeedbackLoop = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state");
|
||||
|
||||
if (supportsDynamicAttachmentFeedbackLoop)
|
||||
{
|
||||
featuresDynamicAttachmentFeedbackLoop.PNext = features2.PNext;
|
||||
features2.PNext = &featuresDynamicAttachmentFeedbackLoop;
|
||||
}
|
||||
|
||||
bool usePortability = _physicalDevice.IsDeviceExtensionPresent("VK_KHR_portability_subset");
|
||||
|
||||
if (usePortability)
|
||||
@ -325,8 +357,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Vendor == Vendor.Broadcom ||
|
||||
Vendor == Vendor.ImgTec;
|
||||
|
||||
IsConcurrentFenceWaitUnsupported = Vendor == Vendor.Qualcomm;
|
||||
|
||||
GpuVendor = VendorUtils.GetNameFromId(properties.VendorID);
|
||||
GpuDriver = hasDriverProperties && !OperatingSystem.IsMacOS() ?
|
||||
VendorUtils.GetFriendlyDriverName(driverProperties.DriverID) : GpuVendor; // Fallback to vendor name if driver is unavailable or on MacOS where vendor is preferred.
|
||||
@ -348,7 +378,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
IsNvidiaPreTuring = gpuNumber < 2000;
|
||||
}
|
||||
else if (GpuDriver.Contains("TITAN") && !GpuDriver.Contains("RTX"))
|
||||
else if (GpuRenderer.Contains("TITAN") && !GpuRenderer.Contains("RTX"))
|
||||
{
|
||||
IsNvidiaPreTuring = true;
|
||||
}
|
||||
@ -358,6 +388,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
IsIntelArc = GpuRenderer.StartsWith("Intel(R) Arc(TM)");
|
||||
}
|
||||
|
||||
IsQualcommProprietary = hasDriverProperties && driverProperties.DriverID == DriverId.QualcommProprietary;
|
||||
|
||||
ulong minResourceAlignment = Math.Max(
|
||||
Math.Max(
|
||||
properties.Limits.MinStorageBufferOffsetAlignment,
|
||||
@ -401,6 +433,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
|
||||
_physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName),
|
||||
supportsDepthClipControl && featuresDepthClipControl.DepthClipControl,
|
||||
supportsAttachmentFeedbackLoop && featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout,
|
||||
supportsDynamicAttachmentFeedbackLoop && featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState,
|
||||
propertiesSubgroup.SubgroupSize,
|
||||
supportedSampleCounts,
|
||||
portabilityFlags,
|
||||
@ -415,7 +449,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExternalMemoryHost hostMemoryApi);
|
||||
HostMemoryAllocator = new HostMemoryAllocator(MemoryAllocator, Api, hostMemoryApi, _device);
|
||||
|
||||
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex, IsConcurrentFenceWaitUnsupported);
|
||||
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex, IsQualcommProprietary);
|
||||
|
||||
PipelineLayoutCache = new PipelineLayoutCache();
|
||||
|
||||
@ -692,7 +726,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
GpuVendor,
|
||||
memoryType: memoryType,
|
||||
hasFrontFacingBug: IsIntelWindows,
|
||||
hasVectorIndexingBug: Vendor == Vendor.Qualcomm,
|
||||
hasVectorIndexingBug: IsQualcommProprietary,
|
||||
needsFragmentOutputSpecialization: IsMoltenVk,
|
||||
reduceShaderPrecision: IsMoltenVk,
|
||||
supportsAstcCompression: features2.Features.TextureCompressionAstcLdr && supportsAstcFormats,
|
||||
@ -939,6 +973,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ScreenCaptured?.Invoke(this, bitmap);
|
||||
}
|
||||
|
||||
public bool SupportsRenderPassBarrier(PipelineStageFlags flags)
|
||||
{
|
||||
return !(IsMoltenVk || IsQualcommProprietary);
|
||||
}
|
||||
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
if (!_initialized)
|
||||
|
@ -160,7 +160,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SwizzleComponent.Blue,
|
||||
SwizzleComponent.Alpha);
|
||||
|
||||
_gd.SwapchainApi.CreateSwapchain(_device, swapchainCreateInfo, null, out _swapchain).ThrowOnError();
|
||||
_gd.SwapchainApi.CreateSwapchain(_device, in swapchainCreateInfo, null, out _swapchain).ThrowOnError();
|
||||
|
||||
_gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, null);
|
||||
|
||||
@ -187,14 +187,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
for (int i = 0; i < _imageAvailableSemaphores.Length; i++)
|
||||
{
|
||||
_gd.Api.CreateSemaphore(_device, semaphoreCreateInfo, null, out _imageAvailableSemaphores[i]).ThrowOnError();
|
||||
_gd.Api.CreateSemaphore(_device, in semaphoreCreateInfo, null, out _imageAvailableSemaphores[i]).ThrowOnError();
|
||||
}
|
||||
|
||||
_renderFinishedSemaphores = new Semaphore[imageCount];
|
||||
|
||||
for (int i = 0; i < _renderFinishedSemaphores.Length; i++)
|
||||
{
|
||||
_gd.Api.CreateSemaphore(_device, semaphoreCreateInfo, null, out _renderFinishedSemaphores[i]).ThrowOnError();
|
||||
_gd.Api.CreateSemaphore(_device, in semaphoreCreateInfo, null, out _renderFinishedSemaphores[i]).ThrowOnError();
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SubresourceRange = subresourceRange,
|
||||
};
|
||||
|
||||
_gd.Api.CreateImageView(_device, imageCreateInfo, null, out var imageView).ThrowOnError();
|
||||
_gd.Api.CreateImageView(_device, in imageCreateInfo, null, out var imageView).ThrowOnError();
|
||||
|
||||
return new TextureView(_gd, _device, new DisposableImageView(_gd.Api, _device, imageView), info, format);
|
||||
}
|
||||
@ -479,7 +479,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
lock (_gd.QueueLock)
|
||||
{
|
||||
_gd.SwapchainApi.QueuePresent(_gd.Queue, presentInfo);
|
||||
_gd.SwapchainApi.QueuePresent(_gd.Queue, in presentInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@ -611,7 +611,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
0,
|
||||
null,
|
||||
1,
|
||||
barrier);
|
||||
in barrier);
|
||||
}
|
||||
|
||||
private void CaptureFrame(TextureView texture, int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY)
|
||||
|
@ -4,15 +4,17 @@ using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.GraphicsDriver;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.SystemInterop;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.Graphics.Vulkan.MoltenVK;
|
||||
using Ryujinx.Modules;
|
||||
using Ryujinx.SDL2.Common;
|
||||
using Ryujinx.UI;
|
||||
using Ryujinx.UI.App.Common;
|
||||
using Ryujinx.UI.Common;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using Ryujinx.UI.Common.Helper;
|
||||
using Ryujinx.UI.Common.SystemInfo;
|
||||
using Ryujinx.UI.Widgets;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@ -40,9 +42,6 @@ namespace Ryujinx
|
||||
[LibraryImport("user32.dll", SetLastError = true)]
|
||||
public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial int setenv([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value, int overwrite);
|
||||
|
||||
private const uint MbIconWarning = 0x30;
|
||||
|
||||
static Program()
|
||||
@ -104,12 +103,13 @@ namespace Ryujinx
|
||||
throw new NotSupportedException("Failed to initialize multi-threading support.");
|
||||
}
|
||||
|
||||
Environment.SetEnvironmentVariable("GDK_BACKEND", "x11");
|
||||
setenv("GDK_BACKEND", "x11", 1);
|
||||
OsUtils.SetEnvironmentVariableNoCaching("GDK_BACKEND", "x11");
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
MVKInitialization.InitializeResolver();
|
||||
|
||||
string baseDirectory = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
|
||||
string resourcesDataDir;
|
||||
|
||||
@ -122,19 +122,13 @@ namespace Ryujinx
|
||||
resourcesDataDir = baseDirectory;
|
||||
}
|
||||
|
||||
static void SetEnvironmentVariableNoCaching(string key, string value)
|
||||
{
|
||||
int res = setenv(key, value, 1);
|
||||
Debug.Assert(res != -1);
|
||||
}
|
||||
|
||||
// On macOS, GTK3 needs XDG_DATA_DIRS to be set, otherwise it will try searching for "gschemas.compiled" in system directories.
|
||||
SetEnvironmentVariableNoCaching("XDG_DATA_DIRS", Path.Combine(resourcesDataDir, "share"));
|
||||
OsUtils.SetEnvironmentVariableNoCaching("XDG_DATA_DIRS", Path.Combine(resourcesDataDir, "share"));
|
||||
|
||||
// On macOS, GTK3 needs GDK_PIXBUF_MODULE_FILE to be set, otherwise it will try searching for "loaders.cache" in system directories.
|
||||
SetEnvironmentVariableNoCaching("GDK_PIXBUF_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gdk-pixbuf-2.0", "2.10.0", "loaders.cache"));
|
||||
OsUtils.SetEnvironmentVariableNoCaching("GDK_PIXBUF_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gdk-pixbuf-2.0", "2.10.0", "loaders.cache"));
|
||||
|
||||
SetEnvironmentVariableNoCaching("GTK_IM_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gtk-3.0", "3.0.0", "immodules.cache"));
|
||||
OsUtils.SetEnvironmentVariableNoCaching("GTK_IM_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gtk-3.0", "3.0.0", "immodules.cache"));
|
||||
}
|
||||
|
||||
string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine);
|
||||
@ -161,12 +155,6 @@ namespace Ryujinx
|
||||
});
|
||||
};
|
||||
|
||||
// Sets ImageSharp Jpeg Encoder Quality.
|
||||
SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder()
|
||||
{
|
||||
Quality = 100,
|
||||
});
|
||||
|
||||
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
|
||||
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
|
||||
|
||||
@ -236,9 +224,9 @@ namespace Ryujinx
|
||||
// Logging system information.
|
||||
PrintSystemInfo();
|
||||
|
||||
// Enable OGL multithreading on the driver, when available.
|
||||
// Enable OGL multithreading on the driver, and some other flags.
|
||||
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
|
||||
DriverUtilities.ToggleOGLThreading(threadingMode == BackendThreading.Off);
|
||||
DriverUtilities.InitDriverConfig(threadingMode == BackendThreading.Off);
|
||||
|
||||
// Initialize Gtk.
|
||||
Application.Init();
|
||||
@ -255,6 +243,12 @@ namespace Ryujinx
|
||||
MainWindow mainWindow = new();
|
||||
mainWindow.Show();
|
||||
|
||||
// Load the game table if no application was requested by the command line
|
||||
if (CommandLineState.LaunchPathArg == null)
|
||||
{
|
||||
mainWindow.UpdateGameTable();
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
int currentVmMaxMapCount = LinuxHelper.VmMaxMapCount;
|
||||
@ -322,7 +316,35 @@ namespace Ryujinx
|
||||
|
||||
if (CommandLineState.LaunchPathArg != null)
|
||||
{
|
||||
mainWindow.RunApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg);
|
||||
if (mainWindow.ApplicationLibrary.TryGetApplicationsFromFile(CommandLineState.LaunchPathArg, out List<ApplicationData> applications))
|
||||
{
|
||||
ApplicationData applicationData;
|
||||
|
||||
if (CommandLineState.LaunchApplicationId != null)
|
||||
{
|
||||
applicationData = applications.Find(application => application.IdString == CommandLineState.LaunchApplicationId);
|
||||
|
||||
if (applicationData != null)
|
||||
{
|
||||
mainWindow.RunApplication(applicationData, CommandLineState.StartFullscreenArg);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Couldn't find requested application id '{CommandLineState.LaunchApplicationId}' in '{CommandLineState.LaunchPathArg}'.");
|
||||
UserErrorDialog.CreateUserErrorDialog(UserError.ApplicationNotFound);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
applicationData = applications[0];
|
||||
mainWindow.RunApplication(applicationData, CommandLineState.StartFullscreenArg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Couldn't find any application in '{CommandLineState.LaunchPathArg}'.");
|
||||
UserErrorDialog.CreateUserErrorDialog(UserError.ApplicationNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
|
||||
|
@ -30,7 +30,6 @@
|
||||
<PackageReference Include="OpenTK.Graphics" />
|
||||
<PackageReference Include="SPB" />
|
||||
<PackageReference Include="SharpZipLib" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -37,7 +37,9 @@ using Ryujinx.UI.Windows;
|
||||
using Silk.NET.Vulkan;
|
||||
using SPB.Graphics.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
@ -60,7 +62,6 @@ namespace Ryujinx.UI
|
||||
|
||||
private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
|
||||
|
||||
private readonly ApplicationLibrary _applicationLibrary;
|
||||
private readonly GtkHostUIHandler _uiHandler;
|
||||
private readonly AutoResetEvent _deviceExitStatus;
|
||||
private readonly ListStore _tableStore;
|
||||
@ -69,11 +70,12 @@ namespace Ryujinx.UI
|
||||
private bool _gameLoaded;
|
||||
private bool _ending;
|
||||
|
||||
private string _currentEmulatedGamePath = null;
|
||||
private ApplicationData _currentApplicationData = null;
|
||||
|
||||
private string _lastScannedAmiiboId = "";
|
||||
private bool _lastScannedAmiiboShowAll = false;
|
||||
|
||||
public readonly ApplicationLibrary ApplicationLibrary;
|
||||
public RendererWidgetBase RendererWidget;
|
||||
public InputManager InputManager;
|
||||
|
||||
@ -180,8 +182,15 @@ namespace Ryujinx.UI
|
||||
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, CommandLineState.Profile);
|
||||
_userChannelPersistence = new UserChannelPersistence();
|
||||
|
||||
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||
? IntegrityCheckLevel.ErrorOnInvalid
|
||||
: IntegrityCheckLevel.None;
|
||||
|
||||
// Instantiate GUI objects.
|
||||
_applicationLibrary = new ApplicationLibrary(_virtualFileSystem);
|
||||
ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel)
|
||||
{
|
||||
DesiredLanguage = ConfigurationState.Instance.System.Language,
|
||||
};
|
||||
_uiHandler = new GtkHostUIHandler(this);
|
||||
_deviceExitStatus = new AutoResetEvent(false);
|
||||
|
||||
@ -190,8 +199,8 @@ namespace Ryujinx.UI
|
||||
FocusInEvent += MainWindow_FocusInEvent;
|
||||
FocusOutEvent += MainWindow_FocusOutEvent;
|
||||
|
||||
_applicationLibrary.ApplicationAdded += Application_Added;
|
||||
_applicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
|
||||
ApplicationLibrary.ApplicationAdded += Application_Added;
|
||||
ApplicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
|
||||
|
||||
_fileMenu.StateChanged += FileMenu_StateChanged;
|
||||
_actionMenu.StateChanged += ActionMenu_StateChanged;
|
||||
@ -319,7 +328,6 @@ namespace Ryujinx.UI
|
||||
_hideUI.Label = _hideUI.Label.Replace("SHOWUIKEY", ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI.ToString());
|
||||
|
||||
UpdateColumns();
|
||||
UpdateGameTable();
|
||||
|
||||
ConfigurationState.Instance.UI.GameDirs.Event += (sender, args) =>
|
||||
{
|
||||
@ -732,7 +740,8 @@ namespace Ryujinx.UI
|
||||
|
||||
Thread applicationLibraryThread = new(() =>
|
||||
{
|
||||
_applicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language);
|
||||
ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language;
|
||||
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs);
|
||||
|
||||
_updatingGameTable = false;
|
||||
})
|
||||
@ -783,7 +792,7 @@ namespace Ryujinx.UI
|
||||
}
|
||||
}
|
||||
|
||||
private bool LoadApplication(string path, bool isFirmwareTitle)
|
||||
private bool LoadApplication(string path, ulong applicationId, bool isFirmwareTitle)
|
||||
{
|
||||
SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
|
||||
|
||||
@ -857,7 +866,7 @@ namespace Ryujinx.UI
|
||||
case ".xci":
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
||||
|
||||
return _emulationContext.LoadXci(path);
|
||||
return _emulationContext.LoadXci(path, applicationId);
|
||||
case ".nca":
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
|
||||
|
||||
@ -866,7 +875,7 @@ namespace Ryujinx.UI
|
||||
case ".pfs0":
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
||||
|
||||
return _emulationContext.LoadNsp(path);
|
||||
return _emulationContext.LoadNsp(path, applicationId);
|
||||
default:
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
|
||||
try
|
||||
@ -887,7 +896,7 @@ namespace Ryujinx.UI
|
||||
return false;
|
||||
}
|
||||
|
||||
public void RunApplication(string path, bool startFullscreen = false)
|
||||
public void RunApplication(ApplicationData application, bool startFullscreen = false)
|
||||
{
|
||||
if (_gameLoaded)
|
||||
{
|
||||
@ -909,14 +918,14 @@ namespace Ryujinx.UI
|
||||
|
||||
bool isFirmwareTitle = false;
|
||||
|
||||
if (path.StartsWith("@SystemContent"))
|
||||
if (application.Path.StartsWith("@SystemContent"))
|
||||
{
|
||||
path = VirtualFileSystem.SwitchPathToSystemPath(path);
|
||||
application.Path = VirtualFileSystem.SwitchPathToSystemPath(application.Path);
|
||||
|
||||
isFirmwareTitle = true;
|
||||
}
|
||||
|
||||
if (!LoadApplication(path, isFirmwareTitle))
|
||||
if (!LoadApplication(application.Path, application.Id, isFirmwareTitle))
|
||||
{
|
||||
_emulationContext.Dispose();
|
||||
SwitchToGameTable();
|
||||
@ -926,7 +935,7 @@ namespace Ryujinx.UI
|
||||
|
||||
SetupProgressUIHandlers();
|
||||
|
||||
_currentEmulatedGamePath = path;
|
||||
_currentApplicationData = application;
|
||||
|
||||
_deviceExitStatus.Reset();
|
||||
|
||||
@ -1165,7 +1174,7 @@ namespace Ryujinx.UI
|
||||
_tableStore.AppendValues(
|
||||
args.AppData.Favorite,
|
||||
new Gdk.Pixbuf(args.AppData.Icon, 75, 75),
|
||||
$"{args.AppData.TitleName}\n{args.AppData.TitleId.ToUpper()}",
|
||||
$"{args.AppData.Name}\n{args.AppData.IdString.ToUpper()}",
|
||||
args.AppData.Developer,
|
||||
args.AppData.Version,
|
||||
args.AppData.TimePlayedString,
|
||||
@ -1253,9 +1262,22 @@ namespace Ryujinx.UI
|
||||
{
|
||||
_gameTableSelection.GetSelected(out TreeIter treeIter);
|
||||
|
||||
string path = (string)_tableStore.GetValue(treeIter, 9);
|
||||
ApplicationData application = new()
|
||||
{
|
||||
Favorite = (bool)_tableStore.GetValue(treeIter, 0),
|
||||
Name = ((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[0],
|
||||
Id = ulong.Parse(((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[1], NumberStyles.HexNumber),
|
||||
Developer = (string)_tableStore.GetValue(treeIter, 3),
|
||||
Version = (string)_tableStore.GetValue(treeIter, 4),
|
||||
TimePlayed = ValueFormatUtils.ParseTimeSpan((string)_tableStore.GetValue(treeIter, 5)),
|
||||
LastPlayed = ValueFormatUtils.ParseDateTime((string)_tableStore.GetValue(treeIter, 6)),
|
||||
FileExtension = (string)_tableStore.GetValue(treeIter, 7),
|
||||
FileSize = ValueFormatUtils.ParseFileSize((string)_tableStore.GetValue(treeIter, 8)),
|
||||
Path = (string)_tableStore.GetValue(treeIter, 9),
|
||||
ControlHolder = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10),
|
||||
};
|
||||
|
||||
RunApplication(path);
|
||||
RunApplication(application);
|
||||
}
|
||||
|
||||
private void VSyncStatus_Clicked(object sender, ButtonReleaseEventArgs args)
|
||||
@ -1313,13 +1335,22 @@ namespace Ryujinx.UI
|
||||
return;
|
||||
}
|
||||
|
||||
string titleFilePath = _tableStore.GetValue(treeIter, 9).ToString();
|
||||
string titleName = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[0];
|
||||
string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower();
|
||||
ApplicationData application = new()
|
||||
{
|
||||
Favorite = (bool)_tableStore.GetValue(treeIter, 0),
|
||||
Name = ((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[0],
|
||||
Id = ulong.Parse(((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[1], NumberStyles.HexNumber),
|
||||
Developer = (string)_tableStore.GetValue(treeIter, 3),
|
||||
Version = (string)_tableStore.GetValue(treeIter, 4),
|
||||
TimePlayed = ValueFormatUtils.ParseTimeSpan((string)_tableStore.GetValue(treeIter, 5)),
|
||||
LastPlayed = ValueFormatUtils.ParseDateTime((string)_tableStore.GetValue(treeIter, 6)),
|
||||
FileExtension = (string)_tableStore.GetValue(treeIter, 7),
|
||||
FileSize = ValueFormatUtils.ParseFileSize((string)_tableStore.GetValue(treeIter, 8)),
|
||||
Path = (string)_tableStore.GetValue(treeIter, 9),
|
||||
ControlHolder = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10),
|
||||
};
|
||||
|
||||
BlitStruct<ApplicationControlProperty> controlData = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10);
|
||||
|
||||
_ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, titleFilePath, titleName, titleId, controlData);
|
||||
_ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, application);
|
||||
}
|
||||
|
||||
private void Load_Application_File(object sender, EventArgs args)
|
||||
@ -1341,7 +1372,15 @@ namespace Ryujinx.UI
|
||||
|
||||
if (fileChooser.Run() == (int)ResponseType.Accept)
|
||||
{
|
||||
RunApplication(fileChooser.Filename);
|
||||
if (ApplicationLibrary.TryGetApplicationsFromFile(fileChooser.Filename,
|
||||
out List<ApplicationData> applications))
|
||||
{
|
||||
RunApplication(applications[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
GtkDialog.CreateErrorDialog("No applications found in selected file.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1351,7 +1390,13 @@ namespace Ryujinx.UI
|
||||
|
||||
if (fileChooser.Run() == (int)ResponseType.Accept)
|
||||
{
|
||||
RunApplication(fileChooser.Filename);
|
||||
ApplicationData applicationData = new()
|
||||
{
|
||||
Name = System.IO.Path.GetFileNameWithoutExtension(fileChooser.Filename),
|
||||
Path = fileChooser.Filename,
|
||||
};
|
||||
|
||||
RunApplication(applicationData);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1366,7 +1411,14 @@ namespace Ryujinx.UI
|
||||
{
|
||||
string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
|
||||
|
||||
RunApplication(contentPath);
|
||||
ApplicationData applicationData = new()
|
||||
{
|
||||
Name = "miiEdit",
|
||||
Id = 0x0100000000001009ul,
|
||||
Path = contentPath,
|
||||
};
|
||||
|
||||
RunApplication(applicationData);
|
||||
}
|
||||
|
||||
private void Open_Ryu_Folder(object sender, EventArgs args)
|
||||
@ -1646,13 +1698,13 @@ namespace Ryujinx.UI
|
||||
{
|
||||
_userChannelPersistence.ShouldRestart = false;
|
||||
|
||||
RunApplication(_currentEmulatedGamePath);
|
||||
RunApplication(_currentApplicationData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise, clear state.
|
||||
_userChannelPersistence = new UserChannelPersistence();
|
||||
_currentEmulatedGamePath = null;
|
||||
_currentApplicationData = null;
|
||||
_actionMenu.Sensitive = false;
|
||||
_firmwareInstallFile.Sensitive = true;
|
||||
_firmwareInstallDirectory.Sensitive = true;
|
||||
@ -1714,7 +1766,7 @@ namespace Ryujinx.UI
|
||||
_emulationContext.Processes.ActiveApplication.ProgramId,
|
||||
_emulationContext.Processes.ActiveApplication.ApplicationControlProperties
|
||||
.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString(),
|
||||
_currentEmulatedGamePath);
|
||||
_currentApplicationData.Path);
|
||||
|
||||
window.Destroyed += CheatWindow_Destroyed;
|
||||
window.Show();
|
||||
|
@ -13,16 +13,13 @@ using Ryujinx.Input.HLE;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using Ryujinx.UI.Common.Helper;
|
||||
using Ryujinx.UI.Widgets;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Png;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
using Key = Ryujinx.Input.Key;
|
||||
using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter;
|
||||
using Switch = Ryujinx.HLE.Switch;
|
||||
@ -404,23 +401,31 @@ namespace Ryujinx.UI
|
||||
return;
|
||||
}
|
||||
|
||||
Image image = e.IsBgra ? Image.LoadPixelData<Bgra32>(e.Data, e.Width, e.Height)
|
||||
: Image.LoadPixelData<Rgba32>(e.Data, e.Width, e.Height);
|
||||
var colorType = e.IsBgra ? SKColorType.Bgra8888 : SKColorType.Rgba8888;
|
||||
using var image = new SKBitmap(new SKImageInfo(e.Width, e.Height, colorType, SKAlphaType.Premul));
|
||||
|
||||
if (e.FlipX)
|
||||
Marshal.Copy(e.Data, 0, image.GetPixels(), e.Data.Length);
|
||||
using var surface = SKSurface.Create(image.Info);
|
||||
var canvas = surface.Canvas;
|
||||
|
||||
if (e.FlipX || e.FlipY)
|
||||
{
|
||||
image.Mutate(x => x.Flip(FlipMode.Horizontal));
|
||||
canvas.Clear(SKColors.Transparent);
|
||||
|
||||
float scaleX = e.FlipX ? -1 : 1;
|
||||
float scaleY = e.FlipY ? -1 : 1;
|
||||
|
||||
var matrix = SKMatrix.CreateScale(scaleX, scaleY, image.Width / 2f, image.Height / 2f);
|
||||
|
||||
canvas.SetMatrix(matrix);
|
||||
}
|
||||
canvas.DrawBitmap(image, new SKPoint());
|
||||
|
||||
if (e.FlipY)
|
||||
{
|
||||
image.Mutate(x => x.Flip(FlipMode.Vertical));
|
||||
}
|
||||
|
||||
image.SaveAsPng(path, new PngEncoder()
|
||||
{
|
||||
ColorType = PngColorType.Rgb,
|
||||
});
|
||||
surface.Flush();
|
||||
using var snapshot = surface.Snapshot();
|
||||
using var encoded = snapshot.Encode(SKEncodedImageFormat.Png, 80);
|
||||
using var file = File.OpenWrite(path);
|
||||
encoded.SaveTo(file);
|
||||
|
||||
image.Dispose();
|
||||
|
||||
|
@ -16,6 +16,8 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using Ryujinx.UI.App.Common;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using Ryujinx.UI.Common.Helper;
|
||||
@ -23,7 +25,6 @@ using Ryujinx.UI.Windows;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
@ -36,17 +37,13 @@ namespace Ryujinx.UI.Widgets
|
||||
private readonly VirtualFileSystem _virtualFileSystem;
|
||||
private readonly AccountManager _accountManager;
|
||||
private readonly HorizonClient _horizonClient;
|
||||
private readonly BlitStruct<ApplicationControlProperty> _controlData;
|
||||
|
||||
private readonly string _titleFilePath;
|
||||
private readonly string _titleName;
|
||||
private readonly string _titleIdText;
|
||||
private readonly ulong _titleId;
|
||||
private readonly ApplicationData _applicationData;
|
||||
|
||||
private MessageDialog _dialog;
|
||||
private bool _cancel;
|
||||
|
||||
public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, string titleFilePath, string titleName, string titleId, BlitStruct<ApplicationControlProperty> controlData)
|
||||
public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, ApplicationData applicationData)
|
||||
{
|
||||
_parent = parent;
|
||||
|
||||
@ -55,23 +52,22 @@ namespace Ryujinx.UI.Widgets
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
_accountManager = accountManager;
|
||||
_horizonClient = horizonClient;
|
||||
_titleFilePath = titleFilePath;
|
||||
_titleName = titleName;
|
||||
_titleIdText = titleId;
|
||||
_controlData = controlData;
|
||||
_applicationData = applicationData;
|
||||
|
||||
if (!ulong.TryParse(_titleIdText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _titleId))
|
||||
if (!_applicationData.ControlHolder.ByteSpan.IsZeros())
|
||||
{
|
||||
GtkDialog.CreateErrorDialog("The selected game did not have a valid Title Id");
|
||||
|
||||
return;
|
||||
_openSaveUserDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.UserAccountSaveDataSize > 0;
|
||||
_openSaveDeviceDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.DeviceSaveDataSize > 0;
|
||||
_openSaveBcatDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_openSaveUserDirMenuItem.Sensitive = false;
|
||||
_openSaveDeviceDirMenuItem.Sensitive = false;
|
||||
_openSaveBcatDirMenuItem.Sensitive = false;
|
||||
}
|
||||
|
||||
_openSaveUserDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0;
|
||||
_openSaveDeviceDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0;
|
||||
_openSaveBcatDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0;
|
||||
|
||||
string fileExt = System.IO.Path.GetExtension(_titleFilePath).ToLower();
|
||||
string fileExt = System.IO.Path.GetExtension(_applicationData.Path).ToLower();
|
||||
bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci";
|
||||
|
||||
_extractRomFsMenuItem.Sensitive = hasNca;
|
||||
@ -137,7 +133,7 @@ namespace Ryujinx.UI.Widgets
|
||||
|
||||
private void OpenSaveDir(in SaveDataFilter saveDataFilter)
|
||||
{
|
||||
if (!TryFindSaveData(_titleName, _titleId, _controlData, in saveDataFilter, out ulong saveDataId))
|
||||
if (!TryFindSaveData(_applicationData.Name, _applicationData.Id, _applicationData.ControlHolder, in saveDataFilter, out ulong saveDataId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -190,7 +186,7 @@ namespace Ryujinx.UI.Widgets
|
||||
{
|
||||
Title = "Ryujinx - NCA Section Extractor",
|
||||
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png"),
|
||||
SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_titleFilePath)}...",
|
||||
SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_applicationData.Path)}...",
|
||||
WindowPosition = WindowPosition.Center,
|
||||
};
|
||||
|
||||
@ -202,29 +198,16 @@ namespace Ryujinx.UI.Widgets
|
||||
}
|
||||
});
|
||||
|
||||
using FileStream file = new(_titleFilePath, FileMode.Open, FileAccess.Read);
|
||||
using FileStream file = new(_applicationData.Path, FileMode.Open, FileAccess.Read);
|
||||
|
||||
Nca mainNca = null;
|
||||
Nca patchNca = null;
|
||||
|
||||
if ((System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nsp") ||
|
||||
(System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".pfs0") ||
|
||||
(System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".xci"))
|
||||
if ((System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".nsp") ||
|
||||
(System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".pfs0") ||
|
||||
(System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".xci"))
|
||||
{
|
||||
IFileSystem pfs;
|
||||
|
||||
if (System.IO.Path.GetExtension(_titleFilePath) == ".xci")
|
||||
{
|
||||
Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
|
||||
|
||||
pfs = xci.OpenPartition(XciPartitionType.Secure);
|
||||
}
|
||||
else
|
||||
{
|
||||
var pfsTemp = new PartitionFileSystem();
|
||||
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||
pfs = pfsTemp;
|
||||
}
|
||||
IFileSystem pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(_applicationData.Path, _virtualFileSystem);
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
@ -249,7 +232,7 @@ namespace Ryujinx.UI.Widgets
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nca")
|
||||
else if (System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".nca")
|
||||
{
|
||||
mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage());
|
||||
}
|
||||
@ -266,7 +249,11 @@ namespace Ryujinx.UI.Widgets
|
||||
return;
|
||||
}
|
||||
|
||||
(Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
|
||||
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||
? IntegrityCheckLevel.ErrorOnInvalid
|
||||
: IntegrityCheckLevel.None;
|
||||
|
||||
(Nca updatePatchNca, _) = mainNca.GetUpdateData(_virtualFileSystem, checkLevel, programIndex, out _);
|
||||
|
||||
if (updatePatchNca != null)
|
||||
{
|
||||
@ -460,44 +447,44 @@ namespace Ryujinx.UI.Widgets
|
||||
private void OpenSaveUserDir_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
var userId = new LibHac.Fs.UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
|
||||
var saveDataFilter = SaveDataFilter.Make(_titleId, saveType: default, userId, saveDataId: default, index: default);
|
||||
var saveDataFilter = SaveDataFilter.Make(_applicationData.Id, saveType: default, userId, saveDataId: default, index: default);
|
||||
|
||||
OpenSaveDir(in saveDataFilter);
|
||||
}
|
||||
|
||||
private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Device, userId: default, saveDataId: default, index: default);
|
||||
var saveDataFilter = SaveDataFilter.Make(_applicationData.Id, SaveDataType.Device, userId: default, saveDataId: default, index: default);
|
||||
|
||||
OpenSaveDir(in saveDataFilter);
|
||||
}
|
||||
|
||||
private void OpenSaveBcatDir_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Bcat, userId: default, saveDataId: default, index: default);
|
||||
var saveDataFilter = SaveDataFilter.Make(_applicationData.Id, SaveDataType.Bcat, userId: default, saveDataId: default, index: default);
|
||||
|
||||
OpenSaveDir(in saveDataFilter);
|
||||
}
|
||||
|
||||
private void ManageTitleUpdates_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
new TitleUpdateWindow(_parent, _virtualFileSystem, _titleIdText, _titleName).Show();
|
||||
new TitleUpdateWindow(_parent, _virtualFileSystem, _applicationData).Show();
|
||||
}
|
||||
|
||||
private void ManageDlc_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
new DlcWindow(_virtualFileSystem, _titleIdText, _titleName).Show();
|
||||
new DlcWindow(_virtualFileSystem, _applicationData.IdBaseString, _applicationData).Show();
|
||||
}
|
||||
|
||||
private void ManageCheats_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
new CheatWindow(_virtualFileSystem, _titleId, _titleName, _titleFilePath).Show();
|
||||
new CheatWindow(_virtualFileSystem, _applicationData.Id, _applicationData.Name, _applicationData.Path).Show();
|
||||
}
|
||||
|
||||
private void OpenTitleModDir_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
string modsBasePath = ModLoader.GetModsBasePath();
|
||||
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, _titleIdText);
|
||||
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, _applicationData.IdString);
|
||||
|
||||
OpenHelper.OpenFolder(titleModsPath);
|
||||
}
|
||||
@ -505,7 +492,7 @@ namespace Ryujinx.UI.Widgets
|
||||
private void OpenTitleSdModDir_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
string sdModsBasePath = ModLoader.GetSdModsBasePath();
|
||||
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, _titleIdText);
|
||||
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, _applicationData.IdString);
|
||||
|
||||
OpenHelper.OpenFolder(titleModsPath);
|
||||
}
|
||||
@ -527,7 +514,7 @@ namespace Ryujinx.UI.Widgets
|
||||
|
||||
private void OpenPtcDir_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu");
|
||||
string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "cpu");
|
||||
|
||||
string mainPath = System.IO.Path.Combine(ptcDir, "0");
|
||||
string backupPath = System.IO.Path.Combine(ptcDir, "1");
|
||||
@ -544,7 +531,7 @@ namespace Ryujinx.UI.Widgets
|
||||
|
||||
private void OpenShaderCacheDir_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader");
|
||||
string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "shader");
|
||||
|
||||
if (!Directory.Exists(shaderCacheDir))
|
||||
{
|
||||
@ -556,10 +543,10 @@ namespace Ryujinx.UI.Widgets
|
||||
|
||||
private void PurgePtcCache_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
DirectoryInfo mainDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "0"));
|
||||
DirectoryInfo backupDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "1"));
|
||||
DirectoryInfo mainDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "cpu", "0"));
|
||||
DirectoryInfo backupDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "cpu", "1"));
|
||||
|
||||
MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to queue a PPTC rebuild on the next boot of:\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?");
|
||||
MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to queue a PPTC rebuild on the next boot of:\n\n<b>{_applicationData.Name}</b>\n\nAre you sure you want to proceed?");
|
||||
|
||||
List<FileInfo> cacheFiles = new();
|
||||
|
||||
@ -593,9 +580,9 @@ namespace Ryujinx.UI.Widgets
|
||||
|
||||
private void PurgeShaderCache_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
DirectoryInfo shaderCacheDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader"));
|
||||
DirectoryInfo shaderCacheDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "shader"));
|
||||
|
||||
using MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?");
|
||||
using MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n<b>{_applicationData.Name}</b>\n\nAre you sure you want to proceed?");
|
||||
|
||||
List<DirectoryInfo> oldCacheDirectories = new();
|
||||
List<FileInfo> newCacheFiles = new();
|
||||
@ -637,8 +624,11 @@ namespace Ryujinx.UI.Widgets
|
||||
|
||||
private void CreateShortcut_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
byte[] appIcon = new ApplicationLibrary(_virtualFileSystem).GetApplicationIcon(_titleFilePath, ConfigurationState.Instance.System.Language);
|
||||
ShortcutHelper.CreateAppShortcut(_titleFilePath, _titleName, _titleIdText, appIcon);
|
||||
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||
? IntegrityCheckLevel.ErrorOnInvalid
|
||||
: IntegrityCheckLevel.None;
|
||||
byte[] appIcon = new ApplicationLibrary(_virtualFileSystem, checkLevel).GetApplicationIcon(_applicationData.Path, ConfigurationState.Instance.System.Language, _applicationData.Id);
|
||||
ShortcutHelper.CreateAppShortcut(_applicationData.Path, _applicationData.Name, _applicationData.IdString, appIcon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,16 +9,13 @@ using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Png;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.UI.Windows
|
||||
{
|
||||
@ -144,9 +141,11 @@ namespace Ryujinx.UI.Windows
|
||||
|
||||
stream.Position = 0;
|
||||
|
||||
Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256);
|
||||
using var avatarImage = new SKBitmap(new SKImageInfo(256, 256, SKColorType.Rgba8888));
|
||||
var data = DecompressYaz0(stream);
|
||||
Marshal.Copy(data, 0, avatarImage.GetPixels(), data.Length);
|
||||
|
||||
avatarImage.SaveAsPng(streamPng);
|
||||
avatarImage.Encode(streamPng, SKEncodedImageFormat.Png, 80);
|
||||
|
||||
_avatarDict.Add(item.FullPath, streamPng.ToArray());
|
||||
}
|
||||
@ -170,15 +169,23 @@ namespace Ryujinx.UI.Windows
|
||||
{
|
||||
using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream();
|
||||
|
||||
Image avatarImage = Image.Load(data, new PngDecoder());
|
||||
using var avatarImage = SKBitmap.Decode(data);
|
||||
using var surface = SKSurface.Create(avatarImage.Info);
|
||||
|
||||
avatarImage.Mutate(x => x.BackgroundColor(new Rgba32(
|
||||
var background = new SKColor(
|
||||
(byte)(_backgroundColor.Red * 255),
|
||||
(byte)(_backgroundColor.Green * 255),
|
||||
(byte)(_backgroundColor.Blue * 255),
|
||||
(byte)(_backgroundColor.Alpha * 255)
|
||||
)));
|
||||
avatarImage.SaveAsJpeg(streamJpg);
|
||||
);
|
||||
var canvas = surface.Canvas;
|
||||
canvas.Clear(background);
|
||||
canvas.DrawBitmap(avatarImage, new SKPoint());
|
||||
|
||||
surface.Flush();
|
||||
using var snapshot = surface.Snapshot();
|
||||
using var encoded = snapshot.Encode(SKEncodedImageFormat.Jpeg, 80);
|
||||
encoded.SaveTo(streamJpg);
|
||||
|
||||
return streamJpg.ToArray();
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
using Gtk;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.UI.App.Common;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@ -27,8 +29,13 @@ namespace Ryujinx.UI.Windows
|
||||
private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : base(builder.GetRawOwnedObject("_cheatWindow"))
|
||||
{
|
||||
builder.Autoconnect(this);
|
||||
|
||||
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||
? IntegrityCheckLevel.ErrorOnInvalid
|
||||
: IntegrityCheckLevel.None;
|
||||
|
||||
_baseTitleInfoLabel.Text = $"Cheats Available for {titleName} [{titleId:X16}]";
|
||||
_buildIdTextView.Buffer.Text = $"BuildId: {ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath)}";
|
||||
_buildIdTextView.Buffer.Text = $"BuildId: {ApplicationData.GetBuildId(virtualFileSystem, checkLevel, titlePath)}";
|
||||
|
||||
string modsBasePath = ModLoader.GetModsBasePath();
|
||||
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, titleId.ToString("X16"));
|
||||
|
@ -2,17 +2,21 @@ using Gtk;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using Ryujinx.UI.App.Common;
|
||||
using Ryujinx.UI.Widgets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
|
||||
namespace Ryujinx.UI.Windows
|
||||
@ -20,7 +24,7 @@ namespace Ryujinx.UI.Windows
|
||||
public class DlcWindow : Window
|
||||
{
|
||||
private readonly VirtualFileSystem _virtualFileSystem;
|
||||
private readonly string _titleId;
|
||||
private readonly string _applicationIdBase;
|
||||
private readonly string _dlcJsonPath;
|
||||
private readonly List<DownloadableContentContainer> _dlcContainerList;
|
||||
|
||||
@ -32,16 +36,16 @@ namespace Ryujinx.UI.Windows
|
||||
[GUI] TreeSelection _dlcTreeSelection;
|
||||
#pragma warning restore CS0649, IDE0044
|
||||
|
||||
public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Gtk3.UI.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { }
|
||||
public DlcWindow(VirtualFileSystem virtualFileSystem, string applicationIdBase, ApplicationData applicationData) : this(new Builder("Ryujinx.Gtk3.UI.Windows.DlcWindow.glade"), virtualFileSystem, applicationIdBase, applicationData) { }
|
||||
|
||||
private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_dlcWindow"))
|
||||
private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string applicationIdBase, ApplicationData applicationData) : base(builder.GetRawOwnedObject("_dlcWindow"))
|
||||
{
|
||||
builder.Autoconnect(this);
|
||||
|
||||
_titleId = titleId;
|
||||
_applicationIdBase = applicationIdBase;
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
_dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "dlc.json");
|
||||
_baseTitleInfoLabel.Text = $"DLC Available for {titleName} [{titleId.ToUpper()}]";
|
||||
_dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationIdBase, "dlc.json");
|
||||
_baseTitleInfoLabel.Text = $"DLC Available for {applicationData.Name} [{applicationIdBase.ToUpper()}]";
|
||||
|
||||
try
|
||||
{
|
||||
@ -72,7 +76,7 @@ namespace Ryujinx.UI.Windows
|
||||
};
|
||||
|
||||
_dlcTreeView.AppendColumn("Enabled", enableToggle, "active", 0);
|
||||
_dlcTreeView.AppendColumn("TitleId", new CellRendererText(), "text", 1);
|
||||
_dlcTreeView.AppendColumn("ApplicationId", new CellRendererText(), "text", 1);
|
||||
_dlcTreeView.AppendColumn("Path", new CellRendererText(), "text", 2);
|
||||
|
||||
foreach (DownloadableContentContainer dlcContainer in _dlcContainerList)
|
||||
@ -86,18 +90,18 @@ namespace Ryujinx.UI.Windows
|
||||
bool areAllContentPacksEnabled = dlcContainer.DownloadableContentNcaList.TrueForAll((nca) => nca.Enabled);
|
||||
TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(areAllContentPacksEnabled, "", dlcContainer.ContainerPath);
|
||||
|
||||
using FileStream containerFile = File.OpenRead(dlcContainer.ContainerPath);
|
||||
using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(dlcContainer.ContainerPath, _virtualFileSystem, false);
|
||||
|
||||
PartitionFileSystem pfs = new();
|
||||
pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||
|
||||
_virtualFileSystem.ImportTickets(pfs);
|
||||
if (partitionFileSystem == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (DownloadableContentNca dlcNca in dlcContainer.DownloadableContentNcaList)
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs.OpenFile(ref ncaFile.Ref, dlcNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
partitionFileSystem.OpenFile(ref ncaFile.Ref, dlcNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), dlcContainer.ContainerPath);
|
||||
|
||||
if (nca != null)
|
||||
@ -112,6 +116,9 @@ namespace Ryujinx.UI.Windows
|
||||
TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(false, "", $"(MISSING) {dlcContainer.ContainerPath}");
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Try to load downloadable contents from PFS last to preserve enabled state.
|
||||
AddDlc(applicationData.Path, true);
|
||||
}
|
||||
|
||||
private Nca TryCreateNca(IStorage ncaStorage, string containerPath)
|
||||
@ -128,6 +135,52 @@ namespace Ryujinx.UI.Windows
|
||||
return null;
|
||||
}
|
||||
|
||||
private void AddDlc(string path, bool ignoreNotFound = false)
|
||||
{
|
||||
if (!File.Exists(path) || _dlcContainerList.Any(x => x.ContainerPath == path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(path, _virtualFileSystem);
|
||||
|
||||
bool containsDlc = false;
|
||||
|
||||
TreeIter? parentIter = null;
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), path);
|
||||
|
||||
if (nca == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.PublicData)
|
||||
{
|
||||
if (nca.GetProgramIdBase() != ulong.Parse(_applicationIdBase, NumberStyles.HexNumber))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
parentIter ??= ((TreeStore)_dlcTreeView.Model).AppendValues(true, "", path);
|
||||
|
||||
((TreeStore)_dlcTreeView.Model).AppendValues(parentIter.Value, true, nca.Header.TitleId.ToString("X16"), fileEntry.FullPath);
|
||||
containsDlc = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!containsDlc && !ignoreNotFound)
|
||||
{
|
||||
GtkDialog.CreateErrorDialog("The specified file does not contain DLC for the selected title!");
|
||||
}
|
||||
}
|
||||
|
||||
private void AddButton_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
FileChooserNative fileChooser = new("Select DLC files", this, FileChooserAction.Open, "Add", "Cancel")
|
||||
@ -147,52 +200,7 @@ namespace Ryujinx.UI.Windows
|
||||
{
|
||||
foreach (string containerPath in fileChooser.Filenames)
|
||||
{
|
||||
if (!File.Exists(containerPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using FileStream containerFile = File.OpenRead(containerPath);
|
||||
|
||||
PartitionFileSystem pfs = new();
|
||||
pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||
bool containsDlc = false;
|
||||
|
||||
_virtualFileSystem.ImportTickets(pfs);
|
||||
|
||||
TreeIter? parentIter = null;
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), containerPath);
|
||||
|
||||
if (nca == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.PublicData)
|
||||
{
|
||||
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000).ToString("x16") != _titleId)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
parentIter ??= ((TreeStore)_dlcTreeView.Model).AppendValues(true, "", containerPath);
|
||||
|
||||
((TreeStore)_dlcTreeView.Model).AppendValues(parentIter.Value, true, nca.Header.TitleId.ToString("X16"), fileEntry.FullPath);
|
||||
containsDlc = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!containsDlc)
|
||||
{
|
||||
GtkDialog.CreateErrorDialog("The specified file does not contain DLC for the selected title!");
|
||||
}
|
||||
AddDlc(containerPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,14 +2,17 @@ using Gtk;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Ns;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using Ryujinx.UI.App.Common;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using Ryujinx.UI.Widgets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -24,7 +27,7 @@ namespace Ryujinx.UI.Windows
|
||||
{
|
||||
private readonly MainWindow _parent;
|
||||
private readonly VirtualFileSystem _virtualFileSystem;
|
||||
private readonly string _titleId;
|
||||
private readonly ApplicationData _applicationData;
|
||||
private readonly string _updateJsonPath;
|
||||
|
||||
private TitleUpdateMetadata _titleUpdateWindowData;
|
||||
@ -38,17 +41,17 @@ namespace Ryujinx.UI.Windows
|
||||
[GUI] RadioButton _noUpdateRadioButton;
|
||||
#pragma warning restore CS0649, IDE0044
|
||||
|
||||
public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Gtk3.UI.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { }
|
||||
public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ApplicationData applicationData) : this(new Builder("Ryujinx.Gtk3.UI.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, applicationData) { }
|
||||
|
||||
private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_titleUpdateWindow"))
|
||||
private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, ApplicationData applicationData) : base(builder.GetRawOwnedObject("_titleUpdateWindow"))
|
||||
{
|
||||
_parent = parent;
|
||||
|
||||
builder.Autoconnect(this);
|
||||
|
||||
_titleId = titleId;
|
||||
_applicationData = applicationData;
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
_updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "updates.json");
|
||||
_updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, applicationData.IdBaseString, "updates.json");
|
||||
_radioButtonToPathDictionary = new Dictionary<RadioButton, string>();
|
||||
|
||||
try
|
||||
@ -64,7 +67,10 @@ namespace Ryujinx.UI.Windows
|
||||
};
|
||||
}
|
||||
|
||||
_baseTitleInfoLabel.Text = $"Updates Available for {titleName} [{titleId.ToUpper()}]";
|
||||
_baseTitleInfoLabel.Text = $"Updates Available for {applicationData.Name} [{applicationData.IdBaseString}]";
|
||||
|
||||
// Try to get updates from PFS first
|
||||
AddUpdate(_applicationData.Path, true);
|
||||
|
||||
foreach (string path in _titleUpdateWindowData.Paths)
|
||||
{
|
||||
@ -84,46 +90,68 @@ namespace Ryujinx.UI.Windows
|
||||
}
|
||||
}
|
||||
|
||||
private void AddUpdate(string path)
|
||||
private void AddUpdate(string path, bool ignoreNotFound = false)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
if (!File.Exists(path) || _radioButtonToPathDictionary.ContainsValue(path))
|
||||
{
|
||||
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||
return;
|
||||
}
|
||||
|
||||
PartitionFileSystem nsp = new();
|
||||
nsp.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||
? IntegrityCheckLevel.ErrorOnInvalid
|
||||
: IntegrityCheckLevel.None;
|
||||
|
||||
try
|
||||
try
|
||||
{
|
||||
using IFileSystem pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(path, _virtualFileSystem);
|
||||
|
||||
Dictionary<ulong, ContentMetaData> updates = pfs.GetContentData(ContentMetaType.Patch, _virtualFileSystem, checkLevel);
|
||||
|
||||
Nca patchNca = null;
|
||||
Nca controlNca = null;
|
||||
|
||||
if (updates.TryGetValue(_applicationData.Id, out ContentMetaData update))
|
||||
{
|
||||
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, nsp, _titleId, 0);
|
||||
patchNca = update.GetNcaByType(_virtualFileSystem.KeySet, LibHac.Ncm.ContentType.Program);
|
||||
controlNca = update.GetNcaByType(_virtualFileSystem.KeySet, LibHac.Ncm.ContentType.Control);
|
||||
}
|
||||
|
||||
if (controlNca != null && patchNca != null)
|
||||
if (controlNca != null && patchNca != null)
|
||||
{
|
||||
ApplicationControlProperty controlData = new();
|
||||
|
||||
using var nacpFile = new UniqueRef<IFile>();
|
||||
|
||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
||||
|
||||
string radioLabel = $"Version {controlData.DisplayVersionString.ToString()} - {path}";
|
||||
|
||||
if (System.IO.Path.GetExtension(path).ToLower() == ".xci")
|
||||
{
|
||||
ApplicationControlProperty controlData = new();
|
||||
|
||||
using var nacpFile = new UniqueRef<IFile>();
|
||||
|
||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
||||
|
||||
RadioButton radioButton = new($"Version {controlData.DisplayVersionString.ToString()} - {path}");
|
||||
radioButton.JoinGroup(_noUpdateRadioButton);
|
||||
|
||||
_availableUpdatesBox.Add(radioButton);
|
||||
_radioButtonToPathDictionary.Add(radioButton, path);
|
||||
|
||||
radioButton.Show();
|
||||
radioButton.Active = true;
|
||||
radioLabel = "Bundled: " + radioLabel;
|
||||
}
|
||||
else
|
||||
|
||||
RadioButton radioButton = new(radioLabel);
|
||||
radioButton.JoinGroup(_noUpdateRadioButton);
|
||||
|
||||
_availableUpdatesBox.Add(radioButton);
|
||||
_radioButtonToPathDictionary.Add(radioButton, path);
|
||||
|
||||
radioButton.Show();
|
||||
radioButton.Active = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ignoreNotFound)
|
||||
{
|
||||
GtkDialog.CreateErrorDialog("The specified file does not contain an update for the selected title!");
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}");
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,15 +4,13 @@ using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using Ryujinx.UI.Widgets;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
|
||||
namespace Ryujinx.UI.Windows
|
||||
{
|
||||
@ -177,13 +175,13 @@ namespace Ryujinx.UI.Windows
|
||||
|
||||
private void ProcessProfileImage(byte[] buffer)
|
||||
{
|
||||
using Image image = Image.Load(buffer);
|
||||
using var image = SKBitmap.Decode(buffer);
|
||||
|
||||
image.Mutate(x => x.Resize(256, 256));
|
||||
image.Resize(new SKImageInfo(256, 256), SKFilterQuality.High);
|
||||
|
||||
using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream();
|
||||
|
||||
image.SaveAsJpeg(streamJpg);
|
||||
image.Encode(streamJpg, SKEncodedImageFormat.Jpeg, 80);
|
||||
|
||||
_bufferImageProfile = streamJpg.ToArray();
|
||||
}
|
||||
|
63
src/Ryujinx.HLE.Generators/CodeGenerator.cs
Normal file
63
src/Ryujinx.HLE.Generators/CodeGenerator.cs
Normal file
@ -0,0 +1,63 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.Generators
|
||||
{
|
||||
class CodeGenerator
|
||||
{
|
||||
private const int IndentLength = 4;
|
||||
|
||||
private readonly StringBuilder _sb;
|
||||
private int _currentIndentCount;
|
||||
|
||||
public CodeGenerator()
|
||||
{
|
||||
_sb = new StringBuilder();
|
||||
}
|
||||
|
||||
public void EnterScope(string header = null)
|
||||
{
|
||||
if (header != null)
|
||||
{
|
||||
AppendLine(header);
|
||||
}
|
||||
|
||||
AppendLine("{");
|
||||
IncreaseIndentation();
|
||||
}
|
||||
|
||||
public void LeaveScope(string suffix = "")
|
||||
{
|
||||
DecreaseIndentation();
|
||||
AppendLine($"}}{suffix}");
|
||||
}
|
||||
|
||||
public void IncreaseIndentation()
|
||||
{
|
||||
_currentIndentCount++;
|
||||
}
|
||||
|
||||
public void DecreaseIndentation()
|
||||
{
|
||||
if (_currentIndentCount - 1 >= 0)
|
||||
{
|
||||
_currentIndentCount--;
|
||||
}
|
||||
}
|
||||
|
||||
public void AppendLine()
|
||||
{
|
||||
_sb.AppendLine();
|
||||
}
|
||||
|
||||
public void AppendLine(string text)
|
||||
{
|
||||
_sb.Append(' ', IndentLength * _currentIndentCount);
|
||||
_sb.AppendLine(text);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user