/ src / Ryujinx.Graphics.Gpu / Shader / ShaderSpecializationState.cs
ShaderSpecializationState.cs
   1  using Ryujinx.Common.Memory;
   2  using Ryujinx.Graphics.GAL;
   3  using Ryujinx.Graphics.Gpu.Image;
   4  using Ryujinx.Graphics.Gpu.Memory;
   5  using Ryujinx.Graphics.Gpu.Shader.DiskCache;
   6  using Ryujinx.Graphics.Shader;
   7  using System;
   8  using System.Collections.Generic;
   9  using System.Linq;
  10  using System.Numerics;
  11  using System.Runtime.CompilerServices;
  12  using System.Runtime.InteropServices;
  13  
  14  namespace Ryujinx.Graphics.Gpu.Shader
  15  {
  16      class ShaderSpecializationState
  17      {
  18          private const uint ComsMagic = (byte)'C' | ((byte)'O' << 8) | ((byte)'M' << 16) | ((byte)'S' << 24);
  19          private const uint GfxsMagic = (byte)'G' | ((byte)'F' << 8) | ((byte)'X' << 16) | ((byte)'S' << 24);
  20          private const uint TfbdMagic = (byte)'T' | ((byte)'F' << 8) | ((byte)'B' << 16) | ((byte)'D' << 24);
  21          private const uint TexkMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'K' << 24);
  22          private const uint TexsMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'S' << 24);
  23          private const uint PgpsMagic = (byte)'P' | ((byte)'G' << 8) | ((byte)'P' << 16) | ((byte)'S' << 24);
  24  
  25          /// <summary>
  26          /// Flags indicating GPU state that is used by the shader.
  27          /// </summary>
  28          [Flags]
  29          private enum QueriedStateFlags
  30          {
  31              PrimitiveTopology = 1 << 1,
  32              TransformFeedback = 1 << 3,
  33              TextureArrayFromBuffer = 1 << 4,
  34              TextureArrayFromPool = 1 << 5,
  35          }
  36  
  37          private QueriedStateFlags _queriedState;
  38          private bool _compute;
  39          private byte _constantBufferUsePerStage;
  40  
  41          /// <summary>
  42          /// Compute engine state.
  43          /// </summary>
  44          public GpuChannelComputeState ComputeState;
  45  
  46          /// <summary>
  47          /// 3D engine state.
  48          /// </summary>
  49          public GpuChannelGraphicsState GraphicsState;
  50  
  51          /// <summary>
  52          /// Contant buffers bound at the time the shader was compiled, per stage.
  53          /// </summary>
  54          public Array5<uint> ConstantBufferUse;
  55  
  56          /// <summary>
  57          /// Pipeline state captured at the time of shader use.
  58          /// </summary>
  59          public ProgramPipelineState? PipelineState;
  60  
  61          /// <summary>
  62          /// Transform feedback buffers active at the time the shader was compiled.
  63          /// </summary>
  64          public TransformFeedbackDescriptor[] TransformFeedbackDescriptors;
  65  
  66          /// <summary>
  67          /// Flags indicating texture state that is used by the shader.
  68          /// </summary>
  69          [Flags]
  70          private enum QueriedTextureStateFlags
  71          {
  72              TextureFormat = 1 << 0,
  73              SamplerType = 1 << 1,
  74              CoordNormalized = 1 << 2,
  75          }
  76  
  77          /// <summary>
  78          /// Reference type wrapping a value.
  79          /// </summary>
  80          private class Box<T>
  81          {
  82              /// <summary>
  83              /// Wrapped value.
  84              /// </summary>
  85              public T Value;
  86          }
  87  
  88          /// <summary>
  89          /// State of a texture or image that is accessed by the shader.
  90          /// </summary>
  91          private struct TextureSpecializationState
  92          {
  93              // New fields should be added to the end of the struct to keep disk shader cache compatibility.
  94  
  95              /// <summary>
  96              /// Flags indicating which state of the texture the shader depends on.
  97              /// </summary>
  98              public QueriedTextureStateFlags QueriedFlags;
  99  
 100              /// <summary>
 101              /// Encoded texture format value.
 102              /// </summary>
 103              public uint Format;
 104  
 105              /// <summary>
 106              /// True if the texture format is sRGB, false otherwise.
 107              /// </summary>
 108              public bool FormatSrgb;
 109  
 110              /// <summary>
 111              /// Texture target.
 112              /// </summary>
 113              public TextureTarget TextureTarget;
 114  
 115              /// <summary>
 116              /// Indicates if the coordinates used to sample the texture are normalized or not (0.0..1.0 or 0..Width/Height).
 117              /// </summary>
 118              public bool CoordNormalized;
 119          }
 120  
 121          /// <summary>
 122          /// Texture binding information, used to identify each texture accessed by the shader.
 123          /// </summary>
 124          private readonly record struct TextureKey
 125          {
 126              // New fields should be added to the end of the struct to keep disk shader cache compatibility.
 127  
 128              /// <summary>
 129              /// Shader stage where the texture is used.
 130              /// </summary>
 131              public readonly int StageIndex;
 132  
 133              /// <summary>
 134              /// Texture handle offset in words on the texture buffer.
 135              /// </summary>
 136              public readonly int Handle;
 137  
 138              /// <summary>
 139              /// Constant buffer slot of the texture buffer (-1 to use the texture buffer index GPU register).
 140              /// </summary>
 141              public readonly int CbufSlot;
 142  
 143              /// <summary>
 144              /// Creates a new texture key.
 145              /// </summary>
 146              /// <param name="stageIndex">Shader stage where the texture is used</param>
 147              /// <param name="handle">Texture handle offset in words on the texture buffer</param>
 148              /// <param name="cbufSlot">Constant buffer slot of the texture buffer (-1 to use the texture buffer index GPU register)</param>
 149              public TextureKey(int stageIndex, int handle, int cbufSlot)
 150              {
 151                  StageIndex = stageIndex;
 152                  Handle = handle;
 153                  CbufSlot = cbufSlot;
 154              }
 155          }
 156  
 157          private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
 158          private readonly Dictionary<TextureKey, int> _textureArrayFromBufferSpecialization;
 159          private readonly Dictionary<bool, int> _textureArrayFromPoolSpecialization;
 160          private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures;
 161          private Box<TextureSpecializationState>[][] _textureByBinding;
 162          private Box<TextureSpecializationState>[][] _imageByBinding;
 163  
 164          /// <summary>
 165          /// Creates a new instance of the shader specialization state.
 166          /// </summary>
 167          private ShaderSpecializationState()
 168          {
 169              _textureSpecialization = new Dictionary<TextureKey, Box<TextureSpecializationState>>();
 170              _textureArrayFromBufferSpecialization = new Dictionary<TextureKey, int>();
 171              _textureArrayFromPoolSpecialization = new Dictionary<bool, int>();
 172          }
 173  
 174          /// <summary>
 175          /// Creates a new instance of the shader specialization state.
 176          /// </summary>
 177          /// <param name="state">Current compute engine state</param>
 178          public ShaderSpecializationState(ref GpuChannelComputeState state) : this()
 179          {
 180              ComputeState = state;
 181              _compute = true;
 182          }
 183  
 184          /// <summary>
 185          /// Creates a new instance of the shader specialization state.
 186          /// </summary>
 187          /// <param name="state">Current 3D engine state</param>
 188          /// <param name="descriptors">Optional transform feedback buffers in use, if any</param>
 189          private ShaderSpecializationState(ref GpuChannelGraphicsState state, TransformFeedbackDescriptor[] descriptors) : this()
 190          {
 191              GraphicsState = state;
 192              _compute = false;
 193  
 194              if (descriptors != null)
 195              {
 196                  TransformFeedbackDescriptors = descriptors;
 197                  _queriedState |= QueriedStateFlags.TransformFeedback;
 198              }
 199          }
 200  
 201          /// <summary>
 202          /// Prepare the shader specialization state for quick binding lookups.
 203          /// </summary>
 204          /// <param name="stages">The shader stages</param>
 205          public void Prepare(CachedShaderStage[] stages)
 206          {
 207              _allTextures = _textureSpecialization.ToArray();
 208  
 209              _textureByBinding = new Box<TextureSpecializationState>[stages.Length][];
 210              _imageByBinding = new Box<TextureSpecializationState>[stages.Length][];
 211  
 212              for (int i = 0; i < stages.Length; i++)
 213              {
 214                  CachedShaderStage stage = stages[i];
 215                  if (stage?.Info != null)
 216                  {
 217                      var textures = stage.Info.Textures;
 218                      var images = stage.Info.Images;
 219  
 220                      var texBindings = new Box<TextureSpecializationState>[textures.Count];
 221                      var imageBindings = new Box<TextureSpecializationState>[images.Count];
 222  
 223                      int stageIndex = Math.Max(i - 1, 0); // Don't count VertexA for looking up spec state. No-Op for compute.
 224  
 225                      for (int j = 0; j < textures.Count; j++)
 226                      {
 227                          var texture = textures[j];
 228                          texBindings[j] = GetTextureSpecState(stageIndex, texture.HandleIndex, texture.CbufSlot);
 229                      }
 230  
 231                      for (int j = 0; j < images.Count; j++)
 232                      {
 233                          var image = images[j];
 234                          imageBindings[j] = GetTextureSpecState(stageIndex, image.HandleIndex, image.CbufSlot);
 235                      }
 236  
 237                      _textureByBinding[i] = texBindings;
 238                      _imageByBinding[i] = imageBindings;
 239                  }
 240              }
 241          }
 242  
 243          /// <summary>
 244          /// Creates a new instance of the shader specialization state.
 245          /// </summary>
 246          /// <param name="state">Current 3D engine state</param>
 247          /// <param name="pipelineState">Current program pipeline state</param>
 248          /// <param name="descriptors">Optional transform feedback buffers in use, if any</param>
 249          public ShaderSpecializationState(
 250              ref GpuChannelGraphicsState state,
 251              ref ProgramPipelineState pipelineState,
 252              TransformFeedbackDescriptor[] descriptors) : this(ref state, descriptors)
 253          {
 254              PipelineState = pipelineState;
 255          }
 256  
 257          /// <summary>
 258          /// Creates a new instance of the shader specialization state.
 259          /// </summary>
 260          /// <param name="state">Current 3D engine state</param>
 261          /// <param name="pipelineState">Current program pipeline state</param>
 262          /// <param name="descriptors">Optional transform feedback buffers in use, if any</param>
 263          public ShaderSpecializationState(
 264              ref GpuChannelGraphicsState state,
 265              ProgramPipelineState? pipelineState,
 266              TransformFeedbackDescriptor[] descriptors) : this(ref state, descriptors)
 267          {
 268              PipelineState = pipelineState;
 269          }
 270  
 271          /// <summary>
 272          /// Indicates that the shader accesses the primitive topology state.
 273          /// </summary>
 274          public void RecordPrimitiveTopology()
 275          {
 276              _queriedState |= QueriedStateFlags.PrimitiveTopology;
 277          }
 278  
 279          /// <summary>
 280          /// Indicates that the shader accesses the constant buffer use state.
 281          /// </summary>
 282          /// <param name="stageIndex">Shader stage index</param>
 283          /// <param name="useMask">Mask indicating the constant buffers bound at the time of the shader compilation</param>
 284          public void RecordConstantBufferUse(int stageIndex, uint useMask)
 285          {
 286              ConstantBufferUse[stageIndex] = useMask;
 287              _constantBufferUsePerStage |= (byte)(1 << stageIndex);
 288          }
 289  
 290          /// <summary>
 291          /// Indicates that a given texture is accessed by the shader.
 292          /// </summary>
 293          /// <param name="stageIndex">Shader stage where the texture is used</param>
 294          /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
 295          /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
 296          /// <param name="descriptor">Descriptor of the texture</param>
 297          public void RegisterTexture(int stageIndex, int handle, int cbufSlot, Image.TextureDescriptor descriptor)
 298          {
 299              Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
 300              state.Value.Format = descriptor.UnpackFormat();
 301              state.Value.FormatSrgb = descriptor.UnpackSrgb();
 302              state.Value.TextureTarget = descriptor.UnpackTextureTarget();
 303              state.Value.CoordNormalized = descriptor.UnpackTextureCoordNormalized();
 304          }
 305  
 306          /// <summary>
 307          /// Indicates that a given texture is accessed by the shader.
 308          /// </summary>
 309          /// <param name="stageIndex">Shader stage where the texture is used</param>
 310          /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
 311          /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
 312          /// <param name="format">Maxwell texture format value</param>
 313          /// <param name="formatSrgb">Whenever the texture format is a sRGB format</param>
 314          /// <param name="target">Texture target type</param>
 315          /// <param name="coordNormalized">Whenever the texture coordinates used on the shader are considered normalized</param>
 316          public void RegisterTexture(
 317              int stageIndex,
 318              int handle,
 319              int cbufSlot,
 320              uint format,
 321              bool formatSrgb,
 322              TextureTarget target,
 323              bool coordNormalized)
 324          {
 325              Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
 326              state.Value.Format = format;
 327              state.Value.FormatSrgb = formatSrgb;
 328              state.Value.TextureTarget = target;
 329              state.Value.CoordNormalized = coordNormalized;
 330          }
 331  
 332          /// <summary>
 333          /// Registers the length of a texture array calculated from a constant buffer size.
 334          /// </summary>
 335          /// <param name="stageIndex">Shader stage where the texture is used</param>
 336          /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
 337          /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
 338          /// <param name="length">Number of elements in the texture array</param>
 339          public void RegisterTextureArrayLengthFromBuffer(int stageIndex, int handle, int cbufSlot, int length)
 340          {
 341              _textureArrayFromBufferSpecialization[new TextureKey(stageIndex, handle, cbufSlot)] = length;
 342              _queriedState |= QueriedStateFlags.TextureArrayFromBuffer;
 343          }
 344  
 345          /// <summary>
 346          /// Registers the length of a texture array calculated from a texture or sampler pool capacity.
 347          /// </summary>
 348          /// <param name="isSampler">True for sampler pool, false for texture pool</param>
 349          /// <param name="length">Number of elements in the texture array</param>
 350          public void RegisterTextureArrayLengthFromPool(bool isSampler, int length)
 351          {
 352              _textureArrayFromPoolSpecialization[isSampler] = length;
 353              _queriedState |= QueriedStateFlags.TextureArrayFromPool;
 354          }
 355  
 356          /// <summary>
 357          /// Indicates that the format of a given texture was used during the shader translation process.
 358          /// </summary>
 359          /// <param name="stageIndex">Shader stage where the texture is used</param>
 360          /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
 361          /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
 362          public void RecordTextureFormat(int stageIndex, int handle, int cbufSlot)
 363          {
 364              Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
 365              state.Value.QueriedFlags |= QueriedTextureStateFlags.TextureFormat;
 366          }
 367  
 368          /// <summary>
 369          /// Indicates that the target of a given texture was used during the shader translation process.
 370          /// </summary>
 371          /// <param name="stageIndex">Shader stage where the texture is used</param>
 372          /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
 373          /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
 374          public void RecordTextureSamplerType(int stageIndex, int handle, int cbufSlot)
 375          {
 376              Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
 377              state.Value.QueriedFlags |= QueriedTextureStateFlags.SamplerType;
 378          }
 379  
 380          /// <summary>
 381          /// Indicates that the coordinate normalization state of a given texture was used during the shader translation process.
 382          /// </summary>
 383          /// <param name="stageIndex">Shader stage where the texture is used</param>
 384          /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
 385          /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
 386          public void RecordTextureCoordNormalized(int stageIndex, int handle, int cbufSlot)
 387          {
 388              Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
 389              state.Value.QueriedFlags |= QueriedTextureStateFlags.CoordNormalized;
 390          }
 391  
 392          /// <summary>
 393          /// Checks if primitive topology was queried by the shader.
 394          /// </summary>
 395          /// <returns>True if queried, false otherwise</returns>
 396          public bool IsPrimitiveTopologyQueried()
 397          {
 398              return _queriedState.HasFlag(QueriedStateFlags.PrimitiveTopology);
 399          }
 400  
 401          /// <summary>
 402          /// Checks if a given texture was registered on this specialization state.
 403          /// </summary>
 404          /// <param name="stageIndex">Shader stage where the texture is used</param>
 405          /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
 406          /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
 407          public bool TextureRegistered(int stageIndex, int handle, int cbufSlot)
 408          {
 409              return GetTextureSpecState(stageIndex, handle, cbufSlot) != null;
 410          }
 411  
 412          /// <summary>
 413          /// Checks if a given texture array (from constant buffer) was registered on this specialization state.
 414          /// </summary>
 415          /// <param name="stageIndex">Shader stage where the texture is used</param>
 416          /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
 417          /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
 418          /// <returns>True if the length for the given buffer and stage exists, false otherwise</returns>
 419          public bool TextureArrayFromBufferRegistered(int stageIndex, int handle, int cbufSlot)
 420          {
 421              return _textureArrayFromBufferSpecialization.ContainsKey(new TextureKey(stageIndex, handle, cbufSlot));
 422          }
 423  
 424          /// <summary>
 425          /// Checks if a given texture array (from a sampler pool or texture pool) was registered on this specialization state.
 426          /// </summary>
 427          /// <param name="isSampler">True for sampler pool, false for texture pool</param>
 428          /// <returns>True if the length for the given pool, false otherwise</returns>
 429          public bool TextureArrayFromPoolRegistered(bool isSampler)
 430          {
 431              return _textureArrayFromPoolSpecialization.ContainsKey(isSampler);
 432          }
 433  
 434          /// <summary>
 435          /// Gets the recorded format of a given texture.
 436          /// </summary>
 437          /// <param name="stageIndex">Shader stage where the texture is used</param>
 438          /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
 439          /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
 440          /// <returns>Format and sRGB tuple</returns>
 441          public (uint, bool) GetFormat(int stageIndex, int handle, int cbufSlot)
 442          {
 443              TextureSpecializationState state = GetTextureSpecState(stageIndex, handle, cbufSlot).Value;
 444              return (state.Format, state.FormatSrgb);
 445          }
 446  
 447          /// <summary>
 448          /// Gets the recorded target of a given texture.
 449          /// </summary>
 450          /// <param name="stageIndex">Shader stage where the texture is used</param>
 451          /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
 452          /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
 453          /// <returns>Texture target</returns>
 454          public TextureTarget GetTextureTarget(int stageIndex, int handle, int cbufSlot)
 455          {
 456              return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.TextureTarget;
 457          }
 458  
 459          /// <summary>
 460          /// Gets the recorded coordinate normalization state of a given texture.
 461          /// </summary>
 462          /// <param name="stageIndex">Shader stage where the texture is used</param>
 463          /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
 464          /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
 465          /// <returns>True if coordinates are normalized, false otherwise</returns>
 466          public bool GetCoordNormalized(int stageIndex, int handle, int cbufSlot)
 467          {
 468              return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized;
 469          }
 470  
 471          /// <summary>
 472          /// Gets the recorded length of a given texture array (from constant buffer).
 473          /// </summary>
 474          /// <param name="stageIndex">Shader stage where the texture is used</param>
 475          /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
 476          /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
 477          /// <returns>Texture array length</returns>
 478          public int GetTextureArrayFromBufferLength(int stageIndex, int handle, int cbufSlot)
 479          {
 480              return _textureArrayFromBufferSpecialization[new TextureKey(stageIndex, handle, cbufSlot)];
 481          }
 482  
 483          /// <summary>
 484          /// Gets the recorded length of a given texture array (from a sampler or texture pool).
 485          /// </summary>
 486          /// <param name="isSampler">True to get the sampler pool length, false to get the texture pool length</param>
 487          /// <returns>Texture array length</returns>
 488          public int GetTextureArrayFromPoolLength(bool isSampler)
 489          {
 490              return _textureArrayFromPoolSpecialization[isSampler];
 491          }
 492  
 493          /// <summary>
 494          /// Gets texture specialization state for a given texture, or create a new one if not present.
 495          /// </summary>
 496          /// <param name="stageIndex">Shader stage where the texture is used</param>
 497          /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
 498          /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
 499          /// <returns>Texture specialization state</returns>
 500          private Box<TextureSpecializationState> GetOrCreateTextureSpecState(int stageIndex, int handle, int cbufSlot)
 501          {
 502              TextureKey key = new(stageIndex, handle, cbufSlot);
 503  
 504              if (!_textureSpecialization.TryGetValue(key, out Box<TextureSpecializationState> state))
 505              {
 506                  _textureSpecialization.Add(key, state = new Box<TextureSpecializationState>());
 507              }
 508  
 509              return state;
 510          }
 511  
 512          /// <summary>
 513          /// Gets texture specialization state for a given texture.
 514          /// </summary>
 515          /// <param name="stageIndex">Shader stage where the texture is used</param>
 516          /// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
 517          /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
 518          /// <returns>Texture specialization state</returns>
 519          private Box<TextureSpecializationState> GetTextureSpecState(int stageIndex, int handle, int cbufSlot)
 520          {
 521              TextureKey key = new(stageIndex, handle, cbufSlot);
 522  
 523              if (_textureSpecialization.TryGetValue(key, out Box<TextureSpecializationState> state))
 524              {
 525                  return state;
 526              }
 527  
 528              return null;
 529          }
 530  
 531          /// <summary>
 532          /// Checks if the recorded state matches the current GPU 3D engine state.
 533          /// </summary>
 534          /// <param name="channel">GPU channel</param>
 535          /// <param name="poolState">Texture pool state</param>
 536          /// <param name="graphicsState">Graphics state</param>
 537          /// <param name="vertexAsCompute">Indicates that the vertex shader has been converted into a compute shader</param>
 538          /// <param name="usesDrawParameters">Indicates whether the vertex shader accesses draw parameters</param>
 539          /// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
 540          /// <returns>True if the state matches, false otherwise</returns>
 541          public bool MatchesGraphics(
 542              GpuChannel channel,
 543              ref GpuChannelPoolState poolState,
 544              ref GpuChannelGraphicsState graphicsState,
 545              bool vertexAsCompute,
 546              bool usesDrawParameters,
 547              bool checkTextures)
 548          {
 549              if (graphicsState.ViewportTransformDisable != GraphicsState.ViewportTransformDisable)
 550              {
 551                  return false;
 552              }
 553  
 554              bool thisA2cDitherEnable = GraphicsState.AlphaToCoverageEnable && GraphicsState.AlphaToCoverageDitherEnable;
 555              bool otherA2cDitherEnable = graphicsState.AlphaToCoverageEnable && graphicsState.AlphaToCoverageDitherEnable;
 556  
 557              if (otherA2cDitherEnable != thisA2cDitherEnable)
 558              {
 559                  return false;
 560              }
 561  
 562              if (graphicsState.DepthMode != GraphicsState.DepthMode)
 563              {
 564                  return false;
 565              }
 566  
 567              if (graphicsState.AlphaTestEnable != GraphicsState.AlphaTestEnable)
 568              {
 569                  return false;
 570              }
 571  
 572              if (graphicsState.AlphaTestEnable &&
 573                  (graphicsState.AlphaTestCompare != GraphicsState.AlphaTestCompare ||
 574                  graphicsState.AlphaTestReference != GraphicsState.AlphaTestReference))
 575              {
 576                  return false;
 577              }
 578  
 579              if (ShaderCache.MayConvertVtgToCompute(ref channel.Capabilities) && !vertexAsCompute)
 580              {
 581                  for (int index = 0; index < graphicsState.AttributeTypes.Length; index++)
 582                  {
 583                      AttributeType lType = FilterAttributeType(channel, graphicsState.AttributeTypes[index]);
 584                      AttributeType rType = FilterAttributeType(channel, GraphicsState.AttributeTypes[index]);
 585  
 586                      if (lType != rType)
 587                      {
 588                          return false;
 589                      }
 590                  }
 591              }
 592              else
 593              {
 594                  if (!graphicsState.AttributeTypes.AsSpan().SequenceEqual(GraphicsState.AttributeTypes.AsSpan()))
 595                  {
 596                      return false;
 597                  }
 598              }
 599  
 600              if (usesDrawParameters && graphicsState.HasConstantBufferDrawParameters != GraphicsState.HasConstantBufferDrawParameters)
 601              {
 602                  return false;
 603              }
 604  
 605              if (graphicsState.HasUnalignedStorageBuffer != GraphicsState.HasUnalignedStorageBuffer)
 606              {
 607                  return false;
 608              }
 609  
 610              if (channel.Capabilities.NeedsFragmentOutputSpecialization && !graphicsState.FragmentOutputTypes.AsSpan().SequenceEqual(GraphicsState.FragmentOutputTypes.AsSpan()))
 611              {
 612                  return false;
 613              }
 614  
 615              if (graphicsState.DualSourceBlendEnable != GraphicsState.DualSourceBlendEnable)
 616              {
 617                  return false;
 618              }
 619  
 620              if (graphicsState.YNegateEnabled != GraphicsState.YNegateEnabled)
 621              {
 622                  return false;
 623              }
 624  
 625              return Matches(channel, ref poolState, checkTextures, isCompute: false);
 626          }
 627  
 628          /// <summary>
 629          /// Converts special vertex attribute groups to their generic equivalents, for comparison purposes.
 630          /// </summary>
 631          /// <param name="channel">GPU channel</param>
 632          /// <param name="type">Vertex attribute type</param>
 633          /// <returns>Filtered attribute</returns>
 634          private static AttributeType FilterAttributeType(GpuChannel channel, AttributeType type)
 635          {
 636              type &= ~(AttributeType.Packed | AttributeType.PackedRgb10A2Signed);
 637  
 638              if (channel.Capabilities.SupportsScaledVertexFormats &&
 639                  (type == AttributeType.Sscaled || type == AttributeType.Uscaled))
 640              {
 641                  type = AttributeType.Float;
 642              }
 643  
 644              return type;
 645          }
 646  
 647          /// <summary>
 648          /// Checks if the recorded state matches the current GPU compute engine state.
 649          /// </summary>
 650          /// <param name="channel">GPU channel</param>
 651          /// <param name="poolState">Texture pool state</param>
 652          /// <param name="computeState">Compute state</param>
 653          /// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
 654          /// <returns>True if the state matches, false otherwise</returns>
 655          public bool MatchesCompute(GpuChannel channel, ref GpuChannelPoolState poolState, GpuChannelComputeState computeState, bool checkTextures)
 656          {
 657              if (computeState.HasUnalignedStorageBuffer != ComputeState.HasUnalignedStorageBuffer)
 658              {
 659                  return false;
 660              }
 661  
 662              return Matches(channel, ref poolState, checkTextures, isCompute: true);
 663          }
 664  
 665          /// <summary>
 666          /// Fetch the constant buffers used for a texture to cache.
 667          /// </summary>
 668          /// <param name="channel">GPU channel</param>
 669          /// <param name="isCompute">Indicates whenever the check is requested by the 3D or compute engine</param>
 670          /// <param name="cachedTextureBufferIndex">The currently cached texture buffer index</param>
 671          /// <param name="cachedSamplerBufferIndex">The currently cached sampler buffer index</param>
 672          /// <param name="cachedTextureBuffer">The currently cached texture buffer data</param>
 673          /// <param name="cachedSamplerBuffer">The currently cached sampler buffer data</param>
 674          /// <param name="cachedStageIndex">The currently cached stage</param>
 675          /// <param name="textureBufferIndex">The new texture buffer index</param>
 676          /// <param name="samplerBufferIndex">The new sampler buffer index</param>
 677          /// <param name="stageIndex">Stage index of the constant buffer</param>
 678          [MethodImpl(MethodImplOptions.AggressiveInlining)]
 679          private static void UpdateCachedBuffer(
 680              GpuChannel channel,
 681              bool isCompute,
 682              scoped ref int cachedTextureBufferIndex,
 683              scoped ref int cachedSamplerBufferIndex,
 684              scoped ref ReadOnlySpan<int> cachedTextureBuffer,
 685              scoped ref ReadOnlySpan<int> cachedSamplerBuffer,
 686              scoped ref int cachedStageIndex,
 687              int textureBufferIndex,
 688              int samplerBufferIndex,
 689              int stageIndex)
 690          {
 691              bool stageChange = stageIndex != cachedStageIndex;
 692  
 693              if (stageChange || textureBufferIndex != cachedTextureBufferIndex)
 694              {
 695                  ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex);
 696  
 697                  cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Range));
 698                  cachedTextureBufferIndex = textureBufferIndex;
 699  
 700                  if (samplerBufferIndex == textureBufferIndex)
 701                  {
 702                      cachedSamplerBuffer = cachedTextureBuffer;
 703                      cachedSamplerBufferIndex = samplerBufferIndex;
 704                  }
 705              }
 706  
 707              if (stageChange || samplerBufferIndex != cachedSamplerBufferIndex)
 708              {
 709                  ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex);
 710  
 711                  cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(channel.MemoryManager.Physical.GetSpan(bounds.Range));
 712                  cachedSamplerBufferIndex = samplerBufferIndex;
 713              }
 714  
 715              cachedStageIndex = stageIndex;
 716          }
 717  
 718          /// <summary>
 719          /// Checks if the recorded state matches the current GPU state.
 720          /// </summary>
 721          /// <param name="channel">GPU channel</param>
 722          /// <param name="poolState">Texture pool state</param>
 723          /// <param name="checkTextures">Indicates whether texture descriptors should be checked</param>
 724          /// <param name="isCompute">Indicates whenever the check is requested by the 3D or compute engine</param>
 725          /// <returns>True if the state matches, false otherwise</returns>
 726          private bool Matches(GpuChannel channel, ref GpuChannelPoolState poolState, bool checkTextures, bool isCompute)
 727          {
 728              int constantBufferUsePerStageMask = _constantBufferUsePerStage;
 729  
 730              while (constantBufferUsePerStageMask != 0)
 731              {
 732                  int index = BitOperations.TrailingZeroCount(constantBufferUsePerStageMask);
 733  
 734                  uint useMask = isCompute
 735                      ? channel.BufferManager.GetComputeUniformBufferUseMask()
 736                      : channel.BufferManager.GetGraphicsUniformBufferUseMask(index);
 737  
 738                  if (ConstantBufferUse[index] != useMask)
 739                  {
 740                      return false;
 741                  }
 742  
 743                  constantBufferUsePerStageMask &= ~(1 << index);
 744              }
 745  
 746              if (checkTextures && _allTextures.Length > 0)
 747              {
 748                  TexturePool pool = channel.TextureManager.GetTexturePool(poolState.TexturePoolGpuVa, poolState.TexturePoolMaximumId);
 749  
 750                  int cachedTextureBufferIndex = -1;
 751                  int cachedSamplerBufferIndex = -1;
 752                  int cachedStageIndex = -1;
 753                  ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
 754                  ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
 755  
 756                  foreach (var kv in _allTextures)
 757                  {
 758                      TextureKey textureKey = kv.Key;
 759  
 760                      (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex);
 761  
 762                      UpdateCachedBuffer(channel,
 763                          isCompute,
 764                          ref cachedTextureBufferIndex,
 765                          ref cachedSamplerBufferIndex,
 766                          ref cachedTextureBuffer,
 767                          ref cachedSamplerBuffer,
 768                          ref cachedStageIndex,
 769                          textureBufferIndex,
 770                          samplerBufferIndex,
 771                          textureKey.StageIndex);
 772  
 773                      int packedId = TextureHandle.ReadPackedId(textureKey.Handle, cachedTextureBuffer, cachedSamplerBuffer);
 774                      int textureId = TextureHandle.UnpackTextureId(packedId);
 775  
 776                      if (pool.IsValidId(textureId))
 777                      {
 778                          ref readonly Image.TextureDescriptor descriptor = ref pool.GetDescriptorRef(textureId);
 779  
 780                          if (!MatchesTexture(kv.Value, descriptor))
 781                          {
 782                              return false;
 783                          }
 784                      }
 785                  }
 786              }
 787  
 788              return true;
 789          }
 790  
 791          /// <summary>
 792          /// Checks if the recorded texture state matches the given texture descriptor.
 793          /// </summary>
 794          /// <param name="specializationState">Texture specialization state</param>
 795          /// <param name="descriptor">Texture descriptor</param>
 796          /// <returns>True if the state matches, false otherwise</returns>
 797          [MethodImpl(MethodImplOptions.AggressiveInlining)]
 798          private static bool MatchesTexture(Box<TextureSpecializationState> specializationState, in Image.TextureDescriptor descriptor)
 799          {
 800              if (specializationState != null)
 801              {
 802                  if (specializationState.Value.QueriedFlags.HasFlag(QueriedTextureStateFlags.CoordNormalized) &&
 803                      specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized())
 804                  {
 805                      return false;
 806                  }
 807              }
 808  
 809              return true;
 810          }
 811  
 812          /// <summary>
 813          /// Checks if the recorded texture state for a given texture binding matches a texture descriptor.
 814          /// </summary>
 815          /// <param name="stage">The shader stage</param>
 816          /// <param name="index">The texture index</param>
 817          /// <param name="descriptor">Texture descriptor</param>
 818          /// <returns>True if the state matches, false otherwise</returns>
 819          public bool MatchesTexture(ShaderStage stage, int index, in Image.TextureDescriptor descriptor)
 820          {
 821              Box<TextureSpecializationState> specializationState = _textureByBinding[(int)stage][index];
 822  
 823              return MatchesTexture(specializationState, descriptor);
 824          }
 825  
 826          /// <summary>
 827          /// Checks if the recorded texture state for a given image binding matches a texture descriptor.
 828          /// </summary>
 829          /// <param name="stage">The shader stage</param>
 830          /// <param name="index">The texture index</param>
 831          /// <param name="descriptor">Texture descriptor</param>
 832          /// <returns>True if the state matches, false otherwise</returns>
 833          public bool MatchesImage(ShaderStage stage, int index, in Image.TextureDescriptor descriptor)
 834          {
 835              Box<TextureSpecializationState> specializationState = _imageByBinding[(int)stage][index];
 836  
 837              return MatchesTexture(specializationState, descriptor);
 838          }
 839  
 840          /// <summary>
 841          /// Populates pipeline state that doesn't exist in older caches with default values
 842          /// based on specialization state.
 843          /// </summary>
 844          /// <param name="pipelineState">Pipeline state to prepare</param>
 845          private void PreparePipelineState(ref ProgramPipelineState pipelineState)
 846          {
 847              if (!_compute)
 848              {
 849                  pipelineState.DepthMode = GraphicsState.DepthMode ? DepthMode.MinusOneToOne : DepthMode.ZeroToOne;
 850              }
 851          }
 852  
 853          /// <summary>
 854          /// Reads shader specialization state that has been serialized.
 855          /// </summary>
 856          /// <param name="dataReader">Data reader</param>
 857          /// <returns>Shader specialization state</returns>
 858          public static ShaderSpecializationState Read(ref BinarySerializer dataReader)
 859          {
 860              ShaderSpecializationState specState = new();
 861  
 862              dataReader.Read(ref specState._queriedState);
 863              dataReader.Read(ref specState._compute);
 864  
 865              if (specState._compute)
 866              {
 867                  dataReader.ReadWithMagicAndSize(ref specState.ComputeState, ComsMagic);
 868              }
 869              else
 870              {
 871                  dataReader.ReadWithMagicAndSize(ref specState.GraphicsState, GfxsMagic);
 872              }
 873  
 874              dataReader.Read(ref specState._constantBufferUsePerStage);
 875  
 876              int constantBufferUsePerStageMask = specState._constantBufferUsePerStage;
 877  
 878              while (constantBufferUsePerStageMask != 0)
 879              {
 880                  int index = BitOperations.TrailingZeroCount(constantBufferUsePerStageMask);
 881                  dataReader.Read(ref specState.ConstantBufferUse[index]);
 882                  constantBufferUsePerStageMask &= ~(1 << index);
 883              }
 884  
 885              bool hasPipelineState = false;
 886  
 887              dataReader.Read(ref hasPipelineState);
 888  
 889              if (hasPipelineState)
 890              {
 891                  ProgramPipelineState pipelineState = default;
 892                  dataReader.ReadWithMagicAndSize(ref pipelineState, PgpsMagic);
 893  
 894                  specState.PreparePipelineState(ref pipelineState);
 895                  specState.PipelineState = pipelineState;
 896              }
 897  
 898              if (specState._queriedState.HasFlag(QueriedStateFlags.TransformFeedback))
 899              {
 900                  ushort tfCount = 0;
 901                  dataReader.Read(ref tfCount);
 902                  specState.TransformFeedbackDescriptors = new TransformFeedbackDescriptor[tfCount];
 903  
 904                  for (int index = 0; index < tfCount; index++)
 905                  {
 906                      dataReader.ReadWithMagicAndSize(ref specState.TransformFeedbackDescriptors[index], TfbdMagic);
 907                  }
 908              }
 909  
 910              ushort count = 0;
 911              dataReader.Read(ref count);
 912  
 913              for (int index = 0; index < count; index++)
 914              {
 915                  TextureKey textureKey = default;
 916                  Box<TextureSpecializationState> textureState = new();
 917  
 918                  dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic);
 919                  dataReader.ReadWithMagicAndSize(ref textureState.Value, TexsMagic);
 920  
 921                  specState._textureSpecialization[textureKey] = textureState;
 922              }
 923  
 924              if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer))
 925              {
 926                  dataReader.Read(ref count);
 927  
 928                  for (int index = 0; index < count; index++)
 929                  {
 930                      TextureKey textureKey = default;
 931                      int length = 0;
 932  
 933                      dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic);
 934                      dataReader.Read(ref length);
 935  
 936                      specState._textureArrayFromBufferSpecialization[textureKey] = length;
 937                  }
 938              }
 939  
 940              if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromPool))
 941              {
 942                  dataReader.Read(ref count);
 943  
 944                  for (int index = 0; index < count; index++)
 945                  {
 946                      bool textureKey = default;
 947                      int length = 0;
 948  
 949                      dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic);
 950                      dataReader.Read(ref length);
 951  
 952                      specState._textureArrayFromPoolSpecialization[textureKey] = length;
 953                  }
 954              }
 955  
 956              return specState;
 957          }
 958  
 959          /// <summary>
 960          /// Serializes the shader specialization state.
 961          /// </summary>
 962          /// <param name="dataWriter">Data writer</param>
 963          public void Write(ref BinarySerializer dataWriter)
 964          {
 965              dataWriter.Write(ref _queriedState);
 966              dataWriter.Write(ref _compute);
 967  
 968              if (_compute)
 969              {
 970                  dataWriter.WriteWithMagicAndSize(ref ComputeState, ComsMagic);
 971              }
 972              else
 973              {
 974                  dataWriter.WriteWithMagicAndSize(ref GraphicsState, GfxsMagic);
 975              }
 976  
 977              dataWriter.Write(ref _constantBufferUsePerStage);
 978  
 979              int constantBufferUsePerStageMask = _constantBufferUsePerStage;
 980  
 981              while (constantBufferUsePerStageMask != 0)
 982              {
 983                  int index = BitOperations.TrailingZeroCount(constantBufferUsePerStageMask);
 984                  dataWriter.Write(ref ConstantBufferUse[index]);
 985                  constantBufferUsePerStageMask &= ~(1 << index);
 986              }
 987  
 988              bool hasPipelineState = PipelineState.HasValue;
 989  
 990              dataWriter.Write(ref hasPipelineState);
 991  
 992              if (hasPipelineState)
 993              {
 994                  ProgramPipelineState pipelineState = PipelineState.Value;
 995                  dataWriter.WriteWithMagicAndSize(ref pipelineState, PgpsMagic);
 996              }
 997  
 998              if (_queriedState.HasFlag(QueriedStateFlags.TransformFeedback))
 999              {
1000                  ushort tfCount = (ushort)TransformFeedbackDescriptors.Length;
1001                  dataWriter.Write(ref tfCount);
1002  
1003                  for (int index = 0; index < TransformFeedbackDescriptors.Length; index++)
1004                  {
1005                      dataWriter.WriteWithMagicAndSize(ref TransformFeedbackDescriptors[index], TfbdMagic);
1006                  }
1007              }
1008  
1009              ushort count = (ushort)_textureSpecialization.Count;
1010              dataWriter.Write(ref count);
1011  
1012              foreach (var kv in _textureSpecialization)
1013              {
1014                  var textureKey = kv.Key;
1015                  var textureState = kv.Value;
1016  
1017                  dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic);
1018                  dataWriter.WriteWithMagicAndSize(ref textureState.Value, TexsMagic);
1019              }
1020  
1021              if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer))
1022              {
1023                  count = (ushort)_textureArrayFromBufferSpecialization.Count;
1024                  dataWriter.Write(ref count);
1025  
1026                  foreach (var kv in _textureArrayFromBufferSpecialization)
1027                  {
1028                      var textureKey = kv.Key;
1029                      var length = kv.Value;
1030  
1031                      dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic);
1032                      dataWriter.Write(ref length);
1033                  }
1034              }
1035  
1036              if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromPool))
1037              {
1038                  count = (ushort)_textureArrayFromPoolSpecialization.Count;
1039                  dataWriter.Write(ref count);
1040  
1041                  foreach (var kv in _textureArrayFromPoolSpecialization)
1042                  {
1043                      var textureKey = kv.Key;
1044                      var length = kv.Value;
1045  
1046                      dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic);
1047                      dataWriter.Write(ref length);
1048                  }
1049              }
1050          }
1051      }
1052  }