using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Shader; using Ryujinx.Graphics.Shader; namespace Ryujinx.Graphics.Gpu.Engine.Threed { /// /// Maintains a "current" specialiation state, and provides a flag to check if it has changed meaningfully. /// internal class SpecializationStateUpdater { private readonly GpuContext _context; private GpuChannelGraphicsState _graphics; private GpuChannelPoolState _pool; private bool _usesDrawParameters; private bool _usesTopology; private bool _changed; /// /// Creates a new instance of the specialization state updater class. /// /// GPU context public SpecializationStateUpdater(GpuContext context) { _context = context; } /// /// Signal that the specialization state has changed. /// private void Signal() { _changed = true; } /// /// Checks if the specialization state has changed since the last check. /// /// True if it has changed, false otherwise public bool HasChanged() { if (_changed) { _changed = false; return true; } else { return false; } } /// /// Sets the active shader, clearing the dirty state and recording if certain specializations are noteworthy. /// /// The active shader public void SetShader(CachedShaderProgram gs) { _usesDrawParameters = gs.Shaders[1]?.Info.UsesDrawParameters ?? false; _usesTopology = gs.SpecializationState.IsPrimitiveTopologyQueried(); _changed = false; } /// /// Get the current graphics state. /// /// GPU graphics state public ref GpuChannelGraphicsState GetGraphicsState() { return ref _graphics; } /// /// Get the current pool state. /// /// GPU pool state public ref GpuChannelPoolState GetPoolState() { return ref _pool; } /// /// Early Z force enable. /// /// The new value public void SetEarlyZForce(bool value) { _graphics.EarlyZForce = value; Signal(); } /// /// Primitive topology of current draw. /// /// The new value public void SetTopology(PrimitiveTopology value) { if (value != _graphics.Topology) { _graphics.Topology = value; if (_usesTopology) { Signal(); } } } /// /// Tessellation mode. /// /// The new value public void SetTessellationMode(TessMode value) { if (value.Packed != _graphics.TessellationMode.Packed) { _graphics.TessellationMode = value; Signal(); } } /// /// Updates alpha-to-coverage state, and sets it as changed. /// /// Whether alpha-to-coverage is enabled /// Whether alpha-to-coverage dithering is enabled public void SetAlphaToCoverageEnable(bool enable, bool ditherEnable) { _graphics.AlphaToCoverageEnable = enable; _graphics.AlphaToCoverageDitherEnable = ditherEnable; Signal(); } /// /// Indicates whether the viewport transform is disabled. /// /// The new value public void SetViewportTransformDisable(bool value) { if (value != _graphics.ViewportTransformDisable) { _graphics.ViewportTransformDisable = value; Signal(); } } /// /// Depth mode zero to one or minus one to one. /// /// The new value public void SetDepthMode(bool value) { if (value != _graphics.DepthMode) { _graphics.DepthMode = value; Signal(); } } /// /// Indicates if the point size is set on the shader or is fixed. /// /// The new value public void SetProgramPointSizeEnable(bool value) { if (value != _graphics.ProgramPointSizeEnable) { _graphics.ProgramPointSizeEnable = value; Signal(); } } /// /// Point size used if is provided false. /// /// The new value public void SetPointSize(float value) { if (value != _graphics.PointSize) { _graphics.PointSize = value; Signal(); } } /// /// Updates alpha test specialization state, and sets it as changed. /// /// Whether alpha test is enabled /// The value to compare with the fragment output alpha /// The comparison that decides if the fragment should be discarded public void SetAlphaTest(bool enable, float reference, CompareOp op) { _graphics.AlphaTestEnable = enable; _graphics.AlphaTestReference = reference; _graphics.AlphaTestCompare = op; Signal(); } /// /// Updates the type of the vertex attributes consumed by the shader. /// /// The new state public void SetAttributeTypes(ref Array32 state) { bool changed = false; ref Array32 attributeTypes = ref _graphics.AttributeTypes; for (int location = 0; location < state.Length; location++) { VertexAttribType type = state[location].UnpackType(); AttributeType value = type switch { VertexAttribType.Sint => AttributeType.Sint, VertexAttribType.Uint => AttributeType.Uint, _ => AttributeType.Float, }; if (attributeTypes[location] != value) { attributeTypes[location] = value; changed = true; } } if (changed) { Signal(); } } /// /// Updates the type of the outputs produced by the fragment shader based on the current render target state. /// /// The render target control register /// The color attachment state public void SetFragmentOutputTypes(RtControl rtControl, ref Array8 state) { bool changed = false; int count = rtControl.UnpackCount(); for (int index = 0; index < Constants.TotalRenderTargets; index++) { int rtIndex = rtControl.UnpackPermutationIndex(index); var colorState = state[rtIndex]; if (index < count && StateUpdater.IsRtEnabled(colorState)) { Format format = colorState.Format.Convert().Format; AttributeType type = format.IsInteger() ? (format.IsSint() ? AttributeType.Sint : AttributeType.Uint) : AttributeType.Float; if (type != _graphics.FragmentOutputTypes[index]) { _graphics.FragmentOutputTypes[index] = type; changed = true; } } } if (changed && _context.Capabilities.NeedsFragmentOutputSpecialization) { Signal(); } } /// /// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0. /// /// The new value public void SetHasConstantBufferDrawParameters(bool value) { if (value != _graphics.HasConstantBufferDrawParameters) { _graphics.HasConstantBufferDrawParameters = value; if (_usesDrawParameters) { Signal(); } } } /// /// Indicates that any storage buffer use is unaligned. /// /// The new value /// True if the unaligned state changed, false otherwise public bool SetHasUnalignedStorageBuffer(bool value) { if (value != _graphics.HasUnalignedStorageBuffer) { _graphics.HasUnalignedStorageBuffer = value; Signal(); return true; } return false; } /// /// Sets the GPU pool state. /// /// The new state public void SetPoolState(GpuChannelPoolState state) { if (!state.Equals(_pool)) { _pool = state; Signal(); } } /// /// Sets the dual-source blend enabled state. /// /// True if blending is enabled and using dual-source blend public void SetDualSourceBlendEnabled(bool enabled) { if (enabled != _graphics.DualSourceBlendEnable) { _graphics.DualSourceBlendEnable = enabled; Signal(); } } /// /// Sets the Y negate enabled state. /// /// True if Y negate of the fragment coordinates is enabled public void SetYNegateEnabled(bool enabled) { if (enabled != _graphics.YNegateEnabled) { _graphics.YNegateEnabled = enabled; Signal(); } } } }