diff --git a/Ryujinx.Graphics.Gpu/Constants.cs b/Ryujinx.Graphics.Gpu/Constants.cs index 65cd8846d..cc476654c 100644 --- a/Ryujinx.Graphics.Gpu/Constants.cs +++ b/Ryujinx.Graphics.Gpu/Constants.cs @@ -8,11 +8,18 @@ namespace Ryujinx.Graphics.Gpu /// /// Maximum number of compute uniform buffers. /// - public const int TotalCpUniformBuffers = 8; + /// + /// This does not reflect the hardware count, the API will emulate some constant buffers using + /// global memory to make up for the low amount of compute constant buffers supported by hardware (only 8). + /// + public const int TotalCpUniformBuffers = 17; // 8 hardware constant buffers + 9 emulated (14 available to the user). /// - /// Maximum number of compute storage buffers (this is an API limitation). + /// Maximum number of compute storage buffers. /// + /// + /// The maximum number of storage buffers is API limited, the hardware supports a unlimited amount. + /// public const int TotalCpStorageBuffers = 16; /// @@ -21,8 +28,11 @@ namespace Ryujinx.Graphics.Gpu public const int TotalGpUniformBuffers = 18; /// - /// Maximum number of graphics storage buffers (this is an API limitation). + /// Maximum number of graphics storage buffers. /// + /// + /// The maximum number of storage buffers is API limited, the hardware supports a unlimited amount. + /// public const int TotalGpStorageBuffers = 16; /// diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute.cs b/Ryujinx.Graphics.Gpu/Engine/Compute.cs index 9178cfb0d..fc257f99c 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Compute.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Compute.cs @@ -69,6 +69,34 @@ namespace Ryujinx.Graphics.Gpu.Engine BufferManager.SetComputeUniformBuffer(index, gpuVa, size); } + for (int index = 0; index < info.CBuffers.Count; index++) + { + BufferDescriptor cb = info.CBuffers[index]; + + // NVN uses the "hardware" constant buffer for anything that is less than 8, + // and those are already bound above. + // Anything greater than or equal to 8 uses the emulated constant buffers. + // They are emulated using global memory loads. + if (cb.Slot < 8) + { + continue; + } + + ubEnableMask |= 1u << cb.Slot; + + ulong cbDescAddress = BufferManager.GetComputeUniformBufferAddress(0); + + int cbDescOffset = 0x260 + cb.Slot * 0x10; + + cbDescAddress += (ulong)cbDescOffset; + + ReadOnlySpan cbDescriptorData = _context.PhysicalMemory.GetSpan(cbDescAddress, 0x10); + + SbDescriptor cbDescriptor = MemoryMarshal.Cast(cbDescriptorData)[0]; + + BufferManager.SetComputeUniformBuffer(cb.Slot, cbDescriptor.PackAddress(), (uint)cbDescriptor.Size); + } + for (int index = 0; index < info.SBuffers.Count; index++) { BufferDescriptor sb = info.SBuffers[index]; diff --git a/Ryujinx.Graphics.Shader/Translation/GlobalMemory.cs b/Ryujinx.Graphics.Shader/Translation/GlobalMemory.cs index a442357dd..75bd9ddff 100644 --- a/Ryujinx.Graphics.Shader/Translation/GlobalMemory.cs +++ b/Ryujinx.Graphics.Shader/Translation/GlobalMemory.cs @@ -11,6 +11,11 @@ namespace Ryujinx.Graphics.Shader.Translation public const int StorageDescsSize = StorageDescSize * StorageMaxCount; + public const int UbeBaseOffset = 0x98; // In words. + public const int UbeMaxCount = 9; + public const int UbeDescsSize = StorageDescSize * UbeMaxCount; + public const int UbeFirstCbuf = 8; + public static bool UsesGlobalMemory(Instruction inst) { return (inst.IsAtomic() && IsGlobalMr(inst)) || @@ -30,17 +35,16 @@ namespace Ryujinx.Graphics.Shader.Translation public static int GetStorageBaseCbOffset(ShaderStage stage) { - switch (stage) + return stage switch { - case ShaderStage.Compute: return StorageDescsBaseOffset + 2 * StorageDescsSize; - case ShaderStage.Vertex: return StorageDescsBaseOffset; - case ShaderStage.TessellationControl: return StorageDescsBaseOffset + 1 * StorageDescsSize; - case ShaderStage.TessellationEvaluation: return StorageDescsBaseOffset + 2 * StorageDescsSize; - case ShaderStage.Geometry: return StorageDescsBaseOffset + 3 * StorageDescsSize; - case ShaderStage.Fragment: return StorageDescsBaseOffset + 4 * StorageDescsSize; - } - - return 0; + ShaderStage.Compute => StorageDescsBaseOffset + 2 * StorageDescsSize, + ShaderStage.Vertex => StorageDescsBaseOffset, + ShaderStage.TessellationControl => StorageDescsBaseOffset + 1 * StorageDescsSize, + ShaderStage.TessellationEvaluation => StorageDescsBaseOffset + 2 * StorageDescsSize, + ShaderStage.Geometry => StorageDescsBaseOffset + 3 * StorageDescsSize, + ShaderStage.Fragment => StorageDescsBaseOffset + 4 * StorageDescsSize, + _ => 0 + }; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Lowering.cs b/Ryujinx.Graphics.Shader/Translation/Lowering.cs index 1ee21e0a0..99aea26e6 100644 --- a/Ryujinx.Graphics.Shader/Translation/Lowering.cs +++ b/Ryujinx.Graphics.Shader/Translation/Lowering.cs @@ -81,7 +81,7 @@ namespace Ryujinx.Graphics.Shader.Translation Operand alignMask = Const(-config.QueryInfo(QueryInfoName.StorageBufferOffsetAlignment)); - Operand baseAddrTrunc = PrependOperation(Instruction.BitwiseAnd, sbBaseAddrLow, Const(-64)); + Operand baseAddrTrunc = PrependOperation(Instruction.BitwiseAnd, sbBaseAddrLow, alignMask); Operand byteOffset = PrependOperation(Instruction.Subtract, addrLow, baseAddrTrunc); Operand wordOffset = PrependOperation(Instruction.ShiftRightU32, byteOffset, Const(2)); diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs index 8efd2c520..7988ef6c1 100644 --- a/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs @@ -31,8 +31,27 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations if (storageIndex >= 0) { + // Storage buffers are implemented using global memory access. + // If we know from where the base address of the access is loaded, + // we can guess which storage buffer it is accessing. + // We can then replace the global memory access with a storage + // buffer access. node = ReplaceGlobalWithStorage(node, config, storageIndex); } + else if (config.Stage == ShaderStage.Compute && operation.Inst == Instruction.LoadGlobal) + { + // Here we effectively try to replace a LDG instruction with LDC. + // The hardware only supports a limited amount of constant buffers + // so NVN "emulates" more constant buffers using global memory access. + // Here we try to replace the global access back to a constant buffer + // load. + storageIndex = SearchForStorageBase(asgOperation, UbeBaseOffset, UbeBaseOffset + UbeDescsSize); + + if (storageIndex >= 0) + { + node = ReplaceLdgWithLdc(node, config, storageIndex); + } + } } } } @@ -42,8 +61,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations { Operation operation = (Operation)node.Value; - Operation storageOp; - Operand GetStorageOffset() { Operand addrLow = operation.GetSource(0); @@ -80,6 +97,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations sources[index] = operation.GetSource(index); } + Operation storageOp; + if (operation.Inst.IsAtomic()) { Instruction inst = (operation.Inst & ~Instruction.MrMask) | Instruction.MrStorage; @@ -109,6 +128,62 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations return node; } + private static LinkedListNode ReplaceLdgWithLdc(LinkedListNode node, ShaderConfig config, int storageIndex) + { + Operation operation = (Operation)node.Value; + + Operand GetCbufOffset() + { + Operand addrLow = operation.GetSource(0); + + Operand baseAddrLow = Cbuf(0, UbeBaseOffset + storageIndex * StorageDescSize); + + Operand baseAddrTrunc = Local(); + + Operand alignMask = Const(-config.QueryInfo(QueryInfoName.StorageBufferOffsetAlignment)); + + Operation andOp = new Operation(Instruction.BitwiseAnd, baseAddrTrunc, baseAddrLow, alignMask); + + node.List.AddBefore(node, andOp); + + Operand byteOffset = Local(); + Operand wordOffset = Local(); + + Operation subOp = new Operation(Instruction.Subtract, byteOffset, addrLow, baseAddrTrunc); + Operation shrOp = new Operation(Instruction.ShiftRightU32, wordOffset, byteOffset, Const(2)); + + node.List.AddBefore(node, subOp); + node.List.AddBefore(node, shrOp); + + return wordOffset; + } + + Operand[] sources = new Operand[operation.SourcesCount]; + + sources[0] = Const(UbeFirstCbuf + storageIndex); + sources[1] = GetCbufOffset(); + + for (int index = 2; index < operation.SourcesCount; index++) + { + sources[index] = operation.GetSource(index); + } + + Operation ldcOp = new Operation(Instruction.LoadConstant, operation.Dest, sources); + + for (int index = 0; index < operation.SourcesCount; index++) + { + operation.SetSource(index, null); + } + + LinkedListNode oldNode = node; + + node = node.List.AddBefore(node, ldcOp); + + node.List.Remove(oldNode); + + return node; + } + private static int SearchForStorageBase(Operation operation, int sbStart, int sbEnd) { Queue assignments = new Queue();