aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs')
-rw-r--r--src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs874
1 files changed, 874 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
new file mode 100644
index 00000000..b2c4fccd
--- /dev/null
+++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
@@ -0,0 +1,874 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Gpu.Shader.DiskCache;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ class ShaderSpecializationState
+ {
+ private const uint ComsMagic = (byte)'C' | ((byte)'O' << 8) | ((byte)'M' << 16) | ((byte)'S' << 24);
+ private const uint GfxsMagic = (byte)'G' | ((byte)'F' << 8) | ((byte)'X' << 16) | ((byte)'S' << 24);
+ private const uint TfbdMagic = (byte)'T' | ((byte)'F' << 8) | ((byte)'B' << 16) | ((byte)'D' << 24);
+ private const uint TexkMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'K' << 24);
+ private const uint TexsMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'S' << 24);
+ private const uint PgpsMagic = (byte)'P' | ((byte)'G' << 8) | ((byte)'P' << 16) | ((byte)'S' << 24);
+
+ /// <summary>
+ /// Flags indicating GPU state that is used by the shader.
+ /// </summary>
+ [Flags]
+ private enum QueriedStateFlags
+ {
+ EarlyZForce = 1 << 0,
+ PrimitiveTopology = 1 << 1,
+ TessellationMode = 1 << 2,
+ TransformFeedback = 1 << 3
+ }
+
+ private QueriedStateFlags _queriedState;
+ private bool _compute;
+ private byte _constantBufferUsePerStage;
+
+ /// <summary>
+ /// Compute engine state.
+ /// </summary>
+ public GpuChannelComputeState ComputeState;
+
+ /// <summary>
+ /// 3D engine state.
+ /// </summary>
+ public GpuChannelGraphicsState GraphicsState;
+
+ /// <summary>
+ /// Contant buffers bound at the time the shader was compiled, per stage.
+ /// </summary>
+ public Array5<uint> ConstantBufferUse;
+
+ /// <summary>
+ /// Pipeline state captured at the time of shader use.
+ /// </summary>
+ public ProgramPipelineState? PipelineState;
+
+ /// <summary>
+ /// Transform feedback buffers active at the time the shader was compiled.
+ /// </summary>
+ public TransformFeedbackDescriptor[] TransformFeedbackDescriptors;
+
+ /// <summary>
+ /// Flags indicating texture state that is used by the shader.
+ /// </summary>
+ [Flags]
+ private enum QueriedTextureStateFlags
+ {
+ TextureFormat = 1 << 0,
+ SamplerType = 1 << 1,
+ CoordNormalized = 1 << 2
+ }
+
+ /// <summary>
+ /// Reference type wrapping a value.
+ /// </summary>
+ private class Box<T>
+ {
+ /// <summary>
+ /// Wrapped value.
+ /// </summary>
+ public T Value;
+ }
+
+ /// <summary>
+ /// State of a texture or image that is accessed by the shader.
+ /// </summary>
+ private struct TextureSpecializationState
+ {
+ // New fields should be added to the end of the struct to keep disk shader cache compatibility.
+
+ /// <summary>
+ /// Flags indicating which state of the texture the shader depends on.
+ /// </summary>
+ public QueriedTextureStateFlags QueriedFlags;
+
+ /// <summary>
+ /// Encoded texture format value.
+ /// </summary>
+ public uint Format;
+
+ /// <summary>
+ /// True if the texture format is sRGB, false otherwise.
+ /// </summary>
+ public bool FormatSrgb;
+
+ /// <summary>
+ /// Texture target.
+ /// </summary>
+ public TextureTarget TextureTarget;
+
+ /// <summary>
+ /// Indicates if the coordinates used to sample the texture are normalized or not (0.0..1.0 or 0..Width/Height).
+ /// </summary>
+ public bool CoordNormalized;
+ }
+
+ /// <summary>
+ /// Texture binding information, used to identify each texture accessed by the shader.
+ /// </summary>
+ private readonly record struct TextureKey
+ {
+ // New fields should be added to the end of the struct to keep disk shader cache compatibility.
+
+ /// <summary>
+ /// Shader stage where the texture is used.
+ /// </summary>
+ public readonly int StageIndex;
+
+ /// <summary>
+ /// Texture handle offset in words on the texture buffer.
+ /// </summary>
+ public readonly int Handle;
+
+ /// <summary>
+ /// Constant buffer slot of the texture buffer (-1 to use the texture buffer index GPU register).
+ /// </summary>
+ public readonly int CbufSlot;
+
+ /// <summary>
+ /// Creates a new texture key.
+ /// </summary>
+ /// <param name="stageIndex">Shader stage where the texture is used</param>
+ /// <param name="handle">Texture handle offset in words on the texture buffer</param>
+ /// <param name="cbufSlot">Constant buffer slot of the texture buffer (-1 to use the texture buffer index GPU register)</param>
+ public TextureKey(int stageIndex, int handle, int cbufSlot)
+ {
+ StageIndex = stageIndex;
+ Handle = handle;
+ CbufSlot = cbufSlot;
+ }
+ }
+
+ private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
+ private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures;
+ private Box<TextureSpecializationState>[][] _textureByBinding;
+ private Box<TextureSpecializationState>[][] _imageByBinding;
+
+ /// <summary>
+ /// Creates a new instance of the shader specialization state.
+ /// </summary>
+ private ShaderSpecializationState()
+ {
+ _textureSpecialization = new Dictionary<TextureKey, Box<TextureSpecializationState>>();
+ }
+
+ /// <summary>
+ /// Creates a new instance of the shader specialization state.
+ /// </summary>
+ /// <param name="state">Current compute engine state</param>
+ public ShaderSpecializationState(ref GpuChannelComputeState state) : this()
+ {
+ ComputeState = state;
+ _compute = true;
+ }
+
+ /// <summary>
+ /// Creates a new instance of the shader specialization state.
+ /// </summary>
+ /// <param name="state">Current 3D engine state</param>
+ /// <param name="descriptors">Optional transform feedback buffers in use, if any</param>
+ private ShaderSpecializationState(ref GpuChannelGraphicsState state, TransformFeedbackDescriptor[] descriptors) : this()
+ {
+ GraphicsState = state;
+ _compute = false;
+
+ if (descriptors != null)
+ {
+ TransformFeedbackDescriptors = descriptors;
+ _queriedState |= QueriedStateFlags.TransformFeedback;
+ }
+ }
+
+ /// <summary>
+ /// Prepare the shader specialization state for quick binding lookups.
+ /// </summary>
+ /// <param name="stages">The shader stages</param>
+ public void Prepare(CachedShaderStage[] stages)
+ {
+ _allTextures = _textureSpecialization.ToArray();
+
+ _textureByBinding = new Box<TextureSpecializationState>[stages.Length][];
+ _imageByBinding = new Box<TextureSpecializationState>[stages.Length][];
+
+ for (int i = 0; i < stages.Length; i++)
+ {
+ CachedShaderStage stage = stages[i];
+ if (stage?.Info != null)
+ {
+ var textures = stage.Info.Textures;
+ var images = stage.Info.Images;
+
+ var texBindings = new Box<TextureSpecializationState>[textures.Count];
+ var imageBindings = new Box<TextureSpecializationState>[images.Count];
+
+ int stageIndex = Math.Max(i - 1, 0); // Don't count VertexA for looking up spec state. No-Op for compute.
+
+ for (int j = 0; j < textures.Count; j++)
+ {
+ var texture = textures[j];
+ texBindings[j] = GetTextureSpecState(stageIndex, texture.HandleIndex, texture.CbufSlot);
+ }
+
+ for (int j = 0; j < images.Count; j++)
+ {
+ var image = images[j];
+ imageBindings[j] = GetTextureSpecState(stageIndex, image.HandleIndex, image.CbufSlot);
+ }
+
+ _textureByBinding[i] = texBindings;
+ _imageByBinding[i] = imageBindings;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Creates a new instance of the shader specialization state.
+ /// </summary>
+ /// <param name="state">Current 3D engine state</param>
+ /// <param name="pipelineState">Current program pipeline state</param>
+ /// <param name="descriptors">Optional transform feedback buffers in use, if any</param>
+ public ShaderSpecializationState(
+ ref GpuChannelGraphicsState state,
+ ref ProgramPipelineState pipelineState,
+ TransformFeedbackDescriptor[] descriptors) : this(ref state, descriptors)
+ {
+ PipelineState = pipelineState;
+ }
+
+ /// <summary>
+ /// Creates a new instance of the shader specialization state.
+ /// </summary>
+ /// <param name="state">Current 3D engine state</param>
+ /// <param name="pipelineState">Current program pipeline state</param>
+ /// <param name="descriptors">Optional transform feedback buffers in use, if any</param>
+ public ShaderSpecializationState(
+ ref GpuChannelGraphicsState state,
+ ProgramPipelineState? pipelineState,
+ TransformFeedbackDescriptor[] descriptors) : this(ref state, descriptors)
+ {
+ PipelineState = pipelineState;
+ }
+
+ /// <summary>
+ /// Indicates that the shader accesses the early Z force state.
+ /// </summary>
+ public void RecordEarlyZForce()
+ {
+ _queriedState |= QueriedStateFlags.EarlyZForce;
+ }
+
+ /// <summary>
+ /// Indicates that the shader accesses the primitive topology state.
+ /// </summary>
+ public void RecordPrimitiveTopology()
+ {
+ _queriedState |= QueriedStateFlags.PrimitiveTopology;
+ }
+
+ /// <summary>
+ /// Indicates that the shader accesses the tessellation mode state.
+ /// </summary>
+ public void RecordTessellationMode()
+ {
+ _queriedState |= QueriedStateFlags.TessellationMode;
+ }
+
+ /// <summary>
+ /// Indicates that the shader accesses the constant buffer use state.
+ /// </summary>
+ /// <param name="stageIndex">Shader stage index</param>
+ /// <param name="useMask">Mask indicating the constant buffers bound at the time of the shader compilation</param>
+ public void RecordConstantBufferUse(int stageIndex, uint useMask)
+ {
+ ConstantBufferUse[stageIndex] = useMask;
+ _constantBufferUsePerStage |= (byte)(1 << stageIndex);
+ }
+
+ /// <summary>
+ /// Indicates that a given texture is accessed by the shader.
+ /// </summary>
+ /// <param name="stageIndex">Shader stage where the texture is used</param>
+ /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
+ /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
+ /// <param name="descriptor">Descriptor of the texture</param>
+ public void RegisterTexture(int stageIndex, int handle, int cbufSlot, Image.TextureDescriptor descriptor)
+ {
+ Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
+ state.Value.Format = descriptor.UnpackFormat();
+ state.Value.FormatSrgb = descriptor.UnpackSrgb();
+ state.Value.TextureTarget = descriptor.UnpackTextureTarget();
+ state.Value.CoordNormalized = descriptor.UnpackTextureCoordNormalized();
+ }
+
+ /// <summary>
+ /// Indicates that a given texture is accessed by the shader.
+ /// </summary>
+ /// <param name="stageIndex">Shader stage where the texture is used</param>
+ /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
+ /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
+ /// <param name="format">Maxwell texture format value</param>
+ /// <param name="formatSrgb">Whenever the texture format is a sRGB format</param>
+ /// <param name="target">Texture target type</param>
+ /// <param name="coordNormalized">Whenever the texture coordinates used on the shader are considered normalized</param>
+ public void RegisterTexture(
+ int stageIndex,
+ int handle,
+ int cbufSlot,
+ uint format,
+ bool formatSrgb,
+ TextureTarget target,
+ bool coordNormalized)
+ {
+ Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
+ state.Value.Format = format;
+ state.Value.FormatSrgb = formatSrgb;
+ state.Value.TextureTarget = target;
+ state.Value.CoordNormalized = coordNormalized;
+ }
+
+ /// <summary>
+ /// Indicates that the format of a given texture was used during the shader translation process.
+ /// </summary>
+ /// <param name="stageIndex">Shader stage where the texture is used</param>
+ /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
+ /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
+ public void RecordTextureFormat(int stageIndex, int handle, int cbufSlot)
+ {
+ Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
+ state.Value.QueriedFlags |= QueriedTextureStateFlags.TextureFormat;
+ }
+
+ /// <summary>
+ /// Indicates that the target of a given texture was used during the shader translation process.
+ /// </summary>
+ /// <param name="stageIndex">Shader stage where the texture is used</param>
+ /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
+ /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
+ public void RecordTextureSamplerType(int stageIndex, int handle, int cbufSlot)
+ {
+ Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
+ state.Value.QueriedFlags |= QueriedTextureStateFlags.SamplerType;
+ }
+
+ /// <summary>
+ /// Indicates that the coordinate normalization state of a given texture was used during the shader translation process.
+ /// </summary>
+ /// <param name="stageIndex">Shader stage where the texture is used</param>
+ /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
+ /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
+ public void RecordTextureCoordNormalized(int stageIndex, int handle, int cbufSlot)
+ {
+ Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
+ state.Value.QueriedFlags |= QueriedTextureStateFlags.CoordNormalized;
+ }
+
+ /// <summary>
+ /// Checks if primitive topology was queried by the shader.
+ /// </summary>
+ /// <returns>True if queried, false otherwise</returns>
+ public bool IsPrimitiveTopologyQueried()
+ {
+ return _queriedState.HasFlag(QueriedStateFlags.PrimitiveTopology);
+ }
+
+ /// <summary>
+ /// Checks if a given texture was registerd on this specialization state.
+ /// </summary>
+ /// <param name="stageIndex">Shader stage where the texture is used</param>
+ /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
+ /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
+ public bool TextureRegistered(int stageIndex, int handle, int cbufSlot)
+ {
+ return GetTextureSpecState(stageIndex, handle, cbufSlot) != null;
+ }
+
+ /// <summary>
+ /// Gets the recorded format of a given texture.
+ /// </summary>
+ /// <param name="stageIndex">Shader stage where the texture is used</param>
+ /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
+ /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
+ public (uint, bool) GetFormat(int stageIndex, int handle, int cbufSlot)
+ {
+ TextureSpecializationState state = GetTextureSpecState(stageIndex, handle, cbufSlot).Value;
+ return (state.Format, state.FormatSrgb);
+ }
+
+ /// <summary>
+ /// Gets the recorded target of a given texture.
+ /// </summary>
+ /// <param name="stageIndex">Shader stage where the texture is used</param>
+ /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
+ /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
+ public TextureTarget GetTextureTarget(int stageIndex, int handle, int cbufSlot)
+ {
+ return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.TextureTarget;
+ }
+
+ /// <summary>
+ /// Gets the recorded coordinate normalization state of a given texture.
+ /// </summary>
+ /// <param name="stageIndex">Shader stage where the texture is used</param>
+ /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
+ /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
+ public bool GetCoordNormalized(int stageIndex, int handle, int cbufSlot)
+ {
+ return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized;
+ }
+
+ /// <summary>
+ /// Gets texture specialization state for a given texture, or create a new one if not present.
+ /// </summary>
+ /// <param name="stageIndex">Shader stage where the texture is used</param>
+ /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
+ /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
+ /// <returns>Texture specialization state</returns>
+ private Box<TextureSpecializationState> GetOrCreateTextureSpecState(int stageIndex, int handle, int cbufSlot)
+ {
+ TextureKey key = new TextureKey(stageIndex, handle, cbufSlot);
+
+ if (!_textureSpecialization.TryGetValue(key, out Box<TextureSpecializationState> state))
+ {
+ _textureSpecialization.Add(key, state = new Box<TextureSpecializationState>());
+ }
+
+ return state;
+ }
+
+ /// <summary>
+ /// Gets texture specialization state for a given texture.
+ /// </summary>
+ /// <param name="stageIndex">Shader stage where the texture is used</param>
+ /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
+ /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
+ /// <returns>Texture specialization state</returns>
+ private Box<TextureSpecializationState> GetTextureSpecState(int stageIndex, int handle, int cbufSlot)
+ {
+ TextureKey key = new TextureKey(stageIndex, handle, cbufSlot);
+
+ if (_textureSpecialization.TryGetValue(key, out Box<TextureSpecializationState> state))
+ {
+ return state;
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Checks if the recorded state matches the current GPU 3D engine state.
+ /// </summary>
+ /// <param name="channel">GPU channel</param>
+ /// <param name="poolState">Texture pool state</param>
+ /// <param name="graphicsState">Graphics state</param>
+ /// <param name="usesDrawParameters">Indicates whether the vertex shader accesses draw parameters</param>
+ /// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
+ /// <returns>True if the state matches, false otherwise</returns>
+ public bool MatchesGraphics(
+ GpuChannel channel,
+ ref GpuChannelPoolState poolState,
+ ref GpuChannelGraphicsState graphicsState,
+ bool usesDrawParameters,
+ bool checkTextures)
+ {
+ if (graphicsState.ViewportTransformDisable != GraphicsState.ViewportTransformDisable)
+ {
+ return false;
+ }
+
+ bool thisA2cDitherEnable = GraphicsState.AlphaToCoverageEnable && GraphicsState.AlphaToCoverageDitherEnable;
+ bool otherA2cDitherEnable = graphicsState.AlphaToCoverageEnable && graphicsState.AlphaToCoverageDitherEnable;
+
+ if (otherA2cDitherEnable != thisA2cDitherEnable)
+ {
+ return false;
+ }
+
+ if (graphicsState.DepthMode != GraphicsState.DepthMode)
+ {
+ return false;
+ }
+
+ if (graphicsState.AlphaTestEnable != GraphicsState.AlphaTestEnable)
+ {
+ return false;
+ }
+
+ if (graphicsState.AlphaTestEnable &&
+ (graphicsState.AlphaTestCompare != GraphicsState.AlphaTestCompare ||
+ graphicsState.AlphaTestReference != GraphicsState.AlphaTestReference))
+ {
+ return false;
+ }
+
+ if (!graphicsState.AttributeTypes.AsSpan().SequenceEqual(GraphicsState.AttributeTypes.AsSpan()))
+ {
+ return false;
+ }
+
+ if (usesDrawParameters && graphicsState.HasConstantBufferDrawParameters != GraphicsState.HasConstantBufferDrawParameters)
+ {
+ return false;
+ }
+
+ if (graphicsState.HasUnalignedStorageBuffer != GraphicsState.HasUnalignedStorageBuffer)
+ {
+ return false;
+ }
+
+ if (channel.Capabilities.NeedsFragmentOutputSpecialization && !graphicsState.FragmentOutputTypes.AsSpan().SequenceEqual(GraphicsState.FragmentOutputTypes.AsSpan()))
+ {
+ return false;
+ }
+
+ if (graphicsState.DualSourceBlendEnable != GraphicsState.DualSourceBlendEnable)
+ {
+ return false;
+ }
+
+ return Matches(channel, ref poolState, checkTextures, isCompute: false);
+ }
+
+ /// <summary>
+ /// Checks if the recorded state matches the current GPU compute engine state.
+ /// </summary>
+ /// <param name="channel">GPU channel</param>
+ /// <param name="poolState">Texture pool state</param>
+ /// <param name="computeState">Compute state</param>
+ /// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
+ /// <returns>True if the state matches, false otherwise</returns>
+ public bool MatchesCompute(GpuChannel channel, ref GpuChannelPoolState poolState, GpuChannelComputeState computeState, bool checkTextures)
+ {
+ if (computeState.HasUnalignedStorageBuffer != ComputeState.HasUnalignedStorageBuffer)
+ {
+ return false;
+ }
+
+ return Matches(channel, ref poolState, checkTextures, isCompute: true);
+ }
+
+ /// <summary>
+ /// Fetch the constant buffers used for a texture to cache.
+ /// </summary>
+ /// <param name="channel">GPU channel</param>
+ /// <param name="isCompute">Indicates whenever the check is requested by the 3D or compute engine</param>
+ /// <param name="cachedTextureBufferIndex">The currently cached texture buffer index</param>
+ /// <param name="cachedSamplerBufferIndex">The currently cached sampler buffer index</param>
+ /// <param name="cachedTextureBuffer">The currently cached texture buffer data</param>
+ /// <param name="cachedSamplerBuffer">The currently cached sampler buffer data</param>
+ /// <param name="cachedStageIndex">The currently cached stage</param>
+ /// <param name="textureBufferIndex">The new texture buffer index</param>
+ /// <param name="samplerBufferIndex">The new sampler buffer index</param>
+ /// <param name="stageIndex">Stage index of the constant buffer</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void UpdateCachedBuffer(
+ GpuChannel channel,
+ bool isCompute,
+ scoped ref int cachedTextureBufferIndex,
+ scoped ref int cachedSamplerBufferIndex,
+ scoped ref ReadOnlySpan<int> cachedTextureBuffer,
+ scoped ref ReadOnlySpan<int> cachedSamplerBuffer,
+ scoped ref int cachedStageIndex,
+ int textureBufferIndex,
+ int samplerBufferIndex,
+ int stageIndex)
+ {
+ bool stageChange = stageIndex != cachedStageIndex;
+
+ if (stageChange || textureBufferIndex != cachedTextureBufferIndex)
+ {
+ ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex);
+
+ cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
+ cachedTextureBufferIndex = textureBufferIndex;
+
+ if (samplerBufferIndex == textureBufferIndex)
+ {
+ cachedSamplerBuffer = cachedTextureBuffer;
+ cachedSamplerBufferIndex = samplerBufferIndex;
+ }
+ }
+
+ if (stageChange || samplerBufferIndex != cachedSamplerBufferIndex)
+ {
+ ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex);
+
+ cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
+ cachedSamplerBufferIndex = samplerBufferIndex;
+ }
+
+ cachedStageIndex = stageIndex;
+ }
+
+ /// <summary>
+ /// Checks if the recorded state matches the current GPU state.
+ /// </summary>
+ /// <param name="channel">GPU channel</param>
+ /// <param name="poolState">Texture pool state</param>
+ /// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
+ /// <param name="isCompute">Indicates whenever the check is requested by the 3D or compute engine</param>
+ /// <returns>True if the state matches, false otherwise</returns>
+ private bool Matches(GpuChannel channel, ref GpuChannelPoolState poolState, bool checkTextures, bool isCompute)
+ {
+ int constantBufferUsePerStageMask = _constantBufferUsePerStage;
+
+ while (constantBufferUsePerStageMask != 0)
+ {
+ int index = BitOperations.TrailingZeroCount(constantBufferUsePerStageMask);
+
+ uint useMask = isCompute
+ ? channel.BufferManager.GetComputeUniformBufferUseMask()
+ : channel.BufferManager.GetGraphicsUniformBufferUseMask(index);
+
+ if (ConstantBufferUse[index] != useMask)
+ {
+ return false;
+ }
+
+ constantBufferUsePerStageMask &= ~(1 << index);
+ }
+
+ if (checkTextures)
+ {
+ TexturePool pool = channel.TextureManager.GetTexturePool(poolState.TexturePoolGpuVa, poolState.TexturePoolMaximumId);
+
+ int cachedTextureBufferIndex = -1;
+ int cachedSamplerBufferIndex = -1;
+ int cachedStageIndex = -1;
+ ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
+ ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
+
+ foreach (var kv in _allTextures)
+ {
+ TextureKey textureKey = kv.Key;
+
+ (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex);
+
+ UpdateCachedBuffer(channel,
+ isCompute,
+ ref cachedTextureBufferIndex,
+ ref cachedSamplerBufferIndex,
+ ref cachedTextureBuffer,
+ ref cachedSamplerBuffer,
+ ref cachedStageIndex,
+ textureBufferIndex,
+ samplerBufferIndex,
+ textureKey.StageIndex);
+
+ int packedId = TextureHandle.ReadPackedId(textureKey.Handle, cachedTextureBuffer, cachedSamplerBuffer);
+ int textureId = TextureHandle.UnpackTextureId(packedId);
+
+ if (pool.IsValidId(textureId))
+ {
+ ref readonly Image.TextureDescriptor descriptor = ref pool.GetDescriptorRef(textureId);
+
+ if (!MatchesTexture(kv.Value, descriptor))
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Checks if the recorded texture state matches the given texture descriptor.
+ /// </summary>
+ /// <param name="specializationState">Texture specialization state</param>
+ /// <param name="descriptor">Texture descriptor</param>
+ /// <returns>True if the state matches, false otherwise</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool MatchesTexture(Box<TextureSpecializationState> specializationState, in Image.TextureDescriptor descriptor)
+ {
+ if (specializationState != null)
+ {
+ if (specializationState.Value.QueriedFlags.HasFlag(QueriedTextureStateFlags.CoordNormalized) &&
+ specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized())
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Checks if the recorded texture state for a given texture binding matches a texture descriptor.
+ /// </summary>
+ /// <param name="stage">The shader stage</param>
+ /// <param name="index">The texture index</param>
+ /// <param name="descriptor">Texture descriptor</param>
+ /// <returns>True if the state matches, false otherwise</returns>
+ public bool MatchesTexture(ShaderStage stage, int index, in Image.TextureDescriptor descriptor)
+ {
+ Box<TextureSpecializationState> specializationState = _textureByBinding[(int)stage][index];
+
+ return MatchesTexture(specializationState, descriptor);
+ }
+
+ /// <summary>
+ /// Checks if the recorded texture state for a given image binding matches a texture descriptor.
+ /// </summary>
+ /// <param name="stage">The shader stage</param>
+ /// <param name="index">The texture index</param>
+ /// <param name="descriptor">Texture descriptor</param>
+ /// <returns>True if the state matches, false otherwise</returns>
+ public bool MatchesImage(ShaderStage stage, int index, in Image.TextureDescriptor descriptor)
+ {
+ Box<TextureSpecializationState> specializationState = _imageByBinding[(int)stage][index];
+
+ return MatchesTexture(specializationState, descriptor);
+ }
+
+ /// <summary>
+ /// Reads shader specialization state that has been serialized.
+ /// </summary>
+ /// <param name="dataReader">Data reader</param>
+ /// <returns>Shader specialization state</returns>
+ public static ShaderSpecializationState Read(ref BinarySerializer dataReader)
+ {
+ ShaderSpecializationState specState = new ShaderSpecializationState();
+
+ dataReader.Read(ref specState._queriedState);
+ dataReader.Read(ref specState._compute);
+
+ if (specState._compute)
+ {
+ dataReader.ReadWithMagicAndSize(ref specState.ComputeState, ComsMagic);
+ }
+ else
+ {
+ dataReader.ReadWithMagicAndSize(ref specState.GraphicsState, GfxsMagic);
+ }
+
+ dataReader.Read(ref specState._constantBufferUsePerStage);
+
+ int constantBufferUsePerStageMask = specState._constantBufferUsePerStage;
+
+ while (constantBufferUsePerStageMask != 0)
+ {
+ int index = BitOperations.TrailingZeroCount(constantBufferUsePerStageMask);
+ dataReader.Read(ref specState.ConstantBufferUse[index]);
+ constantBufferUsePerStageMask &= ~(1 << index);
+ }
+
+ bool hasPipelineState = false;
+
+ dataReader.Read(ref hasPipelineState);
+
+ if (hasPipelineState)
+ {
+ ProgramPipelineState pipelineState = default;
+ dataReader.ReadWithMagicAndSize(ref pipelineState, PgpsMagic);
+ specState.PipelineState = pipelineState;
+ }
+
+ if (specState._queriedState.HasFlag(QueriedStateFlags.TransformFeedback))
+ {
+ ushort tfCount = 0;
+ dataReader.Read(ref tfCount);
+ specState.TransformFeedbackDescriptors = new TransformFeedbackDescriptor[tfCount];
+
+ for (int index = 0; index < tfCount; index++)
+ {
+ dataReader.ReadWithMagicAndSize(ref specState.TransformFeedbackDescriptors[index], TfbdMagic);
+ }
+ }
+
+ ushort count = 0;
+ dataReader.Read(ref count);
+
+ for (int index = 0; index < count; index++)
+ {
+ TextureKey textureKey = default;
+ Box<TextureSpecializationState> textureState = new Box<TextureSpecializationState>();
+
+ dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic);
+ dataReader.ReadWithMagicAndSize(ref textureState.Value, TexsMagic);
+
+ specState._textureSpecialization[textureKey] = textureState;
+ }
+
+ return specState;
+ }
+
+ /// <summary>
+ /// Serializes the shader specialization state.
+ /// </summary>
+ /// <param name="dataWriter">Data writer</param>
+ public void Write(ref BinarySerializer dataWriter)
+ {
+ dataWriter.Write(ref _queriedState);
+ dataWriter.Write(ref _compute);
+
+ if (_compute)
+ {
+ dataWriter.WriteWithMagicAndSize(ref ComputeState, ComsMagic);
+ }
+ else
+ {
+ dataWriter.WriteWithMagicAndSize(ref GraphicsState, GfxsMagic);
+ }
+
+ dataWriter.Write(ref _constantBufferUsePerStage);
+
+ int constantBufferUsePerStageMask = _constantBufferUsePerStage;
+
+ while (constantBufferUsePerStageMask != 0)
+ {
+ int index = BitOperations.TrailingZeroCount(constantBufferUsePerStageMask);
+ dataWriter.Write(ref ConstantBufferUse[index]);
+ constantBufferUsePerStageMask &= ~(1 << index);
+ }
+
+ bool hasPipelineState = PipelineState.HasValue;
+
+ dataWriter.Write(ref hasPipelineState);
+
+ if (hasPipelineState)
+ {
+ ProgramPipelineState pipelineState = PipelineState.Value;
+ dataWriter.WriteWithMagicAndSize(ref pipelineState, PgpsMagic);
+ }
+
+ if (_queriedState.HasFlag(QueriedStateFlags.TransformFeedback))
+ {
+ ushort tfCount = (ushort)TransformFeedbackDescriptors.Length;
+ dataWriter.Write(ref tfCount);
+
+ for (int index = 0; index < TransformFeedbackDescriptors.Length; index++)
+ {
+ dataWriter.WriteWithMagicAndSize(ref TransformFeedbackDescriptors[index], TfbdMagic);
+ }
+ }
+
+ ushort count = (ushort)_textureSpecialization.Count;
+ dataWriter.Write(ref count);
+
+ foreach (var kv in _textureSpecialization)
+ {
+ var textureKey = kv.Key;
+ var textureState = kv.Value;
+
+ dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic);
+ dataWriter.WriteWithMagicAndSize(ref textureState.Value, TexsMagic);
+ }
+ }
+ }
+} \ No newline at end of file