/ src / Ryujinx.Graphics.Gpu / Image / TextureBindingsArrayCache.cs
TextureBindingsArrayCache.cs
   1  using Ryujinx.Graphics.GAL;
   2  using Ryujinx.Graphics.Gpu.Engine.Types;
   3  using Ryujinx.Graphics.Gpu.Memory;
   4  using Ryujinx.Graphics.Shader;
   5  using System;
   6  using System.Collections.Generic;
   7  using System.Linq;
   8  using System.Runtime.InteropServices;
   9  
  10  namespace Ryujinx.Graphics.Gpu.Image
  11  {
  12      /// <summary>
  13      /// Texture bindings array cache.
  14      /// </summary>
  15      class TextureBindingsArrayCache
  16      {
  17          /// <summary>
  18          /// Minimum timestamp delta until texture array can be removed from the cache.
  19          /// </summary>
  20          private const int MinDeltaForRemoval = 20000;
  21  
  22          private readonly GpuContext _context;
  23          private readonly GpuChannel _channel;
  24  
  25          /// <summary>
  26          /// Array cache entry key.
  27          /// </summary>
  28          private readonly struct CacheEntryFromPoolKey : IEquatable<CacheEntryFromPoolKey>
  29          {
  30              /// <summary>
  31              /// Whether the entry is for an image.
  32              /// </summary>
  33              public readonly bool IsImage;
  34  
  35              /// <summary>
  36              /// Whether the entry is for a sampler.
  37              /// </summary>
  38              public readonly bool IsSampler;
  39  
  40              /// <summary>
  41              /// Texture or image target type.
  42              /// </summary>
  43              public readonly Target Target;
  44  
  45              /// <summary>
  46              /// Number of entries of the array.
  47              /// </summary>
  48              public readonly int ArrayLength;
  49  
  50              private readonly TexturePool _texturePool;
  51              private readonly SamplerPool _samplerPool;
  52  
  53              /// <summary>
  54              /// Creates a new array cache entry.
  55              /// </summary>
  56              /// <param name="isImage">Whether the entry is for an image</param>
  57              /// <param name="bindingInfo">Binding information for the array</param>
  58              /// <param name="texturePool">Texture pool where the array textures are located</param>
  59              /// <param name="samplerPool">Sampler pool where the array samplers are located</param>
  60              public CacheEntryFromPoolKey(bool isImage, TextureBindingInfo bindingInfo, TexturePool texturePool, SamplerPool samplerPool)
  61              {
  62                  IsImage = isImage;
  63                  IsSampler = bindingInfo.IsSamplerOnly;
  64                  Target = bindingInfo.Target;
  65                  ArrayLength = bindingInfo.ArrayLength;
  66  
  67                  _texturePool = texturePool;
  68                  _samplerPool = samplerPool;
  69              }
  70  
  71              /// <summary>
  72              /// Checks if the pool matches the cached pool.
  73              /// </summary>
  74              /// <param name="texturePool">Texture or sampler pool instance</param>
  75              /// <returns>True if the pool matches, false otherwise</returns>
  76              public bool MatchesPool<T>(IPool<T> pool)
  77              {
  78                  return _texturePool == pool || _samplerPool == pool;
  79              }
  80  
  81              /// <summary>
  82              /// Checks if the texture and sampler pools matches the cached pools.
  83              /// </summary>
  84              /// <param name="texturePool">Texture pool instance</param>
  85              /// <param name="samplerPool">Sampler pool instance</param>
  86              /// <returns>True if the pools match, false otherwise</returns>
  87              private bool MatchesPools(TexturePool texturePool, SamplerPool samplerPool)
  88              {
  89                  return _texturePool == texturePool && _samplerPool == samplerPool;
  90              }
  91  
  92              public bool Equals(CacheEntryFromPoolKey other)
  93              {
  94                  return IsImage == other.IsImage &&
  95                      IsSampler == other.IsSampler &&
  96                      Target == other.Target &&
  97                      ArrayLength == other.ArrayLength &&
  98                      MatchesPools(other._texturePool, other._samplerPool);
  99              }
 100  
 101              public override bool Equals(object obj)
 102              {
 103                  return obj is CacheEntryFromBufferKey other && Equals(other);
 104              }
 105  
 106              public override int GetHashCode()
 107              {
 108                  return HashCode.Combine(_texturePool, _samplerPool, IsSampler);
 109              }
 110          }
 111  
 112          /// <summary>
 113          /// Array cache entry key.
 114          /// </summary>
 115          private readonly struct CacheEntryFromBufferKey : IEquatable<CacheEntryFromBufferKey>
 116          {
 117              /// <summary>
 118              /// Whether the entry is for an image.
 119              /// </summary>
 120              public readonly bool IsImage;
 121  
 122              /// <summary>
 123              /// Texture or image target type.
 124              /// </summary>
 125              public readonly Target Target;
 126  
 127              /// <summary>
 128              /// Word offset of the first handle on the constant buffer.
 129              /// </summary>
 130              public readonly int HandleIndex;
 131  
 132              /// <summary>
 133              /// Number of entries of the array.
 134              /// </summary>
 135              public readonly int ArrayLength;
 136  
 137              private readonly TexturePool _texturePool;
 138              private readonly SamplerPool _samplerPool;
 139  
 140              private readonly BufferBounds _textureBufferBounds;
 141  
 142              /// <summary>
 143              /// Creates a new array cache entry.
 144              /// </summary>
 145              /// <param name="isImage">Whether the entry is for an image</param>
 146              /// <param name="bindingInfo">Binding information for the array</param>
 147              /// <param name="texturePool">Texture pool where the array textures are located</param>
 148              /// <param name="samplerPool">Sampler pool where the array samplers are located</param>
 149              /// <param name="textureBufferBounds">Constant buffer bounds with the texture handles</param>
 150              public CacheEntryFromBufferKey(
 151                  bool isImage,
 152                  TextureBindingInfo bindingInfo,
 153                  TexturePool texturePool,
 154                  SamplerPool samplerPool,
 155                  ref BufferBounds textureBufferBounds)
 156              {
 157                  IsImage = isImage;
 158                  Target = bindingInfo.Target;
 159                  HandleIndex = bindingInfo.Handle;
 160                  ArrayLength = bindingInfo.ArrayLength;
 161  
 162                  _texturePool = texturePool;
 163                  _samplerPool = samplerPool;
 164  
 165                  _textureBufferBounds = textureBufferBounds;
 166              }
 167  
 168              /// <summary>
 169              /// Checks if the texture and sampler pools matches the cached pools.
 170              /// </summary>
 171              /// <param name="texturePool">Texture pool instance</param>
 172              /// <param name="samplerPool">Sampler pool instance</param>
 173              /// <returns>True if the pools match, false otherwise</returns>
 174              private bool MatchesPools(TexturePool texturePool, SamplerPool samplerPool)
 175              {
 176                  return _texturePool == texturePool && _samplerPool == samplerPool;
 177              }
 178  
 179              /// <summary>
 180              /// Checks if the cached constant buffer address and size matches.
 181              /// </summary>
 182              /// <param name="textureBufferBounds">New buffer address and size</param>
 183              /// <returns>True if the address and size matches, false otherwise</returns>
 184              private bool MatchesBufferBounds(BufferBounds textureBufferBounds)
 185              {
 186                  return _textureBufferBounds.Equals(textureBufferBounds);
 187              }
 188  
 189              public bool Equals(CacheEntryFromBufferKey other)
 190              {
 191                  return IsImage == other.IsImage &&
 192                      Target == other.Target &&
 193                      HandleIndex == other.HandleIndex &&
 194                      ArrayLength == other.ArrayLength &&
 195                      MatchesPools(other._texturePool, other._samplerPool) &&
 196                      MatchesBufferBounds(other._textureBufferBounds);
 197              }
 198  
 199              public override bool Equals(object obj)
 200              {
 201                  return obj is CacheEntryFromBufferKey other && Equals(other);
 202              }
 203  
 204              public override int GetHashCode()
 205              {
 206                  return _textureBufferBounds.Range.GetHashCode();
 207              }
 208          }
 209  
 210          /// <summary>
 211          /// Array cache entry from pool.
 212          /// </summary>
 213          private class CacheEntry
 214          {
 215              /// <summary>
 216              /// All cached textures, along with their invalidated sequence number as value.
 217              /// </summary>
 218              public readonly Dictionary<Texture, int> Textures;
 219  
 220              /// <summary>
 221              /// Backend texture array if the entry is for a texture, otherwise null.
 222              /// </summary>
 223              public readonly ITextureArray TextureArray;
 224  
 225              /// <summary>
 226              /// Backend image array if the entry is for an image, otherwise null.
 227              /// </summary>
 228              public readonly IImageArray ImageArray;
 229  
 230              /// <summary>
 231              /// Texture pool where the array textures are located.
 232              /// </summary>
 233              protected readonly TexturePool TexturePool;
 234  
 235              /// <summary>
 236              /// Sampler pool where the array samplers are located.
 237              /// </summary>
 238              protected readonly SamplerPool SamplerPool;
 239  
 240              private int _texturePoolSequence;
 241              private int _samplerPoolSequence;
 242  
 243              /// <summary>
 244              /// Creates a new array cache entry.
 245              /// </summary>
 246              /// <param name="texturePool">Texture pool where the array textures are located</param>
 247              /// <param name="samplerPool">Sampler pool where the array samplers are located</param>
 248              private CacheEntry(TexturePool texturePool, SamplerPool samplerPool)
 249              {
 250                  Textures = new Dictionary<Texture, int>();
 251  
 252                  TexturePool = texturePool;
 253                  SamplerPool = samplerPool;
 254              }
 255  
 256              /// <summary>
 257              /// Creates a new array cache entry.
 258              /// </summary>
 259              /// <param name="array">Backend texture array</param>
 260              /// <param name="texturePool">Texture pool where the array textures are located</param>
 261              /// <param name="samplerPool">Sampler pool where the array samplers are located</param>
 262              public CacheEntry(ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : this(texturePool, samplerPool)
 263              {
 264                  TextureArray = array;
 265              }
 266  
 267              /// <summary>
 268              /// Creates a new array cache entry.
 269              /// </summary>
 270              /// <param name="array">Backend image array</param>
 271              /// <param name="texturePool">Texture pool where the array textures are located</param>
 272              /// <param name="samplerPool">Sampler pool where the array samplers are located</param>
 273              public CacheEntry(IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : this(texturePool, samplerPool)
 274              {
 275                  ImageArray = array;
 276              }
 277  
 278              /// <summary>
 279              /// Synchronizes memory for all textures in the array.
 280              /// </summary>
 281              /// <param name="isStore">Indicates if the texture may be modified by the access</param>
 282              /// <param name="blacklistScale">Indicates if the texture should be blacklisted for scaling</param>
 283              public void SynchronizeMemory(bool isStore, bool blacklistScale)
 284              {
 285                  foreach (Texture texture in Textures.Keys)
 286                  {
 287                      texture.SynchronizeMemory();
 288  
 289                      if (isStore)
 290                      {
 291                          texture.SignalModified();
 292                      }
 293  
 294                      if (blacklistScale && texture.ScaleMode != TextureScaleMode.Blacklisted)
 295                      {
 296                          // Scaling textures used on arrays is currently not supported.
 297  
 298                          texture.BlacklistScale();
 299                      }
 300                  }
 301              }
 302  
 303              /// <summary>
 304              /// Clears all cached texture instances.
 305              /// </summary>
 306              public virtual void Reset()
 307              {
 308                  Textures.Clear();
 309              }
 310  
 311              /// <summary>
 312              /// Checks if any texture has been deleted since the last call to this method.
 313              /// </summary>
 314              /// <returns>True if one or more textures have been deleted, false otherwise</returns>
 315              public bool ValidateTextures()
 316              {
 317                  foreach ((Texture texture, int invalidatedSequence) in Textures)
 318                  {
 319                      if (texture.InvalidatedSequence != invalidatedSequence)
 320                      {
 321                          return false;
 322                      }
 323                  }
 324  
 325                  return true;
 326              }
 327  
 328              /// <summary>
 329              /// Checks if the cached texture or sampler pool has been modified since the last call to this method.
 330              /// </summary>
 331              /// <returns>True if any used entries of the pool might have been modified, false otherwise</returns>
 332              public bool TexturePoolModified()
 333              {
 334                  return TexturePool.WasModified(ref _texturePoolSequence);
 335              }
 336  
 337              /// <summary>
 338              /// Checks if the cached texture or sampler pool has been modified since the last call to this method.
 339              /// </summary>
 340              /// <returns>True if any used entries of the pool might have been modified, false otherwise</returns>
 341              public bool SamplerPoolModified()
 342              {
 343                  return SamplerPool != null && SamplerPool.WasModified(ref _samplerPoolSequence);
 344              }
 345          }
 346  
 347          /// <summary>
 348          /// Array cache entry from constant buffer.
 349          /// </summary>
 350          private class CacheEntryFromBuffer : CacheEntry
 351          {
 352              /// <summary>
 353              /// Key for this entry on the cache.
 354              /// </summary>
 355              public readonly CacheEntryFromBufferKey Key;
 356  
 357              /// <summary>
 358              /// Linked list node used on the texture bindings array cache.
 359              /// </summary>
 360              public LinkedListNode<CacheEntryFromBuffer> CacheNode;
 361  
 362              /// <summary>
 363              /// Timestamp set on the last use of the array by the cache.
 364              /// </summary>
 365              public int CacheTimestamp;
 366  
 367              /// <summary>
 368              /// All pool texture IDs along with their textures.
 369              /// </summary>
 370              public readonly Dictionary<int, (Texture, TextureDescriptor)> TextureIds;
 371  
 372              /// <summary>
 373              /// All pool sampler IDs along with their samplers.
 374              /// </summary>
 375              public readonly Dictionary<int, (Sampler, SamplerDescriptor)> SamplerIds;
 376  
 377              private int[] _cachedTextureBuffer;
 378              private int[] _cachedSamplerBuffer;
 379  
 380              private int _lastSequenceNumber;
 381  
 382              /// <summary>
 383              /// Creates a new array cache entry.
 384              /// </summary>
 385              /// <param name="key">Key for this entry on the cache</param>
 386              /// <param name="array">Backend texture array</param>
 387              /// <param name="texturePool">Texture pool where the array textures are located</param>
 388              /// <param name="samplerPool">Sampler pool where the array samplers are located</param>
 389              public CacheEntryFromBuffer(ref CacheEntryFromBufferKey key, ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : base(array, texturePool, samplerPool)
 390              {
 391                  Key = key;
 392                  _lastSequenceNumber = -1;
 393                  TextureIds = new Dictionary<int, (Texture, TextureDescriptor)>();
 394                  SamplerIds = new Dictionary<int, (Sampler, SamplerDescriptor)>();
 395              }
 396  
 397              /// <summary>
 398              /// Creates a new array cache entry.
 399              /// </summary>
 400              /// <param name="key">Key for this entry on the cache</param>
 401              /// <param name="array">Backend image array</param>
 402              /// <param name="texturePool">Texture pool where the array textures are located</param>
 403              /// <param name="samplerPool">Sampler pool where the array samplers are located</param>
 404              public CacheEntryFromBuffer(ref CacheEntryFromBufferKey key, IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : base(array, texturePool, samplerPool)
 405              {
 406                  Key = key;
 407                  _lastSequenceNumber = -1;
 408                  TextureIds = new Dictionary<int, (Texture, TextureDescriptor)>();
 409                  SamplerIds = new Dictionary<int, (Sampler, SamplerDescriptor)>();
 410              }
 411  
 412              /// <inheritdoc/>
 413              public override void Reset()
 414              {
 415                  base.Reset();
 416                  TextureIds.Clear();
 417                  SamplerIds.Clear();
 418              }
 419  
 420              /// <summary>
 421              /// Updates the cached constant buffer data.
 422              /// </summary>
 423              /// <param name="cachedTextureBuffer">Constant buffer data with the texture handles (and sampler handles, if they are combined)</param>
 424              /// <param name="cachedSamplerBuffer">Constant buffer data with the sampler handles</param>
 425              /// <param name="separateSamplerBuffer">Whether <paramref name="cachedTextureBuffer"/> and <paramref name="cachedSamplerBuffer"/> comes from different buffers</param>
 426              public void UpdateData(ReadOnlySpan<int> cachedTextureBuffer, ReadOnlySpan<int> cachedSamplerBuffer, bool separateSamplerBuffer)
 427              {
 428                  _cachedTextureBuffer = cachedTextureBuffer.ToArray();
 429                  _cachedSamplerBuffer = separateSamplerBuffer ? cachedSamplerBuffer.ToArray() : _cachedTextureBuffer;
 430              }
 431  
 432              /// <summary>
 433              /// Checks if the sequence number matches the one used on the last call to this method.
 434              /// </summary>
 435              /// <param name="currentSequenceNumber">Current sequence number</param>
 436              /// <returns>True if the sequence numbers match, false otherwise</returns>
 437              public bool MatchesSequenceNumber(int currentSequenceNumber)
 438              {
 439                  if (_lastSequenceNumber == currentSequenceNumber)
 440                  {
 441                      return true;
 442                  }
 443  
 444                  _lastSequenceNumber = currentSequenceNumber;
 445  
 446                  return false;
 447              }
 448  
 449              /// <summary>
 450              /// Checks if the buffer data matches the cached data.
 451              /// </summary>
 452              /// <param name="cachedTextureBuffer">New texture buffer data</param>
 453              /// <param name="cachedSamplerBuffer">New sampler buffer data</param>
 454              /// <param name="separateSamplerBuffer">Whether <paramref name="cachedTextureBuffer"/> and <paramref name="cachedSamplerBuffer"/> comes from different buffers</param>
 455              /// <param name="samplerWordOffset">Word offset of the sampler constant buffer handle that is used</param>
 456              /// <returns>True if the data matches, false otherwise</returns>
 457              public bool MatchesBufferData(
 458                  ReadOnlySpan<int> cachedTextureBuffer,
 459                  ReadOnlySpan<int> cachedSamplerBuffer,
 460                  bool separateSamplerBuffer,
 461                  int samplerWordOffset)
 462              {
 463                  if (_cachedTextureBuffer != null && cachedTextureBuffer.Length > _cachedTextureBuffer.Length)
 464                  {
 465                      cachedTextureBuffer = cachedTextureBuffer[.._cachedTextureBuffer.Length];
 466                  }
 467  
 468                  if (!_cachedTextureBuffer.AsSpan().SequenceEqual(cachedTextureBuffer))
 469                  {
 470                      return false;
 471                  }
 472  
 473                  if (separateSamplerBuffer)
 474                  {
 475                      if (_cachedSamplerBuffer == null ||
 476                          _cachedSamplerBuffer.Length <= samplerWordOffset ||
 477                          cachedSamplerBuffer.Length <= samplerWordOffset)
 478                      {
 479                          return false;
 480                      }
 481  
 482                      int oldValue = _cachedSamplerBuffer[samplerWordOffset];
 483                      int newValue = cachedSamplerBuffer[samplerWordOffset];
 484  
 485                      return oldValue == newValue;
 486                  }
 487  
 488                  return true;
 489              }
 490  
 491              /// <summary>
 492              /// Checks if the cached texture or sampler pool has been modified since the last call to this method.
 493              /// </summary>
 494              /// <returns>True if any used entries of the pools might have been modified, false otherwise</returns>
 495              public bool PoolsModified()
 496              {
 497                  bool texturePoolModified = TexturePoolModified();
 498                  bool samplerPoolModified = SamplerPoolModified();
 499  
 500                  // If both pools were not modified since the last check, we have nothing else to check.
 501                  if (!texturePoolModified && !samplerPoolModified)
 502                  {
 503                      return false;
 504                  }
 505  
 506                  // If the pools were modified, let's check if any of the entries we care about changed.
 507  
 508                  // Check if any of our cached textures changed on the pool.
 509                  foreach ((int textureId, (Texture texture, TextureDescriptor descriptor)) in TextureIds)
 510                  {
 511                      if (TexturePool.GetCachedItem(textureId) != texture ||
 512                          (texture == null && TexturePool.IsValidId(textureId) && !TexturePool.GetDescriptorRef(textureId).Equals(descriptor)))
 513                      {
 514                          return true;
 515                      }
 516                  }
 517  
 518                  // Check if any of our cached samplers changed on the pool.
 519                  if (SamplerPool != null)
 520                  {
 521                      foreach ((int samplerId, (Sampler sampler, SamplerDescriptor descriptor)) in SamplerIds)
 522                      {
 523                          if (SamplerPool.GetCachedItem(samplerId) != sampler ||
 524                              (sampler == null && SamplerPool.IsValidId(samplerId) && !SamplerPool.GetDescriptorRef(samplerId).Equals(descriptor)))
 525                          {
 526                              return true;
 527                          }
 528                      }
 529                  }
 530  
 531                  return false;
 532              }
 533          }
 534  
 535          private readonly Dictionary<CacheEntryFromBufferKey, CacheEntryFromBuffer> _cacheFromBuffer;
 536          private readonly Dictionary<CacheEntryFromPoolKey, CacheEntry> _cacheFromPool;
 537          private readonly LinkedList<CacheEntryFromBuffer> _lruCache;
 538  
 539          private int _currentTimestamp;
 540  
 541          /// <summary>
 542          /// Creates a new instance of the texture bindings array cache.
 543          /// </summary>
 544          /// <param name="context">GPU context</param>
 545          /// <param name="channel">GPU channel</param>
 546          public TextureBindingsArrayCache(GpuContext context, GpuChannel channel)
 547          {
 548              _context = context;
 549              _channel = channel;
 550              _cacheFromBuffer = new Dictionary<CacheEntryFromBufferKey, CacheEntryFromBuffer>();
 551              _cacheFromPool = new Dictionary<CacheEntryFromPoolKey, CacheEntry>();
 552              _lruCache = new LinkedList<CacheEntryFromBuffer>();
 553          }
 554  
 555          /// <summary>
 556          /// Updates a texture array bindings and textures.
 557          /// </summary>
 558          /// <param name="texturePool">Texture pool</param>
 559          /// <param name="samplerPool">Sampler pool</param>
 560          /// <param name="stage">Shader stage where the array is used</param>
 561          /// <param name="stageIndex">Shader stage index where the array is used</param>
 562          /// <param name="textureBufferIndex">Texture constant buffer index</param>
 563          /// <param name="samplerIndex">Sampler handles source</param>
 564          /// <param name="bindingInfo">Array binding information</param>
 565          public void UpdateTextureArray(
 566              TexturePool texturePool,
 567              SamplerPool samplerPool,
 568              ShaderStage stage,
 569              int stageIndex,
 570              int textureBufferIndex,
 571              SamplerIndex samplerIndex,
 572              in TextureBindingInfo bindingInfo)
 573          {
 574              Update(texturePool, samplerPool, stage, stageIndex, textureBufferIndex, isImage: false, samplerIndex, bindingInfo);
 575          }
 576  
 577          /// <summary>
 578          /// Updates a image array bindings and textures.
 579          /// </summary>
 580          /// <param name="texturePool">Texture pool</param>
 581          /// <param name="stage">Shader stage where the array is used</param>
 582          /// <param name="stageIndex">Shader stage index where the array is used</param>
 583          /// <param name="textureBufferIndex">Texture constant buffer index</param>
 584          /// <param name="bindingInfo">Array binding information</param>
 585          public void UpdateImageArray(TexturePool texturePool, ShaderStage stage, int stageIndex, int textureBufferIndex, in TextureBindingInfo bindingInfo)
 586          {
 587              Update(texturePool, null, stage, stageIndex, textureBufferIndex, isImage: true, SamplerIndex.ViaHeaderIndex, bindingInfo);
 588          }
 589  
 590          /// <summary>
 591          /// Updates a texture or image array bindings and textures.
 592          /// </summary>
 593          /// <param name="texturePool">Texture pool</param>
 594          /// <param name="samplerPool">Sampler pool</param>
 595          /// <param name="stage">Shader stage where the array is used</param>
 596          /// <param name="stageIndex">Shader stage index where the array is used</param>
 597          /// <param name="textureBufferIndex">Texture constant buffer index</param>
 598          /// <param name="isImage">Whether the array is a image or texture array</param>
 599          /// <param name="samplerIndex">Sampler handles source</param>
 600          /// <param name="bindingInfo">Array binding information</param>
 601          private void Update(
 602              TexturePool texturePool,
 603              SamplerPool samplerPool,
 604              ShaderStage stage,
 605              int stageIndex,
 606              int textureBufferIndex,
 607              bool isImage,
 608              SamplerIndex samplerIndex,
 609              in TextureBindingInfo bindingInfo)
 610          {
 611              if (IsDirectHandleType(bindingInfo.Handle))
 612              {
 613                  UpdateFromPool(texturePool, samplerPool, stage, isImage, bindingInfo);
 614              }
 615              else
 616              {
 617                  UpdateFromBuffer(texturePool, samplerPool, stage, stageIndex, textureBufferIndex, isImage, samplerIndex, bindingInfo);
 618              }
 619          }
 620  
 621          /// <summary>
 622          /// Updates a texture or image array bindings and textures from a texture or sampler pool.
 623          /// </summary>
 624          /// <param name="texturePool">Texture pool</param>
 625          /// <param name="samplerPool">Sampler pool</param>
 626          /// <param name="stage">Shader stage where the array is used</param>
 627          /// <param name="isImage">Whether the array is a image or texture array</param>
 628          /// <param name="bindingInfo">Array binding information</param>
 629          private void UpdateFromPool(TexturePool texturePool, SamplerPool samplerPool, ShaderStage stage, bool isImage, in TextureBindingInfo bindingInfo)
 630          {
 631              CacheEntry entry = GetOrAddEntry(texturePool, samplerPool, bindingInfo, isImage, out bool isNewEntry);
 632  
 633              bool isSampler = bindingInfo.IsSamplerOnly;
 634              bool poolModified = isSampler ? entry.SamplerPoolModified() : entry.TexturePoolModified();
 635              bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
 636              bool resScaleUnsupported = bindingInfo.Flags.HasFlag(TextureUsageFlags.ResScaleUnsupported);
 637  
 638              if (!poolModified && !isNewEntry && entry.ValidateTextures())
 639              {
 640                  entry.SynchronizeMemory(isStore, resScaleUnsupported);
 641  
 642                  if (isImage)
 643                  {
 644                      SetImageArray(stage, bindingInfo, entry.ImageArray);
 645                  }
 646                  else
 647                  {
 648                      SetTextureArray(stage, bindingInfo, entry.TextureArray);
 649                  }
 650  
 651                  return;
 652              }
 653  
 654              if (!isNewEntry)
 655              {
 656                  entry.Reset();
 657              }
 658  
 659              int length = (isSampler ? samplerPool.MaximumId : texturePool.MaximumId) + 1;
 660              length = Math.Min(length, bindingInfo.ArrayLength);
 661  
 662              ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength];
 663              ITexture[] textures = new ITexture[bindingInfo.ArrayLength];
 664  
 665              for (int index = 0; index < length; index++)
 666              {
 667                  Texture texture = null;
 668                  Sampler sampler = null;
 669  
 670                  if (isSampler)
 671                  {
 672                      sampler = samplerPool?.Get(index);
 673                  }
 674                  else
 675                  {
 676                      ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(index, bindingInfo.FormatInfo, out texture);
 677  
 678                      if (texture != null)
 679                      {
 680                          entry.Textures[texture] = texture.InvalidatedSequence;
 681  
 682                          if (isStore)
 683                          {
 684                              texture.SignalModified();
 685                          }
 686  
 687                          if (resScaleUnsupported && texture.ScaleMode != TextureScaleMode.Blacklisted)
 688                          {
 689                              // Scaling textures used on arrays is currently not supported.
 690  
 691                              texture.BlacklistScale();
 692                          }
 693                      }
 694                  }
 695  
 696                  ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
 697                  ISampler hostSampler = sampler?.GetHostSampler(texture);
 698  
 699                  if (hostTexture != null && texture.Target == Target.TextureBuffer)
 700                  {
 701                      // Ensure that the buffer texture is using the correct buffer as storage.
 702                      // Buffers are frequently re-created to accommodate larger data, so we need to re-bind
 703                      // to ensure we're not using a old buffer that was already deleted.
 704                      if (isImage)
 705                      {
 706                          _channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index);
 707                      }
 708                      else
 709                      {
 710                          _channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index);
 711                      }
 712                  }
 713                  else if (isImage)
 714                  {
 715                      textures[index] = hostTexture;
 716                  }
 717                  else
 718                  {
 719                      samplers[index] = hostSampler;
 720                      textures[index] = hostTexture;
 721                  }
 722              }
 723  
 724              if (isImage)
 725              {
 726                  entry.ImageArray.SetImages(0, textures);
 727  
 728                  SetImageArray(stage, bindingInfo, entry.ImageArray);
 729              }
 730              else
 731              {
 732                  entry.TextureArray.SetSamplers(0, samplers);
 733                  entry.TextureArray.SetTextures(0, textures);
 734  
 735                  SetTextureArray(stage, bindingInfo, entry.TextureArray);
 736              }
 737          }
 738  
 739          /// <summary>
 740          /// Updates a texture or image array bindings and textures from constant buffer handles.
 741          /// </summary>
 742          /// <param name="texturePool">Texture pool</param>
 743          /// <param name="samplerPool">Sampler pool</param>
 744          /// <param name="stage">Shader stage where the array is used</param>
 745          /// <param name="stageIndex">Shader stage index where the array is used</param>
 746          /// <param name="textureBufferIndex">Texture constant buffer index</param>
 747          /// <param name="isImage">Whether the array is a image or texture array</param>
 748          /// <param name="samplerIndex">Sampler handles source</param>
 749          /// <param name="bindingInfo">Array binding information</param>
 750          private void UpdateFromBuffer(
 751              TexturePool texturePool,
 752              SamplerPool samplerPool,
 753              ShaderStage stage,
 754              int stageIndex,
 755              int textureBufferIndex,
 756              bool isImage,
 757              SamplerIndex samplerIndex,
 758              in TextureBindingInfo bindingInfo)
 759          {
 760              (textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, textureBufferIndex);
 761  
 762              bool separateSamplerBuffer = textureBufferIndex != samplerBufferIndex;
 763              bool isCompute = stage == ShaderStage.Compute;
 764  
 765              ref BufferBounds textureBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex);
 766              ref BufferBounds samplerBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex);
 767  
 768              CacheEntryFromBuffer entry = GetOrAddEntry(
 769                  texturePool,
 770                  samplerPool,
 771                  bindingInfo,
 772                  isImage,
 773                  ref textureBufferBounds,
 774                  out bool isNewEntry);
 775  
 776              bool poolsModified = entry.PoolsModified();
 777              bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
 778              bool resScaleUnsupported = bindingInfo.Flags.HasFlag(TextureUsageFlags.ResScaleUnsupported);
 779  
 780              ReadOnlySpan<int> cachedTextureBuffer;
 781              ReadOnlySpan<int> cachedSamplerBuffer;
 782  
 783              if (!poolsModified && !isNewEntry && entry.ValidateTextures())
 784              {
 785                  if (entry.MatchesSequenceNumber(_context.SequenceNumber))
 786                  {
 787                      entry.SynchronizeMemory(isStore, resScaleUnsupported);
 788  
 789                      if (isImage)
 790                      {
 791                          SetImageArray(stage, bindingInfo, entry.ImageArray);
 792                      }
 793                      else
 794                      {
 795                          SetTextureArray(stage, bindingInfo, entry.TextureArray);
 796                      }
 797  
 798                      return;
 799                  }
 800  
 801                  cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range));
 802  
 803                  if (separateSamplerBuffer)
 804                  {
 805                      cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range));
 806                  }
 807                  else
 808                  {
 809                      cachedSamplerBuffer = cachedTextureBuffer;
 810                  }
 811  
 812                  (_, int samplerWordOffset, _) = TextureHandle.UnpackOffsets(bindingInfo.Handle);
 813  
 814                  if (entry.MatchesBufferData(cachedTextureBuffer, cachedSamplerBuffer, separateSamplerBuffer, samplerWordOffset))
 815                  {
 816                      entry.SynchronizeMemory(isStore, resScaleUnsupported);
 817  
 818                      if (isImage)
 819                      {
 820                          SetImageArray(stage, bindingInfo, entry.ImageArray);
 821                      }
 822                      else
 823                      {
 824                          SetTextureArray(stage, bindingInfo, entry.TextureArray);
 825                      }
 826  
 827                      return;
 828                  }
 829              }
 830              else
 831              {
 832                  cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range));
 833  
 834                  if (separateSamplerBuffer)
 835                  {
 836                      cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range));
 837                  }
 838                  else
 839                  {
 840                      cachedSamplerBuffer = cachedTextureBuffer;
 841                  }
 842              }
 843  
 844              if (!isNewEntry)
 845              {
 846                  entry.Reset();
 847              }
 848  
 849              entry.UpdateData(cachedTextureBuffer, cachedSamplerBuffer, separateSamplerBuffer);
 850  
 851              ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength];
 852              ITexture[] textures = new ITexture[bindingInfo.ArrayLength];
 853  
 854              for (int index = 0; index < bindingInfo.ArrayLength; index++)
 855              {
 856                  int handleIndex = bindingInfo.Handle + index * (Constants.TextureHandleSizeInBytes / sizeof(int));
 857                  int packedId = TextureHandle.ReadPackedId(handleIndex, cachedTextureBuffer, cachedSamplerBuffer);
 858                  int textureId = TextureHandle.UnpackTextureId(packedId);
 859                  int samplerId;
 860  
 861                  if (samplerIndex == SamplerIndex.ViaHeaderIndex)
 862                  {
 863                      samplerId = textureId;
 864                  }
 865                  else
 866                  {
 867                      samplerId = TextureHandle.UnpackSamplerId(packedId);
 868                  }
 869  
 870                  ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(textureId, bindingInfo.FormatInfo, out Texture texture);
 871  
 872                  if (texture != null)
 873                  {
 874                      entry.Textures[texture] = texture.InvalidatedSequence;
 875  
 876                      if (isStore)
 877                      {
 878                          texture.SignalModified();
 879                      }
 880  
 881                      if (resScaleUnsupported && texture.ScaleMode != TextureScaleMode.Blacklisted)
 882                      {
 883                          // Scaling textures used on arrays is currently not supported.
 884  
 885                          texture.BlacklistScale();
 886                      }
 887                  }
 888  
 889                  entry.TextureIds[textureId] = (texture, descriptor);
 890  
 891                  ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
 892                  ISampler hostSampler = null;
 893  
 894                  if (!isImage && bindingInfo.Target != Target.TextureBuffer)
 895                  {
 896                      Sampler sampler = samplerPool?.Get(samplerId);
 897  
 898                      entry.SamplerIds[samplerId] = (sampler, samplerPool?.GetDescriptorRef(samplerId) ?? default);
 899  
 900                      hostSampler = sampler?.GetHostSampler(texture);
 901                  }
 902  
 903                  if (hostTexture != null && texture.Target == Target.TextureBuffer)
 904                  {
 905                      // Ensure that the buffer texture is using the correct buffer as storage.
 906                      // Buffers are frequently re-created to accommodate larger data, so we need to re-bind
 907                      // to ensure we're not using a old buffer that was already deleted.
 908                      if (isImage)
 909                      {
 910                          _channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index);
 911                      }
 912                      else
 913                      {
 914                          _channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index);
 915                      }
 916                  }
 917                  else if (isImage)
 918                  {
 919                      textures[index] = hostTexture;
 920                  }
 921                  else
 922                  {
 923                      samplers[index] = hostSampler;
 924                      textures[index] = hostTexture;
 925                  }
 926              }
 927  
 928              if (isImage)
 929              {
 930                  entry.ImageArray.SetImages(0, textures);
 931  
 932                  SetImageArray(stage, bindingInfo, entry.ImageArray);
 933              }
 934              else
 935              {
 936                  entry.TextureArray.SetSamplers(0, samplers);
 937                  entry.TextureArray.SetTextures(0, textures);
 938  
 939                  SetTextureArray(stage, bindingInfo, entry.TextureArray);
 940              }
 941          }
 942  
 943          /// <summary>
 944          /// Updates a texture array binding on the host.
 945          /// </summary>
 946          /// <param name="stage">Shader stage where the array is used</param>
 947          /// <param name="bindingInfo">Array binding information</param>
 948          /// <param name="array">Texture array</param>
 949          private void SetTextureArray(ShaderStage stage, in TextureBindingInfo bindingInfo, ITextureArray array)
 950          {
 951              if (bindingInfo.Set >= _context.Capabilities.ExtraSetBaseIndex && _context.Capabilities.MaximumExtraSets != 0)
 952              {
 953                  _context.Renderer.Pipeline.SetTextureArraySeparate(stage, bindingInfo.Set, array);
 954              }
 955              else
 956              {
 957                  _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, array);
 958              }
 959          }
 960  
 961          /// <summary>
 962          /// Updates a image array binding on the host.
 963          /// </summary>
 964          /// <param name="stage">Shader stage where the array is used</param>
 965          /// <param name="bindingInfo">Array binding information</param>
 966          /// <param name="array">Image array</param>
 967          private void SetImageArray(ShaderStage stage, in TextureBindingInfo bindingInfo, IImageArray array)
 968          {
 969              if (bindingInfo.Set >= _context.Capabilities.ExtraSetBaseIndex && _context.Capabilities.MaximumExtraSets != 0)
 970              {
 971                  _context.Renderer.Pipeline.SetImageArraySeparate(stage, bindingInfo.Set, array);
 972              }
 973              else
 974              {
 975                  _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, array);
 976              }
 977          }
 978  
 979          /// <summary>
 980          /// Gets a cached texture entry from pool, or creates a new one if not found.
 981          /// </summary>
 982          /// <param name="texturePool">Texture pool</param>
 983          /// <param name="samplerPool">Sampler pool</param>
 984          /// <param name="bindingInfo">Array binding information</param>
 985          /// <param name="isImage">Whether the array is a image or texture array</param>
 986          /// <param name="isNew">Whether a new entry was created, or an existing one was returned</param>
 987          /// <returns>Cache entry</returns>
 988          private CacheEntry GetOrAddEntry(
 989              TexturePool texturePool,
 990              SamplerPool samplerPool,
 991              in TextureBindingInfo bindingInfo,
 992              bool isImage,
 993              out bool isNew)
 994          {
 995              CacheEntryFromPoolKey key = new CacheEntryFromPoolKey(isImage, bindingInfo, texturePool, samplerPool);
 996  
 997              isNew = !_cacheFromPool.TryGetValue(key, out CacheEntry entry);
 998  
 999              if (isNew)
1000              {
1001                  int arrayLength = bindingInfo.ArrayLength;
1002  
1003                  if (isImage)
1004                  {
1005                      IImageArray array = _context.Renderer.CreateImageArray(arrayLength, bindingInfo.Target == Target.TextureBuffer);
1006  
1007                      _cacheFromPool.Add(key, entry = new CacheEntry(array, texturePool, samplerPool));
1008                  }
1009                  else
1010                  {
1011                      ITextureArray array = _context.Renderer.CreateTextureArray(arrayLength, bindingInfo.Target == Target.TextureBuffer);
1012  
1013                      _cacheFromPool.Add(key, entry = new CacheEntry(array, texturePool, samplerPool));
1014                  }
1015              }
1016  
1017              return entry;
1018          }
1019  
1020          /// <summary>
1021          /// Gets a cached texture entry from constant buffer, or creates a new one if not found.
1022          /// </summary>
1023          /// <param name="texturePool">Texture pool</param>
1024          /// <param name="samplerPool">Sampler pool</param>
1025          /// <param name="bindingInfo">Array binding information</param>
1026          /// <param name="isImage">Whether the array is a image or texture array</param>
1027          /// <param name="textureBufferBounds">Constant buffer bounds with the texture handles</param>
1028          /// <param name="isNew">Whether a new entry was created, or an existing one was returned</param>
1029          /// <returns>Cache entry</returns>
1030          private CacheEntryFromBuffer GetOrAddEntry(
1031              TexturePool texturePool,
1032              SamplerPool samplerPool,
1033              in TextureBindingInfo bindingInfo,
1034              bool isImage,
1035              ref BufferBounds textureBufferBounds,
1036              out bool isNew)
1037          {
1038              CacheEntryFromBufferKey key = new CacheEntryFromBufferKey(
1039                  isImage,
1040                  bindingInfo,
1041                  texturePool,
1042                  samplerPool,
1043                  ref textureBufferBounds);
1044  
1045              isNew = !_cacheFromBuffer.TryGetValue(key, out CacheEntryFromBuffer entry);
1046  
1047              if (isNew)
1048              {
1049                  int arrayLength = bindingInfo.ArrayLength;
1050  
1051                  if (isImage)
1052                  {
1053                      IImageArray array = _context.Renderer.CreateImageArray(arrayLength, bindingInfo.Target == Target.TextureBuffer);
1054  
1055                      _cacheFromBuffer.Add(key, entry = new CacheEntryFromBuffer(ref key, array, texturePool, samplerPool));
1056                  }
1057                  else
1058                  {
1059                      ITextureArray array = _context.Renderer.CreateTextureArray(arrayLength, bindingInfo.Target == Target.TextureBuffer);
1060  
1061                      _cacheFromBuffer.Add(key, entry = new CacheEntryFromBuffer(ref key, array, texturePool, samplerPool));
1062                  }
1063              }
1064  
1065              if (entry.CacheNode != null)
1066              {
1067                  _lruCache.Remove(entry.CacheNode);
1068                  _lruCache.AddLast(entry.CacheNode);
1069              }
1070              else
1071              {
1072                  entry.CacheNode = _lruCache.AddLast(entry);
1073              }
1074  
1075              entry.CacheTimestamp = ++_currentTimestamp;
1076  
1077              RemoveLeastUsedEntries();
1078  
1079              return entry;
1080          }
1081  
1082          /// <summary>
1083          /// Remove entries from the cache that have not been used for some time.
1084          /// </summary>
1085          private void RemoveLeastUsedEntries()
1086          {
1087              LinkedListNode<CacheEntryFromBuffer> nextNode = _lruCache.First;
1088  
1089              while (nextNode != null && _currentTimestamp - nextNode.Value.CacheTimestamp >= MinDeltaForRemoval)
1090              {
1091                  LinkedListNode<CacheEntryFromBuffer> toRemove = nextNode;
1092                  nextNode = nextNode.Next;
1093                  _cacheFromBuffer.Remove(toRemove.Value.Key);
1094                  _lruCache.Remove(toRemove);
1095  
1096                  if (toRemove.Value.Key.IsImage)
1097                  {
1098                      toRemove.Value.ImageArray.Dispose();
1099                  }
1100                  else
1101                  {
1102                      toRemove.Value.TextureArray.Dispose();
1103                  }
1104              }
1105          }
1106  
1107          /// <summary>
1108          /// Removes all cached texture arrays matching the specified texture pool.
1109          /// </summary>
1110          /// <param name="pool">Texture pool</param>
1111          public void RemoveAllWithPool<T>(IPool<T> pool)
1112          {
1113              List<CacheEntryFromPoolKey> keysToRemove = null;
1114  
1115              foreach ((CacheEntryFromPoolKey key, CacheEntry entry) in _cacheFromPool)
1116              {
1117                  if (key.MatchesPool(pool))
1118                  {
1119                      (keysToRemove ??= new()).Add(key);
1120  
1121                      if (key.IsImage)
1122                      {
1123                          entry.ImageArray.Dispose();
1124                      }
1125                      else
1126                      {
1127                          entry.TextureArray.Dispose();
1128                      }
1129                  }
1130              }
1131  
1132              if (keysToRemove != null)
1133              {
1134                  foreach (CacheEntryFromPoolKey key in keysToRemove)
1135                  {
1136                      _cacheFromPool.Remove(key);
1137                  }
1138              }
1139          }
1140  
1141          /// <summary>
1142          /// Checks if a handle indicates the binding should have all its textures sourced directly from a pool.
1143          /// </summary>
1144          /// <param name="handle">Handle to check</param>
1145          /// <returns>True if the handle represents direct pool access, false otherwise</returns>
1146          private static bool IsDirectHandleType(int handle)
1147          {
1148              (_, _, TextureHandleType type) = TextureHandle.UnpackOffsets(handle);
1149  
1150              return type == TextureHandleType.Direct;
1151          }
1152      }
1153  }