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 }