aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs')
-rw-r--r--src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs1448
1 files changed, 1448 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
new file mode 100644
index 00000000..00e09a31
--- /dev/null
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
@@ -0,0 +1,1448 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Engine.Threed.Blender;
+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.Runtime.CompilerServices;
+
+namespace Ryujinx.Graphics.Gpu.Engine.Threed
+{
+ /// <summary>
+ /// GPU state updater.
+ /// </summary>
+ class StateUpdater
+ {
+ public const int ShaderStateIndex = 26;
+ public const int RasterizerStateIndex = 15;
+ public const int ScissorStateIndex = 16;
+ public const int VertexBufferStateIndex = 0;
+ public const int PrimitiveRestartStateIndex = 12;
+ public const int RenderTargetStateIndex = 27;
+
+ private readonly GpuContext _context;
+ private readonly GpuChannel _channel;
+ private readonly DeviceStateWithShadow<ThreedClassState> _state;
+ private readonly DrawState _drawState;
+ private readonly AdvancedBlendManager _blendManager;
+
+ private readonly StateUpdateTracker<ThreedClassState> _updateTracker;
+
+ private readonly ShaderProgramInfo[] _currentProgramInfo;
+ private ShaderSpecializationState _shaderSpecState;
+ private SpecializationStateUpdater _currentSpecState;
+
+ private ProgramPipelineState _pipeline;
+
+ private bool _vsUsesDrawParameters;
+ private bool _vtgWritesRtLayer;
+ private byte _vsClipDistancesWritten;
+ private uint _vbEnableMask;
+
+ private bool _prevDrawIndexed;
+ private bool _prevDrawIndirect;
+ private IndexType _prevIndexType;
+ private uint _prevFirstVertex;
+ private bool _prevTfEnable;
+
+ private uint _prevRtNoAlphaMask;
+
+ /// <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>
+ /// <param name="blendManager">Advanced blend manager</param>
+ /// <param name="spec">Specialization state updater</param>
+ public StateUpdater(
+ GpuContext context,
+ GpuChannel channel,
+ DeviceStateWithShadow<ThreedClassState> state,
+ DrawState drawState,
+ AdvancedBlendManager blendManager,
+ SpecializationStateUpdater spec)
+ {
+ _context = context;
+ _channel = channel;
+ _state = state;
+ _drawState = drawState;
+ _blendManager = blendManager;
+ _currentProgramInfo = new ShaderProgramInfo[Constants.ShaderStages];
+ _currentSpecState = spec;
+
+ // ShaderState must be updated after other state updates, as specialization/pipeline state is used when fetching shaders.
+ // Render target state must appear after shader state as it 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(UpdateVertexBufferState,
+ nameof(ThreedClassState.VertexBufferDrawState),
+ nameof(ThreedClassState.VertexBufferInstanced),
+ nameof(ThreedClassState.VertexBufferState),
+ nameof(ThreedClassState.VertexBufferEndAddress)),
+
+ // Must be done after vertex buffer updates.
+ new StateUpdateCallbackEntry(UpdateVertexAttribState, nameof(ThreedClassState.VertexAttribState)),
+
+ new StateUpdateCallbackEntry(UpdateBlendState,
+ nameof(ThreedClassState.BlendUcodeEnable),
+ nameof(ThreedClassState.BlendUcodeSize),
+ nameof(ThreedClassState.BlendIndependent),
+ nameof(ThreedClassState.BlendConstant),
+ nameof(ThreedClassState.BlendStateCommon),
+ nameof(ThreedClassState.BlendEnableCommon),
+ nameof(ThreedClassState.BlendEnable),
+ nameof(ThreedClassState.BlendState)),
+
+ new StateUpdateCallbackEntry(UpdateFaceState, nameof(ThreedClassState.FaceState)),
+
+ new StateUpdateCallbackEntry(UpdateStencilTestState,
+ nameof(ThreedClassState.StencilBackMasks),
+ nameof(ThreedClassState.StencilTestState),
+ nameof(ThreedClassState.StencilBackTestState)),
+
+ new StateUpdateCallbackEntry(UpdateDepthTestState,
+ nameof(ThreedClassState.DepthTestEnable),
+ nameof(ThreedClassState.DepthWriteEnable),
+ nameof(ThreedClassState.DepthTestFunc)),
+
+ new StateUpdateCallbackEntry(UpdateTessellationState,
+ nameof(ThreedClassState.TessMode),
+ nameof(ThreedClassState.TessOuterLevel),
+ nameof(ThreedClassState.TessInnerLevel),
+ nameof(ThreedClassState.PatchVertices)),
+
+ new StateUpdateCallbackEntry(UpdateViewportTransform,
+ nameof(ThreedClassState.DepthMode),
+ nameof(ThreedClassState.ViewportTransform),
+ nameof(ThreedClassState.ViewportExtents),
+ nameof(ThreedClassState.YControl),
+ nameof(ThreedClassState.ViewportTransformEnable)),
+
+ new StateUpdateCallbackEntry(UpdateLogicOpState, nameof(ThreedClassState.LogicOpState)),
+
+ new StateUpdateCallbackEntry(UpdateDepthClampState, nameof(ThreedClassState.ViewVolumeClipControl)),
+
+ new StateUpdateCallbackEntry(UpdatePolygonMode,
+ nameof(ThreedClassState.PolygonModeFront),
+ nameof(ThreedClassState.PolygonModeBack)),
+
+ new StateUpdateCallbackEntry(UpdateDepthBiasState,
+ nameof(ThreedClassState.DepthBiasState),
+ nameof(ThreedClassState.DepthBiasFactor),
+ nameof(ThreedClassState.DepthBiasUnits),
+ nameof(ThreedClassState.DepthBiasClamp)),
+
+ new StateUpdateCallbackEntry(UpdatePrimitiveRestartState, nameof(ThreedClassState.PrimitiveRestartState)),
+
+ new StateUpdateCallbackEntry(UpdateLineState,
+ nameof(ThreedClassState.LineWidthSmooth),
+ nameof(ThreedClassState.LineSmoothEnable)),
+
+ new StateUpdateCallbackEntry(UpdateRtColorMask,
+ nameof(ThreedClassState.RtColorMaskShared),
+ nameof(ThreedClassState.RtColorMask)),
+
+ new StateUpdateCallbackEntry(UpdateRasterizerState, nameof(ThreedClassState.RasterizeEnable)),
+
+ new StateUpdateCallbackEntry(UpdateScissorState,
+ nameof(ThreedClassState.ScissorState),
+ nameof(ThreedClassState.ScreenScissorState)),
+
+ new StateUpdateCallbackEntry(UpdateTfBufferState, nameof(ThreedClassState.TfBufferState)),
+ new StateUpdateCallbackEntry(UpdateUserClipState, nameof(ThreedClassState.ClipDistanceEnable)),
+
+ new StateUpdateCallbackEntry(UpdateAlphaTestState,
+ nameof(ThreedClassState.AlphaTestEnable),
+ nameof(ThreedClassState.AlphaTestRef),
+ nameof(ThreedClassState.AlphaTestFunc)),
+
+ new StateUpdateCallbackEntry(UpdateSamplerPoolState,
+ nameof(ThreedClassState.SamplerPoolState),
+ nameof(ThreedClassState.SamplerIndex)),
+
+ new StateUpdateCallbackEntry(UpdateTexturePoolState, nameof(ThreedClassState.TexturePoolState)),
+
+ new StateUpdateCallbackEntry(UpdatePointState,
+ nameof(ThreedClassState.PointSize),
+ nameof(ThreedClassState.VertexProgramPointSize),
+ nameof(ThreedClassState.PointSpriteEnable),
+ nameof(ThreedClassState.PointCoordReplace)),
+
+ new StateUpdateCallbackEntry(UpdateIndexBufferState,
+ nameof(ThreedClassState.IndexBufferState),
+ nameof(ThreedClassState.IndexBufferCount)),
+
+ new StateUpdateCallbackEntry(UpdateMultisampleState,
+ nameof(ThreedClassState.AlphaToCoverageDitherEnable),
+ nameof(ThreedClassState.MultisampleControl)),
+
+ new StateUpdateCallbackEntry(UpdateEarlyZState,
+ nameof(ThreedClassState.EarlyZForce)),
+
+ new StateUpdateCallbackEntry(UpdateShaderState,
+ nameof(ThreedClassState.ShaderBaseAddress),
+ nameof(ThreedClassState.ShaderState)),
+
+ new StateUpdateCallbackEntry(UpdateRenderTargetState,
+ nameof(ThreedClassState.RtColorState),
+ nameof(ThreedClassState.RtDepthStencilState),
+ nameof(ThreedClassState.RtControl),
+ nameof(ThreedClassState.RtDepthStencilSize),
+ nameof(ThreedClassState.RtDepthStencilEnable)),
+ });
+ }
+
+ /// <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);
+
+ // If PrimitiveRestartDrawArrays is false and this is a non-indexed draw, we need to ensure primitive restart is disabled.
+ // If PrimitiveRestartDrawArrays is false and this is a indexed draw, we need to ensure primitive restart enable matches GPU state.
+ // If PrimitiveRestartDrawArrays is true, then primitive restart enable should always match GPU state.
+ // That is because "PrimitiveRestartDrawArrays" is not configurable on the backend, it is always
+ // true on OpenGL and always false on Vulkan.
+ if (!_state.State.PrimitiveRestartDrawArrays && _state.State.PrimitiveRestartState.Enable)
+ {
+ _updateTracker.ForceDirty(PrimitiveRestartStateIndex);
+ }
+
+ _prevDrawIndexed = _drawState.DrawIndexed;
+ }
+
+ // Some draw parameters are used to restrict the vertex buffer size,
+ // but they can't be used on indirect draws because their values are unknown in this case.
+ // When switching between indirect and non-indirect draw, we need to
+ // make sure the vertex buffer sizes are still correct.
+ if (_drawState.DrawIndirect != _prevDrawIndirect)
+ {
+ _updateTracker.ForceDirty(VertexBufferStateIndex);
+ }
+
+ // In some cases, the index type is also used to guess the
+ // vertex buffer size, so we must update it if the type changed too.
+ if (_drawState.DrawIndexed &&
+ (_prevIndexType != _state.State.IndexBufferState.Type ||
+ _prevFirstVertex != _state.State.FirstVertex))
+ {
+ _updateTracker.ForceDirty(VertexBufferStateIndex);
+ _prevIndexType = _state.State.IndexBufferState.Type;
+ _prevFirstVertex = _state.State.FirstVertex;
+ }
+
+ bool tfEnable = _state.State.TfEnable;
+
+ if (!tfEnable && _prevTfEnable)
+ {
+ _context.Renderer.Pipeline.EndTransformFeedback();
+ _prevTfEnable = false;
+ }
+
+ if (_updateTracker.IsDirty(RenderTargetStateIndex))
+ {
+ UpdateRenderTargetSpecialization();
+ }
+
+ _updateTracker.Update(ulong.MaxValue);
+
+ // If any state that the shader depends on changed,
+ // then we may need to compile/bind a different version
+ // of the shader for the new state.
+ if (_shaderSpecState != null && _currentSpecState.HasChanged())
+ {
+ if (!_shaderSpecState.MatchesGraphics(_channel, ref _currentSpecState.GetPoolState(), ref _currentSpecState.GetGraphicsState(), _vsUsesDrawParameters, false))
+ {
+ // Shader must be reloaded. _vtgWritesRtLayer should not change.
+ UpdateShaderState();
+ }
+ }
+
+ 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();
+
+ bool unalignedChanged = _currentSpecState.SetHasUnalignedStorageBuffer(_channel.BufferManager.HasUnalignedStorageBuffers);
+
+ if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState) || unalignedChanged)
+ {
+ // Shader must be reloaded. _vtgWritesRtLayer should not change.
+ UpdateShaderState();
+ }
+
+ _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 tessellation state based on the guest GPU state.
+ /// </summary>
+ private void UpdateTessellationState()
+ {
+ _pipeline.PatchControlPoints = (uint)_state.State.PatchVertices;
+
+ _context.Renderer.Pipeline.SetPatchParameters(
+ _state.State.PatchVertices,
+ _state.State.TessOuterLevel.AsSpan(),
+ _state.State.TessInnerLevel.AsSpan());
+
+ _currentSpecState.SetTessellationMode(_state.State.TessMode);
+ }
+
+ /// <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;
+ _pipeline.RasterizerDiscard = !enable;
+ _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(RenderTargetUpdateFlags.UpdateAll);
+ }
+
+ /// <summary>
+ /// Updates render targets (color and depth-stencil buffers) based on current render target state.
+ /// </summary>
+ /// <param name="updateFlags">Flags indicating which render targets should be updated and how</param>
+ /// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
+ public void UpdateRenderTargetState(RenderTargetUpdateFlags updateFlags, int singleUse = -1)
+ {
+ var memoryManager = _channel.MemoryManager;
+ var rtControl = _state.State.RtControl;
+
+ bool useControl = updateFlags.HasFlag(RenderTargetUpdateFlags.UseControl);
+ bool layered = updateFlags.HasFlag(RenderTargetUpdateFlags.Layered);
+ bool singleColor = updateFlags.HasFlag(RenderTargetUpdateFlags.SingleColor);
+
+ 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) * samplesInX, (scissor.Y + scissor.Height) * samplesInY, 1);
+
+ int clipRegionWidth = int.MaxValue;
+ int clipRegionHeight = int.MaxValue;
+
+ bool changedScale = false;
+ uint rtNoAlphaMask = 0;
+
+ 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) || (singleColor && index != singleUse))
+ {
+ changedScale |= _channel.TextureManager.SetRenderTargetColor(index, null);
+
+ continue;
+ }
+
+ if (colorState.Format.NoAlpha())
+ {
+ rtNoAlphaMask |= 1u << index;
+ }
+
+ Image.Texture color = memoryManager.Physical.TextureCache.FindOrCreateTexture(
+ memoryManager,
+ colorState,
+ _vtgWritesRtLayer || layered,
+ samplesInX,
+ samplesInY,
+ sizeHint);
+
+ changedScale |= _channel.TextureManager.SetRenderTargetColor(index, color);
+
+ if (color != null)
+ {
+ if (clipRegionWidth > color.Width / samplesInX)
+ {
+ clipRegionWidth = color.Width / samplesInX;
+ }
+
+ if (clipRegionHeight > color.Height / samplesInY)
+ {
+ clipRegionHeight = color.Height / samplesInY;
+ }
+ }
+ }
+
+ bool dsEnable = _state.State.RtDepthStencilEnable;
+
+ Image.Texture depthStencil = null;
+
+ if (dsEnable && updateFlags.HasFlag(RenderTargetUpdateFlags.UpdateDepthStencil))
+ {
+ var dsState = _state.State.RtDepthStencilState;
+ var dsSize = _state.State.RtDepthStencilSize;
+
+ depthStencil = memoryManager.Physical.TextureCache.FindOrCreateTexture(
+ memoryManager,
+ dsState,
+ dsSize,
+ _vtgWritesRtLayer || layered,
+ samplesInX,
+ samplesInY,
+ sizeHint);
+
+ if (depthStencil != null)
+ {
+ if (clipRegionWidth > depthStencil.Width / samplesInX)
+ {
+ clipRegionWidth = depthStencil.Width / samplesInX;
+ }
+
+ if (clipRegionHeight > depthStencil.Height / samplesInY)
+ {
+ clipRegionHeight = depthStencil.Height / samplesInY;
+ }
+ }
+ }
+
+ changedScale |= _channel.TextureManager.SetRenderTargetDepthStencil(depthStencil);
+
+ if (changedScale)
+ {
+ float oldScale = _channel.TextureManager.RenderTargetScale;
+ _channel.TextureManager.UpdateRenderTargetScale(singleUse);
+
+ if (oldScale != _channel.TextureManager.RenderTargetScale)
+ {
+ _context.Renderer.Pipeline.SetRenderTargetScale(_channel.TextureManager.RenderTargetScale);
+
+ UpdateViewportTransform();
+ UpdateScissorState();
+ }
+ }
+
+ _channel.TextureManager.SetClipRegion(clipRegionWidth, clipRegionHeight);
+
+ if (useControl && _prevRtNoAlphaMask != rtNoAlphaMask)
+ {
+ _prevRtNoAlphaMask = rtNoAlphaMask;
+
+ UpdateBlendState();
+ }
+ }
+
+ /// <summary>
+ /// Updates specialization state based on render target state.
+ /// </summary>
+ public void UpdateRenderTargetSpecialization()
+ {
+ _currentSpecState.SetFragmentOutputTypes(_state.State.RtControl, ref _state.State.RtColorState);
+ }
+
+ /// <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>
+ internal 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>
+ public void UpdateScissorState()
+ {
+ const int MinX = 0;
+ const int MinY = 0;
+ const int MaxW = 0xffff;
+ const int MaxH = 0xffff;
+
+ Span<Rectangle<int>> regions = stackalloc Rectangle<int>[Constants.TotalViewports];
+
+ for (int index = 0; index < Constants.TotalViewports; index++)
+ {
+ ScissorState scissor = _state.State.ScissorState[index];
+
+ bool enable = scissor.Enable && (scissor.X1 != MinX ||
+ scissor.Y1 != MinY ||
+ scissor.X2 != MaxW ||
+ scissor.Y2 != MaxH);
+
+ if (enable)
+ {
+ int x = scissor.X1;
+ int y = scissor.Y1;
+ int width = scissor.X2 - x;
+ int height = scissor.Y2 - y;
+
+ if (_state.State.YControl.HasFlag(YControl.NegateY))
+ {
+ ref var screenScissor = ref _state.State.ScreenScissorState;
+ y = screenScissor.Height - height - y;
+
+ if (y < 0)
+ {
+ height += y;
+ y = 0;
+ }
+ }
+
+ float scale = _channel.TextureManager.RenderTargetScale;
+ if (scale != 1f)
+ {
+ x = (int)(x * scale);
+ y = (int)(y * scale);
+ width = (int)MathF.Ceiling(width * scale);
+ height = (int)MathF.Ceiling(height * scale);
+ }
+
+ regions[index] = new Rectangle<int>(x, y, width, height);
+ }
+ else
+ {
+ regions[index] = new Rectangle<int>(MinX, MinY, MaxW, MaxH);
+ }
+ }
+
+ _context.Renderer.Pipeline.SetScissors(regions);
+ }
+
+ /// <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;
+ bool clamp = (clip & ViewVolumeClipControl.DepthClampDisabled) == 0;
+
+ _pipeline.DepthClampEnable = clamp;
+ _context.Renderer.Pipeline.SetDepthClamp(clamp);
+ }
+
+ /// <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);
+
+ _currentSpecState.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()
+ {
+ DepthTestDescriptor descriptor = new DepthTestDescriptor(
+ _state.State.DepthTestEnable,
+ _state.State.DepthWriteEnable,
+ _state.State.DepthTestFunc);
+
+ _pipeline.DepthTest = descriptor;
+ _context.Renderer.Pipeline.SetDepthTest(descriptor);
+ }
+
+ /// <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;
+
+ bool disableTransform = _state.State.ViewportTransformEnable == 0;
+
+ UpdateFrontFace(yControl, face.FrontFace);
+ UpdateDepthMode();
+
+ bool flipY = yControl.HasFlag(YControl.NegateY);
+
+ Span<Viewport> viewports = stackalloc Viewport[Constants.TotalViewports];
+
+ for (int index = 0; index < Constants.TotalViewports; index++)
+ {
+ if (disableTransform)
+ {
+ ref var scissor = ref _state.State.ScreenScissorState;
+
+ float rScale = _channel.TextureManager.RenderTargetScale;
+ var scissorRect = new Rectangle<float>(0, 0, (scissor.X + scissor.Width) * rScale, (scissor.Y + scissor.Height) * rScale);
+
+ viewports[index] = new Viewport(scissorRect, ViewportSwizzle.PositiveX, ViewportSwizzle.PositiveY, ViewportSwizzle.PositiveZ, ViewportSwizzle.PositiveW, 0, 1);
+ continue;
+ }
+
+ ref var transform = ref _state.State.ViewportTransform[index];
+ ref var extents = ref _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;
+ }
+
+ 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;
+ }
+
+ Rectangle<float> region = new Rectangle<float>(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.SetDepthMode(GetDepthMode());
+ _context.Renderer.Pipeline.SetViewports(viewports, disableTransform);
+
+ _currentSpecState.SetViewportTransformDisable(_state.State.ViewportTransformEnable == 0);
+ _currentSpecState.SetDepthMode(GetDepthMode() == DepthMode.MinusOneToOne);
+ }
+
+ /// <summary>
+ /// Updates the depth mode (0 to 1 or -1 to 1) based on the current viewport and depth mode register state.
+ /// </summary>
+ private void UpdateDepthMode()
+ {
+ _context.Renderer.Pipeline.SetDepthMode(GetDepthMode());
+ }
+
+ /// <summary>
+ /// Updates polygon mode state based on current GPU state.
+ /// </summary>
+ private void UpdatePolygonMode()
+ {
+ _context.Renderer.Pipeline.SetPolygonMode(_state.State.PolygonModeFront, _state.State.PolygonModeBack);
+ }
+
+ /// <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);
+
+ _pipeline.BiasEnable = enables;
+ _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;
+ }
+
+ StencilTestDescriptor descriptor = 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);
+
+ _pipeline.StencilTest = descriptor;
+ _context.Renderer.Pipeline.SetStencilTest(descriptor);
+ }
+
+ /// <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);
+
+ _currentSpecState.SetPoolState(GetPoolState());
+ }
+
+ /// <summary>
+ /// Updates host vertex attributes based on guest GPU state.
+ /// </summary>
+ private void UpdateVertexAttribState()
+ {
+ uint vbEnableMask = _vbEnableMask;
+
+ Span<VertexAttribDescriptor> vertexAttribs = stackalloc VertexAttribDescriptor[Constants.TotalVertexAttribs];
+
+ for (int index = 0; index < Constants.TotalVertexAttribs; index++)
+ {
+ var vertexAttrib = _state.State.VertexAttribState[index];
+
+ int bufferIndex = vertexAttrib.UnpackBufferIndex();
+
+ if ((vbEnableMask & (1u << bufferIndex)) == 0)
+ {
+ // Using a vertex buffer that doesn't exist is invalid, so let's use a dummy attribute for those cases.
+ vertexAttribs[index] = new VertexAttribDescriptor(0, 0, true, Format.R32G32B32A32Float);
+ continue;
+ }
+
+ if (!FormatTable.TryGetAttribFormat(vertexAttrib.UnpackFormat(), out Format format))
+ {
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}.");
+
+ format = vertexAttrib.UnpackType() switch
+ {
+ VertexAttribType.Sint => Format.R32G32B32A32Sint,
+ VertexAttribType.Uint => Format.R32G32B32A32Uint,
+ _ => Format.R32G32B32A32Float
+ };
+ }
+
+ vertexAttribs[index] = new VertexAttribDescriptor(
+ bufferIndex,
+ vertexAttrib.UnpackOffset(),
+ vertexAttrib.UnpackIsConstant(),
+ format);
+ }
+
+ _pipeline.SetVertexAttribs(vertexAttribs);
+ _context.Renderer.Pipeline.SetVertexAttribs(vertexAttribs);
+ _currentSpecState.SetAttributeTypes(ref _state.State.VertexAttribState);
+ }
+
+ /// <summary>
+ /// Updates host line width based on guest GPU state.
+ /// </summary>
+ private void UpdateLineState()
+ {
+ float width = _state.State.LineWidthSmooth;
+ bool smooth = _state.State.LineSmoothEnable;
+
+ _pipeline.LineWidth = width;
+ _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);
+
+ _currentSpecState.SetProgramPointSizeEnable(isProgramPointSize);
+ _currentSpecState.SetPointSize(size);
+ }
+
+ /// <summary>
+ /// Updates host primitive restart based on guest GPU state.
+ /// </summary>
+ private void UpdatePrimitiveRestartState()
+ {
+ PrimitiveRestartState primitiveRestart = _state.State.PrimitiveRestartState;
+ bool enable = primitiveRestart.Enable && (_drawState.DrawIndexed || _state.State.PrimitiveRestartDrawArrays);
+
+ _pipeline.PrimitiveRestartEnable = enable;
+ _context.Renderer.Pipeline.SetPrimitiveRestart(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()
+ {
+ IndexType indexType = _state.State.IndexBufferState.Type;
+ bool indexTypeSmall = indexType == IndexType.UByte || indexType == IndexType.UShort;
+
+ _drawState.IsAnyVbInstanced = false;
+
+ bool drawIndexed = _drawState.DrawIndexed;
+ bool drawIndirect = _drawState.DrawIndirect;
+ int drawFirstVertex = _drawState.DrawFirstVertex;
+ int drawVertexCount = _drawState.DrawVertexCount;
+ uint vbEnableMask = 0;
+
+ for (int index = 0; index < Constants.TotalVertexBuffers; index++)
+ {
+ var vertexBuffer = _state.State.VertexBufferState[index];
+
+ if (!vertexBuffer.UnpackEnable())
+ {
+ _pipeline.VertexBuffers[index] = new BufferPipelineDescriptor(false, 0, 0);
+ _channel.BufferManager.SetVertexBuffer(index, 0, 0, 0, 0);
+
+ continue;
+ }
+
+ GpuVa endAddress = _state.State.VertexBufferEndAddress[index];
+
+ ulong address = vertexBuffer.Address.Pack();
+
+ if (_channel.MemoryManager.IsMapped(address))
+ {
+ vbEnableMask |= 1u << index;
+ }
+
+ int stride = vertexBuffer.UnpackStride();
+
+ bool instanced = _state.State.VertexBufferInstanced[index];
+
+ int divisor = instanced ? vertexBuffer.Divisor : 0;
+
+ _drawState.IsAnyVbInstanced |= divisor != 0;
+
+ ulong vbSize = endAddress.Pack() - address + 1;
+ ulong size;
+
+ if (_drawState.IbStreamer.HasInlineIndexData || drawIndexed || stride == 0 || instanced)
+ {
+ // This size may be (much) larger than the real vertex buffer size.
+ // Avoid calculating it this way, unless we don't have any other option.
+
+ size = vbSize;
+
+ if (stride > 0 && indexTypeSmall && drawIndexed && !drawIndirect && !instanced)
+ {
+ // If the index type is a small integer type, then we might be still able
+ // to reduce the vertex buffer size based on the maximum possible index value.
+
+ ulong maxVertexBufferSize = indexType == IndexType.UByte ? 0x100UL : 0x10000UL;
+
+ maxVertexBufferSize += _state.State.FirstVertex;
+ maxVertexBufferSize *= (uint)stride;
+
+ size = Math.Min(size, maxVertexBufferSize);
+ }
+ }
+ else
+ {
+ // For non-indexed draws, we can guess the size from the vertex count
+ // and stride.
+
+ int firstInstance = (int)_state.State.FirstInstance;
+
+ size = Math.Min(vbSize, (ulong)((firstInstance + drawFirstVertex + drawVertexCount) * stride));
+ }
+
+ _pipeline.VertexBuffers[index] = new BufferPipelineDescriptor(_channel.MemoryManager.IsMapped(address), stride, divisor);
+ _channel.BufferManager.SetVertexBuffer(index, address, size, stride, divisor);
+ }
+
+ if (_vbEnableMask != vbEnableMask)
+ {
+ _vbEnableMask = vbEnableMask;
+ UpdateVertexAttribState();
+ }
+ }
+
+ /// <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;
+
+ _pipeline.CullEnable = face.CullEnable;
+ _pipeline.CullMode = face.CullFace;
+ _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;
+ }
+
+ _pipeline.FrontFace = frontFace;
+ _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;
+ _pipeline.ColorWriteMask[index] = componentMask;
+ }
+
+ _context.Renderer.Pipeline.SetRenderTargetColorMasks(componentMasks);
+ }
+
+ /// <summary>
+ /// Updates host render target color buffer blending state, based on guest state.
+ /// </summary>
+ private void UpdateBlendState()
+ {
+ if (_state.State.BlendUcodeEnable != BlendUcodeEnable.Disabled)
+ {
+ if (_context.Capabilities.SupportsBlendEquationAdvanced && _blendManager.TryGetAdvancedBlend(out var blendDescriptor))
+ {
+ // Try to HLE it using advanced blend on the host if we can.
+ _context.Renderer.Pipeline.SetBlendState(blendDescriptor);
+ return;
+ }
+ else
+ {
+ // TODO: Blend emulation fallback.
+ }
+ }
+
+ bool blendIndependent = _state.State.BlendIndependent;
+ ColorF blendConstant = _state.State.BlendConstant;
+
+ bool dualSourceBlendEnabled = false;
+
+ if (blendIndependent)
+ {
+ for (int index = 0; index < Constants.TotalRenderTargets; index++)
+ {
+ bool enable = _state.State.BlendEnable[index];
+ var blend = _state.State.BlendState[index];
+
+ var descriptor = new BlendDescriptor(
+ enable,
+ blendConstant,
+ blend.ColorOp,
+ FilterBlendFactor(blend.ColorSrcFactor, index),
+ FilterBlendFactor(blend.ColorDstFactor, index),
+ blend.AlphaOp,
+ FilterBlendFactor(blend.AlphaSrcFactor, index),
+ FilterBlendFactor(blend.AlphaDstFactor, index));
+
+ if (enable &&
+ (blend.ColorSrcFactor.IsDualSource() ||
+ blend.ColorDstFactor.IsDualSource() ||
+ blend.AlphaSrcFactor.IsDualSource() ||
+ blend.AlphaDstFactor.IsDualSource()))
+ {
+ dualSourceBlendEnabled = true;
+ }
+
+ _pipeline.BlendDescriptors[index] = descriptor;
+ _context.Renderer.Pipeline.SetBlendState(index, descriptor);
+ }
+ }
+ else
+ {
+ bool enable = _state.State.BlendEnable[0];
+ var blend = _state.State.BlendStateCommon;
+
+ var descriptor = new BlendDescriptor(
+ enable,
+ blendConstant,
+ blend.ColorOp,
+ FilterBlendFactor(blend.ColorSrcFactor, 0),
+ FilterBlendFactor(blend.ColorDstFactor, 0),
+ blend.AlphaOp,
+ FilterBlendFactor(blend.AlphaSrcFactor, 0),
+ FilterBlendFactor(blend.AlphaDstFactor, 0));
+
+ if (enable &&
+ (blend.ColorSrcFactor.IsDualSource() ||
+ blend.ColorDstFactor.IsDualSource() ||
+ blend.AlphaSrcFactor.IsDualSource() ||
+ blend.AlphaDstFactor.IsDualSource()))
+ {
+ dualSourceBlendEnabled = true;
+ }
+
+ for (int index = 0; index < Constants.TotalRenderTargets; index++)
+ {
+ _pipeline.BlendDescriptors[index] = descriptor;
+ _context.Renderer.Pipeline.SetBlendState(index, descriptor);
+ }
+ }
+
+ _currentSpecState.SetDualSourceBlendEnabled(dualSourceBlendEnabled);
+ }
+
+ /// <summary>
+ /// Gets a blend factor for the color target currently.
+ /// This will return <paramref name="factor"/> unless the target format has no alpha component,
+ /// in which case it will replace destination alpha factor with a constant factor of one or zero.
+ /// </summary>
+ /// <param name="factor">Input factor</param>
+ /// <param name="index">Color target index</param>
+ /// <returns>New blend factor</returns>
+ private BlendFactor FilterBlendFactor(BlendFactor factor, int index)
+ {
+ // If any color target format without alpha is being used, we need to make sure that
+ // if blend is active, it will not use destination alpha as a factor.
+ // That is required because RGBX formats are emulated using host RGBA formats.
+
+ if (_state.State.RtColorState[index].Format.NoAlpha())
+ {
+ switch (factor)
+ {
+ case BlendFactor.DstAlpha:
+ case BlendFactor.DstAlphaGl:
+ factor = BlendFactor.One;
+ break;
+ case BlendFactor.OneMinusDstAlpha:
+ case BlendFactor.OneMinusDstAlphaGl:
+ factor = BlendFactor.Zero;
+ break;
+ }
+ }
+
+ return factor;
+ }
+
+ /// <summary>
+ /// Updates host logical operation state, based on guest state.
+ /// </summary>
+ private void UpdateLogicOpState()
+ {
+ LogicalOpState logicOpState = _state.State.LogicOpState;
+
+ _pipeline.SetLogicOpState(logicOpState.Enable, logicOpState.LogicalOp);
+ _context.Renderer.Pipeline.SetLogicOpState(logicOpState.Enable, logicOpState.LogicalOp);
+ }
+
+ /// <summary>
+ /// Updates multisample state, based on guest state.
+ /// </summary>
+ private void UpdateMultisampleState()
+ {
+ bool alphaToCoverageEnable = (_state.State.MultisampleControl & 1) != 0;
+ bool alphaToOneEnable = (_state.State.MultisampleControl & 0x10) != 0;
+
+ _context.Renderer.Pipeline.SetMultisampleState(new MultisampleDescriptor(
+ alphaToCoverageEnable,
+ _state.State.AlphaToCoverageDitherEnable,
+ alphaToOneEnable));
+
+ _currentSpecState.SetAlphaToCoverageEnable(alphaToCoverageEnable, _state.State.AlphaToCoverageDitherEnable);
+ }
+
+ /// <summary>
+ /// Updates the early z flag, based on guest state.
+ /// </summary>
+ private void UpdateEarlyZState()
+ {
+ _currentSpecState.SetEarlyZForce(_state.State.EarlyZForce);
+ }
+
+ /// <summary>
+ /// Updates host shaders based on the guest GPU state.
+ /// </summary>
+ private void UpdateShaderState()
+ {
+ var shaderCache = _channel.MemoryManager.Physical.ShaderCache;
+
+ _vtgWritesRtLayer = false;
+
+ ShaderAddresses addresses = new ShaderAddresses();
+ Span<ulong> addressesSpan = addresses.AsSpan();
+
+ 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;
+ }
+
+ addressesSpan[index] = baseAddress + shader.Offset;
+ }
+
+ CachedShaderProgram gs = shaderCache.GetGraphicsShader(ref _state.State, ref _pipeline, _channel, ref _currentSpecState.GetPoolState(), ref _currentSpecState.GetGraphicsState(), addresses);
+
+ // Consume the modified flag for spec state so that it isn't checked again.
+ _currentSpecState.SetShader(gs);
+
+ _shaderSpecState = gs.SpecializationState;
+
+ byte oldVsClipDistancesWritten = _vsClipDistancesWritten;
+
+ _drawState.VsUsesInstanceId = gs.Shaders[1]?.Info.UsesInstanceId ?? false;
+ _vsUsesDrawParameters = gs.Shaders[1]?.Info.UsesDrawParameters ?? false;
+ _vsClipDistancesWritten = gs.Shaders[1]?.Info.ClipDistancesWritten ?? 0;
+
+ if (oldVsClipDistancesWritten != _vsClipDistancesWritten)
+ {
+ UpdateUserClipState();
+ }
+
+ UpdateShaderBindings(gs.Bindings);
+
+ for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
+ {
+ ShaderProgramInfo info = gs.Shaders[stageIndex + 1]?.Info;
+
+ if (info?.UsesRtLayer == true)
+ {
+ _vtgWritesRtLayer = true;
+ }
+
+ _currentProgramInfo[stageIndex] = info;
+ }
+
+ _context.Renderer.Pipeline.SetProgram(gs.HostProgram);
+ }
+
+ /// <summary>
+ /// Updates bindings consumed by the shader on the texture and buffer managers.
+ /// </summary>
+ /// <param name="bindings">Bindings for the active shader</param>
+ private void UpdateShaderBindings(CachedShaderBindings bindings)
+ {
+ _channel.TextureManager.SetGraphicsBindings(bindings);
+ _channel.BufferManager.SetGraphicsBufferBindings(bindings);
+ }
+
+ /// <summary>
+ /// Gets the current texture pool state.
+ /// </summary>
+ /// <returns>Texture pool state</returns>
+ private GpuChannelPoolState GetPoolState()
+ {
+ return new GpuChannelPoolState(
+ _state.State.TexturePoolState.Address.Pack(),
+ _state.State.TexturePoolState.MaximumId,
+ (int)_state.State.TextureBufferIndex);
+ }
+
+ /// <summary>
+ /// Gets the depth mode that is currently being used (zero to one or minus one to one).
+ /// </summary>
+ /// <returns>Current depth mode</returns>
+ private DepthMode GetDepthMode()
+ {
+ ref var transform = ref _state.State.ViewportTransform[0];
+ ref var extents = ref _state.State.ViewportExtents[0];
+
+ DepthMode depthMode;
+
+ if (!float.IsInfinity(extents.DepthNear) &&
+ !float.IsInfinity(extents.DepthFar) &&
+ (extents.DepthFar - extents.DepthNear) != 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 = extents.DepthNear != transform.TranslateZ &&
+ extents.DepthFar != transform.TranslateZ
+ ? DepthMode.MinusOneToOne
+ : DepthMode.ZeroToOne;
+ }
+ else
+ {
+ // If we can't guess from the viewport transform, then just use the depth mode register.
+ depthMode = (DepthMode)(_state.State.DepthMode & 1);
+ }
+
+ return depthMode;
+ }
+
+ /// <summary>
+ /// Forces the shaders to be rebound on the next draw.
+ /// </summary>
+ public void ForceShaderUpdate()
+ {
+ _updateTracker.ForceDirty(ShaderStateIndex);
+ }
+ }
+}