diff --git a/src/Ryujinx.Graphics.Gpu/Engine/DeviceStateWithShadow.cs b/src/Ryujinx.Graphics.Gpu/Engine/DeviceStateWithShadow.cs index 74a9aa0493..a2e5b11643 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/DeviceStateWithShadow.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/DeviceStateWithShadow.cs @@ -32,6 +32,11 @@ namespace Ryujinx.Graphics.Gpu.Engine /// public ref TState State => ref _state.State; + /// + /// Current shadow state. + /// + public ref TState ShadowState => ref _shadowState.State; + /// /// Creates a new instance of the device state, with shadow state. /// diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs index a4c4dd1064..7d9e1ec025 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs @@ -1,7 +1,10 @@ using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; using Ryujinx.Graphics.Device; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Engine.GPFifo; +using Ryujinx.Graphics.Gpu.Engine.Threed; +using Ryujinx.Graphics.Gpu.Engine.Types; using System; using System.Collections.Generic; @@ -15,9 +18,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME private const int ColorLayerCountOffset = 0x818; private const int ColorStructSize = 0x40; private const int ZetaLayerCountOffset = 0x1230; + private const int UniformBufferBindVertexOffset = 0x2410; + private const int FirstVertexOffset = 0x1434; private const int IndirectIndexedDataEntrySize = 0x14; + private const int LogicOpOffset = 0x19c4; + private const int ShaderIdScratchOffset = 0x3470; + private const int ShaderAddressScratchOffset = 0x3488; + private const int UpdateConstantBufferAddressesBase = 0x34a8; + private const int UpdateConstantBufferSizesBase = 0x34bc; + private const int UpdateConstantBufferAddressCbu = 0x3460; + private readonly GPFifoProcessor _processor; private readonly MacroHLEFunctionName _functionName; @@ -49,6 +61,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME { switch (_functionName) { + case MacroHLEFunctionName.BindShaderProgram: + BindShaderProgram(state, arg0); + break; case MacroHLEFunctionName.ClearColor: ClearColor(state, arg0); break; @@ -58,6 +73,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME case MacroHLEFunctionName.DrawArraysInstanced: DrawArraysInstanced(state, arg0); break; + case MacroHLEFunctionName.DrawElements: + DrawElements(state, arg0); + break; case MacroHLEFunctionName.DrawElementsInstanced: DrawElementsInstanced(state, arg0); break; @@ -67,6 +85,21 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME case MacroHLEFunctionName.MultiDrawElementsIndirectCount: MultiDrawElementsIndirectCount(state, arg0); break; + case MacroHLEFunctionName.UpdateBlendState: + UpdateBlendState(state, arg0); + break; + case MacroHLEFunctionName.UpdateColorMasks: + UpdateColorMasks(state, arg0); + break; + case MacroHLEFunctionName.UpdateUniformBufferState: + UpdateUniformBufferState(state, arg0); + break; + case MacroHLEFunctionName.UpdateUniformBufferStateCbu: + UpdateUniformBufferStateCbu(state, arg0); + break; + case MacroHLEFunctionName.UpdateUniformBufferStateCbuV2: + UpdateUniformBufferStateCbuV2(state, arg0); + break; default: throw new NotImplementedException(_functionName.ToString()); } @@ -75,6 +108,149 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME Fifo.Clear(); } + /// + /// Binds a shader program with the index in arg0. + /// + /// GPU state at the time of the call + /// First argument of the call + private void BindShaderProgram(IDeviceState state, int arg0) + { + int scratchOffset = ShaderIdScratchOffset + arg0 * 4; + + int lastId = state.Read(scratchOffset); + int id = FetchParam().Word; + int offset = FetchParam().Word; + + if (lastId == id) + { + FetchParam(); + FetchParam(); + + return; + } + + _processor.ThreedClass.SetShaderOffset(arg0, (uint)offset); + + // Removes overflow on the method address into the increment portion. + // Present in the original macro. + int addrMask = unchecked((int)0xfffc0fff) << 2; + + state.Write(scratchOffset & addrMask, id); + state.Write((ShaderAddressScratchOffset + arg0 * 4) & addrMask, offset); + + int stage = FetchParam().Word; + uint cbAddress = (uint)FetchParam().Word; + + _processor.ThreedClass.UpdateUniformBufferState(65536, cbAddress >> 24, cbAddress << 8); + + int stageOffset = (stage & 0x7f) << 3; + + state.Write((UniformBufferBindVertexOffset + stageOffset * 4) & addrMask, 17); + } + + /// + /// Updates uniform buffer state for update or bind. + /// + /// GPU state at the time of the call + /// First argument of the call + private void UpdateUniformBufferState(IDeviceState state, int arg0) + { + uint address = (uint)state.Read(UpdateConstantBufferAddressesBase + arg0 * 4); + int size = state.Read(UpdateConstantBufferSizesBase + arg0 * 4); + + _processor.ThreedClass.UpdateUniformBufferState(size, address >> 24, address << 8); + } + + /// + /// Updates uniform buffer state for update. + /// + /// GPU state at the time of the call + /// First argument of the call + private void UpdateUniformBufferStateCbu(IDeviceState state, int arg0) + { + uint address = (uint)state.Read(UpdateConstantBufferAddressCbu); + + UniformBufferState ubState = new() + { + Address = new() + { + High = address >> 24, + Low = address << 8 + }, + Size = 24320, + Offset = arg0 << 2 + }; + + _processor.ThreedClass.UpdateUniformBufferState(ubState); + } + + /// + /// Updates uniform buffer state for update. + /// + /// GPU state at the time of the call + /// First argument of the call + private void UpdateUniformBufferStateCbuV2(IDeviceState state, int arg0) + { + uint address = (uint)state.Read(UpdateConstantBufferAddressCbu); + + UniformBufferState ubState = new() + { + Address = new() + { + High = address >> 24, + Low = address << 8 + }, + Size = 28672, + Offset = arg0 << 2 + }; + + _processor.ThreedClass.UpdateUniformBufferState(ubState); + } + + /// + /// Updates blend enable using the given argument. + /// + /// GPU state at the time of the call + /// First argument of the call + private void UpdateBlendState(IDeviceState state, int arg0) + { + state.Write(LogicOpOffset, 0); + + Array8 enable = new(); + + for (int i = 0; i < 8; i++) + { + enable[i] = new Boolean32((uint)(arg0 >> (i + 8)) & 1); + } + + _processor.ThreedClass.UpdateBlendEnable(ref enable); + } + + /// + /// Updates color masks using the given argument and three pushed arguments. + /// + /// GPU state at the time of the call + /// First argument of the call + private void UpdateColorMasks(IDeviceState state, int arg0) + { + Array8 masks = new(); + + int index = 0; + + for (int i = 0; i < 4; i++) + { + masks[index++] = new RtColorMask((uint)arg0 & 0x1fff); + masks[index++] = new RtColorMask(((uint)arg0 >> 16) & 0x1fff); + + if (i != 3) + { + arg0 = FetchParam().Word; + } + } + + _processor.ThreedClass.UpdateColorMasks(ref masks); + } + /// /// Clears one bound color target. /// @@ -129,6 +305,36 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME indexed: false); } + /// + /// Performs a indexed draw. + /// + /// GPU state at the time of the call + /// First argument of the call + private void DrawElements(IDeviceState state, int arg0) + { + var topology = (PrimitiveTopology)arg0; + + var indexAddressHigh = FetchParam(); + var indexAddressLow = FetchParam(); + var indexType = FetchParam(); + var firstIndex = 0; + var indexCount = FetchParam(); + + _processor.ThreedClass.UpdateIndexBuffer( + (uint)indexAddressHigh.Word, + (uint)indexAddressLow.Word, + (IndexType)indexType.Word); + + _processor.ThreedClass.Draw( + topology, + indexCount.Word, + 1, + firstIndex, + state.Read(FirstVertexOffset), + 0, + indexed: true); + } + /// /// Performs a indexed draw. /// diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLEFunctionName.cs b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLEFunctionName.cs index 9e71761b43..8dca522623 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLEFunctionName.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLEFunctionName.cs @@ -6,11 +6,19 @@ enum MacroHLEFunctionName { None, + BindShaderProgram, ClearColor, ClearDepthStencil, DrawArraysInstanced, + DrawElements, DrawElementsInstanced, DrawElementsIndirect, MultiDrawElementsIndirectCount, + + UpdateBlendState, + UpdateColorMasks, + UpdateUniformBufferState, + UpdateUniformBufferStateCbu, + UpdateUniformBufferStateCbuV2 } } diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLETable.cs b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLETable.cs index 5630756cba..9a496164d1 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLETable.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLETable.cs @@ -46,12 +46,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME private static readonly TableEntry[] _table = new TableEntry[] { + new(MacroHLEFunctionName.BindShaderProgram, new Hash128(0x5d5efb912369f60b, 0x69131ed5019f08ef), 0x68), new(MacroHLEFunctionName.ClearColor, new Hash128(0xA9FB28D1DC43645A, 0xB177E5D2EAE67FB0), 0x28), new(MacroHLEFunctionName.ClearDepthStencil, new Hash128(0x1B96CB77D4879F4F, 0x8557032FE0C965FB), 0x24), new(MacroHLEFunctionName.DrawArraysInstanced, new Hash128(0x197FB416269DBC26, 0x34288C01DDA82202), 0x48), + new(MacroHLEFunctionName.DrawElements, new Hash128(0x3D7F32AE6C2702A7, 0x9353C9F41C1A244D), 0x20), new(MacroHLEFunctionName.DrawElementsInstanced, new Hash128(0x1A501FD3D54EC8E0, 0x6CF570CF79DA74D6), 0x5c), new(MacroHLEFunctionName.DrawElementsIndirect, new Hash128(0x86A3E8E903AF8F45, 0xD35BBA07C23860A4), 0x7c), new(MacroHLEFunctionName.MultiDrawElementsIndirectCount, new Hash128(0x890AF57ED3FB1C37, 0x35D0C95C61F5386F), 0x19C), + new(MacroHLEFunctionName.UpdateBlendState, new Hash128(0x40F6D4E7B08D7640, 0x82167BEEAECB959F), 0x28), + new(MacroHLEFunctionName.UpdateColorMasks, new Hash128(0x9EE32420B8441DFD, 0x6E7724759A57333E), 0x24), + new(MacroHLEFunctionName.UpdateUniformBufferState, new Hash128(0x8EE66706049CB0B0, 0x51C1CF906EC86F7C), 0x20), + new(MacroHLEFunctionName.UpdateUniformBufferStateCbu, new Hash128(0xA4592676A3E581A0, 0xA39E77FE19FE04AC), 0x18), + new(MacroHLEFunctionName.UpdateUniformBufferStateCbuV2, new Hash128(0x392FA750489983D4, 0x35BACE455155D2C3), 0x18) }; /// @@ -62,18 +69,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME /// True if the host supports the HLE macro, false otherwise private static bool IsMacroHLESupported(Capabilities caps, MacroHLEFunctionName name) { - if (name == MacroHLEFunctionName.ClearColor || - name == MacroHLEFunctionName.ClearDepthStencil || - name == MacroHLEFunctionName.DrawArraysInstanced || - name == MacroHLEFunctionName.DrawElementsInstanced || - name == MacroHLEFunctionName.DrawElementsIndirect) - { - return true; - } - else if (name == MacroHLEFunctionName.MultiDrawElementsIndirectCount) + if (name == MacroHLEFunctionName.MultiDrawElementsIndirectCount) { return caps.SupportsIndirectParameters; } + else if (name != MacroHLEFunctionName.None) + { + return true; + } return false; } diff --git a/src/Ryujinx.Graphics.Gpu/Engine/SetMmeShadowRamControlMode.cs b/src/Ryujinx.Graphics.Gpu/Engine/SetMmeShadowRamControlMode.cs index ebb0ff33e2..b9a5c74a3b 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/SetMmeShadowRamControlMode.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/SetMmeShadowRamControlMode.cs @@ -10,4 +10,22 @@ namespace Ryujinx.Graphics.Gpu.Engine MethodPassthrough = 2, MethodReplay = 3, } + + static class SetMmeShadowRamControlModeExtensions + { + public static bool IsTrack(this SetMmeShadowRamControlMode mode) + { + return mode == SetMmeShadowRamControlMode.MethodTrack || mode == SetMmeShadowRamControlMode.MethodTrackWithFilter; + } + + public static bool IsPassthrough(this SetMmeShadowRamControlMode mode) + { + return mode == SetMmeShadowRamControlMode.MethodPassthrough; + } + + public static bool IsReplay(this SetMmeShadowRamControlMode mode) + { + return mode == SetMmeShadowRamControlMode.MethodReplay; + } + } } diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs index 37e41c51be..1ff821569b 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -17,9 +17,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed class StateUpdater { public const int ShaderStateIndex = 26; + public const int RtColorMaskIndex = 14; public const int RasterizerStateIndex = 15; public const int ScissorStateIndex = 16; public const int VertexBufferStateIndex = 0; + public const int BlendStateIndex = 2; public const int IndexBufferStateIndex = 23; public const int PrimitiveRestartStateIndex = 12; public const int RenderTargetStateIndex = 27; diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs index 7bc2970feb..df9d1f5c9f 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs @@ -1,12 +1,15 @@ -using Ryujinx.Graphics.Device; +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Device; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Engine.GPFifo; using Ryujinx.Graphics.Gpu.Engine.InlineToMemory; using Ryujinx.Graphics.Gpu.Engine.Threed.Blender; +using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Synchronization; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; namespace Ryujinx.Graphics.Gpu.Engine.Threed { @@ -26,6 +29,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed private readonly ConstantBufferUpdater _cbUpdater; private readonly StateUpdater _stateUpdater; + private SetMmeShadowRamControlMode ShadowMode => _state.State.SetMmeShadowRamControlMode; + /// /// Creates a new instance of the 3D engine class. /// @@ -228,6 +233,206 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed _cbUpdater.Update(data); } + /// + /// Test if two 32 byte structs are equal. + /// + /// Type of the 32-byte struct + /// First struct + /// Second struct + /// True if equal, false otherwise + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool UnsafeEquals32Byte(ref T lhs, ref T rhs) where T : unmanaged + { + if (Vector256.IsHardwareAccelerated) + { + return Vector256.EqualsAll( + Unsafe.As>(ref lhs), + Unsafe.As>(ref rhs) + ); + } + else + { + ref var lhsVec = ref Unsafe.As>(ref lhs); + ref var rhsVec = ref Unsafe.As>(ref rhs); + + return Vector128.EqualsAll(lhsVec, rhsVec) && + Vector128.EqualsAll(Unsafe.Add(ref lhsVec, 1), Unsafe.Add(ref rhsVec, 1)); + } + } + + /// + /// Updates blend enable. Respects current shadow mode. + /// + /// Blend enable + public void UpdateBlendEnable(ref Array8 enable) + { + var shadow = ShadowMode; + ref var state = ref _state.State.BlendEnable; + + if (shadow.IsReplay()) + { + enable = _state.ShadowState.BlendEnable; + } + + if (!UnsafeEquals32Byte(ref enable, ref state)) + { + state = enable; + + _stateUpdater.ForceDirty(StateUpdater.BlendStateIndex); + } + + if (shadow.IsTrack()) + { + _state.ShadowState.BlendEnable = enable; + } + } + + /// + /// Updates color masks. Respects current shadow mode. + /// + /// Color masks + public void UpdateColorMasks(ref Array8 masks) + { + var shadow = ShadowMode; + ref var state = ref _state.State.RtColorMask; + + if (shadow.IsReplay()) + { + masks = _state.ShadowState.RtColorMask; + } + + if (!UnsafeEquals32Byte(ref masks, ref state)) + { + state = masks; + + _stateUpdater.ForceDirty(StateUpdater.RtColorMaskIndex); + } + + if (shadow.IsTrack()) + { + _state.ShadowState.RtColorMask = masks; + } + } + + /// + /// Updates index buffer state for an indexed draw. Respects current shadow mode. + /// + /// High part of the address + /// Low part of the address + /// Type of the binding + public void UpdateIndexBuffer(uint addrHigh, uint addrLow, IndexType type) + { + var shadow = ShadowMode; + ref var state = ref _state.State.IndexBufferState; + + if (shadow.IsReplay()) + { + ref var shadowState = ref _state.ShadowState.IndexBufferState; + addrHigh = shadowState.Address.High; + addrLow = shadowState.Address.Low; + type = shadowState.Type; + } + + if (state.Address.High != addrHigh || state.Address.Low != addrLow || state.Type != type) + { + state.Address.High = addrHigh; + state.Address.Low = addrLow; + state.Type = type; + + _stateUpdater.ForceDirty(StateUpdater.IndexBufferStateIndex); + } + + if (shadow.IsTrack()) + { + ref var shadowState = ref _state.ShadowState.IndexBufferState; + shadowState.Address.High = addrHigh; + shadowState.Address.Low = addrLow; + shadowState.Type = type; + } + } + + /// + /// Updates uniform buffer state for update or bind. Respects current shadow mode. + /// + /// Size of the binding + /// High part of the addrsss + /// Low part of the address + public void UpdateUniformBufferState(int size, uint addrHigh, uint addrLow) + { + var shadow = ShadowMode; + ref var state = ref _state.State.UniformBufferState; + + if (shadow.IsReplay()) + { + ref var shadowState = ref _state.ShadowState.UniformBufferState; + size = shadowState.Size; + addrHigh = shadowState.Address.High; + addrLow = shadowState.Address.Low; + } + + state.Size = size; + state.Address.High = addrHigh; + state.Address.Low = addrLow; + + if (shadow.IsTrack()) + { + ref var shadowState = ref _state.ShadowState.UniformBufferState; + shadowState.Size = size; + shadowState.Address.High = addrHigh; + shadowState.Address.Low = addrLow; + } + } + + /// + /// Updates a shader offset. Respects current shadow mode. + /// + /// Index of the shader to update + /// Offset to update with + public void SetShaderOffset(int index, uint offset) + { + var shadow = ShadowMode; + ref var shaderState = ref _state.State.ShaderState[index]; + + if (shadow.IsReplay()) + { + offset = _state.ShadowState.ShaderState[index].Offset; + } + + if (shaderState.Offset != offset) + { + shaderState.Offset = offset; + + _stateUpdater.ForceDirty(StateUpdater.ShaderStateIndex); + } + + if (shadow.IsTrack()) + { + _state.ShadowState.ShaderState[index].Offset = offset; + } + } + + /// + /// Updates uniform buffer state for update. Respects current shadow mode. + /// + /// Uniform buffer state + public void UpdateUniformBufferState(UniformBufferState ubState) + { + var shadow = ShadowMode; + ref var state = ref _state.State.UniformBufferState; + + if (shadow.IsReplay()) + { + ubState = _state.ShadowState.UniformBufferState; + } + + state = ubState; + + if (shadow.IsTrack()) + { + _state.ShadowState.UniformBufferState = ubState; + } + } + /// /// Launches the Inline-to-Memory DMA copy operation. /// diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs index f2997678c7..45284525ca 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs @@ -590,9 +590,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// struct RtColorMask { -#pragma warning disable CS0649 // Field is never assigned to public uint Packed; -#pragma warning restore CS0649 + + public RtColorMask(uint packed) + { + Packed = packed; + } /// /// Unpacks red channel enable. diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Types/Boolean32.cs b/src/Ryujinx.Graphics.Gpu/Engine/Types/Boolean32.cs index 911ad53b4d..7293fab9c7 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Types/Boolean32.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Types/Boolean32.cs @@ -5,9 +5,12 @@ /// readonly struct Boolean32 { -#pragma warning disable CS0649 // Field is never assigned to private readonly uint _value; -#pragma warning restore CS0649 + + public Boolean32(uint value) + { + _value = value; + } public static implicit operator bool(Boolean32 value) {