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/Threed | |
| 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/Threed')
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/Threed/ConditionalRendering.cs | 130 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/Threed/ConstantBufferUpdater.cs | 173 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs | 410 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/Threed/DrawState.cs | 45 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/Threed/IbStreamer.cs | 142 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/Threed/SemaphoreUpdater.cs | 222 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs | 166 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs | 1044 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs | 428 | ||||
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs | 861 |
10 files changed, 3621 insertions, 0 deletions
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ConditionalRendering.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ConditionalRendering.cs new file mode 100644 index 00000000..85f66985 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ConditionalRendering.cs @@ -0,0 +1,130 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Memory; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// <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="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> + public static ConditionalRenderEnabled GetRenderEnable(GpuContext context, MemoryManager memoryManager, GpuVa address, Condition condition) + { + switch (condition) + { + case Condition.Always: + return ConditionalRenderEnabled.True; + case Condition.Never: + return ConditionalRenderEnabled.False; + case Condition.ResultNonZero: + return CounterNonZero(context, memoryManager, address.Pack()); + case Condition.Equal: + return CounterCompare(context, memoryManager, address.Pack(), true); + case Condition.NotEqual: + return CounterCompare(context, memoryManager, address.Pack(), false); + } + + Logger.Warning?.Print(LogClass.Gpu, $"Invalid conditional render condition \"{condition}\"."); + + return ConditionalRenderEnabled.True; + } + + /// <summary> + /// Checks if the counter value at a given GPU memory address is non-zero. + /// </summary> + /// <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 static ConditionalRenderEnabled CounterNonZero(GpuContext context, MemoryManager memoryManager, ulong gpuVa) + { + ICounterEvent evt = memoryManager.CounterCache.FindEvent(gpuVa); + + if (evt == null) + { + return ConditionalRenderEnabled.False; + } + + if (context.Renderer.Pipeline.TryHostConditionalRendering(evt, 0L, false)) + { + return ConditionalRenderEnabled.Host; + } + else + { + evt.Flush(); + 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="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 static ConditionalRenderEnabled CounterCompare(GpuContext context, MemoryManager memoryManager, ulong gpuVa, bool isEqual) + { + 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, memoryManager.Read<ulong>(gpuVa + 16), isEqual); + } + else if (evt == null && evt2 != null) + { + 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); + } + else + { + useHost = false; + } + + if (useHost) + { + return ConditionalRenderEnabled.Host; + } + else + { + evt?.Flush(); + evt2?.Flush(); + + ulong x = memoryManager.Read<ulong>(gpuVa); + ulong y = memoryManager.Read<ulong>(gpuVa + 16); + + return (isEqual ? x == y : x != y) ? ConditionalRenderEnabled.True : ConditionalRenderEnabled.False; + } + } + + /// <summary> + /// Tries to find a counter that is supposed to be written at the specified address, + /// returning the related event. + /// </summary> + /// <param name="counterCache">GPU counter cache to search on</param> + /// <param name="gpuVa">GPU virtual address where the counter is supposed to be written</param> + /// <returns>The counter event, or null if not present</returns> + private static ICounterEvent FindEvent(CounterCache counterCache, ulong gpuVa) + { + return counterCache.FindEvent(gpuVa); + } + } +} 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/Threed/IbStreamer.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/IbStreamer.cs new file mode 100644 index 00000000..96b2ed9c --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/IbStreamer.cs @@ -0,0 +1,142 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// <summary> + /// Holds inline index buffer state. + /// The inline index buffer data is sent to the GPU through the command buffer. + /// </summary> + struct IbStreamer + { + private BufferHandle _inlineIndexBuffer; + private int _inlineIndexBufferSize; + private int _inlineIndexCount; + + /// <summary> + /// Indicates if any index buffer data has been pushed. + /// </summary> + public bool HasInlineIndexData => _inlineIndexCount != 0; + + /// <summary> + /// Gets the handle for the host buffer currently holding the inline index buffer data. + /// </summary> + /// <returns>Host buffer handle</returns> + public BufferHandle GetInlineIndexBuffer() + { + return _inlineIndexBuffer; + } + + /// <summary> + /// Gets the number of elements on the current inline index buffer, + /// while also reseting it to zero for the next draw. + /// </summary> + /// <returns>Inline index bufffer count</returns> + public int GetAndResetInlineIndexCount() + { + int temp = _inlineIndexCount; + _inlineIndexCount = 0; + return temp; + } + + /// <summary> + /// Pushes four 8-bit index buffer elements. + /// </summary> + /// <param name="renderer">Host renderer</param> + /// <param name="argument">Method call argument</param> + public void VbElementU8(IRenderer renderer, int argument) + { + byte i0 = (byte)argument; + byte i1 = (byte)(argument >> 8); + byte i2 = (byte)(argument >> 16); + byte i3 = (byte)(argument >> 24); + + Span<uint> data = stackalloc uint[4]; + + data[0] = i0; + data[1] = i1; + data[2] = i2; + data[3] = i3; + + int offset = _inlineIndexCount * 4; + + renderer.SetBufferData(GetInlineIndexBuffer(renderer, offset), offset, MemoryMarshal.Cast<uint, byte>(data)); + + _inlineIndexCount += 4; + } + + /// <summary> + /// Pushes two 16-bit index buffer elements. + /// </summary> + /// <param name="renderer">Host renderer</param> + /// <param name="argument">Method call argument</param> + public void VbElementU16(IRenderer renderer, int argument) + { + ushort i0 = (ushort)argument; + ushort i1 = (ushort)(argument >> 16); + + Span<uint> data = stackalloc uint[2]; + + data[0] = i0; + data[1] = i1; + + int offset = _inlineIndexCount * 4; + + renderer.SetBufferData(GetInlineIndexBuffer(renderer, offset), offset, MemoryMarshal.Cast<uint, byte>(data)); + + _inlineIndexCount += 2; + } + + /// <summary> + /// Pushes one 32-bit index buffer element. + /// </summary> + /// <param name="renderer">Host renderer</param> + /// <param name="argument">Method call argument</param> + public void VbElementU32(IRenderer renderer, int argument) + { + uint i0 = (uint)argument; + + Span<uint> data = stackalloc uint[1]; + + data[0] = i0; + + int offset = _inlineIndexCount++ * 4; + + renderer.SetBufferData(GetInlineIndexBuffer(renderer, offset), offset, MemoryMarshal.Cast<uint, byte>(data)); + } + + /// <summary> + /// Gets the handle of a buffer large enough to hold the data that will be written to <paramref name="offset"/>. + /// </summary> + /// <param name="renderer">Host renderer</param> + /// <param name="offset">Offset where the data will be written</param> + /// <returns>Buffer handle</returns> + private BufferHandle GetInlineIndexBuffer(IRenderer renderer, int offset) + { + // Calculate a reasonable size for the buffer that can fit all the data, + // and that also won't require frequent resizes if we need to push more data. + int size = BitUtils.AlignUp(offset + 0x10, 0x200); + + if (_inlineIndexBuffer == BufferHandle.Null) + { + _inlineIndexBuffer = renderer.CreateBuffer(size); + _inlineIndexBufferSize = size; + } + else if (_inlineIndexBufferSize < size) + { + BufferHandle oldBuffer = _inlineIndexBuffer; + int oldSize = _inlineIndexBufferSize; + + _inlineIndexBuffer = renderer.CreateBuffer(size); + _inlineIndexBufferSize = size; + + renderer.Pipeline.CopyBuffer(oldBuffer, _inlineIndexBuffer, 0, 0, oldSize); + renderer.DeleteBuffer(oldBuffer); + } + + return _inlineIndexBuffer; + } + } +} 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 + } +} |
