diff options
| author | gdkchan <gab.dark.100@gmail.com> | 2021-07-11 17:20:40 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-07-11 17:20:40 -0300 |
| commit | 40b21cc3c4d2622bbd4f88d43073341854d9a671 (patch) | |
| tree | 6e9dc6a42e7c0bae5b03db468481771d5a6937ef /Ryujinx.Graphics.Gpu/Engine | |
| parent | b5190f16810eb77388c861d1d1773e19644808db (diff) | |
Separate GPU engines (part 2/2) (#2440)
* 3D engine now uses DeviceState too, plus new state modification tracking
* Remove old methods code
* Remove GpuState and friends
* Optimize DeviceState, force inline some functions
* This change was not supposed to go in
* Proper channel initialization
* Optimize state read/write methods even more
* Fix debug build
* Do not dirty state if the write is redundant
* The YControl register should dirty either the viewport or front face state too, to update the host origin
* Avoid redundant vertex buffer updates
* Move state and get rid of the Ryujinx.Graphics.Gpu.State namespace
* Comments and nits
* Fix rebase
* PR feedback
* Move changed = false to improve codegen
* PR feedback
* Carry RyuJIT a bit more
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Engine')
48 files changed, 4112 insertions, 2229 deletions
diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs b/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs index 5d1fd2c0..ac8b1186 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs @@ -1,9 +1,10 @@ using Ryujinx.Graphics.Device; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Engine.InlineToMemory; +using Ryujinx.Graphics.Gpu.Engine.Threed; +using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.Shader; -using Ryujinx.Graphics.Gpu.State; using Ryujinx.Graphics.Shader; using System; using System.Collections.Generic; @@ -14,27 +15,34 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute /// <summary> /// Represents a compute engine class. /// </summary> - class ComputeClass : InlineToMemoryClass, IDeviceState + class ComputeClass : IDeviceState { private readonly GpuContext _context; private readonly GpuChannel _channel; + private readonly ThreedClass _3dEngine; private readonly DeviceState<ComputeClassState> _state; + private readonly InlineToMemoryClass _i2mClass; + /// <summary> /// Creates a new instance of the compute engine class. /// </summary> /// <param name="context">GPU context</param> /// <param name="channel">GPU channel</param> - public ComputeClass(GpuContext context, GpuChannel channel) : base(context, channel, false) + /// <param name="threedEngine">3D engine</param> + public ComputeClass(GpuContext context, GpuChannel channel, ThreedClass threedEngine) { _context = context; _channel = channel; + _3dEngine = threedEngine; _state = new DeviceState<ComputeClassState>(new Dictionary<string, RwCallback> { { nameof(ComputeClassState.LaunchDma), new RwCallback(LaunchDma, null) }, { nameof(ComputeClassState.LoadInlineData), new RwCallback(LoadInlineData, null) }, { nameof(ComputeClassState.SendSignalingPcasB), new RwCallback(SendSignalingPcasB, null) } }); + + _i2mClass = new InlineToMemoryClass(context, channel, initializeState: false); } /// <summary> @@ -42,22 +50,31 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute /// </summary> /// <param name="offset">Register byte offset</param> /// <returns>Data at the specified offset</returns> - public override int Read(int offset) => _state.Read(offset); + public int Read(int offset) => _state.Read(offset); /// <summary> /// Writes data to the class registers. /// </summary> /// <param name="offset">Register byte offset</param> /// <param name="data">Data to be written</param> - public override void Write(int offset, int data) => _state.Write(offset, data); + public void Write(int offset, int data) => _state.Write(offset, data); /// <summary> /// Launches the Inline-to-Memory DMA copy operation. /// </summary> /// <param name="argument">Method call argument</param> - protected override void LaunchDma(int argument) + private void LaunchDma(int argument) + { + _i2mClass.LaunchDma(ref Unsafe.As<ComputeClassState, InlineToMemoryClassState>(ref _state.State), argument); + } + + /// <summary> + /// Pushes a word of data to the Inline-to-Memory engine. + /// </summary> + /// <param name="argument">Method call argument</param> + private void LoadInlineData(int argument) { - LaunchDma(ref Unsafe.As<ComputeClassState, InlineToMemoryClassState>(ref _state.State), argument); + _i2mClass.LoadInlineData(argument); } /// <summary> @@ -68,7 +85,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute { var memoryManager = _channel.MemoryManager; - _context.Methods.FlushUboDirty(memoryManager); + _3dEngine.FlushUboDirty(); uint qmdAddress = _state.State.SendPcasA; @@ -102,7 +119,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute texturePoolGpuVa, _state.State.SetTexHeaderPoolCMaximumIndex, _state.State.SetBindlessTextureConstantBufferSlotSelect, - false); + false, + PrimitiveTopology.Points); ShaderBundle cs = memoryManager.Physical.ShaderCache.GetComputeShader( _channel, @@ -207,7 +225,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute _context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth); - _context.Methods.ForceShaderUpdate(); + _3dEngine.ForceShaderUpdate(); } } } diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeQmd.cs b/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeQmd.cs index a1116074..1b20e41c 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeQmd.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeQmd.cs @@ -1,4 +1,4 @@ -using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Gpu.Engine.Types; using System; using System.Runtime.CompilerServices; diff --git a/Ryujinx.Graphics.Gpu/Engine/ConditionalRenderEnabled.cs b/Ryujinx.Graphics.Gpu/Engine/ConditionalRenderEnabled.cs index 451f4963..5581b5cc 100644 --- a/Ryujinx.Graphics.Gpu/Engine/ConditionalRenderEnabled.cs +++ b/Ryujinx.Graphics.Gpu/Engine/ConditionalRenderEnabled.cs @@ -1,5 +1,8 @@ namespace Ryujinx.Graphics.Gpu.Engine { + /// <summary> + /// Conditional rendering enable. + /// </summary> enum ConditionalRenderEnabled { False, diff --git a/Ryujinx.Graphics.Gpu/Engine/DeviceStateWithShadow.cs b/Ryujinx.Graphics.Gpu/Engine/DeviceStateWithShadow.cs new file mode 100644 index 00000000..3a06bc2a --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/DeviceStateWithShadow.cs @@ -0,0 +1,95 @@ +using Ryujinx.Graphics.Device; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Gpu.Engine +{ + /// <summary> + /// State interface with a shadow memory control register. + /// </summary> + interface IShadowState + { + /// <summary> + /// MME shadow ram control mode. + /// </summary> + SetMmeShadowRamControlMode SetMmeShadowRamControlMode { get; } + } + + /// <summary> + /// Represents a device's state, with a additional shadow state. + /// </summary> + /// <typeparam name="TState">Type of the state</typeparam> + class DeviceStateWithShadow<TState> : IDeviceState where TState : unmanaged, IShadowState + { + private readonly DeviceState<TState> _state; + private readonly DeviceState<TState> _shadowState; + + /// <summary> + /// Current device state. + /// </summary> + public ref TState State => ref _state.State; + + /// <summary> + /// Creates a new instance of the device state, with shadow state. + /// </summary> + /// <param name="callbacks">Optional that will be called if a register specified by name is read or written</param> + /// <param name="debugLogCallback">Optional callback to be used for debug log messages</param> + public DeviceStateWithShadow(IReadOnlyDictionary<string, RwCallback> callbacks = null, Action<string> debugLogCallback = null) + { + _state = new DeviceState<TState>(callbacks, debugLogCallback); + _shadowState = new DeviceState<TState>(); + } + + /// <summary> + /// Reads a value from a register. + /// </summary> + /// <param name="offset">Register offset in bytes</param> + /// <returns>Value stored on the register</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Read(int offset) + { + return _state.Read(offset); + } + + /// <summary> + /// Writes a value to a register. + /// </summary> + /// <param name="offset">Register offset in bytes</param> + /// <param name="value">Value to be written</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(int offset, int value) + { + WriteWithRedundancyCheck(offset, value, out _); + } + + /// <summary> + /// Writes a value to a register, returning a value indicating if <paramref name="value"/> + /// is different from the current value on the register. + /// </summary> + /// <param name="offset">Register offset in bytes</param> + /// <param name="value">Value to be written</param> + /// <param name="changed">True if the value was changed, false otherwise</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteWithRedundancyCheck(int offset, int value, out bool changed) + { + var shadowRamControl = _state.State.SetMmeShadowRamControlMode; + if (shadowRamControl == SetMmeShadowRamControlMode.MethodPassthrough || offset < 0x200) + { + _state.WriteWithRedundancyCheck(offset, value, out changed); + } + else if (shadowRamControl == SetMmeShadowRamControlMode.MethodTrack || + shadowRamControl == SetMmeShadowRamControlMode.MethodTrackWithFilter) + { + _shadowState.Write(offset, value); + _state.WriteWithRedundancyCheck(offset, value, out changed); + } + else /* if (shadowRamControl == SetMmeShadowRamControlMode.MethodReplay) */ + { + Debug.Assert(shadowRamControl == SetMmeShadowRamControlMode.MethodReplay); + _state.WriteWithRedundancyCheck(offset, _shadowState.Read(offset), out changed); + } + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs b/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs index 58fa2326..70909168 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs @@ -1,6 +1,6 @@ using Ryujinx.Common; using Ryujinx.Graphics.Device; -using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Gpu.Engine.Threed; using Ryujinx.Graphics.Texture; using System; using System.Collections.Generic; @@ -16,6 +16,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma { private readonly GpuContext _context; private readonly GpuChannel _channel; + private readonly ThreedClass _3dEngine; private readonly DeviceState<DmaClassState> _state; /// <summary> @@ -35,10 +36,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma /// </summary> /// <param name="context">GPU context</param> /// <param name="channel">GPU channel</param> - public DmaClass(GpuContext context, GpuChannel channel) + /// <param name="threedEngine">3D engine</param> + public DmaClass(GpuContext context, GpuChannel channel, ThreedClass threedEngine) { _context = context; _channel = channel; + _3dEngine = threedEngine; _state = new DeviceState<DmaClassState>(new Dictionary<string, RwCallback> { { nameof(DmaClassState.LaunchDma), new RwCallback(LaunchDma, null) } @@ -69,7 +72,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma /// <param name="xCount">Number of pixels to be copied</param> /// <param name="yCount">Number of lines to be copied</param> /// <returns></returns> - private static bool IsTextureCopyComplete(CopyBufferTexture tex, bool linear, int bpp, int stride, int xCount, int yCount) + private static bool IsTextureCopyComplete(DmaTexture tex, bool linear, int bpp, int stride, int xCount, int yCount) { if (linear) { @@ -116,7 +119,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma int xCount = (int)_state.State.LineLengthIn; int yCount = (int)_state.State.LineCount; - _context.Methods.FlushUboDirty(memoryManager); + _3dEngine.FlushUboDirty(); if (copy2D) { @@ -125,8 +128,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma int srcBpp = remap ? ((int)_state.State.SetRemapComponentsNumSrcComponents + 1) * componentSize : 1; int dstBpp = remap ? ((int)_state.State.SetRemapComponentsNumDstComponents + 1) * componentSize : 1; - var dst = Unsafe.As<uint, CopyBufferTexture>(ref _state.State.SetDstBlockSize); - var src = Unsafe.As<uint, CopyBufferTexture>(ref _state.State.SetSrcBlockSize); + var dst = Unsafe.As<uint, DmaTexture>(ref _state.State.SetDstBlockSize); + var src = Unsafe.As<uint, DmaTexture>(ref _state.State.SetSrcBlockSize); int srcStride = (int)_state.State.PitchIn; int dstStride = (int)_state.State.PitchOut; diff --git a/Ryujinx.Graphics.Gpu/Engine/Dma/DmaTexture.cs b/Ryujinx.Graphics.Gpu/Engine/Dma/DmaTexture.cs new file mode 100644 index 00000000..6873ff40 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Dma/DmaTexture.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.Gpu.Engine.Types; + +namespace Ryujinx.Graphics.Gpu.Engine.Dma +{ + /// <summary> + /// Buffer to texture copy parameters. + /// </summary> + struct DmaTexture + { +#pragma warning disable CS0649 + public MemoryLayout MemoryLayout; + public int Width; + public int Height; + public int Depth; + public int RegionZ; + public ushort RegionX; + public ushort RegionY; +#pragma warning restore CS0649 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs b/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs index 75b19c37..28822f4e 100644 --- a/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs +++ b/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs @@ -1,6 +1,5 @@ using Ryujinx.Graphics.Device; using Ryujinx.Graphics.Gpu.Engine.MME; -using Ryujinx.Graphics.Gpu.State; using System; using System.Collections.Generic; using System.Threading; @@ -150,7 +149,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo /// <param name="argument">Method call argument</param> public void WaitForIdle(int argument) { - _context.Methods.PerformDeferredDraws(); + _parent.PerformDeferredDraws(); _context.Renderer.Pipeline.Barrier(); _context.CreateHostSyncIfNeeded(); @@ -189,7 +188,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo /// <param name="argument">Method call argument</param> public void SetMmeShadowRamControl(int argument) { - _parent.SetShadowRamControl((ShadowRamControl)argument); + _parent.SetShadowRamControl(argument); } /// <summary> @@ -217,7 +216,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo /// </summary> /// <param name="index">Index of the macro</param> /// <param name="state">Current GPU state</param> - public void CallMme(int index, GpuState state) + public void CallMme(int index, IDeviceState state) { _macros[index].Execute(_macroCode, state); } diff --git a/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs b/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs index c2727f48..dd5e6fe5 100644 --- a/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs +++ b/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs @@ -2,9 +2,9 @@ using Ryujinx.Graphics.Gpu.Engine.Compute; using Ryujinx.Graphics.Gpu.Engine.Dma; using Ryujinx.Graphics.Gpu.Engine.InlineToMemory; +using Ryujinx.Graphics.Gpu.Engine.Threed; using Ryujinx.Graphics.Gpu.Engine.Twod; using Ryujinx.Graphics.Gpu.Memory; -using Ryujinx.Graphics.Gpu.State; using System; using System.Runtime.CompilerServices; @@ -18,9 +18,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo private const int MacrosCount = 0x80; private const int MacroIndexMask = MacrosCount - 1; - private readonly GpuContext _context; + private const int UniformBufferUpdateDataMethodOffset = 0x8e4; + private readonly GpuChannel _channel; + /// <summary> + /// Channel memory manager. + /// </summary> public MemoryManager MemoryManager => _channel.MemoryManager; /// <summary> @@ -37,8 +41,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo private DmaState _state; - private readonly GpuState[] _subChannels; - private readonly IDeviceState[] _subChannels2; + private readonly ThreedClass _3dClass; + private readonly ComputeClass _computeClass; + private readonly InlineToMemoryClass _i2mClass; + private readonly TwodClass _2dClass; + private readonly DmaClass _dmaClass; + private readonly GPFifoClass _fifoClass; /// <summary> @@ -48,29 +56,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo /// <param name="channel">Channel that the GPFIFO processor belongs to</param> public GPFifoProcessor(GpuContext context, GpuChannel channel) { - _context = context; _channel = channel; _fifoClass = new GPFifoClass(context, this); - _subChannels = new GpuState[8]; - _subChannels2 = new IDeviceState[8] - { - null, - new ComputeClass(context, channel), - new InlineToMemoryClass(context, channel), - new TwodClass(channel), - new DmaClass(context, channel), - null, - null, - null - }; - - for (int index = 0; index < _subChannels.Length; index++) - { - _subChannels[index] = new GpuState(channel, _subChannels2[index]); - - _context.Methods.RegisterCallbacks(_subChannels[index]); - } + _3dClass = new ThreedClass(context, channel); + _computeClass = new ComputeClass(context, channel, _3dClass); + _i2mClass = new InlineToMemoryClass(context, channel); + _2dClass = new TwodClass(channel); + _dmaClass = new DmaClass(context, channel, _3dClass); } /// <summary> @@ -85,7 +78,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo if (_state.MethodCount != 0) { - Send(new MethodParams(_state.Method, command, _state.SubChannel, _state.MethodCount)); + Send(_state.Method, command, _state.SubChannel, _state.MethodCount <= 1); if (!_state.NonIncrementing) { @@ -121,13 +114,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo _state.NonIncrementing = meth.SecOp == SecOp.NonIncMethod; break; case SecOp.ImmdDataMethod: - Send(new MethodParams(meth.MethodAddress, meth.ImmdData, meth.MethodSubchannel, 1)); + Send(meth.MethodAddress, meth.ImmdData, meth.MethodSubchannel, true); break; } } } - _context.Methods.FlushUboDirty(MemoryManager); + _3dClass.FlushUboDirty(); } /// <summary> @@ -145,11 +138,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo if (meth.MethodCount < availableCount && meth.SecOp == SecOp.NonIncMethod && - meth.MethodAddress == (int)MethodOffset.UniformBufferUpdateData) + meth.MethodAddress == UniformBufferUpdateDataMethodOffset) { - GpuState state = _subChannels[meth.MethodSubchannel]; - - _context.Methods.UniformBufferUpdate(state, commandBuffer.Slice(offset + 1, meth.MethodCount)); + _3dClass.ConstantBufferUpdate(commandBuffer.Slice(offset + 1, meth.MethodCount)); return true; } @@ -161,67 +152,124 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo /// Sends a uncompressed method for processing by the graphics pipeline. /// </summary> /// <param name="meth">Method to be processed</param> - private void Send(MethodParams meth) + private void Send(int offset, int argument, int subChannel, bool isLastCall) { - if ((MethodOffset)meth.Method == MethodOffset.BindChannel) - { - _subChannels[meth.SubChannel].ClearCallbacks(); - - _context.Methods.RegisterCallbacks(_subChannels[meth.SubChannel]); - } - else if (meth.Method < 0x60) - { - // TODO: check if macros are shared between subchannels or not. For now let's assume they are. - _fifoClass.Write(meth.Method * 4, meth.Argument); - } - else if (meth.Method < 0xe00) + if (offset < 0x60) { - _subChannels[meth.SubChannel].CallMethod(meth); + _fifoClass.Write(offset * 4, argument); } - else + else if (offset < 0xe00) { - int macroIndex = (meth.Method >> 1) & MacroIndexMask; + offset *= 4; - if ((meth.Method & 1) != 0) + switch (subChannel) { - _fifoClass.MmePushArgument(macroIndex, meth.Argument); + case 0: + _3dClass.Write(offset, argument); + break; + case 1: + _computeClass.Write(offset, argument); + break; + case 2: + _i2mClass.Write(offset, argument); + break; + case 3: + _2dClass.Write(offset, argument); + break; + case 4: + _dmaClass.Write(offset, argument); + break; } - else + } + else + { + IDeviceState state = subChannel switch { - _fifoClass.MmeStart(macroIndex, meth.Argument); - } + 0 => _3dClass, + 3 => _2dClass, + _ => null + }; - if (meth.IsLastCall) + if (state != null) { - _fifoClass.CallMme(macroIndex, _subChannels[meth.SubChannel]); + int macroIndex = (offset >> 1) & MacroIndexMask; + + if ((offset & 1) != 0) + { + _fifoClass.MmePushArgument(macroIndex, argument); + } + else + { + _fifoClass.MmeStart(macroIndex, argument); + } - _context.Methods.PerformDeferredDraws(); + if (isLastCall) + { + _fifoClass.CallMme(macroIndex, state); + + _3dClass.PerformDeferredDraws(); + } } } } /// <summary> - /// Sets the shadow ram control value of all sub-channels. + /// Writes data directly to the state of the specified class. /// </summary> - /// <param name="control">New shadow ram control value</param> - public void SetShadowRamControl(ShadowRamControl control) + /// <param name="classId">ID of the class to write the data into</param> + /// <param name="offset">State offset in bytes</param> + /// <param name="value">Value to be written</param> + public void Write(ClassId classId, int offset, int value) { - for (int i = 0; i < _subChannels.Length; i++) + switch (classId) { - _subChannels[i].ShadowRamControl = control; + case ClassId.Threed: + _3dClass.Write(offset, value); + break; + case ClassId.Compute: + _computeClass.Write(offset, value); + break; + case ClassId.InlineToMemory: + _i2mClass.Write(offset, value); + break; + case ClassId.Twod: + _2dClass.Write(offset, value); + break; + case ClassId.Dma: + _dmaClass.Write(offset, value); + break; + case ClassId.GPFifo: + _fifoClass.Write(offset, value); + break; } } /// <summary> + /// Sets the shadow ram control value of all sub-channels. + /// </summary> + /// <param name="control">New shadow ram control value</param> + public void SetShadowRamControl(int control) + { + _3dClass.SetShadowRamControl(control); + } + + /// <summary> /// Forces a full host state update by marking all state as modified, /// and also requests all GPU resources in use to be rebound. /// </summary> public void ForceAllDirty() { - for (int index = 0; index < _subChannels.Length; index++) - { - _subChannels[index].ForceAllDirty(); - } + _3dClass.ForceStateDirty(); + _channel.BufferManager.Rebind(); + _channel.TextureManager.Rebind(); + } + + /// <summary> + /// Perform any deferred draws. + /// </summary> + public void PerformDeferredDraws() + { + _3dClass.PerformDeferredDraws(); } } } diff --git a/Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs b/Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs deleted file mode 100644 index 3d23b785..00000000 --- a/Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs +++ /dev/null @@ -1,134 +0,0 @@ -using Ryujinx.Common; -using Ryujinx.Graphics.Gpu.State; -using Ryujinx.Graphics.Texture; -using System; -using System.Runtime.InteropServices; - -namespace Ryujinx.Graphics.Gpu.Engine -{ - partial class Methods - { - private Inline2MemoryParams _params; - - private bool _isLinear; - - private int _offset; - private int _size; - - private bool _finished; - - private int[] _buffer; - - /// <summary> - /// Launches Inline-to-Memory engine DMA copy. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - public void LaunchDma(GpuState state, int argument) - { - _params = state.Get<Inline2MemoryParams>(MethodOffset.I2mParams); - - _isLinear = (argument & 1) != 0; - - _offset = 0; - _size = _params.LineLengthIn * _params.LineCount; - - int count = BitUtils.DivRoundUp(_size, 4); - - if (_buffer == null || _buffer.Length < count) - { - _buffer = new int[count]; - } - - ulong dstBaseAddress = state.Channel.MemoryManager.Translate(_params.DstAddress.Pack()); - - // Trigger read tracking, to flush any managed resources in the destination region. - state.Channel.MemoryManager.Physical.GetSpan(dstBaseAddress, _size, true); - - _finished = false; - } - - /// <summary> - /// Pushes a word of data to the Inline-to-Memory engine. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - public void LoadInlineData(GpuState state, int argument) - { - if (!_finished) - { - _buffer[_offset++] = argument; - - if (_offset * 4 >= _size) - { - FinishTransfer(state); - } - } - } - - /// <summary> - /// Performs actual copy of the inline data after the transfer is finished. - /// </summary> - /// <param name="state">Current GPU state</param> - private void FinishTransfer(GpuState state) - { - Span<byte> data = MemoryMarshal.Cast<int, byte>(_buffer).Slice(0, _size); - - if (_isLinear && _params.LineCount == 1) - { - ulong address = state.Channel.MemoryManager.Translate(_params.DstAddress.Pack()); - - state.Channel.MemoryManager.Physical.Write(address, data); - } - else - { - var dstCalculator = new OffsetCalculator( - _params.DstWidth, - _params.DstHeight, - _params.DstStride, - _isLinear, - _params.DstMemoryLayout.UnpackGobBlocksInY(), - 1); - - int srcOffset = 0; - - ulong dstBaseAddress = state.Channel.MemoryManager.Translate(_params.DstAddress.Pack()); - - for (int y = _params.DstY; y < _params.DstY + _params.LineCount; y++) - { - int x1 = _params.DstX; - int x2 = _params.DstX + _params.LineLengthIn; - int x2Trunc = _params.DstX + BitUtils.AlignDown(_params.LineLengthIn, 16); - - int x; - - for (x = x1; x < x2Trunc; x += 16, srcOffset += 16) - { - int dstOffset = dstCalculator.GetOffset(x, y); - - ulong dstAddress = dstBaseAddress + (ulong)dstOffset; - - Span<byte> pixel = data.Slice(srcOffset, 16); - - state.Channel.MemoryManager.Physical.Write(dstAddress, pixel); - } - - for (; x < x2; x++, srcOffset++) - { - int dstOffset = dstCalculator.GetOffset(x, y); - - ulong dstAddress = dstBaseAddress + (ulong)dstOffset; - - Span<byte> pixel = data.Slice(srcOffset, 1); - - state.Channel.MemoryManager.Physical.Write(dstAddress, pixel); - } - } - } - - _finished = true; - - _context.AdvanceSequence(); - } - } -}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs b/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs index 0e7d6fb0..cb4133ec 100644 --- a/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs +++ b/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs @@ -41,7 +41,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory /// <param name="context">GPU context</param> /// <param name="channel">GPU channel</param> /// <param name="initializeState">Indicates if the internal state should be initialized. Set to false if part of another engine</param> - protected InlineToMemoryClass(GpuContext context, GpuChannel channel, bool initializeState) + public InlineToMemoryClass(GpuContext context, GpuChannel channel, bool initializeState) { _context = context; _channel = channel; @@ -70,20 +70,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory /// </summary> /// <param name="offset">Register byte offset</param> /// <returns>Data at the specified offset</returns> - public virtual int Read(int offset) => _state.Read(offset); + public int Read(int offset) => _state.Read(offset); /// <summary> /// Writes data to the class registers. /// </summary> /// <param name="offset">Register byte offset</param> /// <param name="data">Data to be written</param> - public virtual void Write(int offset, int data) => _state.Write(offset, data); + public void Write(int offset, int data) => _state.Write(offset, data); /// <summary> /// Launches Inline-to-Memory engine DMA copy. /// </summary> /// <param name="argument">Method call argument</param> - protected virtual void LaunchDma(int argument) + private void LaunchDma(int argument) { LaunchDma(ref _state.State, argument); } @@ -93,7 +93,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory /// </summary> /// <param name="state">Current class state</param> /// <param name="argument">Method call argument</param> - protected void LaunchDma(ref InlineToMemoryClassState state, int argument) + public void LaunchDma(ref InlineToMemoryClassState state, int argument) { _isLinear = (argument & 1) != 0; @@ -131,7 +131,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory /// Pushes a word of data to the Inline-to-Memory engine. /// </summary> /// <param name="argument">Method call argument</param> - protected void LoadInlineData(int argument) + public void LoadInlineData(int argument) { if (!_finished) { diff --git a/Ryujinx.Graphics.Gpu/Engine/MME/IMacroEE.cs b/Ryujinx.Graphics.Gpu/Engine/MME/IMacroEE.cs index b056ecc8..b957de08 100644 --- a/Ryujinx.Graphics.Gpu/Engine/MME/IMacroEE.cs +++ b/Ryujinx.Graphics.Gpu/Engine/MME/IMacroEE.cs @@ -1,4 +1,4 @@ -using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Device; using System; using System.Collections.Generic; @@ -12,7 +12,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME /// <summary> /// Arguments FIFO. /// </summary> - public Queue<int> Fifo { get; } + Queue<int> Fifo { get; } /// <summary> /// Should execute the GPU Macro code being passed. @@ -20,6 +20,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME /// <param name="code">Code to be executed</param> /// <param name="state">GPU state at the time of the call</param> /// <param name="arg0">First argument to be passed to the GPU Macro</param> - void Execute(ReadOnlySpan<int> code, GpuState state, int arg0); + void Execute(ReadOnlySpan<int> code, IDeviceState state, int arg0); } } diff --git a/Ryujinx.Graphics.Gpu/Engine/MME/Macro.cs b/Ryujinx.Graphics.Gpu/Engine/MME/Macro.cs index 9847f4c0..1a79afb9 100644 --- a/Ryujinx.Graphics.Gpu/Engine/MME/Macro.cs +++ b/Ryujinx.Graphics.Gpu/Engine/MME/Macro.cs @@ -1,4 +1,4 @@ -using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Device; using System; namespace Ryujinx.Graphics.Gpu.Engine.MME @@ -55,7 +55,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME /// </summary> /// <param name="code">Program code</param> /// <param name="state">Current GPU state</param> - public void Execute(ReadOnlySpan<int> code, GpuState state) + public void Execute(ReadOnlySpan<int> code, IDeviceState state) { if (_executionPending) { diff --git a/Ryujinx.Graphics.Gpu/Engine/MME/MacroInterpreter.cs b/Ryujinx.Graphics.Gpu/Engine/MME/MacroInterpreter.cs index 9a834ca6..0173a7fb 100644 --- a/Ryujinx.Graphics.Gpu/Engine/MME/MacroInterpreter.cs +++ b/Ryujinx.Graphics.Gpu/Engine/MME/MacroInterpreter.cs @@ -1,5 +1,5 @@ using Ryujinx.Common.Logging; -using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Device; using System; using System.Collections.Generic; @@ -45,7 +45,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME /// <param name="code">Code of the program to execute</param> /// <param name="state">Current GPU state</param> /// <param name="arg0">Optional argument passed to the program, 0 if not used</param> - public void Execute(ReadOnlySpan<int> code, GpuState state, int arg0) + public void Execute(ReadOnlySpan<int> code, IDeviceState state, int arg0) { Reset(); @@ -55,7 +55,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME FetchOpCode(code); - while (Step(code, state)) ; + while (Step(code, state)) + { + } // Due to the delay slot, we still need to execute // one more instruction before we actually exit. @@ -85,7 +87,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME /// <param name="code">Program code to execute</param> /// <param name="state">Current GPU state</param> /// <returns>True to continue execution, false if the program exited</returns> - private bool Step(ReadOnlySpan<int> code, GpuState state) + private bool Step(ReadOnlySpan<int> code, IDeviceState state) { int baseAddr = _pc - 1; @@ -193,7 +195,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME /// </summary> /// <param name="state">Current GPU state</param> /// <returns>Operation result</returns> - private int GetAluResult(GpuState state) + private int GetAluResult(IDeviceState state) { AluOperation op = (AluOperation)(_opCode & 7); @@ -378,9 +380,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME /// <param name="state">Current GPU state</param> /// <param name="reg">Register offset to read</param> /// <returns>GPU register value</returns> - private int Read(GpuState state, int reg) + private int Read(IDeviceState state, int reg) { - return state.Read(reg); + return state.Read(reg * 4); } /// <summary> @@ -388,11 +390,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME /// </summary> /// <param name="state">Current GPU state</param> /// <param name="value">Call argument</param> - private void Send(GpuState state, int value) + private void Send(IDeviceState state, int value) { - MethodParams meth = new MethodParams(_methAddr, value); - - state.CallMethod(meth); + state.Write(_methAddr * 4, value); _methAddr += _methIncr; } diff --git a/Ryujinx.Graphics.Gpu/Engine/MME/MacroJit.cs b/Ryujinx.Graphics.Gpu/Engine/MME/MacroJit.cs index 346ae6cf..f0393dd1 100644 --- a/Ryujinx.Graphics.Gpu/Engine/MME/MacroJit.cs +++ b/Ryujinx.Graphics.Gpu/Engine/MME/MacroJit.cs @@ -1,4 +1,4 @@ -using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Device; using System; using System.Collections.Generic; @@ -24,7 +24,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME /// <param name="code">Code of the program to execute</param> /// <param name="state">Current GPU state</param> /// <param name="arg0">Optional argument passed to the program, 0 if not used</param> - public void Execute(ReadOnlySpan<int> code, GpuState state, int arg0) + public void Execute(ReadOnlySpan<int> code, IDeviceState state, int arg0) { if (_execute == null) { diff --git a/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitCompiler.cs b/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitCompiler.cs index e752b9dc..d281a75a 100644 --- a/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitCompiler.cs +++ b/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitCompiler.cs @@ -1,4 +1,4 @@ -using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Device; using System; using System.Collections.Generic; using System.Reflection.Emit; @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME /// </summary> public MacroJitCompiler() { - _meth = new DynamicMethod("Macro", typeof(void), new Type[] { typeof(MacroJitContext), typeof(GpuState), typeof(int) }); + _meth = new DynamicMethod("Macro", typeof(void), new Type[] { typeof(MacroJitContext), typeof(IDeviceState), typeof(int) }); _ilGen = _meth.GetILGenerator(); _gprs = new LocalBuilder[8]; @@ -39,7 +39,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME _ilGen.Emit(OpCodes.Stloc, _gprs[1]); } - public delegate void MacroExecute(MacroJitContext context, GpuState state, int arg0); + public delegate void MacroExecute(MacroJitContext context, IDeviceState state, int arg0); /// <summary> /// Translates a new piece of GPU Macro code into host executable code. diff --git a/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitContext.cs b/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitContext.cs index ba426dc0..aa31c9ee 100644 --- a/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitContext.cs +++ b/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitContext.cs @@ -1,5 +1,5 @@ using Ryujinx.Common.Logging; -using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Device; using System.Collections.Generic; namespace Ryujinx.Graphics.Gpu.Engine.MME @@ -36,9 +36,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME /// <param name="state">Current GPU state</param> /// <param name="reg">Register offset to read</param> /// <returns>GPU register value</returns> - public static int Read(GpuState state, int reg) + public static int Read(IDeviceState state, int reg) { - return state.Read(reg); + return state.Read(reg * 4); } /// <summary> @@ -47,11 +47,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME /// <param name="value">Call argument</param> /// <param name="state">Current GPU state</param> /// <param name="methAddr">Address, in words, of the method</param> - public static void Send(int value, GpuState state, int methAddr) + public static void Send(int value, IDeviceState state, int methAddr) { - MethodParams meth = new MethodParams(methAddr, value); - - state.CallMethod(meth); + state.Write(methAddr * 4, value); } } } diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs b/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs deleted file mode 100644 index 5f6316dc..00000000 --- a/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Ryujinx.Graphics.GAL; -using Ryujinx.Graphics.Gpu.State; - -namespace Ryujinx.Graphics.Gpu.Engine -{ - partial class Methods - { - /// <summary> - /// Clears the current color and depth-stencil buffers. - /// Which buffers should be cleared is also specified on the argument. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - private void Clear(GpuState state, int argument) - { - ConditionalRenderEnabled renderEnable = GetRenderEnable(state); - - if (renderEnable == ConditionalRenderEnabled.False) - { - return; - } - - // Scissor and rasterizer discard also affect clears. - if (state.QueryModified(MethodOffset.ScissorState)) - { - UpdateScissorState(state); - } - - if (state.QueryModified(MethodOffset.RasterizeEnable)) - { - UpdateRasterizerState(state); - } - - int index = (argument >> 6) & 0xf; - - UpdateRenderTargetState(state, useControl: false, singleUse: index); - - state.Channel.TextureManager.UpdateRenderTargets(); - - bool clearDepth = (argument & 1) != 0; - bool clearStencil = (argument & 2) != 0; - - uint componentMask = (uint)((argument >> 2) & 0xf); - - if (componentMask != 0) - { - var clearColor = state.Get<ClearColors>(MethodOffset.ClearColors); - - ColorF color = new ColorF( - clearColor.Red, - clearColor.Green, - clearColor.Blue, - clearColor.Alpha); - - _context.Renderer.Pipeline.ClearRenderTargetColor(index, componentMask, color); - } - - if (clearDepth || clearStencil) - { - float depthValue = state.Get<float>(MethodOffset.ClearDepthValue); - int stencilValue = state.Get<int> (MethodOffset.ClearStencilValue); - - int stencilMask = 0; - - if (clearStencil) - { - stencilMask = state.Get<StencilTestState>(MethodOffset.StencilTestState).FrontMask; - } - - _context.Renderer.Pipeline.ClearRenderTargetDepthStencil( - depthValue, - clearDepth, - stencilValue, - stencilMask); - } - - UpdateRenderTargetState(state, useControl: true); - - if (renderEnable == ConditionalRenderEnabled.Host) - { - _context.Renderer.Pipeline.EndHostConditionalRendering(); - } - } - } -}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs b/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs deleted file mode 100644 index fec1cc46..00000000 --- a/Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs +++ /dev/null @@ -1,343 +0,0 @@ -using Ryujinx.Graphics.GAL; -using Ryujinx.Graphics.Gpu.Image; -using Ryujinx.Graphics.Gpu.State; - -namespace Ryujinx.Graphics.Gpu.Engine -{ - partial class Methods - { - private bool _drawIndexed; - - private bool _instancedDrawPending; - private bool _instancedIndexed; - - private int _instancedFirstIndex; - private int _instancedFirstVertex; - private int _instancedFirstInstance; - private int _instancedIndexCount; - private int _instancedDrawStateFirst; - private int _instancedDrawStateCount; - - private int _instanceIndex; - - private IbStreamer _ibStreamer; - - /// <summary> - /// Primitive topology of the current draw. - /// </summary> - public PrimitiveTopology Topology { get; private set; } - - /// <summary> - /// Finishes the draw call. - /// This draws geometry on the bound buffers based on the current GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - private void DrawEnd(GpuState state, int argument) - { - var indexBuffer = state.Get<IndexBufferState>(MethodOffset.IndexBufferState); - - DrawEnd(state, indexBuffer.First, indexBuffer.Count); - } - - /// <summary> - /// Finishes the draw call. - /// This draws geometry on the bound buffers based on the current GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="firstIndex">Index of the first index buffer element used on the draw</param> - /// <param name="indexCount">Number of index buffer elements used on the draw</param> - private void DrawEnd(GpuState state, int firstIndex, int indexCount) - { - ConditionalRenderEnabled renderEnable = GetRenderEnable(state); - - if (renderEnable == ConditionalRenderEnabled.False || _instancedDrawPending) - { - if (renderEnable == ConditionalRenderEnabled.False) - { - PerformDeferredDraws(); - } - - _drawIndexed = false; - - if (renderEnable == ConditionalRenderEnabled.Host) - { - _context.Renderer.Pipeline.EndHostConditionalRendering(); - } - - return; - } - - UpdateState(state, firstIndex, indexCount); - - bool instanced = _vsUsesInstanceId || _isAnyVbInstanced; - - if (instanced) - { - _instancedDrawPending = true; - - _instancedIndexed = _drawIndexed; - - _instancedFirstIndex = firstIndex; - _instancedFirstVertex = state.Get<int>(MethodOffset.FirstVertex); - _instancedFirstInstance = state.Get<int>(MethodOffset.FirstInstance); - - _instancedIndexCount = indexCount; - - var drawState = state.Get<VertexBufferDrawState>(MethodOffset.VertexBufferDrawState); - - _instancedDrawStateFirst = drawState.First; - _instancedDrawStateCount = drawState.Count; - - _drawIndexed = false; - - if (renderEnable == ConditionalRenderEnabled.Host) - { - _context.Renderer.Pipeline.EndHostConditionalRendering(); - } - - return; - } - - int firstInstance = state.Get<int>(MethodOffset.FirstInstance); - - int inlineIndexCount = _ibStreamer.GetAndResetInlineIndexCount(); - - if (inlineIndexCount != 0) - { - int firstVertex = state.Get<int>(MethodOffset.FirstVertex); - - BufferRange br = new BufferRange(_ibStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4); - - state.Channel.BufferManager.SetIndexBuffer(br, IndexType.UInt); - - _context.Renderer.Pipeline.DrawIndexed( - inlineIndexCount, - 1, - firstIndex, - firstVertex, - firstInstance); - } - else if (_drawIndexed) - { - int firstVertex = state.Get<int>(MethodOffset.FirstVertex); - - _context.Renderer.Pipeline.DrawIndexed( - indexCount, - 1, - firstIndex, - firstVertex, - firstInstance); - } - else - { - var drawState = state.Get<VertexBufferDrawState>(MethodOffset.VertexBufferDrawState); - - _context.Renderer.Pipeline.Draw( - drawState.Count, - 1, - drawState.First, - firstInstance); - } - - _drawIndexed = false; - - if (renderEnable == ConditionalRenderEnabled.Host) - { - _context.Renderer.Pipeline.EndHostConditionalRendering(); - } - } - - /// <summary> - /// Starts draw. - /// This sets primitive type and instanced draw parameters. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - private void DrawBegin(GpuState state, int argument) - { - bool incrementInstance = (argument & (1 << 26)) != 0; - bool resetInstance = (argument & (1 << 27)) == 0; - - PrimitiveType type = (PrimitiveType)(argument & 0xffff); - - PrimitiveTypeOverride typeOverride = state.Get<PrimitiveTypeOverride>(MethodOffset.PrimitiveTypeOverride); - - if (typeOverride != PrimitiveTypeOverride.Invalid) - { - DrawBegin(incrementInstance, resetInstance, typeOverride.Convert()); - } - else - { - DrawBegin(incrementInstance, resetInstance, type.Convert()); - } - } - - /// <summary> - /// Starts draw. - /// This sets primitive type and instanced draw parameters. - /// </summary> - /// <param name="incrementInstance">Indicates if the current instance should be incremented</param> - /// <param name="resetInstance">Indicates if the current instance should be set to zero</param> - /// <param name="topology">Primitive topology</param> - private void DrawBegin(bool incrementInstance, bool resetInstance, PrimitiveTopology topology) - { - if (incrementInstance) - { - _instanceIndex++; - } - else if (resetInstance) - { - PerformDeferredDraws(); - - _instanceIndex = 0; - } - - _context.Renderer.Pipeline.SetPrimitiveTopology(topology); - - Topology = topology; - } - - /// <summary> - /// Sets the index buffer count. - /// This also sets internal state that indicates that the next draw is an indexed draw. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - private void SetIndexBufferCount(GpuState state, int argument) - { - _drawIndexed = true; - } - - /// <summary> - /// Performs a indexed draw with a low number of index buffer elements. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - private void DrawIndexedSmall(GpuState state, int argument) - { - DrawIndexedSmall(state, argument, false); - } - - /// <summary> - /// Performs a indexed draw with a low number of index buffer elements. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - private void DrawIndexedSmall2(GpuState state, int argument) - { - DrawIndexedSmall(state, argument); - } - - /// <summary> - /// Performs a indexed draw with a low number of index buffer elements, - /// while also pre-incrementing the current instance value. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - private void DrawIndexedSmallIncInstance(GpuState state, int argument) - { - DrawIndexedSmall(state, argument, true); - } - - /// <summary> - /// Performs a indexed draw with a low number of index buffer elements, - /// while also pre-incrementing the current instance value. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - private void DrawIndexedSmallIncInstance2(GpuState state, int argument) - { - DrawIndexedSmallIncInstance(state, argument); - } - - /// <summary> - /// Performs a indexed draw with a low number of index buffer elements, - /// while optionally also pre-incrementing the current instance value. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - /// <param name="instanced">True to increment the current instance value, false otherwise</param> - private void DrawIndexedSmall(GpuState state, int argument, bool instanced) - { - PrimitiveTypeOverride typeOverride = state.Get<PrimitiveTypeOverride>(MethodOffset.PrimitiveTypeOverride); - - DrawBegin(instanced, !instanced, typeOverride.Convert()); - - int firstIndex = argument & 0xffff; - int indexCount = (argument >> 16) & 0xfff; - - bool oldDrawIndexed = _drawIndexed; - - _drawIndexed = true; - - DrawEnd(state, firstIndex, indexCount); - - _drawIndexed = oldDrawIndexed; - } - - /// <summary> - /// Pushes four 8-bit index buffer elements. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - private void VbElementU8(GpuState state, int argument) - { - _ibStreamer.VbElementU8(_context.Renderer, argument); - } - - /// <summary> - /// Pushes two 16-bit index buffer elements. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - private void VbElementU16(GpuState state, int argument) - { - _ibStreamer.VbElementU16(_context.Renderer, argument); - } - - /// <summary> - /// Pushes one 32-bit index buffer element. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - private void VbElementU32(GpuState state, int argument) - { - _ibStreamer.VbElementU32(_context.Renderer, argument); - } - - /// <summary> - /// Perform any deferred draws. - /// This is used for instanced draws. - /// Since each instance is a separate draw, we defer the draw and accumulate the instance count. - /// Once we detect the last instanced draw, then we perform the host instanced draw, - /// with the accumulated instance count. - /// </summary> - public void PerformDeferredDraws() - { - // Perform any pending instanced draw. - if (_instancedDrawPending) - { - _instancedDrawPending = false; - - if (_instancedIndexed) - { - _context.Renderer.Pipeline.DrawIndexed( - _instancedIndexCount, - _instanceIndex + 1, - _instancedFirstIndex, - _instancedFirstVertex, - _instancedFirstInstance); - } - else - { - _context.Renderer.Pipeline.Draw( - _instancedDrawStateCount, - _instanceIndex + 1, - _instancedDrawStateFirst, - _instancedFirstInstance); - } - } - } - } -}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodFirmware.cs b/Ryujinx.Graphics.Gpu/Engine/MethodFirmware.cs deleted file mode 100644 index 25a48af9..00000000 --- a/Ryujinx.Graphics.Gpu/Engine/MethodFirmware.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ryujinx.Graphics.Gpu.State; - -namespace Ryujinx.Graphics.Gpu.Engine -{ - partial class Methods - { - private void FirmwareCall4(GpuState state, int argument) - { - state.Write(0xd00, 1); - } - } -} diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodIncrementSyncpoint.cs b/Ryujinx.Graphics.Gpu/Engine/MethodIncrementSyncpoint.cs deleted file mode 100644 index 9c22275d..00000000 --- a/Ryujinx.Graphics.Gpu/Engine/MethodIncrementSyncpoint.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Ryujinx.Graphics.Gpu.State; - -namespace Ryujinx.Graphics.Gpu.Engine -{ - partial class Methods - { - /// <summary> - /// Performs an incrementation on a syncpoint. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - public void IncrementSyncpoint(GpuState state, int argument) - { - uint syncpointId = (uint)(argument) & 0xFFFF; - - _context.CreateHostSyncIfNeeded(); - _context.Renderer.UpdateCounters(); // Poll the query counters, the game may want an updated result. - _context.Synchronization.IncrementSyncpoint(syncpointId); - } - } -} diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs b/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs deleted file mode 100644 index 2dd0bbfa..00000000 --- a/Ryujinx.Graphics.Gpu/Engine/MethodReport.cs +++ /dev/null @@ -1,131 +0,0 @@ -using Ryujinx.Common; -using Ryujinx.Graphics.GAL; -using Ryujinx.Graphics.Gpu.Memory; -using Ryujinx.Graphics.Gpu.State; -using System; -using System.Runtime.InteropServices; - -namespace Ryujinx.Graphics.Gpu.Engine -{ - partial class Methods - { - private const int NsToTicksFractionNumerator = 384; - private const int NsToTicksFractionDenominator = 625; - - /// <summary> - /// Writes a GPU counter to guest memory. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - private void Report(GpuState state, int argument) - { - SemaphoreOperation op = (SemaphoreOperation)(argument & 3); - ReportCounterType type = (ReportCounterType)((argument >> 23) & 0x1f); - - switch (op) - { - case SemaphoreOperation.Release: ReleaseSemaphore(state); break; - case SemaphoreOperation.Counter: ReportCounter(state, type); break; - } - } - - /// <summary> - /// Writes (or Releases) a GPU semaphore value to guest memory. - /// </summary> - /// <param name="state">Current GPU state</param> - private void ReleaseSemaphore(GpuState state) - { - var rs = state.Get<SemaphoreState>(MethodOffset.ReportState); - - state.Channel.MemoryManager.Write(rs.Address.Pack(), rs.Payload); - - _context.AdvanceSequence(); - } - - /// <summary> - /// Packed GPU counter data (including GPU timestamp) in memory. - /// </summary> - private struct CounterData - { - public ulong Counter; - public ulong Timestamp; - } - - /// <summary> - /// Writes a GPU counter to guest memory. - /// This also writes the current timestamp value. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="type">Counter to be written to memory</param> - private void ReportCounter(GpuState state, ReportCounterType type) - { - var rs = state.Get<SemaphoreState>(MethodOffset.ReportState); - - ulong gpuVa = rs.Address.Pack(); - - ulong ticks = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds); - - if (GraphicsConfig.FastGpuTime) - { - // Divide by some amount to report time as if operations were performed faster than they really are. - // This can prevent some games from switching to a lower resolution because rendering is too slow. - ticks /= 256; - } - - ICounterEvent counter = null; - - EventHandler<ulong> resultHandler = (object evt, ulong result) => - { - CounterData counterData = new CounterData(); - - counterData.Counter = result; - counterData.Timestamp = ticks; - - if (counter?.Invalid != true) - { - state.Channel.MemoryManager.Write(gpuVa, counterData); - } - }; - - switch (type) - { - case ReportCounterType.Zero: - resultHandler(null, 0); - break; - case ReportCounterType.SamplesPassed: - counter = _context.Renderer.ReportCounter(CounterType.SamplesPassed, resultHandler); - break; - case ReportCounterType.PrimitivesGenerated: - counter = _context.Renderer.ReportCounter(CounterType.PrimitivesGenerated, resultHandler); - break; - case ReportCounterType.TransformFeedbackPrimitivesWritten: - counter = _context.Renderer.ReportCounter(CounterType.TransformFeedbackPrimitivesWritten, resultHandler); - break; - } - - state.Channel.MemoryManager.CounterCache.AddOrUpdate(gpuVa, counter); - } - - /// <summary> - /// Converts a nanoseconds timestamp value to Maxwell time ticks. - /// </summary> - /// <remarks> - /// The frequency is 614400000 Hz. - /// </remarks> - /// <param name="nanoseconds">Timestamp in nanoseconds</param> - /// <returns>Maxwell ticks</returns> - private static ulong ConvertNanosecondsToTicks(ulong nanoseconds) - { - // We need to divide first to avoid overflows. - // We fix up the result later by calculating the difference and adding - // that to the result. - ulong divided = nanoseconds / NsToTicksFractionDenominator; - - ulong rounded = divided * NsToTicksFractionDenominator; - - ulong errorBias = (nanoseconds - rounded) * NsToTicksFractionNumerator / NsToTicksFractionDenominator; - - return divided * NsToTicksFractionNumerator + errorBias; - } - } -}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs b/Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs deleted file mode 100644 index 79f8e1e8..00000000 --- a/Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Ryujinx.Graphics.GAL; -using Ryujinx.Graphics.Gpu.State; - -namespace Ryujinx.Graphics.Gpu.Engine -{ - partial class Methods - { - /// <summary> - /// Resets the value of an internal GPU counter back to zero. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - private void ResetCounter(GpuState state, int argument) - { - ResetCounterType type = (ResetCounterType)argument; - - switch (type) - { - case ResetCounterType.SamplesPassed: - _context.Renderer.ResetCounter(CounterType.SamplesPassed); - break; - case ResetCounterType.PrimitivesGenerated: - _context.Renderer.ResetCounter(CounterType.PrimitivesGenerated); - break; - case ResetCounterType.TransformFeedbackPrimitivesWritten: - _context.Renderer.ResetCounter(CounterType.TransformFeedbackPrimitivesWritten); - break; - } - } - } -}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs deleted file mode 100644 index 0746efa5..00000000 --- a/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Ryujinx.Graphics.Gpu.State; - -namespace Ryujinx.Graphics.Gpu.Engine -{ - partial class Methods - { - /// <summary> - /// Binds a uniform buffer for the vertex shader stage. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - private void UniformBufferBindVertex(GpuState state, int argument) - { - UniformBufferBind(state, argument, ShaderType.Vertex); - } - - /// <summary> - /// Binds a uniform buffer for the tessellation control shader stage. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - private void UniformBufferBindTessControl(GpuState state, int argument) - { - UniformBufferBind(state, argument, ShaderType.TessellationControl); - } - - /// <summary> - /// Binds a uniform buffer for the tessellation evaluation shader stage. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - private void UniformBufferBindTessEvaluation(GpuState state, int argument) - { - UniformBufferBind(state, argument, ShaderType.TessellationEvaluation); - } - - /// <summary> - /// Binds a uniform buffer for the geometry shader stage. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - private void UniformBufferBindGeometry(GpuState state, int argument) - { - UniformBufferBind(state, argument, ShaderType.Geometry); - } - - /// <summary> - /// Binds a uniform buffer for the fragment shader stage. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - private void UniformBufferBindFragment(GpuState state, int argument) - { - UniformBufferBind(state, argument, ShaderType.Fragment); - } - - /// <summary> - ///Binds a uniform buffer for the specified shader stage. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">Method call argument</param> - /// <param name="type">Shader stage that will access the uniform buffer</param> - private void UniformBufferBind(GpuState state, int argument, ShaderType type) - { - bool enable = (argument & 1) != 0; - - int index = (argument >> 4) & 0x1f; - - FlushUboDirty(state.Channel.MemoryManager); - - if (enable) - { - var uniformBuffer = state.Get<UniformBufferState>(MethodOffset.UniformBufferState); - - ulong address = uniformBuffer.Address.Pack(); - - state.Channel.BufferManager.SetGraphicsUniformBuffer((int)type, index, address, (uint)uniformBuffer.Size); - } - else - { - state.Channel.BufferManager.SetGraphicsUniformBuffer((int)type, index, 0, 0); - } - } - } -}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs b/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs deleted file mode 100644 index 49c8cda4..00000000 --- a/Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Ryujinx.Graphics.Gpu.Memory; -using Ryujinx.Graphics.Gpu.State; -using System; -using System.Runtime.InteropServices; - -namespace Ryujinx.Graphics.Gpu.Engine -{ - partial class Methods - { - // State associated with direct uniform buffer updates. - // This state is used to attempt to batch together consecutive updates. - private ulong _ubBeginCpuAddress = 0; - private ulong _ubFollowUpAddress = 0; - private ulong _ubByteCount = 0; - - /// <summary> - /// Flushes any queued ubo updates. - /// </summary> - /// <param name="memoryManager">GPU memory manager where the uniform buffer is mapped</param> - public void FlushUboDirty(MemoryManager memoryManager) - { - if (_ubFollowUpAddress != 0) - { - memoryManager.Physical.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount); - - _ubFollowUpAddress = 0; - } - } - - /// <summary> - /// Updates the uniform buffer data with inline data. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="argument">New uniform buffer data word</param> - private void UniformBufferUpdate(GpuState state, int argument) - { - var uniformBuffer = state.Get<UniformBufferState>(MethodOffset.UniformBufferState); - - ulong address = uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset; - - if (_ubFollowUpAddress != address) - { - FlushUboDirty(state.Channel.MemoryManager); - - _ubByteCount = 0; - _ubBeginCpuAddress = state.Channel.MemoryManager.Translate(address); - } - - var byteData = MemoryMarshal.Cast<int, byte>(MemoryMarshal.CreateSpan(ref argument, 1)); - state.Channel.MemoryManager.Physical.WriteUntracked(_ubBeginCpuAddress + _ubByteCount, byteData); - - _ubFollowUpAddress = address + 4; - _ubByteCount += 4; - - state.SetUniformBufferOffset(uniformBuffer.Offset + 4); - } - - /// <summary> - /// Updates the uniform buffer data with inline data. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="data">Data to be written to the uniform buffer</param> - public void UniformBufferUpdate(GpuState state, ReadOnlySpan<int> data) - { - var uniformBuffer = state.Get<UniformBufferState>(MethodOffset.UniformBufferState); - - ulong address = uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset; - - ulong size = (ulong)data.Length * 4; - - if (_ubFollowUpAddress != address) - { - FlushUboDirty(state.Channel.MemoryManager); - - _ubByteCount = 0; - _ubBeginCpuAddress = state.Channel.MemoryManager.Translate(address); - } - - var byteData = MemoryMarshal.Cast<int, byte>(data); - state.Channel.MemoryManager.Physical.WriteUntracked(_ubBeginCpuAddress + _ubByteCount, byteData); - - _ubFollowUpAddress = address + size; - _ubByteCount += size; - - state.SetUniformBufferOffset(uniformBuffer.Offset + data.Length * 4); - } - } -}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs deleted file mode 100644 index 756d56d9..00000000 --- a/Ryujinx.Graphics.Gpu/Engine/Methods.cs +++ /dev/null @@ -1,1138 +0,0 @@ -using Ryujinx.Common.Logging; -using Ryujinx.Graphics.GAL; -using Ryujinx.Graphics.Gpu.Image; -using Ryujinx.Graphics.Gpu.Memory; -using Ryujinx.Graphics.Gpu.Shader; -using Ryujinx.Graphics.Gpu.State; -using Ryujinx.Graphics.Shader; -using Ryujinx.Graphics.Texture; -using System; -using System.Linq; -using System.Runtime.InteropServices; - -namespace Ryujinx.Graphics.Gpu.Engine -{ - using Texture = Image.Texture; - - /// <summary> - /// GPU method implementations. - /// </summary> - partial class Methods - { - private readonly GpuContext _context; - private readonly ShaderProgramInfo[] _currentProgramInfo; - - private bool _isAnyVbInstanced; - private bool _vsUsesInstanceId; - private byte _vsClipDistancesWritten; - - private bool _forceShaderUpdate; - - private bool _prevTfEnable; - - /// <summary> - /// Creates a new instance of the GPU methods class. - /// </summary> - /// <param name="context">GPU context</param> - public Methods(GpuContext context) - { - _context = context; - - _currentProgramInfo = new ShaderProgramInfo[Constants.ShaderStages]; - } - - /// <summary> - /// Register callback for GPU method calls that triggers an action on the GPU. - /// </summary> - /// <param name="state">GPU state where the triggers will be registered</param> - public void RegisterCallbacks(GpuState state) - { - state.RegisterCallback(MethodOffset.LaunchDma, LaunchDma); - state.RegisterCallback(MethodOffset.LoadInlineData, LoadInlineData); - - state.RegisterCallback(MethodOffset.SyncpointAction, IncrementSyncpoint); - - state.RegisterCallback(MethodOffset.TextureBarrier, TextureBarrier); - state.RegisterCallback(MethodOffset.TextureBarrierTiled, TextureBarrierTiled); - - state.RegisterCallback(MethodOffset.VbElementU8, VbElementU8); - state.RegisterCallback(MethodOffset.VbElementU16, VbElementU16); - state.RegisterCallback(MethodOffset.VbElementU32, VbElementU32); - - state.RegisterCallback(MethodOffset.ResetCounter, ResetCounter); - - state.RegisterCallback(MethodOffset.DrawEnd, DrawEnd); - state.RegisterCallback(MethodOffset.DrawBegin, DrawBegin); - state.RegisterCallback(MethodOffset.DrawIndexedSmall, DrawIndexedSmall); - state.RegisterCallback(MethodOffset.DrawIndexedSmall2, DrawIndexedSmall2); - state.RegisterCallback(MethodOffset.DrawIndexedSmallIncInstance, DrawIndexedSmallIncInstance); - state.RegisterCallback(MethodOffset.DrawIndexedSmallIncInstance2, DrawIndexedSmallIncInstance2); - - state.RegisterCallback(MethodOffset.IndexBufferCount, SetIndexBufferCount); - - state.RegisterCallback(MethodOffset.Clear, Clear); - - state.RegisterCallback(MethodOffset.Report, Report); - - state.RegisterCallback(MethodOffset.FirmwareCall4, FirmwareCall4); - - state.RegisterCallback(MethodOffset.UniformBufferUpdateData, 16, UniformBufferUpdate); - - state.RegisterCallback(MethodOffset.UniformBufferBindVertex, UniformBufferBindVertex); - state.RegisterCallback(MethodOffset.UniformBufferBindTessControl, UniformBufferBindTessControl); - state.RegisterCallback(MethodOffset.UniformBufferBindTessEvaluation, UniformBufferBindTessEvaluation); - state.RegisterCallback(MethodOffset.UniformBufferBindGeometry, UniformBufferBindGeometry); - state.RegisterCallback(MethodOffset.UniformBufferBindFragment, UniformBufferBindFragment); - } - - /// <summary> - /// Updates host state based on the current guest GPU state. - /// </summary> - /// <param name="state">Guest GPU state</param> - /// <param name="firstIndex">Index of the first index buffer element used on the draw</param> - /// <param name="indexCount">Number of index buffer elements used on the draw</param> - private void UpdateState(GpuState state, int firstIndex, int indexCount) - { - bool tfEnable = state.Get<Boolean32>(MethodOffset.TfEnable); - - if (!tfEnable && _prevTfEnable) - { - _context.Renderer.Pipeline.EndTransformFeedback(); - _prevTfEnable = false; - } - - FlushUboDirty(state.Channel.MemoryManager); - - // Shaders must be the first one to be updated if modified, because - // some of the other state depends on information from the currently - // bound shaders. - if (state.QueryModified(MethodOffset.ShaderBaseAddress, MethodOffset.ShaderState) || _forceShaderUpdate) - { - _forceShaderUpdate = false; - - UpdateShaderState(state); - } - - if (state.QueryModified(MethodOffset.TfBufferState)) - { - UpdateTfBufferState(state); - } - - if (state.QueryModified(MethodOffset.ClipDistanceEnable)) - { - UpdateUserClipState(state); - } - - if (state.QueryModified(MethodOffset.RasterizeEnable)) - { - UpdateRasterizerState(state); - } - - if (state.QueryModified(MethodOffset.RtColorState, - MethodOffset.RtDepthStencilState, - MethodOffset.RtControl, - MethodOffset.RtDepthStencilSize, - MethodOffset.RtDepthStencilEnable)) - { - UpdateRenderTargetState(state, useControl: true); - } - - if (state.QueryModified(MethodOffset.ScissorState)) - { - UpdateScissorState(state); - } - - if (state.QueryModified(MethodOffset.ViewVolumeClipControl)) - { - UpdateDepthClampState(state); - } - - if (state.QueryModified(MethodOffset.AlphaTestEnable, - MethodOffset.AlphaTestRef, - MethodOffset.AlphaTestFunc)) - { - UpdateAlphaTestState(state); - } - - if (state.QueryModified(MethodOffset.DepthTestEnable, - MethodOffset.DepthWriteEnable, - MethodOffset.DepthTestFunc)) - { - UpdateDepthTestState(state); - } - - if (state.QueryModified(MethodOffset.DepthMode, - MethodOffset.ViewportTransform, - MethodOffset.ViewportExtents)) - { - UpdateViewportTransform(state); - } - - if (state.QueryModified(MethodOffset.DepthBiasState, - MethodOffset.DepthBiasFactor, - MethodOffset.DepthBiasUnits, - MethodOffset.DepthBiasClamp)) - { - UpdateDepthBiasState(state); - } - - if (state.QueryModified(MethodOffset.StencilBackMasks, - MethodOffset.StencilTestState, - MethodOffset.StencilBackTestState)) - { - UpdateStencilTestState(state); - } - - // Pools. - if (state.QueryModified(MethodOffset.SamplerPoolState, MethodOffset.SamplerIndex)) - { - UpdateSamplerPoolState(state); - } - - if (state.QueryModified(MethodOffset.TexturePoolState)) - { - UpdateTexturePoolState(state); - } - - // Rasterizer state. - if (state.QueryModified(MethodOffset.VertexAttribState)) - { - UpdateVertexAttribState(state); - } - - if (state.QueryModified(MethodOffset.LineWidthSmooth, MethodOffset.LineSmoothEnable)) - { - UpdateLineState(state); - } - - if (state.QueryModified(MethodOffset.PointSize, - MethodOffset.VertexProgramPointSize, - MethodOffset.PointSpriteEnable, - MethodOffset.PointCoordReplace)) - { - UpdatePointState(state); - } - - if (state.QueryModified(MethodOffset.PrimitiveRestartState)) - { - UpdatePrimitiveRestartState(state); - } - - if (state.QueryModified(MethodOffset.IndexBufferState)) - { - UpdateIndexBufferState(state, firstIndex, indexCount); - } - - if (state.QueryModified(MethodOffset.VertexBufferDrawState, - MethodOffset.VertexBufferInstanced, - MethodOffset.VertexBufferState, - MethodOffset.VertexBufferEndAddress)) - { - UpdateVertexBufferState(state); - } - - if (state.QueryModified(MethodOffset.FaceState)) - { - UpdateFaceState(state); - } - - if (state.QueryModified(MethodOffset.RtColorMaskShared, MethodOffset.RtColorMask)) - { - UpdateRtColorMask(state); - } - - if (state.QueryModified(MethodOffset.BlendIndependent, - MethodOffset.BlendConstant, - MethodOffset.BlendStateCommon, - MethodOffset.BlendEnableCommon, - MethodOffset.BlendEnable, - MethodOffset.BlendState)) - { - UpdateBlendState(state); - } - - if (state.QueryModified(MethodOffset.LogicOpState)) - { - UpdateLogicOpState(state); - } - - CommitBindings(state); - - if (tfEnable && !_prevTfEnable) - { - _context.Renderer.Pipeline.BeginTransformFeedback(Topology); - _prevTfEnable = true; - } - } - - /// <summary> - /// Updates Rasterizer primitive discard state based on guest gpu state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateRasterizerState(GpuState state) - { - Boolean32 enable = state.Get<Boolean32>(MethodOffset.RasterizeEnable); - _context.Renderer.Pipeline.SetRasterizerDiscard(!enable); - } - - /// <summary> - /// Ensures that the bindings are visible to the host GPU. - /// Note: this actually performs the binding using the host graphics API. - /// </summary> - /// <param name="state">Current GPU state</param> - private void CommitBindings(GpuState state) - { - UpdateStorageBuffers(state); - - state.Channel.TextureManager.CommitGraphicsBindings(); - state.Channel.BufferManager.CommitGraphicsBindings(); - } - - /// <summary> - /// Updates storage buffer bindings. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateStorageBuffers(GpuState state) - { - for (int stage = 0; stage < _currentProgramInfo.Length; stage++) - { - ShaderProgramInfo info = _currentProgramInfo[stage]; - - if (info == null) - { - continue; - } - - for (int index = 0; index < info.SBuffers.Count; index++) - { - BufferDescriptor sb = info.SBuffers[index]; - - ulong sbDescAddress = state.Channel.BufferManager.GetGraphicsUniformBufferAddress(stage, 0); - - int sbDescOffset = 0x110 + stage * 0x100 + sb.Slot * 0x10; - - sbDescAddress += (ulong)sbDescOffset; - - SbDescriptor sbDescriptor = state.Channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress); - - state.Channel.BufferManager.SetGraphicsStorageBuffer(stage, sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size, sb.Flags); - } - } - } - - /// <summary> - /// Updates render targets (color and depth-stencil buffers) based on current render target state. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="useControl">Use draw buffers information from render target control register</param> - /// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param> - private void UpdateRenderTargetState(GpuState state, bool useControl, int singleUse = -1) - { - var memoryManager = state.Channel.MemoryManager; - var rtControl = state.Get<RtControl>(MethodOffset.RtControl); - - int count = useControl ? rtControl.UnpackCount() : Constants.TotalRenderTargets; - - var msaaMode = state.Get<TextureMsaaMode>(MethodOffset.RtMsaaMode); - - int samplesInX = msaaMode.SamplesInX(); - int samplesInY = msaaMode.SamplesInY(); - - var scissor = state.Get<ScreenScissorState>(MethodOffset.ScreenScissorState); - Size sizeHint = new Size(scissor.X + scissor.Width, scissor.Y + scissor.Height, 1); - - bool changedScale = false; - - for (int index = 0; index < Constants.TotalRenderTargets; index++) - { - int rtIndex = useControl ? rtControl.UnpackPermutationIndex(index) : index; - - var colorState = state.Get<RtColorState>(MethodOffset.RtColorState, rtIndex); - - if (index >= count || !IsRtEnabled(colorState)) - { - changedScale |= state.Channel.TextureManager.SetRenderTargetColor(index, null); - - continue; - } - - Texture color = memoryManager.Physical.TextureCache.FindOrCreateTexture( - memoryManager, - colorState, - samplesInX, - samplesInY, - sizeHint); - - changedScale |= state.Channel.TextureManager.SetRenderTargetColor(index, color); - } - - bool dsEnable = state.Get<Boolean32>(MethodOffset.RtDepthStencilEnable); - - Texture depthStencil = null; - - if (dsEnable) - { - var dsState = state.Get<RtDepthStencilState>(MethodOffset.RtDepthStencilState); - var dsSize = state.Get<Size3D>(MethodOffset.RtDepthStencilSize); - - depthStencil = memoryManager.Physical.TextureCache.FindOrCreateTexture( - memoryManager, - dsState, - dsSize, - samplesInX, - samplesInY, - sizeHint); - } - - changedScale |= state.Channel.TextureManager.SetRenderTargetDepthStencil(depthStencil); - - if (changedScale) - { - state.Channel.TextureManager.UpdateRenderTargetScale(singleUse); - _context.Renderer.Pipeline.SetRenderTargetScale(state.Channel.TextureManager.RenderTargetScale); - - UpdateViewportTransform(state); - UpdateScissorState(state); - } - } - - /// <summary> - /// Checks if a render target color buffer is used. - /// </summary> - /// <param name="colorState">Color buffer information</param> - /// <returns>True if the specified buffer is enabled/used, false otherwise</returns> - private static bool IsRtEnabled(RtColorState colorState) - { - // Colors are disabled by writing 0 to the format. - return colorState.Format != 0 && colorState.WidthOrStride != 0; - } - - /// <summary> - /// Updates host scissor test state based on current GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateScissorState(GpuState state) - { - for (int index = 0; index < Constants.TotalViewports; index++) - { - ScissorState scissor = state.Get<ScissorState>(MethodOffset.ScissorState, index); - - bool enable = scissor.Enable && (scissor.X1 != 0 || scissor.Y1 != 0 || scissor.X2 != 0xffff || scissor.Y2 != 0xffff); - - if (enable) - { - int x = scissor.X1; - int y = scissor.Y1; - int width = scissor.X2 - x; - int height = scissor.Y2 - y; - - float scale = state.Channel.TextureManager.RenderTargetScale; - if (scale != 1f) - { - x = (int)(x * scale); - y = (int)(y * scale); - width = (int)Math.Ceiling(width * scale); - height = (int)Math.Ceiling(height * scale); - } - - _context.Renderer.Pipeline.SetScissor(index, true, x, y, width, height); - } - else - { - _context.Renderer.Pipeline.SetScissor(index, false, 0, 0, 0, 0); - } - } - } - - /// <summary> - /// Updates host depth clamp state based on current GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateDepthClampState(GpuState state) - { - ViewVolumeClipControl clip = state.Get<ViewVolumeClipControl>(MethodOffset.ViewVolumeClipControl); - _context.Renderer.Pipeline.SetDepthClamp((clip & ViewVolumeClipControl.DepthClampDisabled) == 0); - } - - /// <summary> - /// Updates host alpha test state based on current GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateAlphaTestState(GpuState state) - { - _context.Renderer.Pipeline.SetAlphaTest( - state.Get<Boolean32>(MethodOffset.AlphaTestEnable), - state.Get<float>(MethodOffset.AlphaTestRef), - state.Get<CompareOp>(MethodOffset.AlphaTestFunc)); - } - - /// <summary> - /// Updates host depth test state based on current GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateDepthTestState(GpuState state) - { - _context.Renderer.Pipeline.SetDepthTest(new DepthTestDescriptor( - state.Get<Boolean32>(MethodOffset.DepthTestEnable), - state.Get<Boolean32>(MethodOffset.DepthWriteEnable), - state.Get<CompareOp>(MethodOffset.DepthTestFunc))); - } - - /// <summary> - /// Updates host viewport transform and clipping state based on current GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateViewportTransform(GpuState state) - { - var yControl = state.Get<YControl> (MethodOffset.YControl); - var face = state.Get<FaceState>(MethodOffset.FaceState); - - UpdateFrontFace(yControl, face.FrontFace); - - bool flipY = yControl.HasFlag(YControl.NegateY); - - Span<Viewport> viewports = stackalloc Viewport[Constants.TotalViewports]; - - for (int index = 0; index < Constants.TotalViewports; index++) - { - var transform = state.Get<ViewportTransform>(MethodOffset.ViewportTransform, index); - var extents = state.Get<ViewportExtents> (MethodOffset.ViewportExtents, index); - - float scaleX = MathF.Abs(transform.ScaleX); - float scaleY = transform.ScaleY; - - if (flipY) - { - scaleY = -scaleY; - } - - if (!_context.Capabilities.SupportsViewportSwizzle && transform.UnpackSwizzleY() == ViewportSwizzle.NegativeY) - { - scaleY = -scaleY; - } - - if (index == 0) - { - // Try to guess the depth mode being used on the high level API - // based on current transform. - // It is setup like so by said APIs: - // If depth mode is ZeroToOne: - // TranslateZ = Near - // ScaleZ = Far - Near - // If depth mode is MinusOneToOne: - // TranslateZ = (Near + Far) / 2 - // ScaleZ = (Far - Near) / 2 - // DepthNear/Far are sorted such as that Near is always less than Far. - DepthMode depthMode = extents.DepthNear != transform.TranslateZ && - extents.DepthFar != transform.TranslateZ ? DepthMode.MinusOneToOne : DepthMode.ZeroToOne; - - _context.Renderer.Pipeline.SetDepthMode(depthMode); - } - - float x = transform.TranslateX - scaleX; - float y = transform.TranslateY - scaleY; - - float width = scaleX * 2; - float height = scaleY * 2; - - float scale = state.Channel.TextureManager.RenderTargetScale; - if (scale != 1f) - { - x *= scale; - y *= scale; - width *= scale; - height *= scale; - } - - RectangleF region = new RectangleF(x, y, width, height); - - ViewportSwizzle swizzleX = transform.UnpackSwizzleX(); - ViewportSwizzle swizzleY = transform.UnpackSwizzleY(); - ViewportSwizzle swizzleZ = transform.UnpackSwizzleZ(); - ViewportSwizzle swizzleW = transform.UnpackSwizzleW(); - - float depthNear = extents.DepthNear; - float depthFar = extents.DepthFar; - - if (transform.ScaleZ < 0) - { - float temp = depthNear; - depthNear = depthFar; - depthFar = temp; - } - - viewports[index] = new Viewport(region, swizzleX, swizzleY, swizzleZ, swizzleW, depthNear, depthFar); - } - - _context.Renderer.Pipeline.SetViewports(0, viewports); - } - - /// <summary> - /// Updates host depth bias (also called polygon offset) state based on current GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateDepthBiasState(GpuState state) - { - var depthBias = state.Get<DepthBiasState>(MethodOffset.DepthBiasState); - - float factor = state.Get<float>(MethodOffset.DepthBiasFactor); - float units = state.Get<float>(MethodOffset.DepthBiasUnits); - float clamp = state.Get<float>(MethodOffset.DepthBiasClamp); - - PolygonModeMask enables; - - enables = (depthBias.PointEnable ? PolygonModeMask.Point : 0); - enables |= (depthBias.LineEnable ? PolygonModeMask.Line : 0); - enables |= (depthBias.FillEnable ? PolygonModeMask.Fill : 0); - - _context.Renderer.Pipeline.SetDepthBias(enables, factor, units / 2f, clamp); - } - - /// <summary> - /// Updates host stencil test state based on current GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateStencilTestState(GpuState state) - { - var backMasks = state.Get<StencilBackMasks>(MethodOffset.StencilBackMasks); - var test = state.Get<StencilTestState>(MethodOffset.StencilTestState); - var backTest = state.Get<StencilBackTestState>(MethodOffset.StencilBackTestState); - - CompareOp backFunc; - StencilOp backSFail; - StencilOp backDpPass; - StencilOp backDpFail; - int backFuncRef; - int backFuncMask; - int backMask; - - if (backTest.TwoSided) - { - backFunc = backTest.BackFunc; - backSFail = backTest.BackSFail; - backDpPass = backTest.BackDpPass; - backDpFail = backTest.BackDpFail; - backFuncRef = backMasks.FuncRef; - backFuncMask = backMasks.FuncMask; - backMask = backMasks.Mask; - } - else - { - backFunc = test.FrontFunc; - backSFail = test.FrontSFail; - backDpPass = test.FrontDpPass; - backDpFail = test.FrontDpFail; - backFuncRef = test.FrontFuncRef; - backFuncMask = test.FrontFuncMask; - backMask = test.FrontMask; - } - - _context.Renderer.Pipeline.SetStencilTest(new StencilTestDescriptor( - test.Enable, - test.FrontFunc, - test.FrontSFail, - test.FrontDpPass, - test.FrontDpFail, - test.FrontFuncRef, - test.FrontFuncMask, - test.FrontMask, - backFunc, - backSFail, - backDpPass, - backDpFail, - backFuncRef, - backFuncMask, - backMask)); - } - - /// <summary> - /// Updates current sampler pool address and size based on guest GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateSamplerPoolState(GpuState state) - { - var texturePool = state.Get<PoolState>(MethodOffset.TexturePoolState); - var samplerPool = state.Get<PoolState>(MethodOffset.SamplerPoolState); - - var samplerIndex = state.Get<SamplerIndex>(MethodOffset.SamplerIndex); - - int maximumId = samplerIndex == SamplerIndex.ViaHeaderIndex - ? texturePool.MaximumId - : samplerPool.MaximumId; - - state.Channel.TextureManager.SetGraphicsSamplerPool(samplerPool.Address.Pack(), maximumId, samplerIndex); - } - - /// <summary> - /// Updates current texture pool address and size based on guest GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateTexturePoolState(GpuState state) - { - var texturePool = state.Get<PoolState>(MethodOffset.TexturePoolState); - - state.Channel.TextureManager.SetGraphicsTexturePool(texturePool.Address.Pack(), texturePool.MaximumId); - state.Channel.TextureManager.SetGraphicsTextureBufferIndex(state.Get<int>(MethodOffset.TextureBufferIndex)); - } - - /// <summary> - /// Updates host vertex attributes based on guest GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateVertexAttribState(GpuState state) - { - Span<VertexAttribDescriptor> vertexAttribs = stackalloc VertexAttribDescriptor[Constants.TotalVertexAttribs]; - - for (int index = 0; index < Constants.TotalVertexAttribs; index++) - { - var vertexAttrib = state.Get<VertexAttribState>(MethodOffset.VertexAttribState, index); - - if (!FormatTable.TryGetAttribFormat(vertexAttrib.UnpackFormat(), out Format format)) - { - Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}."); - - format = Format.R32G32B32A32Float; - } - - vertexAttribs[index] = new VertexAttribDescriptor( - vertexAttrib.UnpackBufferIndex(), - vertexAttrib.UnpackOffset(), - vertexAttrib.UnpackIsConstant(), - format); - } - - _context.Renderer.Pipeline.SetVertexAttribs(vertexAttribs); - } - - /// <summary> - /// Updates host line width based on guest GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateLineState(GpuState state) - { - float width = state.Get<float>(MethodOffset.LineWidthSmooth); - bool smooth = state.Get<Boolean32>(MethodOffset.LineSmoothEnable); - - _context.Renderer.Pipeline.SetLineParameters(width, smooth); - } - - /// <summary> - /// Updates host point size based on guest GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdatePointState(GpuState state) - { - float size = state.Get<float>(MethodOffset.PointSize); - bool isProgramPointSize = state.Get<Boolean32>(MethodOffset.VertexProgramPointSize); - bool enablePointSprite = state.Get<Boolean32>(MethodOffset.PointSpriteEnable); - - // TODO: Need to figure out a way to map PointCoordReplace enable bit. - Origin origin = (state.Get<int>(MethodOffset.PointCoordReplace) & 4) == 0 ? Origin.LowerLeft : Origin.UpperLeft; - - _context.Renderer.Pipeline.SetPointParameters(size, isProgramPointSize, enablePointSprite, origin); - } - - /// <summary> - /// Updates host primitive restart based on guest GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdatePrimitiveRestartState(GpuState state) - { - PrimitiveRestartState primitiveRestart = state.Get<PrimitiveRestartState>(MethodOffset.PrimitiveRestartState); - - _context.Renderer.Pipeline.SetPrimitiveRestart( - primitiveRestart.Enable, - primitiveRestart.Index); - } - - /// <summary> - /// Updates host index buffer binding based on guest GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - /// <param name="firstIndex">Index of the first index buffer element used on the draw</param> - /// <param name="indexCount">Number of index buffer elements used on the draw</param> - private void UpdateIndexBufferState(GpuState state, int firstIndex, int indexCount) - { - var indexBuffer = state.Get<IndexBufferState>(MethodOffset.IndexBufferState); - - if (indexCount == 0) - { - return; - } - - ulong gpuVa = indexBuffer.Address.Pack(); - - // Do not use the end address to calculate the size, because - // the result may be much larger than the real size of the index buffer. - ulong size = (ulong)(firstIndex + indexCount); - - switch (indexBuffer.Type) - { - case IndexType.UShort: size *= 2; break; - case IndexType.UInt: size *= 4; break; - } - - state.Channel.BufferManager.SetIndexBuffer(gpuVa, size, indexBuffer.Type); - - // The index buffer affects the vertex buffer size calculation, we - // need to ensure that they are updated. - UpdateVertexBufferState(state); - } - - /// <summary> - /// Updates host vertex buffer bindings based on guest GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateVertexBufferState(GpuState state) - { - _isAnyVbInstanced = false; - - for (int index = 0; index < Constants.TotalVertexBuffers; index++) - { - var vertexBuffer = state.Get<VertexBufferState>(MethodOffset.VertexBufferState, index); - - if (!vertexBuffer.UnpackEnable()) - { - state.Channel.BufferManager.SetVertexBuffer(index, 0, 0, 0, 0); - - continue; - } - - GpuVa endAddress = state.Get<GpuVa>(MethodOffset.VertexBufferEndAddress, index); - - ulong address = vertexBuffer.Address.Pack(); - - int stride = vertexBuffer.UnpackStride(); - - bool instanced = state.Get<Boolean32>(MethodOffset.VertexBufferInstanced + index); - - int divisor = instanced ? vertexBuffer.Divisor : 0; - - _isAnyVbInstanced |= divisor != 0; - - ulong size; - - if (_ibStreamer.HasInlineIndexData || _drawIndexed || stride == 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 = endAddress.Pack() - address + 1; - } - else - { - // For non-indexed draws, we can guess the size from the vertex count - // and stride. - int firstInstance = state.Get<int>(MethodOffset.FirstInstance); - - var drawState = state.Get<VertexBufferDrawState>(MethodOffset.VertexBufferDrawState); - - size = (ulong)((firstInstance + drawState.First + drawState.Count) * stride); - } - - state.Channel.BufferManager.SetVertexBuffer(index, address, size, stride, divisor); - } - } - - /// <summary> - /// Updates host face culling and orientation based on guest GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateFaceState(GpuState state) - { - var yControl = state.Get<YControl> (MethodOffset.YControl); - var face = state.Get<FaceState>(MethodOffset.FaceState); - - _context.Renderer.Pipeline.SetFaceCulling(face.CullEnable, face.CullFace); - - UpdateFrontFace(yControl, face.FrontFace); - } - - /// <summary> - /// Updates the front face based on the current front face and the origin. - /// </summary> - /// <param name="yControl">Y control register value, where the origin is located</param> - /// <param name="frontFace">Front face</param> - private void UpdateFrontFace(YControl yControl, FrontFace frontFace) - { - bool isUpperLeftOrigin = !yControl.HasFlag(YControl.TriangleRastFlip); - - if (isUpperLeftOrigin) - { - frontFace = frontFace == FrontFace.CounterClockwise ? FrontFace.Clockwise : FrontFace.CounterClockwise; - } - - _context.Renderer.Pipeline.SetFrontFace(frontFace); - } - - /// <summary> - /// Updates host render target color masks, based on guest GPU state. - /// This defines which color channels are written to each color buffer. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateRtColorMask(GpuState state) - { - bool rtColorMaskShared = state.Get<Boolean32>(MethodOffset.RtColorMaskShared); - - Span<uint> componentMasks = stackalloc uint[Constants.TotalRenderTargets]; - - for (int index = 0; index < Constants.TotalRenderTargets; index++) - { - var colorMask = state.Get<RtColorMask>(MethodOffset.RtColorMask, rtColorMaskShared ? 0 : index); - - uint componentMask; - - componentMask = (colorMask.UnpackRed() ? 1u : 0u); - componentMask |= (colorMask.UnpackGreen() ? 2u : 0u); - componentMask |= (colorMask.UnpackBlue() ? 4u : 0u); - componentMask |= (colorMask.UnpackAlpha() ? 8u : 0u); - - componentMasks[index] = componentMask; - } - - _context.Renderer.Pipeline.SetRenderTargetColorMasks(componentMasks); - } - - /// <summary> - /// Updates host render target color buffer blending state, based on guest state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateBlendState(GpuState state) - { - bool blendIndependent = state.Get<Boolean32>(MethodOffset.BlendIndependent); - ColorF blendConstant = state.Get<ColorF>(MethodOffset.BlendConstant); - - for (int index = 0; index < Constants.TotalRenderTargets; index++) - { - BlendDescriptor descriptor; - - if (blendIndependent) - { - bool enable = state.Get<Boolean32> (MethodOffset.BlendEnable, index); - var blend = state.Get<BlendState>(MethodOffset.BlendState, index); - - descriptor = new BlendDescriptor( - enable, - blendConstant, - blend.ColorOp, - blend.ColorSrcFactor, - blend.ColorDstFactor, - blend.AlphaOp, - blend.AlphaSrcFactor, - blend.AlphaDstFactor); - } - else - { - bool enable = state.Get<Boolean32> (MethodOffset.BlendEnable, 0); - var blend = state.Get<BlendStateCommon>(MethodOffset.BlendStateCommon); - - descriptor = new BlendDescriptor( - enable, - blendConstant, - blend.ColorOp, - blend.ColorSrcFactor, - blend.ColorDstFactor, - blend.AlphaOp, - blend.AlphaSrcFactor, - blend.AlphaDstFactor); - } - - _context.Renderer.Pipeline.SetBlendState(index, descriptor); - } - } - - /// <summary> - /// Updates host logical operation state, based on guest state. - /// </summary> - /// <param name="state">Current GPU state</param> - public void UpdateLogicOpState(GpuState state) - { - LogicalOpState logicOpState = state.Get<LogicalOpState>(MethodOffset.LogicOpState); - - _context.Renderer.Pipeline.SetLogicOpState(logicOpState.Enable, logicOpState.LogicalOp); - } - - /// <summary> - /// Updates host shaders based on the guest GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateShaderState(GpuState state) - { - ShaderAddresses addresses = new ShaderAddresses(); - - Span<ShaderAddresses> addressesSpan = MemoryMarshal.CreateSpan(ref addresses, 1); - - Span<ulong> addressesArray = MemoryMarshal.Cast<ShaderAddresses, ulong>(addressesSpan); - - ulong baseAddress = state.Get<GpuVa>(MethodOffset.ShaderBaseAddress).Pack(); - - for (int index = 0; index < 6; index++) - { - var shader = state.Get<ShaderState>(MethodOffset.ShaderState, index); - - if (!shader.UnpackEnable() && index != 1) - { - continue; - } - - addressesArray[index] = baseAddress + shader.Offset; - } - - ShaderBundle gs = state.Channel.MemoryManager.Physical.ShaderCache.GetGraphicsShader(state, addresses); - - byte oldVsClipDistancesWritten = _vsClipDistancesWritten; - - _vsUsesInstanceId = gs.Shaders[0]?.Info.UsesInstanceId ?? false; - _vsClipDistancesWritten = gs.Shaders[0]?.Info.ClipDistancesWritten ?? 0; - - if (oldVsClipDistancesWritten != _vsClipDistancesWritten) - { - UpdateUserClipState(state); - } - - int storageBufferBindingsCount = 0; - int uniformBufferBindingsCount = 0; - - for (int stage = 0; stage < Constants.ShaderStages; stage++) - { - ShaderProgramInfo info = gs.Shaders[stage]?.Info; - - _currentProgramInfo[stage] = info; - - if (info == null) - { - state.Channel.TextureManager.SetGraphicsTextures(stage, Array.Empty<TextureBindingInfo>()); - state.Channel.TextureManager.SetGraphicsImages(stage, Array.Empty<TextureBindingInfo>()); - state.Channel.BufferManager.SetGraphicsStorageBufferBindings(stage, null); - state.Channel.BufferManager.SetGraphicsUniformBufferBindings(stage, null); - continue; - } - - var textureBindings = new TextureBindingInfo[info.Textures.Count]; - - for (int index = 0; index < info.Textures.Count; index++) - { - var descriptor = info.Textures[index]; - - Target target = ShaderTexture.GetTarget(descriptor.Type); - - textureBindings[index] = new TextureBindingInfo( - target, - descriptor.Binding, - descriptor.CbufSlot, - descriptor.HandleIndex, - descriptor.Flags); - } - - state.Channel.TextureManager.SetGraphicsTextures(stage, textureBindings); - - var imageBindings = new TextureBindingInfo[info.Images.Count]; - - for (int index = 0; index < info.Images.Count; index++) - { - var descriptor = info.Images[index]; - - Target target = ShaderTexture.GetTarget(descriptor.Type); - Format format = ShaderTexture.GetFormat(descriptor.Format); - - imageBindings[index] = new TextureBindingInfo( - target, - format, - descriptor.Binding, - descriptor.CbufSlot, - descriptor.HandleIndex, - descriptor.Flags); - } - - state.Channel.TextureManager.SetGraphicsImages(stage, imageBindings); - - state.Channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers); - state.Channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers); - - if (info.SBuffers.Count != 0) - { - storageBufferBindingsCount = Math.Max(storageBufferBindingsCount, info.SBuffers.Max(x => x.Binding) + 1); - } - - if (info.CBuffers.Count != 0) - { - uniformBufferBindingsCount = Math.Max(uniformBufferBindingsCount, info.CBuffers.Max(x => x.Binding) + 1); - } - } - - state.Channel.BufferManager.SetGraphicsStorageBufferBindingsCount(storageBufferBindingsCount); - state.Channel.BufferManager.SetGraphicsUniformBufferBindingsCount(uniformBufferBindingsCount); - - _context.Renderer.Pipeline.SetProgram(gs.HostProgram); - } - - /// <summary> - /// Forces the shaders to be rebound on the next draw. - /// </summary> - public void ForceShaderUpdate() - { - _forceShaderUpdate = true; - } - - /// <summary> - /// Updates transform feedback buffer state based on the guest GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateTfBufferState(GpuState state) - { - for (int index = 0; index < Constants.TotalTransformFeedbackBuffers; index++) - { - TfBufferState tfb = state.Get<TfBufferState>(MethodOffset.TfBufferState, index); - - if (!tfb.Enable) - { - state.Channel.BufferManager.SetTransformFeedbackBuffer(index, 0, 0); - - continue; - } - - state.Channel.BufferManager.SetTransformFeedbackBuffer(index, tfb.Address.Pack(), (uint)tfb.Size); - } - } - - /// <summary> - /// Updates user-defined clipping based on the guest GPU state. - /// </summary> - /// <param name="state">Current GPU state</param> - private void UpdateUserClipState(GpuState state) - { - int clipMask = state.Get<int>(MethodOffset.ClipDistanceEnable) & _vsClipDistancesWritten; - - for (int i = 0; i < Constants.TotalClipDistances; ++i) - { - _context.Renderer.Pipeline.SetUserClipDistance(i, (clipMask & (1 << i)) != 0); - } - } - - /// <summary> - /// Issues a texture barrier. - /// This waits until previous texture writes from the GPU to finish, before - /// performing new operations with said textures. - /// </summary> - /// <param name="state">Current GPU state (unused)</param> - /// <param name="argument">Method call argument (unused)</param> - private void TextureBarrier(GpuState state, int argument) - { - _context.Renderer.Pipeline.TextureBarrier(); - } - - /// <summary> - /// Issues a texture barrier. - /// This waits until previous texture writes from the GPU to finish, before - /// performing new operations with said textures. - /// This performs a per-tile wait, it is only valid if both the previous write - /// and current access has the same access patterns. - /// This may be faster than the regular barrier on tile-based rasterizers. - /// </summary> - /// <param name="state">Current GPU state (unused)</param> - /// <param name="argument">Method call argument (unused)</param> - private void TextureBarrierTiled(GpuState state, int argument) - { - _context.Renderer.Pipeline.TextureBarrierTiled(); - } - } -}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MmeShadowScratch.cs b/Ryujinx.Graphics.Gpu/Engine/MmeShadowScratch.cs index df9021e0..a3295616 100644 --- a/Ryujinx.Graphics.Gpu/Engine/MmeShadowScratch.cs +++ b/Ryujinx.Graphics.Gpu/Engine/MmeShadowScratch.cs @@ -3,6 +3,9 @@ using System.Runtime.InteropServices; namespace Ryujinx.Graphics.Gpu.Engine { + /// <summary> + /// Represents temporary storage used by macros. + /// </summary> [StructLayout(LayoutKind.Sequential, Size = 1024)] struct MmeShadowScratch { diff --git a/Ryujinx.Graphics.Gpu/Engine/SetMmeShadowRamControlMode.cs b/Ryujinx.Graphics.Gpu/Engine/SetMmeShadowRamControlMode.cs new file mode 100644 index 00000000..060d35ca --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/SetMmeShadowRamControlMode.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Gpu.Engine +{ + /// <summary> + /// MME shadow RAM control mode. + /// </summary> + enum SetMmeShadowRamControlMode + { + MethodTrack = 0, + MethodTrackWithFilter = 1, + MethodPassthrough = 2, + MethodReplay = 3, + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodConditionalRendering.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ConditionalRendering.cs index 039ed78e..85f66985 100644 --- a/Ryujinx.Graphics.Gpu/Engine/MethodConditionalRendering.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ConditionalRendering.cs @@ -1,37 +1,41 @@ using Ryujinx.Common.Logging; using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Memory; -using Ryujinx.Graphics.Gpu.State; -namespace Ryujinx.Graphics.Gpu.Engine +namespace Ryujinx.Graphics.Gpu.Engine.Threed { - partial class Methods + /// <summary> + /// Helper methods used for conditional rendering. + /// </summary> + static class ConditionalRendering { /// <summary> /// Checks if draws and clears should be performed, according /// to currently set conditional rendering conditions. /// </summary> - /// <param name="state">GPU state</param> + /// <param name="context">GPU context</param> + /// <param name="memoryManager">Memory manager bound to the channel currently executing</param> + /// <param name="address">Conditional rendering buffer address</param> + /// <param name="condition">Conditional rendering condition</param> /// <returns>True if rendering is enabled, false otherwise</returns> - private ConditionalRenderEnabled GetRenderEnable(GpuState state) + public static ConditionalRenderEnabled GetRenderEnable(GpuContext context, MemoryManager memoryManager, GpuVa address, Condition condition) { - ConditionState condState = state.Get<ConditionState>(MethodOffset.ConditionState); - - switch (condState.Condition) + switch (condition) { case Condition.Always: return ConditionalRenderEnabled.True; case Condition.Never: return ConditionalRenderEnabled.False; case Condition.ResultNonZero: - return CounterNonZero(state, condState.Address.Pack()); + return CounterNonZero(context, memoryManager, address.Pack()); case Condition.Equal: - return CounterCompare(state, condState.Address.Pack(), true); + return CounterCompare(context, memoryManager, address.Pack(), true); case Condition.NotEqual: - return CounterCompare(state, condState.Address.Pack(), false); + return CounterCompare(context, memoryManager, address.Pack(), false); } - Logger.Warning?.Print(LogClass.Gpu, $"Invalid conditional render condition \"{condState.Condition}\"."); + Logger.Warning?.Print(LogClass.Gpu, $"Invalid conditional render condition \"{condition}\"."); return ConditionalRenderEnabled.True; } @@ -39,54 +43,56 @@ namespace Ryujinx.Graphics.Gpu.Engine /// <summary> /// Checks if the counter value at a given GPU memory address is non-zero. /// </summary> - /// <param name="state">GPU state</param> + /// <param name="context">GPU context</param> + /// <param name="memoryManager">Memory manager bound to the channel currently executing</param> /// <param name="gpuVa">GPU virtual address of the counter value</param> /// <returns>True if the value is not zero, false otherwise. Returns host if handling with host conditional rendering</returns> - private ConditionalRenderEnabled CounterNonZero(GpuState state, ulong gpuVa) + private static ConditionalRenderEnabled CounterNonZero(GpuContext context, MemoryManager memoryManager, ulong gpuVa) { - ICounterEvent evt = state.Channel.MemoryManager.CounterCache.FindEvent(gpuVa); + ICounterEvent evt = memoryManager.CounterCache.FindEvent(gpuVa); if (evt == null) { return ConditionalRenderEnabled.False; } - if (_context.Renderer.Pipeline.TryHostConditionalRendering(evt, 0L, false)) + if (context.Renderer.Pipeline.TryHostConditionalRendering(evt, 0L, false)) { return ConditionalRenderEnabled.Host; } else { evt.Flush(); - return (state.Channel.MemoryManager.Read<ulong>(gpuVa) != 0) ? ConditionalRenderEnabled.True : ConditionalRenderEnabled.False; + return (memoryManager.Read<ulong>(gpuVa) != 0) ? ConditionalRenderEnabled.True : ConditionalRenderEnabled.False; } } /// <summary> /// Checks if the counter at a given GPU memory address passes a specified equality comparison. /// </summary> - /// <param name="state">GPU state</param> + /// <param name="context">GPU context</param> + /// <param name="memoryManager">Memory manager bound to the channel currently executing</param> /// <param name="gpuVa">GPU virtual address</param> /// <param name="isEqual">True to check if the values are equal, false to check if they are not equal</param> /// <returns>True if the condition is met, false otherwise. Returns host if handling with host conditional rendering</returns> - private ConditionalRenderEnabled CounterCompare(GpuState state, ulong gpuVa, bool isEqual) + private static ConditionalRenderEnabled CounterCompare(GpuContext context, MemoryManager memoryManager, ulong gpuVa, bool isEqual) { - ICounterEvent evt = FindEvent(state.Channel.MemoryManager.CounterCache, gpuVa); - ICounterEvent evt2 = FindEvent(state.Channel.MemoryManager.CounterCache, gpuVa + 16); + ICounterEvent evt = FindEvent(memoryManager.CounterCache, gpuVa); + ICounterEvent evt2 = FindEvent(memoryManager.CounterCache, gpuVa + 16); bool useHost; if (evt != null && evt2 == null) { - useHost = _context.Renderer.Pipeline.TryHostConditionalRendering(evt, state.Channel.MemoryManager.Read<ulong>(gpuVa + 16), isEqual); + useHost = context.Renderer.Pipeline.TryHostConditionalRendering(evt, memoryManager.Read<ulong>(gpuVa + 16), isEqual); } else if (evt == null && evt2 != null) { - useHost = _context.Renderer.Pipeline.TryHostConditionalRendering(evt2, state.Channel.MemoryManager.Read<ulong>(gpuVa), isEqual); + useHost = context.Renderer.Pipeline.TryHostConditionalRendering(evt2, memoryManager.Read<ulong>(gpuVa), isEqual); } else if (evt != null && evt2 != null) { - useHost = _context.Renderer.Pipeline.TryHostConditionalRendering(evt, evt2, isEqual); + useHost = context.Renderer.Pipeline.TryHostConditionalRendering(evt, evt2, isEqual); } else { @@ -102,8 +108,8 @@ namespace Ryujinx.Graphics.Gpu.Engine evt?.Flush(); evt2?.Flush(); - ulong x = state.Channel.MemoryManager.Read<ulong>(gpuVa); - ulong y = state.Channel.MemoryManager.Read<ulong>(gpuVa + 16); + ulong x = memoryManager.Read<ulong>(gpuVa); + ulong y = memoryManager.Read<ulong>(gpuVa + 16); return (isEqual ? x == y : x != y) ? ConditionalRenderEnabled.True : ConditionalRenderEnabled.False; } diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ConstantBufferUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ConstantBufferUpdater.cs new file mode 100644 index 00000000..f4006ba9 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ConstantBufferUpdater.cs @@ -0,0 +1,173 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// <summary> + /// Constant buffer updater. + /// </summary> + class ConstantBufferUpdater + { + private readonly GpuChannel _channel; + private readonly DeviceStateWithShadow<ThreedClassState> _state; + + // State associated with direct uniform buffer updates. + // This state is used to attempt to batch together consecutive updates. + private ulong _ubBeginCpuAddress = 0; + private ulong _ubFollowUpAddress = 0; + private ulong _ubByteCount = 0; + + /// <summary> + /// Creates a new instance of the constant buffer updater. + /// </summary> + /// <param name="channel">GPU channel</param> + /// <param name="state">Channel state</param> + public ConstantBufferUpdater(GpuChannel channel, DeviceStateWithShadow<ThreedClassState> state) + { + _channel = channel; + _state = state; + } + + /// <summary> + /// Binds a uniform buffer for the vertex shader stage. + /// </summary> + /// <param name="argument">Method call argument</param> + public void BindVertex(int argument) + { + Bind(argument, ShaderType.Vertex); + } + + /// <summary> + /// Binds a uniform buffer for the tessellation control shader stage. + /// </summary> + /// <param name="argument">Method call argument</param> + public void BindTessControl(int argument) + { + Bind(argument, ShaderType.TessellationControl); + } + + /// <summary> + /// Binds a uniform buffer for the tessellation evaluation shader stage. + /// </summary> + /// <param name="argument">Method call argument</param> + public void BindTessEvaluation(int argument) + { + Bind(argument, ShaderType.TessellationEvaluation); + } + + /// <summary> + /// Binds a uniform buffer for the geometry shader stage. + /// </summary> + /// <param name="argument">Method call argument</param> + public void BindGeometry(int argument) + { + Bind(argument, ShaderType.Geometry); + } + + /// <summary> + /// Binds a uniform buffer for the fragment shader stage. + /// </summary> + /// <param name="argument">Method call argument</param> + public void BindFragment(int argument) + { + Bind(argument, ShaderType.Fragment); + } + + /// <summary> + /// Binds a uniform buffer for the specified shader stage. + /// </summary> + /// <param name="argument">Method call argument</param> + /// <param name="type">Shader stage that will access the uniform buffer</param> + private void Bind(int argument, ShaderType type) + { + bool enable = (argument & 1) != 0; + + int index = (argument >> 4) & 0x1f; + + FlushUboDirty(); + + if (enable) + { + var uniformBuffer = _state.State.UniformBufferState; + + ulong address = uniformBuffer.Address.Pack(); + + _channel.BufferManager.SetGraphicsUniformBuffer((int)type, index, address, (uint)uniformBuffer.Size); + } + else + { + _channel.BufferManager.SetGraphicsUniformBuffer((int)type, index, 0, 0); + } + } + + /// <summary> + /// Flushes any queued UBO updates. + /// </summary> + public void FlushUboDirty() + { + if (_ubFollowUpAddress != 0) + { + var memoryManager = _channel.MemoryManager; + memoryManager.Physical.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount); + + _ubFollowUpAddress = 0; + } + } + + /// <summary> + /// Updates the uniform buffer data with inline data. + /// </summary> + /// <param name="argument">New uniform buffer data word</param> + public void Update(int argument) + { + var uniformBuffer = _state.State.UniformBufferState; + + ulong address = uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset; + + if (_ubFollowUpAddress != address) + { + FlushUboDirty(); + + _ubByteCount = 0; + _ubBeginCpuAddress = _channel.MemoryManager.Translate(address); + } + + var byteData = MemoryMarshal.Cast<int, byte>(MemoryMarshal.CreateSpan(ref argument, 1)); + _channel.MemoryManager.Physical.WriteUntracked(_ubBeginCpuAddress + _ubByteCount, byteData); + + _ubFollowUpAddress = address + 4; + _ubByteCount += 4; + + _state.State.UniformBufferState.Offset += 4; + } + + /// <summary> + /// Updates the uniform buffer data with inline data. + /// </summary> + /// <param name="data">Data to be written to the uniform buffer</param> + public void Update(ReadOnlySpan<int> data) + { + var uniformBuffer = _state.State.UniformBufferState; + + ulong address = uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset; + + ulong size = (ulong)data.Length * 4; + + if (_ubFollowUpAddress != address) + { + FlushUboDirty(); + + _ubByteCount = 0; + _ubBeginCpuAddress = _channel.MemoryManager.Translate(address); + } + + var byteData = MemoryMarshal.Cast<int, byte>(data); + _channel.MemoryManager.Physical.WriteUntracked(_ubBeginCpuAddress + _ubByteCount, byteData); + + _ubFollowUpAddress = address + size; + _ubByteCount += size; + + _state.State.UniformBufferState.Offset += data.Length * 4; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs new file mode 100644 index 00000000..d58f175d --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs @@ -0,0 +1,410 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Types; +using System.Text; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// <summary> + /// Draw manager. + /// </summary> + class DrawManager + { + private readonly GpuContext _context; + private readonly GpuChannel _channel; + private readonly DeviceStateWithShadow<ThreedClassState> _state; + private readonly DrawState _drawState; + + private bool _instancedDrawPending; + private bool _instancedIndexed; + + private int _instancedFirstIndex; + private int _instancedFirstVertex; + private int _instancedFirstInstance; + private int _instancedIndexCount; + private int _instancedDrawStateFirst; + private int _instancedDrawStateCount; + + private int _instanceIndex; + + /// <summary> + /// Creates a new instance of the draw manager. + /// </summary> + /// <param name="context">GPU context</param> + /// <param name="channel">GPU channel</param> + /// <param name="state">Channel state</param> + /// <param name="drawState">Draw state</param> + public DrawManager(GpuContext context, GpuChannel channel, DeviceStateWithShadow<ThreedClassState> state, DrawState drawState) + { + _context = context; + _channel = channel; + _state = state; + _drawState = drawState; + } + + /// <summary> + /// Pushes four 8-bit index buffer elements. + /// </summary> + /// <param name="argument">Method call argument</param> + public void VbElementU8(int argument) + { + _drawState.IbStreamer.VbElementU8(_context.Renderer, argument); + } + + /// <summary> + /// Pushes two 16-bit index buffer elements. + /// </summary> + /// <param name="argument">Method call argument</param> + public void VbElementU16(int argument) + { + _drawState.IbStreamer.VbElementU16(_context.Renderer, argument); + } + + /// <summary> + /// Pushes one 32-bit index buffer element. + /// </summary> + /// <param name="argument">Method call argument</param> + public void VbElementU32(int argument) + { + _drawState.IbStreamer.VbElementU32(_context.Renderer, argument); + } + + /// <summary> + /// Finishes the draw call. + /// This draws geometry on the bound buffers based on the current GPU state. + /// </summary> + /// <param name="engine">3D engine where this method is being called</param> + /// <param name="argument">Method call argument</param> + public void DrawEnd(ThreedClass engine, int argument) + { + DrawEnd(engine, _state.State.IndexBufferState.First, (int)_state.State.IndexBufferCount); + } + + /// <summary> + /// Finishes the draw call. + /// This draws geometry on the bound buffers based on the current GPU state. + /// </summary> + /// <param name="engine">3D engine where this method is being called</param> + /// <param name="firstIndex">Index of the first index buffer element used on the draw</param> + /// <param name="indexCount">Number of index buffer elements used on the draw</param> + private void DrawEnd(ThreedClass engine, int firstIndex, int indexCount) + { + ConditionalRenderEnabled renderEnable = ConditionalRendering.GetRenderEnable( + _context, + _channel.MemoryManager, + _state.State.RenderEnableAddress, + _state.State.RenderEnableCondition); + + if (renderEnable == ConditionalRenderEnabled.False || _instancedDrawPending) + { + if (renderEnable == ConditionalRenderEnabled.False) + { + PerformDeferredDraws(); + } + + _drawState.DrawIndexed = false; + + if (renderEnable == ConditionalRenderEnabled.Host) + { + _context.Renderer.Pipeline.EndHostConditionalRendering(); + } + + return; + } + + _drawState.FirstIndex = firstIndex; + _drawState.IndexCount = indexCount; + + engine.UpdateState(); + + bool instanced = _drawState.VsUsesInstanceId || _drawState.IsAnyVbInstanced; + + if (instanced) + { + _instancedDrawPending = true; + + _instancedIndexed = _drawState.DrawIndexed; + + _instancedFirstIndex = firstIndex; + _instancedFirstVertex = (int)_state.State.FirstVertex; + _instancedFirstInstance = (int)_state.State.FirstInstance; + + _instancedIndexCount = indexCount; + + var drawState = _state.State.VertexBufferDrawState; + + _instancedDrawStateFirst = drawState.First; + _instancedDrawStateCount = drawState.Count; + + _drawState.DrawIndexed = false; + + if (renderEnable == ConditionalRenderEnabled.Host) + { + _context.Renderer.Pipeline.EndHostConditionalRendering(); + } + + return; + } + + int firstInstance = (int)_state.State.FirstInstance; + + int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(); + + if (inlineIndexCount != 0) + { + int firstVertex = (int)_state.State.FirstVertex; + + BufferRange br = new BufferRange(_drawState.IbStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4); + + _channel.BufferManager.SetIndexBuffer(br, IndexType.UInt); + + _context.Renderer.Pipeline.DrawIndexed(inlineIndexCount, 1, firstIndex, firstVertex, firstInstance); + } + else if (_drawState.DrawIndexed) + { + int firstVertex = (int)_state.State.FirstVertex; + + _context.Renderer.Pipeline.DrawIndexed(indexCount, 1, firstIndex, firstVertex, firstInstance); + } + else + { + var drawState = _state.State.VertexBufferDrawState; + + _context.Renderer.Pipeline.Draw(drawState.Count, 1, drawState.First, firstInstance); + } + + _drawState.DrawIndexed = false; + + if (renderEnable == ConditionalRenderEnabled.Host) + { + _context.Renderer.Pipeline.EndHostConditionalRendering(); + } + } + + /// <summary> + /// Starts draw. + /// This sets primitive type and instanced draw parameters. + /// </summary> + /// <param name="argument">Method call argument</param> + public void DrawBegin(int argument) + { + bool incrementInstance = (argument & (1 << 26)) != 0; + bool resetInstance = (argument & (1 << 27)) == 0; + + if (_state.State.PrimitiveTypeOverrideEnable) + { + PrimitiveTypeOverride typeOverride = _state.State.PrimitiveTypeOverride; + DrawBegin(incrementInstance, resetInstance, typeOverride.Convert()); + } + else + { + PrimitiveType type = (PrimitiveType)(argument & 0xffff); + DrawBegin(incrementInstance, resetInstance, type.Convert()); + } + } + + /// <summary> + /// Starts draw. + /// This sets primitive type and instanced draw parameters. + /// </summary> + /// <param name="incrementInstance">Indicates if the current instance should be incremented</param> + /// <param name="resetInstance">Indicates if the current instance should be set to zero</param> + /// <param name="topology">Primitive topology</param> + private void DrawBegin(bool incrementInstance, bool resetInstance, PrimitiveTopology topology) + { + if (incrementInstance) + { + _instanceIndex++; + } + else if (resetInstance) + { + PerformDeferredDraws(); + + _instanceIndex = 0; + } + + _context.Renderer.Pipeline.SetPrimitiveTopology(topology); + + _drawState.Topology = topology; + } + + /// <summary> + /// Sets the index buffer count. + /// This also sets internal state that indicates that the next draw is an indexed draw. + /// </summary> + /// <param name="argument">Method call argument</param> + public void SetIndexBufferCount(int argument) + { + _drawState.DrawIndexed = true; + } + + /// <summary> + /// Performs a indexed draw with a low number of index buffer elements. + /// </summary> + /// <param name="engine">3D engine where this method is being called</param> + /// <param name="argument">Method call argument</param> + public void DrawIndexedSmall(ThreedClass engine, int argument) + { + DrawIndexedSmall(engine, argument, false); + } + + /// <summary> + /// Performs a indexed draw with a low number of index buffer elements. + /// </summary> + /// <param name="engine">3D engine where this method is being called</param> + /// <param name="argument">Method call argument</param> + public void DrawIndexedSmall2(ThreedClass engine, int argument) + { + DrawIndexedSmall(engine, argument); + } + + /// <summary> + /// Performs a indexed draw with a low number of index buffer elements, + /// while also pre-incrementing the current instance value. + /// </summary> + /// <param name="engine">3D engine where this method is being called</param> + /// <param name="argument">Method call argument</param> + public void DrawIndexedSmallIncInstance(ThreedClass engine, int argument) + { + DrawIndexedSmall(engine, argument, true); + } + + /// <summary> + /// Performs a indexed draw with a low number of index buffer elements, + /// while also pre-incrementing the current instance value. + /// </summary> + /// <param name="engine">3D engine where this method is being called</param> + /// <param name="argument">Method call argument</param> + public void DrawIndexedSmallIncInstance2(ThreedClass engine, int argument) + { + DrawIndexedSmallIncInstance(engine, argument); + } + + /// <summary> + /// Performs a indexed draw with a low number of index buffer elements, + /// while optionally also pre-incrementing the current instance value. + /// </summary> + /// <param name="engine">3D engine where this method is being called</param> + /// <param name="argument">Method call argument</param> + /// <param name="instanced">True to increment the current instance value, false otherwise</param> + private void DrawIndexedSmall(ThreedClass engine, int argument, bool instanced) + { + PrimitiveTypeOverride typeOverride = _state.State.PrimitiveTypeOverride; + + DrawBegin(instanced, !instanced, typeOverride.Convert()); + + int firstIndex = argument & 0xffff; + int indexCount = (argument >> 16) & 0xfff; + + bool oldDrawIndexed = _drawState.DrawIndexed; + + _drawState.DrawIndexed = true; + + DrawEnd(engine, firstIndex, indexCount); + + _drawState.DrawIndexed = oldDrawIndexed; + } + + /// <summary> + /// Perform any deferred draws. + /// This is used for instanced draws. + /// Since each instance is a separate draw, we defer the draw and accumulate the instance count. + /// Once we detect the last instanced draw, then we perform the host instanced draw, + /// with the accumulated instance count. + /// </summary> + public void PerformDeferredDraws() + { + // Perform any pending instanced draw. + if (_instancedDrawPending) + { + _instancedDrawPending = false; + + if (_instancedIndexed) + { + _context.Renderer.Pipeline.DrawIndexed( + _instancedIndexCount, + _instanceIndex + 1, + _instancedFirstIndex, + _instancedFirstVertex, + _instancedFirstInstance); + } + else + { + _context.Renderer.Pipeline.Draw( + _instancedDrawStateCount, + _instanceIndex + 1, + _instancedDrawStateFirst, + _instancedFirstInstance); + } + } + } + + /// <summary> + /// Clears the current color and depth-stencil buffers. + /// Which buffers should be cleared is also specified on the argument. + /// </summary> + /// <param name="engine">3D engine where this method is being called</param> + /// <param name="argument">Method call argument</param> + public void Clear(ThreedClass engine, int argument) + { + ConditionalRenderEnabled renderEnable = ConditionalRendering.GetRenderEnable( + _context, + _channel.MemoryManager, + _state.State.RenderEnableAddress, + _state.State.RenderEnableCondition); + + if (renderEnable == ConditionalRenderEnabled.False) + { + return; + } + + // Scissor and rasterizer discard also affect clears. + engine.UpdateState((1UL << StateUpdater.RasterizerStateIndex) | (1UL << StateUpdater.ScissorStateIndex)); + + int index = (argument >> 6) & 0xf; + + engine.UpdateRenderTargetState(useControl: false, singleUse: index); + + _channel.TextureManager.UpdateRenderTargets(); + + bool clearDepth = (argument & 1) != 0; + bool clearStencil = (argument & 2) != 0; + + uint componentMask = (uint)((argument >> 2) & 0xf); + + if (componentMask != 0) + { + var clearColor = _state.State.ClearColors; + + ColorF color = new ColorF(clearColor.Red, clearColor.Green, clearColor.Blue, clearColor.Alpha); + + _context.Renderer.Pipeline.ClearRenderTargetColor(index, componentMask, color); + } + + if (clearDepth || clearStencil) + { + float depthValue = _state.State.ClearDepthValue; + int stencilValue = (int)_state.State.ClearStencilValue; + + int stencilMask = 0; + + if (clearStencil) + { + stencilMask = _state.State.StencilTestState.FrontMask; + } + + _context.Renderer.Pipeline.ClearRenderTargetDepthStencil( + depthValue, + clearDepth, + stencilValue, + stencilMask); + } + + engine.UpdateRenderTargetState(useControl: true); + + if (renderEnable == ConditionalRenderEnabled.Host) + { + _context.Renderer.Pipeline.EndHostConditionalRendering(); + } + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/DrawState.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/DrawState.cs new file mode 100644 index 00000000..ff186acc --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/DrawState.cs @@ -0,0 +1,45 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// <summary> + /// Draw state. + /// </summary> + class DrawState + { + /// <summary> + /// First index to be used for the draw on the index buffer. + /// </summary> + public int FirstIndex; + + /// <summary> + /// Number of indices to be used for the draw on the index buffer. + /// </summary> + public int IndexCount; + + /// <summary> + /// Indicates if the next draw will be a indexed draw. + /// </summary> + public bool DrawIndexed; + + /// <summary> + /// Indicates if any of the currently used vertex shaders reads the instance ID. + /// </summary> + public bool VsUsesInstanceId; + + /// <summary> + /// Indicates if any of the currently used vertex buffers is instanced. + /// </summary> + public bool IsAnyVbInstanced; + + /// <summary> + /// Primitive topology for the next draw. + /// </summary> + public PrimitiveTopology Topology; + + /// <summary> + /// Index buffer data streamer for inline index buffer updates, such as those used in legacy OpenGL. + /// </summary> + public IbStreamer IbStreamer = new IbStreamer(); + } +} diff --git a/Ryujinx.Graphics.Gpu/Engine/IbStreamer.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/IbStreamer.cs index b407c941..96b2ed9c 100644 --- a/Ryujinx.Graphics.Gpu/Engine/IbStreamer.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/IbStreamer.cs @@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL; using System; using System.Runtime.InteropServices; -namespace Ryujinx.Graphics.Gpu.Engine +namespace Ryujinx.Graphics.Gpu.Engine.Threed { /// <summary> /// Holds inline index buffer state. @@ -15,6 +15,9 @@ namespace Ryujinx.Graphics.Gpu.Engine private int _inlineIndexBufferSize; private int _inlineIndexCount; + /// <summary> + /// Indicates if any index buffer data has been pushed. + /// </summary> public bool HasInlineIndexData => _inlineIndexCount != 0; /// <summary> diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/SemaphoreUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/SemaphoreUpdater.cs new file mode 100644 index 00000000..37fa51c4 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/SemaphoreUpdater.cs @@ -0,0 +1,222 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// <summary> + /// Semaphore updater. + /// </summary> + class SemaphoreUpdater + { + private const int NsToTicksFractionNumerator = 384; + private const int NsToTicksFractionDenominator = 625; + + /// <summary> + /// GPU semaphore operation. + /// </summary> + private enum SemaphoreOperation + { + Release = 0, + Acquire = 1, + Counter = 2 + } + + /// <summary> + /// Counter type for GPU counter reset. + /// </summary> + private enum ResetCounterType + { + SamplesPassed = 1, + ZcullStats = 2, + TransformFeedbackPrimitivesWritten = 0x10, + InputVertices = 0x12, + InputPrimitives = 0x13, + VertexShaderInvocations = 0x15, + TessControlShaderInvocations = 0x16, + TessEvaluationShaderInvocations = 0x17, + TessEvaluationShaderPrimitives = 0x18, + GeometryShaderInvocations = 0x1a, + GeometryShaderPrimitives = 0x1b, + ClipperInputPrimitives = 0x1c, + ClipperOutputPrimitives = 0x1d, + FragmentShaderInvocations = 0x1e, + PrimitivesGenerated = 0x1f + } + + /// <summary> + /// Counter type for GPU counter reporting. + /// </summary> + private enum ReportCounterType + { + Zero = 0, + InputVertices = 1, + InputPrimitives = 3, + VertexShaderInvocations = 5, + GeometryShaderInvocations = 7, + GeometryShaderPrimitives = 9, + ZcullStats0 = 0xa, + TransformFeedbackPrimitivesWritten = 0xb, + ZcullStats1 = 0xc, + ZcullStats2 = 0xe, + ClipperInputPrimitives = 0xf, + ZcullStats3 = 0x10, + ClipperOutputPrimitives = 0x11, + PrimitivesGenerated = 0x12, + FragmentShaderInvocations = 0x13, + SamplesPassed = 0x15, + TransformFeedbackOffset = 0x1a, + TessControlShaderInvocations = 0x1b, + TessEvaluationShaderInvocations = 0x1d, + TessEvaluationShaderPrimitives = 0x1f + } + + private readonly GpuContext _context; + private readonly GpuChannel _channel; + private readonly DeviceStateWithShadow<ThreedClassState> _state; + + /// <summary> + /// Creates a new instance of the semaphore updater. + /// </summary> + /// <param name="context">GPU context</param> + /// <param name="channel">GPU channel</param> + /// <param name="state">Channel state</param> + public SemaphoreUpdater(GpuContext context, GpuChannel channel, DeviceStateWithShadow<ThreedClassState> state) + { + _context = context; + _channel = channel; + _state = state; + } + + /// <summary> + /// Resets the value of an internal GPU counter back to zero. + /// </summary> + /// <param name="argument">Method call argument</param> + public void ResetCounter(int argument) + { + ResetCounterType type = (ResetCounterType)argument; + + switch (type) + { + case ResetCounterType.SamplesPassed: + _context.Renderer.ResetCounter(CounterType.SamplesPassed); + break; + case ResetCounterType.PrimitivesGenerated: + _context.Renderer.ResetCounter(CounterType.PrimitivesGenerated); + break; + case ResetCounterType.TransformFeedbackPrimitivesWritten: + _context.Renderer.ResetCounter(CounterType.TransformFeedbackPrimitivesWritten); + break; + } + } + + /// <summary> + /// Writes a GPU counter to guest memory. + /// </summary> + /// <param name="argument">Method call argument</param> + public void Report(int argument) + { + SemaphoreOperation op = (SemaphoreOperation)(argument & 3); + ReportCounterType type = (ReportCounterType)((argument >> 23) & 0x1f); + + switch (op) + { + case SemaphoreOperation.Release: ReleaseSemaphore(); break; + case SemaphoreOperation.Counter: ReportCounter(type); break; + } + } + + /// <summary> + /// Writes (or Releases) a GPU semaphore value to guest memory. + /// </summary> + private void ReleaseSemaphore() + { + _channel.MemoryManager.Write(_state.State.SemaphoreAddress.Pack(), _state.State.SemaphorePayload); + + _context.AdvanceSequence(); + } + + /// <summary> + /// Packed GPU counter data (including GPU timestamp) in memory. + /// </summary> + private struct CounterData + { + public ulong Counter; + public ulong Timestamp; + } + + /// <summary> + /// Writes a GPU counter to guest memory. + /// This also writes the current timestamp value. + /// </summary> + /// <param name="type">Counter to be written to memory</param> + private void ReportCounter(ReportCounterType type) + { + ulong gpuVa = _state.State.SemaphoreAddress.Pack(); + + ulong ticks = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds); + + if (GraphicsConfig.FastGpuTime) + { + // Divide by some amount to report time as if operations were performed faster than they really are. + // This can prevent some games from switching to a lower resolution because rendering is too slow. + ticks /= 256; + } + + ICounterEvent counter = null; + + void resultHandler(object evt, ulong result) + { + CounterData counterData = new CounterData + { + Counter = result, + Timestamp = ticks + }; + + if (counter?.Invalid != true) + { + _channel.MemoryManager.Write(gpuVa, counterData); + } + } + + switch (type) + { + case ReportCounterType.Zero: + resultHandler(null, 0); + break; + case ReportCounterType.SamplesPassed: + counter = _context.Renderer.ReportCounter(CounterType.SamplesPassed, resultHandler); + break; + case ReportCounterType.PrimitivesGenerated: + counter = _context.Renderer.ReportCounter(CounterType.PrimitivesGenerated, resultHandler); + break; + case ReportCounterType.TransformFeedbackPrimitivesWritten: + counter = _context.Renderer.ReportCounter(CounterType.TransformFeedbackPrimitivesWritten, resultHandler); + break; + } + + _channel.MemoryManager.CounterCache.AddOrUpdate(gpuVa, counter); + } + + /// <summary> + /// Converts a nanoseconds timestamp value to Maxwell time ticks. + /// </summary> + /// <remarks> + /// The frequency is 614400000 Hz. + /// </remarks> + /// <param name="nanoseconds">Timestamp in nanoseconds</param> + /// <returns>Maxwell ticks</returns> + private static ulong ConvertNanosecondsToTicks(ulong nanoseconds) + { + // We need to divide first to avoid overflows. + // We fix up the result later by calculating the difference and adding + // that to the result. + ulong divided = nanoseconds / NsToTicksFractionDenominator; + + ulong rounded = divided * NsToTicksFractionDenominator; + + ulong errorBias = (nanoseconds - rounded) * NsToTicksFractionNumerator / NsToTicksFractionDenominator; + + return divided * NsToTicksFractionNumerator + errorBias; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs new file mode 100644 index 00000000..2af7a402 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs @@ -0,0 +1,166 @@ +using Ryujinx.Graphics.Device; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// <summary> + /// State update callback entry, with the callback function and associated field names. + /// </summary> + struct StateUpdateCallbackEntry + { + /// <summary> + /// Callback function, to be called if the register was written as the state needs to be updated. + /// </summary> + public Action Callback { get; } + + /// <summary> + /// Name of the state fields (registers) associated with the callback function. + /// </summary> + public string[] FieldNames { get; } + + /// <summary> + /// Creates a new state update callback entry. + /// </summary> + /// <param name="callback">Callback function, to be called if the register was written as the state needs to be updated</param> + /// <param name="fieldNames">Name of the state fields (registers) associated with the callback function</param> + public StateUpdateCallbackEntry(Action callback, params string[] fieldNames) + { + Callback = callback; + FieldNames = fieldNames; + } + } + + /// <summary> + /// GPU state update tracker. + /// </summary> + /// <typeparam name="TState">State type</typeparam> + class StateUpdateTracker<TState> + { + private const int BlockSize = 0xe00; + private const int RegisterSize = sizeof(uint); + + private readonly byte[] _registerToGroupMapping; + private readonly Action[] _callbacks; + private ulong _dirtyMask; + + /// <summary> + /// Creates a new instance of the state update tracker. + /// </summary> + /// <param name="entries">Update tracker callback entries</param> + public StateUpdateTracker(StateUpdateCallbackEntry[] entries) + { + _registerToGroupMapping = new byte[BlockSize]; + _callbacks = new Action[entries.Length]; + + var fieldToDelegate = new Dictionary<string, int>(); + + for (int entryIndex = 0; entryIndex < entries.Length; entryIndex++) + { + var entry = entries[entryIndex]; + + foreach (var fieldName in entry.FieldNames) + { + fieldToDelegate.Add(fieldName, entryIndex); + } + + _callbacks[entryIndex] = entry.Callback; + } + + var fields = typeof(TState).GetFields(); + int offset = 0; + + for (int fieldIndex = 0; fieldIndex < fields.Length; fieldIndex++) + { + var field = fields[fieldIndex]; + + int sizeOfField = SizeCalculator.SizeOf(field.FieldType); + + if (fieldToDelegate.TryGetValue(field.Name, out int entryIndex)) + { + for (int i = 0; i < ((sizeOfField + 3) & ~3); i += 4) + { + _registerToGroupMapping[(offset + i) / RegisterSize] = (byte)(entryIndex + 1); + } + } + + offset += sizeOfField; + } + + Debug.Assert(offset == Unsafe.SizeOf<TState>()); + } + + /// <summary> + /// Sets a register as modified. + /// </summary> + /// <param name="offset">Register offset in bytes</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetDirty(int offset) + { + uint index = (uint)offset / RegisterSize; + + if (index < BlockSize) + { + int groupIndex = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_registerToGroupMapping), (IntPtr)index); + if (groupIndex != 0) + { + groupIndex--; + _dirtyMask |= 1UL << groupIndex; + } + } + } + + /// <summary> + /// Forces a register group as dirty, by index. + /// </summary> + /// <param name="groupIndex">Index of the group to be dirtied</param> + public void ForceDirty(int groupIndex) + { + if ((uint)groupIndex >= _callbacks.Length) + { + throw new ArgumentOutOfRangeException(nameof(groupIndex)); + } + + _dirtyMask |= 1UL << groupIndex; + } + + /// <summary> + /// Forces all register groups as dirty, triggering a full update on the next call to <see cref="Update"/>. + /// </summary> + public void SetAllDirty() + { + Debug.Assert(_callbacks.Length <= sizeof(ulong) * 8); + _dirtyMask = ulong.MaxValue >> ((sizeof(ulong) * 8) - _callbacks.Length); + } + + /// <summary> + /// Check all the groups specified by <paramref name="checkMask"/> for modification, and update if modified. + /// </summary> + /// <param name="checkMask">Mask, where each bit set corresponds to a group index that should be checked</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Update(ulong checkMask) + { + ulong mask = _dirtyMask & checkMask; + if (mask == 0) + { + return; + } + + do + { + int groupIndex = BitOperations.TrailingZeroCount(mask); + + _callbacks[groupIndex](); + + mask &= ~(1UL << groupIndex); + } + while (mask != 0); + + _dirtyMask &= ~checkMask; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs new file mode 100644 index 00000000..4ff084e9 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -0,0 +1,1044 @@ +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.Texture; +using System; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// <summary> + /// GPU state updater. + /// </summary> + class StateUpdater + { + public const int ShaderStateIndex = 0; + public const int RasterizerStateIndex = 1; + public const int ScissorStateIndex = 2; + public const int VertexBufferStateIndex = 3; + + private readonly GpuContext _context; + private readonly GpuChannel _channel; + private readonly DeviceStateWithShadow<ThreedClassState> _state; + private readonly DrawState _drawState; + + private readonly StateUpdateTracker<ThreedClassState> _updateTracker; + + private readonly ShaderProgramInfo[] _currentProgramInfo; + + private byte _vsClipDistancesWritten; + + private bool _prevDrawIndexed; + private bool _prevTfEnable; + + /// <summary> + /// Creates a new instance of the state updater. + /// </summary> + /// <param name="context">GPU context</param> + /// <param name="channel">GPU channel</param> + /// <param name="state">3D engine state</param> + /// <param name="drawState">Draw state</param> + public StateUpdater(GpuContext context, GpuChannel channel, DeviceStateWithShadow<ThreedClassState> state, DrawState drawState) + { + _context = context; + _channel = channel; + _state = state; + _drawState = drawState; + _currentProgramInfo = new ShaderProgramInfo[Constants.ShaderStages]; + + // ShaderState must be the first, as other state updates depends on information from the currently bound shader. + // Rasterizer and scissor states are checked by render target clear, their indexes + // must be updated on the constants "RasterizerStateIndex" and "ScissorStateIndex" if modified. + // The vertex buffer state may be forced dirty when a indexed draw starts, the "VertexBufferStateIndex" + // constant must be updated if modified. + // The order of the other state updates doesn't matter. + _updateTracker = new StateUpdateTracker<ThreedClassState>(new[] + { + new StateUpdateCallbackEntry(UpdateShaderState, + nameof(ThreedClassState.ShaderBaseAddress), + nameof(ThreedClassState.ShaderState)), + + new StateUpdateCallbackEntry(UpdateRasterizerState, nameof(ThreedClassState.RasterizeEnable)), + new StateUpdateCallbackEntry(UpdateScissorState, nameof(ThreedClassState.ScissorState)), + + new StateUpdateCallbackEntry(UpdateVertexBufferState, + nameof(ThreedClassState.VertexBufferDrawState), + nameof(ThreedClassState.VertexBufferInstanced), + nameof(ThreedClassState.VertexBufferState), + nameof(ThreedClassState.VertexBufferEndAddress)), + + new StateUpdateCallbackEntry(UpdateTfBufferState, nameof(ThreedClassState.TfBufferState)), + new StateUpdateCallbackEntry(UpdateUserClipState, nameof(ThreedClassState.ClipDistanceEnable)), + + new StateUpdateCallbackEntry(UpdateRenderTargetState, + nameof(ThreedClassState.RtColorState), + nameof(ThreedClassState.RtDepthStencilState), + nameof(ThreedClassState.RtControl), + nameof(ThreedClassState.RtDepthStencilSize), + nameof(ThreedClassState.RtDepthStencilEnable)), + + new StateUpdateCallbackEntry(UpdateDepthClampState, nameof(ThreedClassState.ViewVolumeClipControl)), + + new StateUpdateCallbackEntry(UpdateAlphaTestState, + nameof(ThreedClassState.AlphaTestEnable), + nameof(ThreedClassState.AlphaTestRef), + nameof(ThreedClassState.AlphaTestFunc)), + + new StateUpdateCallbackEntry(UpdateDepthTestState, + nameof(ThreedClassState.DepthTestEnable), + nameof(ThreedClassState.DepthWriteEnable), + nameof(ThreedClassState.DepthTestFunc)), + + new StateUpdateCallbackEntry(UpdateViewportTransform, + nameof(ThreedClassState.DepthMode), + nameof(ThreedClassState.ViewportTransform), + nameof(ThreedClassState.ViewportExtents), + nameof(ThreedClassState.YControl)), + + new StateUpdateCallbackEntry(UpdateDepthBiasState, + nameof(ThreedClassState.DepthBiasState), + nameof(ThreedClassState.DepthBiasFactor), + nameof(ThreedClassState.DepthBiasUnits), + nameof(ThreedClassState.DepthBiasClamp)), + + new StateUpdateCallbackEntry(UpdateStencilTestState, + nameof(ThreedClassState.StencilBackMasks), + nameof(ThreedClassState.StencilTestState), + nameof(ThreedClassState.StencilBackTestState)), + + new StateUpdateCallbackEntry(UpdateSamplerPoolState, + nameof(ThreedClassState.SamplerPoolState), + nameof(ThreedClassState.SamplerIndex)), + + new StateUpdateCallbackEntry(UpdateTexturePoolState, nameof(ThreedClassState.TexturePoolState)), + new StateUpdateCallbackEntry(UpdateVertexAttribState, nameof(ThreedClassState.VertexAttribState)), + + new StateUpdateCallbackEntry(UpdateLineState, + nameof(ThreedClassState.LineWidthSmooth), + nameof(ThreedClassState.LineSmoothEnable)), + + new StateUpdateCallbackEntry(UpdatePointState, + nameof(ThreedClassState.PointSize), + nameof(ThreedClassState.VertexProgramPointSize), + nameof(ThreedClassState.PointSpriteEnable), + nameof(ThreedClassState.PointCoordReplace)), + + new StateUpdateCallbackEntry(UpdatePrimitiveRestartState, nameof(ThreedClassState.PrimitiveRestartState)), + + new StateUpdateCallbackEntry(UpdateIndexBufferState, + nameof(ThreedClassState.IndexBufferState), + nameof(ThreedClassState.IndexBufferCount)), + + new StateUpdateCallbackEntry(UpdateFaceState, nameof(ThreedClassState.FaceState)), + + new StateUpdateCallbackEntry(UpdateRtColorMask, + nameof(ThreedClassState.RtColorMaskShared), + nameof(ThreedClassState.RtColorMask)), + + new StateUpdateCallbackEntry(UpdateBlendState, + nameof(ThreedClassState.BlendIndependent), + nameof(ThreedClassState.BlendConstant), + nameof(ThreedClassState.BlendStateCommon), + nameof(ThreedClassState.BlendEnableCommon), + nameof(ThreedClassState.BlendEnable), + nameof(ThreedClassState.BlendState)), + + new StateUpdateCallbackEntry(UpdateLogicOpState, nameof(ThreedClassState.LogicOpState)) + }); + } + + /// <summary> + /// Sets a register at a specific offset as dirty. + /// This must be called if the register value was modified. + /// </summary> + /// <param name="offset">Register offset</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetDirty(int offset) + { + _updateTracker.SetDirty(offset); + } + + /// <summary> + /// Force all the guest state to be marked as dirty. + /// The next call to <see cref="Update"/> will update all the host state. + /// </summary> + public void SetAllDirty() + { + _updateTracker.SetAllDirty(); + } + + /// <summary> + /// Updates host state for any modified guest state, since the last time this function was called. + /// </summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Update() + { + // The vertex buffer size is calculated using a different + // method when doing indexed draws, so we need to make sure + // to update the vertex buffers if we are doing a regular + // draw after a indexed one and vice-versa. + if (_drawState.DrawIndexed != _prevDrawIndexed) + { + _updateTracker.ForceDirty(VertexBufferStateIndex); + _prevDrawIndexed = _drawState.DrawIndexed; + } + + bool tfEnable = _state.State.TfEnable; + + if (!tfEnable && _prevTfEnable) + { + _context.Renderer.Pipeline.EndTransformFeedback(); + _prevTfEnable = false; + } + + _updateTracker.Update(ulong.MaxValue); + + CommitBindings(); + + if (tfEnable && !_prevTfEnable) + { + _context.Renderer.Pipeline.BeginTransformFeedback(_drawState.Topology); + _prevTfEnable = true; + } + } + + /// <summary> + /// Updates the host state for any modified guest state group with the respective bit set on <paramref name="mask"/>. + /// </summary> + /// <param name="mask">Mask, where each bit set corresponds to a group index that should be checked and updated</param> + public void Update(ulong mask) + { + _updateTracker.Update(mask); + } + + /// <summary> + /// Ensures that the bindings are visible to the host GPU. + /// Note: this actually performs the binding using the host graphics API. + /// </summary> + private void CommitBindings() + { + UpdateStorageBuffers(); + + _channel.TextureManager.CommitGraphicsBindings(); + _channel.BufferManager.CommitGraphicsBindings(); + } + + /// <summary> + /// Updates storage buffer bindings. + /// </summary> + private void UpdateStorageBuffers() + { + for (int stage = 0; stage < Constants.ShaderStages; stage++) + { + ShaderProgramInfo info = _currentProgramInfo[stage]; + + if (info == null) + { + continue; + } + + for (int index = 0; index < info.SBuffers.Count; index++) + { + BufferDescriptor sb = info.SBuffers[index]; + + ulong sbDescAddress = _channel.BufferManager.GetGraphicsUniformBufferAddress(stage, 0); + + int sbDescOffset = 0x110 + stage * 0x100 + sb.Slot * 0x10; + + sbDescAddress += (ulong)sbDescOffset; + + SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress); + + _channel.BufferManager.SetGraphicsStorageBuffer(stage, sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size, sb.Flags); + } + } + } + + /// <summary> + /// Updates transform feedback buffer state based on the guest GPU state. + /// </summary> + private void UpdateTfBufferState() + { + for (int index = 0; index < Constants.TotalTransformFeedbackBuffers; index++) + { + TfBufferState tfb = _state.State.TfBufferState[index]; + + if (!tfb.Enable) + { + _channel.BufferManager.SetTransformFeedbackBuffer(index, 0, 0); + + continue; + } + + _channel.BufferManager.SetTransformFeedbackBuffer(index, tfb.Address.Pack(), (uint)tfb.Size); + } + } + + /// <summary> + /// Updates Rasterizer primitive discard state based on guest gpu state. + /// </summary> + private void UpdateRasterizerState() + { + bool enable = _state.State.RasterizeEnable; + _context.Renderer.Pipeline.SetRasterizerDiscard(!enable); + } + + /// <summary> + /// Updates render targets (color and depth-stencil buffers) based on current render target state. + /// </summary> + private void UpdateRenderTargetState() + { + UpdateRenderTargetState(true); + } + + /// <summary> + /// Updates render targets (color and depth-stencil buffers) based on current render target state. + /// </summary> + /// <param name="useControl">Use draw buffers information from render target control register</param> + /// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param> + public void UpdateRenderTargetState(bool useControl, int singleUse = -1) + { + var memoryManager = _channel.MemoryManager; + var rtControl = _state.State.RtControl; + + int count = useControl ? rtControl.UnpackCount() : Constants.TotalRenderTargets; + + var msaaMode = _state.State.RtMsaaMode; + + int samplesInX = msaaMode.SamplesInX(); + int samplesInY = msaaMode.SamplesInY(); + + var scissor = _state.State.ScreenScissorState; + Size sizeHint = new Size(scissor.X + scissor.Width, scissor.Y + scissor.Height, 1); + + bool changedScale = false; + + for (int index = 0; index < Constants.TotalRenderTargets; index++) + { + int rtIndex = useControl ? rtControl.UnpackPermutationIndex(index) : index; + + var colorState = _state.State.RtColorState[rtIndex]; + + if (index >= count || !IsRtEnabled(colorState)) + { + changedScale |= _channel.TextureManager.SetRenderTargetColor(index, null); + + continue; + } + + Image.Texture color = memoryManager.Physical.TextureCache.FindOrCreateTexture( + memoryManager, + colorState, + samplesInX, + samplesInY, + sizeHint); + + changedScale |= _channel.TextureManager.SetRenderTargetColor(index, color); + } + + bool dsEnable = _state.State.RtDepthStencilEnable; + + Image.Texture depthStencil = null; + + if (dsEnable) + { + var dsState = _state.State.RtDepthStencilState; + var dsSize = _state.State.RtDepthStencilSize; + + depthStencil = memoryManager.Physical.TextureCache.FindOrCreateTexture( + memoryManager, + dsState, + dsSize, + samplesInX, + samplesInY, + sizeHint); + } + + changedScale |= _channel.TextureManager.SetRenderTargetDepthStencil(depthStencil); + + if (changedScale) + { + _channel.TextureManager.UpdateRenderTargetScale(singleUse); + _context.Renderer.Pipeline.SetRenderTargetScale(_channel.TextureManager.RenderTargetScale); + + UpdateViewportTransform(); + UpdateScissorState(); + } + } + + /// <summary> + /// Checks if a render target color buffer is used. + /// </summary> + /// <param name="colorState">Color buffer information</param> + /// <returns>True if the specified buffer is enabled/used, false otherwise</returns> + private static bool IsRtEnabled(RtColorState colorState) + { + // Colors are disabled by writing 0 to the format. + return colorState.Format != 0 && colorState.WidthOrStride != 0; + } + + /// <summary> + /// Updates host scissor test state based on current GPU state. + /// </summary> + private void UpdateScissorState() + { + for (int index = 0; index < Constants.TotalViewports; index++) + { + ScissorState scissor = _state.State.ScissorState[index]; + + bool enable = scissor.Enable && (scissor.X1 != 0 || scissor.Y1 != 0 || scissor.X2 != 0xffff || scissor.Y2 != 0xffff); + + if (enable) + { + int x = scissor.X1; + int y = scissor.Y1; + int width = scissor.X2 - x; + int height = scissor.Y2 - y; + + float scale = _channel.TextureManager.RenderTargetScale; + if (scale != 1f) + { + x = (int)(x * scale); + y = (int)(y * scale); + width = (int)Math.Ceiling(width * scale); + height = (int)Math.Ceiling(height * scale); + } + + _context.Renderer.Pipeline.SetScissor(index, true, x, y, width, height); + } + else + { + _context.Renderer.Pipeline.SetScissor(index, false, 0, 0, 0, 0); + } + } + } + + /// <summary> + /// Updates host depth clamp state based on current GPU state. + /// </summary> + /// <param name="state">Current GPU state</param> + private void UpdateDepthClampState() + { + ViewVolumeClipControl clip = _state.State.ViewVolumeClipControl; + _context.Renderer.Pipeline.SetDepthClamp((clip & ViewVolumeClipControl.DepthClampDisabled) == 0); + } + + /// <summary> + /// Updates host alpha test state based on current GPU state. + /// </summary> + private void UpdateAlphaTestState() + { + _context.Renderer.Pipeline.SetAlphaTest( + _state.State.AlphaTestEnable, + _state.State.AlphaTestRef, + _state.State.AlphaTestFunc); + } + + /// <summary> + /// Updates host depth test state based on current GPU state. + /// </summary> + private void UpdateDepthTestState() + { + _context.Renderer.Pipeline.SetDepthTest(new DepthTestDescriptor( + _state.State.DepthTestEnable, + _state.State.DepthWriteEnable, + _state.State.DepthTestFunc)); + } + + /// <summary> + /// Updates host viewport transform and clipping state based on current GPU state. + /// </summary> + private void UpdateViewportTransform() + { + var yControl = _state.State.YControl; + var face = _state.State.FaceState; + + UpdateFrontFace(yControl, face.FrontFace); + + bool flipY = yControl.HasFlag(YControl.NegateY); + + Span<Viewport> viewports = stackalloc Viewport[Constants.TotalViewports]; + + for (int index = 0; index < Constants.TotalViewports; index++) + { + var transform = _state.State.ViewportTransform[index]; + var extents = _state.State.ViewportExtents[index]; + + float scaleX = MathF.Abs(transform.ScaleX); + float scaleY = transform.ScaleY; + + if (flipY) + { + scaleY = -scaleY; + } + + if (!_context.Capabilities.SupportsViewportSwizzle && transform.UnpackSwizzleY() == ViewportSwizzle.NegativeY) + { + scaleY = -scaleY; + } + + if (index == 0) + { + // Try to guess the depth mode being used on the high level API + // based on current transform. + // It is setup like so by said APIs: + // If depth mode is ZeroToOne: + // TranslateZ = Near + // ScaleZ = Far - Near + // If depth mode is MinusOneToOne: + // TranslateZ = (Near + Far) / 2 + // ScaleZ = (Far - Near) / 2 + // DepthNear/Far are sorted such as that Near is always less than Far. + DepthMode depthMode = extents.DepthNear != transform.TranslateZ && + extents.DepthFar != transform.TranslateZ ? DepthMode.MinusOneToOne : DepthMode.ZeroToOne; + + _context.Renderer.Pipeline.SetDepthMode(depthMode); + } + + float x = transform.TranslateX - scaleX; + float y = transform.TranslateY - scaleY; + + float width = scaleX * 2; + float height = scaleY * 2; + + float scale = _channel.TextureManager.RenderTargetScale; + if (scale != 1f) + { + x *= scale; + y *= scale; + width *= scale; + height *= scale; + } + + RectangleF region = new RectangleF(x, y, width, height); + + ViewportSwizzle swizzleX = transform.UnpackSwizzleX(); + ViewportSwizzle swizzleY = transform.UnpackSwizzleY(); + ViewportSwizzle swizzleZ = transform.UnpackSwizzleZ(); + ViewportSwizzle swizzleW = transform.UnpackSwizzleW(); + + float depthNear = extents.DepthNear; + float depthFar = extents.DepthFar; + + if (transform.ScaleZ < 0) + { + float temp = depthNear; + depthNear = depthFar; + depthFar = temp; + } + + viewports[index] = new Viewport(region, swizzleX, swizzleY, swizzleZ, swizzleW, depthNear, depthFar); + } + + _context.Renderer.Pipeline.SetViewports(0, viewports); + } + + /// <summary> + /// Updates host depth bias (also called polygon offset) state based on current GPU state. + /// </summary> + private void UpdateDepthBiasState() + { + var depthBias = _state.State.DepthBiasState; + + float factor = _state.State.DepthBiasFactor; + float units = _state.State.DepthBiasUnits; + float clamp = _state.State.DepthBiasClamp; + + PolygonModeMask enables; + + enables = (depthBias.PointEnable ? PolygonModeMask.Point : 0); + enables |= (depthBias.LineEnable ? PolygonModeMask.Line : 0); + enables |= (depthBias.FillEnable ? PolygonModeMask.Fill : 0); + + _context.Renderer.Pipeline.SetDepthBias(enables, factor, units / 2f, clamp); + } + + /// <summary> + /// Updates host stencil test state based on current GPU state. + /// </summary> + private void UpdateStencilTestState() + { + var backMasks = _state.State.StencilBackMasks; + var test = _state.State.StencilTestState; + var backTest = _state.State.StencilBackTestState; + + CompareOp backFunc; + StencilOp backSFail; + StencilOp backDpPass; + StencilOp backDpFail; + int backFuncRef; + int backFuncMask; + int backMask; + + if (backTest.TwoSided) + { + backFunc = backTest.BackFunc; + backSFail = backTest.BackSFail; + backDpPass = backTest.BackDpPass; + backDpFail = backTest.BackDpFail; + backFuncRef = backMasks.FuncRef; + backFuncMask = backMasks.FuncMask; + backMask = backMasks.Mask; + } + else + { + backFunc = test.FrontFunc; + backSFail = test.FrontSFail; + backDpPass = test.FrontDpPass; + backDpFail = test.FrontDpFail; + backFuncRef = test.FrontFuncRef; + backFuncMask = test.FrontFuncMask; + backMask = test.FrontMask; + } + + _context.Renderer.Pipeline.SetStencilTest(new StencilTestDescriptor( + test.Enable, + test.FrontFunc, + test.FrontSFail, + test.FrontDpPass, + test.FrontDpFail, + test.FrontFuncRef, + test.FrontFuncMask, + test.FrontMask, + backFunc, + backSFail, + backDpPass, + backDpFail, + backFuncRef, + backFuncMask, + backMask)); + } + + /// <summary> + /// Updates user-defined clipping based on the guest GPU state. + /// </summary> + private void UpdateUserClipState() + { + uint clipMask = _state.State.ClipDistanceEnable & _vsClipDistancesWritten; + + for (int i = 0; i < Constants.TotalClipDistances; ++i) + { + _context.Renderer.Pipeline.SetUserClipDistance(i, (clipMask & (1 << i)) != 0); + } + } + + /// <summary> + /// Updates current sampler pool address and size based on guest GPU state. + /// </summary> + private void UpdateSamplerPoolState() + { + var texturePool = _state.State.TexturePoolState; + var samplerPool = _state.State.SamplerPoolState; + + var samplerIndex = _state.State.SamplerIndex; + + int maximumId = samplerIndex == SamplerIndex.ViaHeaderIndex + ? texturePool.MaximumId + : samplerPool.MaximumId; + + _channel.TextureManager.SetGraphicsSamplerPool(samplerPool.Address.Pack(), maximumId, samplerIndex); + } + + /// <summary> + /// Updates current texture pool address and size based on guest GPU state. + /// </summary> + private void UpdateTexturePoolState() + { + var texturePool = _state.State.TexturePoolState; + + _channel.TextureManager.SetGraphicsTexturePool(texturePool.Address.Pack(), texturePool.MaximumId); + _channel.TextureManager.SetGraphicsTextureBufferIndex((int)_state.State.TextureBufferIndex); + } + + /// <summary> + /// Updates host vertex attributes based on guest GPU state. + /// </summary> + private void UpdateVertexAttribState() + { + Span<VertexAttribDescriptor> vertexAttribs = stackalloc VertexAttribDescriptor[Constants.TotalVertexAttribs]; + + for (int index = 0; index < Constants.TotalVertexAttribs; index++) + { + var vertexAttrib = _state.State.VertexAttribState[index]; + + if (!FormatTable.TryGetAttribFormat(vertexAttrib.UnpackFormat(), out Format format)) + { + Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}."); + + format = Format.R32G32B32A32Float; + } + + vertexAttribs[index] = new VertexAttribDescriptor( + vertexAttrib.UnpackBufferIndex(), + vertexAttrib.UnpackOffset(), + vertexAttrib.UnpackIsConstant(), + format); + } + + _context.Renderer.Pipeline.SetVertexAttribs(vertexAttribs); + } + + /// <summary> + /// Updates host line width based on guest GPU state. + /// </summary> + private void UpdateLineState() + { + float width = _state.State.LineWidthSmooth; + bool smooth = _state.State.LineSmoothEnable; + + _context.Renderer.Pipeline.SetLineParameters(width, smooth); + } + + /// <summary> + /// Updates host point size based on guest GPU state. + /// </summary> + private void UpdatePointState() + { + float size = _state.State.PointSize; + bool isProgramPointSize = _state.State.VertexProgramPointSize; + bool enablePointSprite = _state.State.PointSpriteEnable; + + // TODO: Need to figure out a way to map PointCoordReplace enable bit. + Origin origin = (_state.State.PointCoordReplace & 4) == 0 ? Origin.LowerLeft : Origin.UpperLeft; + + _context.Renderer.Pipeline.SetPointParameters(size, isProgramPointSize, enablePointSprite, origin); + } + + /// <summary> + /// Updates host primitive restart based on guest GPU state. + /// </summary> + private void UpdatePrimitiveRestartState() + { + PrimitiveRestartState primitiveRestart = _state.State.PrimitiveRestartState; + + _context.Renderer.Pipeline.SetPrimitiveRestart(primitiveRestart.Enable, primitiveRestart.Index); + } + + /// <summary> + /// Updates host index buffer binding based on guest GPU state. + /// </summary> + private void UpdateIndexBufferState() + { + var indexBuffer = _state.State.IndexBufferState; + + if (_drawState.IndexCount == 0) + { + return; + } + + ulong gpuVa = indexBuffer.Address.Pack(); + + // Do not use the end address to calculate the size, because + // the result may be much larger than the real size of the index buffer. + ulong size = (ulong)(_drawState.FirstIndex + _drawState.IndexCount); + + switch (indexBuffer.Type) + { + case IndexType.UShort: size *= 2; break; + case IndexType.UInt: size *= 4; break; + } + + _channel.BufferManager.SetIndexBuffer(gpuVa, size, indexBuffer.Type); + } + + /// <summary> + /// Updates host vertex buffer bindings based on guest GPU state. + /// </summary> + private void UpdateVertexBufferState() + { + _drawState.IsAnyVbInstanced = false; + + for (int index = 0; index < Constants.TotalVertexBuffers; index++) + { + var vertexBuffer = _state.State.VertexBufferState[index]; + + if (!vertexBuffer.UnpackEnable()) + { + _channel.BufferManager.SetVertexBuffer(index, 0, 0, 0, 0); + + continue; + } + + GpuVa endAddress = _state.State.VertexBufferEndAddress[index]; + + ulong address = vertexBuffer.Address.Pack(); + + int stride = vertexBuffer.UnpackStride(); + + bool instanced = _state.State.VertexBufferInstanced[index]; + + int divisor = instanced ? vertexBuffer.Divisor : 0; + + _drawState.IsAnyVbInstanced |= divisor != 0; + + ulong size; + + if (_drawState.IbStreamer.HasInlineIndexData || _drawState.DrawIndexed || stride == 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 = endAddress.Pack() - address + 1; + } + else + { + // For non-indexed draws, we can guess the size from the vertex count + // and stride. + int firstInstance = (int)_state.State.FirstInstance; + + var drawState = _state.State.VertexBufferDrawState; + + size = (ulong)((firstInstance + drawState.First + drawState.Count) * stride); + } + + _channel.BufferManager.SetVertexBuffer(index, address, size, stride, divisor); + } + } + + /// <summary> + /// Updates host face culling and orientation based on guest GPU state. + /// </summary> + private void UpdateFaceState() + { + var yControl = _state.State.YControl; + var face = _state.State.FaceState; + + _context.Renderer.Pipeline.SetFaceCulling(face.CullEnable, face.CullFace); + + UpdateFrontFace(yControl, face.FrontFace); + } + + /// <summary> + /// Updates the front face based on the current front face and the origin. + /// </summary> + /// <param name="yControl">Y control register value, where the origin is located</param> + /// <param name="frontFace">Front face</param> + private void UpdateFrontFace(YControl yControl, FrontFace frontFace) + { + bool isUpperLeftOrigin = !yControl.HasFlag(YControl.TriangleRastFlip); + + if (isUpperLeftOrigin) + { + frontFace = frontFace == FrontFace.CounterClockwise ? FrontFace.Clockwise : FrontFace.CounterClockwise; + } + + _context.Renderer.Pipeline.SetFrontFace(frontFace); + } + + /// <summary> + /// Updates host render target color masks, based on guest GPU state. + /// This defines which color channels are written to each color buffer. + /// </summary> + private void UpdateRtColorMask() + { + bool rtColorMaskShared = _state.State.RtColorMaskShared; + + Span<uint> componentMasks = stackalloc uint[Constants.TotalRenderTargets]; + + for (int index = 0; index < Constants.TotalRenderTargets; index++) + { + var colorMask = _state.State.RtColorMask[rtColorMaskShared ? 0 : index]; + + uint componentMask; + + componentMask = (colorMask.UnpackRed() ? 1u : 0u); + componentMask |= (colorMask.UnpackGreen() ? 2u : 0u); + componentMask |= (colorMask.UnpackBlue() ? 4u : 0u); + componentMask |= (colorMask.UnpackAlpha() ? 8u : 0u); + + componentMasks[index] = componentMask; + } + + _context.Renderer.Pipeline.SetRenderTargetColorMasks(componentMasks); + } + + /// <summary> + /// Updates host render target color buffer blending state, based on guest state. + /// </summary> + private void UpdateBlendState() + { + bool blendIndependent = _state.State.BlendIndependent; + ColorF blendConstant = _state.State.BlendConstant; + + for (int index = 0; index < Constants.TotalRenderTargets; index++) + { + BlendDescriptor descriptor; + + if (blendIndependent) + { + bool enable = _state.State.BlendEnable[index]; + var blend = _state.State.BlendState[index]; + + descriptor = new BlendDescriptor( + enable, + blendConstant, + blend.ColorOp, + blend.ColorSrcFactor, + blend.ColorDstFactor, + blend.AlphaOp, + blend.AlphaSrcFactor, + blend.AlphaDstFactor); + } + else + { + bool enable = _state.State.BlendEnable[0]; + var blend = _state.State.BlendStateCommon; + + descriptor = new BlendDescriptor( + enable, + blendConstant, + blend.ColorOp, + blend.ColorSrcFactor, + blend.ColorDstFactor, + blend.AlphaOp, + blend.AlphaSrcFactor, + blend.AlphaDstFactor); + } + + _context.Renderer.Pipeline.SetBlendState(index, descriptor); + } + } + + /// <summary> + /// Updates host logical operation state, based on guest state. + /// </summary> + private void UpdateLogicOpState() + { + LogicalOpState logicOpState = _state.State.LogicOpState; + + _context.Renderer.Pipeline.SetLogicOpState(logicOpState.Enable, logicOpState.LogicalOp); + } + + /// <summary> + /// Updates host shaders based on the guest GPU state. + /// </summary> + private void UpdateShaderState() + { + ShaderAddresses addresses = new ShaderAddresses(); + + Span<ShaderAddresses> addressesSpan = MemoryMarshal.CreateSpan(ref addresses, 1); + + Span<ulong> addressesArray = MemoryMarshal.Cast<ShaderAddresses, ulong>(addressesSpan); + + ulong baseAddress = _state.State.ShaderBaseAddress.Pack(); + + for (int index = 0; index < 6; index++) + { + var shader = _state.State.ShaderState[index]; + + if (!shader.UnpackEnable() && index != 1) + { + continue; + } + + addressesArray[index] = baseAddress + shader.Offset; + } + + GpuAccessorState gas = new GpuAccessorState( + _state.State.TexturePoolState.Address.Pack(), + _state.State.TexturePoolState.MaximumId, + (int)_state.State.TextureBufferIndex, + _state.State.EarlyZForce, + _drawState.Topology); + + ShaderBundle gs = _channel.MemoryManager.Physical.ShaderCache.GetGraphicsShader(ref _state.State, _channel, gas, addresses); + + byte oldVsClipDistancesWritten = _vsClipDistancesWritten; + + _drawState.VsUsesInstanceId = gs.Shaders[0]?.Info.UsesInstanceId ?? false; + _vsClipDistancesWritten = gs.Shaders[0]?.Info.ClipDistancesWritten ?? 0; + + if (oldVsClipDistancesWritten != _vsClipDistancesWritten) + { + UpdateUserClipState(); + } + + int storageBufferBindingsCount = 0; + int uniformBufferBindingsCount = 0; + + for (int stage = 0; stage < Constants.ShaderStages; stage++) + { + ShaderProgramInfo info = gs.Shaders[stage]?.Info; + + _currentProgramInfo[stage] = info; + + if (info == null) + { + _channel.TextureManager.SetGraphicsTextures(stage, Array.Empty<TextureBindingInfo>()); + _channel.TextureManager.SetGraphicsImages(stage, Array.Empty<TextureBindingInfo>()); + _channel.BufferManager.SetGraphicsStorageBufferBindings(stage, null); + _channel.BufferManager.SetGraphicsUniformBufferBindings(stage, null); + continue; + } + + var textureBindings = new TextureBindingInfo[info.Textures.Count]; + + for (int index = 0; index < info.Textures.Count; index++) + { + var descriptor = info.Textures[index]; + + Target target = ShaderTexture.GetTarget(descriptor.Type); + + textureBindings[index] = new TextureBindingInfo( + target, + descriptor.Binding, + descriptor.CbufSlot, + descriptor.HandleIndex, + descriptor.Flags); + } + + _channel.TextureManager.SetGraphicsTextures(stage, textureBindings); + + var imageBindings = new TextureBindingInfo[info.Images.Count]; + + for (int index = 0; index < info.Images.Count; index++) + { + var descriptor = info.Images[index]; + + Target target = ShaderTexture.GetTarget(descriptor.Type); + Format format = ShaderTexture.GetFormat(descriptor.Format); + + imageBindings[index] = new TextureBindingInfo( + target, + format, + descriptor.Binding, + descriptor.CbufSlot, + descriptor.HandleIndex, + descriptor.Flags); + } + + _channel.TextureManager.SetGraphicsImages(stage, imageBindings); + + _channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers); + _channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers); + + if (info.SBuffers.Count != 0) + { + storageBufferBindingsCount = Math.Max(storageBufferBindingsCount, info.SBuffers.Max(x => x.Binding) + 1); + } + + if (info.CBuffers.Count != 0) + { + uniformBufferBindingsCount = Math.Max(uniformBufferBindingsCount, info.CBuffers.Max(x => x.Binding) + 1); + } + } + + _channel.BufferManager.SetGraphicsStorageBufferBindingsCount(storageBufferBindingsCount); + _channel.BufferManager.SetGraphicsUniformBufferBindingsCount(uniformBufferBindingsCount); + + _context.Renderer.Pipeline.SetProgram(gs.HostProgram); + } + + /// <summary> + /// Forces the shaders to be rebound on the next draw. + /// </summary> + public void ForceShaderUpdate() + { + _updateTracker.ForceDirty(ShaderStateIndex); + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs new file mode 100644 index 00000000..ad6a1d0e --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs @@ -0,0 +1,428 @@ +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.Gpu.Engine.InlineToMemory; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// <summary> + /// Represents a 3D engine class. + /// </summary> + class ThreedClass : IDeviceState + { + private readonly GpuContext _context; + private readonly DeviceStateWithShadow<ThreedClassState> _state; + + private readonly InlineToMemoryClass _i2mClass; + private readonly DrawManager _drawManager; + private readonly SemaphoreUpdater _semaphoreUpdater; + private readonly ConstantBufferUpdater _cbUpdater; + private readonly StateUpdater _stateUpdater; + + /// <summary> + /// Creates a new instance of the 3D engine class. + /// </summary> + /// <param name="context">GPU context</param> + /// <param name="channel">GPU channel</param> + public ThreedClass(GpuContext context, GpuChannel channel) + { + _context = context; + _state = new DeviceStateWithShadow<ThreedClassState>(new Dictionary<string, RwCallback> + { + { nameof(ThreedClassState.LaunchDma), new RwCallback(LaunchDma, null) }, + { nameof(ThreedClassState.LoadInlineData), new RwCallback(LoadInlineData, null) }, + { nameof(ThreedClassState.SyncpointAction), new RwCallback(IncrementSyncpoint, null) }, + { nameof(ThreedClassState.TextureBarrier), new RwCallback(TextureBarrier, null) }, + { nameof(ThreedClassState.TextureBarrierTiled), new RwCallback(TextureBarrierTiled, null) }, + { nameof(ThreedClassState.VbElementU8), new RwCallback(VbElementU8, null) }, + { nameof(ThreedClassState.VbElementU16), new RwCallback(VbElementU16, null) }, + { nameof(ThreedClassState.VbElementU32), new RwCallback(VbElementU32, null) }, + { nameof(ThreedClassState.ResetCounter), new RwCallback(ResetCounter, null) }, + { nameof(ThreedClassState.RenderEnableCondition), new RwCallback(null, Zero) }, + { nameof(ThreedClassState.DrawEnd), new RwCallback(DrawEnd, null) }, + { nameof(ThreedClassState.DrawBegin), new RwCallback(DrawBegin, null) }, + { nameof(ThreedClassState.DrawIndexedSmall), new RwCallback(DrawIndexedSmall, null) }, + { nameof(ThreedClassState.DrawIndexedSmall2), new RwCallback(DrawIndexedSmall2, null) }, + { nameof(ThreedClassState.DrawIndexedSmallIncInstance), new RwCallback(DrawIndexedSmallIncInstance, null) }, + { nameof(ThreedClassState.DrawIndexedSmallIncInstance2), new RwCallback(DrawIndexedSmallIncInstance2, null) }, + { nameof(ThreedClassState.IndexBufferCount), new RwCallback(SetIndexBufferCount, null) }, + { nameof(ThreedClassState.Clear), new RwCallback(Clear, null) }, + { nameof(ThreedClassState.SemaphoreControl), new RwCallback(Report, null) }, + { nameof(ThreedClassState.SetFalcon04), new RwCallback(SetFalcon04, null) }, + { nameof(ThreedClassState.UniformBufferUpdateData), new RwCallback(ConstantBufferUpdate, null) }, + { nameof(ThreedClassState.UniformBufferBindVertex), new RwCallback(ConstantBufferBindVertex, null) }, + { nameof(ThreedClassState.UniformBufferBindTessControl), new RwCallback(ConstantBufferBindTessControl, null) }, + { nameof(ThreedClassState.UniformBufferBindTessEvaluation), new RwCallback(ConstantBufferBindTessEvaluation, null) }, + { nameof(ThreedClassState.UniformBufferBindGeometry), new RwCallback(ConstantBufferBindGeometry, null) }, + { nameof(ThreedClassState.UniformBufferBindFragment), new RwCallback(ConstantBufferBindFragment, null) } + }); + + _i2mClass = new InlineToMemoryClass(context, channel, initializeState: false); + + var drawState = new DrawState(); + + _drawManager = new DrawManager(context, channel, _state, drawState); + _semaphoreUpdater = new SemaphoreUpdater(context, channel, _state); + _cbUpdater = new ConstantBufferUpdater(channel, _state); + _stateUpdater = new StateUpdater(context, channel, _state, drawState); + + // This defaults to "always", even without any register write. + // Reads just return 0, regardless of what was set there. + _state.State.RenderEnableCondition = Condition.Always; + } + + /// <summary> + /// Reads data from the class registers. + /// </summary> + /// <param name="offset">Register byte offset</param> + /// <returns>Data at the specified offset</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Read(int offset) => _state.Read(offset); + + /// <summary> + /// Writes data to the class registers. + /// </summary> + /// <param name="offset">Register byte offset</param> + /// <param name="data">Data to be written</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(int offset, int data) + { + _state.WriteWithRedundancyCheck(offset, data, out bool valueChanged); + + if (valueChanged) + { + _stateUpdater.SetDirty(offset); + } + } + + /// <summary> + /// Sets the shadow ram control value of all sub-channels. + /// </summary> + /// <param name="control">New shadow ram control value</param> + public void SetShadowRamControl(int control) + { + _state.State.SetMmeShadowRamControl = (uint)control; + } + + /// <summary> + /// Updates current host state for all registers modified since the last call to this method. + /// </summary> + public void UpdateState() + { + _cbUpdater.FlushUboDirty(); + _stateUpdater.Update(); + } + + /// <summary> + /// Updates current host state for all registers modified since the last call to this method. + /// </summary> + /// <param name="mask">Mask where each bit set indicates that the respective state group index should be checked</param> + public void UpdateState(ulong mask) + { + _stateUpdater.Update(mask); + } + + /// <summary> + /// Updates render targets (color and depth-stencil buffers) based on current render target state. + /// </summary> + /// <param name="useControl">Use draw buffers information from render target control register</param> + /// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param> + public void UpdateRenderTargetState(bool useControl, int singleUse = -1) + { + _stateUpdater.UpdateRenderTargetState(useControl, singleUse); + } + + /// <summary> + /// Marks the entire state as dirty, forcing a full host state update before the next draw. + /// </summary> + public void ForceStateDirty() + { + _stateUpdater.SetAllDirty(); + } + + /// <summary> + /// Forces the shaders to be rebound on the next draw. + /// </summary> + public void ForceShaderUpdate() + { + _stateUpdater.ForceShaderUpdate(); + } + + /// <summary> + /// Flushes any queued UBO updates. + /// </summary> + public void FlushUboDirty() + { + _cbUpdater.FlushUboDirty(); + } + + /// <summary> + /// Perform any deferred draws. + /// </summary> + public void PerformDeferredDraws() + { + _drawManager.PerformDeferredDraws(); + } + + /// <summary> + /// Updates the currently bound constant buffer. + /// </summary> + /// <param name="data">Data to be written to the buffer</param> + public void ConstantBufferUpdate(ReadOnlySpan<int> data) + { + _cbUpdater.Update(data); + } + + /// <summary> + /// Launches the Inline-to-Memory DMA copy operation. + /// </summary> + /// <param name="argument">Method call argument</param> + private void LaunchDma(int argument) + { + _i2mClass.LaunchDma(ref Unsafe.As<ThreedClassState, InlineToMemoryClassState>(ref _state.State), argument); + } + + /// <summary> + /// Pushes a word of data to the Inline-to-Memory engine. + /// </summary> + /// <param name="argument">Method call argument</param> + private void LoadInlineData(int argument) + { + _i2mClass.LoadInlineData(argument); + } + + /// <summary> + /// Performs an incrementation on a syncpoint. + /// </summary> + /// <param name="argument">Method call argument</param> + public void IncrementSyncpoint(int argument) + { + uint syncpointId = (uint)argument & 0xFFFF; + + _context.CreateHostSyncIfNeeded(); + _context.Renderer.UpdateCounters(); // Poll the query counters, the game may want an updated result. + _context.Synchronization.IncrementSyncpoint(syncpointId); + } + + /// <summary> + /// Issues a texture barrier. + /// This waits until previous texture writes from the GPU to finish, before + /// performing new operations with said textures. + /// </summary> + /// <param name="argument">Method call argument (unused)</param> + private void TextureBarrier(int argument) + { + _context.Renderer.Pipeline.TextureBarrier(); + } + + /// <summary> + /// Issues a texture barrier. + /// This waits until previous texture writes from the GPU to finish, before + /// performing new operations with said textures. + /// This performs a per-tile wait, it is only valid if both the previous write + /// and current access has the same access patterns. + /// This may be faster than the regular barrier on tile-based rasterizers. + /// </summary> + /// <param name="argument">Method call argument (unused)</param> + private void TextureBarrierTiled(int argument) + { + _context.Renderer.Pipeline.TextureBarrierTiled(); + } + + /// <summary> + /// Pushes four 8-bit index buffer elements. + /// </summary> + /// <param name="argument">Method call argument</param> + private void VbElementU8(int argument) + { + _drawManager.VbElementU8(argument); + } + + /// <summary> + /// Pushes two 16-bit index buffer elements. + /// </summary> + /// <param name="argument">Method call argument</param> + private void VbElementU16(int argument) + { + _drawManager.VbElementU16(argument); + } + + /// <summary> + /// Pushes one 32-bit index buffer element. + /// </summary> + /// <param name="argument">Method call argument</param> + private void VbElementU32(int argument) + { + _drawManager.VbElementU32(argument); + } + + /// <summary> + /// Resets the value of an internal GPU counter back to zero. + /// </summary> + /// <param name="argument">Method call argument</param> + private void ResetCounter(int argument) + { + _semaphoreUpdater.ResetCounter(argument); + } + + /// <summary> + /// Finishes the draw call. + /// This draws geometry on the bound buffers based on the current GPU state. + /// </summary> + /// <param name="argument">Method call argument</param> + private void DrawEnd(int argument) + { + _drawManager.DrawEnd(this, argument); + } + + /// <summary> + /// Starts draw. + /// This sets primitive type and instanced draw parameters. + /// </summary> + /// <param name="argument">Method call argument</param> + private void DrawBegin(int argument) + { + _drawManager.DrawBegin(argument); + } + + /// <summary> + /// Sets the index buffer count. + /// This also sets internal state that indicates that the next draw is an indexed draw. + /// </summary> + /// <param name="argument">Method call argument</param> + private void SetIndexBufferCount(int argument) + { + _drawManager.SetIndexBufferCount(argument); + } + + /// <summary> + /// Performs a indexed draw with a low number of index buffer elements. + /// </summary> + /// <param name="argument">Method call argument</param> + private void DrawIndexedSmall(int argument) + { + _drawManager.DrawIndexedSmall(this, argument); + } + + /// <summary> + /// Performs a indexed draw with a low number of index buffer elements. + /// </summary> + /// <param name="argument">Method call argument</param> + private void DrawIndexedSmall2(int argument) + { + _drawManager.DrawIndexedSmall2(this, argument); + } + + /// <summary> + /// Performs a indexed draw with a low number of index buffer elements, + /// while also pre-incrementing the current instance value. + /// </summary> + /// <param name="argument">Method call argument</param> + private void DrawIndexedSmallIncInstance(int argument) + { + _drawManager.DrawIndexedSmallIncInstance(this, argument); + } + + /// <summary> + /// Performs a indexed draw with a low number of index buffer elements, + /// while also pre-incrementing the current instance value. + /// </summary> + /// <param name="argument">Method call argument</param> + private void DrawIndexedSmallIncInstance2(int argument) + { + _drawManager.DrawIndexedSmallIncInstance2(this, argument); + } + + /// <summary> + /// Clears the current color and depth-stencil buffers. + /// Which buffers should be cleared is also specified on the argument. + /// </summary> + /// <param name="argument">Method call argument</param> + private void Clear(int argument) + { + _drawManager.Clear(this, argument); + } + + /// <summary> + /// Writes a GPU counter to guest memory. + /// </summary> + /// <param name="argument">Method call argument</param> + private void Report(int argument) + { + _semaphoreUpdater.Report(argument); + } + + /// <summary> + /// Performs high-level emulation of Falcon microcode function number "4". + /// </summary> + /// <param name="argument">Method call argument</param> + private void SetFalcon04(int argument) + { + _state.State.SetMmeShadowScratch[0] = 1; + } + + /// <summary> + /// Updates the uniform buffer data with inline data. + /// </summary> + /// <param name="argument">New uniform buffer data word</param> + private void ConstantBufferUpdate(int argument) + { + _cbUpdater.Update(argument); + } + + /// <summary> + /// Binds a uniform buffer for the vertex shader stage. + /// </summary> + /// <param name="argument">Method call argument</param> + private void ConstantBufferBindVertex(int argument) + { + _cbUpdater.BindVertex(argument); + } + + /// <summary> + /// Binds a uniform buffer for the tessellation control shader stage. + /// </summary> + /// <param name="argument">Method call argument</param> + private void ConstantBufferBindTessControl(int argument) + { + _cbUpdater.BindTessControl(argument); + } + + /// <summary> + /// Binds a uniform buffer for the tessellation evaluation shader stage. + /// </summary> + /// <param name="argument">Method call argument</param> + private void ConstantBufferBindTessEvaluation(int argument) + { + _cbUpdater.BindTessEvaluation(argument); + } + + /// <summary> + /// Binds a uniform buffer for the geometry shader stage. + /// </summary> + /// <param name="argument">Method call argument</param> + private void ConstantBufferBindGeometry(int argument) + { + _cbUpdater.BindGeometry(argument); + } + + /// <summary> + /// Binds a uniform buffer for the fragment shader stage. + /// </summary> + /// <param name="argument">Method call argument</param> + private void ConstantBufferBindFragment(int argument) + { + _cbUpdater.BindFragment(argument); + } + + /// <summary> + /// Generic register read function that just returns 0. + /// </summary> + /// <returns>Zero</returns> + private static int Zero() + { + return 0; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs new file mode 100644 index 00000000..a6392e3d --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs @@ -0,0 +1,861 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.InlineToMemory; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Image; +using System; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// <summary> + /// Shader stage name. + /// </summary> + enum ShaderType + { + Vertex, + TessellationControl, + TessellationEvaluation, + Geometry, + Fragment + } + + /// <summary> + /// Transform feedback buffer state. + /// </summary> + struct TfBufferState + { +#pragma warning disable CS0649 + public Boolean32 Enable; + public GpuVa Address; + public int Size; + public int Offset; + public uint Padding0; + public uint Padding1; + public uint Padding2; +#pragma warning restore CS0649 + } + + /// <summary> + /// Transform feedback state. + /// </summary> + struct TfState + { +#pragma warning disable CS0649 + public int BufferIndex; + public int VaryingsCount; + public int Stride; + public uint Padding; +#pragma warning restore CS0649 + } + + /// <summary> + /// Render target color buffer state. + /// </summary> + struct RtColorState + { +#pragma warning disable CS0649 + public GpuVa Address; + public int WidthOrStride; + public int Height; + public ColorFormat Format; + public MemoryLayout MemoryLayout; + public int Depth; + public int LayerSize; + public int BaseLayer; + public int Unknown0x24; + public int Padding0; + public int Padding1; + public int Padding2; + public int Padding3; + public int Padding4; + public int Padding5; +#pragma warning restore CS0649 + } + + /// <summary> + /// Viewport transform parameters, for viewport transformation. + /// </summary> + struct ViewportTransform + { +#pragma warning disable CS0649 + public float ScaleX; + public float ScaleY; + public float ScaleZ; + public float TranslateX; + public float TranslateY; + public float TranslateZ; + public uint Swizzle; + public uint SubpixelPrecisionBias; +#pragma warning restore CS0649 + + /// <summary> + /// Unpacks viewport swizzle of the position X component. + /// </summary> + /// <returns>Swizzle enum value</returns> + public ViewportSwizzle UnpackSwizzleX() + { + return (ViewportSwizzle)(Swizzle & 7); + } + + /// <summary> + /// Unpacks viewport swizzle of the position Y component. + /// </summary> + /// <returns>Swizzle enum value</returns> + public ViewportSwizzle UnpackSwizzleY() + { + return (ViewportSwizzle)((Swizzle >> 4) & 7); + } + + /// <summary> + /// Unpacks viewport swizzle of the position Z component. + /// </summary> + /// <returns>Swizzle enum value</returns> + public ViewportSwizzle UnpackSwizzleZ() + { + return (ViewportSwizzle)((Swizzle >> 8) & 7); + } + + /// <summary> + /// Unpacks viewport swizzle of the position W component. + /// </summary> + /// <returns>Swizzle enum value</returns> + public ViewportSwizzle UnpackSwizzleW() + { + return (ViewportSwizzle)((Swizzle >> 12) & 7); + } + } + + /// <summary> + /// Viewport extents for viewport clipping, also includes depth range. + /// </summary> + struct ViewportExtents + { +#pragma warning disable CS0649 + public ushort X; + public ushort Width; + public ushort Y; + public ushort Height; + public float DepthNear; + public float DepthFar; +#pragma warning restore CS0649 + } + + /// <summary> + /// Draw state for non-indexed draws. + /// </summary> + struct VertexBufferDrawState + { +#pragma warning disable CS0649 + public int First; + public int Count; +#pragma warning restore CS0649 + } + + /// <summary> + /// Color buffer clear color. + /// </summary> + struct ClearColors + { +#pragma warning disable CS0649 + public float Red; + public float Green; + public float Blue; + public float Alpha; +#pragma warning restore CS0649 + } + + /// <summary> + /// Depth bias (also called polygon offset) parameters. + /// </summary> + struct DepthBiasState + { +#pragma warning disable CS0649 + public Boolean32 PointEnable; + public Boolean32 LineEnable; + public Boolean32 FillEnable; +#pragma warning restore CS0649 + } + + /// <summary> + /// Scissor state. + /// </summary> + struct ScissorState + { +#pragma warning disable CS0649 + public Boolean32 Enable; + public ushort X1; + public ushort X2; + public ushort Y1; + public ushort Y2; + public uint Padding; +#pragma warning restore CS0649 + } + + /// <summary> + /// Stencil test masks for back tests. + /// </summary> + struct StencilBackMasks + { +#pragma warning disable CS0649 + public int FuncRef; + public int Mask; + public int FuncMask; +#pragma warning restore CS0649 + } + + /// <summary> + /// Render target depth-stencil buffer state. + /// </summary> + struct RtDepthStencilState + { +#pragma warning disable CS0649 + public GpuVa Address; + public ZetaFormat Format; + public MemoryLayout MemoryLayout; + public int LayerSize; +#pragma warning restore CS0649 + } + + /// <summary> + /// Screen scissor state. + /// </summary> + struct ScreenScissorState + { +#pragma warning disable CS0649 + public ushort X; + public ushort Width; + public ushort Y; + public ushort Height; +#pragma warning restore CS0649 + } + + /// <summary> + /// Vertex buffer attribute state. + /// </summary> + struct VertexAttribState + { +#pragma warning disable CS0649 + public uint Attribute; +#pragma warning restore CS0649 + + /// <summary> + /// Unpacks the index of the vertex buffer this attribute belongs to. + /// </summary> + /// <returns>Vertex buffer index</returns> + public int UnpackBufferIndex() + { + return (int)(Attribute & 0x1f); + } + + /// <summary> + /// Unpacks the attribute constant flag. + /// </summary> + /// <returns>True if the attribute is constant, false otherwise</returns> + public bool UnpackIsConstant() + { + return (Attribute & 0x40) != 0; + } + + /// <summary> + /// Unpacks the offset, in bytes, of the attribute on the vertex buffer. + /// </summary> + /// <returns>Attribute offset in bytes</returns> + public int UnpackOffset() + { + return (int)((Attribute >> 7) & 0x3fff); + } + + /// <summary> + /// Unpacks the Maxwell attribute format integer. + /// </summary> + /// <returns>Attribute format integer</returns> + public uint UnpackFormat() + { + return Attribute & 0x3fe00000; + } + } + + /// <summary> + /// Render target draw buffers control. + /// </summary> + struct RtControl + { +#pragma warning disable CS0649 + public uint Packed; +#pragma warning restore CS0649 + + /// <summary> + /// Unpacks the number of active draw buffers. + /// </summary> + /// <returns>Number of active draw buffers</returns> + public int UnpackCount() + { + return (int)(Packed & 0xf); + } + + /// <summary> + /// Unpacks the color attachment index for a given draw buffer. + /// </summary> + /// <param name="index">Index of the draw buffer</param> + /// <returns>Attachment index</returns> + public int UnpackPermutationIndex(int index) + { + return (int)((Packed >> (4 + index * 3)) & 7); + } + } + + /// <summary> + /// 3D, 2D or 1D texture size. + /// </summary> + struct Size3D + { +#pragma warning disable CS0649 + public int Width; + public int Height; + public int Depth; +#pragma warning restore CS0649 + } + + /// <summary> + /// Stencil front test state and masks. + /// </summary> + struct StencilTestState + { +#pragma warning disable CS0649 + public Boolean32 Enable; + public StencilOp FrontSFail; + public StencilOp FrontDpFail; + public StencilOp FrontDpPass; + public CompareOp FrontFunc; + public int FrontFuncRef; + public int FrontFuncMask; + public int FrontMask; +#pragma warning restore CS0649 + } + + /// <summary> + /// Screen Y control register. + /// </summary> + [Flags] + enum YControl + { + NegateY = 1 << 0, + TriangleRastFlip = 1 << 4 + } + + /// <summary> + /// Condition for conditional rendering. + /// </summary> + enum Condition + { + Never, + Always, + ResultNonZero, + Equal, + NotEqual + } + + /// <summary> + /// Texture or sampler pool state. + /// </summary> + struct PoolState + { +#pragma warning disable CS0649 + public GpuVa Address; + public int MaximumId; +#pragma warning restore CS0649 + } + + /// <summary> + /// Stencil back test state. + /// </summary> + struct StencilBackTestState + { +#pragma warning disable CS0649 + public Boolean32 TwoSided; + public StencilOp BackSFail; + public StencilOp BackDpFail; + public StencilOp BackDpPass; + public CompareOp BackFunc; +#pragma warning restore CS0649 + } + + /// <summary> + /// Primitive restart state. + /// </summary> + struct PrimitiveRestartState + { +#pragma warning disable CS0649 + public Boolean32 Enable; + public int Index; +#pragma warning restore CS0649 + } + + /// <summary> + /// GPU index buffer state. + /// This is used on indexed draws. + /// </summary> + struct IndexBufferState + { +#pragma warning disable CS0649 + public GpuVa Address; + public GpuVa EndAddress; + public IndexType Type; + public int First; +#pragma warning restore CS0649 + } + + /// <summary> + /// Face culling and orientation parameters. + /// </summary> + struct FaceState + { +#pragma warning disable CS0649 + public Boolean32 CullEnable; + public FrontFace FrontFace; + public Face CullFace; +#pragma warning restore CS0649 + } + + /// <summary> + /// View volume clip control. + /// </summary> + [Flags] + enum ViewVolumeClipControl + { + ForceDepthRangeZeroToOne = 1 << 0, + DepthClampDisabled = 1 << 11 + } + + /// <summary> + /// Logical operation state. + /// </summary> + struct LogicalOpState + { +#pragma warning disable CS0649 + public Boolean32 Enable; + public LogicalOp LogicalOp; +#pragma warning restore CS0649 + } + + /// <summary> + /// Render target color buffer mask. + /// This defines which color channels are written to the color buffer. + /// </summary> + struct RtColorMask + { +#pragma warning disable CS0649 + public uint Packed; +#pragma warning restore CS0649 + + /// <summary> + /// Unpacks red channel enable. + /// </summary> + /// <returns>True to write the new red channel color, false to keep the old value</returns> + public bool UnpackRed() + { + return (Packed & 0x1) != 0; + } + + /// <summary> + /// Unpacks green channel enable. + /// </summary> + /// <returns>True to write the new green channel color, false to keep the old value</returns> + public bool UnpackGreen() + { + return (Packed & 0x10) != 0; + } + + /// <summary> + /// Unpacks blue channel enable. + /// </summary> + /// <returns>True to write the new blue channel color, false to keep the old value</returns> + public bool UnpackBlue() + { + return (Packed & 0x100) != 0; + } + + /// <summary> + /// Unpacks alpha channel enable. + /// </summary> + /// <returns>True to write the new alpha channel color, false to keep the old value</returns> + public bool UnpackAlpha() + { + return (Packed & 0x1000) != 0; + } + } + + /// <summary> + /// Vertex buffer state. + /// </summary> + struct VertexBufferState + { +#pragma warning disable CS0649 + public uint Control; + public GpuVa Address; + public int Divisor; +#pragma warning restore CS0649 + + /// <summary> + /// Vertex buffer stride, defined as the number of bytes occupied by each vertex in memory. + /// </summary> + /// <returns>Vertex buffer stride</returns> + public int UnpackStride() + { + return (int)(Control & 0xfff); + } + + /// <summary> + /// Vertex buffer enable. + /// </summary> + /// <returns>True if the vertex buffer is enabled, false otherwise</returns> + public bool UnpackEnable() + { + return (Control & (1 << 12)) != 0; + } + } + + /// <summary> + /// Color buffer blending parameters, shared by all color buffers. + /// </summary> + struct BlendStateCommon + { +#pragma warning disable CS0649 + public Boolean32 SeparateAlpha; + public BlendOp ColorOp; + public BlendFactor ColorSrcFactor; + public BlendFactor ColorDstFactor; + public BlendOp AlphaOp; + public BlendFactor AlphaSrcFactor; + public uint Unknown0x1354; + public BlendFactor AlphaDstFactor; +#pragma warning restore CS0649 + } + + /// <summary> + /// Color buffer blending parameters. + /// </summary> + struct BlendState + { +#pragma warning disable CS0649 + public Boolean32 SeparateAlpha; + public BlendOp ColorOp; + public BlendFactor ColorSrcFactor; + public BlendFactor ColorDstFactor; + public BlendOp AlphaOp; + public BlendFactor AlphaSrcFactor; + public BlendFactor AlphaDstFactor; + public uint Padding; +#pragma warning restore CS0649 + } + + /// <summary> + /// Graphics shader stage state. + /// </summary> + struct ShaderState + { +#pragma warning disable CS0649 + public uint Control; + public uint Offset; + public uint Unknown0x8; + public int MaxRegisters; + public ShaderType Type; + public uint Unknown0x14; + public uint Unknown0x18; + public uint Unknown0x1c; + public uint Unknown0x20; + public uint Unknown0x24; + public uint Unknown0x28; + public uint Unknown0x2c; + public uint Unknown0x30; + public uint Unknown0x34; + public uint Unknown0x38; + public uint Unknown0x3c; +#pragma warning restore CS0649 + + /// <summary> + /// Unpacks shader enable information. + /// Must be ignored for vertex shaders, those are always enabled. + /// </summary> + /// <returns>True if the stage is enabled, false otherwise</returns> + public bool UnpackEnable() + { + return (Control & 1) != 0; + } + } + + /// <summary> + /// Uniform buffer state for the uniform buffer currently being modified. + /// </summary> + struct UniformBufferState + { +#pragma warning disable CS0649 + public int Size; + public GpuVa Address; + public int Offset; +#pragma warning restore CS0649 + } + + unsafe struct ThreedClassState : IShadowState + { +#pragma warning disable CS0649 + public uint SetObject; + public int SetObjectClassId => (int)((SetObject >> 0) & 0xFFFF); + public int SetObjectEngineId => (int)((SetObject >> 16) & 0x1F); + public fixed uint Reserved04[63]; + public uint NoOperation; + public uint SetNotifyA; + public int SetNotifyAAddressUpper => (int)((SetNotifyA >> 0) & 0xFF); + public uint SetNotifyB; + public uint Notify; + public NotifyType NotifyType => (NotifyType)(Notify); + public uint WaitForIdle; + public uint LoadMmeInstructionRamPointer; + public uint LoadMmeInstructionRam; + public uint LoadMmeStartAddressRamPointer; + public uint LoadMmeStartAddressRam; + public uint SetMmeShadowRamControl; + public SetMmeShadowRamControlMode SetMmeShadowRamControlMode => (SetMmeShadowRamControlMode)((SetMmeShadowRamControl >> 0) & 0x3); + public fixed uint Reserved128[2]; + public uint SetGlobalRenderEnableA; + public int SetGlobalRenderEnableAOffsetUpper => (int)((SetGlobalRenderEnableA >> 0) & 0xFF); + public uint SetGlobalRenderEnableB; + public uint SetGlobalRenderEnableC; + public int SetGlobalRenderEnableCMode => (int)((SetGlobalRenderEnableC >> 0) & 0x7); + public uint SendGoIdle; + public uint PmTrigger; + public uint PmTriggerWfi; + public fixed uint Reserved148[2]; + public uint SetInstrumentationMethodHeader; + public uint SetInstrumentationMethodData; + public fixed uint Reserved158[10]; + public uint LineLengthIn; + public uint LineCount; + public uint OffsetOutUpper; + public int OffsetOutUpperValue => (int)((OffsetOutUpper >> 0) & 0xFF); + public uint OffsetOut; + public uint PitchOut; + public uint SetDstBlockSize; + public SetDstBlockSizeWidth SetDstBlockSizeWidth => (SetDstBlockSizeWidth)((SetDstBlockSize >> 0) & 0xF); + public SetDstBlockSizeHeight SetDstBlockSizeHeight => (SetDstBlockSizeHeight)((SetDstBlockSize >> 4) & 0xF); + public SetDstBlockSizeDepth SetDstBlockSizeDepth => (SetDstBlockSizeDepth)((SetDstBlockSize >> 8) & 0xF); + public uint SetDstWidth; + public uint SetDstHeight; + public uint SetDstDepth; + public uint SetDstLayer; + public uint SetDstOriginBytesX; + public int SetDstOriginBytesXV => (int)((SetDstOriginBytesX >> 0) & 0xFFFFF); + public uint SetDstOriginSamplesY; + public int SetDstOriginSamplesYV => (int)((SetDstOriginSamplesY >> 0) & 0xFFFF); + public uint LaunchDma; + public LaunchDmaDstMemoryLayout LaunchDmaDstMemoryLayout => (LaunchDmaDstMemoryLayout)((LaunchDma >> 0) & 0x1); + public LaunchDmaCompletionType LaunchDmaCompletionType => (LaunchDmaCompletionType)((LaunchDma >> 4) & 0x3); + public LaunchDmaInterruptType LaunchDmaInterruptType => (LaunchDmaInterruptType)((LaunchDma >> 8) & 0x3); + public LaunchDmaSemaphoreStructSize LaunchDmaSemaphoreStructSize => (LaunchDmaSemaphoreStructSize)((LaunchDma >> 12) & 0x1); + public bool LaunchDmaReductionEnable => (LaunchDma & 0x2) != 0; + public LaunchDmaReductionOp LaunchDmaReductionOp => (LaunchDmaReductionOp)((LaunchDma >> 13) & 0x7); + public LaunchDmaReductionFormat LaunchDmaReductionFormat => (LaunchDmaReductionFormat)((LaunchDma >> 2) & 0x3); + public bool LaunchDmaSysmembarDisable => (LaunchDma & 0x40) != 0; + public uint LoadInlineData; + public fixed uint Reserved1B8[22]; + public Boolean32 EarlyZForce; + public fixed uint Reserved214[45]; + public uint SyncpointAction; + public fixed uint Reserved2CC[44]; + public Boolean32 RasterizeEnable; + public Array4<TfBufferState> TfBufferState; + public fixed uint Reserved400[192]; + public Array4<TfState> TfState; + public fixed uint Reserved740[1]; + public Boolean32 TfEnable; + public fixed uint Reserved748[46]; + public Array8<RtColorState> RtColorState; + public Array16<ViewportTransform> ViewportTransform; + public Array16<ViewportExtents> ViewportExtents; + public fixed uint ReservedD00[29]; + public VertexBufferDrawState VertexBufferDrawState; + public uint DepthMode; + public ClearColors ClearColors; + public float ClearDepthValue; + public fixed uint ReservedD94[3]; + public uint ClearStencilValue; + public fixed uint ReservedDA4[7]; + public DepthBiasState DepthBiasState; + public fixed uint ReservedDCC[5]; + public uint TextureBarrier; + public fixed uint ReservedDE4[7]; + public Array16<ScissorState> ScissorState; + public fixed uint ReservedF00[21]; + public StencilBackMasks StencilBackMasks; + public fixed uint ReservedF60[5]; + public uint InvalidateTextures; + public fixed uint ReservedF78[1]; + public uint TextureBarrierTiled; + public fixed uint ReservedF80[4]; + public Boolean32 RtColorMaskShared; + public fixed uint ReservedF94[19]; + public RtDepthStencilState RtDepthStencilState; + public ScreenScissorState ScreenScissorState; + public fixed uint ReservedFFC[89]; + public Array16<VertexAttribState> VertexAttribState; + public fixed uint Reserved11A0[31]; + public RtControl RtControl; + public fixed uint Reserved1220[2]; + public Size3D RtDepthStencilSize; + public SamplerIndex SamplerIndex; + public fixed uint Reserved1238[37]; + public Boolean32 DepthTestEnable; + public fixed uint Reserved12D0[5]; + public Boolean32 BlendIndependent; + public Boolean32 DepthWriteEnable; + public Boolean32 AlphaTestEnable; + public fixed uint Reserved12F0[5]; + public uint VbElementU8; + public uint Reserved1308; + public CompareOp DepthTestFunc; + public float AlphaTestRef; + public CompareOp AlphaTestFunc; + public uint Reserved1318; + public ColorF BlendConstant; + public fixed uint Reserved132C[4]; + public BlendStateCommon BlendStateCommon; + public Boolean32 BlendEnableCommon; + public Array8<Boolean32> BlendEnable; + public StencilTestState StencilTestState; + public fixed uint Reserved13A0[3]; + public YControl YControl; + public float LineWidthSmooth; + public float LineWidthAliased; + public fixed uint Reserved13B8[31]; + public uint FirstVertex; + public uint FirstInstance; + public fixed uint Reserved143C[53]; + public uint ClipDistanceEnable; + public uint Reserved1514; + public float PointSize; + public uint Reserved151C; + public Boolean32 PointSpriteEnable; + public fixed uint Reserved1524[3]; + public uint ResetCounter; + public uint Reserved1534; + public Boolean32 RtDepthStencilEnable; + public fixed uint Reserved153C[5]; + public GpuVa RenderEnableAddress; + public Condition RenderEnableCondition; + public PoolState SamplerPoolState; + public uint Reserved1568; + public float DepthBiasFactor; + public Boolean32 LineSmoothEnable; + public PoolState TexturePoolState; + public fixed uint Reserved1580[5]; + public StencilBackTestState StencilBackTestState; + public fixed uint Reserved15A8[5]; + public float DepthBiasUnits; + public fixed uint Reserved15C0[4]; + public TextureMsaaMode RtMsaaMode; + public fixed uint Reserved15D4[5]; + public uint VbElementU32; + public uint Reserved15EC; + public uint VbElementU16; + public fixed uint Reserved15F4[4]; + public uint PointCoordReplace; + public GpuVa ShaderBaseAddress; + public uint Reserved1610; + public uint DrawEnd; + public uint DrawBegin; + public fixed uint Reserved161C[10]; + public PrimitiveRestartState PrimitiveRestartState; + public fixed uint Reserved164C[95]; + public IndexBufferState IndexBufferState; + public uint IndexBufferCount; + public uint DrawIndexedSmall; + public uint DrawIndexedSmall2; + public uint Reserved17EC; + public uint DrawIndexedSmallIncInstance; + public uint DrawIndexedSmallIncInstance2; + public fixed uint Reserved17F8[33]; + public float DepthBiasClamp; + public Array16<Boolean32> VertexBufferInstanced; + public fixed uint Reserved18C0[20]; + public Boolean32 VertexProgramPointSize; + public uint Reserved1914; + public FaceState FaceState; + public fixed uint Reserved1924[2]; + public uint ViewportTransformEnable; + public fixed uint Reserved1930[3]; + public ViewVolumeClipControl ViewVolumeClipControl; + public fixed uint Reserved1940[2]; + public Boolean32 PrimitiveTypeOverrideEnable; + public fixed uint Reserved194C[9]; + public PrimitiveTypeOverride PrimitiveTypeOverride; + public fixed uint Reserved1974[20]; + public LogicalOpState LogicOpState; + public uint Reserved19CC; + public uint Clear; + public fixed uint Reserved19D4[11]; + public Array8<RtColorMask> RtColorMask; + public fixed uint Reserved1A20[56]; + public GpuVa SemaphoreAddress; + public int SemaphorePayload; + public uint SemaphoreControl; + public fixed uint Reserved1B10[60]; + public Array16<VertexBufferState> VertexBufferState; + public fixed uint Reserved1D00[64]; + public Array8<BlendState> BlendState; + public Array16<GpuVa> VertexBufferEndAddress; + public fixed uint Reserved1F80[32]; + public Array6<ShaderState> ShaderState; + public fixed uint Reserved2180[96]; + public uint SetFalcon00; + public uint SetFalcon01; + public uint SetFalcon02; + public uint SetFalcon03; + public uint SetFalcon04; + public uint SetFalcon05; + public uint SetFalcon06; + public uint SetFalcon07; + public uint SetFalcon08; + public uint SetFalcon09; + public uint SetFalcon10; + public uint SetFalcon11; + public uint SetFalcon12; + public uint SetFalcon13; + public uint SetFalcon14; + public uint SetFalcon15; + public uint SetFalcon16; + public uint SetFalcon17; + public uint SetFalcon18; + public uint SetFalcon19; + public uint SetFalcon20; + public uint SetFalcon21; + public uint SetFalcon22; + public uint SetFalcon23; + public uint SetFalcon24; + public uint SetFalcon25; + public uint SetFalcon26; + public uint SetFalcon27; + public uint SetFalcon28; + public uint SetFalcon29; + public uint SetFalcon30; + public uint SetFalcon31; + public UniformBufferState UniformBufferState; + public Array16<uint> UniformBufferUpdateData; + public fixed uint Reserved23D0[16]; + public uint UniformBufferBindVertex; + public fixed uint Reserved2414[7]; + public uint UniformBufferBindTessControl; + public fixed uint Reserved2434[7]; + public uint UniformBufferBindTessEvaluation; + public fixed uint Reserved2454[7]; + public uint UniformBufferBindGeometry; + public fixed uint Reserved2474[7]; + public uint UniformBufferBindFragment; + public fixed uint Reserved2494[93]; + public uint TextureBufferIndex; + public fixed uint Reserved260C[125]; + public Array4<Array32<uint>> TfVaryingLocations; + public fixed uint Reserved2A00[640]; + public MmeShadowScratch SetMmeShadowScratch; +#pragma warning restore CS0649 + } +} diff --git a/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs b/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs index d4f6d879..3c74c164 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs @@ -1,7 +1,7 @@ using Ryujinx.Graphics.Device; using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Image; -using Ryujinx.Graphics.Gpu.State; using Ryujinx.Graphics.Texture; using System; using System.Collections.Generic; @@ -52,8 +52,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod { var memoryManager = _channel.MemoryManager; - var dstCopyTexture = Unsafe.As<uint, CopyTexture>(ref _state.State.SetDstFormat); - var srcCopyTexture = Unsafe.As<uint, CopyTexture>(ref _state.State.SetSrcFormat); + var dstCopyTexture = Unsafe.As<uint, TwodTexture>(ref _state.State.SetDstFormat); + var srcCopyTexture = Unsafe.As<uint, TwodTexture>(ref _state.State.SetSrcFormat); long srcX = ((long)_state.State.SetPixelsFromMemorySrcX0Int << 32) | (long)(ulong)_state.State.SetPixelsFromMemorySrcX0Frac; long srcY = ((long)_state.State.PixelsFromMemorySrcY0Int << 32) | (long)(ulong)_state.State.SetPixelsFromMemorySrcY0Frac; diff --git a/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClassState.cs b/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClassState.cs index fdc4204d..46fddb04 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClassState.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClassState.cs @@ -14,17 +14,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod } /// <summary> - /// MME shadow RAM control mode. - /// </summary> - enum SetMmeShadowRamControlMode - { - MethodTrack = 0, - MethodTrackWithFilter = 1, - MethodPassthrough = 2, - MethodReplay = 3, - } - - /// <summary> /// Format of the destination texture. /// </summary> enum SetDstFormatV @@ -506,7 +495,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Twod /// <summary> /// 2D class state. /// </summary> - unsafe struct TwodClassState + unsafe struct TwodClassState : IShadowState { #pragma warning disable CS0649 public uint SetObject; diff --git a/Ryujinx.Graphics.Gpu/Engine/Twod/TwodTexture.cs b/Ryujinx.Graphics.Gpu/Engine/Twod/TwodTexture.cs new file mode 100644 index 00000000..c28da094 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Twod/TwodTexture.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.Gpu.Engine.Types; + +namespace Ryujinx.Graphics.Gpu.Engine.Twod +{ + /// <summary> + /// Texture to texture (with optional resizing) copy parameters. + /// </summary> + struct TwodTexture + { +#pragma warning disable CS0649 + public ColorFormat Format; + public Boolean32 LinearLayout; + public MemoryLayout MemoryLayout; + public int Depth; + public int Layer; + public int Stride; + public int Width; + public int Height; + public GpuVa Address; +#pragma warning restore CS0649 + } +} diff --git a/Ryujinx.Graphics.Gpu/Engine/Types/Boolean32.cs b/Ryujinx.Graphics.Gpu/Engine/Types/Boolean32.cs new file mode 100644 index 00000000..c982347a --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Types/Boolean32.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Graphics.Gpu.Engine.Types +{ + /// <summary> + /// Boolean value, stored as a 32-bits integer in memory. + /// </summary> + struct Boolean32 + { +#pragma warning disable CS0649 + private uint _value; +#pragma warning restore CS0649 + + public static implicit operator bool(Boolean32 value) + { + return (value._value & 1) != 0; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Engine/Types/ColorFormat.cs b/Ryujinx.Graphics.Gpu/Engine/Types/ColorFormat.cs new file mode 100644 index 00000000..e780ec23 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Types/ColorFormat.cs @@ -0,0 +1,134 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; + +namespace Ryujinx.Graphics.Gpu.Engine.Types +{ + /// <summary> + /// Color texture format. + /// </summary> + enum ColorFormat + { + R32G32B32A32Float = 0xc0, + R32G32B32A32Sint = 0xc1, + R32G32B32A32Uint = 0xc2, + R32G32B32X32Float = 0xc3, + R32G32B32X32Sint = 0xc4, + R32G32B32X32Uint = 0xc5, + R16G16B16X16Unorm = 0xc6, + R16G16B16X16Snorm = 0xc7, + R16G16B16X16Sint = 0xc8, + R16G16B16X16Uint = 0xc9, + R16G16B16A16Float = 0xca, + R32G32Float = 0xcb, + R32G32Sint = 0xcc, + R32G32Uint = 0xcd, + R16G16B16X16Float = 0xce, + B8G8R8A8Unorm = 0xcf, + B8G8R8A8Srgb = 0xd0, + R10G10B10A2Unorm = 0xd1, + R10G10B10A2Uint = 0xd2, + R8G8B8A8Unorm = 0xd5, + R8G8B8A8Srgb = 0xd6, + R8G8B8X8Snorm = 0xd7, + R8G8B8X8Sint = 0xd8, + R8G8B8X8Uint = 0xd9, + R16G16Unorm = 0xda, + R16G16Snorm = 0xdb, + R16G16Sint = 0xdc, + R16G16Uint = 0xdd, + R16G16Float = 0xde, + R11G11B10Float = 0xe0, + R32Sint = 0xe3, + R32Uint = 0xe4, + R32Float = 0xe5, + B8G8R8X8Unorm = 0xe6, + B8G8R8X8Srgb = 0xe7, + B5G6R5Unorm = 0xe8, + B5G5R5A1Unorm = 0xe9, + R8G8Unorm = 0xea, + R8G8Snorm = 0xeb, + R8G8Sint = 0xec, + R8G8Uint = 0xed, + R16Unorm = 0xee, + R16Snorm = 0xef, + R16Sint = 0xf0, + R16Uint = 0xf1, + R16Float = 0xf2, + R8Unorm = 0xf3, + R8Snorm = 0xf4, + R8Sint = 0xf5, + R8Uint = 0xf6, + B5G5R5X1Unorm = 0xf8, + R8G8B8X8Unorm = 0xf9, + R8G8B8X8Srgb = 0xfa + } + + static class ColorFormatConverter + { + /// <summary> + /// Converts the color texture format to a host compatible format. + /// </summary> + /// <param name="format">Color format</param> + /// <returns>Host compatible format enum value</returns> + public static FormatInfo Convert(this ColorFormat format) + { + return format switch + { + ColorFormat.R32G32B32A32Float => new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16, 4), + ColorFormat.R32G32B32A32Sint => new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16, 4), + ColorFormat.R32G32B32A32Uint => new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16, 4), + ColorFormat.R32G32B32X32Float => new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16, 4), + ColorFormat.R32G32B32X32Sint => new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16, 4), + ColorFormat.R32G32B32X32Uint => new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16, 4), + ColorFormat.R16G16B16X16Unorm => new FormatInfo(Format.R16G16B16A16Unorm, 1, 1, 8, 4), + ColorFormat.R16G16B16X16Snorm => new FormatInfo(Format.R16G16B16A16Snorm, 1, 1, 8, 4), + ColorFormat.R16G16B16X16Sint => new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8, 4), + ColorFormat.R16G16B16X16Uint => new FormatInfo(Format.R16G16B16A16Uint, 1, 1, 8, 4), + ColorFormat.R16G16B16A16Float => new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8, 4), + ColorFormat.R32G32Float => new FormatInfo(Format.R32G32Float, 1, 1, 8, 2), + ColorFormat.R32G32Sint => new FormatInfo(Format.R32G32Sint, 1, 1, 8, 2), + ColorFormat.R32G32Uint => new FormatInfo(Format.R32G32Uint, 1, 1, 8, 2), + ColorFormat.R16G16B16X16Float => new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8, 4), + ColorFormat.B8G8R8A8Unorm => new FormatInfo(Format.B8G8R8A8Unorm, 1, 1, 4, 4), + ColorFormat.B8G8R8A8Srgb => new FormatInfo(Format.B8G8R8A8Srgb, 1, 1, 4, 4), + ColorFormat.R10G10B10A2Unorm => new FormatInfo(Format.R10G10B10A2Unorm, 1, 1, 4, 4), + ColorFormat.R10G10B10A2Uint => new FormatInfo(Format.R10G10B10A2Uint, 1, 1, 4, 4), + ColorFormat.R8G8B8A8Unorm => new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4), + ColorFormat.R8G8B8A8Srgb => new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4), + ColorFormat.R8G8B8X8Snorm => new FormatInfo(Format.R8G8B8A8Snorm, 1, 1, 4, 4), + ColorFormat.R8G8B8X8Sint => new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4, 4), + ColorFormat.R8G8B8X8Uint => new FormatInfo(Format.R8G8B8A8Uint, 1, 1, 4, 4), + ColorFormat.R16G16Unorm => new FormatInfo(Format.R16G16Unorm, 1, 1, 4, 2), + ColorFormat.R16G16Snorm => new FormatInfo(Format.R16G16Snorm, 1, 1, 4, 2), + ColorFormat.R16G16Sint => new FormatInfo(Format.R16G16Sint, 1, 1, 4, 2), + ColorFormat.R16G16Uint => new FormatInfo(Format.R16G16Uint, 1, 1, 4, 2), + ColorFormat.R16G16Float => new FormatInfo(Format.R16G16Float, 1, 1, 4, 2), + ColorFormat.R11G11B10Float => new FormatInfo(Format.R11G11B10Float, 1, 1, 4, 3), + ColorFormat.R32Sint => new FormatInfo(Format.R32Sint, 1, 1, 4, 1), + ColorFormat.R32Uint => new FormatInfo(Format.R32Uint, 1, 1, 4, 1), + ColorFormat.R32Float => new FormatInfo(Format.R32Float, 1, 1, 4, 1), + ColorFormat.B8G8R8X8Unorm => new FormatInfo(Format.B8G8R8A8Unorm, 1, 1, 4, 4), + ColorFormat.B8G8R8X8Srgb => new FormatInfo(Format.B8G8R8A8Srgb, 1, 1, 4, 4), + ColorFormat.B5G6R5Unorm => new FormatInfo(Format.B5G6R5Unorm, 1, 1, 2, 3), + ColorFormat.B5G5R5A1Unorm => new FormatInfo(Format.B5G5R5A1Unorm, 1, 1, 2, 4), + ColorFormat.R8G8Unorm => new FormatInfo(Format.R8G8Unorm, 1, 1, 2, 2), + ColorFormat.R8G8Snorm => new FormatInfo(Format.R8G8Snorm, 1, 1, 2, 2), + ColorFormat.R8G8Sint => new FormatInfo(Format.R8G8Sint, 1, 1, 2, 2), + ColorFormat.R8G8Uint => new FormatInfo(Format.R8G8Uint, 1, 1, 2, 2), + ColorFormat.R16Unorm => new FormatInfo(Format.R16Unorm, 1, 1, 2, 1), + ColorFormat.R16Snorm => new FormatInfo(Format.R16Snorm, 1, 1, 2, 1), + ColorFormat.R16Sint => new FormatInfo(Format.R16Sint, 1, 1, 2, 1), + ColorFormat.R16Uint => new FormatInfo(Format.R16Uint, 1, 1, 2, 1), + ColorFormat.R16Float => new FormatInfo(Format.R16Float, 1, 1, 2, 1), + ColorFormat.R8Unorm => new FormatInfo(Format.R8Unorm, 1, 1, 1, 1), + ColorFormat.R8Snorm => new FormatInfo(Format.R8Snorm, 1, 1, 1, 1), + ColorFormat.R8Sint => new FormatInfo(Format.R8Sint, 1, 1, 1, 1), + ColorFormat.R8Uint => new FormatInfo(Format.R8Uint, 1, 1, 1, 1), + ColorFormat.B5G5R5X1Unorm => new FormatInfo(Format.B5G5R5X1Unorm, 1, 1, 2, 4), + ColorFormat.R8G8B8X8Unorm => new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4), + ColorFormat.R8G8B8X8Srgb => new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4), + _ => FormatInfo.Default + }; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/Types/GpuVa.cs b/Ryujinx.Graphics.Gpu/Engine/Types/GpuVa.cs new file mode 100644 index 00000000..839faac9 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Types/GpuVa.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.Gpu.Engine.Types +{ + /// <summary> + /// Split GPU virtual address. + /// </summary> + struct GpuVa + { +#pragma warning disable CS0649 + public uint High; + public uint Low; +#pragma warning restore CS0649 + + /// <summary> + /// Packs the split address into a 64-bits address value. + /// </summary> + /// <returns>The 64-bits address value</returns> + public ulong Pack() + { + return Low | ((ulong)High << 32); + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Engine/Types/MemoryLayout.cs b/Ryujinx.Graphics.Gpu/Engine/Types/MemoryLayout.cs new file mode 100644 index 00000000..6da96bd4 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Types/MemoryLayout.cs @@ -0,0 +1,37 @@ +namespace Ryujinx.Graphics.Gpu.Engine.Types +{ + /// <summary> + /// Memory layout parameters, for block linear textures. + /// </summary> + struct MemoryLayout + { +#pragma warning disable CS0649 + public uint Packed; +#pragma warning restore CS0649 + + public int UnpackGobBlocksInX() + { + return 1 << (int)(Packed & 0xf); + } + + public int UnpackGobBlocksInY() + { + return 1 << (int)((Packed >> 4) & 0xf); + } + + public int UnpackGobBlocksInZ() + { + return 1 << (int)((Packed >> 8) & 0xf); + } + + public bool UnpackIsLinear() + { + return (Packed & 0x1000) != 0; + } + + public bool UnpackIsTarget3D() + { + return (Packed & 0x10000) != 0; + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Engine/Types/PrimitiveType.cs b/Ryujinx.Graphics.Gpu/Engine/Types/PrimitiveType.cs new file mode 100644 index 00000000..dae63124 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Types/PrimitiveType.cs @@ -0,0 +1,99 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Engine.Types +{ + /// <summary> + /// Draw primitive type. + /// </summary> + enum PrimitiveType + { + Points, + Lines, + LineLoop, + LineStrip, + Triangles, + TriangleStrip, + TriangleFan, + Quads, + QuadStrip, + Polygon, + LinesAdjacency, + LineStripAdjacency, + TrianglesAdjacency, + TriangleStripAdjacency, + Patches + } + + /// <summary> + /// Alternative primitive type that might override <see cref="PrimitiveType"/>. + /// </summary> + enum PrimitiveTypeOverride + { + Points = 1, + Lines = 2, + LineStrip = 3, + Triangles = 4, + TriangleStrip = 5, + TriangleFan = 0x1015, + LinesAdjacency = 10, + LineStripAdjacency = 11, + TrianglesAdjacency = 12, + TriangleStripAdjacency = 13, + Patches = 14 + } + + static class PrimitiveTypeConverter + { + /// <summary> + /// Converts the primitive type into something that can be used with the host API. + /// </summary> + /// <param name="type">The primitive type to convert</param> + /// <returns>A host compatible enum value</returns> + public static PrimitiveTopology Convert(this PrimitiveType type) + { + return type switch + { + PrimitiveType.Points => PrimitiveTopology.Points, + PrimitiveType.Lines => PrimitiveTopology.Lines, + PrimitiveType.LineLoop => PrimitiveTopology.LineLoop, + PrimitiveType.LineStrip => PrimitiveTopology.LineStrip, + PrimitiveType.Triangles => PrimitiveTopology.Triangles, + PrimitiveType.TriangleStrip => PrimitiveTopology.TriangleStrip, + PrimitiveType.TriangleFan => PrimitiveTopology.TriangleFan, + PrimitiveType.Quads => PrimitiveTopology.Quads, + PrimitiveType.QuadStrip => PrimitiveTopology.QuadStrip, + PrimitiveType.Polygon => PrimitiveTopology.Polygon, + PrimitiveType.LinesAdjacency => PrimitiveTopology.LinesAdjacency, + PrimitiveType.LineStripAdjacency => PrimitiveTopology.LineStripAdjacency, + PrimitiveType.TrianglesAdjacency => PrimitiveTopology.TrianglesAdjacency, + PrimitiveType.TriangleStripAdjacency => PrimitiveTopology.TriangleStripAdjacency, + PrimitiveType.Patches => PrimitiveTopology.Patches, + _ => PrimitiveTopology.Triangles + }; + } + + /// <summary> + /// Converts the primitive type into something that can be used with the host API. + /// </summary> + /// <param name="type">The primitive type to convert</param> + /// <returns>A host compatible enum value</returns> + public static PrimitiveTopology Convert(this PrimitiveTypeOverride type) + { + return type switch + { + PrimitiveTypeOverride.Points => PrimitiveTopology.Points, + PrimitiveTypeOverride.Lines => PrimitiveTopology.Lines, + PrimitiveTypeOverride.LineStrip => PrimitiveTopology.LineStrip, + PrimitiveTypeOverride.Triangles => PrimitiveTopology.Triangles, + PrimitiveTypeOverride.TriangleStrip => PrimitiveTopology.TriangleStrip, + PrimitiveTypeOverride.TriangleFan => PrimitiveTopology.TriangleFan, + PrimitiveTypeOverride.LinesAdjacency => PrimitiveTopology.LinesAdjacency, + PrimitiveTypeOverride.LineStripAdjacency => PrimitiveTopology.LineStripAdjacency, + PrimitiveTypeOverride.TrianglesAdjacency => PrimitiveTopology.TrianglesAdjacency, + PrimitiveTypeOverride.TriangleStripAdjacency => PrimitiveTopology.TriangleStripAdjacency, + PrimitiveTypeOverride.Patches => PrimitiveTopology.Patches, + _ => PrimitiveTopology.Triangles + }; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/Types/SamplerIndex.cs b/Ryujinx.Graphics.Gpu/Engine/Types/SamplerIndex.cs new file mode 100644 index 00000000..839a4d0a --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Types/SamplerIndex.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu.Engine.Types +{ + /// <summary> + /// Sampler pool indexing mode. + /// </summary> + enum SamplerIndex + { + Independently = 0, + ViaHeaderIndex = 1 + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/Types/SbDescriptor.cs b/Ryujinx.Graphics.Gpu/Engine/Types/SbDescriptor.cs new file mode 100644 index 00000000..c457dbf9 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Types/SbDescriptor.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.Gpu.Engine.Types +{ + /// <summary> + /// Storage buffer address and size information. + /// </summary> + struct SbDescriptor + { +#pragma warning disable CS0649 + public uint AddressLow; + public uint AddressHigh; + public int Size; + public int Padding; +#pragma warning restore CS0649 + + public ulong PackAddress() + { + return AddressLow | ((ulong)AddressHigh << 32); + } + } +} diff --git a/Ryujinx.Graphics.Gpu/Engine/Types/ZetaFormat.cs b/Ryujinx.Graphics.Gpu/Engine/Types/ZetaFormat.cs new file mode 100644 index 00000000..2de38fd2 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Types/ZetaFormat.cs @@ -0,0 +1,42 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; + +namespace Ryujinx.Graphics.Gpu.Engine.Types +{ + /// <summary> + /// Depth-stencil texture format. + /// </summary> + enum ZetaFormat + { + D32Float = 0xa, + D16Unorm = 0x13, + D24UnormS8Uint = 0x14, + D24Unorm = 0x15, + S8UintD24Unorm = 0x16, + S8Uint = 0x17, + D32FloatS8Uint = 0x19 + } + + static class ZetaFormatConverter + { + /// <summary> + /// Converts the depth-stencil texture format to a host compatible format. + /// </summary> + /// <param name="format">Depth-stencil format</param> + /// <returns>Host compatible format enum value</returns> + public static FormatInfo Convert(this ZetaFormat format) + { + return format switch + { + ZetaFormat.D32Float => new FormatInfo(Format.D32Float, 1, 1, 4, 1), + ZetaFormat.D16Unorm => new FormatInfo(Format.D16Unorm, 1, 1, 2, 1), + ZetaFormat.D24UnormS8Uint => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2), + ZetaFormat.D24Unorm => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 1), + ZetaFormat.S8UintD24Unorm => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2), + ZetaFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1, 1), + ZetaFormat.D32FloatS8Uint => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2), + _ => FormatInfo.Default + }; + } + } +} |
