From 6ed613a6e6a66d57d2fdb045d926e42dfcdd3206 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Wed, 16 Aug 2023 21:31:07 -0300 Subject: [PATCH 01/64] Fix vote and shuffle shader instructions on AMD GPUs (#5540) * Move shuffle handling out of the backend to a transform pass * Handle subgroup sizes higher than 32 * Stop using the subgroup size control extension * Make GenerateShuffleFunction static * Shader cache version bump --- src/Ryujinx.Graphics.GAL/Capabilities.cs | 3 + .../Shader/DiskCache/DiskCacheHostStorage.cs | 2 +- .../Shader/GpuAccessorBase.cs | 2 + src/Ryujinx.Graphics.OpenGL/Constants.cs | 1 + src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs | 1 + .../CodeGen/Glsl/Declarations.cs | 21 +-- .../HelperFunctions/HelperFunctionNames.cs | 4 - .../CodeGen/Glsl/HelperFunctions/Shuffle.glsl | 11 -- .../Glsl/HelperFunctions/ShuffleDown.glsl | 11 -- .../Glsl/HelperFunctions/ShuffleUp.glsl | 9 -- .../Glsl/HelperFunctions/ShuffleXor.glsl | 11 -- .../CodeGen/Glsl/Instructions/InstGen.cs | 4 + .../Glsl/Instructions/InstGenBallot.cs | 5 +- .../Glsl/Instructions/InstGenHelper.cs | 8 +- .../Glsl/Instructions/InstGenShuffle.cs | 25 +++ .../CodeGen/Spirv/Instructions.cs | 95 ++---------- .../CodeGen/Spirv/SpirvGenerator.cs | 7 +- .../Decoders/Decoder.cs | 3 + src/Ryujinx.Graphics.Shader/IGpuAccessor.cs | 9 ++ .../Instructions/InstEmitMove.cs | 58 ++++++- .../Instructions/InstEmitWarp.cs | 91 +++++++++-- .../Ryujinx.Graphics.Shader.csproj | 4 - .../StructuredIr/HelperFunctionsMask.cs | 4 - .../StructuredIr/InstructionInfo.cs | 11 +- .../StructuredIr/StructuredProgram.cs | 12 -- .../Translation/EmitterContextInsts.cs | 28 +++- .../Translation/FeatureFlags.cs | 1 + .../Translation/HelperFunctionManager.cs | 145 ++++++++++++++++++ .../Translation/HelperFunctionName.cs | 6 + .../Translation/Transforms/ShufflePass.cs | 52 +++++++ .../Translation/Transforms/TransformPasses.cs | 1 + .../HardwareCapabilities.cs | 15 +- src/Ryujinx.Graphics.Vulkan/PipelineState.cs | 23 --- .../VulkanInitialization.cs | 1 - src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 26 ++-- 35 files changed, 445 insertions(+), 265 deletions(-) delete mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl delete mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl delete mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl delete mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenShuffle.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Transforms/ShufflePass.cs diff --git a/src/Ryujinx.Graphics.GAL/Capabilities.cs b/src/Ryujinx.Graphics.GAL/Capabilities.cs index f4b1d4d10..d41f8e59f 100644 --- a/src/Ryujinx.Graphics.GAL/Capabilities.cs +++ b/src/Ryujinx.Graphics.GAL/Capabilities.cs @@ -52,6 +52,7 @@ namespace Ryujinx.Graphics.GAL public readonly int MaximumComputeSharedMemorySize; public readonly float MaximumSupportedAnisotropy; + public readonly int ShaderSubgroupSize; public readonly int StorageBufferOffsetAlignment; public readonly int GatherBiasPrecision; @@ -101,6 +102,7 @@ namespace Ryujinx.Graphics.GAL uint maximumImagesPerStage, int maximumComputeSharedMemorySize, float maximumSupportedAnisotropy, + int shaderSubgroupSize, int storageBufferOffsetAlignment, int gatherBiasPrecision) { @@ -148,6 +150,7 @@ namespace Ryujinx.Graphics.GAL MaximumImagesPerStage = maximumImagesPerStage; MaximumComputeSharedMemorySize = maximumComputeSharedMemorySize; MaximumSupportedAnisotropy = maximumSupportedAnisotropy; + ShaderSubgroupSize = shaderSubgroupSize; StorageBufferOffsetAlignment = storageBufferOffsetAlignment; GatherBiasPrecision = gatherBiasPrecision; } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 9afc5b618..71a738255 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 5576; + private const uint CodeGenVersion = 5540; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs index e7a2d345f..52193940b 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs @@ -137,6 +137,8 @@ namespace Ryujinx.Graphics.Gpu.Shader public int QueryHostStorageBufferOffsetAlignment() => _context.Capabilities.StorageBufferOffsetAlignment; + public int QueryHostSubgroupSize() => _context.Capabilities.ShaderSubgroupSize; + public bool QueryHostSupportsBgraFormat() => _context.Capabilities.SupportsBgraFormat; public bool QueryHostSupportsFragmentShaderInterlock() => _context.Capabilities.SupportsFragmentShaderInterlock; diff --git a/src/Ryujinx.Graphics.OpenGL/Constants.cs b/src/Ryujinx.Graphics.OpenGL/Constants.cs index 8817011a9..38fedea0d 100644 --- a/src/Ryujinx.Graphics.OpenGL/Constants.cs +++ b/src/Ryujinx.Graphics.OpenGL/Constants.cs @@ -7,5 +7,6 @@ public const int MaxVertexAttribs = 16; public const int MaxVertexBuffers = 16; public const int MaxTransformFeedbackBuffers = 4; + public const int MaxSubgroupSize = 64; } } diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs index 8a7ac8559..35d1569fe 100644 --- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs +++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -175,6 +175,7 @@ namespace Ryujinx.Graphics.OpenGL maximumImagesPerStage: 8, maximumComputeSharedMemorySize: HwCapabilities.MaximumComputeSharedMemorySize, maximumSupportedAnisotropy: HwCapabilities.MaximumSupportedAnisotropy, + shaderSubgroupSize: Constants.MaxSubgroupSize, storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment, gatherBiasPrecision: intelWindows || amdWindows ? 8 : 0); // Precision is 8 for these vendors on Vulkan. } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs index e181ae98d..607ff431e 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -25,6 +25,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { context.AppendLine("#extension GL_KHR_shader_subgroup_basic : enable"); context.AppendLine("#extension GL_KHR_shader_subgroup_ballot : enable"); + context.AppendLine("#extension GL_KHR_shader_subgroup_shuffle : enable"); } context.AppendLine("#extension GL_ARB_shader_group_vote : enable"); @@ -201,26 +202,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighU32.glsl"); } - if ((info.HelperFunctionsMask & HelperFunctionsMask.Shuffle) != 0) - { - AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl"); - } - - if ((info.HelperFunctionsMask & HelperFunctionsMask.ShuffleDown) != 0) - { - AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl"); - } - - if ((info.HelperFunctionsMask & HelperFunctionsMask.ShuffleUp) != 0) - { - AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl"); - } - - if ((info.HelperFunctionsMask & HelperFunctionsMask.ShuffleXor) != 0) - { - AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl"); - } - if ((info.HelperFunctionsMask & HelperFunctionsMask.SwizzleAdd) != 0) { AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl"); diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs index 221802727..0b80ac2b6 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs @@ -5,10 +5,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl public static string MultiplyHighS32 = "Helper_MultiplyHighS32"; public static string MultiplyHighU32 = "Helper_MultiplyHighU32"; - public static string Shuffle = "Helper_Shuffle"; - public static string ShuffleDown = "Helper_ShuffleDown"; - public static string ShuffleUp = "Helper_ShuffleUp"; - public static string ShuffleXor = "Helper_ShuffleXor"; public static string SwizzleAdd = "Helper_SwizzleAdd"; } } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl deleted file mode 100644 index 7cb4764dd..000000000 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl +++ /dev/null @@ -1,11 +0,0 @@ -float Helper_Shuffle(float x, uint index, uint mask, out bool valid) -{ - uint clamp = mask & 0x1fu; - uint segMask = (mask >> 8) & 0x1fu; - uint minThreadId = $SUBGROUP_INVOCATION$ & segMask; - uint maxThreadId = minThreadId | (clamp & ~segMask); - uint srcThreadId = (index & ~segMask) | minThreadId; - valid = srcThreadId <= maxThreadId; - float v = $SUBGROUP_BROADCAST$(x, srcThreadId); - return valid ? v : x; -} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl deleted file mode 100644 index 71d901d5d..000000000 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl +++ /dev/null @@ -1,11 +0,0 @@ -float Helper_ShuffleDown(float x, uint index, uint mask, out bool valid) -{ - uint clamp = mask & 0x1fu; - uint segMask = (mask >> 8) & 0x1fu; - uint minThreadId = $SUBGROUP_INVOCATION$ & segMask; - uint maxThreadId = minThreadId | (clamp & ~segMask); - uint srcThreadId = $SUBGROUP_INVOCATION$ + index; - valid = srcThreadId <= maxThreadId; - float v = $SUBGROUP_BROADCAST$(x, srcThreadId); - return valid ? v : x; -} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl deleted file mode 100644 index ae264d870..000000000 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl +++ /dev/null @@ -1,9 +0,0 @@ -float Helper_ShuffleUp(float x, uint index, uint mask, out bool valid) -{ - uint segMask = (mask >> 8) & 0x1fu; - uint minThreadId = $SUBGROUP_INVOCATION$ & segMask; - uint srcThreadId = $SUBGROUP_INVOCATION$ - index; - valid = int(srcThreadId) >= int(minThreadId); - float v = $SUBGROUP_BROADCAST$(x, srcThreadId); - return valid ? v : x; -} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl deleted file mode 100644 index 789089d69..000000000 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl +++ /dev/null @@ -1,11 +0,0 @@ -float Helper_ShuffleXor(float x, uint index, uint mask, out bool valid) -{ - uint clamp = mask & 0x1fu; - uint segMask = (mask >> 8) & 0x1fu; - uint minThreadId = $SUBGROUP_INVOCATION$ & segMask; - uint maxThreadId = minThreadId | (clamp & ~segMask); - uint srcThreadId = $SUBGROUP_INVOCATION$ ^ index; - valid = srcThreadId <= maxThreadId; - float v = $SUBGROUP_BROADCAST$(x, srcThreadId); - return valid ? v : x; -} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs index 9208ceead..796eb4417 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs @@ -9,6 +9,7 @@ using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenFSI; using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenMemory; using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenPacking; +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenShuffle; using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenVector; using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; @@ -174,6 +175,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions case Instruction.PackHalf2x16: return PackHalf2x16(context, operation); + case Instruction.Shuffle: + return Shuffle(context, operation); + case Instruction.Store: return Store(context, operation); diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs index b44759c0d..6cc7048bd 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs @@ -13,14 +13,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions AggregateType dstType = GetSrcVarType(operation.Inst, 0); string arg = GetSoureExpr(context, operation.GetSource(0), dstType); + char component = "xyzw"[operation.Index]; if (context.HostCapabilities.SupportsShaderBallot) { - return $"unpackUint2x32(ballotARB({arg})).x"; + return $"unpackUint2x32(ballotARB({arg})).{component}"; } else { - return $"subgroupBallot({arg}).x"; + return $"subgroupBallot({arg}).{component}"; } } } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs index c3d52b2c5..eb194c209 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs @@ -108,10 +108,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions Add(Instruction.ShiftLeft, InstType.OpBinary, "<<", 3); Add(Instruction.ShiftRightS32, InstType.OpBinary, ">>", 3); Add(Instruction.ShiftRightU32, InstType.OpBinary, ">>", 3); - Add(Instruction.Shuffle, InstType.CallQuaternary, HelperFunctionNames.Shuffle); - Add(Instruction.ShuffleDown, InstType.CallQuaternary, HelperFunctionNames.ShuffleDown); - Add(Instruction.ShuffleUp, InstType.CallQuaternary, HelperFunctionNames.ShuffleUp); - Add(Instruction.ShuffleXor, InstType.CallQuaternary, HelperFunctionNames.ShuffleXor); + Add(Instruction.Shuffle, InstType.Special); + Add(Instruction.ShuffleDown, InstType.CallBinary, "subgroupShuffleDown"); + Add(Instruction.ShuffleUp, InstType.CallBinary, "subgroupShuffleUp"); + Add(Instruction.ShuffleXor, InstType.CallBinary, "subgroupShuffleXor"); Add(Instruction.Sine, InstType.CallUnary, "sin"); Add(Instruction.SquareRoot, InstType.CallUnary, "sqrt"); Add(Instruction.Store, InstType.Special); diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenShuffle.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenShuffle.cs new file mode 100644 index 000000000..6d3859efd --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenShuffle.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenShuffle + { + public static string Shuffle(CodeGenContext context, AstOperation operation) + { + string value = GetSoureExpr(context, operation.GetSource(0), AggregateType.FP32); + string index = GetSoureExpr(context, operation.GetSource(1), AggregateType.U32); + + if (context.HostCapabilities.SupportsShaderBallot) + { + return $"readInvocationARB({value}, {index})"; + } + else + { + return $"subgroupShuffle({value}, {index})"; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs index 98c1b9d28..719ccf0cf 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs @@ -231,7 +231,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv var execution = context.Constant(context.TypeU32(), Scope.Subgroup); var maskVector = context.GroupNonUniformBallot(uvec4Type, execution, context.Get(AggregateType.Bool, source)); - var mask = context.CompositeExtract(context.TypeU32(), maskVector, (SpvLiteralInteger)0); + var mask = context.CompositeExtract(context.TypeU32(), maskVector, (SpvLiteralInteger)operation.Index); return new OperationResult(AggregateType.U32, mask); } @@ -1100,117 +1100,40 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv private static OperationResult GenerateShuffle(CodeGenContext context, AstOperation operation) { - var x = context.GetFP32(operation.GetSource(0)); + var value = context.GetFP32(operation.GetSource(0)); var index = context.GetU32(operation.GetSource(1)); - var mask = context.GetU32(operation.GetSource(2)); - var const31 = context.Constant(context.TypeU32(), 31); - var const8 = context.Constant(context.TypeU32(), 8); - - var clamp = context.BitwiseAnd(context.TypeU32(), mask, const31); - var segMask = context.BitwiseAnd(context.TypeU32(), context.ShiftRightLogical(context.TypeU32(), mask, const8), const31); - var notSegMask = context.Not(context.TypeU32(), segMask); - var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask); - var indexNotSegMask = context.BitwiseAnd(context.TypeU32(), index, notSegMask); - - var threadId = GetScalarInput(context, IoVariable.SubgroupLaneId); - - var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask); - var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask); - var srcThreadId = context.BitwiseOr(context.TypeU32(), indexNotSegMask, minThreadId); - var valid = context.ULessThanEqual(context.TypeBool(), srcThreadId, maxThreadId); - var value = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), x, srcThreadId); - var result = context.Select(context.TypeFP32(), valid, value, x); - - var validLocal = (AstOperand)operation.GetSource(3); - - context.Store(context.GetLocalPointer(validLocal), context.BitcastIfNeeded(validLocal.VarType, AggregateType.Bool, valid)); + var result = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), value, index); return new OperationResult(AggregateType.FP32, result); } private static OperationResult GenerateShuffleDown(CodeGenContext context, AstOperation operation) { - var x = context.GetFP32(operation.GetSource(0)); + var value = context.GetFP32(operation.GetSource(0)); var index = context.GetU32(operation.GetSource(1)); - var mask = context.GetU32(operation.GetSource(2)); - var const31 = context.Constant(context.TypeU32(), 31); - var const8 = context.Constant(context.TypeU32(), 8); - - var clamp = context.BitwiseAnd(context.TypeU32(), mask, const31); - var segMask = context.BitwiseAnd(context.TypeU32(), context.ShiftRightLogical(context.TypeU32(), mask, const8), const31); - var notSegMask = context.Not(context.TypeU32(), segMask); - var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask); - - var threadId = GetScalarInput(context, IoVariable.SubgroupLaneId); - - var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask); - var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask); - var srcThreadId = context.IAdd(context.TypeU32(), threadId, index); - var valid = context.ULessThanEqual(context.TypeBool(), srcThreadId, maxThreadId); - var value = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), x, srcThreadId); - var result = context.Select(context.TypeFP32(), valid, value, x); - - var validLocal = (AstOperand)operation.GetSource(3); - - context.Store(context.GetLocalPointer(validLocal), context.BitcastIfNeeded(validLocal.VarType, AggregateType.Bool, valid)); + var result = context.GroupNonUniformShuffleDown(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), value, index); return new OperationResult(AggregateType.FP32, result); } private static OperationResult GenerateShuffleUp(CodeGenContext context, AstOperation operation) { - var x = context.GetFP32(operation.GetSource(0)); + var value = context.GetFP32(operation.GetSource(0)); var index = context.GetU32(operation.GetSource(1)); - var mask = context.GetU32(operation.GetSource(2)); - var const31 = context.Constant(context.TypeU32(), 31); - var const8 = context.Constant(context.TypeU32(), 8); - - var segMask = context.BitwiseAnd(context.TypeU32(), context.ShiftRightLogical(context.TypeU32(), mask, const8), const31); - - var threadId = GetScalarInput(context, IoVariable.SubgroupLaneId); - - var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask); - var srcThreadId = context.ISub(context.TypeU32(), threadId, index); - var valid = context.SGreaterThanEqual(context.TypeBool(), srcThreadId, minThreadId); - var value = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), x, srcThreadId); - var result = context.Select(context.TypeFP32(), valid, value, x); - - var validLocal = (AstOperand)operation.GetSource(3); - - context.Store(context.GetLocalPointer(validLocal), context.BitcastIfNeeded(validLocal.VarType, AggregateType.Bool, valid)); + var result = context.GroupNonUniformShuffleUp(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), value, index); return new OperationResult(AggregateType.FP32, result); } private static OperationResult GenerateShuffleXor(CodeGenContext context, AstOperation operation) { - var x = context.GetFP32(operation.GetSource(0)); + var value = context.GetFP32(operation.GetSource(0)); var index = context.GetU32(operation.GetSource(1)); - var mask = context.GetU32(operation.GetSource(2)); - var const31 = context.Constant(context.TypeU32(), 31); - var const8 = context.Constant(context.TypeU32(), 8); - - var clamp = context.BitwiseAnd(context.TypeU32(), mask, const31); - var segMask = context.BitwiseAnd(context.TypeU32(), context.ShiftRightLogical(context.TypeU32(), mask, const8), const31); - var notSegMask = context.Not(context.TypeU32(), segMask); - var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask); - - var threadId = GetScalarInput(context, IoVariable.SubgroupLaneId); - - var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask); - var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask); - var srcThreadId = context.BitwiseXor(context.TypeU32(), threadId, index); - var valid = context.ULessThanEqual(context.TypeBool(), srcThreadId, maxThreadId); - var value = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), x, srcThreadId); - var result = context.Select(context.TypeFP32(), valid, value, x); - - var validLocal = (AstOperand)operation.GetSource(3); - - context.Store(context.GetLocalPointer(validLocal), context.BitcastIfNeeded(validLocal.VarType, AggregateType.Bool, valid)); + var result = context.GroupNonUniformShuffleXor(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), value, index); return new OperationResult(AggregateType.FP32, result); } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs index 5eee888e4..70f1dd3c4 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs @@ -28,12 +28,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv _poolLock = new object(); } - private const HelperFunctionsMask NeedsInvocationIdMask = - HelperFunctionsMask.Shuffle | - HelperFunctionsMask.ShuffleDown | - HelperFunctionsMask.ShuffleUp | - HelperFunctionsMask.ShuffleXor | - HelperFunctionsMask.SwizzleAdd; + private const HelperFunctionsMask NeedsInvocationIdMask = HelperFunctionsMask.SwizzleAdd; public static byte[] Generate(StructuredProgramInfo info, CodeGenParameters parameters) { diff --git a/src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs b/src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs index d18a9baf8..4266dedca 100644 --- a/src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs +++ b/src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs @@ -307,6 +307,9 @@ namespace Ryujinx.Graphics.Shader.Decoders case InstName.Sts: context.SetUsedFeature(FeatureFlags.SharedMemory); break; + case InstName.Shfl: + context.SetUsedFeature(FeatureFlags.Shuffle); + break; } block.OpCodes.Add(op); diff --git a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs index ee31f02d1..ba10f2720 100644 --- a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs @@ -194,6 +194,15 @@ namespace Ryujinx.Graphics.Shader return 16; } + /// + /// Queries host shader subgroup size. + /// + /// Host shader subgroup size in invocations + int QueryHostSubgroupSize() + { + return 32; + } + /// /// Queries host support for texture formats with BGRA component order (such as BGRA8). /// diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs index 9d1c7d087..944039d65 100644 --- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs @@ -76,7 +76,7 @@ namespace Ryujinx.Graphics.Shader.Instructions switch (op.SReg) { case SReg.LaneId: - src = context.Load(StorageKind.Input, IoVariable.SubgroupLaneId); + src = EmitLoadSubgroupLaneId(context); break; case SReg.InvocationId: @@ -146,19 +146,19 @@ namespace Ryujinx.Graphics.Shader.Instructions break; case SReg.EqMask: - src = context.Load(StorageKind.Input, IoVariable.SubgroupEqMask, null, Const(0)); + src = EmitLoadSubgroupMask(context, IoVariable.SubgroupEqMask); break; case SReg.LtMask: - src = context.Load(StorageKind.Input, IoVariable.SubgroupLtMask, null, Const(0)); + src = EmitLoadSubgroupMask(context, IoVariable.SubgroupLtMask); break; case SReg.LeMask: - src = context.Load(StorageKind.Input, IoVariable.SubgroupLeMask, null, Const(0)); + src = EmitLoadSubgroupMask(context, IoVariable.SubgroupLeMask); break; case SReg.GtMask: - src = context.Load(StorageKind.Input, IoVariable.SubgroupGtMask, null, Const(0)); + src = EmitLoadSubgroupMask(context, IoVariable.SubgroupGtMask); break; case SReg.GeMask: - src = context.Load(StorageKind.Input, IoVariable.SubgroupGeMask, null, Const(0)); + src = EmitLoadSubgroupMask(context, IoVariable.SubgroupGeMask); break; default: @@ -169,6 +169,52 @@ namespace Ryujinx.Graphics.Shader.Instructions context.Copy(GetDest(op.Dest), src); } + private static Operand EmitLoadSubgroupLaneId(EmitterContext context) + { + if (context.TranslatorContext.GpuAccessor.QueryHostSubgroupSize() <= 32) + { + return context.Load(StorageKind.Input, IoVariable.SubgroupLaneId); + } + + return context.BitwiseAnd(context.Load(StorageKind.Input, IoVariable.SubgroupLaneId), Const(0x1f)); + } + + private static Operand EmitLoadSubgroupMask(EmitterContext context, IoVariable ioVariable) + { + int subgroupSize = context.TranslatorContext.GpuAccessor.QueryHostSubgroupSize(); + + if (subgroupSize <= 32) + { + return context.Load(StorageKind.Input, ioVariable, null, Const(0)); + } + else if (subgroupSize == 64) + { + Operand laneId = context.Load(StorageKind.Input, IoVariable.SubgroupLaneId); + Operand low = context.Load(StorageKind.Input, ioVariable, null, Const(0)); + Operand high = context.Load(StorageKind.Input, ioVariable, null, Const(1)); + + return context.ConditionalSelect(context.BitwiseAnd(laneId, Const(32)), high, low); + } + else + { + Operand laneId = context.Load(StorageKind.Input, IoVariable.SubgroupLaneId); + Operand element = context.ShiftRightU32(laneId, Const(5)); + + Operand res = context.Load(StorageKind.Input, ioVariable, null, Const(0)); + res = context.ConditionalSelect( + context.ICompareEqual(element, Const(1)), + context.Load(StorageKind.Input, ioVariable, null, Const(1)), res); + res = context.ConditionalSelect( + context.ICompareEqual(element, Const(2)), + context.Load(StorageKind.Input, ioVariable, null, Const(2)), res); + res = context.ConditionalSelect( + context.ICompareEqual(element, Const(3)), + context.Load(StorageKind.Input, ioVariable, null, Const(3)), res); + + return res; + } + } + public static void SelR(EmitterContext context) { InstSelR op = context.GetOp(); diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitWarp.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitWarp.cs index a84944e43..73eea5c34 100644 --- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitWarp.cs +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitWarp.cs @@ -50,20 +50,7 @@ namespace Ryujinx.Graphics.Shader.Instructions InstVote op = context.GetOp(); Operand pred = GetPredicate(context, op.SrcPred, op.SrcPredInv); - Operand res = null; - - switch (op.VoteMode) - { - case VoteMode.All: - res = context.VoteAll(pred); - break; - case VoteMode.Any: - res = context.VoteAny(pred); - break; - case VoteMode.Eq: - res = context.VoteAllEqual(pred); - break; - } + Operand res = EmitVote(context, op.VoteMode, pred); if (res != null) { @@ -76,7 +63,81 @@ namespace Ryujinx.Graphics.Shader.Instructions if (op.Dest != RegisterConsts.RegisterZeroIndex) { - context.Copy(GetDest(op.Dest), context.Ballot(pred)); + context.Copy(GetDest(op.Dest), EmitBallot(context, pred)); + } + } + + private static Operand EmitVote(EmitterContext context, VoteMode voteMode, Operand pred) + { + int subgroupSize = context.TranslatorContext.GpuAccessor.QueryHostSubgroupSize(); + + if (subgroupSize <= 32) + { + return voteMode switch + { + VoteMode.All => context.VoteAll(pred), + VoteMode.Any => context.VoteAny(pred), + VoteMode.Eq => context.VoteAllEqual(pred), + _ => null, + }; + } + + // Emulate vote with ballot masks. + // We do that when the GPU thread count is not 32, + // since the shader code assumes it is 32. + // allInvocations => ballot(pred) == ballot(true), + // anyInvocation => ballot(pred) != 0, + // allInvocationsEqual => ballot(pred) == balot(true) || ballot(pred) == 0 + Operand ballotMask = EmitBallot(context, pred); + + Operand AllTrue() => context.ICompareEqual(ballotMask, EmitBallot(context, Const(IrConsts.True))); + + return voteMode switch + { + VoteMode.All => AllTrue(), + VoteMode.Any => context.ICompareNotEqual(ballotMask, Const(0)), + VoteMode.Eq => context.BitwiseOr(AllTrue(), context.ICompareEqual(ballotMask, Const(0))), + _ => null, + }; + } + + private static Operand EmitBallot(EmitterContext context, Operand pred) + { + int subgroupSize = context.TranslatorContext.GpuAccessor.QueryHostSubgroupSize(); + + if (subgroupSize <= 32) + { + return context.Ballot(pred, 0); + } + else if (subgroupSize == 64) + { + // TODO: Add support for vector destination and do that with a single operation. + + Operand laneId = context.Load(StorageKind.Input, IoVariable.SubgroupLaneId); + Operand low = context.Ballot(pred, 0); + Operand high = context.Ballot(pred, 1); + + return context.ConditionalSelect(context.BitwiseAnd(laneId, Const(32)), high, low); + } + else + { + // TODO: Add support for vector destination and do that with a single operation. + + Operand laneId = context.Load(StorageKind.Input, IoVariable.SubgroupLaneId); + Operand element = context.ShiftRightU32(laneId, Const(5)); + + Operand res = context.Ballot(pred, 0); + res = context.ConditionalSelect( + context.ICompareEqual(element, Const(1)), + context.Ballot(pred, 1), res); + res = context.ConditionalSelect( + context.ICompareEqual(element, Const(2)), + context.Ballot(pred, 2), res); + res = context.ConditionalSelect( + context.ICompareEqual(element, Const(3)), + context.Ballot(pred, 3), res); + + return res; } } } diff --git a/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj b/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj index b1f1fb963..ea9a7821b 100644 --- a/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj +++ b/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj @@ -12,10 +12,6 @@ - - - - diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs index 73ce90827..2a3d65e75 100644 --- a/src/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs @@ -7,10 +7,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr { MultiplyHighS32 = 1 << 2, MultiplyHighU32 = 1 << 3, - Shuffle = 1 << 4, - ShuffleDown = 1 << 5, - ShuffleUp = 1 << 6, - ShuffleXor = 1 << 7, SwizzleAdd = 1 << 10, FSI = 1 << 11, } diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs index 6cd0fd086..1169512e9 100644 --- a/src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs @@ -109,14 +109,15 @@ namespace Ryujinx.Graphics.Shader.StructuredIr Add(Instruction.PackDouble2x32, AggregateType.FP64, AggregateType.U32, AggregateType.U32); Add(Instruction.PackHalf2x16, AggregateType.U32, AggregateType.FP32, AggregateType.FP32); Add(Instruction.ReciprocalSquareRoot, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.Return, AggregateType.Void, AggregateType.U32); Add(Instruction.Round, AggregateType.Scalar, AggregateType.Scalar); Add(Instruction.ShiftLeft, AggregateType.S32, AggregateType.S32, AggregateType.S32); Add(Instruction.ShiftRightS32, AggregateType.S32, AggregateType.S32, AggregateType.S32); Add(Instruction.ShiftRightU32, AggregateType.U32, AggregateType.U32, AggregateType.S32); - Add(Instruction.Shuffle, AggregateType.FP32, AggregateType.FP32, AggregateType.U32, AggregateType.U32, AggregateType.Bool); - Add(Instruction.ShuffleDown, AggregateType.FP32, AggregateType.FP32, AggregateType.U32, AggregateType.U32, AggregateType.Bool); - Add(Instruction.ShuffleUp, AggregateType.FP32, AggregateType.FP32, AggregateType.U32, AggregateType.U32, AggregateType.Bool); - Add(Instruction.ShuffleXor, AggregateType.FP32, AggregateType.FP32, AggregateType.U32, AggregateType.U32, AggregateType.Bool); + Add(Instruction.Shuffle, AggregateType.FP32, AggregateType.FP32, AggregateType.U32); + Add(Instruction.ShuffleDown, AggregateType.FP32, AggregateType.FP32, AggregateType.U32); + Add(Instruction.ShuffleUp, AggregateType.FP32, AggregateType.FP32, AggregateType.U32); + Add(Instruction.ShuffleXor, AggregateType.FP32, AggregateType.FP32, AggregateType.U32); Add(Instruction.Sine, AggregateType.Scalar, AggregateType.Scalar); Add(Instruction.SquareRoot, AggregateType.Scalar, AggregateType.Scalar); Add(Instruction.Store, AggregateType.Void); @@ -131,7 +132,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr Add(Instruction.VoteAll, AggregateType.Bool, AggregateType.Bool); Add(Instruction.VoteAllEqual, AggregateType.Bool, AggregateType.Bool); Add(Instruction.VoteAny, AggregateType.Bool, AggregateType.Bool); -#pragma warning restore IDE0055v +#pragma warning restore IDE0055 } private static void Add(Instruction inst, AggregateType destType, params AggregateType[] srcTypes) diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs index 862fef126..b0db0ffb0 100644 --- a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs @@ -282,18 +282,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr case Instruction.MultiplyHighU32: context.Info.HelperFunctionsMask |= HelperFunctionsMask.MultiplyHighU32; break; - case Instruction.Shuffle: - context.Info.HelperFunctionsMask |= HelperFunctionsMask.Shuffle; - break; - case Instruction.ShuffleDown: - context.Info.HelperFunctionsMask |= HelperFunctionsMask.ShuffleDown; - break; - case Instruction.ShuffleUp: - context.Info.HelperFunctionsMask |= HelperFunctionsMask.ShuffleUp; - break; - case Instruction.ShuffleXor: - context.Info.HelperFunctionsMask |= HelperFunctionsMask.ShuffleXor; - break; case Instruction.SwizzleAdd: context.Info.HelperFunctionsMask |= HelperFunctionsMask.SwizzleAdd; break; diff --git a/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs b/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs index 6cb572381..a08c8ea9d 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs @@ -112,9 +112,13 @@ namespace Ryujinx.Graphics.Shader.Translation return context.Add(Instruction.AtomicXor, storageKind, Local(), Const(binding), e0, e1, value); } - public static Operand Ballot(this EmitterContext context, Operand a) + public static Operand Ballot(this EmitterContext context, Operand a, int index) { - return context.Add(Instruction.Ballot, Local(), a); + Operand dest = Local(); + + context.Add(new Operation(Instruction.Ballot, index, dest, a)); + + return dest; } public static Operand Barrier(this EmitterContext context) @@ -782,21 +786,41 @@ namespace Ryujinx.Graphics.Shader.Translation return context.Add(Instruction.ShiftRightU32, Local(), a, b); } + public static Operand Shuffle(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Shuffle, Local(), a, b); + } + public static (Operand, Operand) Shuffle(this EmitterContext context, Operand a, Operand b, Operand c) { return context.Add(Instruction.Shuffle, (Local(), Local()), a, b, c); } + public static Operand ShuffleDown(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.ShuffleDown, Local(), a, b); + } + public static (Operand, Operand) ShuffleDown(this EmitterContext context, Operand a, Operand b, Operand c) { return context.Add(Instruction.ShuffleDown, (Local(), Local()), a, b, c); } + public static Operand ShuffleUp(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.ShuffleUp, Local(), a, b); + } + public static (Operand, Operand) ShuffleUp(this EmitterContext context, Operand a, Operand b, Operand c) { return context.Add(Instruction.ShuffleUp, (Local(), Local()), a, b, c); } + public static Operand ShuffleXor(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.ShuffleXor, Local(), a, b); + } + public static (Operand, Operand) ShuffleXor(this EmitterContext context, Operand a, Operand b, Operand c) { return context.Add(Instruction.ShuffleXor, (Local(), Local()), a, b, c); diff --git a/src/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs b/src/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs index 5b7226acd..552a3f310 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs @@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader.Translation InstanceId = 1 << 3, DrawParameters = 1 << 4, RtLayer = 1 << 5, + Shuffle = 1 << 6, FixedFuncAttr = 1 << 9, LocalMemory = 1 << 10, SharedMemory = 1 << 11, diff --git a/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs index 2addff5c0..ef2f8759d 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs @@ -56,6 +56,20 @@ namespace Ryujinx.Graphics.Shader.Translation return functionId; } + public int GetOrCreateShuffleFunctionId(HelperFunctionName functionName, int subgroupSize) + { + if (_functionIds.TryGetValue((int)functionName, out int functionId)) + { + return functionId; + } + + Function function = GenerateShuffleFunction(functionName, subgroupSize); + functionId = AddFunction(function); + _functionIds.Add((int)functionName, functionId); + + return functionId; + } + private Function GenerateFunction(HelperFunctionName functionName) { return functionName switch @@ -216,6 +230,137 @@ namespace Ryujinx.Graphics.Shader.Translation return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, $"SharedStore{bitSize}_{id}", false, 2, 0); } + private static Function GenerateShuffleFunction(HelperFunctionName functionName, int subgroupSize) + { + return functionName switch + { + HelperFunctionName.Shuffle => GenerateShuffle(subgroupSize), + HelperFunctionName.ShuffleDown => GenerateShuffleDown(subgroupSize), + HelperFunctionName.ShuffleUp => GenerateShuffleUp(subgroupSize), + HelperFunctionName.ShuffleXor => GenerateShuffleXor(subgroupSize), + _ => throw new ArgumentException($"Invalid function name {functionName}"), + }; + } + + private static Function GenerateShuffle(int subgroupSize) + { + EmitterContext context = new(); + + Operand value = Argument(0); + Operand index = Argument(1); + Operand mask = Argument(2); + + Operand clamp = context.BitwiseAnd(mask, Const(0x1f)); + Operand segMask = context.BitwiseAnd(context.ShiftRightU32(mask, Const(8)), Const(0x1f)); + Operand minThreadId = context.BitwiseAnd(GenerateLoadSubgroupLaneId(context, subgroupSize), segMask); + Operand maxThreadId = context.BitwiseOr(context.BitwiseAnd(clamp, context.BitwiseNot(segMask)), minThreadId); + Operand srcThreadId = context.BitwiseOr(context.BitwiseAnd(index, context.BitwiseNot(segMask)), minThreadId); + Operand valid = context.ICompareLessOrEqualUnsigned(srcThreadId, maxThreadId); + + context.Copy(Argument(3), valid); + + Operand result = context.Shuffle(value, GenerateSubgroupShuffleIndex(context, srcThreadId, subgroupSize)); + + context.Return(context.ConditionalSelect(valid, result, value)); + + return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "Shuffle", true, 3, 1); + } + + private static Function GenerateShuffleDown(int subgroupSize) + { + EmitterContext context = new(); + + Operand value = Argument(0); + Operand index = Argument(1); + Operand mask = Argument(2); + + Operand clamp = context.BitwiseAnd(mask, Const(0x1f)); + Operand segMask = context.BitwiseAnd(context.ShiftRightU32(mask, Const(8)), Const(0x1f)); + Operand laneId = GenerateLoadSubgroupLaneId(context, subgroupSize); + Operand minThreadId = context.BitwiseAnd(laneId, segMask); + Operand maxThreadId = context.BitwiseOr(context.BitwiseAnd(clamp, context.BitwiseNot(segMask)), minThreadId); + Operand srcThreadId = context.IAdd(laneId, index); + Operand valid = context.ICompareLessOrEqualUnsigned(srcThreadId, maxThreadId); + + context.Copy(Argument(3), valid); + + Operand result = context.Shuffle(value, GenerateSubgroupShuffleIndex(context, srcThreadId, subgroupSize)); + + context.Return(context.ConditionalSelect(valid, result, value)); + + return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "ShuffleDown", true, 3, 1); + } + + private static Function GenerateShuffleUp(int subgroupSize) + { + EmitterContext context = new(); + + Operand value = Argument(0); + Operand index = Argument(1); + Operand mask = Argument(2); + + Operand segMask = context.BitwiseAnd(context.ShiftRightU32(mask, Const(8)), Const(0x1f)); + Operand laneId = GenerateLoadSubgroupLaneId(context, subgroupSize); + Operand minThreadId = context.BitwiseAnd(laneId, segMask); + Operand srcThreadId = context.ISubtract(laneId, index); + Operand valid = context.ICompareGreaterOrEqual(srcThreadId, minThreadId); + + context.Copy(Argument(3), valid); + + Operand result = context.Shuffle(value, GenerateSubgroupShuffleIndex(context, srcThreadId, subgroupSize)); + + context.Return(context.ConditionalSelect(valid, result, value)); + + return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "ShuffleUp", true, 3, 1); + } + + private static Function GenerateShuffleXor(int subgroupSize) + { + EmitterContext context = new(); + + Operand value = Argument(0); + Operand index = Argument(1); + Operand mask = Argument(2); + + Operand clamp = context.BitwiseAnd(mask, Const(0x1f)); + Operand segMask = context.BitwiseAnd(context.ShiftRightU32(mask, Const(8)), Const(0x1f)); + Operand laneId = GenerateLoadSubgroupLaneId(context, subgroupSize); + Operand minThreadId = context.BitwiseAnd(laneId, segMask); + Operand maxThreadId = context.BitwiseOr(context.BitwiseAnd(clamp, context.BitwiseNot(segMask)), minThreadId); + Operand srcThreadId = context.BitwiseExclusiveOr(laneId, index); + Operand valid = context.ICompareLessOrEqualUnsigned(srcThreadId, maxThreadId); + + context.Copy(Argument(3), valid); + + Operand result = context.Shuffle(value, GenerateSubgroupShuffleIndex(context, srcThreadId, subgroupSize)); + + context.Return(context.ConditionalSelect(valid, result, value)); + + return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "ShuffleXor", true, 3, 1); + } + + private static Operand GenerateLoadSubgroupLaneId(EmitterContext context, int subgroupSize) + { + if (subgroupSize <= 32) + { + return context.Load(StorageKind.Input, IoVariable.SubgroupLaneId); + } + + return context.BitwiseAnd(context.Load(StorageKind.Input, IoVariable.SubgroupLaneId), Const(0x1f)); + } + + private static Operand GenerateSubgroupShuffleIndex(EmitterContext context, Operand srcThreadId, int subgroupSize) + { + if (subgroupSize <= 32) + { + return srcThreadId; + } + + return context.BitwiseOr( + context.BitwiseAnd(context.Load(StorageKind.Input, IoVariable.SubgroupLaneId), Const(0x60)), + srcThreadId); + } + private Function GenerateTexelFetchScaleFunction() { EmitterContext context = new(); diff --git a/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionName.cs b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionName.cs index e5af17355..09b17729d 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionName.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionName.cs @@ -2,12 +2,18 @@ namespace Ryujinx.Graphics.Shader.Translation { enum HelperFunctionName { + Invalid, + ConvertDoubleToFloat, ConvertFloatToDouble, SharedAtomicMaxS32, SharedAtomicMinS32, SharedStore8, SharedStore16, + Shuffle, + ShuffleDown, + ShuffleUp, + ShuffleXor, TexelFetchScale, TextureSizeUnscale, } diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/ShufflePass.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/ShufflePass.cs new file mode 100644 index 000000000..839d4f818 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/ShufflePass.cs @@ -0,0 +1,52 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation.Optimizations; +using System.Collections.Generic; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Transforms +{ + class ShufflePass : ITransformPass + { + public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures) + { + return usedFeatures.HasFlag(FeatureFlags.Shuffle); + } + + public static LinkedListNode RunPass(TransformContext context, LinkedListNode node) + { + Operation operation = (Operation)node.Value; + + HelperFunctionName functionName = operation.Inst switch + { + Instruction.Shuffle => HelperFunctionName.Shuffle, + Instruction.ShuffleDown => HelperFunctionName.ShuffleDown, + Instruction.ShuffleUp => HelperFunctionName.ShuffleUp, + Instruction.ShuffleXor => HelperFunctionName.ShuffleXor, + _ => HelperFunctionName.Invalid, + }; + + if (functionName == HelperFunctionName.Invalid || operation.SourcesCount != 3 || operation.DestsCount != 2) + { + return node; + } + + int functionId = context.Hfm.GetOrCreateShuffleFunctionId(functionName, context.GpuAccessor.QueryHostSubgroupSize()); + + Operand result = operation.GetDest(0); + Operand valid = operation.GetDest(1); + Operand value = operation.GetSource(0); + Operand index = operation.GetSource(1); + Operand mask = operation.GetSource(2); + + operation.Dest = null; + + Operand[] callArgs = new Operand[] { Const(functionId), value, index, mask, valid }; + + LinkedListNode newNode = node.List.AddBefore(node, new Operation(Instruction.Call, 0, result, callArgs)); + + Utils.DeleteNode(node, operation); + + return newNode; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TransformPasses.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TransformPasses.cs index c3bbe7ddf..293938807 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TransformPasses.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TransformPasses.cs @@ -13,6 +13,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms RunPass(context); RunPass(context); RunPass(context); + RunPass(context); } private static void RunPass(TransformContext context) where T : ITransformPass diff --git a/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs b/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs index e76a332f4..798de5c90 100644 --- a/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs +++ b/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs @@ -25,7 +25,6 @@ namespace Ryujinx.Graphics.Vulkan public readonly bool SupportsIndirectParameters; public readonly bool SupportsFragmentShaderInterlock; public readonly bool SupportsGeometryShaderPassthrough; - public readonly bool SupportsSubgroupSizeControl; public readonly bool SupportsShaderFloat64; public readonly bool SupportsShaderInt8; public readonly bool SupportsShaderStencilExport; @@ -45,9 +44,7 @@ namespace Ryujinx.Graphics.Vulkan public readonly bool SupportsViewportArray2; public readonly bool SupportsHostImportedMemory; public readonly bool SupportsDepthClipControl; - public readonly uint MinSubgroupSize; - public readonly uint MaxSubgroupSize; - public readonly ShaderStageFlags RequiredSubgroupSizeStages; + public readonly uint SubgroupSize; public readonly SampleCountFlags SupportedSampleCounts; public readonly PortabilitySubsetFlags PortabilitySubset; public readonly uint VertexBufferAlignment; @@ -64,7 +61,6 @@ namespace Ryujinx.Graphics.Vulkan bool supportsIndirectParameters, bool supportsFragmentShaderInterlock, bool supportsGeometryShaderPassthrough, - bool supportsSubgroupSizeControl, bool supportsShaderFloat64, bool supportsShaderInt8, bool supportsShaderStencilExport, @@ -84,9 +80,7 @@ namespace Ryujinx.Graphics.Vulkan bool supportsViewportArray2, bool supportsHostImportedMemory, bool supportsDepthClipControl, - uint minSubgroupSize, - uint maxSubgroupSize, - ShaderStageFlags requiredSubgroupSizeStages, + uint subgroupSize, SampleCountFlags supportedSampleCounts, PortabilitySubsetFlags portabilitySubset, uint vertexBufferAlignment, @@ -102,7 +96,6 @@ namespace Ryujinx.Graphics.Vulkan SupportsIndirectParameters = supportsIndirectParameters; SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock; SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough; - SupportsSubgroupSizeControl = supportsSubgroupSizeControl; SupportsShaderFloat64 = supportsShaderFloat64; SupportsShaderInt8 = supportsShaderInt8; SupportsShaderStencilExport = supportsShaderStencilExport; @@ -122,9 +115,7 @@ namespace Ryujinx.Graphics.Vulkan SupportsViewportArray2 = supportsViewportArray2; SupportsHostImportedMemory = supportsHostImportedMemory; SupportsDepthClipControl = supportsDepthClipControl; - MinSubgroupSize = minSubgroupSize; - MaxSubgroupSize = maxSubgroupSize; - RequiredSubgroupSizeStages = requiredSubgroupSizeStages; + SubgroupSize = subgroupSize; SupportedSampleCounts = supportedSampleCounts; PortabilitySubset = portabilitySubset; VertexBufferAlignment = vertexBufferAlignment; diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs index cc9af5b6d..5a30cff8e 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs @@ -352,11 +352,6 @@ namespace Ryujinx.Graphics.Vulkan return pipeline; } - if (gd.Capabilities.SupportsSubgroupSizeControl) - { - UpdateStageRequiredSubgroupSizes(gd, 1); - } - var pipelineCreateInfo = new ComputePipelineCreateInfo { SType = StructureType.ComputePipelineCreateInfo, @@ -616,11 +611,6 @@ namespace Ryujinx.Graphics.Vulkan PDynamicStates = dynamicStates, }; - if (gd.Capabilities.SupportsSubgroupSizeControl) - { - UpdateStageRequiredSubgroupSizes(gd, (int)StagesCount); - } - var pipelineCreateInfo = new GraphicsPipelineCreateInfo { SType = StructureType.GraphicsPipelineCreateInfo, @@ -659,19 +649,6 @@ namespace Ryujinx.Graphics.Vulkan return pipeline; } - private readonly unsafe void UpdateStageRequiredSubgroupSizes(VulkanRenderer gd, int count) - { - for (int index = 0; index < count; index++) - { - bool canUseExplicitSubgroupSize = - (gd.Capabilities.RequiredSubgroupSizeStages & Stages[index].Stage) != 0 && - gd.Capabilities.MinSubgroupSize <= RequiredSubgroupSize && - gd.Capabilities.MaxSubgroupSize >= RequiredSubgroupSize; - - Stages[index].PNext = canUseExplicitSubgroupSize ? StageRequiredSubgroupSizes.Pointer + index : null; - } - } - private void UpdateVertexAttributeDescriptions(VulkanRenderer gd) { // Vertex attributes exceeding the stride are invalid. diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs index 6f73397b8..973c6d396 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs @@ -37,7 +37,6 @@ namespace Ryujinx.Graphics.Vulkan "VK_EXT_shader_stencil_export", "VK_KHR_shader_float16_int8", "VK_EXT_shader_subgroup_ballot", - "VK_EXT_subgroup_size_control", "VK_NV_geometry_shader_passthrough", "VK_NV_viewport_array2", "VK_EXT_depth_clip_control", diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 7848bc877..675512293 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -151,6 +151,14 @@ namespace Ryujinx.Graphics.Vulkan SType = StructureType.PhysicalDeviceProperties2, }; + PhysicalDeviceSubgroupProperties propertiesSubgroup = new() + { + SType = StructureType.PhysicalDeviceSubgroupProperties, + PNext = properties2.PNext, + }; + + properties2.PNext = &propertiesSubgroup; + PhysicalDeviceBlendOperationAdvancedPropertiesEXT propertiesBlendOperationAdvanced = new() { SType = StructureType.PhysicalDeviceBlendOperationAdvancedPropertiesExt, @@ -164,18 +172,6 @@ namespace Ryujinx.Graphics.Vulkan properties2.PNext = &propertiesBlendOperationAdvanced; } - PhysicalDeviceSubgroupSizeControlPropertiesEXT propertiesSubgroupSizeControl = new() - { - SType = StructureType.PhysicalDeviceSubgroupSizeControlPropertiesExt, - }; - - bool supportsSubgroupSizeControl = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_subgroup_size_control"); - - if (supportsSubgroupSizeControl) - { - properties2.PNext = &propertiesSubgroupSizeControl; - } - bool supportsTransformFeedback = _physicalDevice.IsDeviceExtensionPresent(ExtTransformFeedback.ExtensionName); PhysicalDeviceTransformFeedbackPropertiesEXT propertiesTransformFeedback = new() @@ -315,7 +311,6 @@ namespace Ryujinx.Graphics.Vulkan _physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName), _physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock"), _physicalDevice.IsDeviceExtensionPresent("VK_NV_geometry_shader_passthrough"), - supportsSubgroupSizeControl, features2.Features.ShaderFloat64, featuresShaderInt8.ShaderInt8, _physicalDevice.IsDeviceExtensionPresent("VK_EXT_shader_stencil_export"), @@ -335,9 +330,7 @@ namespace Ryujinx.Graphics.Vulkan _physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"), _physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName), supportsDepthClipControl && featuresDepthClipControl.DepthClipControl, - propertiesSubgroupSizeControl.MinSubgroupSize, - propertiesSubgroupSizeControl.MaxSubgroupSize, - propertiesSubgroupSizeControl.RequiredSubgroupSizeStages, + propertiesSubgroup.SubgroupSize, supportedSampleCounts, portabilityFlags, vertexBufferAlignment, @@ -623,6 +616,7 @@ namespace Ryujinx.Graphics.Vulkan maximumImagesPerStage: Constants.MaxImagesPerStage, maximumComputeSharedMemorySize: (int)limits.MaxComputeSharedMemorySize, maximumSupportedAnisotropy: (int)limits.MaxSamplerAnisotropy, + shaderSubgroupSize: (int)Capabilities.SubgroupSize, storageBufferOffsetAlignment: (int)limits.MinStorageBufferOffsetAlignment, gatherBiasPrecision: IsIntelWindows || IsAmdWindows ? (int)Capabilities.SubTexelPrecisionBits : 0); } From b0b7843d5c7372db60211255be0edfcb97dcb56d Mon Sep 17 00:00:00 2001 From: Ac_K Date: Thu, 17 Aug 2023 14:59:05 +0200 Subject: [PATCH 02/64] mm: Migrate service in Horizon project (#5580) * mm: Migrate service in Horizon project This PR migrate the `mm:u` service to the Horizon project, things were checked by some RE aswell, that's why some vars are renamed, the logic should be the same as before. Tests are welcome. * Lock _sessionList instead * Fix comment * Fix Session fields order --- src/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs | 196 ------------------ .../Mm/Types/MultiMediaOperationType.cs | 10 - .../Services/Mm/Types/MultiMediaSession.cs | 24 --- src/Ryujinx.Horizon/MmNv/Ipc/Request.cs | 160 ++++++++++++++ src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs | 43 ++++ src/Ryujinx.Horizon/MmNv/MmNvMain.cs | 17 ++ src/Ryujinx.Horizon/Sdk/MmNv/IRequest.cs | 17 ++ src/Ryujinx.Horizon/Sdk/MmNv/Module.cs | 15 ++ src/Ryujinx.Horizon/Sdk/MmNv/Session.cs | 26 +++ src/Ryujinx.Horizon/ServiceTable.cs | 2 + 10 files changed, 280 insertions(+), 230 deletions(-) delete mode 100644 src/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Mm/Types/MultiMediaOperationType.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Mm/Types/MultiMediaSession.cs create mode 100644 src/Ryujinx.Horizon/MmNv/Ipc/Request.cs create mode 100644 src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs create mode 100644 src/Ryujinx.Horizon/MmNv/MmNvMain.cs create mode 100644 src/Ryujinx.Horizon/Sdk/MmNv/IRequest.cs create mode 100644 src/Ryujinx.Horizon/Sdk/MmNv/Module.cs create mode 100644 src/Ryujinx.Horizon/Sdk/MmNv/Session.cs diff --git a/src/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs b/src/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs deleted file mode 100644 index 31d325a94..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs +++ /dev/null @@ -1,196 +0,0 @@ -using Ryujinx.Common.Logging; -using Ryujinx.HLE.HOS.Services.Mm.Types; -using System.Collections.Generic; - -namespace Ryujinx.HLE.HOS.Services.Mm -{ - [Service("mm:u")] - class IRequest : IpcService - { - private readonly object _sessionListLock = new(); - private readonly List _sessionList = new(); - - private uint _uniqueId = 1; - - public IRequest(ServiceCtx context) { } - - [CommandCmif(0)] - // InitializeOld(u32, u32, u32) - public ResultCode InitializeOld(ServiceCtx context) - { - MultiMediaOperationType operationType = (MultiMediaOperationType)context.RequestData.ReadUInt32(); - int fgmId = context.RequestData.ReadInt32(); - bool isAutoClearEvent = context.RequestData.ReadInt32() != 0; - - Logger.Stub?.PrintStub(LogClass.ServiceMm, new { operationType, fgmId, isAutoClearEvent }); - - Register(operationType, fgmId, isAutoClearEvent); - - return ResultCode.Success; - } - - [CommandCmif(1)] - // FinalizeOld(u32) - public ResultCode FinalizeOld(ServiceCtx context) - { - MultiMediaOperationType operationType = (MultiMediaOperationType)context.RequestData.ReadUInt32(); - - Logger.Stub?.PrintStub(LogClass.ServiceMm, new { operationType }); - - lock (_sessionListLock) - { - _sessionList.Remove(GetSessionByType(operationType)); - } - - return ResultCode.Success; - } - - [CommandCmif(2)] - // SetAndWaitOld(u32, u32, u32) - public ResultCode SetAndWaitOld(ServiceCtx context) - { - MultiMediaOperationType operationType = (MultiMediaOperationType)context.RequestData.ReadUInt32(); - uint frequenceHz = context.RequestData.ReadUInt32(); - int timeout = context.RequestData.ReadInt32(); - - Logger.Stub?.PrintStub(LogClass.ServiceMm, new { operationType, frequenceHz, timeout }); - - lock (_sessionListLock) - { - GetSessionByType(operationType)?.SetAndWait(frequenceHz, timeout); - } - - return ResultCode.Success; - } - - [CommandCmif(3)] - // GetOld(u32) -> u32 - public ResultCode GetOld(ServiceCtx context) - { - MultiMediaOperationType operationType = (MultiMediaOperationType)context.RequestData.ReadUInt32(); - - Logger.Stub?.PrintStub(LogClass.ServiceMm, new { operationType }); - - lock (_sessionListLock) - { - MultiMediaSession session = GetSessionByType(operationType); - - uint currentValue = session == null ? 0 : session.CurrentValue; - - context.ResponseData.Write(currentValue); - } - - return ResultCode.Success; - } - - [CommandCmif(4)] - // Initialize(u32, u32, u32) -> u32 - public ResultCode Initialize(ServiceCtx context) - { - MultiMediaOperationType operationType = (MultiMediaOperationType)context.RequestData.ReadUInt32(); - int fgmId = context.RequestData.ReadInt32(); - bool isAutoClearEvent = context.RequestData.ReadInt32() != 0; - - Logger.Stub?.PrintStub(LogClass.ServiceMm, new { operationType, fgmId, isAutoClearEvent }); - - uint id = Register(operationType, fgmId, isAutoClearEvent); - - context.ResponseData.Write(id); - - return ResultCode.Success; - } - - [CommandCmif(5)] - // Finalize(u32) - public ResultCode Finalize(ServiceCtx context) - { - uint id = context.RequestData.ReadUInt32(); - - Logger.Stub?.PrintStub(LogClass.ServiceMm, new { id }); - - lock (_sessionListLock) - { - _sessionList.Remove(GetSessionById(id)); - } - - return ResultCode.Success; - } - - [CommandCmif(6)] - // SetAndWait(u32, u32, u32) - public ResultCode SetAndWait(ServiceCtx context) - { - uint id = context.RequestData.ReadUInt32(); - uint frequenceHz = context.RequestData.ReadUInt32(); - int timeout = context.RequestData.ReadInt32(); - - Logger.Stub?.PrintStub(LogClass.ServiceMm, new { id, frequenceHz, timeout }); - - lock (_sessionListLock) - { - GetSessionById(id)?.SetAndWait(frequenceHz, timeout); - } - - return ResultCode.Success; - } - - [CommandCmif(7)] - // Get(u32) -> u32 - public ResultCode Get(ServiceCtx context) - { - uint id = context.RequestData.ReadUInt32(); - - Logger.Stub?.PrintStub(LogClass.ServiceMm, new { id }); - - lock (_sessionListLock) - { - MultiMediaSession session = GetSessionById(id); - - uint currentValue = session == null ? 0 : session.CurrentValue; - - context.ResponseData.Write(currentValue); - } - - return ResultCode.Success; - } - - private MultiMediaSession GetSessionById(uint id) - { - foreach (MultiMediaSession session in _sessionList) - { - if (session.Id == id) - { - return session; - } - } - - return null; - } - - private MultiMediaSession GetSessionByType(MultiMediaOperationType type) - { - foreach (MultiMediaSession session in _sessionList) - { - if (session.Type == type) - { - return session; - } - } - - return null; - } - - private uint Register(MultiMediaOperationType type, int fgmId, bool isAutoClearEvent) - { - lock (_sessionListLock) - { - // Nintendo ignore the fgm id as the other interfaces were deprecated. - MultiMediaSession session = new(_uniqueId++, type, isAutoClearEvent); - - _sessionList.Add(session); - - return session.Id; - } - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Mm/Types/MultiMediaOperationType.cs b/src/Ryujinx.HLE/HOS/Services/Mm/Types/MultiMediaOperationType.cs deleted file mode 100644 index 095dbfc31..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Mm/Types/MultiMediaOperationType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Mm.Types -{ - enum MultiMediaOperationType : uint - { - Ram = 2, - NvEnc = 5, - NvDec = 6, - NvJpg = 7, - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Mm/Types/MultiMediaSession.cs b/src/Ryujinx.HLE/HOS/Services/Mm/Types/MultiMediaSession.cs deleted file mode 100644 index 32b52ca54..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Mm/Types/MultiMediaSession.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Mm.Types -{ - class MultiMediaSession - { - public MultiMediaOperationType Type { get; } - - public bool IsAutoClearEvent { get; } - public uint Id { get; } - public uint CurrentValue { get; private set; } - - public MultiMediaSession(uint id, MultiMediaOperationType type, bool isAutoClearEvent) - { - Type = type; - Id = id; - IsAutoClearEvent = isAutoClearEvent; - CurrentValue = 0; - } - - public void SetAndWait(uint value, int timeout) - { - CurrentValue = value; - } - } -} diff --git a/src/Ryujinx.Horizon/MmNv/Ipc/Request.cs b/src/Ryujinx.Horizon/MmNv/Ipc/Request.cs new file mode 100644 index 000000000..9a24c75e8 --- /dev/null +++ b/src/Ryujinx.Horizon/MmNv/Ipc/Request.cs @@ -0,0 +1,160 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.MmNv; +using Ryujinx.Horizon.Sdk.Sf; +using System.Collections.Generic; + +namespace Ryujinx.Horizon.MmNv.Ipc +{ + partial class Request : IRequest + { + private readonly List _sessionList = new(); + + private uint _uniqueId = 1; + + [CmifCommand(0)] + public Result InitializeOld(Module module, uint fgmPriority, uint autoClearEvent) + { + bool isAutoClearEvent = autoClearEvent != 0; + + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module, fgmPriority, isAutoClearEvent }); + + Register(module, fgmPriority, isAutoClearEvent); + + return Result.Success; + } + + [CmifCommand(1)] + public Result FinalizeOld(Module module) + { + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module }); + + lock (_sessionList) + { + _sessionList.Remove(GetSessionByModule(module)); + } + + return Result.Success; + } + + [CmifCommand(2)] + public Result SetAndWaitOld(Module module, uint clockRateMin, int clockRateMax) + { + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module, clockRateMin, clockRateMax }); + + lock (_sessionList) + { + GetSessionByModule(module)?.SetAndWait(clockRateMin, clockRateMax); + } + + return Result.Success; + } + + [CmifCommand(3)] + public Result GetOld(out uint clockRateActual, Module module) + { + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module }); + + lock (_sessionList) + { + Session session = GetSessionByModule(module); + + clockRateActual = session == null ? 0 : session.ClockRateMin; + } + + return Result.Success; + } + + [CmifCommand(4)] + public Result Initialize(out uint requestId, Module module, uint fgmPriority, uint autoClearEvent) + { + bool isAutoClearEvent = autoClearEvent != 0; + + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module, fgmPriority, isAutoClearEvent }); + + requestId = Register(module, fgmPriority, isAutoClearEvent); + + return Result.Success; + } + + [CmifCommand(5)] + public Result Finalize(uint requestId) + { + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { requestId }); + + lock (_sessionList) + { + _sessionList.Remove(GetSessionById(requestId)); + } + + return Result.Success; + } + + [CmifCommand(6)] + public Result SetAndWait(uint requestId, uint clockRateMin, int clockRateMax) + { + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { requestId, clockRateMin, clockRateMax }); + + lock (_sessionList) + { + GetSessionById(requestId)?.SetAndWait(clockRateMin, clockRateMax); + } + + return Result.Success; + } + + [CmifCommand(7)] + public Result Get(out uint clockRateActual, uint requestId) + { + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { requestId }); + + lock (_sessionList) + { + Session session = GetSessionById(requestId); + + clockRateActual = session == null ? 0 : session.ClockRateMin; + } + + return Result.Success; + } + + private Session GetSessionById(uint id) + { + foreach (Session session in _sessionList) + { + if (session.Id == id) + { + return session; + } + } + + return null; + } + + private Session GetSessionByModule(Module module) + { + foreach (Session session in _sessionList) + { + if (session.Module == module) + { + return session; + } + } + + return null; + } + + private uint Register(Module module, uint fgmPriority, bool isAutoClearEvent) + { + lock (_sessionList) + { + // Nintendo ignores the fgm priority as the other services were deprecated. + Session session = new(_uniqueId++, module, isAutoClearEvent); + + _sessionList.Add(session); + + return session.Id; + } + } + } +} diff --git a/src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs b/src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs new file mode 100644 index 000000000..e60b25581 --- /dev/null +++ b/src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs @@ -0,0 +1,43 @@ +using Ryujinx.Horizon.MmNv.Ipc; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.MmNv +{ + class MmNvIpcServer + { + private const int MmNvMaxSessionsCount = 9; + + private const int PointerBufferSize = 0; + private const int MaxDomains = 0; + private const int MaxDomainObjects = 0; + private const int MaxPortsCount = 1; + + private static readonly ManagerOptions _mmNvOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _mmNvOptions, MmNvMaxSessionsCount); + + _serverManager.RegisterObjectForServer(new Request(), ServiceName.Encode("mm:u"), MmNvMaxSessionsCount); + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/MmNv/MmNvMain.cs b/src/Ryujinx.Horizon/MmNv/MmNvMain.cs new file mode 100644 index 000000000..ac5eff1a9 --- /dev/null +++ b/src/Ryujinx.Horizon/MmNv/MmNvMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.MmNv +{ + class MmNvMain : IService + { + public static void Main(ServiceTable serviceTable) + { + MmNvIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/MmNv/IRequest.cs b/src/Ryujinx.Horizon/Sdk/MmNv/IRequest.cs new file mode 100644 index 000000000..300b957fd --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/MmNv/IRequest.cs @@ -0,0 +1,17 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.MmNv +{ + interface IRequest : IServiceObject + { + Result InitializeOld(Module module, uint fgmPriority, uint autoClearEvent); + Result FinalizeOld(Module module); + Result SetAndWaitOld(Module module, uint clockRateMin, int clockRateMax); + Result GetOld(out uint clockRateActual, Module module); + Result Initialize(out uint requestId, Module module, uint fgmPriority, uint autoClearEvent); + Result Finalize(uint requestId); + Result SetAndWait(uint requestId, uint clockRateMin, int clockRateMax); + Result Get(out uint clockRateActual, uint requestId); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/MmNv/Module.cs b/src/Ryujinx.Horizon/Sdk/MmNv/Module.cs new file mode 100644 index 000000000..e029d037b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/MmNv/Module.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Horizon.Sdk.MmNv +{ + enum Module : uint + { + Cpu, + Gpu, + Emc, + SysBus, + MSelect, + NvDec, + NvEnc, + NvJpg, + Test, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/MmNv/Session.cs b/src/Ryujinx.Horizon/Sdk/MmNv/Session.cs new file mode 100644 index 000000000..b91585693 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/MmNv/Session.cs @@ -0,0 +1,26 @@ +namespace Ryujinx.Horizon.Sdk.MmNv +{ + class Session + { + public Module Module { get; } + public uint Id { get; } + public bool IsAutoClearEvent { get; } + public uint ClockRateMin { get; private set; } + public int ClockRateMax { get; private set; } + + public Session(uint id, Module module, bool isAutoClearEvent) + { + Module = module; + Id = id; + IsAutoClearEvent = isAutoClearEvent; + ClockRateMin = 0; + ClockRateMax = -1; + } + + public void SetAndWait(uint clockRateMin, int clockRateMax) + { + ClockRateMin = clockRateMin; + ClockRateMax = clockRateMax; + } + } +} diff --git a/src/Ryujinx.Horizon/ServiceTable.cs b/src/Ryujinx.Horizon/ServiceTable.cs index d47f91bf9..8dfacbebd 100644 --- a/src/Ryujinx.Horizon/ServiceTable.cs +++ b/src/Ryujinx.Horizon/ServiceTable.cs @@ -1,5 +1,6 @@ using Ryujinx.Horizon.Bcat; using Ryujinx.Horizon.LogManager; +using Ryujinx.Horizon.MmNv; using Ryujinx.Horizon.Prepo; using System.Collections.Generic; using System.Threading; @@ -25,6 +26,7 @@ namespace Ryujinx.Horizon RegisterService(); RegisterService(); RegisterService(); + RegisterService(); _totalServices = entries.Count; From 2563f88de018eb26bfbb7474a3c8b26c4b47f49f Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Thu, 17 Aug 2023 19:26:21 +0200 Subject: [PATCH 03/64] Convert app and installation ids to int (#5587) --- .github/update_reviewers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/update_reviewers.py b/.github/update_reviewers.py index dd1b1e566..955d40e7c 100644 --- a/.github/update_reviewers.py +++ b/.github/update_reviewers.py @@ -62,9 +62,9 @@ if __name__ == "__main__": sys.stderr.write("usage: \n") sys.exit(1) - app_id = sys.argv[1] + app_id = int(sys.argv[1]) private_key = os.environ[sys.argv[2]] - installation_id = sys.argv[3] + installation_id = int(sys.argv[3]) repo_path = sys.argv[4] pr_id = int(sys.argv[5]) config_path = Path(sys.argv[6]) From c6a699414a9937946b711ceff02e95972a816d0d Mon Sep 17 00:00:00 2001 From: Mary Date: Thu, 17 Aug 2023 19:34:48 +0200 Subject: [PATCH 04/64] infra: add missing quotes around @ developers in reviewers.yml --- .github/reviewers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/reviewers.yml b/.github/reviewers.yml index f50755cca..052594f23 100644 --- a/.github/reviewers.yml +++ b/.github/reviewers.yml @@ -29,4 +29,4 @@ infra: - TSRBerry default: - - @developers + - '@developers' From 153b8bfc7c8c8711f8c2ce40f88085355d870b6a Mon Sep 17 00:00:00 2001 From: gdkchan Date: Fri, 18 Aug 2023 02:25:54 -0300 Subject: [PATCH 05/64] Implement support for masked stencil clears on Vulkan (#5589) * Implement support for masked stencil clears on Vulkan * PR feedback --- .../FramebufferParams.cs | 10 ++ src/Ryujinx.Graphics.Vulkan/HelperShader.cs | 108 ++++++++++++------ src/Ryujinx.Graphics.Vulkan/PipelineBase.cs | 6 +- src/Ryujinx.Graphics.Vulkan/PipelineFull.cs | 36 ++++++ .../Ryujinx.Graphics.Vulkan.csproj | 1 + ...DepthStencilClearFragmentShaderSource.frag | 8 ++ .../DepthStencilClearFragment.spv | Bin 0 -> 468 bytes 7 files changed, 131 insertions(+), 38 deletions(-) create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/DepthStencilClearFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/DepthStencilClearFragment.spv diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs index 749d5929c..7600c2d5e 100644 --- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs +++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs @@ -148,6 +148,16 @@ namespace Ryujinx.Graphics.Vulkan return _attachments[index]; } + public Auto GetDepthStencilAttachment() + { + if (!HasDepthStencil) + { + return null; + } + + return _attachments[AttachmentsCount - 1]; + } + public ComponentType GetAttachmentComponentType(int index) { if (_colors != null && (uint)index < _colors.Length) diff --git a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs index 684ed068a..a6d237644 100644 --- a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs +++ b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs @@ -38,6 +38,7 @@ namespace Ryujinx.Graphics.Vulkan private readonly IProgram _programColorClearF; private readonly IProgram _programColorClearSI; private readonly IProgram _programColorClearUI; + private readonly IProgram _programDepthStencilClear; private readonly IProgram _programStrideChange; private readonly IProgram _programConvertD32S8ToD24S8; private readonly IProgram _programConvertIndexBuffer; @@ -105,6 +106,12 @@ namespace Ryujinx.Graphics.Vulkan new ShaderSource(ReadSpirv("ColorClearUIFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv), }, colorClearResourceLayout); + _programDepthStencilClear = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorClearVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ReadSpirv("DepthStencilClearFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv), + }, colorClearResourceLayout); + var strideChangeResourceLayout = new ResourceLayoutBuilder() .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0) .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1) @@ -446,10 +453,6 @@ namespace Ryujinx.Graphics.Vulkan 0f, 1f); - Span> scissors = stackalloc Rectangle[1]; - - scissors[0] = new Rectangle(0, 0, dstWidth, dstHeight); - if (dstIsDepthOrStencil) { _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit); @@ -470,7 +473,7 @@ namespace Ryujinx.Graphics.Vulkan _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, (uint)dstSamples, dstIsDepthOrStencil, dstFormat); _pipeline.SetRenderTargetColorMasks(new uint[] { 0xf }); - _pipeline.SetScissors(scissors); + _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dstWidth, dstHeight) }); if (clearAlpha) { @@ -547,12 +550,8 @@ namespace Ryujinx.Graphics.Vulkan 0f, 1f); - Span> scissors = stackalloc Rectangle[1]; - - scissors[0] = new Rectangle(0, 0, dstWidth, dstHeight); - _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, (uint)dstSamples, true, dstFormat); - _pipeline.SetScissors(scissors); + _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dstWidth, dstHeight) }); _pipeline.SetViewports(viewports); _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); @@ -639,7 +638,11 @@ namespace Ryujinx.Graphics.Vulkan } } - private static StencilTestDescriptor CreateStencilTestDescriptor(bool enabled) + private static StencilTestDescriptor CreateStencilTestDescriptor( + bool enabled, + int refValue = 0, + int compareMask = 0xff, + int writeMask = 0xff) { return new StencilTestDescriptor( enabled, @@ -647,16 +650,16 @@ namespace Ryujinx.Graphics.Vulkan StencilOp.Replace, StencilOp.Replace, StencilOp.Replace, - 0, - 0xff, - 0xff, + refValue, + compareMask, + writeMask, CompareOp.Always, StencilOp.Replace, StencilOp.Replace, StencilOp.Replace, - 0, - 0xff, - 0xff); + refValue, + compareMask, + writeMask); } public void Clear( @@ -695,10 +698,6 @@ namespace Ryujinx.Graphics.Vulkan 0f, 1f); - Span> scissors = stackalloc Rectangle[1]; - - scissors[0] = scissor; - IProgram program; if (type == ComponentType.SignedInteger) @@ -718,7 +717,7 @@ namespace Ryujinx.Graphics.Vulkan _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat); _pipeline.SetRenderTargetColorMasks(new[] { componentMask }); _pipeline.SetViewports(viewports); - _pipeline.SetScissors(scissors); + _pipeline.SetScissors(stackalloc Rectangle[] { scissor }); _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); _pipeline.Draw(4, 1, 0, 0); _pipeline.Finish(); @@ -726,6 +725,56 @@ namespace Ryujinx.Graphics.Vulkan gd.BufferManager.Delete(bufferHandle); } + public void Clear( + VulkanRenderer gd, + Auto dst, + float depthValue, + bool depthMask, + int stencilValue, + int stencilMask, + int dstWidth, + int dstHeight, + VkFormat dstFormat, + Rectangle scissor) + { + const int ClearColorBufferSize = 16; + + gd.FlushAllCommands(); + + using var cbs = gd.CommandBufferPool.Rent(); + + _pipeline.SetCommandBuffer(cbs); + + var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ClearColorBufferSize); + + gd.BufferManager.SetData(bufferHandle, 0, stackalloc float[] { depthValue }); + + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, ClearColorBufferSize)) }); + + Span viewports = stackalloc Viewport[1]; + + viewports[0] = new Viewport( + new Rectangle(0, 0, dstWidth, dstHeight), + ViewportSwizzle.PositiveX, + ViewportSwizzle.PositiveY, + ViewportSwizzle.PositiveZ, + ViewportSwizzle.PositiveW, + 0f, + 1f); + + _pipeline.SetProgram(_programDepthStencilClear); + _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, true, dstFormat); + _pipeline.SetViewports(viewports); + _pipeline.SetScissors(stackalloc Rectangle[] { scissor }); + _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); + _pipeline.SetDepthTest(new DepthTestDescriptor(true, depthMask, CompareOp.Always)); + _pipeline.SetStencilTest(CreateStencilTestDescriptor(stencilMask != 0, stencilValue, 0xff, stencilMask)); + _pipeline.Draw(4, 1, 0, 0); + _pipeline.Finish(); + + gd.BufferManager.Delete(bufferHandle); + } + public void DrawTexture( VulkanRenderer gd, PipelineBase pipeline, @@ -778,8 +827,6 @@ namespace Ryujinx.Graphics.Vulkan 0f, 1f); - Span> scissors = stackalloc Rectangle[1]; - pipeline.SetProgram(_programColorBlit); pipeline.SetViewports(viewports); pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); @@ -1119,11 +1166,7 @@ namespace Ryujinx.Graphics.Vulkan 0f, 1f); - Span> scissors = stackalloc Rectangle[1]; - - scissors[0] = new Rectangle(0, 0, dst.Width, dst.Height); - - _pipeline.SetScissors(scissors); + _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dst.Width, dst.Height) }); _pipeline.SetViewports(viewports); _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); @@ -1251,12 +1294,8 @@ namespace Ryujinx.Graphics.Vulkan 0f, 1f); - Span> scissors = stackalloc Rectangle[1]; - - scissors[0] = new Rectangle(0, 0, dst.Width, dst.Height); - _pipeline.SetRenderTargetColorMasks(new uint[] { 0xf }); - _pipeline.SetScissors(scissors); + _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dst.Width, dst.Height) }); _pipeline.SetViewports(viewports); _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); @@ -1731,6 +1770,7 @@ namespace Ryujinx.Graphics.Vulkan _programColorClearF.Dispose(); _programColorClearSI.Dispose(); _programColorClearUI.Dispose(); + _programDepthStencilClear.Dispose(); _programStrideChange.Dispose(); _programConvertIndexBuffer.Dispose(); _programConvertIndirectData.Dispose(); diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs index 67b16ec96..54b67f35e 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -243,10 +243,8 @@ namespace Ryujinx.Graphics.Vulkan Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect); } - public unsafe void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask) + public unsafe void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, bool stencilMask) { - // TODO: Use stencilMask (fully). - if (FramebufferParams == null || !FramebufferParams.HasDepthStencil) { return; @@ -255,7 +253,7 @@ namespace Ryujinx.Graphics.Vulkan var clearValue = new ClearValue(null, new ClearDepthStencilValue(depthValue, (uint)stencilValue)); var flags = depthMask ? ImageAspectFlags.DepthBit : 0; - if (stencilMask != 0) + if (stencilMask) { flags |= ImageAspectFlags.StencilBit; } diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs index dcc6c5300..c3e6f37c3 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs @@ -81,6 +81,42 @@ namespace Ryujinx.Graphics.Vulkan } } + public void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask) + { + if (FramebufferParams == null) + { + return; + } + + if (stencilMask != 0 && stencilMask != 0xff) + { + // 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. + var dstTexture = FramebufferParams.GetDepthStencilAttachment(); + if (dstTexture == null) + { + return; + } + + // TODO: Clear only the specified layer. + Gd.HelperShader.Clear( + Gd, + dstTexture, + depthValue, + depthMask, + stencilValue, + stencilMask, + (int)FramebufferParams.Width, + (int)FramebufferParams.Height, + FramebufferParams.AttachmentFormats[FramebufferParams.AttachmentsCount - 1], + ClearScissor); + } + else + { + ClearRenderTargetDepthStencil(layer, layerCount, depthValue, depthMask, stencilValue, stencilMask != 0); + } + } + public void EndHostConditionalRendering() { if (Gd.Capabilities.SupportsConditionalRendering) diff --git a/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj b/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj index 343951186..8d30457e2 100644 --- a/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj +++ b/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj @@ -42,6 +42,7 @@ + diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/DepthStencilClearFragmentShaderSource.frag b/src/Ryujinx.Graphics.Vulkan/Shaders/DepthStencilClearFragmentShaderSource.frag new file mode 100644 index 000000000..689a0fffb --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/DepthStencilClearFragmentShaderSource.frag @@ -0,0 +1,8 @@ +#version 450 core + +layout (location = 0) in vec4 clear_colour; + +void main() +{ + gl_FragDepth = clear_colour.x; +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/DepthStencilClearFragment.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/DepthStencilClearFragment.spv new file mode 100644 index 0000000000000000000000000000000000000000..dcd3235b6c9a8eda2441711583ea42ad04b924bf GIT binary patch literal 468 zcmYk1%}T>i5QVQvlUA$!L99E8xD<*DErKWrVmICS043B?g=i(#7xbyz2!7vf6>pf# z%(-XInVTjj%@DRiJG4SStWQ5AAOS3oc|3WT4BuCe!}E(X8J&zj~KC Date: Sat, 19 Aug 2023 15:16:59 -0300 Subject: [PATCH 06/64] Fix debug assert on services without pointer buffer (#5599) --- src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs index 9ac2a337e..65a433038 100644 --- a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs @@ -31,7 +31,10 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc if (allocator != null) { - _pointerBuffersBaseAddress = allocator.Allocate((ulong)maxSessions * (ulong)options.PointerBufferSize); + if (options.PointerBufferSize != 0) + { + _pointerBuffersBaseAddress = allocator.Allocate((ulong)maxSessions * (ulong)options.PointerBufferSize); + } if (options.CanDeferInvokeRequest) { From 7f96dbc0242f169caeb8461237bc01a23c115f56 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sun, 20 Aug 2023 03:56:30 -0300 Subject: [PATCH 07/64] Fix invalid audio renderer buffer size when end offset < start offset (#5588) * Fix invalid audio renderer buffer size when end offset < start offset * Fix possible overflow on IsSampleOffsetInRangeForPcm --- src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs | 5 +++++ src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs index d209c515b..8134e6b77 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs @@ -20,6 +20,11 @@ namespace Ryujinx.Audio.Renderer.Dsp [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetBufferSize(int startSampleOffset, int endSampleOffset, int offset, int count) where T : unmanaged { + if (endSampleOffset < startSampleOffset) + { + return 0; + } + return GetCountToDecode(startSampleOffset, endSampleOffset, offset, count) * Unsafe.SizeOf(); } diff --git a/src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs index 86f92442b..f33d82aa0 100644 --- a/src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs +++ b/src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs @@ -264,8 +264,8 @@ namespace Ryujinx.Audio.Renderer.Parameter { uint dataTypeSize = (uint)Unsafe.SizeOf(); - return StartSampleOffset * dataTypeSize <= Size && - EndSampleOffset * dataTypeSize <= Size; + return (ulong)StartSampleOffset * dataTypeSize <= Size && + (ulong)EndSampleOffset * dataTypeSize <= Size; } /// From cd7b52f995690505cdb0bc65d06236c18dd06b89 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Wed, 23 Aug 2023 20:40:25 +0100 Subject: [PATCH 08/64] Vulkan: Fix MoltenVK flickering (#5612) #5576 changed where the position was declared, but forgot to add the Invariant declaration to position when the ReducedPrecision flag was enabled. This was causing weird graphical bugs in a bunch of games, mostly to do with mismatching depth between multiple draws of the same geometry. Maybe the attempt to add it to Position in DeclareInputOrOutput can be removed now, assuming that path is never used. --- .../Shader/DiskCache/DiskCacheHostStorage.cs | 2 +- src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 71a738255..08096bd5a 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 5540; + private const uint CodeGenVersion = 5609; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs index 153a6940b..b0659ba44 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs @@ -434,6 +434,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv context.Decorate(perVertexStructType, Decoration.Block); + if (context.HostCapabilities.ReducedPrecision) + { + context.MemberDecorate(perVertexStructType, 0, Decoration.Invariant); + } + context.MemberDecorate(perVertexStructType, 0, Decoration.BuiltIn, (LiteralInteger)BuiltIn.Position); context.MemberDecorate(perVertexStructType, 1, Decoration.BuiltIn, (LiteralInteger)BuiltIn.PointSize); context.MemberDecorate(perVertexStructType, 2, Decoration.BuiltIn, (LiteralInteger)BuiltIn.ClipDistance); From 93d78f9ac4a37a50f0cc2e57addd330d072af742 Mon Sep 17 00:00:00 2001 From: MutantAura <44103205+MutantAura@users.noreply.github.com> Date: Tue, 29 Aug 2023 09:56:43 +0100 Subject: [PATCH 09/64] Add SmallChange properties to the rest of the sliders (fixes keyboard input). (#5621) --- src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml | 5 +++++ src/Ryujinx.Ava/UI/Views/Input/MotionInputView.axaml | 2 ++ src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml | 2 ++ src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml | 1 + 4 files changed, 10 insertions(+) diff --git a/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml b/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml index 65a66d9e2..2ab42e6ee 100644 --- a/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml +++ b/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml @@ -465,6 +465,7 @@ Maximum="1" TickFrequency="0.01" IsSnapToTickEnabled="True" + SmallChange="0.01" Minimum="0" Value="{ReflectionBinding Configuration.DeadzoneLeft, Mode=TwoWay}" /> @@ -50,6 +51,7 @@ MaxWidth="150" TickFrequency="0.01" IsSnapToTickEnabled="True" + SmallChange="0.01" Maximum="100" Minimum="0" Value="{Binding GyroDeadzone, Mode=TwoWay}" /> diff --git a/src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml b/src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml index 16190d391..f633c0ed2 100644 --- a/src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml +++ b/src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml @@ -26,6 +26,7 @@ Width="200" TickFrequency="0.01" IsSnapToTickEnabled="True" + SmallChange="0.01" Maximum="10" Minimum="0" Value="{Binding StrongRumble, Mode=TwoWay}" /> @@ -47,6 +48,7 @@ Maximum="10" TickFrequency="0.01" IsSnapToTickEnabled="True" + SmallChange="0.01" Minimum="0" Value="{Binding WeakRumble, Mode=TwoWay}" /> Date: Tue, 29 Aug 2023 21:10:34 -0300 Subject: [PATCH 10/64] Geometry shader emulation for macOS (#5551) * Implement vertex and geometry shader conversion to compute * Call InitializeReservedCounts for compute too * PR feedback * Set clip distance mask for geometry and tessellation shaders too * Transform feedback emulation only for vertex --- src/Ryujinx.Graphics.GAL/Capabilities.cs | 6 + .../Engine/GPFifo/GPFifoProcessor.cs | 16 +- .../ComputeDraw/VertexInfoBufferUpdater.cs | 141 ++++ .../Engine/Threed/ComputeDraw/VtgAsCompute.cs | 96 +++ .../Threed/ComputeDraw/VtgAsComputeContext.cs | 648 ++++++++++++++++++ .../Threed/ComputeDraw/VtgAsComputeState.cs | 535 +++++++++++++++ .../Engine/Threed/DrawManager.cs | 142 +++- .../Engine/Threed/DrawState.cs | 16 + .../Threed/SpecializationStateUpdater.cs | 16 +- .../Engine/Threed/StateUpdater.cs | 31 +- .../Engine/Threed/ThreedClass.cs | 29 +- src/Ryujinx.Graphics.Gpu/GpuChannel.cs | 1 + src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs | 102 +++ .../Memory/BufferManager.cs | 49 +- .../Memory/BufferUpdater.cs | 123 ++++ .../Memory/SupportBufferUpdater.cs | 85 +-- .../Shader/CachedShaderProgram.cs | 23 + .../Shader/DiskCache/DiskCacheGpuAccessor.cs | 2 +- .../Shader/DiskCache/DiskCacheHostStorage.cs | 25 +- .../DiskCache/ParallelDiskCacheLoader.cs | 16 +- .../Shader/GpuAccessor.cs | 8 +- .../Shader/GpuAccessorBase.cs | 41 +- .../Shader/ShaderAsCompute.cs | 20 + .../Shader/ShaderCache.cs | 162 +++-- .../Shader/ShaderInfoBuilder.cs | 63 +- .../Shader/ShaderSpecializationList.cs | 9 +- .../Shader/ShaderSpecializationState.cs | 35 +- src/Ryujinx.Graphics.OpenGL/HwCapabilities.cs | 2 + src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs | 2 + src/Ryujinx.Graphics.Shader/AttributeType.cs | 8 +- .../CodeGen/Glsl/Declarations.cs | 20 +- .../CodeGen/Glsl/Instructions/IoMap.cs | 1 + .../CodeGen/Spirv/CodeGenContext.cs | 15 - .../CodeGen/Spirv/Declarations.cs | 4 +- .../CodeGen/Spirv/IoMap.cs | 1 + .../CodeGen/Spirv/SpirvGenerator.cs | 12 +- src/Ryujinx.Graphics.Shader/Constants.cs | 6 - .../Decoders/DecodedProgram.cs | 5 + .../Decoders/Decoder.cs | 35 +- src/Ryujinx.Graphics.Shader/InputTopology.cs | 13 + .../Instructions/InstEmitAttribute.cs | 2 +- .../IntermediateRepresentation/IoVariable.cs | 1 + .../ResourceReservationCounts.cs | 22 + .../ShaderIdentification.cs | 8 - .../ShaderProgramInfo.cs | 15 +- src/Ryujinx.Graphics.Shader/SupportBuffer.cs | 25 +- .../Translation/AttributeConsts.cs | 2 + .../Translation/EmitterContext.cs | 197 +++++- .../Translation/EmitterContextInsts.cs | 5 + .../Translation/FeatureFlags.cs | 4 + .../Translation/IoUsage.cs | 28 + .../Translation/ResourceManager.cs | 46 +- .../Translation/ResourceReservations.cs | 186 +++++ .../Translation/ShaderDefinitions.cs | 59 +- .../Translation/ShaderIdentifier.cs | 187 ----- .../Translation/TransformContext.cs | 3 + .../Transforms/GeometryToCompute.cs | 378 ++++++++++ .../Translation/Transforms/TexturePass.cs | 8 +- .../Translation/Transforms/TransformPasses.cs | 2 + .../Translation/Transforms/VertexToCompute.cs | 364 ++++++++++ .../Translation/Translator.cs | 41 +- .../Translation/TranslatorContext.cs | 279 ++++++-- .../VertexInfoBuffer.cs | 59 ++ src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 2 + src/Ryujinx.ShaderTools/Program.cs | 18 +- 65 files changed, 3912 insertions(+), 593 deletions(-) create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VertexInfoBufferUpdater.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsCompute.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferUpdater.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderAsCompute.cs create mode 100644 src/Ryujinx.Graphics.Shader/ResourceReservationCounts.cs delete mode 100644 src/Ryujinx.Graphics.Shader/ShaderIdentification.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/IoUsage.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs delete mode 100644 src/Ryujinx.Graphics.Shader/Translation/ShaderIdentifier.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Transforms/GeometryToCompute.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Transforms/VertexToCompute.cs create mode 100644 src/Ryujinx.Graphics.Shader/VertexInfoBuffer.cs diff --git a/src/Ryujinx.Graphics.GAL/Capabilities.cs b/src/Ryujinx.Graphics.GAL/Capabilities.cs index d41f8e59f..756422049 100644 --- a/src/Ryujinx.Graphics.GAL/Capabilities.cs +++ b/src/Ryujinx.Graphics.GAL/Capabilities.cs @@ -39,6 +39,7 @@ namespace Ryujinx.Graphics.GAL public readonly bool SupportsShaderBarrierDivergence; public readonly bool SupportsShaderFloat64; public readonly bool SupportsTextureShadowLod; + public readonly bool SupportsVertexStoreAndAtomics; public readonly bool SupportsViewportIndexVertexTessellation; public readonly bool SupportsViewportMask; public readonly bool SupportsViewportSwizzle; @@ -54,6 +55,7 @@ namespace Ryujinx.Graphics.GAL public readonly float MaximumSupportedAnisotropy; public readonly int ShaderSubgroupSize; public readonly int StorageBufferOffsetAlignment; + public readonly int TextureBufferOffsetAlignment; public readonly int GatherBiasPrecision; @@ -91,6 +93,7 @@ namespace Ryujinx.Graphics.GAL bool supportsShaderBarrierDivergence, bool supportsShaderFloat64, bool supportsTextureShadowLod, + bool supportsVertexStoreAndAtomics, bool supportsViewportIndexVertexTessellation, bool supportsViewportMask, bool supportsViewportSwizzle, @@ -104,6 +107,7 @@ namespace Ryujinx.Graphics.GAL float maximumSupportedAnisotropy, int shaderSubgroupSize, int storageBufferOffsetAlignment, + int textureBufferOffsetAlignment, int gatherBiasPrecision) { Api = api; @@ -139,6 +143,7 @@ namespace Ryujinx.Graphics.GAL SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence; SupportsShaderFloat64 = supportsShaderFloat64; SupportsTextureShadowLod = supportsTextureShadowLod; + SupportsVertexStoreAndAtomics = supportsVertexStoreAndAtomics; SupportsViewportIndexVertexTessellation = supportsViewportIndexVertexTessellation; SupportsViewportMask = supportsViewportMask; SupportsViewportSwizzle = supportsViewportSwizzle; @@ -152,6 +157,7 @@ namespace Ryujinx.Graphics.GAL MaximumSupportedAnisotropy = maximumSupportedAnisotropy; ShaderSubgroupSize = shaderSubgroupSize; StorageBufferOffsetAlignment = storageBufferOffsetAlignment; + TextureBufferOffsetAlignment = textureBufferOffsetAlignment; GatherBiasPrecision = gatherBiasPrecision; } } diff --git a/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs index 6ba1bc22e..180e2a6b6 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs @@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo /// /// Represents a GPU General Purpose FIFO command processor. /// - class GPFifoProcessor + class GPFifoProcessor : IDisposable { private const int MacrosCount = 0x80; private const int MacroIndexMask = MacrosCount - 1; @@ -327,5 +327,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo { _3dClass.PerformDeferredDraws(); } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _3dClass.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } } } diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VertexInfoBufferUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VertexInfoBufferUpdater.cs new file mode 100644 index 000000000..65f556fcb --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VertexInfoBufferUpdater.cs @@ -0,0 +1,141 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Shader; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw +{ + /// + /// Vertex info buffer data updater. + /// + class VertexInfoBufferUpdater : BufferUpdater + { + private VertexInfoBuffer _data; + + /// + /// Creates a new instance of the vertex info buffer updater. + /// + /// Renderer that the vertex info buffer will be used with + public VertexInfoBufferUpdater(IRenderer renderer) : base(renderer) + { + } + + /// + /// Sets vertex data related counts. + /// + /// Number of vertices used on the draw + /// Number of draw instances + /// Index of the first vertex on the vertex buffer + /// Index of the first instanced vertex on the vertex buffer + public void SetVertexCounts(int vertexCount, int instanceCount, int firstVertex, int firstInstance) + { + if (_data.VertexCounts.X != vertexCount) + { + _data.VertexCounts.X = vertexCount; + MarkDirty(VertexInfoBuffer.VertexCountsOffset, sizeof(int)); + } + + if (_data.VertexCounts.Y != instanceCount) + { + _data.VertexCounts.Y = instanceCount; + MarkDirty(VertexInfoBuffer.VertexCountsOffset + sizeof(int), sizeof(int)); + } + + if (_data.VertexCounts.Z != firstVertex) + { + _data.VertexCounts.Z = firstVertex; + MarkDirty(VertexInfoBuffer.VertexCountsOffset + sizeof(int) * 2, sizeof(int)); + } + + if (_data.VertexCounts.W != firstInstance) + { + _data.VertexCounts.W = firstInstance; + MarkDirty(VertexInfoBuffer.VertexCountsOffset + sizeof(int) * 3, sizeof(int)); + } + } + + /// + /// Sets vertex data related counts. + /// + /// Number of primitives consumed by the geometry shader + public void SetGeometryCounts(int primitivesCount) + { + if (_data.GeometryCounts.X != primitivesCount) + { + _data.GeometryCounts.X = primitivesCount; + MarkDirty(VertexInfoBuffer.GeometryCountsOffset, sizeof(int)); + } + } + + /// + /// Sets a vertex stride and related data. + /// + /// Index of the vertex stride to be updated + /// Stride divided by the component or format size + /// Number of components that the format has + public void SetVertexStride(int index, int stride, int componentCount) + { + if (_data.VertexStrides[index].X != stride) + { + _data.VertexStrides[index].X = stride; + MarkDirty(VertexInfoBuffer.VertexStridesOffset + index * Unsafe.SizeOf>(), sizeof(int)); + } + + for (int c = 1; c < 4; c++) + { + int value = c < componentCount ? 1 : 0; + + ref int currentValue = ref GetElementRef(ref _data.VertexStrides[index], c); + + if (currentValue != value) + { + currentValue = value; + MarkDirty(VertexInfoBuffer.VertexStridesOffset + index * Unsafe.SizeOf>() + c * sizeof(int), sizeof(int)); + } + } + } + + /// + /// Sets a vertex offset and related data. + /// + /// Index of the vertex offset to be updated + /// Offset divided by the component or format size + /// If the draw is instanced, should have the vertex divisor value, otherwise should be zero + public void SetVertexOffset(int index, int offset, int divisor) + { + if (_data.VertexOffsets[index].X != offset) + { + _data.VertexOffsets[index].X = offset; + MarkDirty(VertexInfoBuffer.VertexOffsetsOffset + index * Unsafe.SizeOf>(), sizeof(int)); + } + + if (_data.VertexOffsets[index].Y != divisor) + { + _data.VertexOffsets[index].Y = divisor; + MarkDirty(VertexInfoBuffer.VertexOffsetsOffset + index * Unsafe.SizeOf>() + sizeof(int), sizeof(int)); + } + } + + /// + /// Sets the offset of the index buffer. + /// + /// Offset divided by the component size + public void SetIndexBufferOffset(int offset) + { + if (_data.GeometryCounts.W != offset) + { + _data.GeometryCounts.W = offset; + MarkDirty(VertexInfoBuffer.GeometryCountsOffset + sizeof(int) * 3, sizeof(int)); + } + } + + /// + /// Submits all pending buffer updates to the GPU. + /// + public void Commit() + { + Commit(MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref _data, 1))); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsCompute.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsCompute.cs new file mode 100644 index 000000000..cbbfd251d --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsCompute.cs @@ -0,0 +1,96 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Shader; +using System; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw +{ + /// + /// Vertex, tessellation and geometry as compute shader draw manager. + /// + class VtgAsCompute : IDisposable + { + private readonly GpuContext _context; + private readonly GpuChannel _channel; + private readonly DeviceStateWithShadow _state; + private readonly VtgAsComputeContext _vacContext; + + /// + /// Creates a new instance of the vertex, tessellation and geometry as compute shader draw manager. + /// + /// GPU context + /// GPU channel + /// 3D engine state + public VtgAsCompute(GpuContext context, GpuChannel channel, DeviceStateWithShadow state) + { + _context = context; + _channel = channel; + _state = state; + _vacContext = new(context); + } + + /// + /// Emulates the pre-rasterization stages of a draw operation using a compute shader. + /// + /// 3D engine + /// Vertex shader converted to compute + /// Optional geometry shader converted to compute + /// Fragment shader with a vertex passthrough shader to feed the compute output into the fragment stage + /// Primitive topology of the draw + /// Index or vertex count of the draw + /// Instance count + /// First index on the index buffer, for indexed draws + /// First vertex on the vertex buffer + /// First instance + /// Whether the draw is indexed + public void DrawAsCompute( + ThreedClass engine, + ShaderAsCompute vertexAsCompute, + ShaderAsCompute geometryAsCompute, + IProgram vertexPassthroughProgram, + PrimitiveTopology topology, + int count, + int instanceCount, + int firstIndex, + int firstVertex, + int firstInstance, + bool indexed) + { + VtgAsComputeState state = new( + _context, + _channel, + _state, + _vacContext, + engine, + vertexAsCompute, + geometryAsCompute, + vertexPassthroughProgram, + topology, + count, + instanceCount, + firstIndex, + firstVertex, + firstInstance, + indexed); + + state.RunVertex(); + state.RunGeometry(); + state.RunFragment(); + + _vacContext.FreeBuffers(); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _vacContext.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs new file mode 100644 index 000000000..e9b754ff1 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs @@ -0,0 +1,648 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw +{ + /// + /// Vertex, tessellation and geometry as compute shader context. + /// + class VtgAsComputeContext : IDisposable + { + private const int DummyBufferSize = 16; + + private readonly GpuContext _context; + + /// + /// Cache of buffer textures used for vertex and index buffers. + /// + private class BufferTextureCache : IDisposable + { + private readonly Dictionary _cache; + + /// + /// Creates a new instance of the buffer texture cache. + /// + public BufferTextureCache() + { + _cache = new(); + } + + /// + /// Gets a cached or creates and caches a buffer texture with the specified format. + /// + /// Renderer where the texture will be used + /// Format of the buffer texture + /// Buffer texture + public ITexture Get(IRenderer renderer, Format format) + { + if (!_cache.TryGetValue(format, out ITexture bufferTexture)) + { + bufferTexture = renderer.CreateTexture(new TextureCreateInfo( + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + format, + DepthStencilMode.Depth, + Target.TextureBuffer, + SwizzleComponent.Red, + SwizzleComponent.Green, + SwizzleComponent.Blue, + SwizzleComponent.Alpha)); + + _cache.Add(format, bufferTexture); + } + + return bufferTexture; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + foreach (var texture in _cache.Values) + { + texture.Release(); + } + + _cache.Clear(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } + + /// + /// Buffer state. + /// + private struct Buffer + { + /// + /// Buffer handle. + /// + public BufferHandle Handle; + + /// + /// Current free buffer offset. + /// + public int Offset; + + /// + /// Total buffer size in bytes. + /// + public int Size; + } + + /// + /// Index buffer state. + /// + private readonly struct IndexBuffer + { + /// + /// Buffer handle. + /// + public BufferHandle Handle { get; } + + /// + /// Index count. + /// + public int Count { get; } + + /// + /// Size in bytes. + /// + public int Size { get; } + + /// + /// Creates a new index buffer state. + /// + /// Buffer handle + /// Index count + /// Size in bytes + public IndexBuffer(BufferHandle handle, int count, int size) + { + Handle = handle; + Count = count; + Size = size; + } + + /// + /// Creates a full range starting from the beggining of the buffer. + /// + /// Range + public readonly BufferRange ToRange() + { + return new BufferRange(Handle, 0, Size); + } + + /// + /// Creates a range starting from the beggining of the buffer, with the specified size. + /// + /// Size in bytes of the range + /// Range + public readonly BufferRange ToRange(int size) + { + return new BufferRange(Handle, 0, size); + } + } + + private readonly BufferTextureCache[] _bufferTextures; + private BufferHandle _dummyBuffer; + private Buffer _vertexDataBuffer; + private Buffer _geometryVertexDataBuffer; + private Buffer _geometryIndexDataBuffer; + private BufferHandle _sequentialIndexBuffer; + private int _sequentialIndexBufferCount; + + private readonly Dictionary _topologyRemapBuffers; + + /// + /// Vertex information buffer updater. + /// + public VertexInfoBufferUpdater VertexInfoBufferUpdater { get; } + + /// + /// Creates a new instance of the vertex, tessellation and geometry as compute shader context. + /// + /// + public VtgAsComputeContext(GpuContext context) + { + _context = context; + _bufferTextures = new BufferTextureCache[Constants.TotalVertexBuffers + 2]; + _topologyRemapBuffers = new(); + VertexInfoBufferUpdater = new(context.Renderer); + } + + /// + /// Gets the number of complete primitives that can be formed with a given vertex count, for a given topology. + /// + /// Topology + /// Vertex count + /// Total of complete primitives + public static int GetPrimitivesCount(PrimitiveTopology primitiveType, int count) + { + return primitiveType switch + { + PrimitiveTopology.Lines => count / 2, + PrimitiveTopology.LinesAdjacency => count / 4, + PrimitiveTopology.LineLoop => count > 1 ? count : 0, + PrimitiveTopology.LineStrip => Math.Max(count - 1, 0), + PrimitiveTopology.LineStripAdjacency => Math.Max(count - 3, 0), + PrimitiveTopology.Triangles => count / 3, + PrimitiveTopology.TrianglesAdjacency => count / 6, + PrimitiveTopology.TriangleStrip or + PrimitiveTopology.TriangleFan or + PrimitiveTopology.Polygon => Math.Max(count - 2, 0), + PrimitiveTopology.TriangleStripAdjacency => Math.Max(count - 2, 0) / 2, + PrimitiveTopology.Quads => (count / 4) * 2, // In triangles. + PrimitiveTopology.QuadStrip => Math.Max((count - 2) / 2, 0) * 2, // In triangles. + _ => count, + }; + } + + /// + /// Gets the total of vertices that a single primitive has, for the specified topology. + /// + /// Topology + /// Vertex count + private static int GetVerticesPerPrimitive(PrimitiveTopology primitiveType) + { + return primitiveType switch + { + PrimitiveTopology.Lines or + PrimitiveTopology.LineLoop or + PrimitiveTopology.LineStrip => 2, + PrimitiveTopology.LinesAdjacency or + PrimitiveTopology.LineStripAdjacency => 4, + PrimitiveTopology.Triangles or + PrimitiveTopology.TriangleStrip or + PrimitiveTopology.TriangleFan or + PrimitiveTopology.Polygon => 3, + PrimitiveTopology.TrianglesAdjacency or + PrimitiveTopology.TriangleStripAdjacency => 6, + PrimitiveTopology.Quads or + PrimitiveTopology.QuadStrip => 3, // 2 triangles. + _ => 1, + }; + } + + /// + /// Gets a cached or creates a new buffer that can be used to map linear indices to ones + /// of a specified topology, and build complete primitives. + /// + /// Topology + /// Number of input vertices that needs to be mapped using that buffer + /// Remap buffer range + public BufferRange GetOrCreateTopologyRemapBuffer(PrimitiveTopology topology, int count) + { + if (!_topologyRemapBuffers.TryGetValue(topology, out IndexBuffer buffer) || buffer.Count < count) + { + if (buffer.Handle != BufferHandle.Null) + { + _context.Renderer.DeleteBuffer(buffer.Handle); + } + + buffer = CreateTopologyRemapBuffer(topology, count); + _topologyRemapBuffers[topology] = buffer; + + return buffer.ToRange(); + } + + return buffer.ToRange(Math.Max(GetPrimitivesCount(topology, count) * GetVerticesPerPrimitive(topology), 1) * sizeof(uint)); + } + + /// + /// Creates a new topology remap buffer. + /// + /// Topology + /// Maximum of vertices that will be accessed + /// Remap buffer range + private IndexBuffer CreateTopologyRemapBuffer(PrimitiveTopology topology, int count) + { + // Size can't be zero as creating zero sized buffers is invalid. + Span data = new int[Math.Max(GetPrimitivesCount(topology, count) * GetVerticesPerPrimitive(topology), 1)]; + + switch (topology) + { + case PrimitiveTopology.Points: + case PrimitiveTopology.Lines: + case PrimitiveTopology.LinesAdjacency: + case PrimitiveTopology.Triangles: + case PrimitiveTopology.TrianglesAdjacency: + case PrimitiveTopology.Patches: + for (int index = 0; index < data.Length; index++) + { + data[index] = index; + } + break; + case PrimitiveTopology.LineLoop: + data[^1] = 0; + + for (int index = 0; index < ((data.Length - 1) & ~1); index += 2) + { + data[index] = index >> 1; + data[index + 1] = (index >> 1) + 1; + } + break; + case PrimitiveTopology.LineStrip: + for (int index = 0; index < ((data.Length - 1) & ~1); index += 2) + { + data[index] = index >> 1; + data[index + 1] = (index >> 1) + 1; + } + break; + case PrimitiveTopology.TriangleStrip: + int tsTrianglesCount = data.Length / 3; + int tsOutIndex = 3; + + if (tsTrianglesCount > 0) + { + data[0] = 0; + data[1] = 1; + data[2] = 2; + } + + for (int tri = 1; tri < tsTrianglesCount; tri++) + { + int baseIndex = tri * 3; + + if ((tri & 1) != 0) + { + data[baseIndex] = tsOutIndex - 1; + data[baseIndex + 1] = tsOutIndex - 2; + data[baseIndex + 2] = tsOutIndex++; + } + else + { + data[baseIndex] = tsOutIndex - 2; + data[baseIndex + 1] = tsOutIndex - 1; + data[baseIndex + 2] = tsOutIndex++; + } + } + break; + case PrimitiveTopology.TriangleFan: + case PrimitiveTopology.Polygon: + int tfTrianglesCount = data.Length / 3; + int tfOutIndex = 1; + + for (int index = 0; index < tfTrianglesCount * 3; index += 3) + { + data[index] = 0; + data[index + 1] = tfOutIndex; + data[index + 2] = ++tfOutIndex; + } + break; + case PrimitiveTopology.Quads: + int qQuadsCount = data.Length / 6; + + for (int quad = 0; quad < qQuadsCount; quad++) + { + int index = quad * 6; + int qIndex = quad * 4; + + data[index] = qIndex; + data[index + 1] = qIndex + 1; + data[index + 2] = qIndex + 2; + data[index + 3] = qIndex; + data[index + 4] = qIndex + 2; + data[index + 5] = qIndex + 3; + } + break; + case PrimitiveTopology.QuadStrip: + int qsQuadsCount = data.Length / 6; + + if (qsQuadsCount > 0) + { + data[0] = 0; + data[1] = 1; + data[2] = 2; + data[3] = 0; + data[4] = 2; + data[5] = 3; + } + + for (int quad = 1; quad < qsQuadsCount; quad++) + { + int index = quad * 6; + int qIndex = quad * 2; + + data[index] = qIndex + 1; + data[index + 1] = qIndex; + data[index + 2] = qIndex + 2; + data[index + 3] = qIndex + 1; + data[index + 4] = qIndex + 2; + data[index + 5] = qIndex + 3; + } + break; + case PrimitiveTopology.LineStripAdjacency: + for (int index = 0; index < ((data.Length - 3) & ~3); index += 4) + { + int lIndex = index >> 2; + + data[index] = lIndex; + data[index + 1] = lIndex + 1; + data[index + 2] = lIndex + 2; + data[index + 3] = lIndex + 3; + } + break; + case PrimitiveTopology.TriangleStripAdjacency: + int tsaTrianglesCount = data.Length / 6; + int tsaOutIndex = 6; + + if (tsaTrianglesCount > 0) + { + data[0] = 0; + data[1] = 1; + data[2] = 2; + data[3] = 3; + data[4] = 4; + data[5] = 5; + } + + for (int tri = 1; tri < tsaTrianglesCount; tri++) + { + int baseIndex = tri * 6; + + if ((tri & 1) != 0) + { + data[baseIndex] = tsaOutIndex - 2; + data[baseIndex + 1] = tsaOutIndex - 1; + data[baseIndex + 2] = tsaOutIndex - 4; + data[baseIndex + 3] = tsaOutIndex - 3; + data[baseIndex + 4] = tsaOutIndex++; + data[baseIndex + 5] = tsaOutIndex++; + } + else + { + data[baseIndex] = tsaOutIndex - 4; + data[baseIndex + 1] = tsaOutIndex - 3; + data[baseIndex + 2] = tsaOutIndex - 2; + data[baseIndex + 3] = tsaOutIndex - 1; + data[baseIndex + 4] = tsaOutIndex++; + data[baseIndex + 5] = tsaOutIndex++; + } + } + break; + } + + ReadOnlySpan dataBytes = MemoryMarshal.Cast(data); + + BufferHandle buffer = _context.Renderer.CreateBuffer(dataBytes.Length); + _context.Renderer.SetBufferData(buffer, 0, dataBytes); + + return new IndexBuffer(buffer, count, dataBytes.Length); + } + + /// + /// Gets a buffer texture with a given format, for the given index. + /// + /// Index of the buffer texture + /// Format of the buffer texture + /// Buffer texture + public ITexture EnsureBufferTexture(int index, Format format) + { + return (_bufferTextures[index] ??= new()).Get(_context.Renderer, format); + } + + /// + /// Gets the offset and size of usable storage on the output vertex buffer. + /// + /// Size in bytes that will be used + /// Usable offset and size on the buffer + public (int, int) GetVertexDataBuffer(int size) + { + return EnsureBuffer(ref _vertexDataBuffer, size); + } + + /// + /// Gets the offset and size of usable storage on the output geometry shader vertex buffer. + /// + /// Size in bytes that will be used + /// Usable offset and size on the buffer + public (int, int) GetGeometryVertexDataBuffer(int size) + { + return EnsureBuffer(ref _geometryVertexDataBuffer, size); + } + + /// + /// Gets the offset and size of usable storage on the output geometry shader index buffer. + /// + /// Size in bytes that will be used + /// Usable offset and size on the buffer + public (int, int) GetGeometryIndexDataBuffer(int size) + { + return EnsureBuffer(ref _geometryIndexDataBuffer, size); + } + + /// + /// Gets a range of the output vertex buffer for binding. + /// + /// Offset of the range + /// Size of the range in bytes + /// Range + public BufferRange GetVertexDataBufferRange(int offset, int size) + { + return new BufferRange(_vertexDataBuffer.Handle, offset, size); + } + + /// + /// Gets a range of the output geometry shader vertex buffer for binding. + /// + /// Offset of the range + /// Size of the range in bytes + /// Range + public BufferRange GetGeometryVertexDataBufferRange(int offset, int size) + { + return new BufferRange(_geometryVertexDataBuffer.Handle, offset, size); + } + + /// + /// Gets a range of the output geometry shader index buffer for binding. + /// + /// Offset of the range + /// Size of the range in bytes + /// Range + public BufferRange GetGeometryIndexDataBufferRange(int offset, int size) + { + return new BufferRange(_geometryIndexDataBuffer.Handle, offset, size); + } + + /// + /// Gets the range for a dummy 16 bytes buffer, filled with zeros. + /// + /// Dummy buffer range + public BufferRange GetDummyBufferRange() + { + if (_dummyBuffer == BufferHandle.Null) + { + _dummyBuffer = _context.Renderer.CreateBuffer(DummyBufferSize); + _context.Renderer.Pipeline.ClearBuffer(_dummyBuffer, 0, DummyBufferSize, 0); + } + + return new BufferRange(_dummyBuffer, 0, DummyBufferSize); + } + + /// + /// Gets the range for a sequential index buffer, with ever incrementing index values. + /// + /// Minimum number of indices that the buffer should have + /// Buffer handle + public BufferHandle GetSequentialIndexBuffer(int count) + { + if (_sequentialIndexBufferCount < count) + { + if (_sequentialIndexBuffer != BufferHandle.Null) + { + _context.Renderer.DeleteBuffer(_sequentialIndexBuffer); + } + + _sequentialIndexBuffer = _context.Renderer.CreateBuffer(count * sizeof(uint)); + _sequentialIndexBufferCount = count; + + Span data = new int[count]; + + for (int index = 0; index < count; index++) + { + data[index] = index; + } + + _context.Renderer.SetBufferData(_sequentialIndexBuffer, 0, MemoryMarshal.Cast(data)); + } + + return _sequentialIndexBuffer; + } + + /// + /// Ensure that a buffer exists, is large enough, and allocates a sub-region of the specified size inside the buffer. + /// + /// Buffer state + /// Required size in bytes + /// Allocated offset and size + private (int, int) EnsureBuffer(ref Buffer buffer, int size) + { + int newSize = buffer.Offset + size; + + if (buffer.Size < newSize) + { + if (buffer.Handle != BufferHandle.Null) + { + _context.Renderer.DeleteBuffer(buffer.Handle); + } + + buffer.Handle = _context.Renderer.CreateBuffer(newSize); + buffer.Size = newSize; + } + + int offset = buffer.Offset; + + buffer.Offset = BitUtils.AlignUp(newSize, _context.Capabilities.StorageBufferOffsetAlignment); + + return (offset, size); + } + + /// + /// Frees all buffer sub-regions that were previously allocated. + /// + public void FreeBuffers() + { + _vertexDataBuffer.Offset = 0; + _geometryVertexDataBuffer.Offset = 0; + _geometryIndexDataBuffer.Offset = 0; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + for (int index = 0; index < _bufferTextures.Length; index++) + { + _bufferTextures[index]?.Dispose(); + _bufferTextures[index] = null; + } + + DestroyIfNotNull(ref _dummyBuffer); + DestroyIfNotNull(ref _vertexDataBuffer.Handle); + DestroyIfNotNull(ref _geometryVertexDataBuffer.Handle); + DestroyIfNotNull(ref _geometryIndexDataBuffer.Handle); + DestroyIfNotNull(ref _sequentialIndexBuffer); + + foreach (var indexBuffer in _topologyRemapBuffers.Values) + { + _context.Renderer.DeleteBuffer(indexBuffer.Handle); + } + + _topologyRemapBuffers.Clear(); + } + } + + /// + /// Deletes a buffer if the handle is valid (not null), then sets the handle to null. + /// + /// Buffer handle + private void DestroyIfNotNull(ref BufferHandle handle) + { + if (handle != BufferHandle.Null) + { + _context.Renderer.DeleteBuffer(handle); + handle = BufferHandle.Null; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs new file mode 100644 index 000000000..59a925087 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs @@ -0,0 +1,535 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Shader; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using System; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw +{ + /// + /// Vertex, tessellation and geometry as compute shader state. + /// + struct VtgAsComputeState + { + private const int ComputeLocalSize = 32; + + private readonly GpuContext _context; + private readonly GpuChannel _channel; + private readonly DeviceStateWithShadow _state; + private readonly VtgAsComputeContext _vacContext; + private readonly ThreedClass _engine; + private readonly ShaderAsCompute _vertexAsCompute; + private readonly ShaderAsCompute _geometryAsCompute; + private readonly IProgram _vertexPassthroughProgram; + private readonly PrimitiveTopology _topology; + private readonly int _count; + private readonly int _instanceCount; + private readonly int _firstIndex; + private readonly int _firstVertex; + private readonly int _firstInstance; + private readonly bool _indexed; + + private readonly int _vertexDataOffset; + private readonly int _vertexDataSize; + private readonly int _geometryVertexDataOffset; + private readonly int _geometryVertexDataSize; + private readonly int _geometryIndexDataOffset; + private readonly int _geometryIndexDataSize; + private readonly int _geometryIndexDataCount; + + /// + /// Creates a new vertex, tessellation and geometry as compute shader state. + /// + /// GPU context + /// GPU channel + /// 3D engine state + /// Vertex as compute context + /// 3D engine + /// Vertex shader converted to compute + /// Optional geometry shader converted to compute + /// Fragment shader with a vertex passthrough shader to feed the compute output into the fragment stage + /// Primitive topology of the draw + /// Index or vertex count of the draw + /// Instance count + /// First index on the index buffer, for indexed draws + /// First vertex on the vertex buffer + /// First instance + /// Whether the draw is indexed + public VtgAsComputeState( + GpuContext context, + GpuChannel channel, + DeviceStateWithShadow state, + VtgAsComputeContext vacContext, + ThreedClass engine, + ShaderAsCompute vertexAsCompute, + ShaderAsCompute geometryAsCompute, + IProgram vertexPassthroughProgram, + PrimitiveTopology topology, + int count, + int instanceCount, + int firstIndex, + int firstVertex, + int firstInstance, + bool indexed) + { + _context = context; + _channel = channel; + _state = state; + _vacContext = vacContext; + _engine = engine; + _vertexAsCompute = vertexAsCompute; + _geometryAsCompute = geometryAsCompute; + _vertexPassthroughProgram = vertexPassthroughProgram; + _topology = topology; + _count = count; + _instanceCount = instanceCount; + _firstIndex = firstIndex; + _firstVertex = firstVertex; + _firstInstance = firstInstance; + _indexed = indexed; + + int vertexDataSize = vertexAsCompute.Reservations.OutputSizeInBytesPerInvocation * count * instanceCount; + + (_vertexDataOffset, _vertexDataSize) = _vacContext.GetVertexDataBuffer(vertexDataSize); + + if (geometryAsCompute != null) + { + int totalPrimitivesCount = VtgAsComputeContext.GetPrimitivesCount(topology, count * instanceCount); + int maxCompleteStrips = GetMaxCompleteStrips(geometryAsCompute.Info.GeometryVerticesPerPrimitive, geometryAsCompute.Info.GeometryMaxOutputVertices); + int totalVerticesCount = totalPrimitivesCount * geometryAsCompute.Info.GeometryMaxOutputVertices * geometryAsCompute.Info.ThreadsPerInputPrimitive; + int geometryVbDataSize = totalVerticesCount * geometryAsCompute.Reservations.OutputSizeInBytesPerInvocation; + int geometryIbDataCount = totalVerticesCount + totalPrimitivesCount * maxCompleteStrips; + int geometryIbDataSize = geometryIbDataCount * sizeof(uint); + + (_geometryVertexDataOffset, _geometryVertexDataSize) = vacContext.GetGeometryVertexDataBuffer(geometryVbDataSize); + (_geometryIndexDataOffset, _geometryIndexDataSize) = vacContext.GetGeometryIndexDataBuffer(geometryIbDataSize); + + _geometryIndexDataCount = geometryIbDataCount; + } + } + + /// + /// Emulates the vertex stage using compute. + /// + public readonly void RunVertex() + { + _context.Renderer.Pipeline.SetProgram(_vertexAsCompute.HostProgram); + + int primitivesCount = VtgAsComputeContext.GetPrimitivesCount(_topology, _count); + + _vacContext.VertexInfoBufferUpdater.SetVertexCounts(_count, _instanceCount, _firstVertex, _firstInstance); + _vacContext.VertexInfoBufferUpdater.SetGeometryCounts(primitivesCount); + + for (int index = 0; index < Constants.TotalVertexAttribs; index++) + { + var vertexAttrib = _state.State.VertexAttribState[index]; + + if (!FormatTable.TryGetSingleComponentAttribFormat(vertexAttrib.UnpackFormat(), out Format format, out int componentsCount)) + { + Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}."); + + format = vertexAttrib.UnpackType() switch + { + VertexAttribType.Sint => Format.R32Sint, + VertexAttribType.Uint => Format.R32Uint, + _ => Format.R32Float + }; + + componentsCount = 4; + } + + if (vertexAttrib.UnpackIsConstant()) + { + _vacContext.VertexInfoBufferUpdater.SetVertexStride(index, 0, componentsCount); + _vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, 0, 0); + SetDummyBufferTexture(_vertexAsCompute.Reservations, index, format); + continue; + } + + int bufferIndex = vertexAttrib.UnpackBufferIndex(); + + GpuVa endAddress = _state.State.VertexBufferEndAddress[bufferIndex]; + var vertexBuffer = _state.State.VertexBufferState[bufferIndex]; + bool instanced = _state.State.VertexBufferInstanced[bufferIndex]; + + ulong address = vertexBuffer.Address.Pack(); + + if (!vertexBuffer.UnpackEnable() || !_channel.MemoryManager.IsMapped(address)) + { + _vacContext.VertexInfoBufferUpdater.SetVertexStride(index, 0, componentsCount); + _vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, 0, 0); + SetDummyBufferTexture(_vertexAsCompute.Reservations, index, format); + continue; + } + + int vbStride = vertexBuffer.UnpackStride(); + ulong vbSize = GetVertexBufferSize(address, endAddress.Pack(), vbStride, _indexed, instanced, _firstVertex, _count); + + ulong oldVbSize = vbSize; + + ulong attributeOffset = (ulong)vertexAttrib.UnpackOffset(); + int componentSize = format.GetScalarSize(); + + address += attributeOffset; + + ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1); + + vbSize = Align(vbSize - attributeOffset + misalign, componentSize); + + SetBufferTexture(_vertexAsCompute.Reservations, index, format, address - misalign, vbSize); + + _vacContext.VertexInfoBufferUpdater.SetVertexStride(index, vbStride / componentSize, componentsCount); + _vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, (int)misalign / componentSize, instanced ? vertexBuffer.Divisor : 0); + } + + if (_indexed) + { + SetIndexBufferTexture(_vertexAsCompute.Reservations, _firstIndex, _count, out int ibOffset); + _vacContext.VertexInfoBufferUpdater.SetIndexBufferOffset(ibOffset); + } + else + { + SetSequentialIndexBufferTexture(_vertexAsCompute.Reservations, _count); + _vacContext.VertexInfoBufferUpdater.SetIndexBufferOffset(0); + } + + int vertexInfoBinding = _vertexAsCompute.Reservations.VertexInfoConstantBufferBinding; + BufferRange vertexInfoRange = new(_vacContext.VertexInfoBufferUpdater.Handle, 0, VertexInfoBuffer.RequiredSize); + _context.Renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(vertexInfoBinding, vertexInfoRange) }); + + int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding; + BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize); + _context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexDataRange) }); + + _vacContext.VertexInfoBufferUpdater.Commit(); + + _context.Renderer.Pipeline.DispatchCompute( + BitUtils.DivRoundUp(_count, ComputeLocalSize), + BitUtils.DivRoundUp(_instanceCount, ComputeLocalSize), + 1); + } + + /// + /// Emulates the geometry stage using compute, if it exists, otherwise does nothing. + /// + public readonly void RunGeometry() + { + if (_geometryAsCompute == null) + { + return; + } + + int primitivesCount = VtgAsComputeContext.GetPrimitivesCount(_topology, _count); + + _vacContext.VertexInfoBufferUpdater.SetVertexCounts(_count, _instanceCount, _firstVertex, _firstInstance); + _vacContext.VertexInfoBufferUpdater.SetGeometryCounts(primitivesCount); + _vacContext.VertexInfoBufferUpdater.Commit(); + + int vertexInfoBinding = _vertexAsCompute.Reservations.VertexInfoConstantBufferBinding; + BufferRange vertexInfoRange = new(_vacContext.VertexInfoBufferUpdater.Handle, 0, VertexInfoBuffer.RequiredSize); + _context.Renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(vertexInfoBinding, vertexInfoRange) }); + + int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding; + + // Wait until compute is done. + // TODO: Batch compute and draw operations to avoid pipeline stalls. + _context.Renderer.Pipeline.Barrier(); + _context.Renderer.Pipeline.SetProgram(_geometryAsCompute.HostProgram); + + SetTopologyRemapBufferTexture(_geometryAsCompute.Reservations, _topology, _count); + + int geometryVbBinding = _geometryAsCompute.Reservations.GeometryVertexOutputStorageBufferBinding; + int geometryIbBinding = _geometryAsCompute.Reservations.GeometryIndexOutputStorageBufferBinding; + + BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize); + BufferRange vertexBuffer = _vacContext.GetGeometryVertexDataBufferRange(_geometryVertexDataOffset, _geometryVertexDataSize); + BufferRange indexBuffer = _vacContext.GetGeometryIndexDataBufferRange(_geometryIndexDataOffset, _geometryIndexDataSize); + + _context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] + { + new BufferAssignment(vertexDataBinding, vertexDataRange), + new BufferAssignment(geometryVbBinding, vertexBuffer), + new BufferAssignment(geometryIbBinding, indexBuffer), + }); + + _context.Renderer.Pipeline.DispatchCompute( + BitUtils.DivRoundUp(primitivesCount, ComputeLocalSize), + BitUtils.DivRoundUp(_instanceCount, ComputeLocalSize), + _geometryAsCompute.Info.ThreadsPerInputPrimitive); + } + + /// + /// Performs a draw using the data produced on the vertex, tessellation and geometry stages, + /// if rasterizer discard is disabled. + /// + public readonly void RunFragment() + { + bool tfEnabled = _state.State.TfEnable; + + if (!_state.State.RasterizeEnable && (!tfEnabled || !_context.Capabilities.SupportsTransformFeedback)) + { + // No need to run fragment if rasterizer discard is enabled, + // and we are emulating transform feedback or transform feedback is disabled. + + // Note: We might skip geometry shader here, but right now, this is fine, + // because the only cases that triggers VTG to compute are geometry shader + // being not supported, or the vertex pipeline doing store operations. + // If the geometry shader does not do any store and rasterizer discard is enabled, the geometry shader can be skipped. + // If the geometry shader does have stores, it would have been converted to compute too if stores are not supported. + + return; + } + + int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding; + + _context.Renderer.Pipeline.Barrier(); + + _vacContext.VertexInfoBufferUpdater.SetVertexCounts(_count, _instanceCount, _firstVertex, _firstInstance); + _vacContext.VertexInfoBufferUpdater.Commit(); + + if (_geometryAsCompute != null) + { + BufferRange vertexBuffer = _vacContext.GetGeometryVertexDataBufferRange(_geometryVertexDataOffset, _geometryVertexDataSize); + BufferRange indexBuffer = _vacContext.GetGeometryIndexDataBufferRange(_geometryIndexDataOffset, _geometryIndexDataSize); + + _context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram); + _context.Renderer.Pipeline.SetIndexBuffer(indexBuffer, IndexType.UInt); + _context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexBuffer) }); + + _context.Renderer.Pipeline.SetPrimitiveRestart(true, -1); + _context.Renderer.Pipeline.SetPrimitiveTopology(GetGeometryOutputTopology(_geometryAsCompute.Info.GeometryVerticesPerPrimitive)); + + _context.Renderer.Pipeline.DrawIndexed(_geometryIndexDataCount, 1, 0, 0, 0); + + _engine.ForceStateDirtyByIndex(StateUpdater.IndexBufferStateIndex); + _engine.ForceStateDirtyByIndex(StateUpdater.PrimitiveRestartStateIndex); + } + else + { + BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize); + + _context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram); + _context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexDataRange) }); + _context.Renderer.Pipeline.Draw(_count, _instanceCount, 0, 0); + } + } + + /// + /// Gets a strip primitive topology from the vertices per primitive count. + /// + /// Vertices per primitive count + /// Primitive topology + private static PrimitiveTopology GetGeometryOutputTopology(int verticesPerPrimitive) + { + return verticesPerPrimitive switch + { + 3 => PrimitiveTopology.TriangleStrip, + 2 => PrimitiveTopology.LineStrip, + _ => PrimitiveTopology.Points, + }; + } + + /// + /// Gets the maximum number of complete primitive strips for a vertex count. + /// + /// Vertices per primitive count + /// Maximum geometry shader output vertices count + /// Maximum number of complete primitive strips + private static int GetMaxCompleteStrips(int verticesPerPrimitive, int maxOutputVertices) + { + return maxOutputVertices / verticesPerPrimitive; + } + + /// + /// Binds a dummy buffer as vertex buffer into a buffer texture. + /// + /// Shader resource binding reservations + /// Buffer texture index + /// Buffer texture format + private readonly void SetDummyBufferTexture(ResourceReservations reservations, int index, Format format) + { + ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format); + bufferTexture.SetStorage(_vacContext.GetDummyBufferRange()); + + _context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.GetVertexBufferTextureBinding(index), bufferTexture, null); + } + + /// + /// Binds a vertex buffer into a buffer texture. + /// + /// Shader resource binding reservations + /// Buffer texture index + /// Buffer texture format + /// Address of the vertex buffer + /// Size of the buffer in bytes + private readonly void SetBufferTexture(ResourceReservations reservations, int index, Format format, ulong address, ulong size) + { + var memoryManager = _channel.MemoryManager; + + address = memoryManager.Translate(address); + BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(address, size); + + ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format); + bufferTexture.SetStorage(range); + + _context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.GetVertexBufferTextureBinding(index), bufferTexture, null); + } + + /// + /// Binds the index buffer into a buffer texture. + /// + /// Shader resource binding reservations + /// First index of the index buffer + /// Index count + /// Offset that should be added when accessing the buffer texture on the shader + private readonly void SetIndexBufferTexture(ResourceReservations reservations, int firstIndex, int count, out int misalignedOffset) + { + ulong address = _state.State.IndexBufferState.Address.Pack(); + ulong indexOffset = (ulong)firstIndex; + ulong size = (ulong)count; + + int shift = 0; + Format format = Format.R8Uint; + + switch (_state.State.IndexBufferState.Type) + { + case IndexType.UShort: + shift = 1; + format = Format.R16Uint; + break; + case IndexType.UInt: + shift = 2; + format = Format.R32Uint; + break; + } + + indexOffset <<= shift; + size <<= shift; + + var memoryManager = _channel.MemoryManager; + + address = memoryManager.Translate(address + indexOffset); + ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1); + BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(address - misalign, size + misalign); + misalignedOffset = (int)misalign >> shift; + + SetIndexBufferTexture(reservations, range, format); + } + + /// + /// Sets the host buffer texture for the index buffer. + /// + /// Shader resource binding reservations + /// Index buffer range + /// Index buffer format + private readonly void SetIndexBufferTexture(ResourceReservations reservations, BufferRange range, Format format) + { + ITexture bufferTexture = _vacContext.EnsureBufferTexture(0, format); + bufferTexture.SetStorage(range); + + _context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.IndexBufferTextureBinding, bufferTexture, null); + } + + /// + /// Sets the host buffer texture for the topology remap buffer. + /// + /// Shader resource binding reservations + /// Input topology + /// Input vertex count + private readonly void SetTopologyRemapBufferTexture(ResourceReservations reservations, PrimitiveTopology topology, int count) + { + ITexture bufferTexture = _vacContext.EnsureBufferTexture(1, Format.R32Uint); + bufferTexture.SetStorage(_vacContext.GetOrCreateTopologyRemapBuffer(topology, count)); + + _context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.TopologyRemapBufferTextureBinding, bufferTexture, null); + } + + /// + /// Sets the host buffer texture to a generated sequential index buffer. + /// + /// Shader resource binding reservations + /// Vertex count + private readonly void SetSequentialIndexBufferTexture(ResourceReservations reservations, int count) + { + BufferHandle sequentialIndexBuffer = _vacContext.GetSequentialIndexBuffer(count); + + ITexture bufferTexture = _vacContext.EnsureBufferTexture(0, Format.R32Uint); + bufferTexture.SetStorage(new BufferRange(sequentialIndexBuffer, 0, count * sizeof(uint))); + + _context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.IndexBufferTextureBinding, bufferTexture, null); + } + + /// + /// Gets the size of a vertex buffer based on the current 3D engine state. + /// + /// Vertex buffer address + /// Vertex buffer end address (exclusive) + /// Vertex buffer stride + /// Whether the draw is indexed + /// Whether the draw is instanced + /// First vertex index + /// Vertex count + /// Size of the vertex buffer, in bytes + private readonly ulong GetVertexBufferSize(ulong vbAddress, ulong vbEndAddress, int vbStride, bool indexed, bool instanced, int firstVertex, int vertexCount) + { + IndexType indexType = _state.State.IndexBufferState.Type; + bool indexTypeSmall = indexType == IndexType.UByte || indexType == IndexType.UShort; + ulong vbSize = vbEndAddress - vbAddress + 1; + ulong size; + + if (indexed || vbStride == 0 || instanced) + { + // This size may be (much) larger than the real vertex buffer size. + // Avoid calculating it this way, unless we don't have any other option. + + size = vbSize; + + if (vbStride > 0 && indexTypeSmall && indexed && !instanced) + { + // If the index type is a small integer type, then we might be still able + // to reduce the vertex buffer size based on the maximum possible index value. + + ulong maxVertexBufferSize = indexType == IndexType.UByte ? 0x100UL : 0x10000UL; + + maxVertexBufferSize += _state.State.FirstVertex; + maxVertexBufferSize *= (uint)vbStride; + + size = Math.Min(size, maxVertexBufferSize); + } + } + else + { + // For non-indexed draws, we can guess the size from the vertex count + // and stride. + + int firstInstance = (int)_state.State.FirstInstance; + + size = Math.Min(vbSize, (ulong)((firstInstance + firstVertex + vertexCount) * vbStride)); + } + + return size; + } + + /// + /// Aligns a size to a given alignment value. + /// + /// Size + /// Alignment + /// Aligned size + private static ulong Align(ulong size, int alignment) + { + ulong align = (ulong)alignment; + + size += align - 1; + + size /= align; + size *= align; + + return size; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs index d7ee24b19..18e7ac003 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs @@ -1,4 +1,5 @@ using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw; using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Memory; using System; @@ -8,7 +9,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// /// Draw manager. /// - class DrawManager + class DrawManager : IDisposable { // Since we don't know the index buffer size for indirect draws, // we must assume a minimum and maximum size and use that for buffer data update purposes. @@ -20,6 +21,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed private readonly DeviceStateWithShadow _state; private readonly DrawState _drawState; private readonly SpecializationStateUpdater _currentSpecState; + private readonly VtgAsCompute _vtgAsCompute; private bool _topologySet; private bool _instancedDrawPending; @@ -53,6 +55,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed _state = state; _drawState = drawState; _currentSpecState = spec; + _vtgAsCompute = new(context, channel, state); } /// @@ -127,7 +130,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed { if (renderEnable == ConditionalRenderEnabled.False) { - PerformDeferredDraws(); + PerformDeferredDraws(engine); } _drawState.DrawIndexed = false; @@ -190,13 +193,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed _channel.BufferManager.SetIndexBuffer(br, IndexType.UInt); - _context.Renderer.Pipeline.DrawIndexed(inlineIndexCount, 1, firstIndex, firstVertex, firstInstance); + DrawImpl(engine, inlineIndexCount, 1, firstIndex, firstVertex, firstInstance, indexed: true); } else if (_drawState.DrawIndexed) { int firstVertex = (int)_state.State.FirstVertex; - _context.Renderer.Pipeline.DrawIndexed(indexCount, 1, firstIndex, firstVertex, firstInstance); + DrawImpl(engine, indexCount, 1, firstIndex, firstVertex, firstInstance, indexed: true); } else { @@ -204,7 +207,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed var drawState = _state.State.VertexBufferDrawState; #pragma warning restore IDE0059 - _context.Renderer.Pipeline.Draw(drawVertexCount, 1, drawFirstVertex, firstInstance); + DrawImpl(engine, drawVertexCount, 1, 0, drawFirstVertex, firstInstance, indexed: false); } _drawState.DrawIndexed = false; @@ -219,24 +222,26 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// Starts draw. /// This sets primitive type and instanced draw parameters. /// + /// 3D engine where this method is being called /// Method call argument - public void DrawBegin(int argument) + public void DrawBegin(ThreedClass engine, int argument) { bool incrementInstance = (argument & (1 << 26)) != 0; bool resetInstance = (argument & (1 << 27)) == 0; PrimitiveType type = (PrimitiveType)(argument & 0xffff); - DrawBegin(incrementInstance, resetInstance, type); + DrawBegin(engine, incrementInstance, resetInstance, type); } /// /// Starts draw. /// This sets primitive type and instanced draw parameters. /// + /// 3D engine where this method is being called /// Indicates if the current instance should be incremented /// Indicates if the current instance should be set to zero /// Primitive type - private void DrawBegin(bool incrementInstance, bool resetInstance, PrimitiveType primitiveType) + private void DrawBegin(ThreedClass engine, bool incrementInstance, bool resetInstance, PrimitiveType primitiveType) { if (incrementInstance) { @@ -244,7 +249,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed } else if (resetInstance) { - PerformDeferredDraws(); + PerformDeferredDraws(engine); _instanceIndex = 0; } @@ -364,7 +369,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// True to increment the current instance value, false otherwise private void DrawIndexBufferBeginEndInstance(ThreedClass engine, int argument, bool instanced) { - DrawBegin(instanced, !instanced, (PrimitiveType)((argument >> 28) & 0xf)); + DrawBegin(engine, instanced, !instanced, (PrimitiveType)((argument >> 28) & 0xf)); int firstIndex = argument & 0xffff; int indexCount = (argument >> 16) & 0xfff; @@ -409,7 +414,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// True to increment the current instance value, false otherwise private void DrawVertexArrayBeginEndInstance(ThreedClass engine, int argument, bool instanced) { - DrawBegin(instanced, !instanced, (PrimitiveType)((argument >> 28) & 0xf)); + DrawBegin(engine, instanced, !instanced, (PrimitiveType)((argument >> 28) & 0xf)); int firstVertex = argument & 0xffff; int vertexCount = (argument >> 16) & 0xfff; @@ -541,23 +546,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed engine.UpdateState(); - if (instanceCount > 1) - { - // Must be called after UpdateState as it assumes the shader state - // has already been set, and that bindings have been updated already. - - _channel.BufferManager.SetInstancedDrawVertexCount(count); - } + DrawImpl(engine, count, instanceCount, firstIndex, firstVertex, firstInstance, indexed); if (indexed) { - _context.Renderer.Pipeline.DrawIndexed(count, instanceCount, firstIndex, firstVertex, firstInstance); _state.State.FirstVertex = 0; } - else - { - _context.Renderer.Pipeline.Draw(count, instanceCount, firstVertex, firstInstance); - } _state.State.FirstInstance = 0; @@ -569,6 +563,67 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed } } + /// + /// Performs a indexed or non-indexed draw. + /// + /// 3D engine where this method is being called + /// Index count for indexed draws, vertex count for non-indexed draws + /// Instance count + /// First index on the index buffer for indexed draws, ignored for non-indexed draws + /// First vertex on the vertex buffer + /// First instance + /// True if the draw is indexed, false otherwise + private void DrawImpl( + ThreedClass engine, + int count, + int instanceCount, + int firstIndex, + int firstVertex, + int firstInstance, + bool indexed) + { + if (instanceCount > 1) + { + _channel.BufferManager.SetInstancedDrawVertexCount(count); + } + + if (_drawState.VertexAsCompute != null) + { + _vtgAsCompute.DrawAsCompute( + engine, + _drawState.VertexAsCompute, + _drawState.GeometryAsCompute, + _drawState.VertexPassthrough, + _drawState.Topology, + count, + instanceCount, + firstIndex, + firstVertex, + firstInstance, + indexed); + + if (_drawState.GeometryAsCompute != null) + { + // Geometry draws need to change the topology, so we need to set it here again + // if we are going to do a regular draw. + // Would have been better to do that on the callee, but doing it here + // avoids having to pass the draw manager instance. + ForceStateDirty(); + } + } + else + { + if (indexed) + { + _context.Renderer.Pipeline.DrawIndexed(count, instanceCount, firstIndex, firstVertex, firstInstance); + } + else + { + _context.Renderer.Pipeline.Draw(count, instanceCount, firstVertex, firstInstance); + } + } + } + /// /// Performs a indirect draw, with parameters from a GPU buffer. /// @@ -667,43 +722,42 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// Once we detect the last instanced draw, then we perform the host instanced draw, /// with the accumulated instance count. /// - public void PerformDeferredDraws() + /// 3D engine where this method is being called + public void PerformDeferredDraws(ThreedClass engine) { // Perform any pending instanced draw. if (_instancedDrawPending) { _instancedDrawPending = false; + int instanceCount = _instanceIndex + 1; + int firstInstance = _instancedFirstInstance; bool indexedInline = _instancedIndexedInline; if (_instancedIndexed || indexedInline) { + int indexCount = _instancedIndexCount; + if (indexedInline) { int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(_context.Renderer); BufferRange br = new(_drawState.IbStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4); _channel.BufferManager.SetIndexBuffer(br, IndexType.UInt); + indexCount = inlineIndexCount; } - _channel.BufferManager.SetInstancedDrawVertexCount(_instancedIndexCount); + int firstIndex = _instancedFirstIndex; + int firstVertex = _instancedFirstVertex; - _context.Renderer.Pipeline.DrawIndexed( - _instancedIndexCount, - _instanceIndex + 1, - _instancedFirstIndex, - _instancedFirstVertex, - _instancedFirstInstance); + DrawImpl(engine, indexCount, instanceCount, firstIndex, firstVertex, firstInstance, indexed: true); } else { - _channel.BufferManager.SetInstancedDrawVertexCount(_instancedDrawStateCount); + int vertexCount = _instancedDrawStateCount; + int firstVertex = _instancedDrawStateFirst; - _context.Renderer.Pipeline.Draw( - _instancedDrawStateCount, - _instanceIndex + 1, - _instancedDrawStateFirst, - _instancedFirstInstance); + DrawImpl(engine, vertexCount, instanceCount, 0, firstVertex, firstInstance, indexed: false); } } } @@ -866,5 +920,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed _context.Renderer.Pipeline.EndHostConditionalRendering(); } } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _vtgAsCompute.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } } } diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawState.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawState.cs index 12099aef9..cb43b0025 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawState.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawState.cs @@ -1,4 +1,5 @@ using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Shader; namespace Ryujinx.Graphics.Gpu.Engine.Threed { @@ -61,5 +62,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// Index buffer data streamer for inline index buffer updates, such as those used in legacy OpenGL. /// public IbStreamer IbStreamer = new(); + + /// + /// If the vertex shader is emulated on compute, this should be set to the compute program, otherwise it should be null. + /// + public ShaderAsCompute VertexAsCompute; + + /// + /// If a geometry shader exists and is emulated on compute, this should be set to the compute program, otherwise it should be null. + /// + public ShaderAsCompute GeometryAsCompute; + + /// + /// If the vertex shader is emulated on compute, this should be set to the passthrough vertex program, otherwise it should be null. + /// + public IProgram VertexPassthrough; } } diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs index e0607fbfe..4fbbee3bc 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs @@ -218,11 +218,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed { bool changed = false; ref Array32 attributeTypes = ref _graphics.AttributeTypes; - bool supportsScaledFormats = _context.Capabilities.SupportsScaledVertexFormats; + bool mayConvertVtgToCompute = ShaderCache.MayConvertVtgToCompute(ref _context.Capabilities); + bool supportsScaledFormats = _context.Capabilities.SupportsScaledVertexFormats && !mayConvertVtgToCompute; for (int location = 0; location < state.Length; location++) { VertexAttribType type = state[location].UnpackType(); + VertexAttribSize size = state[location].UnpackSize(); AttributeType value; @@ -247,6 +249,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed }; } + if (mayConvertVtgToCompute && (size == VertexAttribSize.Rgb10A2 || size == VertexAttribSize.Rg11B10)) + { + value |= AttributeType.Packed; + + if (type == VertexAttribType.Snorm || + type == VertexAttribType.Sint || + type == VertexAttribType.Sscaled) + { + value |= AttributeType.PackedRgb10A2Signed; + } + } + if (attributeTypes[location] != value) { attributeTypes[location] = value; diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs index 1f919d9b2..48a497b59 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -20,6 +20,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed public const int RasterizerStateIndex = 15; public const int ScissorStateIndex = 16; public const int VertexBufferStateIndex = 0; + public const int IndexBufferStateIndex = 23; public const int PrimitiveRestartStateIndex = 12; public const int RenderTargetStateIndex = 27; @@ -290,7 +291,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed // of the shader for the new state. if (_shaderSpecState != null && _currentSpecState.HasChanged()) { - if (!_shaderSpecState.MatchesGraphics(_channel, ref _currentSpecState.GetPoolState(), ref _currentSpecState.GetGraphicsState(), _vsUsesDrawParameters, false)) + if (!_shaderSpecState.MatchesGraphics( + _channel, + ref _currentSpecState.GetPoolState(), + ref _currentSpecState.GetGraphicsState(), + _drawState.VertexAsCompute != null, + _vsUsesDrawParameters, + checkTextures: false)) { // Shader must be reloaded. _vtgWritesRtLayer should not change. UpdateShaderState(); @@ -1453,6 +1460,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed _fsReadsFragCoord = false; } + if (gs.VertexAsCompute != null) + { + _drawState.VertexAsCompute = gs.VertexAsCompute; + _drawState.GeometryAsCompute = gs.GeometryAsCompute; + _drawState.VertexPassthrough = gs.HostProgram; + } + else + { + _drawState.VertexAsCompute = null; + _drawState.GeometryAsCompute = null; + _drawState.VertexPassthrough = null; + } + _context.Renderer.Pipeline.SetProgram(gs.HostProgram); } @@ -1540,5 +1560,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed { _updateTracker.ForceDirty(ShaderStateIndex); } + + /// + /// Forces a register group as dirty, by index. + /// + /// Index of the group to be dirtied + public void ForceDirty(int groupIndex) + { + _updateTracker.ForceDirty(groupIndex); + } } } diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs index 1f6628909..7bc2970fe 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs @@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// /// Represents a 3D engine class. /// - class ThreedClass : IDeviceState + class ThreedClass : IDeviceState, IDisposable { private readonly GpuContext _context; private readonly GPFifoClass _fifoClass; @@ -178,6 +178,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed _stateUpdater.SetDirty(offset); } + /// + /// Marks the specified register range for a group index as dirty, forcing the associated state to update on the next draw. + /// + /// Index of the group to dirty + public void ForceStateDirtyByIndex(int groupIndex) + { + _stateUpdater.ForceDirty(groupIndex); + } + /// /// Forces the shaders to be rebound on the next draw. /// @@ -207,7 +216,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// public void PerformDeferredDraws() { - _drawManager.PerformDeferredDraws(); + _drawManager.PerformDeferredDraws(this); } /// @@ -402,7 +411,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// Method call argument private void DrawBegin(int argument) { - _drawManager.DrawBegin(argument); + _drawManager.DrawBegin(this, argument); } /// @@ -617,5 +626,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed { _drawManager.Clear(this, argument, layerCount); } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _drawManager.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } } } diff --git a/src/Ryujinx.Graphics.Gpu/GpuChannel.cs b/src/Ryujinx.Graphics.Gpu/GpuChannel.cs index 8fe643815..d70c9645e 100644 --- a/src/Ryujinx.Graphics.Gpu/GpuChannel.cs +++ b/src/Ryujinx.Graphics.Gpu/GpuChannel.cs @@ -135,6 +135,7 @@ namespace Ryujinx.Graphics.Gpu /// private void Destroy() { + _processor.Dispose(); TextureManager.Dispose(); var oldMemoryManager = Interlocked.Exchange(ref _memoryManager, null); diff --git a/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs b/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs index fcc6b8cfc..1b517e63f 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs @@ -557,6 +557,91 @@ namespace Ryujinx.Graphics.Gpu.Image }; #pragma warning restore IDE0055 + // Note: Some of those formats have been changed and requires conversion on the shader, + // as GPUs don't support them when used as buffer texture format. + private static readonly Dictionary _singleComponentAttribFormats = new() + { + { VertexAttributeFormat.R8Unorm, (Format.R8Unorm, 1) }, + { VertexAttributeFormat.R8Snorm, (Format.R8Snorm, 1) }, + { VertexAttributeFormat.R8Uint, (Format.R8Uint, 1) }, + { VertexAttributeFormat.R8Sint, (Format.R8Sint, 1) }, + { VertexAttributeFormat.R16Float, (Format.R16Float, 1) }, + { VertexAttributeFormat.R16Unorm, (Format.R16Unorm, 1) }, + { VertexAttributeFormat.R16Snorm, (Format.R16Snorm, 1) }, + { VertexAttributeFormat.R16Uint, (Format.R16Uint, 1) }, + { VertexAttributeFormat.R16Sint, (Format.R16Sint, 1) }, + { VertexAttributeFormat.R32Float, (Format.R32Float, 1) }, + { VertexAttributeFormat.R32Uint, (Format.R32Uint, 1) }, + { VertexAttributeFormat.R32Sint, (Format.R32Sint, 1) }, + { VertexAttributeFormat.R8G8Unorm, (Format.R8Unorm, 2) }, + { VertexAttributeFormat.R8G8Snorm, (Format.R8Snorm, 2) }, + { VertexAttributeFormat.R8G8Uint, (Format.R8Uint, 2) }, + { VertexAttributeFormat.R8G8Sint, (Format.R8Sint, 2) }, + { VertexAttributeFormat.R16G16Float, (Format.R16Float, 2) }, + { VertexAttributeFormat.R16G16Unorm, (Format.R16Unorm, 2) }, + { VertexAttributeFormat.R16G16Snorm, (Format.R16Snorm, 2) }, + { VertexAttributeFormat.R16G16Uint, (Format.R16Uint, 2) }, + { VertexAttributeFormat.R16G16Sint, (Format.R16Sint, 2) }, + { VertexAttributeFormat.R32G32Float, (Format.R32Float, 2) }, + { VertexAttributeFormat.R32G32Uint, (Format.R32Uint, 2) }, + { VertexAttributeFormat.R32G32Sint, (Format.R32Sint, 2) }, + { VertexAttributeFormat.R8G8B8Unorm, (Format.R8Unorm, 3) }, + { VertexAttributeFormat.R8G8B8Snorm, (Format.R8Snorm, 3) }, + { VertexAttributeFormat.R8G8B8Uint, (Format.R8Uint, 3) }, + { VertexAttributeFormat.R8G8B8Sint, (Format.R8Sint, 3) }, + { VertexAttributeFormat.R16G16B16Float, (Format.R16Float, 3) }, + { VertexAttributeFormat.R16G16B16Unorm, (Format.R16Unorm, 3) }, + { VertexAttributeFormat.R16G16B16Snorm, (Format.R16Snorm, 3) }, + { VertexAttributeFormat.R16G16B16Uint, (Format.R16Uint, 3) }, + { VertexAttributeFormat.R16G16B16Sint, (Format.R16Sint, 3) }, + { VertexAttributeFormat.R32G32B32Float, (Format.R32Float, 3) }, + { VertexAttributeFormat.R32G32B32Uint, (Format.R32Uint, 3) }, + { VertexAttributeFormat.R32G32B32Sint, (Format.R32Sint, 3) }, + { VertexAttributeFormat.R8G8B8A8Unorm, (Format.R8Unorm, 4) }, + { VertexAttributeFormat.R8G8B8A8Snorm, (Format.R8Snorm, 4) }, + { VertexAttributeFormat.R8G8B8A8Uint, (Format.R8Uint, 4) }, + { VertexAttributeFormat.R8G8B8A8Sint, (Format.R8Sint, 4) }, + { VertexAttributeFormat.R16G16B16A16Float, (Format.R16Float, 4) }, + { VertexAttributeFormat.R16G16B16A16Unorm, (Format.R16Unorm, 4) }, + { VertexAttributeFormat.R16G16B16A16Snorm, (Format.R16Snorm, 4) }, + { VertexAttributeFormat.R16G16B16A16Uint, (Format.R16Uint, 4) }, + { VertexAttributeFormat.R16G16B16A16Sint, (Format.R16Sint, 4) }, + { VertexAttributeFormat.R32G32B32A32Float, (Format.R32Float, 4) }, + { VertexAttributeFormat.R32G32B32A32Uint, (Format.R32Uint, 4) }, + { VertexAttributeFormat.R32G32B32A32Sint, (Format.R32Sint, 4) }, + { VertexAttributeFormat.A2B10G10R10Unorm, (Format.R10G10B10A2Unorm, 4) }, + { VertexAttributeFormat.A2B10G10R10Uint, (Format.R10G10B10A2Uint, 4) }, + { VertexAttributeFormat.B10G11R11Float, (Format.R11G11B10Float, 3) }, + { VertexAttributeFormat.R8Uscaled, (Format.R8Uint, 1) }, // Uscaled -> Uint + { VertexAttributeFormat.R8Sscaled, (Format.R8Sint, 1) }, // Sscaled -> Sint + { VertexAttributeFormat.R16Uscaled, (Format.R16Uint, 1) }, // Uscaled -> Uint + { VertexAttributeFormat.R16Sscaled, (Format.R16Sint, 1) }, // Sscaled -> Sint + { VertexAttributeFormat.R32Uscaled, (Format.R32Uint, 1) }, // Uscaled -> Uint + { VertexAttributeFormat.R32Sscaled, (Format.R32Sint, 1) }, // Sscaled -> Sint + { VertexAttributeFormat.R8G8Uscaled, (Format.R8Uint, 2) }, // Uscaled -> Uint + { VertexAttributeFormat.R8G8Sscaled, (Format.R8Sint, 2) }, // Sscaled -> Sint + { VertexAttributeFormat.R16G16Uscaled, (Format.R16Uint, 2) }, // Uscaled -> Uint + { VertexAttributeFormat.R16G16Sscaled, (Format.R16Sint, 2) }, // Sscaled -> Sint + { VertexAttributeFormat.R32G32Uscaled, (Format.R32Uint, 2) }, // Uscaled -> Uint + { VertexAttributeFormat.R32G32Sscaled, (Format.R32Sint, 2) }, // Sscaled -> Sint + { VertexAttributeFormat.R8G8B8Uscaled, (Format.R8Uint, 3) }, // Uscaled -> Uint + { VertexAttributeFormat.R8G8B8Sscaled, (Format.R8Sint, 3) }, // Sscaled -> Sint + { VertexAttributeFormat.R16G16B16Uscaled, (Format.R16Uint, 3) }, // Uscaled -> Uint + { VertexAttributeFormat.R16G16B16Sscaled, (Format.R16Sint, 3) }, // Sscaled -> Sint + { VertexAttributeFormat.R32G32B32Uscaled, (Format.R32Uint, 3) }, // Uscaled -> Uint + { VertexAttributeFormat.R32G32B32Sscaled, (Format.R32Sint , 3) }, // Sscaled -> Sint + { VertexAttributeFormat.R8G8B8A8Uscaled, (Format.R8Uint, 4) }, // Uscaled -> Uint + { VertexAttributeFormat.R8G8B8A8Sscaled, (Format.R8Sint, 4) }, // Sscaled -> Sint + { VertexAttributeFormat.R16G16B16A16Uscaled, (Format.R16Uint, 4) }, // Uscaled -> Uint + { VertexAttributeFormat.R16G16B16A16Sscaled, (Format.R16Sint, 4) }, // Sscaled -> Sint + { VertexAttributeFormat.R32G32B32A32Uscaled, (Format.R32Uint, 4) }, // Uscaled -> Uint + { VertexAttributeFormat.R32G32B32A32Sscaled, (Format.R32Sint, 4) }, // Sscaled -> Sint + { VertexAttributeFormat.A2B10G10R10Snorm, (Format.R10G10B10A2Uint, 4) }, // Snorm -> Uint + { VertexAttributeFormat.A2B10G10R10Sint, (Format.R10G10B10A2Uint, 4) }, // Sint -> Uint + { VertexAttributeFormat.A2B10G10R10Uscaled, (Format.R10G10B10A2Uint, 4) }, // Uscaled -> Uint + { VertexAttributeFormat.A2B10G10R10Sscaled, (Format.R10G10B10A2Sint, 4) } // Sscaled -> Sint + }; + /// /// Try getting the texture format from an encoded format integer from the Maxwell texture descriptor. /// @@ -581,5 +666,22 @@ namespace Ryujinx.Graphics.Gpu.Image { return _attribFormats.TryGetValue((VertexAttributeFormat)encoded, out format); } + + /// + /// Try getting a single component vertex attribute format from an encoded format integer from Maxwell attribute registers. + /// + /// The encoded format integer from the attribute registers + /// The output single component vertex attribute format + /// Number of components that the format has + /// True if the format is valid, false otherwise + public static bool TryGetSingleComponentAttribFormat(uint encoded, out Format format, out int componentsCount) + { + bool result = _singleComponentAttribFormats.TryGetValue((VertexAttributeFormat)encoded, out var tuple); + + format = tuple.Item1; + componentsCount = tuple.Item2; + + return result; + } } } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs index bf4cb5d05..8e9b4b858 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs @@ -6,7 +6,6 @@ using Ryujinx.Graphics.Shader; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace Ryujinx.Graphics.Gpu.Memory { @@ -15,9 +14,6 @@ namespace Ryujinx.Graphics.Gpu.Memory /// class BufferManager { - private const int TfInfoVertexCountOffset = Constants.TotalTransformFeedbackBuffers * sizeof(int); - private const int TfInfoBufferSize = TfInfoVertexCountOffset + sizeof(int); - private readonly GpuContext _context; private readonly GpuChannel _channel; @@ -104,9 +100,6 @@ namespace Ryujinx.Graphics.Gpu.Memory private readonly BuffersPerStage[] _gpStorageBuffers; private readonly BuffersPerStage[] _gpUniformBuffers; - private BufferHandle _tfInfoBuffer; - private readonly int[] _tfInfoData; - private bool _gpStorageBuffersDirty; private bool _gpUniformBuffersDirty; @@ -146,11 +139,6 @@ namespace Ryujinx.Graphics.Gpu.Memory _bufferTextures = new List(); _ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages]; - - if (!context.Capabilities.SupportsTransformFeedback) - { - _tfInfoData = new int[Constants.TotalTransformFeedbackBuffers]; - } } @@ -339,13 +327,10 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Vertex count per instance public void SetInstancedDrawVertexCount(int vertexCount) { - if (!_context.Capabilities.SupportsTransformFeedback && - HasTransformFeedbackOutputs && - _tfInfoBuffer != BufferHandle.Null) + if (!_context.Capabilities.SupportsTransformFeedback && HasTransformFeedbackOutputs) { - Span data = stackalloc byte[sizeof(int)]; - MemoryMarshal.Cast(data)[0] = vertexCount; - _context.Renderer.SetBufferData(_tfInfoBuffer, TfInfoVertexCountOffset, data); + _context.SupportBufferUpdater.SetTfeVertexCount(vertexCount); + _context.SupportBufferUpdater.Commit(); } } @@ -607,17 +592,7 @@ namespace Ryujinx.Graphics.Gpu.Memory } else if (HasTransformFeedbackOutputs) { - Span info = _tfInfoData.AsSpan(); - Span buffers = stackalloc BufferAssignment[Constants.TotalTransformFeedbackBuffers + 1]; - - bool needsDataUpdate = false; - - if (_tfInfoBuffer == BufferHandle.Null) - { - _tfInfoBuffer = _context.Renderer.CreateBuffer(TfInfoBufferSize, BufferAccess.Stream); - } - - buffers[0] = new BufferAssignment(0, new BufferRange(_tfInfoBuffer, 0, TfInfoBufferSize)); + Span buffers = stackalloc BufferAssignment[Constants.TotalTransformFeedbackBuffers]; int alignment = _context.Capabilities.StorageBufferOffsetAlignment; @@ -627,7 +602,7 @@ namespace Ryujinx.Graphics.Gpu.Memory if (tfb.Address == 0) { - buffers[1 + index] = new BufferAssignment(1 + index, BufferRange.Empty); + buffers[index] = new BufferAssignment(index, BufferRange.Empty); } else { @@ -637,22 +612,12 @@ namespace Ryujinx.Graphics.Gpu.Memory int tfeOffset = ((int)tfb.Address & (alignment - 1)) / 4; - if (info[index] != tfeOffset) - { - info[index] = tfeOffset; - needsDataUpdate = true; - } + _context.SupportBufferUpdater.SetTfeOffset(index, tfeOffset); - buffers[1 + index] = new BufferAssignment(1 + index, bufferCache.GetBufferRange(address, size, write: true)); + buffers[index] = new BufferAssignment(index, bufferCache.GetBufferRange(address, size, write: true)); } } - if (needsDataUpdate) - { - Span infoData = MemoryMarshal.Cast(info); - _context.Renderer.SetBufferData(_tfInfoBuffer, 0, infoData); - } - _context.Renderer.Pipeline.SetStorageBuffers(buffers); } } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferUpdater.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferUpdater.cs new file mode 100644 index 000000000..02090c04f --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferUpdater.cs @@ -0,0 +1,123 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using System; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Buffer data updater. + /// + class BufferUpdater : IDisposable + { + private BufferHandle _handle; + + /// + /// Handle of the buffer. + /// + public BufferHandle Handle => _handle; + + private readonly IRenderer _renderer; + private int _startOffset = -1; + private int _endOffset = -1; + + /// + /// Creates a new instance of the buffer updater. + /// + /// Renderer that the buffer will be used with + public BufferUpdater(IRenderer renderer) + { + _renderer = renderer; + } + + /// + /// Mark a region of the buffer as modified and needing to be sent to the GPU. + /// + /// Start offset of the region in bytes + /// Size of the region in bytes + protected void MarkDirty(int startOffset, int byteSize) + { + int endOffset = startOffset + byteSize; + + if (_startOffset == -1) + { + _startOffset = startOffset; + _endOffset = endOffset; + } + else + { + if (startOffset < _startOffset) + { + _startOffset = startOffset; + } + + if (endOffset > _endOffset) + { + _endOffset = endOffset; + } + } + } + + /// + /// Submits all pending buffer updates to the GPU. + /// + /// All data that should be sent to the GPU. Only the modified regions will be updated + /// Optional binding to bind the buffer if a new buffer was created + protected void Commit(ReadOnlySpan data, int binding = -1) + { + if (_startOffset != -1) + { + if (_handle == BufferHandle.Null) + { + _handle = _renderer.CreateBuffer(data.Length, BufferAccess.Stream); + _renderer.Pipeline.ClearBuffer(_handle, 0, data.Length, 0); + + if (binding >= 0) + { + var range = new BufferRange(_handle, 0, data.Length); + _renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, range) }); + } + }; + + _renderer.SetBufferData(_handle, _startOffset, data[_startOffset.._endOffset]); + + _startOffset = -1; + _endOffset = -1; + } + } + + /// + /// Gets a reference to a given element of a vector. + /// + /// Vector to get the element reference from + /// Element index + /// Reference to the specified element + protected static ref T GetElementRef(ref Vector4 vector, int elementIndex) + { + switch (elementIndex) + { + case 0: + return ref vector.X; + case 1: + return ref vector.Y; + case 2: + return ref vector.Z; + case 3: + return ref vector.W; + default: + throw new ArgumentOutOfRangeException(nameof(elementIndex)); + } + } + + /// + /// Destroys the buffer. + /// + public void Dispose() + { + if (_handle != BufferHandle.Null) + { + _renderer.DeleteBuffer(_handle); + _handle = BufferHandle.Null; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/SupportBufferUpdater.cs b/src/Ryujinx.Graphics.Gpu/Memory/SupportBufferUpdater.cs index c1e91c54b..fb141db42 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/SupportBufferUpdater.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/SupportBufferUpdater.cs @@ -9,56 +9,21 @@ namespace Ryujinx.Graphics.Gpu.Memory /// /// Support buffer data updater. /// - class SupportBufferUpdater : IDisposable + class SupportBufferUpdater : BufferUpdater { private SupportBuffer _data; - private BufferHandle _handle; - - private readonly IRenderer _renderer; - private int _startOffset = -1; - private int _endOffset = -1; /// /// Creates a new instance of the support buffer updater. /// /// Renderer that the support buffer will be used with - public SupportBufferUpdater(IRenderer renderer) + public SupportBufferUpdater(IRenderer renderer) : base(renderer) { - _renderer = renderer; - var defaultScale = new Vector4 { X = 1f, Y = 0f, Z = 0f, W = 0f }; _data.RenderScale.AsSpan().Fill(defaultScale); DirtyRenderScale(0, SupportBuffer.RenderScaleMaxCount); } - /// - /// Mark a region of the support buffer as modified and needing to be sent to the GPU. - /// - /// Start offset of the region in bytes - /// Size of the region in bytes - private void MarkDirty(int startOffset, int byteSize) - { - int endOffset = startOffset + byteSize; - - if (_startOffset == -1) - { - _startOffset = startOffset; - _endOffset = endOffset; - } - else - { - if (startOffset < _startOffset) - { - _startOffset = startOffset; - } - - if (endOffset > _endOffset) - { - _endOffset = endOffset; - } - } - } - /// /// Marks the fragment render scale count as being modified. /// @@ -220,40 +185,40 @@ namespace Ryujinx.Graphics.Gpu.Memory } /// - /// Submits all pending buffer updates to the GPU. + /// Sets offset for the misaligned portion of a transform feedback buffer, and the buffer size, for transform feedback emulation. /// - public void Commit() + /// Index of the transform feedback buffer + /// Misaligned offset of the buffer + public void SetTfeOffset(int bufferIndex, int offset) { - if (_startOffset != -1) + ref int currentOffset = ref GetElementRef(ref _data.TfeOffset, bufferIndex); + + if (currentOffset != offset) { - if (_handle == BufferHandle.Null) - { - _handle = _renderer.CreateBuffer(SupportBuffer.RequiredSize, BufferAccess.Stream); - _renderer.Pipeline.ClearBuffer(_handle, 0, SupportBuffer.RequiredSize, 0); - - var range = new BufferRange(_handle, 0, SupportBuffer.RequiredSize); - _renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, range) }); - } - - ReadOnlySpan data = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref _data, 1)); - - _renderer.SetBufferData(_handle, _startOffset, data[_startOffset.._endOffset]); - - _startOffset = -1; - _endOffset = -1; + currentOffset = offset; + MarkDirty(SupportBuffer.TfeOffsetOffset + bufferIndex * sizeof(int), sizeof(int)); } } /// - /// Destroys the support buffer. + /// Sets the vertex count used for transform feedback emulation with instanced draws. /// - public void Dispose() + /// Vertex count of the instanced draw + public void SetTfeVertexCount(int vertexCount) { - if (_handle != BufferHandle.Null) + if (_data.TfeVertexCount.X != vertexCount) { - _renderer.DeleteBuffer(_handle); - _handle = BufferHandle.Null; + _data.TfeVertexCount.X = vertexCount; + MarkDirty(SupportBuffer.TfeVertexCountOffset, sizeof(int)); } } + + /// + /// Submits all pending buffer updates to the GPU. + /// + public void Commit() + { + Commit(MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref _data, 1)), SupportBuffer.Binding); + } } } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs index ff9c39a19..600c8a985 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs @@ -14,6 +14,16 @@ namespace Ryujinx.Graphics.Gpu.Shader /// public IProgram HostProgram { get; } + /// + /// Optional vertex shader converted to compute. + /// + public ShaderAsCompute VertexAsCompute { get; } + + /// + /// Optional geometry shader converted to compute. + /// + public ShaderAsCompute GeometryAsCompute { get; } + /// /// GPU state used to create this version of the shader. /// @@ -45,12 +55,25 @@ namespace Ryujinx.Graphics.Gpu.Shader Bindings = new CachedShaderBindings(shaders.Length == 1, shaders); } + public CachedShaderProgram( + IProgram hostProgram, + ShaderAsCompute vertexAsCompute, + ShaderAsCompute geometryAsCompute, + ShaderSpecializationState specializationState, + CachedShaderStage[] shaders) : this(hostProgram, specializationState, shaders) + { + VertexAsCompute = vertexAsCompute; + GeometryAsCompute = geometryAsCompute; + } + /// /// Dispose of the host shader resources. /// public void Dispose() { HostProgram.Dispose(); + VertexAsCompute?.HostProgram.Dispose(); + GeometryAsCompute?.HostProgram.Dispose(); } } } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs index 93d293f62..de6432bc1 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs @@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache ShaderSpecializationState oldSpecState, ShaderSpecializationState newSpecState, ResourceCounts counts, - int stageIndex) : base(context, counts, stageIndex, oldSpecState.TransformFeedbackDescriptors != null) + int stageIndex) : base(context, counts, stageIndex) { _data = data; _cb1Data = cb1Data; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 08096bd5a..99ef89ed9 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 5609; + private const uint CodeGenVersion = 5551; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; @@ -140,6 +140,21 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// public ShaderStage Stage; + /// + /// Number of vertices that each output primitive has on a geometry shader. + /// + public byte GeometryVerticesPerPrimitive; + + /// + /// Maximum number of vertices that a geometry shader may generate. + /// + public ushort GeometryMaxOutputVertices; + + /// + /// Number of invocations per primitive on tessellation or geometry shaders. + /// + public ushort ThreadsPerInputPrimitive; + /// /// Indicates if the fragment shader accesses the fragment coordinate built-in variable. /// @@ -783,9 +798,10 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache sBuffers, textures, images, - ShaderIdentification.None, - 0, dataInfo.Stage, + dataInfo.GeometryVerticesPerPrimitive, + dataInfo.GeometryMaxOutputVertices, + dataInfo.ThreadsPerInputPrimitive, dataInfo.UsesFragCoord, dataInfo.UsesInstanceId, dataInfo.UsesDrawParameters, @@ -813,6 +829,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache TexturesCount = (ushort)info.Textures.Count, ImagesCount = (ushort)info.Images.Count, Stage = info.Stage, + GeometryVerticesPerPrimitive = (byte)info.GeometryVerticesPerPrimitive, + GeometryMaxOutputVertices = (ushort)info.GeometryMaxOutputVertices, + ThreadsPerInputPrimitive = (ushort)info.ThreadsPerInputPrimitive, UsesFragCoord = info.UsesFragCoord, UsesInstanceId = info.UsesInstanceId, UsesDrawParameters = info.UsesDrawParameters, diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs index 8c2108bfa..153fc4427 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs @@ -595,6 +595,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache ResourceCounts counts = new(); + DiskCacheGpuAccessor[] gpuAccessors = new DiskCacheGpuAccessor[Constants.ShaderStages]; TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1]; TranslatorContext nextStage = null; @@ -626,14 +627,22 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, api, DefaultFlags | TranslationFlags.VertexA, 0); } + gpuAccessors[stageIndex] = gpuAccessor; translatorContexts[stageIndex + 1] = currentStage; nextStage = currentStage; } } - if (!_context.Capabilities.SupportsGeometryShader) + bool hasGeometryShader = translatorContexts[4] != null; + bool vertexHasStore = translatorContexts[1] != null && translatorContexts[1].HasStore; + bool geometryHasStore = hasGeometryShader && translatorContexts[4].HasStore; + bool vertexToCompute = ShouldConvertVertexToCompute(_context, vertexHasStore, geometryHasStore, hasGeometryShader); + + // We don't support caching shader stages that have been converted to compute currently, + // so just eliminate them if they exist in the cache. + if (vertexToCompute) { - ShaderCache.TryRemoveGeometryStage(translatorContexts); + return; } CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length]; @@ -647,6 +656,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache if (currentStage != null) { + gpuAccessors[stageIndex].InitializeReservedCounts(specState.TransformFeedbackDescriptors != null, vertexToCompute); + ShaderProgram program; byte[] guestCode = guestShaders[stageIndex + 1].Value.Code; @@ -701,6 +712,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache ResourceCounts counts = new(); ShaderSpecializationState newSpecState = new(ref specState.ComputeState); DiskCacheGpuAccessor gpuAccessor = new(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0); + gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false); TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, 0); diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs index b4f4a439c..1d84d0e46 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs @@ -25,11 +25,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// GPU channel /// Current GPU state /// Graphics shader stage index (0 = Vertex, 4 = Fragment) - public GpuAccessor( - GpuContext context, - GpuChannel channel, - GpuAccessorState state, - int stageIndex) : base(context, state.ResourceCounts, stageIndex, state.TransformFeedbackDescriptors != null) + public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state, int stageIndex) : base(context, state.ResourceCounts, stageIndex) { _isVulkan = context.Capabilities.Api == TargetApi.Vulkan; _channel = channel; @@ -49,7 +45,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// GPU context /// GPU channel /// Current GPU state - public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context, state.ResourceCounts, 0, false) + public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context, state.ResourceCounts, 0) { _channel = channel; _state = state; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs index 52193940b..9d030cd60 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs @@ -15,8 +15,10 @@ namespace Ryujinx.Graphics.Gpu.Shader private readonly ResourceCounts _resourceCounts; private readonly int _stageIndex; - private readonly int _reservedConstantBuffers; - private readonly int _reservedStorageBuffers; + private int _reservedConstantBuffers; + private int _reservedStorageBuffers; + private int _reservedTextures; + private int _reservedImages; /// /// Creates a new GPU accessor. @@ -24,15 +26,26 @@ namespace Ryujinx.Graphics.Gpu.Shader /// GPU context /// Counter of GPU resources used by the shader /// Index of the shader stage, 0 for compute - /// Indicates if the current graphics shader is used with transform feedback enabled - public GpuAccessorBase(GpuContext context, ResourceCounts resourceCounts, int stageIndex, bool tfEnabled) + public GpuAccessorBase(GpuContext context, ResourceCounts resourceCounts, int stageIndex) { _context = context; _resourceCounts = resourceCounts; _stageIndex = stageIndex; + } - _reservedConstantBuffers = 1; // For the support buffer. - _reservedStorageBuffers = !context.Capabilities.SupportsTransformFeedback && tfEnabled ? 5 : 0; + /// + /// Initializes counts for bindings that will be reserved for emulator use. + /// + /// Indicates if the current graphics shader is used with transform feedback enabled + /// Indicates that the vertex shader will be emulated on a compute shader + public void InitializeReservedCounts(bool tfEnabled, bool vertexAsCompute) + { + ResourceReservationCounts rrc = new(!_context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute); + + _reservedConstantBuffers = rrc.ReservedConstantBuffers; + _reservedStorageBuffers = rrc.ReservedStorageBuffers; + _reservedTextures = rrc.ReservedTextures; + _reservedImages = rrc.ReservedImages; } public int QueryBindingConstantBuffer(int index) @@ -69,6 +82,8 @@ namespace Ryujinx.Graphics.Gpu.Shader public int QueryBindingTexture(int index, bool isBuffer) { + int binding; + if (_context.Capabilities.Api == TargetApi.Vulkan) { if (isBuffer) @@ -76,16 +91,20 @@ namespace Ryujinx.Graphics.Gpu.Shader index += (int)_context.Capabilities.MaximumTexturesPerStage; } - return GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture"); + binding = GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture"); } else { - return _resourceCounts.TexturesCount++; + binding = _resourceCounts.TexturesCount++; } + + return binding + _reservedTextures; } public int QueryBindingImage(int index, bool isBuffer) { + int binding; + if (_context.Capabilities.Api == TargetApi.Vulkan) { if (isBuffer) @@ -93,12 +112,14 @@ namespace Ryujinx.Graphics.Gpu.Shader index += (int)_context.Capabilities.MaximumImagesPerStage; } - return GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image"); + binding = GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image"); } else { - return _resourceCounts.ImagesCount++; + binding = _resourceCounts.ImagesCount++; } + + return binding + _reservedImages; } private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName) diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderAsCompute.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderAsCompute.cs new file mode 100644 index 000000000..71540a13f --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderAsCompute.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + class ShaderAsCompute + { + public IProgram HostProgram { get; } + public ShaderProgramInfo Info { get; } + public ResourceReservations Reservations { get; } + + public ShaderAsCompute(IProgram hostProgram, ShaderProgramInfo info, ResourceReservations reservations) + { + HostProgram = hostProgram; + Info = info; + Reservations = reservations; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index 97d7a7206..38be262a7 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -215,9 +215,10 @@ namespace Ryujinx.Graphics.Gpu.Shader ShaderSpecializationState specState = new(ref computeState); GpuAccessorState gpuAccessorState = new(poolState, computeState, default, specState); GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState); + gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false); TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa); - TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode); + TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode, asCompute: false); ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) }; ShaderInfo info = ShaderInfoBuilder.BuildForCompute(_context, translatedShader.Program.Info); @@ -321,6 +322,7 @@ namespace Ryujinx.Graphics.Gpu.Shader ReadOnlySpan addressesSpan = addresses.AsSpan(); + GpuAccessor[] gpuAccessors = new GpuAccessor[Constants.ShaderStages]; TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1]; TranslatorContext nextStage = null; @@ -345,22 +347,31 @@ namespace Ryujinx.Graphics.Gpu.Shader translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA); } + gpuAccessors[stageIndex] = gpuAccessor; translatorContexts[stageIndex + 1] = currentStage; nextStage = currentStage; } } - if (!_context.Capabilities.SupportsGeometryShader) - { - TryRemoveGeometryStage(translatorContexts); - } + bool hasGeometryShader = translatorContexts[4] != null; + bool vertexHasStore = translatorContexts[1] != null && translatorContexts[1].HasStore; + bool geometryHasStore = hasGeometryShader && translatorContexts[4].HasStore; + bool vertexToCompute = ShouldConvertVertexToCompute(_context, vertexHasStore, geometryHasStore, hasGeometryShader); + bool geometryToCompute = ShouldConvertGeometryToCompute(_context, geometryHasStore); CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1]; List shaderSources = new(); TranslatorContext previousStage = null; + ShaderInfoBuilder infoBuilder = new(_context, transformFeedbackDescriptors != null, vertexToCompute); - ShaderInfoBuilder infoBuilder = new(_context, transformFeedbackDescriptors != null); + if (geometryToCompute && translatorContexts[4] != null) + { + translatorContexts[4].SetVertexOutputMapForGeometryAsCompute(translatorContexts[1]); + } + + ShaderAsCompute vertexAsCompute = null; + ShaderAsCompute geometryAsCompute = null; for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++) { @@ -368,8 +379,12 @@ namespace Ryujinx.Graphics.Gpu.Shader if (currentStage != null) { + gpuAccessors[stageIndex].InitializeReservedCounts(transformFeedbackDescriptors != null, vertexToCompute); + ShaderProgram program; + bool asCompute = (stageIndex == 0 && vertexToCompute) || (stageIndex == 3 && geometryToCompute); + if (stageIndex == 0 && translatorContexts[0] != null) { TranslatedShaderVertexPair translatedShader = TranslateShader( @@ -378,7 +393,8 @@ namespace Ryujinx.Graphics.Gpu.Shader currentStage, translatorContexts[0], cachedGuestCode.VertexACode, - cachedGuestCode.VertexBCode); + cachedGuestCode.VertexBCode, + asCompute); shaders[0] = translatedShader.VertexA; shaders[1] = translatedShader.VertexB; @@ -388,12 +404,31 @@ namespace Ryujinx.Graphics.Gpu.Shader { byte[] code = cachedGuestCode.GetByIndex(stageIndex); - TranslatedShader translatedShader = TranslateShader(_dumper, channel, currentStage, code); + TranslatedShader translatedShader = TranslateShader(_dumper, channel, currentStage, code, asCompute); shaders[stageIndex + 1] = translatedShader.Shader; program = translatedShader.Program; } + if (asCompute) + { + bool tfEnabled = transformFeedbackDescriptors != null; + + if (stageIndex == 0) + { + vertexAsCompute = CreateHostVertexAsComputeProgram(program, currentStage, tfEnabled); + + TranslatorContext lastInVertexPipeline = geometryToCompute ? translatorContexts[4] ?? currentStage : currentStage; + + program = lastInVertexPipeline.GenerateVertexPassthroughForCompute(); + } + else + { + geometryAsCompute = CreateHostVertexAsComputeProgram(program, currentStage, tfEnabled); + program = null; + } + } + if (program != null) { shaderSources.Add(CreateShaderSource(program)); @@ -418,46 +453,81 @@ namespace Ryujinx.Graphics.Gpu.Shader IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info); - gpShaders = new CachedShaderProgram(hostProgram, specState, shaders); + gpShaders = new(hostProgram, vertexAsCompute, geometryAsCompute, specState, shaders); _graphicsShaderCache.Add(gpShaders); - EnqueueProgramToSave(gpShaders, hostProgram, shaderSourcesArray); + + // We don't currently support caching shaders that have been converted to compute. + if (vertexAsCompute == null) + { + EnqueueProgramToSave(gpShaders, hostProgram, shaderSourcesArray); + } + _gpPrograms[addresses] = gpShaders; return gpShaders; } /// - /// Tries to eliminate the geometry stage from the array of translator contexts. + /// Checks if a vertex shader should be converted to a compute shader due to it making use of + /// features that are not supported on the host. /// - /// Array of translator contexts - public static void TryRemoveGeometryStage(TranslatorContext[] translatorContexts) + /// GPU context of the shader + /// Whether the vertex shader has image or storage buffer store operations + /// Whether the geometry shader has image or storage buffer store operations, if one exists + /// Whether a geometry shader exists + /// True if the vertex shader should be converted to compute, false otherwise + public static bool ShouldConvertVertexToCompute(GpuContext context, bool vertexHasStore, bool geometryHasStore, bool hasGeometryShader) { - if (translatorContexts[4] != null) + // If the host does not support store operations on vertex, + // we need to emulate it on a compute shader. + if (!context.Capabilities.SupportsVertexStoreAndAtomics && vertexHasStore) { - // We have a geometry shader, but geometry shaders are not supported. - // Try to eliminate the geometry shader. - - ShaderProgramInfo info = translatorContexts[4].Translate().Info; - - if (info.Identification == ShaderIdentification.GeometryLayerPassthrough) - { - // We managed to identify that this geometry shader is only used to set the output Layer value, - // we can set the Layer on the previous stage instead (usually the vertex stage) and eliminate it. - - for (int i = 3; i >= 1; i--) - { - if (translatorContexts[i] != null) - { - translatorContexts[i].SetGeometryShaderLayerInputAttribute(info.GpLayerInputAttribute); - translatorContexts[i].SetLastInVertexPipeline(); - break; - } - } - - translatorContexts[4] = null; - } + return true; } + + // If any stage after the vertex stage is converted to compute, + // we need to convert vertex to compute too. + return hasGeometryShader && ShouldConvertGeometryToCompute(context, geometryHasStore); + } + + /// + /// Checks if a geometry shader should be converted to a compute shader due to it making use of + /// features that are not supported on the host. + /// + /// GPU context of the shader + /// Whether the geometry shader has image or storage buffer store operations, if one exists + /// True if the geometry shader should be converted to compute, false otherwise + public static bool ShouldConvertGeometryToCompute(GpuContext context, bool geometryHasStore) + { + return (!context.Capabilities.SupportsVertexStoreAndAtomics && geometryHasStore) || + !context.Capabilities.SupportsGeometryShader; + } + + /// + /// Checks if it might be necessary for any vertex, tessellation or geometry shader to be converted to compute, + /// based on the supported host features. + /// + /// Host capabilities + /// True if the possibility of a shader being converted to compute exists, false otherwise + public static bool MayConvertVtgToCompute(ref Capabilities capabilities) + { + return !capabilities.SupportsVertexStoreAndAtomics || !capabilities.SupportsGeometryShader; + } + + /// + /// Creates a compute shader from a vertex, tessellation or geometry shader that has been converted to compute. + /// + /// Shader program + /// Translation context of the shader + /// Whether transform feedback is enabled + /// Compute shader + private ShaderAsCompute CreateHostVertexAsComputeProgram(ShaderProgram program, TranslatorContext context, bool tfEnabled) + { + ShaderSource source = new(program.Code, program.BinaryCode, ShaderStage.Compute, program.Language); + ShaderInfo info = ShaderInfoBuilder.BuildForVertexAsCompute(_context, program.Info, tfEnabled); + + return new(_context.Renderer.CreateProgram(new[] { source }, info), program.Info, context.GetResourceReservations()); } /// @@ -573,9 +643,16 @@ namespace Ryujinx.Graphics.Gpu.Shader } } + bool vertexAsCompute = gpShaders.VertexAsCompute != null; bool usesDrawParameters = gpShaders.Shaders[1]?.Info.UsesDrawParameters ?? false; - return gpShaders.SpecializationState.MatchesGraphics(channel, ref poolState, ref graphicsState, usesDrawParameters, true); + return gpShaders.SpecializationState.MatchesGraphics( + channel, + ref poolState, + ref graphicsState, + vertexAsCompute, + usesDrawParameters, + checkTextures: true); } /// @@ -636,6 +713,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Optional translator context of the shader that should be combined /// Optional Maxwell binary code of the Vertex A shader, if present /// Optional Maxwell binary code of the Vertex B or current stage shader, if present on cache + /// Indicates that the vertex shader should be converted to a compute shader /// Compiled graphics shader code private static TranslatedShaderVertexPair TranslateShader( ShaderDumper dumper, @@ -643,7 +721,8 @@ namespace Ryujinx.Graphics.Gpu.Shader TranslatorContext currentStage, TranslatorContext vertexA, byte[] codeA, - byte[] codeB) + byte[] codeB, + bool asCompute) { ulong cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1); @@ -663,7 +742,7 @@ namespace Ryujinx.Graphics.Gpu.Shader pathsB = dumper.Dump(codeB, compute: false); } - ShaderProgram program = currentStage.Translate(vertexA); + ShaderProgram program = currentStage.Translate(vertexA, asCompute); pathsB.Prepend(program); pathsA.Prepend(program); @@ -681,8 +760,9 @@ namespace Ryujinx.Graphics.Gpu.Shader /// GPU channel using the shader /// Translator context of the stage to be translated /// Optional Maxwell binary code of the current stage shader, if present on cache + /// Indicates that the vertex shader should be converted to a compute shader /// Compiled graphics shader code - private static TranslatedShader TranslateShader(ShaderDumper dumper, GpuChannel channel, TranslatorContext context, byte[] code) + private static TranslatedShader TranslateShader(ShaderDumper dumper, GpuChannel channel, TranslatorContext context, byte[] code, bool asCompute) { var memoryManager = channel.MemoryManager; @@ -694,7 +774,7 @@ namespace Ryujinx.Graphics.Gpu.Shader code ??= memoryManager.GetSpan(context.Address, context.Size).ToArray(); ShaderDumpPaths paths = dumper?.Dump(code, context.Stage == ShaderStage.Compute) ?? default; - ShaderProgram program = context.Translate(); + ShaderProgram program = context.Translate(asCompute); paths.Prepend(program); diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs index af1e1ee3f..bea916a6d 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs @@ -33,6 +33,8 @@ namespace Ryujinx.Graphics.Gpu.Shader private readonly int _reservedConstantBuffers; private readonly int _reservedStorageBuffers; + private readonly int _reservedTextures; + private readonly int _reservedImages; private readonly List[] _resourceDescriptors; private readonly List[] _resourceUsages; @@ -42,7 +44,8 @@ namespace Ryujinx.Graphics.Gpu.Shader /// /// GPU context that owns the shaders that will be added to the builder /// Indicates if the graphics shader is used with transform feedback enabled - public ShaderInfoBuilder(GpuContext context, bool tfEnabled) + /// Indicates that the vertex shader will be emulated on a compute shader + public ShaderInfoBuilder(GpuContext context, bool tfEnabled, bool vertexAsCompute = false) { _context = context; @@ -60,27 +63,34 @@ namespace Ryujinx.Graphics.Gpu.Shader AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1); AddUsage(SupportBufferStages, ResourceType.UniformBuffer, ResourceAccess.Read, UniformSetIndex, 0, 1); - _reservedConstantBuffers = 1; // For the support buffer. + ResourceReservationCounts rrc = new(!context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute); - if (!context.Capabilities.SupportsTransformFeedback && tfEnabled) - { - _reservedStorageBuffers = 5; + _reservedConstantBuffers = rrc.ReservedConstantBuffers; + _reservedStorageBuffers = rrc.ReservedStorageBuffers; + _reservedTextures = rrc.ReservedTextures; + _reservedImages = rrc.ReservedImages; - AddDescriptor(VtgStages, ResourceType.StorageBuffer, StorageSetIndex, 0, 5); - AddUsage(VtgStages, ResourceType.StorageBuffer, ResourceAccess.Read, StorageSetIndex, 0, 1); - AddUsage(VtgStages, ResourceType.StorageBuffer, ResourceAccess.Write, StorageSetIndex, 1, 4); - } - else - { - _reservedStorageBuffers = 0; - } + // TODO: Handle that better? Maybe we should only set the binding that are really needed on each shader. + ResourceStages stages = vertexAsCompute ? ResourceStages.Compute | ResourceStages.Vertex : VtgStages; + + PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, ResourceAccess.Read, UniformSetIndex, 1, rrc.ReservedConstantBuffers - 1); + PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, ResourceAccess.ReadWrite, StorageSetIndex, 0, rrc.ReservedStorageBuffers); + PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, ResourceAccess.Read, TextureSetIndex, 0, rrc.ReservedTextures); + PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, ResourceAccess.ReadWrite, ImageSetIndex, 0, rrc.ReservedImages); + } + + private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, ResourceAccess access, int setIndex, int start, int count) + { + AddDescriptor(stages, type, setIndex, start, count); + AddUsage(stages, type, access, setIndex, start, count); } /// /// Adds information from a given shader stage. /// /// Shader stage information - public void AddStageInfo(ShaderProgramInfo info) + /// True if the shader stage has been converted into a compute shader + public void AddStageInfo(ShaderProgramInfo info, bool vertexAsCompute = false) { if (info.Stage == ShaderStage.Fragment) { @@ -96,7 +106,7 @@ namespace Ryujinx.Graphics.Gpu.Shader _ => 0, }); - ResourceStages stages = info.Stage switch + ResourceStages stages = vertexAsCompute ? ResourceStages.Compute : info.Stage switch { ShaderStage.Compute => ResourceStages.Compute, ShaderStage.Vertex => ResourceStages.Vertex, @@ -114,8 +124,8 @@ namespace Ryujinx.Graphics.Gpu.Shader int uniformBinding = _reservedConstantBuffers + stageIndex * uniformsPerStage; int storageBinding = _reservedStorageBuffers + stageIndex * storagesPerStage; - int textureBinding = stageIndex * texturesPerStage * 2; - int imageBinding = stageIndex * imagesPerStage * 2; + int textureBinding = _reservedTextures + stageIndex * texturesPerStage * 2; + int imageBinding = _reservedImages + stageIndex * imagesPerStage * 2; AddDescriptor(stages, ResourceType.UniformBuffer, UniformSetIndex, uniformBinding, uniformsPerStage); AddDescriptor(stages, ResourceType.StorageBuffer, StorageSetIndex, storageBinding, storagesPerStage); @@ -285,11 +295,28 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Shader information public static ShaderInfo BuildForCompute(GpuContext context, ShaderProgramInfo info, bool fromCache = false) { - ShaderInfoBuilder builder = new(context, tfEnabled: false); + ShaderInfoBuilder builder = new(context, tfEnabled: false, vertexAsCompute: false); builder.AddStageInfo(info); return builder.Build(null, fromCache); } + + /// + /// Builds shader information for a vertex or geometry shader thas was converted to compute shader. + /// + /// GPU context that owns the shader + /// Compute shader information + /// Indicates if the graphics shader is used with transform feedback enabled + /// True if the compute shader comes from a disk cache, false otherwise + /// Shader information + public static ShaderInfo BuildForVertexAsCompute(GpuContext context, ShaderProgramInfo info, bool tfEnabled, bool fromCache = false) + { + ShaderInfoBuilder builder = new(context, tfEnabled, vertexAsCompute: true); + + builder.AddStageInfo(info, vertexAsCompute: true); + + return builder.Build(null, fromCache); + } } } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs index e57e1df1a..3c2f0b9be 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs @@ -35,9 +35,16 @@ namespace Ryujinx.Graphics.Gpu.Shader { foreach (var entry in _entries) { + bool vertexAsCompute = entry.VertexAsCompute != null; bool usesDrawParameters = entry.Shaders[1]?.Info.UsesDrawParameters ?? false; - if (entry.SpecializationState.MatchesGraphics(channel, ref poolState, ref graphicsState, usesDrawParameters, true)) + if (entry.SpecializationState.MatchesGraphics( + channel, + ref poolState, + ref graphicsState, + vertexAsCompute, + usesDrawParameters, + checkTextures: true)) { program = entry; return true; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs index fcd953754..a41f761bd 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs @@ -457,6 +457,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// GPU channel /// Texture pool state /// Graphics state + /// Indicates that the vertex shader has been converted into a compute shader /// Indicates whether the vertex shader accesses draw parameters /// Indicates whether texture descriptors should be checked /// True if the state matches, false otherwise @@ -464,6 +465,7 @@ namespace Ryujinx.Graphics.Gpu.Shader GpuChannel channel, ref GpuChannelPoolState poolState, ref GpuChannelGraphicsState graphicsState, + bool vertexAsCompute, bool usesDrawParameters, bool checkTextures) { @@ -497,9 +499,25 @@ namespace Ryujinx.Graphics.Gpu.Shader return false; } - if (!graphicsState.AttributeTypes.AsSpan().SequenceEqual(GraphicsState.AttributeTypes.AsSpan())) + if (ShaderCache.MayConvertVtgToCompute(ref channel.Capabilities) && !vertexAsCompute) { - return false; + for (int index = 0; index < graphicsState.AttributeTypes.Length; index++) + { + AttributeType lType = FilterAttributeType(channel, graphicsState.AttributeTypes[index]); + AttributeType rType = FilterAttributeType(channel, GraphicsState.AttributeTypes[index]); + + if (lType != rType) + { + return false; + } + } + } + else + { + if (!graphicsState.AttributeTypes.AsSpan().SequenceEqual(GraphicsState.AttributeTypes.AsSpan())) + { + return false; + } } if (usesDrawParameters && graphicsState.HasConstantBufferDrawParameters != GraphicsState.HasConstantBufferDrawParameters) @@ -530,6 +548,19 @@ namespace Ryujinx.Graphics.Gpu.Shader return Matches(channel, ref poolState, checkTextures, isCompute: false); } + private static AttributeType FilterAttributeType(GpuChannel channel, AttributeType type) + { + type &= ~(AttributeType.Packed | AttributeType.PackedRgb10A2Signed); + + if (channel.Capabilities.SupportsScaledVertexFormats && + (type == AttributeType.Sscaled || type == AttributeType.Uscaled)) + { + type = AttributeType.Float; + } + + return type; + } + /// /// Checks if the recorded state matches the current GPU compute engine state. /// diff --git a/src/Ryujinx.Graphics.OpenGL/HwCapabilities.cs b/src/Ryujinx.Graphics.OpenGL/HwCapabilities.cs index 617b129ab..cf0b0645c 100644 --- a/src/Ryujinx.Graphics.OpenGL/HwCapabilities.cs +++ b/src/Ryujinx.Graphics.OpenGL/HwCapabilities.cs @@ -29,6 +29,7 @@ namespace Ryujinx.Graphics.OpenGL private static readonly Lazy _maximumComputeSharedMemorySize = new(() => GetLimit(All.MaxComputeSharedMemorySize)); private static readonly Lazy _storageBufferOffsetAlignment = new(() => GetLimit(All.ShaderStorageBufferOffsetAlignment)); + private static readonly Lazy _textureBufferOffsetAlignment = new(() => GetLimit(All.TextureBufferOffsetAlignment)); public enum GpuVendor { @@ -78,6 +79,7 @@ namespace Ryujinx.Graphics.OpenGL public static int MaximumComputeSharedMemorySize => _maximumComputeSharedMemorySize.Value; public static int StorageBufferOffsetAlignment => _storageBufferOffsetAlignment.Value; + public static int TextureBufferOffsetAlignment => _textureBufferOffsetAlignment.Value; public static float MaximumSupportedAnisotropy => _maxSupportedAnisotropy.Value; diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs index 35d1569fe..3eba15e34 100644 --- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs +++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -164,6 +164,7 @@ namespace Ryujinx.Graphics.OpenGL supportsShaderBarrierDivergence: !(intelWindows || intelUnix), supportsShaderFloat64: true, supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod, + supportsVertexStoreAndAtomics: true, supportsViewportIndexVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray, supportsViewportMask: HwCapabilities.SupportsViewportArray2, supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle, @@ -177,6 +178,7 @@ namespace Ryujinx.Graphics.OpenGL maximumSupportedAnisotropy: HwCapabilities.MaximumSupportedAnisotropy, shaderSubgroupSize: Constants.MaxSubgroupSize, storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment, + textureBufferOffsetAlignment: HwCapabilities.TextureBufferOffsetAlignment, gatherBiasPrecision: intelWindows || amdWindows ? 8 : 0); // Precision is 8 for these vendors on Vulkan. } diff --git a/src/Ryujinx.Graphics.Shader/AttributeType.cs b/src/Ryujinx.Graphics.Shader/AttributeType.cs index 1d9507731..d2d146ecb 100644 --- a/src/Ryujinx.Graphics.Shader/AttributeType.cs +++ b/src/Ryujinx.Graphics.Shader/AttributeType.cs @@ -11,13 +11,17 @@ namespace Ryujinx.Graphics.Shader Uint, Sscaled, Uscaled, + + Packed = 1 << 6, + PackedRgb10A2Signed = 1 << 7, + AnyPacked = Packed | PackedRgb10A2Signed, } static class AttributeTypeExtensions { public static AggregateType ToAggregateType(this AttributeType type) { - return type switch + return (type & ~AttributeType.AnyPacked) switch { AttributeType.Float => AggregateType.FP32, AttributeType.Sint => AggregateType.S32, @@ -28,7 +32,7 @@ namespace Ryujinx.Graphics.Shader public static AggregateType ToAggregateType(this AttributeType type, bool supportsScaledFormats) { - return type switch + return (type & ~AttributeType.AnyPacked) switch { AttributeType.Float => AggregateType.FP32, AttributeType.Sint => AggregateType.S32, diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs index 607ff431e..500de71f6 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -100,10 +100,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl else { string outPrimitive = context.Definitions.OutputTopology.ToGlslString(); - - int maxOutputVertices = context.Definitions.GpPassthrough - ? context.Definitions.InputTopology.ToInputVertices() - : context.Definitions.MaxOutputVertices; + int maxOutputVertices = context.Definitions.MaxOutputVertices; context.AppendLine($"layout ({outPrimitive}, max_vertices = {maxOutputVertices}) out;"); } @@ -320,15 +317,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl { string typeName = GetVarTypeName(context, memory.Type & ~AggregateType.Array); - if (memory.ArrayLength > 0) + if (memory.Type.HasFlag(AggregateType.Array)) { - string arraySize = memory.ArrayLength.ToString(CultureInfo.InvariantCulture); + if (memory.ArrayLength > 0) + { + string arraySize = memory.ArrayLength.ToString(CultureInfo.InvariantCulture); - context.AppendLine($"{prefix}{typeName} {memory.Name}[{arraySize}];"); + context.AppendLine($"{prefix}{typeName} {memory.Name}[{arraySize}];"); + } + else + { + context.AppendLine($"{prefix}{typeName} {memory.Name}[];"); + } } else { - context.AppendLine($"{prefix}{typeName} {memory.Name}[];"); + context.AppendLine($"{prefix}{typeName} {memory.Name};"); } } } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/IoMap.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/IoMap.cs index b5f453aef..caa6ef642 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/IoMap.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/IoMap.cs @@ -31,6 +31,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions IoVariable.FrontColorDiffuse => ("gl_FrontColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated. IoVariable.FrontColorSpecular => ("gl_FrontSecondaryColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated. IoVariable.FrontFacing => ("gl_FrontFacing", AggregateType.Bool), + IoVariable.GlobalId => ("gl_GlobalInvocationID", AggregateType.Vector3 | AggregateType.U32), IoVariable.InstanceId => ("gl_InstanceID", AggregateType.S32), IoVariable.InstanceIndex => ("gl_InstanceIndex", AggregateType.S32), IoVariable.InvocationId => ("gl_InvocationID", AggregateType.S32), diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs index d385782af..9f9411a9d 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs @@ -27,8 +27,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv public ILogger Logger { get; } public TargetApi TargetApi { get; } - public int InputVertices { get; } - public Dictionary ConstantBuffers { get; } = new(); public Dictionary StorageBuffers { get; } = new(); @@ -101,19 +99,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv Logger = parameters.Logger; TargetApi = parameters.TargetApi; - if (parameters.Definitions.Stage == ShaderStage.Geometry) - { - InputVertices = parameters.Definitions.InputTopology switch - { - InputTopology.Points => 1, - InputTopology.Lines => 2, - InputTopology.LinesAdjacency => 2, - InputTopology.Triangles => 3, - InputTopology.TrianglesAdjacency => 3, - _ => throw new InvalidOperationException($"Invalid input topology \"{parameters.Definitions.InputTopology}\"."), - }; - } - AddCapability(Capability.Shader); AddCapability(Capability.Float64); diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs index b0659ba44..54767c2fb 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs @@ -369,7 +369,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv if (context.Definitions.Stage != ShaderStage.Vertex) { var perVertexInputStructType = CreatePerVertexStructType(context); - int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.InputVertices : 32; + int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.Definitions.InputTopology.ToInputVertices() : 32; var perVertexInputArrayType = context.TypeArray(perVertexInputStructType, context.Constant(context.TypeU32(), arraySize)); var perVertexInputPointerType = context.TypePointer(StorageClass.Input, perVertexInputArrayType); var perVertexInputVariable = context.Variable(perVertexInputPointerType, StorageClass.Input); @@ -506,7 +506,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv if (!isPerPatch && IoMap.IsPerVertex(ioVariable, context.Definitions.Stage, isOutput)) { - int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.InputVertices : 32; + int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.Definitions.InputTopology.ToInputVertices() : 32; spvType = context.TypeArray(spvType, context.Constant(context.TypeU32(), arraySize)); if (context.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough) diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/IoMap.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/IoMap.cs index 08d403e2f..7b4e14ff0 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/IoMap.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/IoMap.cs @@ -22,6 +22,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv IoVariable.FragmentCoord => (BuiltIn.FragCoord, AggregateType.Vector4 | AggregateType.FP32), IoVariable.FragmentOutputDepth => (BuiltIn.FragDepth, AggregateType.FP32), IoVariable.FrontFacing => (BuiltIn.FrontFacing, AggregateType.Bool), + IoVariable.GlobalId => (BuiltIn.GlobalInvocationId, AggregateType.Vector3 | AggregateType.U32), IoVariable.InstanceId => (BuiltIn.InstanceId, AggregateType.S32), IoVariable.InstanceIndex => (BuiltIn.InstanceIndex, AggregateType.S32), IoVariable.InvocationId => (BuiltIn.InvocationId, AggregateType.S32), diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs index 70f1dd3c4..44d3e9853 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs @@ -239,9 +239,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv _ => throw new InvalidOperationException($"Invalid output topology \"{context.Definitions.OutputTopology}\"."), }); - int maxOutputVertices = context.Definitions.GpPassthrough ? context.InputVertices : context.Definitions.MaxOutputVertices; - - context.AddExecutionMode(spvFunc, ExecutionMode.OutputVertices, (SpvLiteralInteger)maxOutputVertices); + context.AddExecutionMode(spvFunc, ExecutionMode.OutputVertices, (SpvLiteralInteger)context.Definitions.MaxOutputVertices); } else if (context.Definitions.Stage == ShaderStage.Fragment) { @@ -279,6 +277,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv localSizeZ); } + if (context.Definitions.Stage != ShaderStage.Fragment && + context.Definitions.Stage != ShaderStage.Geometry && + context.Definitions.Stage != ShaderStage.Compute && + context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Output, IoVariable.Layer))) + { + context.AddCapability(Capability.ShaderLayer); + } + if (context.Definitions.TransformFeedbackEnabled && context.Definitions.LastInVertexPipeline) { context.AddExecutionMode(spvFunc, ExecutionMode.Xfb); diff --git a/src/Ryujinx.Graphics.Shader/Constants.cs b/src/Ryujinx.Graphics.Shader/Constants.cs index cff2c37a0..6317369f0 100644 --- a/src/Ryujinx.Graphics.Shader/Constants.cs +++ b/src/Ryujinx.Graphics.Shader/Constants.cs @@ -10,11 +10,5 @@ namespace Ryujinx.Graphics.Shader public const int NvnBaseVertexByteOffset = 0x640; public const int NvnBaseInstanceByteOffset = 0x644; public const int NvnDrawIndexByteOffset = 0x648; - - // Transform Feedback emulation. - - public const int TfeInfoBinding = 0; - public const int TfeBufferBaseBinding = 1; - public const int TfeBuffersCount = 4; } } diff --git a/src/Ryujinx.Graphics.Shader/Decoders/DecodedProgram.cs b/src/Ryujinx.Graphics.Shader/Decoders/DecodedProgram.cs index 67304d027..fdf3eacc3 100644 --- a/src/Ryujinx.Graphics.Shader/Decoders/DecodedProgram.cs +++ b/src/Ryujinx.Graphics.Shader/Decoders/DecodedProgram.cs @@ -60,6 +60,11 @@ namespace Ryujinx.Graphics.Shader.Decoders _functionsWithId.Add(function); } + public IoUsage GetIoUsage() + { + return new IoUsage(UsedFeatures, ClipDistancesWritten, AttributeUsage.UsedOutputAttributes); + } + public IEnumerator GetEnumerator() { return _functions.Values.GetEnumerator(); diff --git a/src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs b/src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs index 4266dedca..1211e561f 100644 --- a/src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs +++ b/src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs @@ -297,6 +297,9 @@ namespace Ryujinx.Graphics.Shader.Decoders case InstName.Ssy: block.AddPushOp(op); break; + case InstName.Shfl: + context.SetUsedFeature(FeatureFlags.Shuffle); + break; case InstName.Ldl: case InstName.Stl: context.SetUsedFeature(FeatureFlags.LocalMemory); @@ -307,8 +310,22 @@ namespace Ryujinx.Graphics.Shader.Decoders case InstName.Sts: context.SetUsedFeature(FeatureFlags.SharedMemory); break; - case InstName.Shfl: - context.SetUsedFeature(FeatureFlags.Shuffle); + case InstName.Atom: + case InstName.AtomCas: + case InstName.Red: + case InstName.Stg: + case InstName.Suatom: + case InstName.SuatomB: + case InstName.SuatomB2: + case InstName.SuatomCas: + case InstName.SuatomCasB: + case InstName.Sured: + case InstName.SuredB: + case InstName.Sust: + case InstName.SustB: + case InstName.SustD: + case InstName.SustDB: + context.SetUsedFeature(FeatureFlags.Store); break; } @@ -424,6 +441,12 @@ namespace Ryujinx.Graphics.Shader.Decoders context.SetUsedFeature(FeatureFlags.RtLayer); } break; + case AttributeConsts.ViewportIndex: + if (definitions.Stage != ShaderStage.Fragment) + { + context.SetUsedFeature(FeatureFlags.ViewportIndex); + } + break; case AttributeConsts.ClipDistance0: case AttributeConsts.ClipDistance1: case AttributeConsts.ClipDistance2: @@ -432,11 +455,17 @@ namespace Ryujinx.Graphics.Shader.Decoders case AttributeConsts.ClipDistance5: case AttributeConsts.ClipDistance6: case AttributeConsts.ClipDistance7: - if (definitions.Stage == ShaderStage.Vertex) + if (definitions.Stage.IsVtg()) { context.SetClipDistanceWritten((attr - AttributeConsts.ClipDistance0) / 4); } break; + case AttributeConsts.ViewportMask: + if (definitions.Stage != ShaderStage.Fragment) + { + context.SetUsedFeature(FeatureFlags.ViewportMask); + } + break; } } else diff --git a/src/Ryujinx.Graphics.Shader/InputTopology.cs b/src/Ryujinx.Graphics.Shader/InputTopology.cs index ebd2930e4..9438263de 100644 --- a/src/Ryujinx.Graphics.Shader/InputTopology.cs +++ b/src/Ryujinx.Graphics.Shader/InputTopology.cs @@ -25,6 +25,19 @@ namespace Ryujinx.Graphics.Shader } public static int ToInputVertices(this InputTopology topology) + { + return topology switch + { + InputTopology.Points => 1, + InputTopology.Lines => 2, + InputTopology.LinesAdjacency => 4, + InputTopology.Triangles => 3, + InputTopology.TrianglesAdjacency => 6, + _ => 1, + }; + } + + public static int ToInputVerticesNoAdjacency(this InputTopology topology) { return topology switch { diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs index 53d774d69..63ce38e25 100644 --- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs @@ -63,7 +63,7 @@ namespace Ryujinx.Graphics.Shader.Instructions { value = AttributeMap.GenerateAttributeLoad(context, primVertex, offset, isOutput, op.P); - if (!context.TranslatorContext.Definitions.SupportsScaledVertexFormats && + if ((!context.TranslatorContext.Definitions.SupportsScaledVertexFormats || context.VertexAsCompute) && context.TranslatorContext.Stage == ShaderStage.Vertex && !op.O && offset >= 0x80 && diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IoVariable.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IoVariable.cs index fdee83451..21e208636 100644 --- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IoVariable.cs +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IoVariable.cs @@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation FrontColorDiffuse, FrontColorSpecular, FrontFacing, + GlobalId, InstanceId, InstanceIndex, InvocationId, diff --git a/src/Ryujinx.Graphics.Shader/ResourceReservationCounts.cs b/src/Ryujinx.Graphics.Shader/ResourceReservationCounts.cs new file mode 100644 index 000000000..c0bae8eab --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/ResourceReservationCounts.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Shader +{ + public readonly struct ResourceReservationCounts + { + public readonly int ReservedConstantBuffers { get; } + public readonly int ReservedStorageBuffers { get; } + public readonly int ReservedTextures { get; } + public readonly int ReservedImages { get; } + + public ResourceReservationCounts(bool isTransformFeedbackEmulated, bool vertexAsCompute) + { + ResourceReservations reservations = new(isTransformFeedbackEmulated, vertexAsCompute); + + ReservedConstantBuffers = reservations.ReservedConstantBuffers; + ReservedStorageBuffers = reservations.ReservedStorageBuffers; + ReservedTextures = reservations.ReservedTextures; + ReservedImages = reservations.ReservedImages; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/ShaderIdentification.cs b/src/Ryujinx.Graphics.Shader/ShaderIdentification.cs deleted file mode 100644 index 551e318c0..000000000 --- a/src/Ryujinx.Graphics.Shader/ShaderIdentification.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.Graphics.Shader -{ - public enum ShaderIdentification - { - None, - GeometryLayerPassthrough, - } -} diff --git a/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs b/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs index f9776afc0..22823ac38 100644 --- a/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs +++ b/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs @@ -10,9 +10,10 @@ namespace Ryujinx.Graphics.Shader public ReadOnlyCollection Textures { get; } public ReadOnlyCollection Images { get; } - public ShaderIdentification Identification { get; } - public int GpLayerInputAttribute { get; } public ShaderStage Stage { get; } + public int GeometryVerticesPerPrimitive { get; } + public int GeometryMaxOutputVertices { get; } + public int ThreadsPerInputPrimitive { get; } public bool UsesFragCoord { get; } public bool UsesInstanceId { get; } public bool UsesDrawParameters { get; } @@ -25,9 +26,10 @@ namespace Ryujinx.Graphics.Shader BufferDescriptor[] sBuffers, TextureDescriptor[] textures, TextureDescriptor[] images, - ShaderIdentification identification, - int gpLayerInputAttribute, ShaderStage stage, + int geometryVerticesPerPrimitive, + int geometryMaxOutputVertices, + int threadsPerInputPrimitive, bool usesFragCoord, bool usesInstanceId, bool usesDrawParameters, @@ -40,9 +42,10 @@ namespace Ryujinx.Graphics.Shader Textures = Array.AsReadOnly(textures); Images = Array.AsReadOnly(images); - Identification = identification; - GpLayerInputAttribute = gpLayerInputAttribute; Stage = stage; + GeometryVerticesPerPrimitive = geometryVerticesPerPrimitive; + GeometryMaxOutputVertices = geometryMaxOutputVertices; + ThreadsPerInputPrimitive = threadsPerInputPrimitive; UsesFragCoord = usesFragCoord; UsesInstanceId = usesInstanceId; UsesDrawParameters = usesDrawParameters; diff --git a/src/Ryujinx.Graphics.Shader/SupportBuffer.cs b/src/Ryujinx.Graphics.Shader/SupportBuffer.cs index 0b7a2edd6..d4d3cbf8f 100644 --- a/src/Ryujinx.Graphics.Shader/SupportBuffer.cs +++ b/src/Ryujinx.Graphics.Shader/SupportBuffer.cs @@ -22,11 +22,13 @@ namespace Ryujinx.Graphics.Shader ViewportSize, FragmentRenderScaleCount, RenderScale, + TfeOffset, + TfeVertexCount, } public struct SupportBuffer { - internal const int Binding = 0; + public const int Binding = 0; public static readonly int FieldSize; public static readonly int RequiredSize; @@ -38,6 +40,8 @@ namespace Ryujinx.Graphics.Shader public static readonly int FragmentRenderScaleCountOffset; public static readonly int GraphicsRenderScaleOffset; public static readonly int ComputeRenderScaleOffset; + public static readonly int TfeOffsetOffset; + public static readonly int TfeVertexCountOffset; public const int FragmentIsBgraCount = 8; // One for the render target, 64 for the textures, and 8 for the images. @@ -62,18 +66,22 @@ namespace Ryujinx.Graphics.Shader FragmentRenderScaleCountOffset = OffsetOf(ref instance, ref instance.FragmentRenderScaleCount); GraphicsRenderScaleOffset = OffsetOf(ref instance, ref instance.RenderScale); ComputeRenderScaleOffset = GraphicsRenderScaleOffset + FieldSize; + TfeOffsetOffset = OffsetOf(ref instance, ref instance.TfeOffset); + TfeVertexCountOffset = OffsetOf(ref instance, ref instance.TfeVertexCount); } internal static StructureType GetStructureType() { return new StructureType(new[] { - new StructureField(AggregateType.U32, "s_alpha_test"), - new StructureField(AggregateType.Array | AggregateType.U32, "s_is_bgra", FragmentIsBgraCount), - new StructureField(AggregateType.Vector4 | AggregateType.FP32, "s_viewport_inverse"), - new StructureField(AggregateType.Vector4 | AggregateType.FP32, "s_viewport_size"), - new StructureField(AggregateType.S32, "s_frag_scale_count"), - new StructureField(AggregateType.Array | AggregateType.FP32, "s_render_scale", RenderScaleMaxCount), + new StructureField(AggregateType.U32, "alpha_test"), + new StructureField(AggregateType.Array | AggregateType.U32, "is_bgra", FragmentIsBgraCount), + new StructureField(AggregateType.Vector4 | AggregateType.FP32, "viewport_inverse"), + new StructureField(AggregateType.Vector4 | AggregateType.FP32, "viewport_size"), + new StructureField(AggregateType.S32, "frag_scale_count"), + new StructureField(AggregateType.Array | AggregateType.FP32, "render_scale", RenderScaleMaxCount), + new StructureField(AggregateType.Vector4 | AggregateType.S32, "tfe_offset"), + new StructureField(AggregateType.S32, "tfe_vertex_count"), }); } @@ -85,5 +93,8 @@ namespace Ryujinx.Graphics.Shader // Render scale max count: 1 + 64 + 8. First scale is fragment output scale, others are textures/image inputs. public Array73> RenderScale; + + public Vector4 TfeOffset; + public Vector4 TfeVertexCount; } } diff --git a/src/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs b/src/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs index f749cecb8..c4bd2cbfa 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs @@ -4,6 +4,7 @@ namespace Ryujinx.Graphics.Shader.Translation { public const int PrimitiveId = 0x060; public const int Layer = 0x064; + public const int ViewportIndex = 0x068; public const int PositionX = 0x070; public const int PositionY = 0x074; public const int FrontColorDiffuseR = 0x280; @@ -24,6 +25,7 @@ namespace Ryujinx.Graphics.Shader.Translation public const int TexCoordCount = 10; public const int TexCoordBase = 0x300; public const int TexCoordEnd = TexCoordBase + TexCoordCount * 16; + public const int ViewportMask = 0x3a0; public const int FrontFacing = 0x3fc; public const int UserAttributesCount = 32; diff --git a/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs b/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs index 43263dd4b..f1dffb351 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs @@ -14,6 +14,8 @@ namespace Ryujinx.Graphics.Shader.Translation public TranslatorContext TranslatorContext { get; } public ResourceManager ResourceManager { get; } + public bool VertexAsCompute { get; } + public bool IsNonMain { get; } public Block CurrBlock { get; set; } @@ -59,11 +61,13 @@ namespace Ryujinx.Graphics.Shader.Translation TranslatorContext translatorContext, ResourceManager resourceManager, DecodedProgram program, + bool vertexAsCompute, bool isNonMain) : this() { TranslatorContext = translatorContext; ResourceManager = resourceManager; Program = program; + VertexAsCompute = vertexAsCompute; IsNonMain = isNonMain; EmitStart(); @@ -71,13 +75,87 @@ namespace Ryujinx.Graphics.Shader.Translation private void EmitStart() { - if (TranslatorContext.Definitions.Stage == ShaderStage.Vertex && - TranslatorContext.Options.TargetApi == TargetApi.Vulkan && - (TranslatorContext.Options.Flags & TranslationFlags.VertexA) == 0) + if (TranslatorContext.Options.Flags.HasFlag(TranslationFlags.VertexA)) + { + return; + } + + if (TranslatorContext.Definitions.Stage == ShaderStage.Vertex && TranslatorContext.Options.TargetApi == TargetApi.Vulkan) { // Vulkan requires the point size to be always written on the shader if the primitive topology is points. this.Store(StorageKind.Output, IoVariable.PointSize, null, ConstF(TranslatorContext.Definitions.PointSize)); } + + if (VertexAsCompute) + { + int vertexInfoCbBinding = ResourceManager.Reservations.VertexInfoConstantBufferBinding; + int countFieldIndex = TranslatorContext.Stage == ShaderStage.Vertex + ? (int)VertexInfoBufferField.VertexCounts + : (int)VertexInfoBufferField.GeometryCounts; + + Operand outputVertexOffset = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(0)); + Operand vertexCount = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const(countFieldIndex), Const(0)); + Operand isVertexOob = this.ICompareGreaterOrEqualUnsigned(outputVertexOffset, vertexCount); + + Operand lblVertexInBounds = Label(); + + this.BranchIfFalse(lblVertexInBounds, isVertexOob); + this.Return(); + this.MarkLabel(lblVertexInBounds); + + Operand outputInstanceOffset = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(1)); + Operand instanceCount = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(1)); + Operand firstVertex = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(2)); + Operand firstInstance = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(3)); + Operand ibBaseOffset = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.GeometryCounts), Const(3)); + Operand isInstanceOob = this.ICompareGreaterOrEqualUnsigned(outputInstanceOffset, instanceCount); + + Operand lblInstanceInBounds = Label(); + + this.BranchIfFalse(lblInstanceInBounds, isInstanceOob); + this.Return(); + this.MarkLabel(lblInstanceInBounds); + + if (TranslatorContext.Stage == ShaderStage.Vertex) + { + Operand vertexIndexVr = Local(); + + this.TextureSample( + SamplerType.TextureBuffer, + TextureFlags.IntCoords, + ResourceManager.Reservations.IndexBufferTextureBinding, + 1, + new[] { vertexIndexVr }, + new[] { this.IAdd(ibBaseOffset, outputVertexOffset) }); + + this.Store(StorageKind.LocalMemory, ResourceManager.LocalVertexIndexVertexRateMemoryId, this.IAdd(firstVertex, vertexIndexVr)); + this.Store(StorageKind.LocalMemory, ResourceManager.LocalVertexIndexInstanceRateMemoryId, this.IAdd(firstInstance, outputInstanceOffset)); + } + else if (TranslatorContext.Stage == ShaderStage.Geometry) + { + int inputVertices = TranslatorContext.Definitions.InputTopology.ToInputVertices(); + + Operand baseVertex = this.IMultiply(outputVertexOffset, Const(inputVertices)); + + for (int index = 0; index < inputVertices; index++) + { + Operand vertexIndex = Local(); + + this.TextureSample( + SamplerType.TextureBuffer, + TextureFlags.IntCoords, + ResourceManager.Reservations.TopologyRemapBufferTextureBinding, + 1, + new[] { vertexIndex }, + new[] { this.IAdd(baseVertex, Const(index)) }); + + this.Store(StorageKind.LocalMemory, ResourceManager.LocalTopologyRemapMemoryId, Const(index), vertexIndex); + } + + this.Store(StorageKind.LocalMemory, ResourceManager.LocalGeometryOutputVertexCountMemoryId, Const(0)); + this.Store(StorageKind.LocalMemory, ResourceManager.LocalGeometryOutputIndexCountMemoryId, Const(0)); + } + } } public T GetOp() where T : unmanaged @@ -166,16 +244,21 @@ namespace Ryujinx.Graphics.Shader.Translation public void PrepareForVertexReturn() { - if (!TranslatorContext.GpuAccessor.QueryHostSupportsTransformFeedback() && TranslatorContext.GpuAccessor.QueryTransformFeedbackEnabled()) - { - Operand vertexCount = this.Load(StorageKind.StorageBuffer, Constants.TfeInfoBinding, Const(1)); + // TODO: Support transform feedback emulation on stages other than vertex. + // Those stages might produce more primitives, so it needs a way to "compact" the output after it is written. - for (int tfbIndex = 0; tfbIndex < Constants.TfeBuffersCount; tfbIndex++) + if (!TranslatorContext.GpuAccessor.QueryHostSupportsTransformFeedback() && + TranslatorContext.GpuAccessor.QueryTransformFeedbackEnabled() && + TranslatorContext.Stage == ShaderStage.Vertex) + { + Operand vertexCount = this.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.TfeVertexCount)); + + for (int tfbIndex = 0; tfbIndex < ResourceReservations.TfeBuffersCount; tfbIndex++) { var locations = TranslatorContext.GpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex); var stride = TranslatorContext.GpuAccessor.QueryTransformFeedbackStride(tfbIndex); - Operand baseOffset = this.Load(StorageKind.StorageBuffer, Constants.TfeInfoBinding, Const(0), Const(tfbIndex)); + Operand baseOffset = this.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.TfeOffset), Const(tfbIndex)); Operand baseVertex = this.Load(StorageKind.Input, IoVariable.BaseVertex); Operand baseInstance = this.Load(StorageKind.Input, IoVariable.BaseInstance); Operand vertexIndex = this.Load(StorageKind.Input, IoVariable.VertexIndex); @@ -200,7 +283,9 @@ namespace Ryujinx.Graphics.Shader.Translation Operand offset = this.IAdd(baseOffset, Const(j)); Operand value = Instructions.AttributeMap.GenerateAttributeLoad(this, null, location * 4, isOutput: true, isPerPatch: false); - this.Store(StorageKind.StorageBuffer, Constants.TfeBufferBaseBinding + tfbIndex, Const(0), offset, value); + int binding = ResourceManager.Reservations.GetTfeBufferStorageBufferBinding(tfbIndex); + + this.Store(StorageKind.StorageBuffer, binding, Const(0), offset, value); } } } @@ -225,16 +310,6 @@ namespace Ryujinx.Graphics.Shader.Translation this.Store(StorageKind.Output, IoVariable.Position, null, Const(2), this.FPFusedMultiplyAdd(z, ConstF(0.5f), halfW)); } - - if (TranslatorContext.Definitions.Stage != ShaderStage.Geometry && TranslatorContext.HasLayerInputAttribute) - { - int attrVecIndex = TranslatorContext.GpLayerInputAttribute >> 2; - int attrComponentIndex = TranslatorContext.GpLayerInputAttribute & 3; - - Operand layer = this.Load(StorageKind.Output, IoVariable.UserDefined, null, Const(attrVecIndex), Const(attrComponentIndex)); - - this.Store(StorageKind.Output, IoVariable.Layer, null, layer); - } } public void PrepareForVertexReturn(out Operand oldXLocal, out Operand oldYLocal, out Operand oldZLocal) @@ -308,9 +383,30 @@ namespace Ryujinx.Graphics.Shader.Translation if (TranslatorContext.Definitions.GpPassthrough && !TranslatorContext.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough()) { - int inputVertices = TranslatorContext.Definitions.InputTopology.ToInputVertices(); + int inputStart, inputEnd, inputStep; - for (int primIndex = 0; primIndex < inputVertices; primIndex++) + InputTopology topology = TranslatorContext.Definitions.InputTopology; + + if (topology == InputTopology.LinesAdjacency) + { + inputStart = 1; + inputEnd = 3; + inputStep = 1; + } + else if (topology == InputTopology.TrianglesAdjacency) + { + inputStart = 0; + inputEnd = 6; + inputStep = 2; + } + else + { + inputStart = 0; + inputEnd = topology.ToInputVerticesNoAdjacency(); + inputStep = 1; + } + + for (int primIndex = inputStart; primIndex < inputEnd; primIndex += inputStep) { WritePositionOutput(primIndex); @@ -428,6 +524,65 @@ namespace Ryujinx.Graphics.Shader.Translation } } + if (VertexAsCompute) + { + if (TranslatorContext.Stage == ShaderStage.Vertex) + { + int vertexInfoCbBinding = ResourceManager.Reservations.VertexInfoConstantBufferBinding; + int vertexOutputSbBinding = ResourceManager.Reservations.VertexOutputStorageBufferBinding; + int stride = ResourceManager.Reservations.OutputSizePerInvocation; + + Operand vertexCount = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(0)); + + Operand outputVertexOffset = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(0)); + Operand outputInstanceOffset = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(1)); + + Operand outputBaseVertex = this.IMultiply(outputInstanceOffset, vertexCount); + + Operand baseOffset = this.IMultiply(this.IAdd(outputBaseVertex, outputVertexOffset), Const(stride)); + + for (int offset = 0; offset < stride; offset++) + { + Operand vertexOffset = this.IAdd(baseOffset, Const(offset)); + Operand value = this.Load(StorageKind.LocalMemory, ResourceManager.LocalVertexDataMemoryId, Const(offset)); + + this.Store(StorageKind.StorageBuffer, vertexOutputSbBinding, Const(0), vertexOffset, value); + } + } + else if (TranslatorContext.Stage == ShaderStage.Geometry) + { + Operand lblLoopHead = Label(); + Operand lblExit = Label(); + + this.MarkLabel(lblLoopHead); + + Operand writtenIndices = this.Load(StorageKind.LocalMemory, ResourceManager.LocalGeometryOutputIndexCountMemoryId); + + int maxIndicesPerPrimitiveInvocation = TranslatorContext.Definitions.GetGeometryOutputIndexBufferStridePerInstance(); + int maxIndicesPerPrimitive = maxIndicesPerPrimitiveInvocation * TranslatorContext.Definitions.ThreadsPerInputPrimitive; + + this.BranchIfTrue(lblExit, this.ICompareGreaterOrEqualUnsigned(writtenIndices, Const(maxIndicesPerPrimitiveInvocation))); + + int vertexInfoCbBinding = ResourceManager.Reservations.VertexInfoConstantBufferBinding; + + Operand primitiveIndex = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(0)); + Operand instanceIndex = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(1)); + Operand invocationId = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(2)); + Operand vertexCount = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(0)); + Operand primitiveId = this.IAdd(this.IMultiply(instanceIndex, vertexCount), primitiveIndex); + Operand ibOffset = this.IMultiply(primitiveId, Const(maxIndicesPerPrimitive)); + ibOffset = this.IAdd(ibOffset, this.IMultiply(invocationId, Const(maxIndicesPerPrimitiveInvocation))); + ibOffset = this.IAdd(ibOffset, writtenIndices); + + this.Store(StorageKind.StorageBuffer, ResourceManager.Reservations.GeometryIndexOutputStorageBufferBinding, Const(0), ibOffset, Const(-1)); + this.Store(StorageKind.LocalMemory, ResourceManager.LocalGeometryOutputIndexCountMemoryId, this.IAdd(writtenIndices, Const(1))); + + this.Branch(lblLoopHead); + + this.MarkLabel(lblExit); + } + } + return true; } diff --git a/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs b/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs index a08c8ea9d..afa830de3 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs @@ -831,6 +831,11 @@ namespace Ryujinx.Graphics.Shader.Translation return context.Add(Instruction.Store, storageKind, null, e0, e1, value); } + public static Operand Store(this EmitterContext context, StorageKind storageKind, int binding, Operand value) + { + return context.Add(Instruction.Store, storageKind, null, Const(binding), value); + } + public static Operand Store(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand value) { return context.Add(Instruction.Store, storageKind, null, Const(binding), e0, value); diff --git a/src/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs b/src/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs index 552a3f310..88525462d 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs @@ -19,8 +19,12 @@ namespace Ryujinx.Graphics.Shader.Translation DrawParameters = 1 << 4, RtLayer = 1 << 5, Shuffle = 1 << 6, + ViewportIndex = 1 << 7, + ViewportMask = 1 << 8, FixedFuncAttr = 1 << 9, LocalMemory = 1 << 10, SharedMemory = 1 << 11, + Store = 1 << 12, + VtgAsCompute = 1 << 13, } } diff --git a/src/Ryujinx.Graphics.Shader/Translation/IoUsage.cs b/src/Ryujinx.Graphics.Shader/Translation/IoUsage.cs new file mode 100644 index 000000000..8ce2da4a0 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/IoUsage.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.Graphics.Shader.Translation +{ + readonly struct IoUsage + { + private readonly FeatureFlags _usedFeatures; + + public readonly bool UsesRtLayer => _usedFeatures.HasFlag(FeatureFlags.RtLayer); + public readonly bool UsesViewportIndex => _usedFeatures.HasFlag(FeatureFlags.ViewportIndex); + public readonly bool UsesViewportMask => _usedFeatures.HasFlag(FeatureFlags.ViewportMask); + public readonly byte ClipDistancesWritten { get; } + public readonly int UserDefinedMap { get; } + + public IoUsage(FeatureFlags usedFeatures, byte clipDistancesWritten, int userDefinedMap) + { + _usedFeatures = usedFeatures; + ClipDistancesWritten = clipDistancesWritten; + UserDefinedMap = userDefinedMap; + } + + public readonly IoUsage Combine(IoUsage other) + { + return new IoUsage( + _usedFeatures | other._usedFeatures, + (byte)(ClipDistancesWritten | other.ClipDistancesWritten), + UserDefinedMap | other.UserDefinedMap); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs index d07d8dce5..9c487c467 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs @@ -48,12 +48,22 @@ namespace Ryujinx.Graphics.Shader.Translation public int LocalMemoryId { get; private set; } public int SharedMemoryId { get; private set; } + public int LocalVertexDataMemoryId { get; private set; } + public int LocalTopologyRemapMemoryId { get; private set; } + public int LocalVertexIndexVertexRateMemoryId { get; private set; } + public int LocalVertexIndexInstanceRateMemoryId { get; private set; } + public int LocalGeometryOutputVertexCountMemoryId { get; private set; } + public int LocalGeometryOutputIndexCountMemoryId { get; private set; } + public ShaderProperties Properties { get; } - public ResourceManager(ShaderStage stage, IGpuAccessor gpuAccessor) + public ResourceReservations Reservations { get; } + + public ResourceManager(ShaderStage stage, IGpuAccessor gpuAccessor, ResourceReservations reservations = null) { _gpuAccessor = gpuAccessor; Properties = new(); + Reservations = reservations; _stage = stage; _stagePrefix = GetShaderStagePrefix(stage); @@ -114,6 +124,29 @@ namespace Ryujinx.Graphics.Shader.Translation } } + public void SetVertexAsComputeLocalMemories(ShaderStage stage, InputTopology inputTopology) + { + LocalVertexDataMemoryId = AddMemoryDefinition("local_vertex_data", AggregateType.Array | AggregateType.FP32, Reservations.OutputSizePerInvocation); + + if (stage == ShaderStage.Vertex) + { + LocalVertexIndexVertexRateMemoryId = AddMemoryDefinition("local_vertex_index_vr", AggregateType.U32); + LocalVertexIndexInstanceRateMemoryId = AddMemoryDefinition("local_vertex_index_ir", AggregateType.U32); + } + else if (stage == ShaderStage.Geometry) + { + LocalTopologyRemapMemoryId = AddMemoryDefinition("local_topology_remap", AggregateType.Array | AggregateType.U32, inputTopology.ToInputVertices()); + + LocalGeometryOutputVertexCountMemoryId = AddMemoryDefinition("local_geometry_output_vertex", AggregateType.U32); + LocalGeometryOutputIndexCountMemoryId = AddMemoryDefinition("local_geometry_output_index", AggregateType.U32); + } + } + + private int AddMemoryDefinition(string name, AggregateType type, int arrayLength = 1) + { + return Properties.AddLocalMemory(new MemoryDefinition(name, type, arrayLength)); + } + public int GetConstantBufferBinding(int slot) { int binding = _cbSlotToBindingMap[slot]; @@ -465,17 +498,22 @@ namespace Ryujinx.Graphics.Shader.Translation return descriptors; } - public (int, int) GetCbufSlotAndHandleForTexture(int binding) + public bool TryGetCbufSlotAndHandleForTexture(int binding, out int cbufSlot, out int handle) { foreach ((TextureInfo info, TextureMeta meta) in _usedTextures) { if (meta.Binding == binding) { - return (info.CbufSlot, info.Handle); + cbufSlot = info.CbufSlot; + handle = info.Handle; + + return true; } } - throw new ArgumentException($"Binding {binding} is invalid."); + cbufSlot = 0; + handle = 0; + return false; } private static int FindDescriptorIndex(TextureDescriptor[] array, int binding) diff --git a/src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs b/src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs new file mode 100644 index 000000000..d559f6699 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs @@ -0,0 +1,186 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System.Collections.Generic; +using System.Numerics; + +namespace Ryujinx.Graphics.Shader.Translation +{ + public class ResourceReservations + { + public const int TfeBuffersCount = 4; + + public const int MaxVertexBufferTextures = 32; + + public int VertexInfoConstantBufferBinding { get; } + public int VertexOutputStorageBufferBinding { get; } + public int GeometryVertexOutputStorageBufferBinding { get; } + public int GeometryIndexOutputStorageBufferBinding { get; } + public int IndexBufferTextureBinding { get; } + public int TopologyRemapBufferTextureBinding { get; } + + public int ReservedConstantBuffers { get; } + public int ReservedStorageBuffers { get; } + public int ReservedTextures { get; } + public int ReservedImages { get; } + public int InputSizePerInvocation { get; } + public int OutputSizePerInvocation { get; } + public int OutputSizeInBytesPerInvocation => OutputSizePerInvocation * sizeof(uint); + + private readonly int _tfeBufferSbBaseBinding; + private readonly int _vertexBufferTextureBaseBinding; + + private readonly Dictionary _offsets; + internal IReadOnlyDictionary Offsets => _offsets; + + internal ResourceReservations(bool isTransformFeedbackEmulated, bool vertexAsCompute) + { + // All stages reserves the first constant buffer binding for the support buffer. + ReservedConstantBuffers = 1; + ReservedStorageBuffers = 0; + ReservedTextures = 0; + ReservedImages = 0; + + if (isTransformFeedbackEmulated) + { + // Transform feedback emulation currently always uses 4 storage buffers. + _tfeBufferSbBaseBinding = ReservedStorageBuffers; + ReservedStorageBuffers = TfeBuffersCount; + } + + if (vertexAsCompute) + { + // One constant buffer reserved for vertex related state. + VertexInfoConstantBufferBinding = ReservedConstantBuffers++; + + // One storage buffer for the output vertex data. + VertexOutputStorageBufferBinding = ReservedStorageBuffers++; + + // One storage buffer for the output geometry vertex data. + GeometryVertexOutputStorageBufferBinding = ReservedStorageBuffers++; + + // One storage buffer for the output geometry index data. + GeometryIndexOutputStorageBufferBinding = ReservedStorageBuffers++; + + // Enough textures reserved for all vertex attributes, plus the index buffer. + IndexBufferTextureBinding = ReservedTextures; + TopologyRemapBufferTextureBinding = ReservedTextures + 1; + _vertexBufferTextureBaseBinding = ReservedTextures + 2; + ReservedTextures += 2 + MaxVertexBufferTextures; + } + } + + internal ResourceReservations( + IGpuAccessor gpuAccessor, + bool isTransformFeedbackEmulated, + bool vertexAsCompute, + IoUsage? vacInput, + IoUsage vacOutput) : this(isTransformFeedbackEmulated, vertexAsCompute) + { + if (vertexAsCompute) + { + _offsets = new(); + + if (vacInput.HasValue) + { + InputSizePerInvocation = FillIoOffsetMap(gpuAccessor, StorageKind.Input, vacInput.Value); + } + + OutputSizePerInvocation = FillIoOffsetMap(gpuAccessor, StorageKind.Output, vacOutput); + } + } + + private int FillIoOffsetMap(IGpuAccessor gpuAccessor, StorageKind storageKind, IoUsage vacUsage) + { + int offset = 0; + + for (int c = 0; c < 4; c++) + { + _offsets.Add(new IoDefinition(storageKind, IoVariable.Position, 0, c), offset++); + } + + _offsets.Add(new IoDefinition(storageKind, IoVariable.PointSize), offset++); + + int clipDistancesWrittenMap = vacUsage.ClipDistancesWritten; + + while (clipDistancesWrittenMap != 0) + { + int index = BitOperations.TrailingZeroCount(clipDistancesWrittenMap); + + _offsets.Add(new IoDefinition(storageKind, IoVariable.ClipDistance, 0, index), offset++); + + clipDistancesWrittenMap &= ~(1 << index); + } + + if (vacUsage.UsesRtLayer) + { + _offsets.Add(new IoDefinition(storageKind, IoVariable.Layer), offset++); + } + + if (vacUsage.UsesViewportIndex && gpuAccessor.QueryHostSupportsViewportIndexVertexTessellation()) + { + _offsets.Add(new IoDefinition(storageKind, IoVariable.VertexIndex), offset++); + } + + if (vacUsage.UsesViewportMask && gpuAccessor.QueryHostSupportsViewportMask()) + { + _offsets.Add(new IoDefinition(storageKind, IoVariable.ViewportMask), offset++); + } + + int usedDefinedMap = vacUsage.UserDefinedMap; + + while (usedDefinedMap != 0) + { + int location = BitOperations.TrailingZeroCount(usedDefinedMap); + + for (int c = 0; c < 4; c++) + { + _offsets.Add(new IoDefinition(storageKind, IoVariable.UserDefined, location, c), offset++); + } + + usedDefinedMap &= ~(1 << location); + } + + return offset; + } + + internal static bool IsVectorOrArrayVariable(IoVariable variable) + { + return variable switch + { + IoVariable.ClipDistance or + IoVariable.Position => true, + _ => false, + }; + } + + public int GetTfeBufferStorageBufferBinding(int bufferIndex) + { + return _tfeBufferSbBaseBinding + bufferIndex; + } + + public int GetVertexBufferTextureBinding(int vaLocation) + { + return _vertexBufferTextureBaseBinding + vaLocation; + } + + internal bool TryGetOffset(StorageKind storageKind, int location, int component, out int offset) + { + return _offsets.TryGetValue(new IoDefinition(storageKind, IoVariable.UserDefined, location, component), out offset); + } + + internal bool TryGetOffset(StorageKind storageKind, IoVariable ioVariable, int location, int component, out int offset) + { + return _offsets.TryGetValue(new IoDefinition(storageKind, ioVariable, location, component), out offset); + } + + internal bool TryGetOffset(StorageKind storageKind, IoVariable ioVariable, int component, out int offset) + { + return _offsets.TryGetValue(new IoDefinition(storageKind, ioVariable, 0, component), out offset); + } + + internal bool TryGetOffset(StorageKind storageKind, IoVariable ioVariable, out int offset) + { + return _offsets.TryGetValue(new IoDefinition(storageKind, ioVariable, 0, 0), out offset); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs b/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs index 204f4278f..3246e2594 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs @@ -32,7 +32,7 @@ namespace Ryujinx.Graphics.Shader.Translation public bool GpPassthrough { get; } public bool LastInVertexPipeline { get; set; } - public int ThreadsPerInputPrimitive { get; } + public int ThreadsPerInputPrimitive { get; private set; } public InputTopology InputTopology => _graphicsState.Topology; public OutputTopology OutputTopology { get; } @@ -97,9 +97,14 @@ namespace Ryujinx.Graphics.Shader.Translation private readonly Dictionary _transformFeedbackDefinitions; - public ShaderDefinitions(ShaderStage stage) + public ShaderDefinitions(ShaderStage stage, ulong transformFeedbackVecMap, TransformFeedbackOutput[] transformFeedbackOutputs) { Stage = stage; + TransformFeedbackEnabled = transformFeedbackOutputs != null; + _transformFeedbackOutputs = transformFeedbackOutputs; + _transformFeedbackDefinitions = new(); + + PopulateTransformFeedbackDefinitions(transformFeedbackVecMap, transformFeedbackOutputs); } public ShaderDefinitions( @@ -142,7 +147,6 @@ namespace Ryujinx.Graphics.Shader.Translation bool omapSampleMask, bool omapDepth, bool supportsScaledVertexFormats, - bool transformFeedbackEnabled, ulong transformFeedbackVecMap, TransformFeedbackOutput[] transformFeedbackOutputs) { @@ -151,17 +155,22 @@ namespace Ryujinx.Graphics.Shader.Translation GpPassthrough = gpPassthrough; ThreadsPerInputPrimitive = threadsPerInputPrimitive; OutputTopology = outputTopology; - MaxOutputVertices = maxOutputVertices; + MaxOutputVertices = gpPassthrough ? graphicsState.Topology.ToInputVerticesNoAdjacency() : maxOutputVertices; ImapTypes = imapTypes; OmapTargets = omapTargets; OmapSampleMask = omapSampleMask; OmapDepth = omapDepth; LastInVertexPipeline = stage < ShaderStage.Fragment; SupportsScaledVertexFormats = supportsScaledVertexFormats; - TransformFeedbackEnabled = transformFeedbackEnabled; + TransformFeedbackEnabled = transformFeedbackOutputs != null; _transformFeedbackOutputs = transformFeedbackOutputs; _transformFeedbackDefinitions = new(); + PopulateTransformFeedbackDefinitions(transformFeedbackVecMap, transformFeedbackOutputs); + } + + private void PopulateTransformFeedbackDefinitions(ulong transformFeedbackVecMap, TransformFeedbackOutput[] transformFeedbackOutputs) + { while (transformFeedbackVecMap != 0) { int vecIndex = BitOperations.TrailingZeroCount(transformFeedbackVecMap); @@ -200,16 +209,6 @@ namespace Ryujinx.Graphics.Shader.Translation OaIndexing = true; } - public TransformFeedbackOutput[] GetTransformFeedbackOutputs() - { - if (!HasTransformFeedbackOutputs()) - { - return null; - } - - return _transformFeedbackOutputs; - } - public bool TryGetTransformFeedbackOutput(IoVariable ioVariable, int location, int component, out TransformFeedbackOutput transformFeedbackOutput) { if (!HasTransformFeedbackOutputs()) @@ -320,5 +319,35 @@ namespace Ryujinx.Graphics.Shader.Translation { return _graphicsState.AttributeTypes[location]; } + + public bool IsAttributeSint(int location) + { + return (_graphicsState.AttributeTypes[location] & ~AttributeType.AnyPacked) == AttributeType.Sint; + } + + public bool IsAttributePacked(int location) + { + return _graphicsState.AttributeTypes[location].HasFlag(AttributeType.Packed); + } + + public bool IsAttributePackedRgb10A2Signed(int location) + { + return _graphicsState.AttributeTypes[location].HasFlag(AttributeType.PackedRgb10A2Signed); + } + + public int GetGeometryOutputIndexBufferStridePerInstance() + { + return MaxOutputVertices + OutputTopology switch + { + OutputTopology.LineStrip => MaxOutputVertices / 2, + OutputTopology.TriangleStrip => MaxOutputVertices / 3, + _ => MaxOutputVertices, + }; + } + + public int GetGeometryOutputIndexBufferStride() + { + return GetGeometryOutputIndexBufferStridePerInstance() * ThreadsPerInputPrimitive; + } } } diff --git a/src/Ryujinx.Graphics.Shader/Translation/ShaderIdentifier.cs b/src/Ryujinx.Graphics.Shader/Translation/ShaderIdentifier.cs deleted file mode 100644 index c077e1cde..000000000 --- a/src/Ryujinx.Graphics.Shader/Translation/ShaderIdentifier.cs +++ /dev/null @@ -1,187 +0,0 @@ -using Ryujinx.Graphics.Shader.IntermediateRepresentation; -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Shader.Translation -{ - static class ShaderIdentifier - { - public static ShaderIdentification Identify( - IReadOnlyList functions, - IGpuAccessor gpuAccessor, - ShaderStage stage, - InputTopology inputTopology, - out int layerInputAttr) - { - if (stage == ShaderStage.Geometry && - inputTopology == InputTopology.Triangles && - !gpuAccessor.QueryHostSupportsGeometryShader() && - IsLayerPassthroughGeometryShader(functions, out layerInputAttr)) - { - return ShaderIdentification.GeometryLayerPassthrough; - } - - layerInputAttr = 0; - return ShaderIdentification.None; - } - - private static bool IsLayerPassthroughGeometryShader(IReadOnlyList functions, out int layerInputAttr) - { - bool writesLayer = false; - layerInputAttr = 0; - - if (functions.Count != 1) - { - return false; - } - - int verticesCount = 0; - int totalVerticesCount = 0; - - foreach (BasicBlock block in functions[0].Blocks) - { - // We are not expecting loops or any complex control flow here, so fail in those cases. - if (block.Branch != null && block.Branch.Index <= block.Index) - { - return false; - } - - foreach (INode node in block.Operations) - { - if (node is not Operation operation) - { - continue; - } - - if (IsResourceWrite(operation.Inst, operation.StorageKind)) - { - return false; - } - - if (operation.Inst == Instruction.Store && operation.StorageKind == StorageKind.Output) - { - Operand src = operation.GetSource(operation.SourcesCount - 1); - Operation srcAttributeAsgOp = null; - - if (src.Type == OperandType.LocalVariable && - src.AsgOp is Operation asgOp && - asgOp.Inst == Instruction.Load && - asgOp.StorageKind.IsInputOrOutput()) - { - if (asgOp.StorageKind != StorageKind.Input) - { - return false; - } - - srcAttributeAsgOp = asgOp; - } - - if (srcAttributeAsgOp != null) - { - IoVariable dstAttribute = (IoVariable)operation.GetSource(0).Value; - IoVariable srcAttribute = (IoVariable)srcAttributeAsgOp.GetSource(0).Value; - - if (dstAttribute == IoVariable.Layer && srcAttribute == IoVariable.UserDefined) - { - if (srcAttributeAsgOp.SourcesCount != 4) - { - return false; - } - - writesLayer = true; - layerInputAttr = srcAttributeAsgOp.GetSource(1).Value * 4 + srcAttributeAsgOp.GetSource(3).Value; - } - else - { - if (dstAttribute != srcAttribute) - { - return false; - } - - int inputsCount = operation.SourcesCount - 2; - - if (dstAttribute == IoVariable.UserDefined) - { - if (operation.GetSource(1).Value != srcAttributeAsgOp.GetSource(1).Value) - { - return false; - } - - inputsCount--; - } - - for (int i = 0; i < inputsCount; i++) - { - int dstIndex = operation.SourcesCount - 2 - i; - int srcIndex = srcAttributeAsgOp.SourcesCount - 1 - i; - - if ((dstIndex | srcIndex) < 0) - { - return false; - } - - if (operation.GetSource(dstIndex).Type != OperandType.Constant || - srcAttributeAsgOp.GetSource(srcIndex).Type != OperandType.Constant || - operation.GetSource(dstIndex).Value != srcAttributeAsgOp.GetSource(srcIndex).Value) - { - return false; - } - } - } - } - else if (src.Type == OperandType.Constant) - { - int dstComponent = operation.GetSource(operation.SourcesCount - 2).Value; - float expectedValue = dstComponent == 3 ? 1f : 0f; - - if (src.AsFloat() != expectedValue) - { - return false; - } - } - else - { - return false; - } - } - else if (operation.Inst == Instruction.EmitVertex) - { - verticesCount++; - } - else if (operation.Inst == Instruction.EndPrimitive) - { - totalVerticesCount += verticesCount; - verticesCount = 0; - } - } - } - - return totalVerticesCount + verticesCount == 3 && writesLayer; - } - - private static bool IsResourceWrite(Instruction inst, StorageKind storageKind) - { - switch (inst) - { - case Instruction.AtomicAdd: - case Instruction.AtomicAnd: - case Instruction.AtomicCompareAndSwap: - case Instruction.AtomicMaxS32: - case Instruction.AtomicMaxU32: - case Instruction.AtomicMinS32: - case Instruction.AtomicMinU32: - case Instruction.AtomicOr: - case Instruction.AtomicSwap: - case Instruction.AtomicXor: - case Instruction.ImageAtomic: - case Instruction.ImageStore: - return true; - case Instruction.Store: - return storageKind == StorageKind.StorageBuffer || - storageKind == StorageKind.SharedMemory || - storageKind == StorageKind.LocalMemory; - } - - return false; - } - } -} diff --git a/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs index fa687eca0..87ebb8e7c 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs @@ -6,6 +6,7 @@ namespace Ryujinx.Graphics.Shader.Translation { public readonly HelperFunctionManager Hfm; public readonly BasicBlock[] Blocks; + public readonly ShaderDefinitions Definitions; public readonly ResourceManager ResourceManager; public readonly IGpuAccessor GpuAccessor; public readonly TargetLanguage TargetLanguage; @@ -15,6 +16,7 @@ namespace Ryujinx.Graphics.Shader.Translation public TransformContext( HelperFunctionManager hfm, BasicBlock[] blocks, + ShaderDefinitions definitions, ResourceManager resourceManager, IGpuAccessor gpuAccessor, TargetLanguage targetLanguage, @@ -23,6 +25,7 @@ namespace Ryujinx.Graphics.Shader.Translation { Hfm = hfm; Blocks = blocks; + Definitions = definitions; ResourceManager = resourceManager; GpuAccessor = gpuAccessor; TargetLanguage = targetLanguage; diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/GeometryToCompute.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/GeometryToCompute.cs new file mode 100644 index 000000000..0013cf0eb --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/GeometryToCompute.cs @@ -0,0 +1,378 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation.Optimizations; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Transforms +{ + class GeometryToCompute : ITransformPass + { + public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures) + { + return usedFeatures.HasFlag(FeatureFlags.VtgAsCompute); + } + + public static LinkedListNode RunPass(TransformContext context, LinkedListNode node) + { + if (context.Definitions.Stage != ShaderStage.Geometry) + { + return node; + } + + Operation operation = (Operation)node.Value; + + LinkedListNode newNode = node; + + switch (operation.Inst) + { + case Instruction.EmitVertex: + newNode = GenerateEmitVertex(context.Definitions, context.ResourceManager, node); + break; + case Instruction.EndPrimitive: + newNode = GenerateEndPrimitive(context.Definitions, context.ResourceManager, node); + break; + case Instruction.Load: + if (operation.StorageKind == StorageKind.Input) + { + IoVariable ioVariable = (IoVariable)operation.GetSource(0).Value; + + if (TryGetOffset(context.ResourceManager, operation, StorageKind.Input, out int inputOffset)) + { + Operand primVertex = ioVariable == IoVariable.UserDefined + ? operation.GetSource(2) + : operation.GetSource(1); + + Operand vertexElemOffset = GenerateVertexOffset(context.ResourceManager, node, inputOffset, primVertex); + + newNode = node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.StorageBuffer, + operation.Dest, + new[] { Const(context.ResourceManager.Reservations.VertexOutputStorageBufferBinding), Const(0), vertexElemOffset })); + } + else + { + switch (ioVariable) + { + case IoVariable.InvocationId: + newNode = GenerateInvocationId(node, operation.Dest); + break; + case IoVariable.PrimitiveId: + newNode = GeneratePrimitiveId(context.ResourceManager, node, operation.Dest); + break; + case IoVariable.GlobalId: + case IoVariable.SubgroupEqMask: + case IoVariable.SubgroupGeMask: + case IoVariable.SubgroupGtMask: + case IoVariable.SubgroupLaneId: + case IoVariable.SubgroupLeMask: + case IoVariable.SubgroupLtMask: + // Those are valid or expected for geometry shaders. + break; + default: + context.GpuAccessor.Log($"Invalid input \"{ioVariable}\"."); + break; + } + } + } + else if (operation.StorageKind == StorageKind.Output) + { + if (TryGetOffset(context.ResourceManager, operation, StorageKind.Output, out int outputOffset)) + { + newNode = node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.LocalMemory, + operation.Dest, + new[] { Const(context.ResourceManager.LocalVertexDataMemoryId), Const(outputOffset) })); + } + else + { + context.GpuAccessor.Log($"Invalid output \"{(IoVariable)operation.GetSource(0).Value}\"."); + } + } + break; + case Instruction.Store: + if (operation.StorageKind == StorageKind.Output) + { + if (TryGetOffset(context.ResourceManager, operation, StorageKind.Output, out int outputOffset)) + { + Operand value = operation.GetSource(operation.SourcesCount - 1); + + newNode = node.List.AddBefore(node, new Operation( + Instruction.Store, + StorageKind.LocalMemory, + (Operand)null, + new[] { Const(context.ResourceManager.LocalVertexDataMemoryId), Const(outputOffset), value })); + } + else + { + context.GpuAccessor.Log($"Invalid output \"{(IoVariable)operation.GetSource(0).Value}\"."); + } + } + break; + } + + if (newNode != node) + { + Utils.DeleteNode(node, operation); + } + + return newNode; + } + + private static LinkedListNode GenerateEmitVertex(ShaderDefinitions definitions, ResourceManager resourceManager, LinkedListNode node) + { + int vbOutputBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding; + int ibOutputBinding = resourceManager.Reservations.GeometryIndexOutputStorageBufferBinding; + int stride = resourceManager.Reservations.OutputSizePerInvocation; + + Operand outputPrimVertex = IncrementLocalMemory(node, resourceManager.LocalGeometryOutputVertexCountMemoryId); + Operand baseVertexOffset = GenerateBaseOffset( + resourceManager, + node, + definitions.MaxOutputVertices * definitions.ThreadsPerInputPrimitive, + definitions.ThreadsPerInputPrimitive); + Operand outputBaseVertex = Local(); + node.List.AddBefore(node, new Operation(Instruction.Add, outputBaseVertex, new[] { baseVertexOffset, outputPrimVertex })); + + Operand outputPrimIndex = IncrementLocalMemory(node, resourceManager.LocalGeometryOutputIndexCountMemoryId); + Operand baseIndexOffset = GenerateBaseOffset( + resourceManager, + node, + definitions.GetGeometryOutputIndexBufferStride(), + definitions.ThreadsPerInputPrimitive); + Operand outputBaseIndex = Local(); + node.List.AddBefore(node, new Operation(Instruction.Add, outputBaseIndex, new[] { baseIndexOffset, outputPrimIndex })); + + node.List.AddBefore(node, new Operation( + Instruction.Store, + StorageKind.StorageBuffer, + null, + new[] { Const(ibOutputBinding), Const(0), outputBaseIndex, outputBaseVertex })); + + Operand baseOffset = Local(); + node.List.AddBefore(node, new Operation(Instruction.Multiply, baseOffset, new[] { outputBaseVertex, Const(stride) })); + + LinkedListNode newNode = node; + + for (int offset = 0; offset < stride; offset++) + { + Operand vertexOffset; + + if (offset > 0) + { + vertexOffset = Local(); + node.List.AddBefore(node, new Operation(Instruction.Add, vertexOffset, new[] { baseOffset, Const(offset) })); + } + else + { + vertexOffset = baseOffset; + } + + Operand value = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.LocalMemory, + value, + new[] { Const(resourceManager.LocalVertexDataMemoryId), Const(offset) })); + + newNode = node.List.AddBefore(node, new Operation( + Instruction.Store, + StorageKind.StorageBuffer, + null, + new[] { Const(vbOutputBinding), Const(0), vertexOffset, value })); + } + + return newNode; + } + + private static LinkedListNode GenerateEndPrimitive(ShaderDefinitions definitions, ResourceManager resourceManager, LinkedListNode node) + { + int ibOutputBinding = resourceManager.Reservations.GeometryIndexOutputStorageBufferBinding; + + Operand outputPrimIndex = IncrementLocalMemory(node, resourceManager.LocalGeometryOutputIndexCountMemoryId); + Operand baseIndexOffset = GenerateBaseOffset( + resourceManager, + node, + definitions.GetGeometryOutputIndexBufferStride(), + definitions.ThreadsPerInputPrimitive); + Operand outputBaseIndex = Local(); + node.List.AddBefore(node, new Operation(Instruction.Add, outputBaseIndex, new[] { baseIndexOffset, outputPrimIndex })); + + return node.List.AddBefore(node, new Operation( + Instruction.Store, + StorageKind.StorageBuffer, + null, + new[] { Const(ibOutputBinding), Const(0), outputBaseIndex, Const(-1) })); + } + + private static Operand GenerateBaseOffset(ResourceManager resourceManager, LinkedListNode node, int stride, int threadsPerInputPrimitive) + { + Operand primitiveId = Local(); + GeneratePrimitiveId(resourceManager, node, primitiveId); + + Operand baseOffset = Local(); + node.List.AddBefore(node, new Operation(Instruction.Multiply, baseOffset, new[] { primitiveId, Const(stride) })); + + Operand invocationId = Local(); + GenerateInvocationId(node, invocationId); + + Operand invocationOffset = Local(); + node.List.AddBefore(node, new Operation(Instruction.Multiply, invocationOffset, new[] { invocationId, Const(stride / threadsPerInputPrimitive) })); + + Operand combinedOffset = Local(); + node.List.AddBefore(node, new Operation(Instruction.Add, combinedOffset, new[] { baseOffset, invocationOffset })); + + return combinedOffset; + } + + private static Operand IncrementLocalMemory(LinkedListNode node, int memoryId) + { + Operand oldValue = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.LocalMemory, + oldValue, + new[] { Const(memoryId) })); + + Operand newValue = Local(); + node.List.AddBefore(node, new Operation(Instruction.Add, newValue, new[] { oldValue, Const(1) })); + + node.List.AddBefore(node, new Operation(Instruction.Store, StorageKind.LocalMemory, null, new[] { Const(memoryId), newValue })); + + return oldValue; + } + + private static Operand GenerateVertexOffset( + ResourceManager resourceManager, + LinkedListNode node, + int elementOffset, + Operand primVertex) + { + int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding; + + Operand vertexCount = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.ConstantBuffer, + vertexCount, + new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexCounts), Const(0) })); + + Operand primInputVertex = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.LocalMemory, + primInputVertex, + new[] { Const(resourceManager.LocalTopologyRemapMemoryId), primVertex })); + + Operand instanceIndex = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.Input, + instanceIndex, + new[] { Const((int)IoVariable.GlobalId), Const(1) })); + + Operand baseVertex = Local(); + node.List.AddBefore(node, new Operation(Instruction.Multiply, baseVertex, new[] { instanceIndex, vertexCount })); + + Operand vertexIndex = Local(); + node.List.AddBefore(node, new Operation(Instruction.Add, vertexIndex, new[] { baseVertex, primInputVertex })); + + Operand vertexBaseOffset = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Multiply, + vertexBaseOffset, + new[] { vertexIndex, Const(resourceManager.Reservations.InputSizePerInvocation) })); + + Operand vertexElemOffset; + + if (elementOffset != 0) + { + vertexElemOffset = Local(); + + node.List.AddBefore(node, new Operation(Instruction.Add, vertexElemOffset, new[] { vertexBaseOffset, Const(elementOffset) })); + } + else + { + vertexElemOffset = vertexBaseOffset; + } + + return vertexElemOffset; + } + + private static LinkedListNode GeneratePrimitiveId(ResourceManager resourceManager, LinkedListNode node, Operand dest) + { + int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding; + + Operand vertexCount = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.ConstantBuffer, + vertexCount, + new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexCounts), Const(0) })); + + Operand vertexIndex = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.Input, + vertexIndex, + new[] { Const((int)IoVariable.GlobalId), Const(0) })); + + Operand instanceIndex = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.Input, + instanceIndex, + new[] { Const((int)IoVariable.GlobalId), Const(1) })); + + Operand baseVertex = Local(); + node.List.AddBefore(node, new Operation(Instruction.Multiply, baseVertex, new[] { instanceIndex, vertexCount })); + + return node.List.AddBefore(node, new Operation(Instruction.Add, dest, new[] { baseVertex, vertexIndex })); + } + + private static LinkedListNode GenerateInvocationId(LinkedListNode node, Operand dest) + { + return node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.Input, + dest, + new[] { Const((int)IoVariable.GlobalId), Const(2) })); + } + + private static bool TryGetOffset(ResourceManager resourceManager, Operation operation, StorageKind storageKind, out int outputOffset) + { + bool isStore = operation.Inst == Instruction.Store; + + IoVariable ioVariable = (IoVariable)operation.GetSource(0).Value; + + bool isValidOutput; + + if (ioVariable == IoVariable.UserDefined) + { + int lastIndex = operation.SourcesCount - (isStore ? 2 : 1); + + int location = operation.GetSource(1).Value; + int component = operation.GetSource(lastIndex).Value; + + isValidOutput = resourceManager.Reservations.TryGetOffset(storageKind, location, component, out outputOffset); + } + else + { + if (ResourceReservations.IsVectorOrArrayVariable(ioVariable)) + { + int component = operation.GetSource(operation.SourcesCount - (isStore ? 2 : 1)).Value; + + isValidOutput = resourceManager.Reservations.TryGetOffset(storageKind, ioVariable, component, out outputOffset); + } + else + { + isValidOutput = resourceManager.Reservations.TryGetOffset(storageKind, ioVariable, out outputOffset); + } + } + + return isValidOutput; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs index 5ceed4b7f..2479d85f9 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs @@ -153,15 +153,13 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - if (isBindless) + if (isBindless || !resourceManager.TryGetCbufSlotAndHandleForTexture(texOp.Binding, out int cbufSlot, out int handle)) { return node; } bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; - (int cbufSlot, int handle) = resourceManager.GetCbufSlotAndHandleForTexture(texOp.Binding); - bool isCoordNormalized = gpuAccessor.QueryTextureCoordNormalized(handle, cbufSlot); if (isCoordNormalized || intCoords) @@ -607,13 +605,11 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms // We can't query the format of a bindless texture, // because the handle is unknown, it can have any format. - if (texOp.Flags.HasFlag(TextureFlags.Bindless)) + if (texOp.Flags.HasFlag(TextureFlags.Bindless) || !resourceManager.TryGetCbufSlotAndHandleForTexture(texOp.Binding, out int cbufSlot, out int handle)) { return node; } - (int cbufSlot, int handle) = resourceManager.GetCbufSlotAndHandleForTexture(texOp.Binding); - TextureFormat format = gpuAccessor.QueryTextureFormat(handle, cbufSlot); int maxPositive = format switch diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TransformPasses.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TransformPasses.cs index 293938807..7ff3b8bf1 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TransformPasses.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TransformPasses.cs @@ -14,6 +14,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms RunPass(context); RunPass(context); RunPass(context); + RunPass(context); + RunPass(context); } private static void RunPass(TransformContext context) where T : ITransformPass diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/VertexToCompute.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/VertexToCompute.cs new file mode 100644 index 000000000..d71ada865 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/VertexToCompute.cs @@ -0,0 +1,364 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation.Optimizations; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Transforms +{ + class VertexToCompute : ITransformPass + { + public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures) + { + return usedFeatures.HasFlag(FeatureFlags.VtgAsCompute); + } + + public static LinkedListNode RunPass(TransformContext context, LinkedListNode node) + { + if (context.Definitions.Stage != ShaderStage.Vertex) + { + return node; + } + + Operation operation = (Operation)node.Value; + + LinkedListNode newNode = node; + + if (operation.Inst == Instruction.Load && operation.StorageKind == StorageKind.Input) + { + Operand dest = operation.Dest; + + switch ((IoVariable)operation.GetSource(0).Value) + { + case IoVariable.BaseInstance: + newNode = GenerateBaseInstanceLoad(context.ResourceManager, node, dest); + break; + case IoVariable.BaseVertex: + newNode = GenerateBaseVertexLoad(context.ResourceManager, node, dest); + break; + case IoVariable.InstanceId: + newNode = GenerateInstanceIdLoad(node, dest); + break; + case IoVariable.InstanceIndex: + newNode = GenerateInstanceIndexLoad(context.ResourceManager, node, dest); + break; + case IoVariable.VertexId: + case IoVariable.VertexIndex: + newNode = GenerateVertexIndexLoad(context.ResourceManager, node, dest); + break; + case IoVariable.UserDefined: + int location = operation.GetSource(1).Value; + int component = operation.GetSource(2).Value; + + if (context.Definitions.IsAttributePacked(location)) + { + bool needsSextNorm = context.Definitions.IsAttributePackedRgb10A2Signed(location); + + Operand temp = needsSextNorm ? Local() : dest; + Operand vertexElemOffset = GenerateVertexOffset(context.ResourceManager, node, location, 0); + + newNode = node.List.AddBefore(node, new TextureOperation( + Instruction.TextureSample, + SamplerType.TextureBuffer, + TextureFormat.Unknown, + TextureFlags.IntCoords, + context.ResourceManager.Reservations.GetVertexBufferTextureBinding(location), + 1 << component, + new[] { temp }, + new[] { vertexElemOffset })); + + if (needsSextNorm) + { + bool sint = context.Definitions.IsAttributeSint(location); + CopySignExtendedNormalized(node, component == 3 ? 2 : 10, !sint, dest, temp); + } + } + else + { + Operand temp = component > 0 ? Local() : dest; + Operand vertexElemOffset = GenerateVertexOffset(context.ResourceManager, node, location, component); + + newNode = node.List.AddBefore(node, new TextureOperation( + Instruction.TextureSample, + SamplerType.TextureBuffer, + TextureFormat.Unknown, + TextureFlags.IntCoords, + context.ResourceManager.Reservations.GetVertexBufferTextureBinding(location), + 1, + new[] { temp }, + new[] { vertexElemOffset })); + + if (component > 0) + { + newNode = CopyMasked(context.ResourceManager, newNode, location, component, dest, temp); + } + } + break; + case IoVariable.GlobalId: + case IoVariable.SubgroupEqMask: + case IoVariable.SubgroupGeMask: + case IoVariable.SubgroupGtMask: + case IoVariable.SubgroupLaneId: + case IoVariable.SubgroupLeMask: + case IoVariable.SubgroupLtMask: + // Those are valid or expected for vertex shaders. + break; + default: + context.GpuAccessor.Log($"Invalid input \"{(IoVariable)operation.GetSource(0).Value}\"."); + break; + } + } + else if (operation.Inst == Instruction.Load && operation.StorageKind == StorageKind.Output) + { + if (TryGetOutputOffset(context.ResourceManager, operation, out int outputOffset)) + { + newNode = node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.LocalMemory, + operation.Dest, + new[] { Const(context.ResourceManager.LocalVertexDataMemoryId), Const(outputOffset) })); + } + else + { + context.GpuAccessor.Log($"Invalid output \"{(IoVariable)operation.GetSource(0).Value}\"."); + } + } + else if (operation.Inst == Instruction.Store && operation.StorageKind == StorageKind.Output) + { + if (TryGetOutputOffset(context.ResourceManager, operation, out int outputOffset)) + { + Operand value = operation.GetSource(operation.SourcesCount - 1); + + newNode = node.List.AddBefore(node, new Operation( + Instruction.Store, + StorageKind.LocalMemory, + (Operand)null, + new[] { Const(context.ResourceManager.LocalVertexDataMemoryId), Const(outputOffset), value })); + } + else + { + context.GpuAccessor.Log($"Invalid output \"{(IoVariable)operation.GetSource(0).Value}\"."); + } + } + + if (newNode != node) + { + Utils.DeleteNode(node, operation); + } + + return newNode; + } + + private static Operand GenerateVertexOffset(ResourceManager resourceManager, LinkedListNode node, int location, int component) + { + int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding; + + Operand vertexIdVr = Local(); + GenerateVertexIdVertexRateLoad(resourceManager, node, vertexIdVr); + + Operand vertexIdIr = Local(); + GenerateVertexIdInstanceRateLoad(resourceManager, node, vertexIdIr); + + Operand attributeOffset = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.ConstantBuffer, + attributeOffset, + new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexOffsets), Const(location), Const(0) })); + + Operand isInstanceRate = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.ConstantBuffer, + isInstanceRate, + new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexOffsets), Const(location), Const(1) })); + + Operand vertexId = Local(); + node.List.AddBefore(node, new Operation( + Instruction.ConditionalSelect, + vertexId, + new[] { isInstanceRate, vertexIdIr, vertexIdVr })); + + Operand vertexStride = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.ConstantBuffer, + vertexStride, + new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexStrides), Const(location), Const(0) })); + + Operand vertexBaseOffset = Local(); + node.List.AddBefore(node, new Operation(Instruction.Multiply, vertexBaseOffset, new[] { vertexId, vertexStride })); + + Operand vertexOffset = Local(); + node.List.AddBefore(node, new Operation(Instruction.Add, vertexOffset, new[] { attributeOffset, vertexBaseOffset })); + + Operand vertexElemOffset; + + if (component != 0) + { + vertexElemOffset = Local(); + + node.List.AddBefore(node, new Operation(Instruction.Add, vertexElemOffset, new[] { vertexOffset, Const(component) })); + } + else + { + vertexElemOffset = vertexOffset; + } + + return vertexElemOffset; + } + + private static LinkedListNode CopySignExtendedNormalized(LinkedListNode node, int bits, bool normalize, Operand dest, Operand src) + { + Operand leftShifted = Local(); + node = node.List.AddAfter(node, new Operation( + Instruction.ShiftLeft, + leftShifted, + new[] { src, Const(32 - bits) })); + + Operand rightShifted = normalize ? Local() : dest; + node = node.List.AddAfter(node, new Operation( + Instruction.ShiftRightS32, + rightShifted, + new[] { leftShifted, Const(32 - bits) })); + + if (normalize) + { + Operand asFloat = Local(); + node = node.List.AddAfter(node, new Operation(Instruction.ConvertS32ToFP32, asFloat, new[] { rightShifted })); + node = node.List.AddAfter(node, new Operation( + Instruction.FP32 | Instruction.Multiply, + dest, + new[] { asFloat, ConstF(1f / (1 << (bits - 1))) })); + } + + return node; + } + + private static LinkedListNode CopyMasked( + ResourceManager resourceManager, + LinkedListNode node, + int location, + int component, + Operand dest, + Operand src) + { + Operand componentExists = Local(); + int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding; + node = node.List.AddAfter(node, new Operation( + Instruction.Load, + StorageKind.ConstantBuffer, + componentExists, + new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexStrides), Const(location), Const(component) })); + + return node.List.AddAfter(node, new Operation( + Instruction.ConditionalSelect, + dest, + new[] { componentExists, src, ConstF(component == 3 ? 1f : 0f) })); + } + + private static LinkedListNode GenerateBaseVertexLoad(ResourceManager resourceManager, LinkedListNode node, Operand dest) + { + int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding; + + return node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.ConstantBuffer, + dest, + new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexCounts), Const(2) })); + } + + private static LinkedListNode GenerateBaseInstanceLoad(ResourceManager resourceManager, LinkedListNode node, Operand dest) + { + int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding; + + return node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.ConstantBuffer, + dest, + new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexCounts), Const(3) })); + } + + private static LinkedListNode GenerateVertexIndexLoad(ResourceManager resourceManager, LinkedListNode node, Operand dest) + { + Operand baseVertex = Local(); + Operand vertexId = Local(); + + GenerateBaseVertexLoad(resourceManager, node, baseVertex); + GenerateVertexIdVertexRateLoad(resourceManager, node, vertexId); + + return node.List.AddBefore(node, new Operation(Instruction.Add, dest, new[] { baseVertex, vertexId })); + } + + private static LinkedListNode GenerateInstanceIndexLoad(ResourceManager resourceManager, LinkedListNode node, Operand dest) + { + Operand baseInstance = Local(); + Operand instanceId = Local(); + + GenerateBaseInstanceLoad(resourceManager, node, baseInstance); + + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.Input, + instanceId, + new[] { Const((int)IoVariable.GlobalId), Const(1) })); + + return node.List.AddBefore(node, new Operation(Instruction.Add, dest, new[] { baseInstance, instanceId })); + } + + private static LinkedListNode GenerateVertexIdVertexRateLoad(ResourceManager resourceManager, LinkedListNode node, Operand dest) + { + Operand[] sources = new Operand[] { Const(resourceManager.LocalVertexIndexVertexRateMemoryId) }; + + return node.List.AddBefore(node, new Operation(Instruction.Load, StorageKind.LocalMemory, dest, sources)); + } + + private static LinkedListNode GenerateVertexIdInstanceRateLoad(ResourceManager resourceManager, LinkedListNode node, Operand dest) + { + Operand[] sources = new Operand[] { Const(resourceManager.LocalVertexIndexInstanceRateMemoryId) }; + + return node.List.AddBefore(node, new Operation(Instruction.Load, StorageKind.LocalMemory, dest, sources)); + } + + private static LinkedListNode GenerateInstanceIdLoad(LinkedListNode node, Operand dest) + { + Operand[] sources = new Operand[] { Const((int)IoVariable.GlobalId), Const(1) }; + + return node.List.AddBefore(node, new Operation(Instruction.Load, StorageKind.Input, dest, sources)); + } + + private static bool TryGetOutputOffset(ResourceManager resourceManager, Operation operation, out int outputOffset) + { + bool isStore = operation.Inst == Instruction.Store; + + IoVariable ioVariable = (IoVariable)operation.GetSource(0).Value; + + bool isValidOutput; + + if (ioVariable == IoVariable.UserDefined) + { + int lastIndex = operation.SourcesCount - (isStore ? 2 : 1); + + int location = operation.GetSource(1).Value; + int component = operation.GetSource(lastIndex).Value; + + isValidOutput = resourceManager.Reservations.TryGetOffset(StorageKind.Output, location, component, out outputOffset); + } + else + { + if (ResourceReservations.IsVectorOrArrayVariable(ioVariable)) + { + int component = operation.GetSource(operation.SourcesCount - (isStore ? 2 : 1)).Value; + + isValidOutput = resourceManager.Reservations.TryGetOffset(StorageKind.Output, ioVariable, component, out outputOffset); + } + else + { + isValidOutput = resourceManager.Reservations.TryGetOffset(StorageKind.Output, ioVariable, out outputOffset); + } + } + + return isValidOutput; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Translator.cs b/src/Ryujinx.Graphics.Shader/Translation/Translator.cs index 93a70ace8..6a31ea2e7 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Translator.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Translator.cs @@ -77,12 +77,32 @@ namespace Ryujinx.Graphics.Shader.Translation } private static ShaderDefinitions CreateGraphicsDefinitions(IGpuAccessor gpuAccessor, ShaderHeader header) + { + TransformFeedbackOutput[] transformFeedbackOutputs = GetTransformFeedbackOutputs(gpuAccessor, out ulong transformFeedbackVecMap); + + return new ShaderDefinitions( + header.Stage, + gpuAccessor.QueryGraphicsState(), + header.Stage == ShaderStage.Geometry && header.GpPassthrough, + header.ThreadsPerInputPrimitive, + header.OutputTopology, + header.MaxOutputVertexCount, + header.ImapTypes, + header.OmapTargets, + header.OmapSampleMask, + header.OmapDepth, + gpuAccessor.QueryHostSupportsScaledVertexFormats(), + transformFeedbackVecMap, + transformFeedbackOutputs); + } + + internal static TransformFeedbackOutput[] GetTransformFeedbackOutputs(IGpuAccessor gpuAccessor, out ulong transformFeedbackVecMap) { bool transformFeedbackEnabled = gpuAccessor.QueryTransformFeedbackEnabled() && gpuAccessor.QueryHostSupportsTransformFeedback(); TransformFeedbackOutput[] transformFeedbackOutputs = null; - ulong transformFeedbackVecMap = 0UL; + transformFeedbackVecMap = 0UL; if (transformFeedbackEnabled) { @@ -105,21 +125,7 @@ namespace Ryujinx.Graphics.Shader.Translation } } - return new ShaderDefinitions( - header.Stage, - gpuAccessor.QueryGraphicsState(), - header.Stage == ShaderStage.Geometry && header.GpPassthrough, - header.ThreadsPerInputPrimitive, - header.OutputTopology, - header.MaxOutputVertexCount, - header.ImapTypes, - header.OmapTargets, - header.OmapSampleMask, - header.OmapDepth, - gpuAccessor.QueryHostSupportsScaledVertexFormats(), - transformFeedbackEnabled, - transformFeedbackVecMap, - transformFeedbackOutputs); + return transformFeedbackOutputs; } private static int GetLocalMemorySize(ShaderHeader header) @@ -131,6 +137,7 @@ namespace Ryujinx.Graphics.Shader.Translation TranslatorContext translatorContext, ResourceManager resourceManager, DecodedProgram program, + bool vertexAsCompute, bool initializeOutputs, out int initializationOperations) { @@ -147,7 +154,7 @@ namespace Ryujinx.Graphics.Shader.Translation for (int index = 0; index < functions.Length; index++) { - EmitterContext context = new(translatorContext, resourceManager, program, index != 0); + EmitterContext context = new(translatorContext, resourceManager, program, vertexAsCompute, index != 0); if (initializeOutputs && index == 0) { diff --git a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs index 39ce92c9d..f1226ae6d 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs @@ -8,7 +8,6 @@ using Ryujinx.Graphics.Shader.Translation.Optimizations; using Ryujinx.Graphics.Shader.Translation.Transforms; using System; using System.Collections.Generic; -using System.Linq; using System.Numerics; using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; using static Ryujinx.Graphics.Shader.Translation.Translator; @@ -19,14 +18,12 @@ namespace Ryujinx.Graphics.Shader.Translation { private readonly DecodedProgram _program; private readonly int _localMemorySize; + private IoUsage _vertexOutput; public ulong Address { get; } public int Size { get; } public int Cb1DataSize => _program.Cb1DataSize; - internal bool HasLayerInputAttribute { get; private set; } - internal int GpLayerInputAttribute { get; private set; } - internal AttributeUsage AttributeUsage => _program.AttributeUsage; internal ShaderDefinitions Definitions { get; } @@ -37,7 +34,8 @@ namespace Ryujinx.Graphics.Shader.Translation internal TranslationOptions Options { get; } - internal FeatureFlags UsedFeatures { get; private set; } + private bool IsTransformFeedbackEmulated => !GpuAccessor.QueryHostSupportsTransformFeedback() && GpuAccessor.QueryTransformFeedbackEnabled(); + public bool HasStore => _program.UsedFeatures.HasFlag(FeatureFlags.Store) || (IsTransformFeedbackEmulated && Definitions.LastInVertexPipeline); public bool LayerOutputWritten { get; private set; } public int LayerOutputAttribute { get; private set; } @@ -55,10 +53,10 @@ namespace Ryujinx.Graphics.Shader.Translation Size = size; _program = program; _localMemorySize = localMemorySize; + _vertexOutput = new IoUsage(FeatureFlags.None, 0, -1); Definitions = definitions; GpuAccessor = gpuAccessor; Options = options; - UsedFeatures = program.UsedFeatures; } private static bool IsLoadUserDefined(Operation operation) @@ -171,13 +169,6 @@ namespace Ryujinx.Graphics.Shader.Translation LayerOutputAttribute = attr; } - public void SetGeometryShaderLayerInputAttribute(int attr) - { - UsedFeatures |= FeatureFlags.RtLayer; - HasLayerInputAttribute = true; - GpLayerInputAttribute = attr; - } - public void SetLastInVertexPipeline() { Definitions.LastInVertexPipeline = true; @@ -187,7 +178,7 @@ namespace Ryujinx.Graphics.Shader.Translation { AttributeUsage.MergeFromtNextStage( Definitions.GpPassthrough, - nextStage.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr), + nextStage._program.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr), nextStage.AttributeUsage); // We don't consider geometry shaders using the geometry shader passthrough feature @@ -200,9 +191,9 @@ namespace Ryujinx.Graphics.Shader.Translation } } - public ShaderProgram Translate() + public ShaderProgram Translate(bool asCompute = false) { - ResourceManager resourceManager = CreateResourceManager(); + ResourceManager resourceManager = CreateResourceManager(asCompute); bool usesLocalMemory = _program.UsedFeatures.HasFlag(FeatureFlags.LocalMemory); @@ -215,36 +206,42 @@ namespace Ryujinx.Graphics.Shader.Translation resourceManager.SetCurrentSharedMemory(GpuAccessor.QueryComputeSharedMemorySize(), usesSharedMemory); } - FunctionCode[] code = EmitShader(this, resourceManager, _program, initializeOutputs: true, out _); + FunctionCode[] code = EmitShader(this, resourceManager, _program, asCompute, initializeOutputs: true, out _); - return Translate(code, resourceManager, UsedFeatures, _program.ClipDistancesWritten); + return Translate(code, resourceManager, _program.UsedFeatures, _program.ClipDistancesWritten, asCompute); } - public ShaderProgram Translate(TranslatorContext other) + public ShaderProgram Translate(TranslatorContext other, bool asCompute = false) { - ResourceManager resourceManager = CreateResourceManager(); + ResourceManager resourceManager = CreateResourceManager(asCompute); bool usesLocalMemory = _program.UsedFeatures.HasFlag(FeatureFlags.LocalMemory); resourceManager.SetCurrentLocalMemory(_localMemorySize, usesLocalMemory); - FunctionCode[] code = EmitShader(this, resourceManager, _program, initializeOutputs: false, out _); + FunctionCode[] code = EmitShader(this, resourceManager, _program, asCompute, initializeOutputs: false, out _); bool otherUsesLocalMemory = other._program.UsedFeatures.HasFlag(FeatureFlags.LocalMemory); resourceManager.SetCurrentLocalMemory(other._localMemorySize, otherUsesLocalMemory); - FunctionCode[] otherCode = EmitShader(other, resourceManager, other._program, initializeOutputs: true, out int aStart); + FunctionCode[] otherCode = EmitShader(other, resourceManager, other._program, asCompute, initializeOutputs: true, out int aStart); code = Combine(otherCode, code, aStart); return Translate( code, resourceManager, - UsedFeatures | other.UsedFeatures, - (byte)(_program.ClipDistancesWritten | other._program.ClipDistancesWritten)); + _program.UsedFeatures | other._program.UsedFeatures, + (byte)(_program.ClipDistancesWritten | other._program.ClipDistancesWritten), + asCompute); } - private ShaderProgram Translate(FunctionCode[] functions, ResourceManager resourceManager, FeatureFlags usedFeatures, byte clipDistancesWritten) + private ShaderProgram Translate(FunctionCode[] functions, ResourceManager resourceManager, FeatureFlags usedFeatures, byte clipDistancesWritten, bool asCompute) { + if (asCompute) + { + usedFeatures |= FeatureFlags.VtgAsCompute; + } + var cfgs = new ControlFlowGraph[functions.Length]; var frus = new RegisterUsage.FunctionRegisterUsage[functions.Length]; @@ -294,6 +291,7 @@ namespace Ryujinx.Graphics.Shader.Translation TransformContext context = new( hfm, cfg.Blocks, + Definitions, resourceManager, GpuAccessor, Options.TargetLanguage, @@ -307,28 +305,24 @@ namespace Ryujinx.Graphics.Shader.Translation funcs[i] = new Function(cfg.Blocks, $"fun{i}", false, inArgumentsCount, outArgumentsCount); } - var identification = ShaderIdentifier.Identify(funcs, GpuAccessor, Definitions.Stage, Definitions.InputTopology, out int layerInputAttr); - return Generate( funcs, AttributeUsage, + GetDefinitions(asCompute), Definitions, resourceManager, usedFeatures, - clipDistancesWritten, - identification, - layerInputAttr); + clipDistancesWritten); } private ShaderProgram Generate( IReadOnlyList funcs, AttributeUsage attributeUsage, ShaderDefinitions definitions, + ShaderDefinitions originalDefinitions, ResourceManager resourceManager, FeatureFlags usedFeatures, - byte clipDistancesWritten, - ShaderIdentification identification = ShaderIdentification.None, - int layerInputAttr = 0) + byte clipDistancesWritten) { var sInfo = StructuredProgram.MakeStructuredProgram( funcs, @@ -337,20 +331,28 @@ namespace Ryujinx.Graphics.Shader.Translation resourceManager, Options.Flags.HasFlag(TranslationFlags.DebugMode)); + int geometryVerticesPerPrimitive = Definitions.OutputTopology switch + { + OutputTopology.LineStrip => 2, + OutputTopology.TriangleStrip => 3, + _ => 1 + }; + var info = new ShaderProgramInfo( resourceManager.GetConstantBufferDescriptors(), resourceManager.GetStorageBufferDescriptors(), resourceManager.GetTextureDescriptors(), resourceManager.GetImageDescriptors(), - identification, - layerInputAttr, - definitions.Stage, + originalDefinitions.Stage, + geometryVerticesPerPrimitive, + originalDefinitions.MaxOutputVertices, + originalDefinitions.ThreadsPerInputPrimitive, usedFeatures.HasFlag(FeatureFlags.FragCoordXY), usedFeatures.HasFlag(FeatureFlags.InstanceId), usedFeatures.HasFlag(FeatureFlags.DrawParameters), usedFeatures.HasFlag(FeatureFlags.RtLayer), clipDistancesWritten, - definitions.OmapTargets); + originalDefinitions.OmapTargets); var hostCapabilities = new HostCapabilities( GpuAccessor.QueryHostReducedPrecision(), @@ -372,37 +374,203 @@ namespace Ryujinx.Graphics.Shader.Translation }; } - private ResourceManager CreateResourceManager() + private ResourceManager CreateResourceManager(bool vertexAsCompute) { - ResourceManager resourceManager = new(Definitions.Stage, GpuAccessor); + ResourceManager resourceManager = new(Definitions.Stage, GpuAccessor, GetResourceReservations()); - if (!GpuAccessor.QueryHostSupportsTransformFeedback() && GpuAccessor.QueryTransformFeedbackEnabled()) + if (IsTransformFeedbackEmulated) { - StructureType tfeInfoStruct = new(new StructureField[] - { - new StructureField(AggregateType.Array | AggregateType.U32, "base_offset", 4), - new StructureField(AggregateType.U32, "vertex_count") - }); - - BufferDefinition tfeInfoBuffer = new(BufferLayout.Std430, 1, Constants.TfeInfoBinding, "tfe_info", tfeInfoStruct); - resourceManager.Properties.AddOrUpdateStorageBuffer(tfeInfoBuffer); - StructureType tfeDataStruct = new(new StructureField[] { new StructureField(AggregateType.Array | AggregateType.U32, "data", 0) }); - for (int i = 0; i < Constants.TfeBuffersCount; i++) + for (int i = 0; i < ResourceReservations.TfeBuffersCount; i++) { - int binding = Constants.TfeBufferBaseBinding + i; + int binding = resourceManager.Reservations.GetTfeBufferStorageBufferBinding(i); BufferDefinition tfeDataBuffer = new(BufferLayout.Std430, 1, binding, $"tfe_data{i}", tfeDataStruct); resourceManager.Properties.AddOrUpdateStorageBuffer(tfeDataBuffer); } } + if (vertexAsCompute) + { + int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding; + BufferDefinition vertexInfoBuffer = new(BufferLayout.Std140, 0, vertexInfoCbBinding, "vb_info", VertexInfoBuffer.GetStructureType()); + resourceManager.Properties.AddOrUpdateConstantBuffer(vertexInfoBuffer); + + StructureType vertexOutputStruct = new(new StructureField[] + { + new StructureField(AggregateType.Array | AggregateType.FP32, "data", 0) + }); + + int vertexOutputSbBinding = resourceManager.Reservations.VertexOutputStorageBufferBinding; + BufferDefinition vertexOutputBuffer = new(BufferLayout.Std430, 1, vertexOutputSbBinding, "vertex_output", vertexOutputStruct); + resourceManager.Properties.AddOrUpdateStorageBuffer(vertexOutputBuffer); + + if (Stage == ShaderStage.Vertex) + { + int ibBinding = resourceManager.Reservations.IndexBufferTextureBinding; + TextureDefinition indexBuffer = new(2, ibBinding, "ib_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); + resourceManager.Properties.AddOrUpdateTexture(indexBuffer); + + int inputMap = _program.AttributeUsage.UsedInputAttributes; + + while (inputMap != 0) + { + int location = BitOperations.TrailingZeroCount(inputMap); + int binding = resourceManager.Reservations.GetVertexBufferTextureBinding(location); + TextureDefinition vaBuffer = new(2, binding, $"vb_data{location}", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); + resourceManager.Properties.AddOrUpdateTexture(vaBuffer); + + inputMap &= ~(1 << location); + } + } + else if (Stage == ShaderStage.Geometry) + { + int trbBinding = resourceManager.Reservations.TopologyRemapBufferTextureBinding; + TextureDefinition remapBuffer = new(2, trbBinding, "trb_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None); + resourceManager.Properties.AddOrUpdateTexture(remapBuffer); + + int geometryVbOutputSbBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding; + BufferDefinition geometryVbOutputBuffer = new(BufferLayout.Std430, 1, geometryVbOutputSbBinding, "geometry_vb_output", vertexOutputStruct); + resourceManager.Properties.AddOrUpdateStorageBuffer(geometryVbOutputBuffer); + + StructureType geometryIbOutputStruct = new(new StructureField[] + { + new StructureField(AggregateType.Array | AggregateType.U32, "data", 0) + }); + + int geometryIbOutputSbBinding = resourceManager.Reservations.GeometryIndexOutputStorageBufferBinding; + BufferDefinition geometryIbOutputBuffer = new(BufferLayout.Std430, 1, geometryIbOutputSbBinding, "geometry_ib_output", geometryIbOutputStruct); + resourceManager.Properties.AddOrUpdateStorageBuffer(geometryIbOutputBuffer); + } + + resourceManager.SetVertexAsComputeLocalMemories(Definitions.Stage, Definitions.InputTopology); + } + return resourceManager; } + private ShaderDefinitions GetDefinitions(bool vertexAsCompute) + { + if (vertexAsCompute) + { + return new ShaderDefinitions(ShaderStage.Compute, 32, 32, 1); + } + else + { + return Definitions; + } + } + + public ResourceReservations GetResourceReservations() + { + IoUsage ioUsage = _program.GetIoUsage(); + + if (Definitions.GpPassthrough) + { + ioUsage = ioUsage.Combine(_vertexOutput); + } + + return new ResourceReservations(GpuAccessor, IsTransformFeedbackEmulated, vertexAsCompute: true, _vertexOutput, ioUsage); + } + + public void SetVertexOutputMapForGeometryAsCompute(TranslatorContext vertexContext) + { + _vertexOutput = vertexContext._program.GetIoUsage(); + } + + public ShaderProgram GenerateVertexPassthroughForCompute() + { + var attributeUsage = new AttributeUsage(GpuAccessor); + var resourceManager = new ResourceManager(ShaderStage.Vertex, GpuAccessor); + + var reservations = GetResourceReservations(); + + int vertexInfoCbBinding = reservations.VertexInfoConstantBufferBinding; + + if (Stage == ShaderStage.Vertex) + { + BufferDefinition vertexInfoBuffer = new(BufferLayout.Std140, 0, vertexInfoCbBinding, "vb_info", VertexInfoBuffer.GetStructureType()); + resourceManager.Properties.AddOrUpdateConstantBuffer(vertexInfoBuffer); + } + + StructureType vertexInputStruct = new(new StructureField[] + { + new StructureField(AggregateType.Array | AggregateType.FP32, "data", 0) + }); + + int vertexDataSbBinding = reservations.VertexOutputStorageBufferBinding; + BufferDefinition vertexOutputBuffer = new(BufferLayout.Std430, 1, vertexDataSbBinding, "vb_input", vertexInputStruct); + resourceManager.Properties.AddOrUpdateStorageBuffer(vertexOutputBuffer); + + var context = new EmitterContext(); + + Operand vertexIndex = Options.TargetApi == TargetApi.OpenGL + ? context.Load(StorageKind.Input, IoVariable.VertexId) + : context.Load(StorageKind.Input, IoVariable.VertexIndex); + + if (Stage == ShaderStage.Vertex) + { + Operand vertexCount = context.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(0)); + + // Base instance will be always zero when this shader is used, so which one we use here doesn't really matter. + Operand instanceId = Options.TargetApi == TargetApi.OpenGL + ? context.Load(StorageKind.Input, IoVariable.InstanceId) + : context.Load(StorageKind.Input, IoVariable.InstanceIndex); + + vertexIndex = context.IAdd(context.IMultiply(instanceId, vertexCount), vertexIndex); + } + + Operand baseOffset = context.IMultiply(vertexIndex, Const(reservations.OutputSizePerInvocation)); + + foreach ((IoDefinition ioDefinition, int inputOffset) in reservations.Offsets) + { + if (ioDefinition.StorageKind != StorageKind.Output) + { + continue; + } + + Operand vertexOffset = inputOffset != 0 ? context.IAdd(baseOffset, Const(inputOffset)) : baseOffset; + Operand value = context.Load(StorageKind.StorageBuffer, vertexDataSbBinding, Const(0), vertexOffset); + + if (ioDefinition.IoVariable == IoVariable.UserDefined) + { + context.Store(StorageKind.Output, ioDefinition.IoVariable, null, Const(ioDefinition.Location), Const(ioDefinition.Component), value); + attributeUsage.SetOutputUserAttribute(ioDefinition.Location); + } + else if (ResourceReservations.IsVectorOrArrayVariable(ioDefinition.IoVariable)) + { + context.Store(StorageKind.Output, ioDefinition.IoVariable, null, Const(ioDefinition.Component), value); + } + else + { + context.Store(StorageKind.Output, ioDefinition.IoVariable, null, value); + } + } + + var operations = context.GetOperations(); + var cfg = ControlFlowGraph.Create(operations); + var function = new Function(cfg.Blocks, "main", false, 0, 0); + + var transformFeedbackOutputs = GetTransformFeedbackOutputs(GpuAccessor, out ulong transformFeedbackVecMap); + + var definitions = new ShaderDefinitions(ShaderStage.Vertex, transformFeedbackVecMap, transformFeedbackOutputs) + { + LastInVertexPipeline = true + }; + + return Generate( + new[] { function }, + attributeUsage, + definitions, + definitions, + resourceManager, + FeatureFlags.None, + 0); + } + public ShaderProgram GenerateGeometryPassthrough() { int outputAttributesMask = AttributeUsage.UsedOutputAttributes; @@ -484,7 +652,14 @@ namespace Ryujinx.Graphics.Shader.Translation outputTopology, maxOutputVertices); - return Generate(new[] { function }, attributeUsage, definitions, resourceManager, FeatureFlags.RtLayer, 0); + return Generate( + new[] { function }, + attributeUsage, + definitions, + definitions, + resourceManager, + FeatureFlags.RtLayer, + 0); } } } diff --git a/src/Ryujinx.Graphics.Shader/VertexInfoBuffer.cs b/src/Ryujinx.Graphics.Shader/VertexInfoBuffer.cs new file mode 100644 index 000000000..845135f86 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/VertexInfoBuffer.cs @@ -0,0 +1,59 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Shader +{ + enum VertexInfoBufferField + { + // Must match the order of the fields on the struct. + VertexCounts, + GeometryCounts, + VertexStrides, + VertexOffsets, + } + + public struct VertexInfoBuffer + { + public static readonly int RequiredSize; + + public static readonly int VertexCountsOffset; + public static readonly int GeometryCountsOffset; + public static readonly int VertexStridesOffset; + public static readonly int VertexOffsetsOffset; + + private static int OffsetOf(ref VertexInfoBuffer storage, ref T target) + { + return (int)Unsafe.ByteOffset(ref Unsafe.As(ref storage), ref target); + } + + static VertexInfoBuffer() + { + RequiredSize = Unsafe.SizeOf(); + + VertexInfoBuffer instance = new(); + + VertexCountsOffset = OffsetOf(ref instance, ref instance.VertexCounts); + GeometryCountsOffset = OffsetOf(ref instance, ref instance.GeometryCounts); + VertexStridesOffset = OffsetOf(ref instance, ref instance.VertexStrides); + VertexOffsetsOffset = OffsetOf(ref instance, ref instance.VertexOffsets); + } + + internal static StructureType GetStructureType() + { + return new StructureType(new[] + { + new StructureField(AggregateType.Vector4 | AggregateType.U32, "vertex_counts"), + new StructureField(AggregateType.Vector4 | AggregateType.U32, "geometry_counts"), + new StructureField(AggregateType.Array | AggregateType.Vector4 | AggregateType.U32, "vertex_strides", ResourceReservations.MaxVertexBufferTextures), + new StructureField(AggregateType.Array | AggregateType.Vector4 | AggregateType.U32, "vertex_offsets", ResourceReservations.MaxVertexBufferTextures), + }); + } + + public Vector4 VertexCounts; + public Vector4 GeometryCounts; + public Array32> VertexStrides; + public Array32> VertexOffsets; + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 675512293..7b6b89a74 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -605,6 +605,7 @@ namespace Ryujinx.Graphics.Vulkan supportsShaderBarrierDivergence: Vendor != Vendor.Intel, supportsShaderFloat64: Capabilities.SupportsShaderFloat64, supportsTextureShadowLod: false, + supportsVertexStoreAndAtomics: features2.Features.VertexPipelineStoresAndAtomics, supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex, supportsViewportMask: Capabilities.SupportsViewportArray2, supportsViewportSwizzle: false, @@ -618,6 +619,7 @@ namespace Ryujinx.Graphics.Vulkan maximumSupportedAnisotropy: (int)limits.MaxSamplerAnisotropy, shaderSubgroupSize: (int)Capabilities.SubgroupSize, storageBufferOffsetAlignment: (int)limits.MinStorageBufferOffsetAlignment, + textureBufferOffsetAlignment: (int)limits.MinTexelBufferOffsetAlignment, gatherBiasPrecision: IsIntelWindows || IsAmdWindows ? (int)Capabilities.SubTexelPrecisionBits : 0); } diff --git a/src/Ryujinx.ShaderTools/Program.cs b/src/Ryujinx.ShaderTools/Program.cs index 55ff12bea..4211ab491 100644 --- a/src/Ryujinx.ShaderTools/Program.cs +++ b/src/Ryujinx.ShaderTools/Program.cs @@ -29,6 +29,12 @@ namespace Ryujinx.ShaderTools [Option("compute", Required = false, Default = false, HelpText = "Indicate that the shader is a compute shader.")] public bool Compute { get; set; } + [Option("vertex-as-compute", Required = false, Default = false, HelpText = "Indicate that the shader is a vertex shader and should be converted to compute.")] + public bool VertexAsCompute { get; set; } + + [Option("vertex-passthrough", Required = false, Default = false, HelpText = "Indicate that the shader is a vertex passthrough shader for compute output.")] + public bool VertexPassthrough { get; set; } + [Option("target-language", Required = false, Default = TargetLanguage.Glsl, HelpText = "Indicate the target shader language to use.")] public TargetLanguage TargetLanguage { get; set; } @@ -54,8 +60,18 @@ namespace Ryujinx.ShaderTools byte[] data = File.ReadAllBytes(options.InputPath); TranslationOptions translationOptions = new(options.TargetLanguage, options.TargetApi, flags); + TranslatorContext translatorContext = Translator.CreateContext(0, new GpuAccessor(data), translationOptions); - ShaderProgram program = Translator.CreateContext(0, new GpuAccessor(data), translationOptions).Translate(); + ShaderProgram program; + + if (options.VertexPassthrough) + { + program = translatorContext.GenerateVertexPassthroughForCompute(); + } + else + { + program = translatorContext.Translate(options.VertexAsCompute); + } if (options.OutputPath == null) { From 437c78e19840a9f4a9fdb4e491a8214019c536fe Mon Sep 17 00:00:00 2001 From: Ac_K Date: Wed, 30 Aug 2023 22:04:25 +0200 Subject: [PATCH 11/64] opus: Implement GetWorkBufferSizeExEx and GetWorkBufferSizeForMultiStreamExEx (#5624) * opus: Implement GetWorkBufferSizeExEx and GetWorkBufferSizeForMultiStreamExEx * Fix comments --- .../Audio/IHardwareOpusDecoderManager.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs index b69a23921..514b51a51 100644 --- a/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs @@ -154,6 +154,28 @@ namespace Ryujinx.HLE.HOS.Services.Audio return ResultCode.Success; } + [CommandCmif(8)] // 16.0.0+ + // GetWorkBufferSizeExEx(OpusParametersEx) -> u32 + public ResultCode GetWorkBufferSizeExEx(ServiceCtx context) + { + // NOTE: GetWorkBufferSizeEx use hardcoded values to compute the returned size. + // GetWorkBufferSizeExEx fixes that by using dynamic values. + // Since we're already doing that, it's fine to call it directly. + + return GetWorkBufferSizeEx(context); + } + + [CommandCmif(9)] // 16.0.0+ + // GetWorkBufferSizeForMultiStreamExEx(buffer, 0x19>) -> u32 + public ResultCode GetWorkBufferSizeForMultiStreamExEx(ServiceCtx context) + { + // NOTE: GetWorkBufferSizeForMultiStreamEx use hardcoded values to compute the returned size. + // GetWorkBufferSizeForMultiStreamExEx fixes that by using dynamic values. + // Since we're already doing that, it's fine to call it directly. + + return GetWorkBufferSizeForMultiStreamEx(context); + } + private static int GetOpusMultistreamDecoderSize(int streams, int coupledStreams) { if (streams < 1 || coupledStreams > streams || coupledStreams < 0) From 12cbacffca7b04a7f377e8fdf09ce78445351913 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> Date: Fri, 1 Sep 2023 19:08:42 +0100 Subject: [PATCH 12/64] Fix numeric SWKB validation (#5627) * Fix numeric swkbd validation * GTK --- src/Ryujinx.Ava/Assets/Locales/en_US.json | 2 +- .../UI/Applet/SwkbdAppletDialog.axaml.cs | 6 +++--- .../Applets/SoftwareKeyboard/KeyboardMode.cs | 4 ++-- .../NumericCharacterValidation.cs | 17 +++++++++++++++++ src/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs | 6 +++--- 5 files changed, 26 insertions(+), 9 deletions(-) create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/NumericCharacterValidation.cs diff --git a/src/Ryujinx.Ava/Assets/Locales/en_US.json b/src/Ryujinx.Ava/Assets/Locales/en_US.json index efd3187ad..35a69b8f5 100644 --- a/src/Ryujinx.Ava/Assets/Locales/en_US.json +++ b/src/Ryujinx.Ava/Assets/Locales/en_US.json @@ -544,7 +544,7 @@ "SwkbdMinCharacters": "Must be at least {0} characters long", "SwkbdMinRangeCharacters": "Must be {0}-{1} characters long", "SoftwareKeyboard": "Software Keyboard", - "SoftwareKeyboardModeNumbersOnly": "Must be numbers only", + "SoftwareKeyboardModeNumeric": "Must be 0-9 or '.' only", "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", "SoftwareKeyboardModeASCII": "Must be ASCII text only", "DialogControllerAppletMessagePlayerRange": "Application requests {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.", diff --git a/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs b/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs index 210fc9a2b..5a4cd855f 100644 --- a/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs +++ b/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs @@ -136,10 +136,10 @@ namespace Ryujinx.Ava.UI.Controls string localeText; switch (mode) { - case KeyboardMode.NumbersOnly: - localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeNumbersOnly); + case KeyboardMode.Numeric: + localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeNumeric); validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText); - _checkInput = text => text.All(char.IsDigit); + _checkInput = text => text.All(NumericCharacterValidation.IsNumeric); break; case KeyboardMode.Alphabet: localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeAlphabet); diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs index 917e1f9e1..c2dfc31a3 100644 --- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs @@ -11,9 +11,9 @@ Default = 0, /// - /// Only numbers allowed. + /// Only 0-9 or '.' allowed. /// - NumbersOnly = 1, + Numeric = 1, /// /// Only ASCII characters allowed. diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/NumericCharacterValidation.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/NumericCharacterValidation.cs new file mode 100644 index 000000000..d72b68eae --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/NumericCharacterValidation.cs @@ -0,0 +1,17 @@ +using System.Text.RegularExpressions; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + public static partial class NumericCharacterValidation + { + public static bool IsNumeric(char value) + { + Regex regex = NumericRegex(); + + return regex.IsMatch(value.ToString()); + } + + [GeneratedRegex("[0-9]|.")] + private static partial Regex NumericRegex(); + } +} diff --git a/src/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs b/src/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs index 1ed08250f..c1f3d77c1 100644 --- a/src/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs +++ b/src/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs @@ -90,9 +90,9 @@ namespace Ryujinx.Ui.Applet switch (mode) { - case KeyboardMode.NumbersOnly: - _validationInfoText += "Must be numbers only."; - _checkInput = text => text.All(char.IsDigit); + case KeyboardMode.Numeric: + _validationInfoText += "Must be 0-9 or '.' only."; + _checkInput = text => text.All(NumericCharacterValidation.IsNumeric); break; case KeyboardMode.Alphabet: _validationInfoText += "Must be non CJK-characters only."; From 93cd327873bed829c3a0aad938cb8d2cca2ff806 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Sat, 2 Sep 2023 21:58:15 +0100 Subject: [PATCH 13/64] Vulkan: Device Local and higher invocation count for buffer conversions (#5623) Just some simple changes to the buffer conversion shaders. (stride conversion, D32S8 to D24S8) The first change is using a device local buffer for converted vertex buffers, since they're only read/written on the GPU. These paths don't trigger on NVIDIA, but if you force them to use it demonstrates the full extent writing to host owned memory from compute absolutely destroys them. AMD GPUs are less heavily affected by this issue, but since the game in question was writing 230MB from compute, I imagine it should have some effect. The second change is allowing the buffer conversion shaders to scale their work group count. While dividing the work between 32 invocations works OK for M1 macs, it's not so great for anything with more cores like AMD GPUs, which should be able to do a lot more parallel copies. Now, it scales by roughly 100 elements per invocation. Some stride change cases could be improved further by either limiting vertex buffer size somehow (reading the index buffer could help, but is always risky) or only updating regions that changed, rather than invalidating the whole thing. --- src/Ryujinx.Graphics.Vulkan/BufferHolder.cs | 6 +++--- src/Ryujinx.Graphics.Vulkan/HelperShader.cs | 6 +++--- .../ChangeBufferStrideShaderSource.comp | 4 ++-- .../ConvertD32S8ToD24S8ShaderSource.comp | 4 ++-- .../SpirvBinaries/ChangeBufferStride.spv | Bin 3812 -> 3932 bytes .../SpirvBinaries/ConvertD32S8ToD24S8.spv | Bin 3236 -> 3356 bytes 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs b/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs index c767a57a7..a93ced0e5 100644 --- a/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs +++ b/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs @@ -967,7 +967,7 @@ namespace Ryujinx.Graphics.Vulkan if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder)) { - holder = _gd.BufferManager.Create(_gd, (size * 2 + 3) & ~3); + holder = _gd.BufferManager.Create(_gd, (size * 2 + 3) & ~3, baseType: BufferAllocationType.DeviceLocal); _gd.PipelineInternal.EndRenderPass(); _gd.HelperShader.ConvertI8ToI16(_gd, cbs, this, holder, offset, size); @@ -993,7 +993,7 @@ namespace Ryujinx.Graphics.Vulkan { int alignedStride = (stride + (alignment - 1)) & -alignment; - holder = _gd.BufferManager.Create(_gd, (size / stride) * alignedStride); + holder = _gd.BufferManager.Create(_gd, (size / stride) * alignedStride, baseType: BufferAllocationType.DeviceLocal); _gd.PipelineInternal.EndRenderPass(); _gd.HelperShader.ChangeStride(_gd, cbs, this, holder, offset, size, stride, alignedStride); @@ -1023,7 +1023,7 @@ namespace Ryujinx.Graphics.Vulkan int convertedCount = pattern.GetConvertedCount(indexCount); - holder = _gd.BufferManager.Create(_gd, convertedCount * 4); + holder = _gd.BufferManager.Create(_gd, convertedCount * 4, baseType: BufferAllocationType.DeviceLocal); _gd.PipelineInternal.EndRenderPass(); _gd.HelperShader.ConvertIndexBuffer(_gd, cbs, this, holder, pattern, indexSize, offset, indexCount); diff --git a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs index a6d237644..5be4a9329 100644 --- a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs +++ b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs @@ -5,7 +5,6 @@ using Ryujinx.Graphics.Shader.Translation; using Silk.NET.Vulkan; using System; using System.Collections.Generic; -using System.IO; using System.Numerics; using CompareOp = Ryujinx.Graphics.GAL.CompareOp; using Format = Ryujinx.Graphics.GAL.Format; @@ -27,6 +26,7 @@ namespace Ryujinx.Graphics.Vulkan class HelperShader : IDisposable { private const int UniformBufferAlignment = 256; + private const int ConvertElementsPerWorkgroup = 32 * 100; // Work group size of 32 times 100 elements. private const string ShaderBinariesPath = "Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries"; private readonly PipelineHelperShader _pipeline; @@ -894,7 +894,7 @@ namespace Ryujinx.Graphics.Vulkan _pipeline.SetStorageBuffers(1, sbRanges); _pipeline.SetProgram(_programStrideChange); - _pipeline.DispatchCompute(1, 1, 1); + _pipeline.DispatchCompute(1 + elems / ConvertElementsPerWorkgroup, 1, 1); gd.BufferManager.Delete(bufferHandle); @@ -1742,7 +1742,7 @@ namespace Ryujinx.Graphics.Vulkan _pipeline.SetStorageBuffers(1, sbRanges); _pipeline.SetProgram(_programConvertD32S8ToD24S8); - _pipeline.DispatchCompute(1, 1, 1); + _pipeline.DispatchCompute(1 + inSize / ConvertElementsPerWorkgroup, 1, 1); gd.BufferManager.Delete(bufferHandle); diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/ChangeBufferStrideShaderSource.comp b/src/Ryujinx.Graphics.Vulkan/Shaders/ChangeBufferStrideShaderSource.comp index 081fc119f..4deba30d9 100644 --- a/src/Ryujinx.Graphics.Vulkan/Shaders/ChangeBufferStrideShaderSource.comp +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/ChangeBufferStrideShaderSource.comp @@ -29,7 +29,7 @@ void main() int sourceOffset = stride_arguments_data.w; int strideRemainder = targetStride - sourceStride; - int invocations = int(gl_WorkGroupSize.x); + int invocations = int(gl_WorkGroupSize.x * gl_NumWorkGroups.x); int copiesRequired = bufferSize / sourceStride; @@ -39,7 +39,7 @@ void main() int allInvocationCopies = copiesRequired / invocations; // - Extra remainder copy that this invocation performs. - int index = int(gl_LocalInvocationID.x); + int index = int(gl_GlobalInvocationID.x); int extra = (index < (copiesRequired % invocations)) ? 1 : 0; int copyCount = allInvocationCopies + extra; diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/ConvertD32S8ToD24S8ShaderSource.comp b/src/Ryujinx.Graphics.Vulkan/Shaders/ConvertD32S8ToD24S8ShaderSource.comp index d3a74b1c8..96cbdebbb 100644 --- a/src/Ryujinx.Graphics.Vulkan/Shaders/ConvertD32S8ToD24S8ShaderSource.comp +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/ConvertD32S8ToD24S8ShaderSource.comp @@ -23,7 +23,7 @@ layout (std430, set = 1, binding = 2) buffer out_s void main() { // Determine what slice of the stride copies this invocation will perform. - int invocations = int(gl_WorkGroupSize.x); + int invocations = int(gl_WorkGroupSize.x * gl_NumWorkGroups.x); int copiesRequired = pixelCount; @@ -33,7 +33,7 @@ void main() int allInvocationCopies = copiesRequired / invocations; // - Extra remainder copy that this invocation performs. - int index = int(gl_LocalInvocationID.x); + int index = int(gl_GlobalInvocationID.x); int extra = (index < (copiesRequired % invocations)) ? 1 : 0; int copyCount = allInvocationCopies + extra; diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ChangeBufferStride.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ChangeBufferStride.spv index 49e7d23f9a9c463775404cee485a872fd0d636bb..1e0d8810fbc2a6513b0edb32ce596c3aa68bb6ce 100644 GIT binary patch literal 3932 zcmZ9MX>(Ln5QZ-#ldvxW;syjzL0JR^L=j93IG~XTL4%?WlguOxBr{vUtXW! z>r3t8$k@pKR=cwQ(BT6{u1R{*1Y_4GE0O}b7nv>B>N-{-yO1Nub>t?pj#|c0HW@_B zReJa>MgNJ>n=DI;Bc)RpFO*tW%9U!fbabNDF16Z?W_h}*e}(?y=xA}|RB3W<4%hrl zwKP+!S4)*zvpU(X-ALooI$2z;J~`8=tGbdAjZ}KX-daK=BoJAoS#%9g=;YO$KhU~kDZZVWL>&$1|Il1QK!;Kp^ z4p(Wlb6ihy39gpr^dz*OTupIr(m*#lTpV;CKW#s`>}aUilip2v_trtz>S+oYh1Avu zm!=r+J@ykXpk41YmUqO@Gw>_om-Aj1sqG!uf%t{JebG#9-^xnlvCQvWwV%ptA5|a1 z6G_*L{p8n^i}ieSu4laKX}ezdoqfKZd-u!NlZ$xwov){Dy{P{y*nS^JHjriBc4p9n z7H^^l*Be008NlvGEApGMmuI*ay)~PkoUzunjx#-$+0ImM54JT{BYv*620fs`r(b(* zYAx1V2R7Hd@NWd$TiBjO)HmO=a6jRH8a#;D`!i@`^xN;VXlu#aueN6_r+;^b%?ZC} zu74w|zK(Vs`y0x5_caUlf68wQtJTqR&dc-Hw*DO2`N&yc+dCj~82VAl?NJJ|m3atqwo4rBn4H{bJ=8^XUE{T#9jiCOqgp3nGu#Axq9ym#_< zvU%;zcx!qmw4;apVD}dG3t)S?pY?PAY>d45-XXb&e-SJf_Df*bi2OrfW8}^E&dJ4| zUIrgQ@_RapEg${A0+uu1JE|S)9RtgS{Tevm&v9(~d64br1lSn)Kj8dN8APn3?{`kZ zvv>m;M1~OWpKJRb;NvXxi2sQ(yB|{D;(p9?e{#mzzk6{_b*%X#cp;U= w|C67;M1& literal 3812 zcmZ9NX>(Ln5QZ-#ldvxW;syk8Ll8v)Q3S(gKu4nqBCf+Qa}!2~nJ{w)37{aNsHp$L zkNyRJl%-Z#<@4O!rf%h?y87+6m(!=unUkTVJ4chsl4M1)I{7VG_|_*&lO;g)h4o3Y zG8xJ3iRyH9Z$E49-GAUkBiAHDd4jQPljTVTJ&atgw>mmjAqKXQ9v{yTHv6a>OS+`f8P4%zPKQTEuQ9V^_%+2ANZ>P0( ztCQB6tzO#5T37S9yiO6<>NMJeW;vijrE;$odRs;h{qCUGNT;)2t4WoFJ&m#=e9qMB zz1hL#w3GGqjj(5ZevAFJW<9IJSJ<0#@N6HQVXon7A=n7Nu3}@T5Duqi4XHG)r@dwc9wXCFW8 zbO#-qwzuQxJ{!+iXTIR7a=pfb%^5cZ*X(B{ZYVhi*UEE-k{bGp91rKZJK%C)kMJk& zCs&N0ibMJRlXq`(=vF6BA)}Jp`rz^uLcMihbIl9?MzFnw{RlYfoBssjeqy~R!Q+U%??oG<-+o^} zTT9-4wLMcg{rd`R&PG;w4IOn~FMRIf64MRs+OJ+;X89V+Mh z7L{$^3hiUe(7WjWIcM%2UW*)IPTtLj5Pe6PqBGNuoK4`!aqf}xFj${C&Q&{d{C!4_ z_d9YP1M4%#*=tA67I5VF8;G2(V14FzKHA|M1G{$E+rjpKhg;yjb|7Pjy!oD|oY=GU zw-vchgY7BoXTa9FSJZkIY>d45p1oYeKL?fzdpFoMB7YCq70>< zC$HB(jF4j5*Hf9_#-+M0?@h8CL{FB)3hv`8L)L%@vCreZy`$%&(wJR(evA2*EPnT{cXxct#`oQ$FSc88x!^< zco`CVcn@rhe5~<4*gE=Lql%V`8q?r%&9m6v_o(>+*cf^9eIMk+=g_N>e|Qr=MEf?X z|Kcuvg#H-u)Bg!tzdF|b6l{&KKLdMiaaTSE8zcWOHMP$p*0z?|K75m+j|+uQZ66oW z4aCnrnrL&?v2F^sM%Xi8=Nf&?f{l^45A8Z)ZEJ}m_cGWS#r!*9Iq!DNPcGv7V9zAt zGq7C5>yz^h8=$WuJ;ePQXPryvuMm9^cMaS|jB|b0mNU*dI1_tUM=$eW=McSI2g^k- z`sB>NfHvP5So;Rrwe;OAd}`0&8}u#2PycPSx$0Q^Td+04z60Kd#2I`CHb&kv(Eb{+ zwzb5Odly_jgYU8BVjueCt`xKT0qmO?v%3e5*_r45_w7F2ea`8f6#6FyT&d3uy94ff*IS2Ozw47Z z?8>RGEi5lg#YuDO{DrebjJiS^NX$VuOVXoe2x9f4T8~XJy zi2Qmp==hB!*!G{~$>M^j5q6vTfRZxh`z`4NgX$rOwp$G^30hGsW*|MsMp$3$ZmqUD zo3&1>+m00`U)haGH(Kq$k8k_;yQce5`h`8bO@Aki9Zc_D zz1PClnwQ%#Kb!iC?qz9zC+T=8F1aaboo6>|OF2vKoVb{VRA)Dfy8_o~Sc_>~Rh)Xy zaRoOgE|AdME&P5vxtHPF_@a7tFC_agS)$qjk!jV6Ry#_>LI%uP)>h+}Ta!5f{D zundp)V2ZJ+!TaI=Sh_4B7y0e{R+Id2mVZSy`RRfEU~(Gfp7Z(a-1&TPmXGu2^MT1{ z{_kYd@3WF4V&TWtQ$ZU0sCuAYMM4i1;Q?s`9{*D*um7~pWOzt+RdQUI{KSxx{Ug%k zg1?+$-UiP0kIH5de^449IZg`m9@OsjRdmN>_j>K+1M^OBHwmm7VYBHDUVsCF|#P*~Gv<5_ekq zjD$S+epT1>{e}d7Qo>zOoA-yeT5rncJciH8Cgwp_>n+*Dz_XuufwTCxW#{*w7p_Qn zJ9@YvAqL*|T$G(Z-#fzj^IZ}q2Hy6(E4v~Y(KM0wJxQO0dmtXa^?X@2b%~*8W*W|N zU6l=IJzh_J#P;H^NW3_LwlrQxvo&F~(7>$m-Z zG{155pPJPh((@7){tu<`qpkK$+2k<1Ae(owS@=jcG4TH=C%7gdH@UEF?_^4M|h-ljaovhJ=`P$yRozdl^rDPd3=j zuqC@Hv3rYT!`T_|!C9{@+2r_5^|-J561cX+Vmh)p#~)ctA{#E2{GM@L+3dw<_p>cK rl<+3>K)-OrGDFN0?~bfe)KO`TK*$Tt+L|#%uc6{8LH`i-|o}h=bYXHrQy>ft}^8Ix&7{2H~0>_VK*f6 z30KW^qq*FiO0)LV#Y?Xdaln=GKw=KMF;|f;OE$u|gJZws8Ofq#O>#(i@E?}cC9FeA z`ZFy3t3cU}x<)gYyK_58TVWFRf_q7~wH_qlcDJA5--o}kxY%gU1+C4^AkDUuC`jT? z6tv@B)XL(mXg5z07k64ozwHN9l*#Y4qV7l4L)_Www!$p#c2Y4_RdcS@-HfAjDSFUX z{(~chIv7`C?T1qNeTFYLXw$ z^7FeOKRu8iOn$=}wg2U_efs&}EFb&z^MT1{{w>+``?TbUSolfxRFcL%rXJ|OCZUI# z@Q5@5f3EP4$*xN#gvp0*u&(UJWs?j3n*3nS5zf{hkj+Q_gVOj|PyG`)ufO!25PmAx zyb*(VdZ#yd@`JetIP$Ze``{!0bJFeB!e{s`VR|{PYV*>yT*~f!Y0d!d zq-{7~vZk)E9yTl--LwvoF&-mP0EHdd`h<84{r?pJTCfqK{he) zzlh^^^pb=;_5~{)~jPpmt3f-fF!foBbF*C%YzjRMa{zn;3Z3bI))VKPB5= z|Ee&1`Jq_Pe@=*jw>7WJ_V@ROu)n`Ig^7W;H5X-*cU-s5-Y!XoB#W z<8S>Yr=|IAqW{#a%t+5l`0&q3<40Sq_hi>S+>p(^*eu+VO$_`$$_ai~LT++lTdt2} zvtR4`W7%-*)y@KLTBp7wy)5A!rGMhc!@S>?;A7tBr9UY+a`0OQTg^MN{hFT%!}&Gg ztmfykttN3+^9$MdsIejq#z+0HB-9<(WP$HWm>1?4A8&gg`B8I<|DNQoB$R9vduqvs zCqM5E*!FN=Hg{_07R!dSJ>Y}0UcZq|j^9*|^GYOe>k^CEkj*~+C}O&@;X0Dv3vN?3 yYw_9nJdnL6;ZEp*e&L8^hL|Vr9c}&hWY;Cue<~Z!`o{<7&*+TszdFUMlK%jz)Cy_< From 5f771f5661b3f6d77ae15b72727c664d45a37b96 Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Mon, 4 Sep 2023 13:39:25 +0200 Subject: [PATCH 14/64] Update reviewers using Ryujinx-Mako command (#5635) * Update reviewers using Ryujinx-Mako command * Fix worklflow step 'uses' path --- .github/update_reviewers.py | 87 --------------------------------- .github/workflows/pr_triage.yml | 19 +++++-- 2 files changed, 15 insertions(+), 91 deletions(-) delete mode 100644 .github/update_reviewers.py diff --git a/.github/update_reviewers.py b/.github/update_reviewers.py deleted file mode 100644 index 955d40e7c..000000000 --- a/.github/update_reviewers.py +++ /dev/null @@ -1,87 +0,0 @@ -from pathlib import Path -from typing import List, Set -from github import Auth, Github -from github.Repository import Repository -from github.GithubException import GithubException - -import os -import sys -import yaml - - -def add_reviewers( - reviewers: Set[str], team_reviewers: Set[str], new_entries: List[str] -): - for reviewer in new_entries: - if reviewer.startswith("@"): - team_reviewers.add(reviewer[1:]) - else: - reviewers.add(reviewer) - - -def update_reviewers(config, repo: Repository, pr_id: int) -> int: - pull_request = repo.get_pull(pr_id) - - if not pull_request: - sys.stderr.writable(f"Unknown PR #{pr_id}\n") - return 1 - - if pull_request.draft: - print("Not assigning reviewers for draft PRs") - return 0 - - pull_request_author = pull_request.user.login - reviewers = set() - team_reviewers = set() - - for label in pull_request.labels: - if label.name in config: - add_reviewers(reviewers, team_reviewers, config[label.name]) - - if "default" in config: - add_reviewers(reviewers, team_reviewers, config["default"]) - - if pull_request_author in reviewers: - reviewers.remove(pull_request_author) - - try: - reviewers = list(reviewers) - team_reviewers = list(team_reviewers) - print( - f"Attempting to assign reviewers ({reviewers}) and team_reviewers ({team_reviewers})" - ) - pull_request.create_review_request(reviewers, team_reviewers) - return 0 - except GithubException as e: - sys.stderr.write(f"Cannot assign review request for PR #{pr_id}: {e}\n") - return 1 - - -if __name__ == "__main__": - if len(sys.argv) != 7: - sys.stderr.write("usage: \n") - sys.exit(1) - - app_id = int(sys.argv[1]) - private_key = os.environ[sys.argv[2]] - installation_id = int(sys.argv[3]) - repo_path = sys.argv[4] - pr_id = int(sys.argv[5]) - config_path = Path(sys.argv[6]) - - auth = Auth.AppAuth(app_id, private_key).get_installation_auth(installation_id) - g = Github(auth=auth) - repo = g.get_repo(repo_path) - - if not repo: - sys.stderr.write("Repository not found!\n") - sys.exit(1) - - if not config_path.exists(): - sys.stderr.write(f'Config "{config_path}" not found!\n') - sys.exit(1) - - with open(config_path, "r") as f: - config = yaml.safe_load(f) - - sys.exit(update_reviewers(config, repo, pr_id)) diff --git a/.github/workflows/pr_triage.yml b/.github/workflows/pr_triage.yml index 448e2c7de..106663836 100644 --- a/.github/workflows/pr_triage.yml +++ b/.github/workflows/pr_triage.yml @@ -12,7 +12,17 @@ jobs: runs-on: ubuntu-latest steps: - # Grab sources to get update_reviewers.py and reviewers.yml + - name: Checkout Ryujinx-Mako + uses: actions/checkout@v3 + with: + repository: Ryujinx/Ryujinx-Mako + ref: master + path: '.ryujinx-mako' + + - name: Setup Ryujinx-Mako + uses: ./.ryujinx-mako/.github/actions/setup-mako + + # Grab sources to get latest labeler.yml - name: Fetch sources uses: actions/checkout@v3 with: @@ -27,11 +37,12 @@ jobs: sync-labels: true dot: true - - run: pip3 install PyGithub - - name: Assign reviewers run: | - python3 .github/update_reviewers.py ${{ secrets.MAKO_APP_ID }} "MAKO_PRIVATE_KEY" ${{ secrets.MAKO_INSTALLATION_ID }} ${{ github.repository }} ${{ github.event.pull_request.number }} .github/reviewers.yml + poetry -C .ryujinx-mako shell + ryujinx-mako update-reviewers ${{ github.repository }} ${{ github.event.pull_request.number }} .github/reviewers.yml shell: bash env: + MAKO_APP_ID: ${{ secrets.MAKO_APP_ID }} MAKO_PRIVATE_KEY: ${{ secrets.MAKO_PRIVATE_KEY }} + MAKO_INSTALLATION_ID: ${{ secrets.MAKO_INSTALLATION_ID }} From 34447d73591f87ca5756caa8f8133ef4b58f9a74 Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Mon, 4 Sep 2023 19:14:20 +0200 Subject: [PATCH 15/64] Fix overwriting .ryujinx-mako directory (#5651) --- .github/workflows/pr_triage.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/pr_triage.yml b/.github/workflows/pr_triage.yml index 106663836..076c2825b 100644 --- a/.github/workflows/pr_triage.yml +++ b/.github/workflows/pr_triage.yml @@ -12,6 +12,15 @@ jobs: runs-on: ubuntu-latest steps: + # Grab sources to get latest labeler.yml + - name: Fetch sources + uses: actions/checkout@v3 + with: + # Ensure we pin the source origin as pull_request_target run under forks. + fetch-depth: 0 + repository: Ryujinx/Ryujinx + ref: master + - name: Checkout Ryujinx-Mako uses: actions/checkout@v3 with: @@ -22,15 +31,6 @@ jobs: - name: Setup Ryujinx-Mako uses: ./.ryujinx-mako/.github/actions/setup-mako - # Grab sources to get latest labeler.yml - - name: Fetch sources - uses: actions/checkout@v3 - with: - # Ensure we pin the source origin as pull_request_target run under forks. - fetch-depth: 0 - repository: Ryujinx/Ryujinx - ref: master - - name: Update labels based on changes uses: actions/labeler@v4 with: From db0f3c0b747e84b7400274127bece49988c7e246 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 22:16:17 +0200 Subject: [PATCH 16/64] ci: bump actions/checkout from 3 to 4 (#5650) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 4 ++-- .github/workflows/checks.yml | 2 +- .github/workflows/flatpak.yml | 4 ++-- .github/workflows/pr_triage.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f0aa2a06a..3f0d02c74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,7 +35,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v3 with: @@ -108,7 +108,7 @@ jobs: configuration: [ Debug, Release ] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v3 with: diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 25276b72f..2c982d506 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -23,7 +23,7 @@ jobs: format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml index d4380e05f..4c8ba3e17 100644 --- a/.github/workflows/flatpak.yml +++ b/.github/workflows/flatpak.yml @@ -24,7 +24,7 @@ jobs: RYUJINX_VERSION: "${{ inputs.ryujinx_version }}" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: path: Ryujinx @@ -38,7 +38,7 @@ jobs: run: | echo "git_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: repository: flathub/org.ryujinx.Ryujinx token: ${{ secrets.RYUJINX_BOT_PAT }} diff --git a/.github/workflows/pr_triage.yml b/.github/workflows/pr_triage.yml index 076c2825b..a31806f97 100644 --- a/.github/workflows/pr_triage.yml +++ b/.github/workflows/pr_triage.yml @@ -14,7 +14,7 @@ jobs: steps: # Grab sources to get latest labeler.yml - name: Fetch sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Ensure we pin the source origin as pull_request_target run under forks. fetch-depth: 0 @@ -22,7 +22,7 @@ jobs: ref: master - name: Checkout Ryujinx-Mako - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: Ryujinx/Ryujinx-Mako ref: master diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5091944a1..2aed3aa7c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,7 +62,7 @@ jobs: DOTNET_RUNTIME_IDENTIFIER: win10-x64 RELEASE_ZIP_OS_NAME: win_x64 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v3 with: @@ -150,7 +150,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v3 with: From fbe0c211c1e1c9ccf5664e4008e7098581cf7815 Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Tue, 5 Sep 2023 00:55:04 +0200 Subject: [PATCH 17/64] Use poetry run instead of spawning a shell (#5653) --- .github/workflows/pr_triage.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pr_triage.yml b/.github/workflows/pr_triage.yml index a31806f97..e1c7b8ae8 100644 --- a/.github/workflows/pr_triage.yml +++ b/.github/workflows/pr_triage.yml @@ -39,8 +39,7 @@ jobs: - name: Assign reviewers run: | - poetry -C .ryujinx-mako shell - ryujinx-mako update-reviewers ${{ github.repository }} ${{ github.event.pull_request.number }} .github/reviewers.yml + poetry -n -C .ryujinx-mako run ryujinx-mako update-reviewers ${{ github.repository }} ${{ github.event.pull_request.number }} .github/reviewers.yml shell: bash env: MAKO_APP_ID: ${{ secrets.MAKO_APP_ID }} From c2d9c6955d8fc1331a6b2b94a308af0da7ce54f1 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Mon, 4 Sep 2023 20:14:08 -0300 Subject: [PATCH 18/64] Fix layer size for 3D textures with NPOT depth (#5640) --- .../SizeCalculator.cs | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/Ryujinx.Graphics.Texture/SizeCalculator.cs b/src/Ryujinx.Graphics.Texture/SizeCalculator.cs index 7fe89e7e2..e6122a6ca 100644 --- a/src/Ryujinx.Graphics.Texture/SizeCalculator.cs +++ b/src/Ryujinx.Graphics.Texture/SizeCalculator.cs @@ -38,6 +38,7 @@ namespace Ryujinx.Graphics.Texture bool is3D = depth > 1 || gobBlocksInZ > 1; int layerSize = 0; + int layerSizeAligned = 0; int[] allOffsets = new int[is3D ? Calculate3DOffsetCount(levels, depth) : levels * layers * depth]; int[] mipOffsets = new int[levels]; @@ -91,6 +92,8 @@ namespace Ryujinx.Graphics.Texture sliceSizes[level] = totalBlocksOfGobsInY * robSize; levelSizes[level] = totalBlocksOfGobsInZ * sliceSizes[level]; + layerSizeAligned += levelSizes[level]; + if (is3D) { int gobSize = mipGobBlocksInY * GobSize; @@ -130,28 +133,32 @@ namespace Ryujinx.Graphics.Texture depthLevelOffset += d; } + int totalSize; + if (layers > 1) { - layerSize = AlignLayerSize( - layerSize, + layerSizeAligned = AlignLayerSize( + layerSizeAligned, height, depth, blockHeight, gobBlocksInY, gobBlocksInZ, gobBlocksInTileX); - } - int totalSize; - - if (layerSize < gpuLayerSize) - { - totalSize = (layers - 1) * gpuLayerSize + layerSize; - layerSize = gpuLayerSize; + if (layerSizeAligned < gpuLayerSize) + { + totalSize = (layers - 1) * gpuLayerSize + layerSizeAligned; + layerSizeAligned = gpuLayerSize; + } + else + { + totalSize = layerSizeAligned * layers; + } } else { - totalSize = layerSize * layers; + totalSize = layerSize; } if (!is3D) @@ -159,7 +166,7 @@ namespace Ryujinx.Graphics.Texture for (int layer = 0; layer < layers; layer++) { int baseIndex = layer * levels; - int baseOffset = layer * layerSize; + int baseOffset = layer * layerSizeAligned; for (int level = 0; level < levels; level++) { @@ -168,7 +175,7 @@ namespace Ryujinx.Graphics.Texture } } - return new SizeInfo(mipOffsets, allOffsets, sliceSizes, levelSizes, depth, levels, layerSize, totalSize, is3D); + return new SizeInfo(mipOffsets, allOffsets, sliceSizes, levelSizes, depth, levels, layerSizeAligned, totalSize, is3D); } public static SizeInfo GetLinearTextureSize(int stride, int height, int blockHeight) From 5ff6ea6d82db2fb6ee8035ac43c0a80092241bb0 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Mon, 4 Sep 2023 20:16:09 -0300 Subject: [PATCH 19/64] Fix ShaderTools GpuAcessor default values (#5646) --- src/Ryujinx.Graphics.Shader/IGpuAccessor.cs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs index ba10f2720..4dc75a3e1 100644 --- a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs @@ -128,7 +128,26 @@ namespace Ryujinx.Graphics.Shader /// GPU graphics state GpuGraphicsState QueryGraphicsState() { - return default; + return new GpuGraphicsState( + false, + InputTopology.Points, + false, + TessPatchType.Triangles, + TessSpacing.EqualSpacing, + false, + false, + false, + false, + false, + 1f, + AlphaTestOp.Always, + 0f, + default, + true, + default, + false, + false, + false); } /// From f631933e6050c8db965c0d63b6dbea5971c3d685 Mon Sep 17 00:00:00 2001 From: siegmund-heiss-ich <119589995+siegmund-heiss-ich@users.noreply.github.com> Date: Tue, 5 Sep 2023 01:28:44 +0200 Subject: [PATCH 20/64] Add macOS Headless release workflow (#5272) * Add macOS Headless release workflow * Add MACH-O check before applying signature * Rename script for consistency * Remove redundant compiler flag * Fix release.yml arguments + names * Update headless.sh to include changes of pr #5398 --- .github/workflows/build.yml | 15 ++- .github/workflows/release.yml | 10 +- ...cos_build.sh => create_macos_build_ava.sh} | 0 .../macos/create_macos_build_headless.sh | 111 ++++++++++++++++++ 4 files changed, 131 insertions(+), 5 deletions(-) rename distribution/macos/{create_macos_build.sh => create_macos_build_ava.sh} (100%) create mode 100755 distribution/macos/create_macos_build_headless.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3f0d02c74..16058d9f8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -135,9 +135,13 @@ jobs: id: git_short_hash run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT - - name: Publish macOS + - name: Publish macOS Ryujinx.Ava run: | - ./distribution/macos/create_macos_build.sh . publish_tmp publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER" + ./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER" + + - name: Publish macOS Ryujinx.Headless.SDL2 + run: | + ./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER" - name: Upload Ryujinx.Ava artifact uses: actions/upload-artifact@v3 @@ -145,3 +149,10 @@ jobs: name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal path: "publish_ava/*.tar.gz" if: github.event_name == 'pull_request' + + - name: Upload Ryujinx.Headless.SDL2 artifact + uses: actions/upload-artifact@v3 + with: + name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal + path: "publish_headless/*.tar.gz" + if: github.event_name == 'pull_request' \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2aed3aa7c..988264a31 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -188,15 +188,19 @@ jobs: sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs shell: bash - - name: Publish macOS + - name: Publish macOS Ryujinx.Ava run: | - ./distribution/macos/create_macos_build.sh . publish_tmp publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release + ./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release + + - name: Publish macOS Ryujinx.Headless.SDL2 + run: | + ./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release - name: Pushing new release uses: ncipollo/release-action@v1 with: name: ${{ steps.version_info.outputs.build_version }} - artifacts: "publish_ava/*.tar.gz" + artifacts: "publish_ava/*.tar.gz, publish_headless/*.tar.gz" tag: ${{ steps.version_info.outputs.build_version }} body: "For more information about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)." omitBodyDuringUpdate: true diff --git a/distribution/macos/create_macos_build.sh b/distribution/macos/create_macos_build_ava.sh similarity index 100% rename from distribution/macos/create_macos_build.sh rename to distribution/macos/create_macos_build_ava.sh diff --git a/distribution/macos/create_macos_build_headless.sh b/distribution/macos/create_macos_build_headless.sh new file mode 100755 index 000000000..a439aef45 --- /dev/null +++ b/distribution/macos/create_macos_build_headless.sh @@ -0,0 +1,111 @@ +#!/bin/bash + +set -e + +if [ "$#" -lt 7 ]; then + echo "usage " + exit 1 +fi + +mkdir -p "$1" +mkdir -p "$2" +mkdir -p "$3" + +BASE_DIR=$(readlink -f "$1") +TEMP_DIRECTORY=$(readlink -f "$2") +OUTPUT_DIRECTORY=$(readlink -f "$3") +ENTITLEMENTS_FILE_PATH=$(readlink -f "$4") +VERSION=$5 +SOURCE_REVISION_ID=$6 +CONFIGURATION=$7 +EXTRA_ARGS=$8 + +if [ "$VERSION" == "1.1.0" ]; +then + RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar +else + RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$VERSION-macos_universal.tar +fi + +ARM64_OUTPUT="$TEMP_DIRECTORY/publish_arm64" +X64_OUTPUT="$TEMP_DIRECTORY/publish_x64" +UNIVERSAL_OUTPUT="$OUTPUT_DIRECTORY/publish" +EXECUTABLE_SUB_PATH=Ryujinx.Headless.SDL2 + +rm -rf "$TEMP_DIRECTORY" +mkdir -p "$TEMP_DIRECTORY" + +DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS) + +dotnet restore +dotnet build -c "$CONFIGURATION" src/Ryujinx.Headless.SDL2 +dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL2 +dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL2 + +# Get rid of the support library for ARMeilleure for x64 (that's only for arm64) +rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib" + +# Get rid of libsoundio from arm64 builds as we don't have a arm64 variant +# TODO: remove this once done +rm -rf "$TEMP_DIRECTORY/publish_arm64/libsoundio.dylib" + +rm -rf "$OUTPUT_DIRECTORY" +mkdir -p "$OUTPUT_DIRECTORY" + +# Let's copy one of the two different outputs and remove the executable +cp -R "$ARM64_OUTPUT/" "$UNIVERSAL_OUTPUT" +rm "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH" + +# Make it libraries universal +python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_OUTPUT" "$X64_OUTPUT" "$UNIVERSAL_OUTPUT" "**/*.dylib" + +if ! [ -x "$(command -v lipo)" ]; +then + if ! [ -x "$(command -v llvm-lipo-14)" ]; + then + LIPO=llvm-lipo + else + LIPO=llvm-lipo-14 + fi +else + LIPO=lipo +fi + +# Make the executable universal +$LIPO "$ARM64_OUTPUT/$EXECUTABLE_SUB_PATH" "$X64_OUTPUT/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH" -create + +# Now sign it +if ! [ -x "$(command -v codesign)" ]; +then + if ! [ -x "$(command -v rcodesign)" ]; + then + echo "Cannot find rcodesign on your system, please install rcodesign." + exit 1 + fi + + # NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes. + # cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign" + echo "Using rcodesign for ad-hoc signing" + for FILE in "$UNIVERSAL_OUTPUT"/*; do + if [[ $(file "$FILE") == *"Mach-O"* ]]; then + rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$FILE" + fi + done +else + echo "Using codesign for ad-hoc signing" + for FILE in "$UNIVERSAL_OUTPUT"/*; do + if [[ $(file "$FILE") == *"Mach-O"* ]]; then + codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$FILE" + fi + done +fi + +echo "Creating archive" +pushd "$OUTPUT_DIRECTORY" +tar --exclude "publish/Ryujinx.Headless.SDL2" -cvf "$RELEASE_TAR_FILE_NAME" publish 1> /dev/null +python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "publish/Ryujinx.Headless.SDL2" "publish/Ryujinx.Headless.SDL2" +gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz" +rm "$RELEASE_TAR_FILE_NAME" +popd + +echo "Done" \ No newline at end of file From ddb64938968db5aa2a973604cf761f44c99d1c3d Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 5 Sep 2023 17:59:21 -0300 Subject: [PATCH 21/64] Delete ResourceAccess (#5626) * Delete ResourceAccess * Set write flag for vertex/geometry as compute output buffers --- src/Ryujinx.Graphics.GAL/ResourceLayout.cs | 16 +++--------- .../Threed/ComputeDraw/VtgAsComputeContext.cs | 15 ++++++----- .../Threed/ComputeDraw/VtgAsComputeState.cs | 14 +++++------ .../Shader/ShaderInfoBuilder.cs | 25 ++++++++----------- .../ResourceBindingSegment.cs | 4 +-- .../ResourceLayoutBuilder.cs | 12 +-------- .../ShaderCollection.cs | 21 ++++------------ 7 files changed, 37 insertions(+), 70 deletions(-) diff --git a/src/Ryujinx.Graphics.GAL/ResourceLayout.cs b/src/Ryujinx.Graphics.GAL/ResourceLayout.cs index 200292eee..84bca5b41 100644 --- a/src/Ryujinx.Graphics.GAL/ResourceLayout.cs +++ b/src/Ryujinx.Graphics.GAL/ResourceLayout.cs @@ -15,14 +15,6 @@ namespace Ryujinx.Graphics.GAL BufferImage, } - public enum ResourceAccess : byte - { - None = 0, - Read = 1, - Write = 2, - ReadWrite = Read | Write, - } - [Flags] public enum ResourceStages : byte { @@ -81,19 +73,17 @@ namespace Ryujinx.Graphics.GAL public int Binding { get; } public ResourceType Type { get; } public ResourceStages Stages { get; } - public ResourceAccess Access { get; } - public ResourceUsage(int binding, ResourceType type, ResourceStages stages, ResourceAccess access) + public ResourceUsage(int binding, ResourceType type, ResourceStages stages) { Binding = binding; Type = type; Stages = stages; - Access = access; } public override int GetHashCode() { - return HashCode.Combine(Binding, Type, Stages, Access); + return HashCode.Combine(Binding, Type, Stages); } public override bool Equals(object obj) @@ -103,7 +93,7 @@ namespace Ryujinx.Graphics.GAL public bool Equals(ResourceUsage other) { - return Binding == other.Binding && Type == other.Type && Stages == other.Stages && Access == other.Access; + return Binding == other.Binding && Type == other.Type && Stages == other.Stages; } public static bool operator ==(ResourceUsage left, ResourceUsage right) diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs index e9b754ff1..f9cb40b0d 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs @@ -490,10 +490,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw /// /// Offset of the range /// Size of the range in bytes + /// Indicates if the buffer contents will be modified /// Range - public BufferRange GetVertexDataBufferRange(int offset, int size) + public BufferRange GetVertexDataBufferRange(int offset, int size, bool write) { - return new BufferRange(_vertexDataBuffer.Handle, offset, size); + return new BufferRange(_vertexDataBuffer.Handle, offset, size, write); } /// @@ -501,10 +502,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw /// /// Offset of the range /// Size of the range in bytes + /// Indicates if the buffer contents will be modified /// Range - public BufferRange GetGeometryVertexDataBufferRange(int offset, int size) + public BufferRange GetGeometryVertexDataBufferRange(int offset, int size, bool write) { - return new BufferRange(_geometryVertexDataBuffer.Handle, offset, size); + return new BufferRange(_geometryVertexDataBuffer.Handle, offset, size, write); } /// @@ -512,10 +514,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw /// /// Offset of the range /// Size of the range in bytes + /// Indicates if the buffer contents will be modified /// Range - public BufferRange GetGeometryIndexDataBufferRange(int offset, int size) + public BufferRange GetGeometryIndexDataBufferRange(int offset, int size, bool write) { - return new BufferRange(_geometryIndexDataBuffer.Handle, offset, size); + return new BufferRange(_geometryIndexDataBuffer.Handle, offset, size, write); } /// diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs index 59a925087..d1a333a71 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs @@ -202,7 +202,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw _context.Renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(vertexInfoBinding, vertexInfoRange) }); int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding; - BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize); + BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize, write: true); _context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexDataRange) }); _vacContext.VertexInfoBufferUpdater.Commit(); @@ -245,9 +245,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw int geometryVbBinding = _geometryAsCompute.Reservations.GeometryVertexOutputStorageBufferBinding; int geometryIbBinding = _geometryAsCompute.Reservations.GeometryIndexOutputStorageBufferBinding; - BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize); - BufferRange vertexBuffer = _vacContext.GetGeometryVertexDataBufferRange(_geometryVertexDataOffset, _geometryVertexDataSize); - BufferRange indexBuffer = _vacContext.GetGeometryIndexDataBufferRange(_geometryIndexDataOffset, _geometryIndexDataSize); + BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize, write: false); + BufferRange vertexBuffer = _vacContext.GetGeometryVertexDataBufferRange(_geometryVertexDataOffset, _geometryVertexDataSize, write: true); + BufferRange indexBuffer = _vacContext.GetGeometryIndexDataBufferRange(_geometryIndexDataOffset, _geometryIndexDataSize, write: true); _context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { @@ -293,8 +293,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw if (_geometryAsCompute != null) { - BufferRange vertexBuffer = _vacContext.GetGeometryVertexDataBufferRange(_geometryVertexDataOffset, _geometryVertexDataSize); - BufferRange indexBuffer = _vacContext.GetGeometryIndexDataBufferRange(_geometryIndexDataOffset, _geometryIndexDataSize); + BufferRange vertexBuffer = _vacContext.GetGeometryVertexDataBufferRange(_geometryVertexDataOffset, _geometryVertexDataSize, write: false); + BufferRange indexBuffer = _vacContext.GetGeometryIndexDataBufferRange(_geometryIndexDataOffset, _geometryIndexDataSize, write: false); _context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram); _context.Renderer.Pipeline.SetIndexBuffer(indexBuffer, IndexType.UInt); @@ -310,7 +310,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw } else { - BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize); + BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize, write: false); _context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram); _context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexDataRange) }); diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs index bea916a6d..c2258026c 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs @@ -61,7 +61,7 @@ namespace Ryujinx.Graphics.Gpu.Shader } AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1); - AddUsage(SupportBufferStages, ResourceType.UniformBuffer, ResourceAccess.Read, UniformSetIndex, 0, 1); + AddUsage(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1); ResourceReservationCounts rrc = new(!context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute); @@ -73,16 +73,16 @@ namespace Ryujinx.Graphics.Gpu.Shader // TODO: Handle that better? Maybe we should only set the binding that are really needed on each shader. ResourceStages stages = vertexAsCompute ? ResourceStages.Compute | ResourceStages.Vertex : VtgStages; - PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, ResourceAccess.Read, UniformSetIndex, 1, rrc.ReservedConstantBuffers - 1); - PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, ResourceAccess.ReadWrite, StorageSetIndex, 0, rrc.ReservedStorageBuffers); - PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, ResourceAccess.Read, TextureSetIndex, 0, rrc.ReservedTextures); - PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, ResourceAccess.ReadWrite, ImageSetIndex, 0, rrc.ReservedImages); + PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, UniformSetIndex, 1, rrc.ReservedConstantBuffers - 1); + PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, StorageSetIndex, 0, rrc.ReservedStorageBuffers); + PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, TextureSetIndex, 0, rrc.ReservedTextures); + PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, ImageSetIndex, 0, rrc.ReservedImages); } - private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, ResourceAccess access, int setIndex, int start, int count) + private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count) { AddDescriptor(stages, type, setIndex, start, count); - AddUsage(stages, type, access, setIndex, start, count); + AddUsage(stages, type, setIndex, start, count); } /// @@ -174,15 +174,14 @@ namespace Ryujinx.Graphics.Gpu.Shader /// /// Shader stages where the resource is used /// Type of the resource - /// How the resource is accessed by the shader stages where it is used /// Descriptor set number where the resource will be bound /// Binding number where the resource will be bound /// Number of resources bound at the binding location - private void AddUsage(ResourceStages stages, ResourceType type, ResourceAccess access, int setIndex, int binding, int count) + private void AddUsage(ResourceStages stages, ResourceType type, int setIndex, int binding, int count) { for (int index = 0; index < count; index++) { - _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, type, stages, access)); + _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, type, stages)); } } @@ -200,8 +199,7 @@ namespace Ryujinx.Graphics.Gpu.Shader _resourceUsages[setIndex].Add(new ResourceUsage( buffer.Binding, isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer, - stages, - buffer.Flags.HasFlag(BufferUsageFlags.Write) ? ResourceAccess.ReadWrite : ResourceAccess.Read)); + stages)); } } @@ -225,8 +223,7 @@ namespace Ryujinx.Graphics.Gpu.Shader _resourceUsages[setIndex].Add(new ResourceUsage( texture.Binding, type, - stages, - texture.Flags.HasFlag(TextureUsageFlags.ImageStore) ? ResourceAccess.ReadWrite : ResourceAccess.Read)); + stages)); } } diff --git a/src/Ryujinx.Graphics.Vulkan/ResourceBindingSegment.cs b/src/Ryujinx.Graphics.Vulkan/ResourceBindingSegment.cs index 9de46e614..8902f13e6 100644 --- a/src/Ryujinx.Graphics.Vulkan/ResourceBindingSegment.cs +++ b/src/Ryujinx.Graphics.Vulkan/ResourceBindingSegment.cs @@ -8,15 +8,13 @@ namespace Ryujinx.Graphics.Vulkan public readonly int Count; public readonly ResourceType Type; public readonly ResourceStages Stages; - public readonly ResourceAccess Access; - public ResourceBindingSegment(int binding, int count, ResourceType type, ResourceStages stages, ResourceAccess access) + public ResourceBindingSegment(int binding, int count, ResourceType type, ResourceStages stages) { Binding = binding; Count = count; Type = type; Stages = stages; - Access = access; } } } diff --git a/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs b/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs index 0b87d8000..f5ac39684 100644 --- a/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs +++ b/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs @@ -34,22 +34,12 @@ namespace Ryujinx.Graphics.Vulkan _ => throw new ArgumentException($"Invalid resource type \"{type}\"."), }; - ResourceAccess access = IsReadOnlyType(type) ? ResourceAccess.Read : ResourceAccess.ReadWrite; - _resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, 1, type, stages)); - _resourceUsages[setIndex].Add(new ResourceUsage(binding, type, stages, access)); + _resourceUsages[setIndex].Add(new ResourceUsage(binding, type, stages)); return this; } - private static bool IsReadOnlyType(ResourceType type) - { - return type == ResourceType.UniformBuffer || - type == ResourceType.Sampler || - type == ResourceType.TextureAndSampler || - type == ResourceType.BufferTexture; - } - public ResourceLayout Build() { var descriptors = new ResourceDescriptorCollection[TotalSets]; diff --git a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs index 1e56d1e89..346fd9166 100644 --- a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs +++ b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs @@ -162,8 +162,7 @@ namespace Ryujinx.Graphics.Vulkan currentDescriptor.Binding, currentCount, currentDescriptor.Type, - currentDescriptor.Stages, - ResourceAccess.ReadWrite)); + currentDescriptor.Stages)); } currentDescriptor = descriptor; @@ -181,8 +180,7 @@ namespace Ryujinx.Graphics.Vulkan currentDescriptor.Binding, currentCount, currentDescriptor.Type, - currentDescriptor.Stages, - ResourceAccess.ReadWrite)); + currentDescriptor.Stages)); } segments[setIndex] = currentSegments.ToArray(); @@ -206,16 +204,9 @@ namespace Ryujinx.Graphics.Vulkan { ResourceUsage usage = setUsages[setIndex].Usages[index]; - // If the resource is not accessed, we don't need to update it. - if (usage.Access == ResourceAccess.None) - { - continue; - } - if (currentUsage.Binding + currentCount != usage.Binding || currentUsage.Type != usage.Type || - currentUsage.Stages != usage.Stages || - currentUsage.Access != usage.Access) + currentUsage.Stages != usage.Stages) { if (currentCount != 0) { @@ -223,8 +214,7 @@ namespace Ryujinx.Graphics.Vulkan currentUsage.Binding, currentCount, currentUsage.Type, - currentUsage.Stages, - currentUsage.Access)); + currentUsage.Stages)); } currentUsage = usage; @@ -242,8 +232,7 @@ namespace Ryujinx.Graphics.Vulkan currentUsage.Binding, currentCount, currentUsage.Type, - currentUsage.Stages, - currentUsage.Access)); + currentUsage.Stages)); } segments[setIndex] = currentSegments.ToArray(); From 210f4754849bf147a584695c0cced9d14c905f8e Mon Sep 17 00:00:00 2001 From: Marco Carvalho Date: Thu, 7 Sep 2023 09:10:58 -0300 Subject: [PATCH 22/64] Replacing 'Assembly.GetExecutingAssembly()' with 'Type.Assembly' (#5545) --- .../SoftwareKeyboard/SoftwareKeyboardRendererBase.cs | 8 ++++---- src/Ryujinx.HLE/HOS/Services/IpcService.cs | 4 ++-- src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs | 2 +- src/Ryujinx.Headless.SDL2/WindowBase.cs | 3 +-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs index 770bf6e5a..2f4fd2a89 100644 --- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs @@ -68,15 +68,15 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard int ryujinxLogoSize = 32; string ryujinxIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Logo_Ryujinx.png"; - _ryujinxLogo = LoadResource(Assembly.GetExecutingAssembly(), ryujinxIconPath, ryujinxLogoSize, ryujinxLogoSize); + _ryujinxLogo = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, ryujinxIconPath, ryujinxLogoSize, ryujinxLogoSize); string padAcceptIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnA.png"; string padCancelIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnB.png"; string keyModeIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_KeyF6.png"; - _padAcceptIcon = LoadResource(Assembly.GetExecutingAssembly(), padAcceptIconPath, 0, 0); - _padCancelIcon = LoadResource(Assembly.GetExecutingAssembly(), padCancelIconPath, 0, 0); - _keyModeIcon = LoadResource(Assembly.GetExecutingAssembly(), keyModeIconPath, 0, 0); + _padAcceptIcon = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, padAcceptIconPath, 0, 0); + _padCancelIcon = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, padCancelIconPath, 0, 0); + _keyModeIcon = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, keyModeIconPath, 0, 0); Color panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255); Color panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150); diff --git a/src/Ryujinx.HLE/HOS/Services/IpcService.cs b/src/Ryujinx.HLE/HOS/Services/IpcService.cs index eb9f6b2fd..808f21c0e 100644 --- a/src/Ryujinx.HLE/HOS/Services/IpcService.cs +++ b/src/Ryujinx.HLE/HOS/Services/IpcService.cs @@ -23,14 +23,14 @@ namespace Ryujinx.HLE.HOS.Services public IpcService(ServerBase server = null) { - CmifCommands = Assembly.GetExecutingAssembly().GetTypes() + CmifCommands = typeof(IpcService).Assembly.GetTypes() .Where(type => type == GetType()) .SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public)) .SelectMany(methodInfo => methodInfo.GetCustomAttributes(typeof(CommandCmifAttribute)) .Select(command => (((CommandCmifAttribute)command).Id, methodInfo))) .ToDictionary(command => command.Id, command => command.methodInfo); - TipcCommands = Assembly.GetExecutingAssembly().GetTypes() + TipcCommands = typeof(IpcService).Assembly.GetTypes() .Where(type => type == GetType()) .SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public)) .SelectMany(methodInfo => methodInfo.GetCustomAttributes(typeof(CommandTipcAttribute)) diff --git a/src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs b/src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs index 2d8502208..9110d06d3 100644 --- a/src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs +++ b/src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs @@ -28,7 +28,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm static IUserInterface() { - _services = Assembly.GetExecutingAssembly().GetTypes() + _services = typeof(IUserInterface).Assembly.GetTypes() .SelectMany(type => type.GetCustomAttributes(typeof(ServiceAttribute), true) .Select(service => (((ServiceAttribute)service).Name, type))) .ToDictionary(service => service.Name, service => service.type); diff --git a/src/Ryujinx.Headless.SDL2/WindowBase.cs b/src/Ryujinx.Headless.SDL2/WindowBase.cs index c542a4e61..0b91da5c6 100644 --- a/src/Ryujinx.Headless.SDL2/WindowBase.cs +++ b/src/Ryujinx.Headless.SDL2/WindowBase.cs @@ -17,7 +17,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Reflection; using System.Runtime.InteropServices; using System.Threading; using static SDL2.SDL; @@ -122,7 +121,7 @@ namespace Ryujinx.Headless.SDL2 private void SetWindowIcon() { - Stream iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.Headless.SDL2.Ryujinx.bmp"); + Stream iconStream = typeof(WindowBase).Assembly.GetManifestResourceStream("Ryujinx.Headless.SDL2.Ryujinx.bmp"); byte[] iconBytes = new byte[iconStream!.Length]; if (iconStream.Read(iconBytes, 0, iconBytes.Length) != iconBytes.Length) From e2cfe6fe44b62d7aba1425ae2e621f50045f406c Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sun, 10 Sep 2023 22:22:18 -0300 Subject: [PATCH 23/64] Fix shader GlobalToStorage pass when base address comes from local or shared memory (#5668) * Fix shader GlobalToStorage pass when base address comes from local or shared memory * Shader cache version bump --- .../Shader/DiskCache/DiskCacheHostStorage.cs | 2 +- .../Translation/Optimizations/GlobalToStorage.cs | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 99ef89ed9..4b99bb097 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 5551; + private const uint CodeGenVersion = 5668; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs index 0f043f772..e426d54de 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs @@ -1126,7 +1126,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations // so we want to get the byte offset back, since each one of those word // offsets are a new "local variable" which will not match. - if (operation.GetSource(0).AsgOp is Operation shiftRightOp && + if (operation.GetSource(1).AsgOp is Operation shiftRightOp && shiftRightOp.Inst == Instruction.ShiftRightU32 && shiftRightOp.GetSource(1).Type == OperandType.Constant && shiftRightOp.GetSource(1).Value == 2) @@ -1158,9 +1158,11 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations private static bool TryGetLocalMemoryOffset(Operation operation, out int constOffset) { - if (operation.GetSource(0).Type == OperandType.Constant) + Operand offset = operation.GetSource(1); + + if (offset.Type == OperandType.Constant) { - constOffset = operation.GetSource(0).Value; + constOffset = offset.Value; return true; } From e6700b314f1384f015666767baf9ea1d8411e330 Mon Sep 17 00:00:00 2001 From: Ac_K Date: Thu, 14 Sep 2023 09:50:19 +0200 Subject: [PATCH 24/64] lbl: Migrate service to Horizon (#5628) * lbl: Migrate service to Horizon * Fix formatting * Addresses gdkchan's feedback * Fix comments --- .../SystemAppletProxy/ICommonStateGetter.cs | 9 +- .../HOS/Services/Lbl/ILblController.cs | 92 ------------- .../HOS/Services/Lbl/LblControllerServer.cs | 54 -------- src/Ryujinx.HLE/HOS/Services/ServerBase.cs | 8 ++ src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs | 16 +-- src/Ryujinx.Horizon/HorizonStatic.cs | 2 +- src/Ryujinx.Horizon/Lbl/Ipc/LblController.cs | 130 ++++++++++++++++++ src/Ryujinx.Horizon/Lbl/LblIpcServer.cs | 43 ++++++ src/Ryujinx.Horizon/Lbl/LblMain.cs | 17 +++ src/Ryujinx.Horizon/LogManager/LmIpcServer.cs | 8 +- src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs | 8 +- src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs | 20 +-- src/Ryujinx.Horizon/Sdk/Lbl/ILblController.cs | 20 +++ src/Ryujinx.Horizon/Sdk/Lbl/LblApi.cs | 43 ++++++ src/Ryujinx.Horizon/Sdk/Sm/ServiceName.cs | 2 +- src/Ryujinx.Horizon/Sdk/Sm/SmApi.cs | 14 +- src/Ryujinx.Horizon/ServiceTable.cs | 6 +- 17 files changed, 311 insertions(+), 181 deletions(-) delete mode 100644 src/Ryujinx.HLE/HOS/Services/Lbl/ILblController.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Lbl/LblControllerServer.cs create mode 100644 src/Ryujinx.Horizon/Lbl/Ipc/LblController.cs create mode 100644 src/Ryujinx.Horizon/Lbl/LblIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Lbl/LblMain.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Lbl/ILblController.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Lbl/LblApi.cs diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs index 0d2ec8bc4..602fc2c4d 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs @@ -5,6 +5,7 @@ using Ryujinx.HLE.HOS.Services.Settings.Types; using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService; using Ryujinx.HLE.HOS.SystemState; using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Lbl; using System; namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy @@ -15,7 +16,6 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys private readonly Apm.ManagerServer _apmManagerServer; private readonly Apm.SystemManagerServer _apmSystemManagerServer; - private readonly Lbl.LblControllerServer _lblControllerServer; private bool _vrModeEnabled; #pragma warning disable CS0414, IDE0052 // Remove unread private member @@ -34,7 +34,6 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys _apmManagerServer = new Apm.ManagerServer(context); _apmSystemManagerServer = new Apm.SystemManagerServer(context); - _lblControllerServer = new Lbl.LblControllerServer(context); _acquiredSleepLockEvent = new KEvent(context.Device.System.KernelContext); } @@ -215,13 +214,15 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys _vrModeEnabled = vrModeEnabled; + using var lblApi = new LblApi(); + if (vrModeEnabled) { - _lblControllerServer.EnableVrMode(); + lblApi.EnableVrMode().AbortOnFailure(); } else { - _lblControllerServer.DisableVrMode(); + lblApi.DisableVrMode().AbortOnFailure(); } // TODO: It signals an internal event of ICommonStateGetter. We have to determine where this event is used. diff --git a/src/Ryujinx.HLE/HOS/Services/Lbl/ILblController.cs b/src/Ryujinx.HLE/HOS/Services/Lbl/ILblController.cs deleted file mode 100644 index 75d787432..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Lbl/ILblController.cs +++ /dev/null @@ -1,92 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Lbl -{ - abstract class ILblController : IpcService - { - public ILblController(ServiceCtx context) { } - - protected abstract void SetCurrentBrightnessSettingForVrMode(float currentBrightnessSettingForVrMode); - protected abstract float GetCurrentBrightnessSettingForVrMode(); - internal abstract void EnableVrMode(); - internal abstract void DisableVrMode(); - protected abstract bool IsVrModeEnabled(); - - [CommandCmif(17)] - // SetBrightnessReflectionDelayLevel(float, float) - public ResultCode SetBrightnessReflectionDelayLevel(ServiceCtx context) - { - return ResultCode.Success; - } - - [CommandCmif(18)] - // GetBrightnessReflectionDelayLevel(float) -> float - public ResultCode GetBrightnessReflectionDelayLevel(ServiceCtx context) - { - context.ResponseData.Write(0.0f); - - return ResultCode.Success; - } - - [CommandCmif(21)] - // SetCurrentAmbientLightSensorMapping(unknown<0xC>) - public ResultCode SetCurrentAmbientLightSensorMapping(ServiceCtx context) - { - return ResultCode.Success; - } - - [CommandCmif(22)] - // GetCurrentAmbientLightSensorMapping() -> unknown<0xC> - public ResultCode GetCurrentAmbientLightSensorMapping(ServiceCtx context) - { - return ResultCode.Success; - } - - [CommandCmif(24)] // 3.0.0+ - // SetCurrentBrightnessSettingForVrMode(float) - public ResultCode SetCurrentBrightnessSettingForVrMode(ServiceCtx context) - { - float currentBrightnessSettingForVrMode = context.RequestData.ReadSingle(); - - SetCurrentBrightnessSettingForVrMode(currentBrightnessSettingForVrMode); - - return ResultCode.Success; - } - - [CommandCmif(25)] // 3.0.0+ - // GetCurrentBrightnessSettingForVrMode() -> float - public ResultCode GetCurrentBrightnessSettingForVrMode(ServiceCtx context) - { - float currentBrightnessSettingForVrMode = GetCurrentBrightnessSettingForVrMode(); - - context.ResponseData.Write(currentBrightnessSettingForVrMode); - - return ResultCode.Success; - } - - [CommandCmif(26)] // 3.0.0+ - // EnableVrMode() - public ResultCode EnableVrMode(ServiceCtx context) - { - EnableVrMode(); - - return ResultCode.Success; - } - - [CommandCmif(27)] // 3.0.0+ - // DisableVrMode() - public ResultCode DisableVrMode(ServiceCtx context) - { - DisableVrMode(); - - return ResultCode.Success; - } - - [CommandCmif(28)] // 3.0.0+ - // IsVrModeEnabled() -> bool - public ResultCode IsVrModeEnabled(ServiceCtx context) - { - context.ResponseData.Write(IsVrModeEnabled()); - - return ResultCode.Success; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Lbl/LblControllerServer.cs b/src/Ryujinx.HLE/HOS/Services/Lbl/LblControllerServer.cs deleted file mode 100644 index 899e882e5..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Lbl/LblControllerServer.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Lbl -{ - [Service("lbl")] - class LblControllerServer : ILblController - { - private bool _vrModeEnabled; - private float _currentBrightnessSettingForVrMode; - - public LblControllerServer(ServiceCtx context) : base(context) { } - - protected override void SetCurrentBrightnessSettingForVrMode(float currentBrightnessSettingForVrMode) - { - if (float.IsNaN(currentBrightnessSettingForVrMode) || float.IsInfinity(currentBrightnessSettingForVrMode)) - { - _currentBrightnessSettingForVrMode = 0.0f; - - return; - } - - _currentBrightnessSettingForVrMode = currentBrightnessSettingForVrMode; - } - - protected override float GetCurrentBrightnessSettingForVrMode() - { - if (float.IsNaN(_currentBrightnessSettingForVrMode) || float.IsInfinity(_currentBrightnessSettingForVrMode)) - { - return 0.0f; - } - - return _currentBrightnessSettingForVrMode; - } - - internal override void EnableVrMode() - { - _vrModeEnabled = true; - - // NOTE: Service check _vrModeEnabled field value in a thread and then change the screen brightness. - // Since we don't support that. It's fine to do nothing. - } - - internal override void DisableVrMode() - { - _vrModeEnabled = false; - - // NOTE: Service check _vrModeEnabled field value in a thread and then change the screen brightness. - // Since we don't support that. It's fine to do nothing. - } - - protected override bool IsVrModeEnabled() - { - return _vrModeEnabled; - } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs index f107f5026..9d7e4d4c5 100644 --- a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs +++ b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs @@ -6,6 +6,7 @@ using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel.Ipc; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon; using Ryujinx.Horizon.Common; using System; using System.Buffers; @@ -172,6 +173,13 @@ namespace Ryujinx.HLE.HOS.Services _selfProcess = KernelStatic.GetCurrentProcess(); _selfThread = KernelStatic.GetCurrentThread(); + HorizonStatic.Register( + default, + _context.Syscall, + _selfProcess.CpuMemory, + _selfThread.ThreadContext, + (int)_selfThread.ThreadContext.GetX(1)); + if (SmObjectFactory != null) { _context.Syscall.ManageNamedPort(out int serverPortHandle, "sm:", 50); diff --git a/src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs b/src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs index f39929ddc..5d01cd9de 100644 --- a/src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs +++ b/src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs @@ -6,8 +6,8 @@ namespace Ryujinx.Horizon.Bcat { internal class BcatIpcServer { - private const int BcatMaxSessionsCount = 8; - private const int BcatTotalMaxSessionsCount = BcatMaxSessionsCount * 4; + private const int MaxSessionsCount = 8; + private const int TotalMaxSessionsCount = MaxSessionsCount * 4; private const int PointerBufferSize = 0x400; private const int MaxDomains = 64; @@ -17,7 +17,7 @@ namespace Ryujinx.Horizon.Bcat private SmApi _sm; private BcatServerManager _serverManager; - private static readonly ManagerOptions _bcatManagerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); internal void Initialize() { @@ -26,13 +26,13 @@ namespace Ryujinx.Horizon.Bcat _sm = new SmApi(); _sm.Initialize().AbortOnFailure(); - _serverManager = new BcatServerManager(allocator, _sm, MaxPortsCount, _bcatManagerOptions, BcatTotalMaxSessionsCount); + _serverManager = new BcatServerManager(allocator, _sm, MaxPortsCount, _managerOptions, TotalMaxSessionsCount); #pragma warning disable IDE0055 // Disable formatting - _serverManager.RegisterServer((int)BcatPortIndex.Admin, ServiceName.Encode("bcat:a"), BcatMaxSessionsCount); - _serverManager.RegisterServer((int)BcatPortIndex.Manager, ServiceName.Encode("bcat:m"), BcatMaxSessionsCount); - _serverManager.RegisterServer((int)BcatPortIndex.User, ServiceName.Encode("bcat:u"), BcatMaxSessionsCount); - _serverManager.RegisterServer((int)BcatPortIndex.System, ServiceName.Encode("bcat:s"), BcatMaxSessionsCount); + _serverManager.RegisterServer((int)BcatPortIndex.Admin, ServiceName.Encode("bcat:a"), MaxSessionsCount); + _serverManager.RegisterServer((int)BcatPortIndex.Manager, ServiceName.Encode("bcat:m"), MaxSessionsCount); + _serverManager.RegisterServer((int)BcatPortIndex.User, ServiceName.Encode("bcat:u"), MaxSessionsCount); + _serverManager.RegisterServer((int)BcatPortIndex.System, ServiceName.Encode("bcat:s"), MaxSessionsCount); #pragma warning restore IDE0055 } diff --git a/src/Ryujinx.Horizon/HorizonStatic.cs b/src/Ryujinx.Horizon/HorizonStatic.cs index 1e483cd44..3e992eadb 100644 --- a/src/Ryujinx.Horizon/HorizonStatic.cs +++ b/src/Ryujinx.Horizon/HorizonStatic.cs @@ -4,7 +4,7 @@ using System; namespace Ryujinx.Horizon { - static class HorizonStatic + public static class HorizonStatic { [ThreadStatic] private static HorizonOptions _options; diff --git a/src/Ryujinx.Horizon/Lbl/Ipc/LblController.cs b/src/Ryujinx.Horizon/Lbl/Ipc/LblController.cs new file mode 100644 index 000000000..0a27d5efa --- /dev/null +++ b/src/Ryujinx.Horizon/Lbl/Ipc/LblController.cs @@ -0,0 +1,130 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Lbl; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Lbl.Ipc +{ + partial class LblController : ILblController + { + private bool _vrModeEnabled; + private float _currentBrightnessSettingForVrMode; + + [CmifCommand(17)] + public Result SetBrightnessReflectionDelayLevel(float unknown0, float unknown1) + { + // NOTE: Stubbed in system module. + + return Result.Success; + } + + [CmifCommand(18)] + public Result GetBrightnessReflectionDelayLevel(out float unknown1, float unknown0) + { + // NOTE: Stubbed in system module. + + unknown1 = 0.0f; + + return Result.Success; + } + + [CmifCommand(19)] + public Result SetCurrentBrightnessMapping(float unknown0, float unknown1, float unknown2) + { + // NOTE: Stubbed in system module. + + return Result.Success; + } + + [CmifCommand(20)] + public Result GetCurrentBrightnessMapping(out float unknown0, out float unknown1, out float unknown2) + { + // NOTE: Stubbed in system module. + + unknown0 = 0.0f; + unknown1 = 0.0f; + unknown2 = 0.0f; + + return Result.Success; + } + + [CmifCommand(21)] + public Result SetCurrentAmbientLightSensorMapping(float unknown0, float unknown1, float unknown2) + { + // NOTE: Stubbed in system module. + + return Result.Success; + } + + [CmifCommand(22)] + public Result GetCurrentAmbientLightSensorMapping(out float unknown0, out float unknown1, out float unknown2) + { + // NOTE: Stubbed in system module. + + unknown0 = 0.0f; + unknown1 = 0.0f; + unknown2 = 0.0f; + + return Result.Success; + } + + [CmifCommand(24)] + public Result SetCurrentBrightnessSettingForVrMode(float currentBrightnessSettingForVrMode) + { + if (float.IsNaN(currentBrightnessSettingForVrMode) || float.IsInfinity(currentBrightnessSettingForVrMode)) + { + _currentBrightnessSettingForVrMode = 0.0f; + } + else + { + _currentBrightnessSettingForVrMode = currentBrightnessSettingForVrMode; + } + + return Result.Success; + } + + [CmifCommand(25)] + public Result GetCurrentBrightnessSettingForVrMode(out float currentBrightnessSettingForVrMode) + { + if (float.IsNaN(_currentBrightnessSettingForVrMode) || float.IsInfinity(_currentBrightnessSettingForVrMode)) + { + currentBrightnessSettingForVrMode = 0.0f; + } + else + { + currentBrightnessSettingForVrMode = _currentBrightnessSettingForVrMode; + } + + return Result.Success; + } + + [CmifCommand(26)] + public Result EnableVrMode() + { + _vrModeEnabled = true; + + // NOTE: The service checks _vrModeEnabled field value in a thread and then changes the screen brightness. + // Since we don't support that, it's fine to do nothing. + + return Result.Success; + } + + [CmifCommand(27)] + public Result DisableVrMode() + { + _vrModeEnabled = false; + + // NOTE: The service checks _vrModeEnabled field value in a thread and then changes the screen brightness. + // Since we don't support that, it's fine to do nothing. + + return Result.Success; + } + + [CmifCommand(28)] + public Result IsVrModeEnabled(out bool vrModeEnabled) + { + vrModeEnabled = _vrModeEnabled; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Lbl/LblIpcServer.cs b/src/Ryujinx.Horizon/Lbl/LblIpcServer.cs new file mode 100644 index 000000000..53e74d515 --- /dev/null +++ b/src/Ryujinx.Horizon/Lbl/LblIpcServer.cs @@ -0,0 +1,43 @@ +using Ryujinx.Horizon.Lbl.Ipc; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Lbl +{ + class LblIpcServer + { + private const int MaxSessionsCount = 5; + + private const int PointerBufferSize = 0; + private const int MaxDomains = 0; + private const int MaxDomainObjects = 0; + private const int MaxPortsCount = 1; + + private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _managerOptions, MaxSessionsCount); + + _serverManager.RegisterObjectForServer(new LblController(), ServiceName.Encode("lbl"), MaxSessionsCount); + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Lbl/LblMain.cs b/src/Ryujinx.Horizon/Lbl/LblMain.cs new file mode 100644 index 000000000..f471f31b7 --- /dev/null +++ b/src/Ryujinx.Horizon/Lbl/LblMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Lbl +{ + class LblMain : IService + { + public static void Main(ServiceTable serviceTable) + { + LblIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/LogManager/LmIpcServer.cs b/src/Ryujinx.Horizon/LogManager/LmIpcServer.cs index d1a405b87..6bdc3c429 100644 --- a/src/Ryujinx.Horizon/LogManager/LmIpcServer.cs +++ b/src/Ryujinx.Horizon/LogManager/LmIpcServer.cs @@ -6,14 +6,14 @@ namespace Ryujinx.Horizon.LogManager { class LmIpcServer { - private const int LogMaxSessionsCount = 42; + private const int MaxSessionsCount = 42; private const int PointerBufferSize = 0x400; private const int MaxDomains = 31; private const int MaxDomainObjects = 61; private const int MaxPortsCount = 1; - private static readonly ManagerOptions _logManagerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); private SmApi _sm; private ServerManager _serverManager; @@ -25,9 +25,9 @@ namespace Ryujinx.Horizon.LogManager _sm = new SmApi(); _sm.Initialize().AbortOnFailure(); - _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _logManagerOptions, LogMaxSessionsCount); + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _managerOptions, MaxSessionsCount); - _serverManager.RegisterObjectForServer(new LogService(), ServiceName.Encode("lm"), LogMaxSessionsCount); + _serverManager.RegisterObjectForServer(new LogService(), ServiceName.Encode("lm"), MaxSessionsCount); } public void ServiceRequests() diff --git a/src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs b/src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs index e60b25581..b6615d2cd 100644 --- a/src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs +++ b/src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs @@ -6,14 +6,14 @@ namespace Ryujinx.Horizon.MmNv { class MmNvIpcServer { - private const int MmNvMaxSessionsCount = 9; + private const int MaxSessionsCount = 40; private const int PointerBufferSize = 0; private const int MaxDomains = 0; private const int MaxDomainObjects = 0; private const int MaxPortsCount = 1; - private static readonly ManagerOptions _mmNvOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); private SmApi _sm; private ServerManager _serverManager; @@ -25,9 +25,9 @@ namespace Ryujinx.Horizon.MmNv _sm = new SmApi(); _sm.Initialize().AbortOnFailure(); - _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _mmNvOptions, MmNvMaxSessionsCount); + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _managerOptions, MaxSessionsCount); - _serverManager.RegisterObjectForServer(new Request(), ServiceName.Encode("mm:u"), MmNvMaxSessionsCount); + _serverManager.RegisterObjectForServer(new Request(), ServiceName.Encode("mm:u"), MaxSessionsCount); } public void ServiceRequests() diff --git a/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs b/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs index 9c185520d..410f997e7 100644 --- a/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs +++ b/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs @@ -6,15 +6,15 @@ namespace Ryujinx.Horizon.Prepo { class PrepoIpcServer { - private const int PrepoMaxSessionsCount = 12; - private const int PrepoTotalMaxSessionsCount = PrepoMaxSessionsCount * 6; + private const int MaxSessionsCount = 12; + private const int TotalMaxSessionsCount = MaxSessionsCount * 6; private const int PointerBufferSize = 0x80; private const int MaxDomains = 64; private const int MaxDomainObjects = 16; private const int MaxPortsCount = 6; - private static readonly ManagerOptions _prepoManagerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); private SmApi _sm; private PrepoServerManager _serverManager; @@ -26,15 +26,15 @@ namespace Ryujinx.Horizon.Prepo _sm = new SmApi(); _sm.Initialize().AbortOnFailure(); - _serverManager = new PrepoServerManager(allocator, _sm, MaxPortsCount, _prepoManagerOptions, PrepoTotalMaxSessionsCount); + _serverManager = new PrepoServerManager(allocator, _sm, MaxPortsCount, _managerOptions, TotalMaxSessionsCount); #pragma warning disable IDE0055 // Disable formatting - _serverManager.RegisterServer((int)PrepoPortIndex.Admin, ServiceName.Encode("prepo:a"), PrepoMaxSessionsCount); // 1.0.0-5.1.0 - _serverManager.RegisterServer((int)PrepoPortIndex.Admin2, ServiceName.Encode("prepo:a2"), PrepoMaxSessionsCount); // 6.0.0+ - _serverManager.RegisterServer((int)PrepoPortIndex.Manager, ServiceName.Encode("prepo:m"), PrepoMaxSessionsCount); - _serverManager.RegisterServer((int)PrepoPortIndex.User, ServiceName.Encode("prepo:u"), PrepoMaxSessionsCount); - _serverManager.RegisterServer((int)PrepoPortIndex.System, ServiceName.Encode("prepo:s"), PrepoMaxSessionsCount); - _serverManager.RegisterServer((int)PrepoPortIndex.Debug, ServiceName.Encode("prepo:d"), PrepoMaxSessionsCount); // 1.0.0 + _serverManager.RegisterServer((int)PrepoPortIndex.Admin, ServiceName.Encode("prepo:a"), MaxSessionsCount); // 1.0.0-5.1.0 + _serverManager.RegisterServer((int)PrepoPortIndex.Admin2, ServiceName.Encode("prepo:a2"), MaxSessionsCount); // 6.0.0+ + _serverManager.RegisterServer((int)PrepoPortIndex.Manager, ServiceName.Encode("prepo:m"), MaxSessionsCount); + _serverManager.RegisterServer((int)PrepoPortIndex.User, ServiceName.Encode("prepo:u"), MaxSessionsCount); + _serverManager.RegisterServer((int)PrepoPortIndex.System, ServiceName.Encode("prepo:s"), MaxSessionsCount); + _serverManager.RegisterServer((int)PrepoPortIndex.Debug, ServiceName.Encode("prepo:d"), MaxSessionsCount); // 1.0.0 #pragma warning restore IDE0055 } diff --git a/src/Ryujinx.Horizon/Sdk/Lbl/ILblController.cs b/src/Ryujinx.Horizon/Sdk/Lbl/ILblController.cs new file mode 100644 index 000000000..594722e9c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Lbl/ILblController.cs @@ -0,0 +1,20 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Lbl +{ + interface ILblController : IServiceObject + { + Result SetBrightnessReflectionDelayLevel(float unknown0, float unknown1); + Result GetBrightnessReflectionDelayLevel(out float unknown1, float unknown0); + Result SetCurrentBrightnessMapping(float unknown0, float unknown1, float unknown2); + Result GetCurrentBrightnessMapping(out float unknown0, out float unknown1, out float unknown2); + Result SetCurrentAmbientLightSensorMapping(float unknown0, float unknown1, float unknown2); + Result GetCurrentAmbientLightSensorMapping(out float unknown0, out float unknown1, out float unknown2); + Result SetCurrentBrightnessSettingForVrMode(float currentBrightnessSettingForVrMode); + Result GetCurrentBrightnessSettingForVrMode(out float currentBrightnessSettingForVrMode); + Result EnableVrMode(); + Result DisableVrMode(); + Result IsVrModeEnabled(out bool vrModeEnabled); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Lbl/LblApi.cs b/src/Ryujinx.Horizon/Sdk/Lbl/LblApi.cs new file mode 100644 index 000000000..843a9acd6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Lbl/LblApi.cs @@ -0,0 +1,43 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sm; +using System; + +namespace Ryujinx.Horizon.Sdk.Lbl +{ + public class LblApi : IDisposable + { + private const string LblName = "lbl"; + + private int _sessionHandle; + + public LblApi() + { + using var smApi = new SmApi(); + + smApi.Initialize(); + smApi.GetServiceHandle(out _sessionHandle, ServiceName.Encode(LblName)).AbortOnFailure(); + } + + public Result EnableVrMode() + { + return ServiceUtil.SendRequest(out _, _sessionHandle, 26, sendPid: false, ReadOnlySpan.Empty); + } + + public Result DisableVrMode() + { + return ServiceUtil.SendRequest(out _, _sessionHandle, 27, sendPid: false, ReadOnlySpan.Empty); + } + + public void Dispose() + { + if (_sessionHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_sessionHandle); + + _sessionHandle = 0; + } + + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sm/ServiceName.cs b/src/Ryujinx.Horizon/Sdk/Sm/ServiceName.cs index f90d39c22..c89f118cf 100644 --- a/src/Ryujinx.Horizon/Sdk/Sm/ServiceName.cs +++ b/src/Ryujinx.Horizon/Sdk/Sm/ServiceName.cs @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; namespace Ryujinx.Horizon.Sdk.Sm { [StructLayout(LayoutKind.Sequential, Pack = 1)] - readonly struct ServiceName + public readonly struct ServiceName { public static ServiceName Invalid { get; } = new(0); diff --git a/src/Ryujinx.Horizon/Sdk/Sm/SmApi.cs b/src/Ryujinx.Horizon/Sdk/Sm/SmApi.cs index 3e5635bf1..1ab400bde 100644 --- a/src/Ryujinx.Horizon/Sdk/Sm/SmApi.cs +++ b/src/Ryujinx.Horizon/Sdk/Sm/SmApi.cs @@ -5,7 +5,7 @@ using System; namespace Ryujinx.Horizon.Sdk.Sm { - class SmApi + public class SmApi : IDisposable { private const string SmName = "sm:"; @@ -109,5 +109,17 @@ namespace Ryujinx.Horizon.Sdk.Sm return ServiceUtil.SendRequest(out _, _portHandle, 4, sendPid: true, data); } + + public void Dispose() + { + if (_portHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_portHandle); + + _portHandle = 0; + } + + GC.SuppressFinalize(this); + } } } diff --git a/src/Ryujinx.Horizon/ServiceTable.cs b/src/Ryujinx.Horizon/ServiceTable.cs index 8dfacbebd..cd443329b 100644 --- a/src/Ryujinx.Horizon/ServiceTable.cs +++ b/src/Ryujinx.Horizon/ServiceTable.cs @@ -1,4 +1,5 @@ using Ryujinx.Horizon.Bcat; +using Ryujinx.Horizon.Lbl; using Ryujinx.Horizon.LogManager; using Ryujinx.Horizon.MmNv; using Ryujinx.Horizon.Prepo; @@ -23,10 +24,11 @@ namespace Ryujinx.Horizon entries.Add(new ServiceEntry(T.Main, this, options)); } - RegisterService(); - RegisterService(); RegisterService(); + RegisterService(); + RegisterService(); RegisterService(); + RegisterService(); _totalServices = entries.Count; From a745913329c4f4a135b276212f172578f08bc718 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 14 Sep 2023 14:53:53 -0300 Subject: [PATCH 25/64] Fix gl_Layer to geometry shader change not writing gl_Layer (#5682) * Fix gl_Layer to geometry shader change not writing gl_Layer * Shader cache version bump --- .../Shader/DiskCache/DiskCacheHostStorage.cs | 2 +- src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 4b99bb097..b46ceb4f6 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 5668; + private const uint CodeGenVersion = 5682; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs index f1226ae6d..3feb881a2 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs @@ -576,6 +576,11 @@ namespace Ryujinx.Graphics.Shader.Translation int outputAttributesMask = AttributeUsage.UsedOutputAttributes; int layerOutputAttr = LayerOutputAttribute; + if (LayerOutputWritten) + { + outputAttributesMask |= 1 << ((layerOutputAttr - AttributeConsts.UserAttributeBase) / 16); + } + OutputTopology outputTopology; int maxOutputVertices; From 7ccff037e87f82f3461f3e1422235e29800eaa2f Mon Sep 17 00:00:00 2001 From: gdkchan Date: Thu, 14 Sep 2023 14:58:11 -0300 Subject: [PATCH 26/64] Fix some Vulkan validation errors (mostly related to barriers) (#5603) * Replace image barriers inside render pass with more generic memory barrier * Remove forceStorage since it was creating images with storage bit for formats that are not StorageImage compatible * Add missing flags on subpass dependency * Don't call vkCmdSetScissor with a scissor count of 0 * One semaphore per swapchain image * Remove compute stage from read to write barriers * Try to improve Pipeline.Barrier nonsense * Set PipelineStateFlags based on supported stages --- .../FramebufferParams.cs | 12 +++- .../HardwareCapabilities.cs | 3 + src/Ryujinx.Graphics.Vulkan/PipelineBase.cs | 16 ++++- .../PipelineConverter.cs | 16 +++-- .../PipelineDynamicState.cs | 5 +- src/Ryujinx.Graphics.Vulkan/TextureStorage.cs | 46 ++++++------ src/Ryujinx.Graphics.Vulkan/TextureView.cs | 28 ++++++++ src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 1 + src/Ryujinx.Graphics.Vulkan/Window.cs | 72 ++++++++++++++----- 9 files changed, 145 insertions(+), 54 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs index 7600c2d5e..662bb80f8 100644 --- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs +++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs @@ -257,14 +257,22 @@ namespace Ryujinx.Graphics.Vulkan if (realIndex != -1) { - _colors[realIndex].Storage?.InsertReadToWriteBarrier(cbs, AccessFlags.ColorAttachmentWriteBit, PipelineStageFlags.ColorAttachmentOutputBit); + _colors[realIndex].Storage?.InsertReadToWriteBarrier( + cbs, + AccessFlags.ColorAttachmentWriteBit, + PipelineStageFlags.ColorAttachmentOutputBit, + insideRenderPass: true); } } } public void InsertClearBarrierDS(CommandBufferScoped cbs) { - _depthStencil?.Storage?.InsertReadToWriteBarrier(cbs, AccessFlags.DepthStencilAttachmentWriteBit, PipelineStageFlags.LateFragmentTestsBit); + _depthStencil?.Storage?.InsertReadToWriteBarrier( + cbs, + AccessFlags.DepthStencilAttachmentWriteBit, + PipelineStageFlags.LateFragmentTestsBit, + insideRenderPass: true); } } } diff --git a/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs b/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs index 798de5c90..32e941dba 100644 --- a/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs +++ b/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs @@ -41,6 +41,7 @@ namespace Ryujinx.Graphics.Vulkan public readonly bool SupportsPreciseOcclusionQueries; public readonly bool SupportsPipelineStatisticsQuery; public readonly bool SupportsGeometryShader; + public readonly bool SupportsTessellationShader; public readonly bool SupportsViewportArray2; public readonly bool SupportsHostImportedMemory; public readonly bool SupportsDepthClipControl; @@ -77,6 +78,7 @@ namespace Ryujinx.Graphics.Vulkan bool supportsPreciseOcclusionQueries, bool supportsPipelineStatisticsQuery, bool supportsGeometryShader, + bool supportsTessellationShader, bool supportsViewportArray2, bool supportsHostImportedMemory, bool supportsDepthClipControl, @@ -112,6 +114,7 @@ namespace Ryujinx.Graphics.Vulkan SupportsPreciseOcclusionQueries = supportsPreciseOcclusionQueries; SupportsPipelineStatisticsQuery = supportsPipelineStatisticsQuery; SupportsGeometryShader = supportsGeometryShader; + SupportsTessellationShader = supportsTessellationShader; SupportsViewportArray2 = supportsViewportArray2; SupportsHostImportedMemory = supportsHostImportedMemory; SupportsDepthClipControl = supportsDepthClipControl; diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs index 54b67f35e..156b3db16 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -149,10 +149,22 @@ namespace Ryujinx.Graphics.Vulkan 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.FragmentShaderBit, - PipelineStageFlags.FragmentShaderBit, + pipelineStageFlags, + pipelineStageFlags, 0, 1, memoryBarrier, diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs b/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs index 7c1ddef8d..b2da61031 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs @@ -9,8 +9,12 @@ namespace Ryujinx.Graphics.Vulkan { static class PipelineConverter { - private const AccessFlags SubpassSrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit | AccessFlags.ColorAttachmentWriteBit; - private const AccessFlags SubpassDstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit | AccessFlags.ShaderReadBit; + 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) { @@ -132,8 +136,8 @@ namespace Ryujinx.Graphics.Vulkan 0, PipelineStageFlags.AllGraphicsBit, PipelineStageFlags.AllGraphicsBit, - SubpassSrcAccessMask, - SubpassDstAccessMask, + SubpassAccessMask, + SubpassAccessMask, 0); } @@ -146,8 +150,8 @@ namespace Ryujinx.Graphics.Vulkan 0, PipelineStageFlags.AllGraphicsBit, PipelineStageFlags.AllGraphicsBit, - SubpassSrcAccessMask, - SubpassDstAccessMask, + SubpassAccessMask, + SubpassAccessMask, 0); } diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs index ff88e4cf4..a5c218ac2 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs @@ -146,7 +146,10 @@ namespace Ryujinx.Graphics.Vulkan private void RecordScissor(Vk api, CommandBuffer commandBuffer) { - api.CmdSetScissor(commandBuffer, 0, (uint)ScissorsCount, _scissors.AsSpan()); + if (ScissorsCount != 0) + { + api.CmdSetScissor(commandBuffer, 0, (uint)ScissorsCount, _scissors.AsSpan()); + } } private readonly void RecordStencilMasks(Vk api, CommandBuffer commandBuffer) diff --git a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs index 090f69dca..e9c2bf1ec 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs @@ -78,7 +78,7 @@ namespace Ryujinx.Graphics.Vulkan var sampleCountFlags = ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)info.Samples); - var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample, forceStorage: true); + var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample); var flags = ImageCreateFlags.CreateMutableFormatBit; @@ -291,7 +291,7 @@ namespace Ryujinx.Graphics.Vulkan } } - public static ImageUsageFlags GetImageUsage(Format format, Target target, bool supportsMsStorage, bool forceStorage = false) + public static ImageUsageFlags GetImageUsage(Format format, Target target, bool supportsMsStorage) { var usage = DefaultUsageFlags; @@ -304,7 +304,7 @@ namespace Ryujinx.Graphics.Vulkan usage |= ImageUsageFlags.ColorAttachmentBit; } - if (((forceStorage && !format.IsDepthOrStencil()) || format.IsImageCompatible()) && (supportsMsStorage || !target.IsMultisample())) + if (format.IsImageCompatible() && (supportsMsStorage || !target.IsMultisample())) { usage |= ImageUsageFlags.StorageBit; } @@ -440,25 +440,27 @@ namespace Ryujinx.Graphics.Vulkan _lastModificationStage = stage; } - public void InsertReadToWriteBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags) + public void InsertReadToWriteBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags, bool insideRenderPass) { - if (_lastReadAccess != AccessFlags.None) - { - ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags(); + var lastReadStage = _lastReadStage; - TextureView.InsertImageBarrier( + if (insideRenderPass) + { + // We can't have barrier from compute inside a render pass, + // as it is invalid to specify compute in the subpass dependency stage mask. + + lastReadStage &= ~PipelineStageFlags.ComputeShaderBit; + } + + if (lastReadStage != PipelineStageFlags.None) + { + TextureView.InsertMemoryBarrier( _gd.Api, cbs.CommandBuffer, - _imageAuto.Get(cbs).Value, _lastReadAccess, dstAccessFlags, - _lastReadStage, - dstStageFlags, - aspectFlags, - 0, - 0, - _info.GetLayers(), - _info.Levels); + lastReadStage, + dstStageFlags); _lastReadAccess = AccessFlags.None; _lastReadStage = PipelineStageFlags.None; @@ -472,21 +474,13 @@ namespace Ryujinx.Graphics.Vulkan if (_lastModificationAccess != AccessFlags.None) { - ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags(); - - TextureView.InsertImageBarrier( + TextureView.InsertMemoryBarrier( _gd.Api, cbs.CommandBuffer, - _imageAuto.Get(cbs).Value, _lastModificationAccess, dstAccessFlags, _lastModificationStage, - dstStageFlags, - aspectFlags, - 0, - 0, - _info.GetLayers(), - _info.Levels); + dstStageFlags); _lastModificationAccess = AccessFlags.None; } diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs index 9fc50f67a..09128f007 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs @@ -435,6 +435,34 @@ namespace Ryujinx.Graphics.Vulkan ImageAspectFlags.ColorBit); } + public static unsafe void InsertMemoryBarrier( + Vk api, + CommandBuffer commandBuffer, + AccessFlags srcAccessMask, + AccessFlags dstAccessMask, + PipelineStageFlags srcStageMask, + PipelineStageFlags dstStageMask) + { + MemoryBarrier memoryBarrier = new() + { + SType = StructureType.MemoryBarrier, + SrcAccessMask = srcAccessMask, + DstAccessMask = dstAccessMask, + }; + + api.CmdPipelineBarrier( + commandBuffer, + srcStageMask, + dstStageMask, + DependencyFlags.None, + 1, + memoryBarrier, + 0, + null, + 0, + null); + } + public static unsafe void InsertImageBarrier( Vk api, CommandBuffer commandBuffer, diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 7b6b89a74..ac598c587 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -327,6 +327,7 @@ namespace Ryujinx.Graphics.Vulkan features2.Features.OcclusionQueryPrecise, _physicalDevice.PhysicalDeviceFeatures.PipelineStatisticsQuery, _physicalDevice.PhysicalDeviceFeatures.GeometryShader, + _physicalDevice.PhysicalDeviceFeatures.TessellationShader, _physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"), _physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName), supportsDepthClipControl && featuresDepthClipControl.DepthClipControl, diff --git a/src/Ryujinx.Graphics.Vulkan/Window.cs b/src/Ryujinx.Graphics.Vulkan/Window.cs index 2d0ad664c..afaa7defe 100644 --- a/src/Ryujinx.Graphics.Vulkan/Window.cs +++ b/src/Ryujinx.Graphics.Vulkan/Window.cs @@ -22,8 +22,10 @@ namespace Ryujinx.Graphics.Vulkan private Image[] _swapchainImages; private Auto[] _swapchainImageViews; - private Semaphore _imageAvailableSemaphore; - private Semaphore _renderFinishedSemaphore; + private Semaphore[] _imageAvailableSemaphores; + private Semaphore[] _renderFinishedSemaphores; + + private int _frameIndex; private int _width; private int _height; @@ -48,14 +50,6 @@ namespace Ryujinx.Graphics.Vulkan _surface = surface; CreateSwapchain(); - - var semaphoreCreateInfo = new SemaphoreCreateInfo - { - SType = StructureType.SemaphoreCreateInfo, - }; - - gd.Api.CreateSemaphore(device, semaphoreCreateInfo, null, out _imageAvailableSemaphore).ThrowOnError(); - gd.Api.CreateSemaphore(device, semaphoreCreateInfo, null, out _renderFinishedSemaphore).ThrowOnError(); } private void RecreateSwapchain() @@ -69,7 +63,22 @@ namespace Ryujinx.Graphics.Vulkan } // Destroy old Swapchain. + _gd.Api.DeviceWaitIdle(_device); + + unsafe + { + for (int i = 0; i < _imageAvailableSemaphores.Length; i++) + { + _gd.Api.DestroySemaphore(_device, _imageAvailableSemaphores[i], null); + } + + for (int i = 0; i < _renderFinishedSemaphores.Length; i++) + { + _gd.Api.DestroySemaphore(_device, _renderFinishedSemaphores[i], null); + } + } + _gd.SwapchainApi.DestroySwapchain(_device, oldSwapchain, Span.Empty); CreateSwapchain(); @@ -151,6 +160,25 @@ namespace Ryujinx.Graphics.Vulkan { _swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format); } + + var semaphoreCreateInfo = new SemaphoreCreateInfo + { + SType = StructureType.SemaphoreCreateInfo, + }; + + _imageAvailableSemaphores = new Semaphore[imageCount]; + + for (int i = 0; i < _imageAvailableSemaphores.Length; i++) + { + _gd.Api.CreateSemaphore(_device, 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(); + } } private unsafe Auto CreateSwapchainImageView(Image swapchainImage, VkFormat format) @@ -185,6 +213,7 @@ namespace Ryujinx.Graphics.Vulkan { return new SurfaceFormatKHR(VkFormat.B8G8R8A8Unorm, ColorSpaceKHR.PaceSrgbNonlinearKhr); } + var formatToReturn = availableFormats[0]; if (colorSpacePassthroughEnabled) { @@ -212,6 +241,7 @@ namespace Ryujinx.Graphics.Vulkan } } } + return formatToReturn; } @@ -265,6 +295,7 @@ namespace Ryujinx.Graphics.Vulkan _gd.PipelineInternal.AutoFlush.Present(); uint nextImage = 0; + int semaphoreIndex = _frameIndex++ % _imageAvailableSemaphores.Length; while (true) { @@ -272,7 +303,7 @@ namespace Ryujinx.Graphics.Vulkan _device, _swapchain, ulong.MaxValue, - _imageAvailableSemaphore, + _imageAvailableSemaphores[semaphoreIndex], new Fence(), ref nextImage); @@ -411,12 +442,12 @@ namespace Ryujinx.Graphics.Vulkan _gd.CommandBufferPool.Return( cbs, - stackalloc[] { _imageAvailableSemaphore }, + stackalloc[] { _imageAvailableSemaphores[semaphoreIndex] }, stackalloc[] { PipelineStageFlags.ColorAttachmentOutputBit }, - stackalloc[] { _renderFinishedSemaphore }); + stackalloc[] { _renderFinishedSemaphores[semaphoreIndex] }); // TODO: Present queue. - var semaphore = _renderFinishedSemaphore; + var semaphore = _renderFinishedSemaphores[semaphoreIndex]; var swapchain = _swapchain; Result result; @@ -593,14 +624,21 @@ namespace Ryujinx.Graphics.Vulkan { unsafe { - _gd.Api.DestroySemaphore(_device, _renderFinishedSemaphore, null); - _gd.Api.DestroySemaphore(_device, _imageAvailableSemaphore, null); - for (int i = 0; i < _swapchainImageViews.Length; i++) { _swapchainImageViews[i].Dispose(); } + for (int i = 0; i < _imageAvailableSemaphores.Length; i++) + { + _gd.Api.DestroySemaphore(_device, _imageAvailableSemaphores[i], null); + } + + for (int i = 0; i < _renderFinishedSemaphores.Length; i++) + { + _gd.Api.DestroySemaphore(_device, _renderFinishedSemaphores[i], null); + } + _gd.SwapchainApi.DestroySwapchain(_device, _swapchain, null); } From 88df636c870528a24695a23ffeb3768399bcf0af Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sat, 16 Sep 2023 13:49:13 -0300 Subject: [PATCH 27/64] Replace ShaderOutputLayer with equivalent ShaderViewportIndexLayerEXT capability (#5683) --- .../CodeGen/Spirv/SpirvGenerator.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs index 44d3e9853..0e9e32bba 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs @@ -89,6 +89,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv context.AddCapability(Capability.DrawParameters); } + if (context.Definitions.Stage != ShaderStage.Fragment && + context.Definitions.Stage != ShaderStage.Geometry && + context.Definitions.Stage != ShaderStage.Compute && + (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Output, IoVariable.Layer)) || + context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Output, IoVariable.ViewportIndex)))) + { + context.AddExtension("SPV_EXT_shader_viewport_index_layer"); + context.AddCapability(Capability.ShaderViewportIndexLayerEXT); + } + if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Output, IoVariable.ViewportMask))) { context.AddExtension("SPV_NV_viewport_array2"); @@ -277,14 +287,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv localSizeZ); } - if (context.Definitions.Stage != ShaderStage.Fragment && - context.Definitions.Stage != ShaderStage.Geometry && - context.Definitions.Stage != ShaderStage.Compute && - context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Output, IoVariable.Layer))) - { - context.AddCapability(Capability.ShaderLayer); - } - if (context.Definitions.TransformFeedbackEnabled && context.Definitions.LastInVertexPipeline) { context.AddExecutionMode(spvFunc, ExecutionMode.Xfb); From 9f26fd36008d2cf4fe96b8dce7ec935b38b0b4c7 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 18 Sep 2023 20:09:22 +0000 Subject: [PATCH 28/64] remove some usages of reflection binding (#5686) --- src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml | 6 ++++-- src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml | 8 +++++--- .../UI/Controls/ApplicationGridView.axaml | 14 +++++++------- .../UI/Controls/ApplicationListView.axaml | 12 ++++++------ src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml | 15 ++++++++------- .../DownloadableContentManagerWindow.axaml | 10 +++++----- 6 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml b/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml index a70fc8d44..6186b7d93 100644 --- a/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml +++ b/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml @@ -6,9 +6,11 @@ xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="{locale:Locale ErrorWindowTitle}" + xmlns:views="using:Ryujinx.Ava.UI.Applet" Width="450" Height="340" CanResize="False" + x:DataType="views:ErrorAppletWindow" SizeToContent="Height" mc:Ignorable="d" Focusable="True"> @@ -38,7 +40,7 @@ Grid.Column="1" Margin="10" VerticalAlignment="Stretch" - Text="{ReflectionBinding Message}" + Text="{Binding Message}" TextWrapping="Wrap" /> - \ No newline at end of file + diff --git a/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml b/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml index 64b23f987..7e0836065 100644 --- a/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml +++ b/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml @@ -4,7 +4,9 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:views="using:Ryujinx.Ava.UI.Controls" Width="400" + x:DataType="views:SwkbdAppletDialog" mc:Ignorable="d" Focusable="True"> diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml b/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml index 214fc0755..bbdb4c4a7 100644 --- a/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml +++ b/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml @@ -46,7 +46,7 @@ @@ -56,10 +56,10 @@ Margin="10" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" - Classes.huge="{ReflectionBinding $parent[UserControl].DataContext.IsGridHuge}" - Classes.large="{ReflectionBinding $parent[UserControl].DataContext.IsGridLarge}" - Classes.normal="{ReflectionBinding $parent[UserControl].DataContext.IsGridMedium}" - Classes.small="{ReflectionBinding $parent[UserControl].DataContext.IsGridSmall}" + Classes.huge="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridHuge}" + Classes.large="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridLarge}" + Classes.normal="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridMedium}" + Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}" ClipToBounds="True" CornerRadius="4"> @@ -78,7 +78,7 @@ Margin="0,10,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" - IsVisible="{ReflectionBinding $parent[UserControl].DataContext.ShowNames}"> + IsVisible="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}"> - \ No newline at end of file + diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml b/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml index 75bbf9d0d..09011005b 100644 --- a/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml +++ b/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml @@ -42,7 +42,7 @@ @@ -67,10 +67,10 @@ Grid.RowSpan="3" Grid.Column="0" Margin="0" - Classes.huge="{ReflectionBinding $parent[UserControl].DataContext.IsGridHuge}" - Classes.large="{ReflectionBinding $parent[UserControl].DataContext.IsGridLarge}" - Classes.normal="{ReflectionBinding $parent[UserControl].DataContext.IsGridMedium}" - Classes.small="{ReflectionBinding $parent[UserControl].DataContext.IsGridSmall}" + Classes.huge="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridHuge}" + Classes.large="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridLarge}" + Classes.normal="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridMedium}" + Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}" Source="{Binding Icon, Converter={StaticResource ByteImage}}" /> - \ No newline at end of file + diff --git a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml b/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml index b9cbcb9cc..8a5da5cc2 100644 --- a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml +++ b/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml @@ -11,6 +11,7 @@ Height="500" MinWidth="500" MinHeight="500" + x:DataType="window:CheatWindow" WindowStartupLocation="CenterOwner" mc:Ignorable="d" Focusable="True"> @@ -40,7 +41,7 @@ HorizontalAlignment="Center" VerticalAlignment="Center" LineHeight="18" - Text="{ReflectionBinding Heading}" + Text="{Binding Heading}" TextAlignment="Center" TextWrapping="Wrap" /> + ItemsSource="{Binding LoadedCheats}"> + + + + + + + + + + + + Enum.GetNames(typeof(AspectRatio)).Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1; } From 4e2bb130809c9fe0b8707fb9aac0058217330a49 Mon Sep 17 00:00:00 2001 From: TSRBerry <20988865+TSRBerry@users.noreply.github.com> Date: Mon, 9 Oct 2023 15:47:47 +0200 Subject: [PATCH 62/64] Fix games freezing after initializing LDN 1021 times (#5787) * Close handle for stateChangeEvent on Finalize * Properly dispose NetworkClient before setting it to null --- .../IUserLocalCommunicationService.cs | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs index 6abd2b893..29cc0e1b9 100644 --- a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs @@ -29,6 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator private const bool IsDevelopment = false; private readonly KEvent _stateChangeEvent; + private int _stateChangeEventHandle; private NetworkState _state; private DisconnectReason _disconnectReason; @@ -277,12 +278,12 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator // AttachStateChangeEvent() -> handle public ResultCode AttachStateChangeEvent(ServiceCtx context) { - if (context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out int stateChangeEventHandle) != Result.Success) + if (_stateChangeEventHandle == 0 && context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out _stateChangeEventHandle) != Result.Success) { throw new InvalidOperationException("Out of handles!"); } - context.Response.HandleDesc = IpcHandleDesc.MakeCopy(stateChangeEventHandle); + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_stateChangeEventHandle); // Returns ResultCode.InvalidArgument if handle is null, doesn't occur in our case since we already throw an Exception. @@ -964,6 +965,12 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator SetDisconnectReason(DisconnectReason.None); } + if (_stateChangeEventHandle != 0) + { + context.Process.HandleTable.CloseHandle(_stateChangeEventHandle); + _stateChangeEventHandle = 0; + } + return resultCode; } @@ -1021,7 +1028,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator SetState(NetworkState.None); - NetworkClient?.DisconnectAndStop(); + NetworkClient?.Dispose(); NetworkClient = null; return ResultCode.Success; @@ -1072,7 +1079,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator } else { - // NOTE: Service returns differents ResultCode here related to the nifm ResultCode. + // NOTE: Service returns different ResultCode here related to the nifm ResultCode. resultCode = ResultCode.DeviceDisabled; _nifmResultCode = resultCode; } @@ -1084,14 +1091,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator public void Dispose() { - if (NetworkClient != null) - { - _station?.Dispose(); - _accessPoint?.Dispose(); + _station?.Dispose(); + _station = null; - NetworkClient.DisconnectAndStop(); - } + _accessPoint?.Dispose(); + _accessPoint = null; + NetworkClient?.Dispose(); NetworkClient = null; } } From e768a54f17b390c3ac10904c7909e3bef020edbd Mon Sep 17 00:00:00 2001 From: sunshineinabox Date: Thu, 12 Oct 2023 09:11:15 -0700 Subject: [PATCH 63/64] Replace ReaderWriterLock with ReaderWriterLockSlim (#5785) * Replace ReaderWriterLock with ReaderWriterLockSlim * Resolve Feedback + Correct typo * Revert some unncessary logic --- .../Translation/TranslatorCache.cs | 36 +++++----- src/Ryujinx.Common/ReactiveObject.cs | 10 +-- src/Ryujinx.Graphics.Vulkan/BufferHolder.cs | 68 ++++++++++--------- 3 files changed, 58 insertions(+), 56 deletions(-) diff --git a/src/ARMeilleure/Translation/TranslatorCache.cs b/src/ARMeilleure/Translation/TranslatorCache.cs index 11286381b..99ca58dc6 100644 --- a/src/ARMeilleure/Translation/TranslatorCache.cs +++ b/src/ARMeilleure/Translation/TranslatorCache.cs @@ -7,14 +7,14 @@ namespace ARMeilleure.Translation internal class TranslatorCache { private readonly IntervalTree _tree; - private readonly ReaderWriterLock _treeLock; + private readonly ReaderWriterLockSlim _treeLock; public int Count => _tree.Count; public TranslatorCache() { _tree = new IntervalTree(); - _treeLock = new ReaderWriterLock(); + _treeLock = new ReaderWriterLockSlim(); } public bool TryAdd(ulong address, ulong size, T value) @@ -24,70 +24,70 @@ namespace ARMeilleure.Translation public bool AddOrUpdate(ulong address, ulong size, T value, Func updateFactoryCallback) { - _treeLock.AcquireWriterLock(Timeout.Infinite); + _treeLock.EnterWriteLock(); bool result = _tree.AddOrUpdate(address, address + size, value, updateFactoryCallback); - _treeLock.ReleaseWriterLock(); + _treeLock.ExitWriteLock(); return result; } public T GetOrAdd(ulong address, ulong size, T value) { - _treeLock.AcquireWriterLock(Timeout.Infinite); + _treeLock.EnterWriteLock(); value = _tree.GetOrAdd(address, address + size, value); - _treeLock.ReleaseWriterLock(); + _treeLock.ExitWriteLock(); return value; } public bool Remove(ulong address) { - _treeLock.AcquireWriterLock(Timeout.Infinite); + _treeLock.EnterWriteLock(); bool removed = _tree.Remove(address) != 0; - _treeLock.ReleaseWriterLock(); + _treeLock.ExitWriteLock(); return removed; } public void Clear() { - _treeLock.AcquireWriterLock(Timeout.Infinite); + _treeLock.EnterWriteLock(); _tree.Clear(); - _treeLock.ReleaseWriterLock(); + _treeLock.ExitWriteLock(); } public bool ContainsKey(ulong address) { - _treeLock.AcquireReaderLock(Timeout.Infinite); + _treeLock.EnterReadLock(); bool result = _tree.ContainsKey(address); - _treeLock.ReleaseReaderLock(); + _treeLock.ExitReadLock(); return result; } public bool TryGetValue(ulong address, out T value) { - _treeLock.AcquireReaderLock(Timeout.Infinite); + _treeLock.EnterReadLock(); bool result = _tree.TryGet(address, out value); - _treeLock.ReleaseReaderLock(); + _treeLock.ExitReadLock(); return result; } public int GetOverlaps(ulong address, ulong size, ref ulong[] overlaps) { - _treeLock.AcquireReaderLock(Timeout.Infinite); + _treeLock.EnterReadLock(); int count = _tree.Get(address, address + size, ref overlaps); - _treeLock.ReleaseReaderLock(); + _treeLock.ExitReadLock(); return count; } public List AsList() { - _treeLock.AcquireReaderLock(Timeout.Infinite); + _treeLock.EnterReadLock(); List list = _tree.AsList(); - _treeLock.ReleaseReaderLock(); + _treeLock.ExitReadLock(); return list; } diff --git a/src/Ryujinx.Common/ReactiveObject.cs b/src/Ryujinx.Common/ReactiveObject.cs index d2624c365..ac7d2c4d8 100644 --- a/src/Ryujinx.Common/ReactiveObject.cs +++ b/src/Ryujinx.Common/ReactiveObject.cs @@ -5,7 +5,7 @@ namespace Ryujinx.Common { public class ReactiveObject { - private readonly ReaderWriterLock _readerWriterLock = new(); + private readonly ReaderWriterLockSlim _readerWriterLock = new(); private bool _isInitialized; private T _value; @@ -15,15 +15,15 @@ namespace Ryujinx.Common { get { - _readerWriterLock.AcquireReaderLock(Timeout.Infinite); + _readerWriterLock.EnterReadLock(); T value = _value; - _readerWriterLock.ReleaseReaderLock(); + _readerWriterLock.ExitReadLock(); return value; } set { - _readerWriterLock.AcquireWriterLock(Timeout.Infinite); + _readerWriterLock.EnterWriteLock(); T oldValue = _value; @@ -32,7 +32,7 @@ namespace Ryujinx.Common _isInitialized = true; _value = value; - _readerWriterLock.ReleaseWriterLock(); + _readerWriterLock.ExitWriteLock(); if (!oldIsInitialized || oldValue == null || !oldValue.Equals(_value)) { diff --git a/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs b/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs index a93ced0e5..d3a3cae11 100644 --- a/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs +++ b/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs @@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.Vulkan private int _flushTemp; private int _lastFlushWrite = -1; - private readonly ReaderWriterLock _flushLock; + private readonly ReaderWriterLockSlim _flushLock; private FenceHolder _flushFence; private int _flushWaiting; @@ -85,7 +85,7 @@ namespace Ryujinx.Graphics.Vulkan _currentType = currentType; DesiredType = currentType; - _flushLock = new ReaderWriterLock(); + _flushLock = new ReaderWriterLockSlim(); _useMirrors = gd.IsTBDR; } @@ -106,7 +106,7 @@ namespace Ryujinx.Graphics.Vulkan _currentType = currentType; DesiredType = currentType; - _flushLock = new ReaderWriterLock(); + _flushLock = new ReaderWriterLockSlim(); } public bool TryBackingSwap(ref CommandBufferScoped? cbs) @@ -116,7 +116,7 @@ namespace Ryujinx.Graphics.Vulkan // Only swap if the buffer is not used in any queued command buffer. bool isRented = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool); - if (!isRented && _gd.CommandBufferPool.OwnedByCurrentThread && !_flushLock.IsReaderLockHeld && (_pendingData == null || cbs != null)) + if (!isRented && _gd.CommandBufferPool.OwnedByCurrentThread && !_flushLock.IsReadLockHeld && (_pendingData == null || cbs != null)) { var currentAllocation = _allocationAuto; var currentBuffer = _buffer; @@ -131,7 +131,7 @@ namespace Ryujinx.Graphics.Vulkan ClearMirrors(cbs.Value, 0, Size); } - _flushLock.AcquireWriterLock(Timeout.Infinite); + _flushLock.EnterWriteLock(); ClearFlushFence(); @@ -185,7 +185,7 @@ namespace Ryujinx.Graphics.Vulkan _gd.PipelineInternal.SwapBuffer(currentBuffer, _buffer); - _flushLock.ReleaseWriterLock(); + _flushLock.ExitWriteLock(); } _swapQueued = false; @@ -548,42 +548,44 @@ namespace Ryujinx.Graphics.Vulkan private void WaitForFlushFence() { - // Assumes the _flushLock is held as reader, returns in same state. + if (_flushFence == null) + { + return; + } + + // If storage has changed, make sure the fence has been reached so that the data is in place. + _flushLock.ExitReadLock(); + _flushLock.EnterWriteLock(); if (_flushFence != null) { - // If storage has changed, make sure the fence has been reached so that the data is in place. + var fence = _flushFence; + Interlocked.Increment(ref _flushWaiting); - var cookie = _flushLock.UpgradeToWriterLock(Timeout.Infinite); + // Don't wait in the lock. - if (_flushFence != null) + _flushLock.ExitWriteLock(); + + fence.Wait(); + + _flushLock.EnterWriteLock(); + + if (Interlocked.Decrement(ref _flushWaiting) == 0) { - var fence = _flushFence; - Interlocked.Increment(ref _flushWaiting); - - // Don't wait in the lock. - - var restoreCookie = _flushLock.ReleaseLock(); - - fence.Wait(); - - _flushLock.RestoreLock(ref restoreCookie); - - if (Interlocked.Decrement(ref _flushWaiting) == 0) - { - fence.Put(); - } - - _flushFence = null; + fence.Put(); } - _flushLock.DowngradeFromWriterLock(ref cookie); + _flushFence = null; } + + // Assumes the _flushLock is held as reader, returns in same state. + _flushLock.ExitWriteLock(); + _flushLock.EnterReadLock(); } public PinnedSpan GetData(int offset, int size) { - _flushLock.AcquireReaderLock(Timeout.Infinite); + _flushLock.EnterReadLock(); WaitForFlushFence(); @@ -603,7 +605,7 @@ namespace Ryujinx.Graphics.Vulkan // Need to be careful here, the buffer can't be unmapped while the data is being used. _buffer.IncrementReferenceCount(); - _flushLock.ReleaseReaderLock(); + _flushLock.ExitReadLock(); return PinnedSpan.UnsafeFromSpan(result, _buffer.DecrementReferenceCount); } @@ -621,7 +623,7 @@ namespace Ryujinx.Graphics.Vulkan result = resource.GetFlushBuffer().GetBufferData(resource.GetPool(), this, offset, size); } - _flushLock.ReleaseReaderLock(); + _flushLock.ExitReadLock(); // Flush buffer is pinned until the next GetBufferData on the thread, which is fine for current uses. return PinnedSpan.UnsafeFromSpan(result); @@ -1073,11 +1075,11 @@ namespace Ryujinx.Graphics.Vulkan _allocationAuto.Dispose(); } - _flushLock.AcquireWriterLock(Timeout.Infinite); + _flushLock.EnterWriteLock(); ClearFlushFence(); - _flushLock.ReleaseWriterLock(); + _flushLock.ExitWriteLock(); } } } From 1e06b28b22848706014b18bffcec7553cdab2b2b Mon Sep 17 00:00:00 2001 From: Ac_K Date: Sat, 14 Oct 2023 04:13:15 +0200 Subject: [PATCH 64/64] Horizon: Migrate usb and psc services (#5800) * Horizon: Migrate Usb and Psc services * Fix formatting * Adresses feedback --- .../HOS/Services/Ins/IReceiverManager.cs | 8 --- .../HOS/Services/Ins/ISenderManager.cs | 8 --- .../HOS/Services/Ovln/IReceiverService.cs | 8 --- .../HOS/Services/Ovln/ISenderService.cs | 8 --- .../HOS/Services/Psc/IPmControl.cs | 8 --- .../HOS/Services/Psc/IPmService.cs | 8 --- .../HOS/Services/Psc/IPmUnknown.cs | 8 --- .../HOS/Services/Srepo/ISrepoService.cs | 9 --- .../HOS/Services/Usb/IClientRootSession.cs | 9 --- .../HOS/Services/Usb/IDsService.cs | 8 --- .../HOS/Services/Usb/IPdCradleManager.cs | 8 --- .../HOS/Services/Usb/IPdManager.cs | 8 --- .../HOS/Services/Usb/IPmService.cs | 8 --- src/Ryujinx.HLE/HOS/Services/Usb/IUnknown1.cs | 8 --- src/Ryujinx.HLE/HOS/Services/Usb/IUnknown2.cs | 8 --- .../Hipc/HipcGenerator.cs | 2 +- src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs | 47 ++++++++++++ src/Ryujinx.Horizon/Hshl/HshlMain.cs | 17 +++++ src/Ryujinx.Horizon/Hshl/Ipc/Manager.cs | 8 +++ src/Ryujinx.Horizon/Hshl/Ipc/SetterManager.cs | 8 +++ src/Ryujinx.Horizon/Ins/InsIpcServer.cs | 47 ++++++++++++ src/Ryujinx.Horizon/Ins/InsMain.cs | 17 +++++ .../Ins/Ipc/ReceiverManager.cs | 8 +++ src/Ryujinx.Horizon/Ins/Ipc/SenderManager.cs | 8 +++ .../Ovln/Ipc/ReceiverService.cs | 8 +++ src/Ryujinx.Horizon/Ovln/Ipc/SenderService.cs | 8 +++ src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs | 48 +++++++++++++ src/Ryujinx.Horizon/Ovln/OvlnMain.cs | 17 +++++ src/Ryujinx.Horizon/Psc/Ipc/PmControl.cs | 8 +++ src/Ryujinx.Horizon/Psc/Ipc/PmService.cs | 8 +++ src/Ryujinx.Horizon/Psc/Ipc/PmStateLock.cs | 8 +++ src/Ryujinx.Horizon/Psc/PscIpcServer.cs | 50 +++++++++++++ src/Ryujinx.Horizon/Psc/PscMain.cs | 17 +++++ src/Ryujinx.Horizon/Sdk/Hshl/IManager.cs | 8 +++ .../Sdk/Hshl/ISetterManager.cs | 8 +++ .../Sdk/Ins/IReceiverManager.cs | 8 +++ src/Ryujinx.Horizon/Sdk/Ins/ISenderManager.cs | 8 +++ .../Sdk/Ovln/IReceiverService.cs | 8 +++ .../Sdk/Ovln/ISenderService.cs | 8 +++ src/Ryujinx.Horizon/Sdk/Psc/IPmControl.cs | 8 +++ src/Ryujinx.Horizon/Sdk/Psc/IPmService.cs | 8 +++ src/Ryujinx.Horizon/Sdk/Psc/IPmStateLock.cs | 8 +++ .../Sdk/Srepo/ISrepoService.cs | 8 +++ .../Sdk/Usb/IClientRootSession.cs | 8 +++ src/Ryujinx.Horizon/Sdk/Usb/IDsRootSession.cs | 8 +++ .../Sdk/Usb/IPdCradleManager.cs | 8 +++ src/Ryujinx.Horizon/Sdk/Usb/IPdManager.cs | 8 +++ .../Sdk/Usb/IPdManufactureManager.cs | 8 +++ .../Sdk/Usb/IPmObserverService.cs | 8 +++ src/Ryujinx.Horizon/Sdk/Usb/IPmService.cs | 8 +++ src/Ryujinx.Horizon/Sdk/Usb/IQdbManager.cs | 8 +++ src/Ryujinx.Horizon/ServiceTable.cs | 16 ++++- src/Ryujinx.Horizon/Srepo/Ipc/SrepoService.cs | 8 +++ src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs | 46 ++++++++++++ src/Ryujinx.Horizon/Srepo/SrepoMain.cs | 17 +++++ .../Usb/Ipc/ClientRootSession.cs | 8 +++ src/Ryujinx.Horizon/Usb/Ipc/DsRootSession.cs | 8 +++ .../Usb/Ipc/PdCradleManager.cs | 8 +++ src/Ryujinx.Horizon/Usb/Ipc/PdManager.cs | 9 +++ .../Usb/Ipc/PdManufactureManager.cs | 8 +++ .../Usb/Ipc/PmObserverService.cs | 8 +++ src/Ryujinx.Horizon/Usb/Ipc/PmService.cs | 8 +++ src/Ryujinx.Horizon/Usb/Ipc/QdbManager.cs | 8 +++ src/Ryujinx.Horizon/Usb/UsbIpcServer.cs | 71 +++++++++++++++++++ src/Ryujinx.Horizon/Usb/UsbMain.cs | 17 +++++ 65 files changed, 715 insertions(+), 125 deletions(-) delete mode 100644 src/Ryujinx.HLE/HOS/Services/Ins/IReceiverManager.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Ins/ISenderManager.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Ovln/IReceiverService.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Ovln/ISenderService.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Psc/IPmControl.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Psc/IPmService.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Psc/IPmUnknown.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Srepo/ISrepoService.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Usb/IClientRootSession.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Usb/IDsService.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Usb/IPdCradleManager.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Usb/IPdManager.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Usb/IPmService.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Usb/IUnknown1.cs delete mode 100644 src/Ryujinx.HLE/HOS/Services/Usb/IUnknown2.cs create mode 100644 src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Hshl/HshlMain.cs create mode 100644 src/Ryujinx.Horizon/Hshl/Ipc/Manager.cs create mode 100644 src/Ryujinx.Horizon/Hshl/Ipc/SetterManager.cs create mode 100644 src/Ryujinx.Horizon/Ins/InsIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Ins/InsMain.cs create mode 100644 src/Ryujinx.Horizon/Ins/Ipc/ReceiverManager.cs create mode 100644 src/Ryujinx.Horizon/Ins/Ipc/SenderManager.cs create mode 100644 src/Ryujinx.Horizon/Ovln/Ipc/ReceiverService.cs create mode 100644 src/Ryujinx.Horizon/Ovln/Ipc/SenderService.cs create mode 100644 src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Ovln/OvlnMain.cs create mode 100644 src/Ryujinx.Horizon/Psc/Ipc/PmControl.cs create mode 100644 src/Ryujinx.Horizon/Psc/Ipc/PmService.cs create mode 100644 src/Ryujinx.Horizon/Psc/Ipc/PmStateLock.cs create mode 100644 src/Ryujinx.Horizon/Psc/PscIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Psc/PscMain.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Hshl/IManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Hshl/ISetterManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ins/IReceiverManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ins/ISenderManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ovln/IReceiverService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ovln/ISenderService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Psc/IPmControl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Psc/IPmService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Psc/IPmStateLock.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Srepo/ISrepoService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Usb/IClientRootSession.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Usb/IDsRootSession.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Usb/IPdCradleManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Usb/IPdManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Usb/IPdManufactureManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Usb/IPmObserverService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Usb/IPmService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Usb/IQdbManager.cs create mode 100644 src/Ryujinx.Horizon/Srepo/Ipc/SrepoService.cs create mode 100644 src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Srepo/SrepoMain.cs create mode 100644 src/Ryujinx.Horizon/Usb/Ipc/ClientRootSession.cs create mode 100644 src/Ryujinx.Horizon/Usb/Ipc/DsRootSession.cs create mode 100644 src/Ryujinx.Horizon/Usb/Ipc/PdCradleManager.cs create mode 100644 src/Ryujinx.Horizon/Usb/Ipc/PdManager.cs create mode 100644 src/Ryujinx.Horizon/Usb/Ipc/PdManufactureManager.cs create mode 100644 src/Ryujinx.Horizon/Usb/Ipc/PmObserverService.cs create mode 100644 src/Ryujinx.Horizon/Usb/Ipc/PmService.cs create mode 100644 src/Ryujinx.Horizon/Usb/Ipc/QdbManager.cs create mode 100644 src/Ryujinx.Horizon/Usb/UsbIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Usb/UsbMain.cs diff --git a/src/Ryujinx.HLE/HOS/Services/Ins/IReceiverManager.cs b/src/Ryujinx.HLE/HOS/Services/Ins/IReceiverManager.cs deleted file mode 100644 index 8ee00d0e9..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Ins/IReceiverManager.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Ins -{ - [Service("ins:r")] - class IReceiverManager : IpcService - { - public IReceiverManager(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Ins/ISenderManager.cs b/src/Ryujinx.HLE/HOS/Services/Ins/ISenderManager.cs deleted file mode 100644 index 239c4cc83..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Ins/ISenderManager.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Ins -{ - [Service("ins:s")] - class ISenderManager : IpcService - { - public ISenderManager(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Ovln/IReceiverService.cs b/src/Ryujinx.HLE/HOS/Services/Ovln/IReceiverService.cs deleted file mode 100644 index 99e929a70..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Ovln/IReceiverService.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Ovln -{ - [Service("ovln:rcv")] - class IReceiverService : IpcService - { - public IReceiverService(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Ovln/ISenderService.cs b/src/Ryujinx.HLE/HOS/Services/Ovln/ISenderService.cs deleted file mode 100644 index e445c16cd..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Ovln/ISenderService.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Ovln -{ - [Service("ovln:snd")] - class ISenderService : IpcService - { - public ISenderService(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Psc/IPmControl.cs b/src/Ryujinx.HLE/HOS/Services/Psc/IPmControl.cs deleted file mode 100644 index 6682a8481..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Psc/IPmControl.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Psc -{ - [Service("psc:c")] - class IPmControl : IpcService - { - public IPmControl(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Psc/IPmService.cs b/src/Ryujinx.HLE/HOS/Services/Psc/IPmService.cs deleted file mode 100644 index 1be338660..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Psc/IPmService.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Psc -{ - [Service("psc:m")] - class IPmService : IpcService - { - public IPmService(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Psc/IPmUnknown.cs b/src/Ryujinx.HLE/HOS/Services/Psc/IPmUnknown.cs deleted file mode 100644 index 95aff9ece..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Psc/IPmUnknown.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Psc -{ - [Service("psc:l")] // 9.0.0+ - class IPmUnknown : IpcService - { - public IPmUnknown(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Srepo/ISrepoService.cs b/src/Ryujinx.HLE/HOS/Services/Srepo/ISrepoService.cs deleted file mode 100644 index f5467983a..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Srepo/ISrepoService.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Srepo -{ - [Service("srepo:a")] // 5.0.0+ - [Service("srepo:u")] // 5.0.0+ - class ISrepoService : IpcService - { - public ISrepoService(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Usb/IClientRootSession.cs b/src/Ryujinx.HLE/HOS/Services/Usb/IClientRootSession.cs deleted file mode 100644 index b41b8a48c..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Usb/IClientRootSession.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Usb -{ - [Service("usb:hs")] - [Service("usb:hs:a")] // 7.0.0+ - class IClientRootSession : IpcService - { - public IClientRootSession(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Usb/IDsService.cs b/src/Ryujinx.HLE/HOS/Services/Usb/IDsService.cs deleted file mode 100644 index ee6c8f070..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Usb/IDsService.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Usb -{ - [Service("usb:ds")] - class IDsService : IpcService - { - public IDsService(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Usb/IPdCradleManager.cs b/src/Ryujinx.HLE/HOS/Services/Usb/IPdCradleManager.cs deleted file mode 100644 index 18cbce79a..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Usb/IPdCradleManager.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Usb -{ - [Service("usb:pd:c")] - class IPdCradleManager : IpcService - { - public IPdCradleManager(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Usb/IPdManager.cs b/src/Ryujinx.HLE/HOS/Services/Usb/IPdManager.cs deleted file mode 100644 index 011debafd..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Usb/IPdManager.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Usb -{ - [Service("usb:pd")] - class IPdManager : IpcService - { - public IPdManager(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Usb/IPmService.cs b/src/Ryujinx.HLE/HOS/Services/Usb/IPmService.cs deleted file mode 100644 index ed6bba694..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Usb/IPmService.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Usb -{ - [Service("usb:pm")] - class IPmService : IpcService - { - public IPmService(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Usb/IUnknown1.cs b/src/Ryujinx.HLE/HOS/Services/Usb/IUnknown1.cs deleted file mode 100644 index 65bf1c9fa..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Usb/IUnknown1.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Usb -{ - [Service("usb:qdb")] // 7.0.0+ - class IUnknown1 : IpcService - { - public IUnknown1(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Usb/IUnknown2.cs b/src/Ryujinx.HLE/HOS/Services/Usb/IUnknown2.cs deleted file mode 100644 index e0bf0bf4a..000000000 --- a/src/Ryujinx.HLE/HOS/Services/Usb/IUnknown2.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Usb -{ - [Service("usb:obsv")] // 8.0.0+ - class IUnknown2 : IpcService - { - public IUnknown2(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs b/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs index 1b92e9180..4e14f47e9 100644 --- a/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs +++ b/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs @@ -93,7 +93,7 @@ namespace Ryujinx.Horizon.Generators.Hipc generator.LeaveScope(); generator.LeaveScope(); - context.AddSource($"{className}.g.cs", generator.ToString()); + context.AddSource($"{GetNamespaceName(commandInterface.ClassDeclarationSyntax)}.{className}.g.cs", generator.ToString()); } } diff --git a/src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs b/src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs new file mode 100644 index 000000000..7182725cb --- /dev/null +++ b/src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs @@ -0,0 +1,47 @@ +using Ryujinx.Horizon.Hshl.Ipc; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Hshl +{ + class HshlIpcServer + { + private const int HshlMaxSessionsCount = 10; + private const int TotalMaxSessionsCount = HshlMaxSessionsCount * 2; + + private const int PointerBufferSize = 0; + private const int MaxDomains = 0; + private const int MaxDomainObjects = 0; + private const int MaxPortsCount = 2; + + private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, TotalMaxSessionsCount); + +#pragma warning disable IDE0055 // Disable formatting + _serverManager.RegisterObjectForServer(new SetterManager(), ServiceName.Encode("hshl:set"), HshlMaxSessionsCount); // 11.0.0+ + _serverManager.RegisterObjectForServer(new Manager(), ServiceName.Encode("hshl:sys"), HshlMaxSessionsCount); // 11.0.0+ +#pragma warning restore IDE0055 + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Hshl/HshlMain.cs b/src/Ryujinx.Horizon/Hshl/HshlMain.cs new file mode 100644 index 000000000..4e894b6f6 --- /dev/null +++ b/src/Ryujinx.Horizon/Hshl/HshlMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Hshl +{ + class HshlMain : IService + { + public static void Main(ServiceTable serviceTable) + { + HshlIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Hshl/Ipc/Manager.cs b/src/Ryujinx.Horizon/Hshl/Ipc/Manager.cs new file mode 100644 index 000000000..29d9069ac --- /dev/null +++ b/src/Ryujinx.Horizon/Hshl/Ipc/Manager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Hshl; + +namespace Ryujinx.Horizon.Hshl.Ipc +{ + partial class Manager : IManager + { + } +} diff --git a/src/Ryujinx.Horizon/Hshl/Ipc/SetterManager.cs b/src/Ryujinx.Horizon/Hshl/Ipc/SetterManager.cs new file mode 100644 index 000000000..ac1006f0f --- /dev/null +++ b/src/Ryujinx.Horizon/Hshl/Ipc/SetterManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Hshl; + +namespace Ryujinx.Horizon.Hshl.Ipc +{ + partial class SetterManager : ISetterManager + { + } +} diff --git a/src/Ryujinx.Horizon/Ins/InsIpcServer.cs b/src/Ryujinx.Horizon/Ins/InsIpcServer.cs new file mode 100644 index 000000000..68698bf6a --- /dev/null +++ b/src/Ryujinx.Horizon/Ins/InsIpcServer.cs @@ -0,0 +1,47 @@ +using Ryujinx.Horizon.Ins.Ipc; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Ins +{ + class InsIpcServer + { + private const int InsMaxSessionsCount = 8; + private const int TotalMaxSessionsCount = InsMaxSessionsCount * 2; + + private const int PointerBufferSize = 0x200; + private const int MaxDomains = 0; + private const int MaxDomainObjects = 0; + private const int MaxPortsCount = 2; + + private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, TotalMaxSessionsCount); + +#pragma warning disable IDE0055 // Disable formatting + _serverManager.RegisterObjectForServer(new ReceiverManager(), ServiceName.Encode("ins:r"), InsMaxSessionsCount); // 9.0.0+ + _serverManager.RegisterObjectForServer(new SenderManager(), ServiceName.Encode("ins:s"), InsMaxSessionsCount); // 9.0.0+ +#pragma warning restore IDE0055 + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Ins/InsMain.cs b/src/Ryujinx.Horizon/Ins/InsMain.cs new file mode 100644 index 000000000..e428d090a --- /dev/null +++ b/src/Ryujinx.Horizon/Ins/InsMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Ins +{ + class InsMain : IService + { + public static void Main(ServiceTable serviceTable) + { + InsIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Ins/Ipc/ReceiverManager.cs b/src/Ryujinx.Horizon/Ins/Ipc/ReceiverManager.cs new file mode 100644 index 000000000..6e9b29a99 --- /dev/null +++ b/src/Ryujinx.Horizon/Ins/Ipc/ReceiverManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Ins; + +namespace Ryujinx.Horizon.Ins.Ipc +{ + partial class ReceiverManager : IReceiverManager + { + } +} diff --git a/src/Ryujinx.Horizon/Ins/Ipc/SenderManager.cs b/src/Ryujinx.Horizon/Ins/Ipc/SenderManager.cs new file mode 100644 index 000000000..e133014e1 --- /dev/null +++ b/src/Ryujinx.Horizon/Ins/Ipc/SenderManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Ins; + +namespace Ryujinx.Horizon.Ins.Ipc +{ + partial class SenderManager : ISenderManager + { + } +} diff --git a/src/Ryujinx.Horizon/Ovln/Ipc/ReceiverService.cs b/src/Ryujinx.Horizon/Ovln/Ipc/ReceiverService.cs new file mode 100644 index 000000000..6cc448e8a --- /dev/null +++ b/src/Ryujinx.Horizon/Ovln/Ipc/ReceiverService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Ovln; + +namespace Ryujinx.Horizon.Ovln.Ipc +{ + partial class ReceiverService : IReceiverService + { + } +} diff --git a/src/Ryujinx.Horizon/Ovln/Ipc/SenderService.cs b/src/Ryujinx.Horizon/Ovln/Ipc/SenderService.cs new file mode 100644 index 000000000..cab123ecf --- /dev/null +++ b/src/Ryujinx.Horizon/Ovln/Ipc/SenderService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Ovln; + +namespace Ryujinx.Horizon.Ovln.Ipc +{ + partial class SenderService : ISenderService + { + } +} diff --git a/src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs b/src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs new file mode 100644 index 000000000..2c00107fb --- /dev/null +++ b/src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs @@ -0,0 +1,48 @@ +using Ryujinx.Horizon.Ovln.Ipc; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Ovln +{ + class OvlnIpcServer + { + private const int OvlnRcvMaxSessionsCount = 2; + private const int OvlnSndMaxSessionsCount = 20; + private const int TotalMaxSessionsCount = OvlnRcvMaxSessionsCount + OvlnSndMaxSessionsCount; + + private const int PointerBufferSize = 0; + private const int MaxDomains = 21; + private const int MaxDomainObjects = 60; + private const int MaxPortsCount = 2; + + private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, TotalMaxSessionsCount); + +#pragma warning disable IDE0055 // Disable formatting + _serverManager.RegisterObjectForServer(new ReceiverService(), ServiceName.Encode("ovln:rcv"), OvlnRcvMaxSessionsCount); // 8.0.0+ + _serverManager.RegisterObjectForServer(new SenderService(), ServiceName.Encode("ovln:snd"), OvlnSndMaxSessionsCount); // 8.0.0+ +#pragma warning restore IDE0055 + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Ovln/OvlnMain.cs b/src/Ryujinx.Horizon/Ovln/OvlnMain.cs new file mode 100644 index 000000000..8c6cf84e7 --- /dev/null +++ b/src/Ryujinx.Horizon/Ovln/OvlnMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Ovln +{ + class OvlnMain : IService + { + public static void Main(ServiceTable serviceTable) + { + OvlnIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Psc/Ipc/PmControl.cs b/src/Ryujinx.Horizon/Psc/Ipc/PmControl.cs new file mode 100644 index 000000000..671472e4e --- /dev/null +++ b/src/Ryujinx.Horizon/Psc/Ipc/PmControl.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Psc; + +namespace Ryujinx.Horizon.Psc.Ipc +{ + partial class PmControl : IPmControl + { + } +} diff --git a/src/Ryujinx.Horizon/Psc/Ipc/PmService.cs b/src/Ryujinx.Horizon/Psc/Ipc/PmService.cs new file mode 100644 index 000000000..c38da8581 --- /dev/null +++ b/src/Ryujinx.Horizon/Psc/Ipc/PmService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Psc; + +namespace Ryujinx.Horizon.Psc.Ipc +{ + partial class PmService : IPmService + { + } +} diff --git a/src/Ryujinx.Horizon/Psc/Ipc/PmStateLock.cs b/src/Ryujinx.Horizon/Psc/Ipc/PmStateLock.cs new file mode 100644 index 000000000..cef68ac54 --- /dev/null +++ b/src/Ryujinx.Horizon/Psc/Ipc/PmStateLock.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Psc; + +namespace Ryujinx.Horizon.Psc.Ipc +{ + partial class PmStateLock : IPmStateLock + { + } +} diff --git a/src/Ryujinx.Horizon/Psc/PscIpcServer.cs b/src/Ryujinx.Horizon/Psc/PscIpcServer.cs new file mode 100644 index 000000000..f8da56724 --- /dev/null +++ b/src/Ryujinx.Horizon/Psc/PscIpcServer.cs @@ -0,0 +1,50 @@ +using Ryujinx.Horizon.Psc.Ipc; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Psc +{ + class PscIpcServer + { + private const int PscCMaxSessionsCount = 1; + private const int PscMMaxSessionsCount = 50; + private const int PscLMaxSessionsCount = 5; + private const int TotalMaxSessionsCount = PscCMaxSessionsCount + PscMMaxSessionsCount + PscLMaxSessionsCount; + + private const int PointerBufferSize = 0; + private const int MaxDomains = 0; + private const int MaxDomainObjects = 0; + private const int MaxPortsCount = 3; + + private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, TotalMaxSessionsCount); + +#pragma warning disable IDE0055 // Disable formatting + _serverManager.RegisterObjectForServer(new PmControl(), ServiceName.Encode("psc:c"), PscCMaxSessionsCount); + _serverManager.RegisterObjectForServer(new PmService(), ServiceName.Encode("psc:m"), PscMMaxSessionsCount); + _serverManager.RegisterObjectForServer(new PmStateLock(), ServiceName.Encode("psc:l"), PscLMaxSessionsCount); // 9.0.0+ +#pragma warning restore IDE0055 + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Psc/PscMain.cs b/src/Ryujinx.Horizon/Psc/PscMain.cs new file mode 100644 index 000000000..facb6bc08 --- /dev/null +++ b/src/Ryujinx.Horizon/Psc/PscMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Psc +{ + class PscMain : IService + { + public static void Main(ServiceTable serviceTable) + { + PscIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Hshl/IManager.cs b/src/Ryujinx.Horizon/Sdk/Hshl/IManager.cs new file mode 100644 index 000000000..13955c692 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Hshl/IManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Hshl +{ + interface IManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Hshl/ISetterManager.cs b/src/Ryujinx.Horizon/Sdk/Hshl/ISetterManager.cs new file mode 100644 index 000000000..8a4b93dd1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Hshl/ISetterManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Hshl +{ + interface ISetterManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ins/IReceiverManager.cs b/src/Ryujinx.Horizon/Sdk/Ins/IReceiverManager.cs new file mode 100644 index 000000000..28fc757e5 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ins/IReceiverManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Ins +{ + interface IReceiverManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ins/ISenderManager.cs b/src/Ryujinx.Horizon/Sdk/Ins/ISenderManager.cs new file mode 100644 index 000000000..878dbfb32 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ins/ISenderManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Ins +{ + interface ISenderManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ovln/IReceiverService.cs b/src/Ryujinx.Horizon/Sdk/Ovln/IReceiverService.cs new file mode 100644 index 000000000..f59e8002d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ovln/IReceiverService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Ovln +{ + interface IReceiverService : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ovln/ISenderService.cs b/src/Ryujinx.Horizon/Sdk/Ovln/ISenderService.cs new file mode 100644 index 000000000..93323ba50 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ovln/ISenderService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Ovln +{ + interface ISenderService : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Psc/IPmControl.cs b/src/Ryujinx.Horizon/Sdk/Psc/IPmControl.cs new file mode 100644 index 000000000..6a71d6842 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Psc/IPmControl.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Psc +{ + interface IPmControl : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Psc/IPmService.cs b/src/Ryujinx.Horizon/Sdk/Psc/IPmService.cs new file mode 100644 index 000000000..c58665818 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Psc/IPmService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Psc +{ + interface IPmService : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Psc/IPmStateLock.cs b/src/Ryujinx.Horizon/Sdk/Psc/IPmStateLock.cs new file mode 100644 index 000000000..41ead492e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Psc/IPmStateLock.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Psc +{ + interface IPmStateLock : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Srepo/ISrepoService.cs b/src/Ryujinx.Horizon/Sdk/Srepo/ISrepoService.cs new file mode 100644 index 000000000..9a1f4ba4f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Srepo/ISrepoService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Srepo +{ + interface ISrepoService : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Usb/IClientRootSession.cs b/src/Ryujinx.Horizon/Sdk/Usb/IClientRootSession.cs new file mode 100644 index 000000000..4975ad6b5 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Usb/IClientRootSession.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Usb +{ + interface IClientRootSession : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Usb/IDsRootSession.cs b/src/Ryujinx.Horizon/Sdk/Usb/IDsRootSession.cs new file mode 100644 index 000000000..32d7aba6c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Usb/IDsRootSession.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Usb +{ + interface IDsRootSession : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Usb/IPdCradleManager.cs b/src/Ryujinx.Horizon/Sdk/Usb/IPdCradleManager.cs new file mode 100644 index 000000000..0d3865114 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Usb/IPdCradleManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Usb +{ + interface IPdCradleManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Usb/IPdManager.cs b/src/Ryujinx.Horizon/Sdk/Usb/IPdManager.cs new file mode 100644 index 000000000..d63821516 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Usb/IPdManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Usb +{ + interface IPdManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Usb/IPdManufactureManager.cs b/src/Ryujinx.Horizon/Sdk/Usb/IPdManufactureManager.cs new file mode 100644 index 000000000..18bac3ea4 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Usb/IPdManufactureManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Usb +{ + interface IPdManufactureManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Usb/IPmObserverService.cs b/src/Ryujinx.Horizon/Sdk/Usb/IPmObserverService.cs new file mode 100644 index 000000000..ef4cc65af --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Usb/IPmObserverService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Usb +{ + interface IPmObserverService : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Usb/IPmService.cs b/src/Ryujinx.Horizon/Sdk/Usb/IPmService.cs new file mode 100644 index 000000000..b8d177bd0 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Usb/IPmService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Usb +{ + interface IPmService : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Usb/IQdbManager.cs b/src/Ryujinx.Horizon/Sdk/Usb/IQdbManager.cs new file mode 100644 index 000000000..a8f61f058 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Usb/IQdbManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Usb +{ + interface IQdbManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/ServiceTable.cs b/src/Ryujinx.Horizon/ServiceTable.cs index 41da222af..c79328a96 100644 --- a/src/Ryujinx.Horizon/ServiceTable.cs +++ b/src/Ryujinx.Horizon/ServiceTable.cs @@ -1,9 +1,15 @@ using Ryujinx.Horizon.Bcat; +using Ryujinx.Horizon.Hshl; +using Ryujinx.Horizon.Ins; using Ryujinx.Horizon.Lbl; using Ryujinx.Horizon.LogManager; using Ryujinx.Horizon.MmNv; using Ryujinx.Horizon.Ngc; +using Ryujinx.Horizon.Ovln; using Ryujinx.Horizon.Prepo; +using Ryujinx.Horizon.Psc; +using Ryujinx.Horizon.Srepo; +using Ryujinx.Horizon.Usb; using Ryujinx.Horizon.Wlan; using System.Collections.Generic; using System.Threading; @@ -27,12 +33,18 @@ namespace Ryujinx.Horizon } RegisterService(); + RegisterService(); + RegisterService(); RegisterService(); RegisterService(); RegisterService(); - RegisterService(); - RegisterService(); RegisterService(); + RegisterService(); + RegisterService(); + RegisterService(); + RegisterService(); + RegisterService(); + RegisterService(); _totalServices = entries.Count; diff --git a/src/Ryujinx.Horizon/Srepo/Ipc/SrepoService.cs b/src/Ryujinx.Horizon/Srepo/Ipc/SrepoService.cs new file mode 100644 index 000000000..501eb5fed --- /dev/null +++ b/src/Ryujinx.Horizon/Srepo/Ipc/SrepoService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Srepo; + +namespace Ryujinx.Horizon.Srepo.Ipc +{ + partial class SrepoService : ISrepoService + { + } +} diff --git a/src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs b/src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs new file mode 100644 index 000000000..a971f97b8 --- /dev/null +++ b/src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs @@ -0,0 +1,46 @@ +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; +using Ryujinx.Horizon.Srepo.Ipc; + +namespace Ryujinx.Horizon.Srepo +{ + class SrepoIpcServer + { + private const int SrepoAMaxSessionsCount = 2; + private const int SrepoUMaxSessionsCount = 30; + private const int TotalMaxSessionsCount = SrepoAMaxSessionsCount + SrepoUMaxSessionsCount; + + private const int PointerBufferSize = 0x80; + private const int MaxDomains = 32; + private const int MaxDomainObjects = 192; + private const int MaxPortsCount = 2; + + private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, TotalMaxSessionsCount); + + _serverManager.RegisterObjectForServer(new SrepoService(), ServiceName.Encode("srepo:a"), SrepoAMaxSessionsCount); // 5.0.0+ + _serverManager.RegisterObjectForServer(new SrepoService(), ServiceName.Encode("srepo:u"), SrepoUMaxSessionsCount); // 5.0.0+ + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Srepo/SrepoMain.cs b/src/Ryujinx.Horizon/Srepo/SrepoMain.cs new file mode 100644 index 000000000..78d813ac9 --- /dev/null +++ b/src/Ryujinx.Horizon/Srepo/SrepoMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Srepo +{ + class SrepoMain : IService + { + public static void Main(ServiceTable serviceTable) + { + SrepoIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Usb/Ipc/ClientRootSession.cs b/src/Ryujinx.Horizon/Usb/Ipc/ClientRootSession.cs new file mode 100644 index 000000000..2167ebcad --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/Ipc/ClientRootSession.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Usb; + +namespace Ryujinx.Horizon.Usb.Ipc +{ + partial class ClientRootSession : IClientRootSession + { + } +} diff --git a/src/Ryujinx.Horizon/Usb/Ipc/DsRootSession.cs b/src/Ryujinx.Horizon/Usb/Ipc/DsRootSession.cs new file mode 100644 index 000000000..8a84537f8 --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/Ipc/DsRootSession.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Usb; + +namespace Ryujinx.Horizon.Usb.Ipc +{ + partial class DsRootSession : IDsRootSession + { + } +} diff --git a/src/Ryujinx.Horizon/Usb/Ipc/PdCradleManager.cs b/src/Ryujinx.Horizon/Usb/Ipc/PdCradleManager.cs new file mode 100644 index 000000000..27e1c4e37 --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/Ipc/PdCradleManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Usb; + +namespace Ryujinx.Horizon.Usb.Ipc +{ + partial class PdCradleManager : IPdCradleManager + { + } +} diff --git a/src/Ryujinx.Horizon/Usb/Ipc/PdManager.cs b/src/Ryujinx.Horizon/Usb/Ipc/PdManager.cs new file mode 100644 index 000000000..c501e3f20 --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/Ipc/PdManager.cs @@ -0,0 +1,9 @@ +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Usb; + +namespace Ryujinx.Horizon.Usb.Ipc +{ + partial class PdManager : IPdManager + { + } +} diff --git a/src/Ryujinx.Horizon/Usb/Ipc/PdManufactureManager.cs b/src/Ryujinx.Horizon/Usb/Ipc/PdManufactureManager.cs new file mode 100644 index 000000000..04f78b9c2 --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/Ipc/PdManufactureManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Usb; + +namespace Ryujinx.Horizon.Usb.Ipc +{ + partial class PdManufactureManager : IPdManufactureManager + { + } +} diff --git a/src/Ryujinx.Horizon/Usb/Ipc/PmObserverService.cs b/src/Ryujinx.Horizon/Usb/Ipc/PmObserverService.cs new file mode 100644 index 000000000..e2edf4cb9 --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/Ipc/PmObserverService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Usb; + +namespace Ryujinx.Horizon.Usb.Ipc +{ + partial class PmObserverService : IPmObserverService + { + } +} diff --git a/src/Ryujinx.Horizon/Usb/Ipc/PmService.cs b/src/Ryujinx.Horizon/Usb/Ipc/PmService.cs new file mode 100644 index 000000000..625aaa497 --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/Ipc/PmService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Usb; + +namespace Ryujinx.Horizon.Usb.Ipc +{ + partial class PmService : IPmService + { + } +} diff --git a/src/Ryujinx.Horizon/Usb/Ipc/QdbManager.cs b/src/Ryujinx.Horizon/Usb/Ipc/QdbManager.cs new file mode 100644 index 000000000..1421142fb --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/Ipc/QdbManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Usb; + +namespace Ryujinx.Horizon.Usb.Ipc +{ + partial class QdbManager : IQdbManager + { + } +} diff --git a/src/Ryujinx.Horizon/Usb/UsbIpcServer.cs b/src/Ryujinx.Horizon/Usb/UsbIpcServer.cs new file mode 100644 index 000000000..a9158b507 --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/UsbIpcServer.cs @@ -0,0 +1,71 @@ +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; +using Ryujinx.Horizon.Usb.Ipc; + +namespace Ryujinx.Horizon.Usb +{ + class UsbIpcServer + { + private const int UsbDsMaxSessionsCount = 4; + private const int UsbHsMaxSessionsCount = 20; + private const int UsbHsAMaxSessionsCount = 3; + private const int UsbObsvMaxSessionsCount = 2; + private const int UsbPdMaxSessionsCount = 6; + private const int UsbPdCMaxSessionsCount = 4; + private const int UsbPdMMaxSessionsCount = 1; + private const int UsbPmMaxSessionsCount = 5; + private const int UsbQdbMaxSessionsCount = 4; + private const int TotalMaxSessionsCount = + UsbDsMaxSessionsCount + + UsbHsMaxSessionsCount + + UsbHsAMaxSessionsCount + + UsbObsvMaxSessionsCount + + UsbPdMaxSessionsCount + + UsbPdCMaxSessionsCount + + UsbPdMMaxSessionsCount + + UsbPmMaxSessionsCount + + UsbQdbMaxSessionsCount; + + private const int PointerBufferSize = 0; + private const int MaxDomains = 0; + private const int MaxDomainObjects = 0; + private const int MaxPortsCount = 9; + + private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, TotalMaxSessionsCount); + +#pragma warning disable IDE0055 // Disable formatting + _serverManager.RegisterObjectForServer(new DsRootSession(), ServiceName.Encode("usb:ds"), UsbDsMaxSessionsCount); + _serverManager.RegisterObjectForServer(new ClientRootSession(), ServiceName.Encode("usb:hs"), UsbHsMaxSessionsCount); + _serverManager.RegisterObjectForServer(new ClientRootSession(), ServiceName.Encode("usb:hs:a"), UsbHsAMaxSessionsCount); // 7.0.0+ + _serverManager.RegisterObjectForServer(new PmObserverService(), ServiceName.Encode("usb:obsv"), UsbObsvMaxSessionsCount); // 8.0.0+ + _serverManager.RegisterObjectForServer(new PdManager(), ServiceName.Encode("usb:pd"), UsbPdMaxSessionsCount); + _serverManager.RegisterObjectForServer(new PdCradleManager(), ServiceName.Encode("usb:pd:c"), UsbPdCMaxSessionsCount); + _serverManager.RegisterObjectForServer(new PdManufactureManager(), ServiceName.Encode("usb:pd:m"), UsbPdMMaxSessionsCount); // 1.0.0 + _serverManager.RegisterObjectForServer(new PmService(), ServiceName.Encode("usb:pm"), UsbPmMaxSessionsCount); + _serverManager.RegisterObjectForServer(new QdbManager(), ServiceName.Encode("usb:qdb"), UsbQdbMaxSessionsCount); // 7.0.0+ +#pragma warning restore IDE0055 + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Usb/UsbMain.cs b/src/Ryujinx.Horizon/Usb/UsbMain.cs new file mode 100644 index 000000000..c54b39a65 --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/UsbMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Usb +{ + class UsbMain : IService + { + public static void Main(ServiceTable serviceTable) + { + UsbIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +}