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;
bool mayConvertVtgToCompute = ShaderCache.MayConvertVtgToCompute(ref _context.Capabilities);
bool supportsScaledFormats = _context.Capabilities.SupportsScaledVertexFormats && !mayConvertVtgToCompute;
for (int location = 0; location < state.Length; location++)
{
VertexAttribType type = state[location].UnpackType();
VertexAttribSize size = state[location].UnpackSize();
AttributeType value;
if (supportsScaledFormats)
{
value = type switch
{
VertexAttribType.Sint => AttributeType.Sint,
VertexAttribType.Uint => AttributeType.Uint,
_ => AttributeType.Float,
};
}
else
{
value = type switch
{
VertexAttribType.Sint => AttributeType.Sint,
VertexAttribType.Uint => AttributeType.Uint,
VertexAttribType.Uscaled => AttributeType.Uscaled,
VertexAttribType.Sscaled => AttributeType.Sscaled,
_ => AttributeType.Float,
};
}
if (mayConvertVtgToCompute && (size == VertexAttribSize.Rgb10A2 || size == VertexAttribSize.Rg11B10))
{
value |= AttributeType.Packed;
if (type == VertexAttribType.Snorm ||
type == VertexAttribType.Sint ||
type == VertexAttribType.Sscaled)
{
value |= AttributeType.PackedRgb10A2Signed;
}
}
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();
}
}
}
}