TextureCache.cs
1 using Ryujinx.Common; 2 using Ryujinx.Graphics.GAL; 3 using Ryujinx.Graphics.Gpu.Engine.Threed; 4 using Ryujinx.Graphics.Gpu.Engine.Twod; 5 using Ryujinx.Graphics.Gpu.Engine.Types; 6 using Ryujinx.Graphics.Gpu.Memory; 7 using Ryujinx.Graphics.Texture; 8 using Ryujinx.Memory.Range; 9 using System; 10 using System.Collections.Generic; 11 using System.Threading; 12 13 namespace Ryujinx.Graphics.Gpu.Image 14 { 15 /// <summary> 16 /// Texture cache. 17 /// </summary> 18 class TextureCache : IDisposable 19 { 20 private readonly struct OverlapInfo 21 { 22 public TextureViewCompatibility Compatibility { get; } 23 public int FirstLayer { get; } 24 public int FirstLevel { get; } 25 26 public OverlapInfo(TextureViewCompatibility compatibility, int firstLayer, int firstLevel) 27 { 28 Compatibility = compatibility; 29 FirstLayer = firstLayer; 30 FirstLevel = firstLevel; 31 } 32 } 33 34 private const int OverlapsBufferInitialCapacity = 10; 35 private const int OverlapsBufferMaxCapacity = 10000; 36 37 private readonly GpuContext _context; 38 private readonly PhysicalMemory _physicalMemory; 39 40 private readonly MultiRangeList<Texture> _textures; 41 private readonly HashSet<Texture> _partiallyMappedTextures; 42 43 private readonly ReaderWriterLockSlim _texturesLock; 44 45 private Texture[] _textureOverlaps; 46 private OverlapInfo[] _overlapInfo; 47 48 private readonly AutoDeleteCache _cache; 49 50 /// <summary> 51 /// Constructs a new instance of the texture manager. 52 /// </summary> 53 /// <param name="context">The GPU context that the texture manager belongs to</param> 54 /// <param name="physicalMemory">Physical memory where the textures managed by this cache are mapped</param> 55 public TextureCache(GpuContext context, PhysicalMemory physicalMemory) 56 { 57 _context = context; 58 _physicalMemory = physicalMemory; 59 60 _textures = new MultiRangeList<Texture>(); 61 _partiallyMappedTextures = new HashSet<Texture>(); 62 63 _texturesLock = new ReaderWriterLockSlim(); 64 65 _textureOverlaps = new Texture[OverlapsBufferInitialCapacity]; 66 _overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity]; 67 68 _cache = new AutoDeleteCache(); 69 } 70 71 /// <summary> 72 /// Initializes the cache, setting the maximum texture capacity for the specified GPU context. 73 /// </summary> 74 public void Initialize() 75 { 76 _cache.Initialize(_context); 77 } 78 79 /// <summary> 80 /// Handles marking of textures written to a memory region being (partially) remapped. 81 /// </summary> 82 /// <param name="sender">Sender object</param> 83 /// <param name="e">Event arguments</param> 84 public void MemoryUnmappedHandler(object sender, UnmapEventArgs e) 85 { 86 Texture[] overlaps = new Texture[10]; 87 int overlapCount; 88 89 MultiRange unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size); 90 91 _texturesLock.EnterReadLock(); 92 93 try 94 { 95 overlapCount = _textures.FindOverlaps(unmapped, ref overlaps); 96 } 97 finally 98 { 99 _texturesLock.ExitReadLock(); 100 } 101 102 if (overlapCount > 0) 103 { 104 for (int i = 0; i < overlapCount; i++) 105 { 106 overlaps[i].Unmapped(unmapped); 107 } 108 } 109 110 lock (_partiallyMappedTextures) 111 { 112 if (overlapCount > 0 || _partiallyMappedTextures.Count > 0) 113 { 114 e.AddRemapAction(() => 115 { 116 lock (_partiallyMappedTextures) 117 { 118 if (overlapCount > 0) 119 { 120 for (int i = 0; i < overlapCount; i++) 121 { 122 _partiallyMappedTextures.Add(overlaps[i]); 123 } 124 } 125 126 // Any texture that has been unmapped at any point or is partially unmapped 127 // should update their pool references after the remap completes. 128 129 foreach (var texture in _partiallyMappedTextures) 130 { 131 texture.UpdatePoolMappings(); 132 } 133 } 134 }); 135 } 136 } 137 } 138 139 /// <summary> 140 /// Determines if a given texture is eligible for upscaling from its info. 141 /// </summary> 142 /// <param name="info">The texture info to check</param> 143 /// <param name="withUpscale">True if the user of the texture would prefer it to be upscaled immediately</param> 144 /// <returns>True if eligible</returns> 145 private static TextureScaleMode IsUpscaleCompatible(TextureInfo info, bool withUpscale) 146 { 147 if ((info.Target == Target.Texture2D || info.Target == Target.Texture2DArray || info.Target == Target.Texture2DMultisample) && !info.FormatInfo.IsCompressed) 148 { 149 return UpscaleSafeMode(info) ? (withUpscale ? TextureScaleMode.Scaled : TextureScaleMode.Eligible) : TextureScaleMode.Undesired; 150 } 151 152 return TextureScaleMode.Blacklisted; 153 } 154 155 /// <summary> 156 /// Determines if a given texture is "safe" for upscaling from its info. 157 /// Note that this is different from being compatible - this elilinates targets that would have detrimental effects when scaled. 158 /// </summary> 159 /// <param name="info">The texture info to check</param> 160 /// <returns>True if safe</returns> 161 private static bool UpscaleSafeMode(TextureInfo info) 162 { 163 // While upscaling works for all targets defined by IsUpscaleCompatible, we additionally blacklist targets here that 164 // may have undesirable results (upscaling blur textures) or simply waste GPU resources (upscaling texture atlas). 165 166 if (info.Levels > 3) 167 { 168 // Textures with more than 3 levels are likely to be game textures, rather than render textures. 169 // Small textures with full mips are likely to be removed by the next check. 170 return false; 171 } 172 173 if (info.Width < 8 || info.Height < 8) 174 { 175 // Discount textures with small dimensions. 176 return false; 177 } 178 179 int widthAlignment = (info.IsLinear ? Constants.StrideAlignment : Constants.GobAlignment) / info.FormatInfo.BytesPerPixel; 180 181 if (!(info.FormatInfo.Format.IsDepthOrStencil() || info.FormatInfo.Components == 1)) 182 { 183 // Discount square textures that aren't depth-stencil like. (excludes game textures, cubemap faces, most 3D texture LUT, texture atlas) 184 // Detect if the texture is possibly square. Widths may be aligned, so to remove the uncertainty we align both the width and height. 185 186 bool possiblySquare = BitUtils.AlignUp(info.Width, widthAlignment) == BitUtils.AlignUp(info.Height, widthAlignment); 187 188 if (possiblySquare) 189 { 190 return false; 191 } 192 } 193 194 if (info.Height < 360) 195 { 196 int aspectWidth = (int)MathF.Ceiling((info.Height / 9f) * 16f); 197 int aspectMaxWidth = BitUtils.AlignUp(aspectWidth, widthAlignment); 198 int aspectMinWidth = BitUtils.AlignDown(aspectWidth, widthAlignment); 199 200 if (info.Width >= aspectMinWidth && info.Width <= aspectMaxWidth && info.Height < 360) 201 { 202 // Targets that are roughly 16:9 can only be rescaled if they're equal to or above 360p. (excludes blur and bloom textures) 203 return false; 204 } 205 } 206 207 if (info.Width == info.Height * info.Height) 208 { 209 // Possibly used for a "3D texture" drawn onto a 2D surface. 210 // Some games do this to generate a tone mapping LUT without rendering into 3D texture slices. 211 212 return false; 213 } 214 215 return true; 216 } 217 218 /// <summary> 219 /// Lifts the texture to the top of the AutoDeleteCache. This is primarily used to enforce that 220 /// data written to a target will be flushed to memory should the texture be deleted, but also 221 /// keeps rendered textures alive without a pool reference. 222 /// </summary> 223 /// <param name="texture">Texture to lift</param> 224 public void Lift(Texture texture) 225 { 226 _cache.Lift(texture); 227 } 228 229 /// <summary> 230 /// Attempts to update a texture's physical memory range. 231 /// Returns false if there is an existing texture that matches with the updated range. 232 /// </summary> 233 /// <param name="texture">Texture to update</param> 234 /// <param name="range">New physical memory range</param> 235 /// <returns>True if the mapping was updated, false otherwise</returns> 236 public bool UpdateMapping(Texture texture, MultiRange range) 237 { 238 // There cannot be an existing texture compatible with this mapping in the texture cache already. 239 int overlapCount; 240 241 _texturesLock.EnterReadLock(); 242 243 try 244 { 245 overlapCount = _textures.FindOverlaps(range, ref _textureOverlaps); 246 } 247 finally 248 { 249 _texturesLock.ExitReadLock(); 250 } 251 252 for (int i = 0; i < overlapCount; i++) 253 { 254 var other = _textureOverlaps[i]; 255 256 if (texture != other && 257 (texture.IsViewCompatible(other.Info, other.Range, true, other.LayerSize, _context.Capabilities, out _, out _) != TextureViewCompatibility.Incompatible || 258 other.IsViewCompatible(texture.Info, texture.Range, true, texture.LayerSize, _context.Capabilities, out _, out _) != TextureViewCompatibility.Incompatible)) 259 { 260 return false; 261 } 262 } 263 264 _texturesLock.EnterWriteLock(); 265 266 try 267 { 268 _textures.Remove(texture); 269 270 texture.ReplaceRange(range); 271 272 _textures.Add(texture); 273 } 274 finally 275 { 276 _texturesLock.ExitWriteLock(); 277 } 278 279 return true; 280 } 281 282 /// <summary> 283 /// Tries to find an existing texture, or create a new one if not found. 284 /// </summary> 285 /// <param name="memoryManager">GPU memory manager where the texture is mapped</param> 286 /// <param name="copyTexture">Copy texture to find or create</param> 287 /// <param name="offset">Offset to be added to the physical texture address</param> 288 /// <param name="formatInfo">Format information of the copy texture</param> 289 /// <param name="depthAlias">Indicates if aliasing between color and depth format should be allowed</param> 290 /// <param name="shouldCreate">Indicates if a new texture should be created if none is found on the cache</param> 291 /// <param name="preferScaling">Indicates if the texture should be scaled from the start</param> 292 /// <param name="sizeHint">A hint indicating the minimum used size for the texture</param> 293 /// <returns>The texture</returns> 294 public Texture FindOrCreateTexture( 295 MemoryManager memoryManager, 296 TwodTexture copyTexture, 297 ulong offset, 298 FormatInfo formatInfo, 299 bool depthAlias, 300 bool shouldCreate, 301 bool preferScaling, 302 Size sizeHint) 303 { 304 int gobBlocksInY = copyTexture.MemoryLayout.UnpackGobBlocksInY(); 305 int gobBlocksInZ = copyTexture.MemoryLayout.UnpackGobBlocksInZ(); 306 307 int width; 308 309 if (copyTexture.LinearLayout) 310 { 311 width = copyTexture.Stride / formatInfo.BytesPerPixel; 312 } 313 else 314 { 315 width = copyTexture.Width; 316 } 317 318 TextureInfo info = new( 319 copyTexture.Address.Pack() + offset, 320 GetMinimumWidthInGob(width, sizeHint.Width, formatInfo.BytesPerPixel, copyTexture.LinearLayout), 321 copyTexture.Height, 322 copyTexture.Depth, 323 1, 324 1, 325 1, 326 copyTexture.Stride, 327 copyTexture.LinearLayout, 328 gobBlocksInY, 329 gobBlocksInZ, 330 1, 331 Target.Texture2D, 332 formatInfo); 333 334 TextureSearchFlags flags = TextureSearchFlags.ForCopy; 335 336 if (depthAlias) 337 { 338 flags |= TextureSearchFlags.DepthAlias; 339 } 340 341 if (preferScaling) 342 { 343 flags |= TextureSearchFlags.WithUpscale; 344 } 345 346 if (!shouldCreate) 347 { 348 flags |= TextureSearchFlags.NoCreate; 349 } 350 351 Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0, sizeHint: sizeHint); 352 353 texture?.SynchronizeMemory(); 354 355 return texture; 356 } 357 358 /// <summary> 359 /// Tries to find an existing texture, or create a new one if not found. 360 /// </summary> 361 /// <param name="memoryManager">GPU memory manager where the texture is mapped</param> 362 /// <param name="formatInfo">Format of the texture</param> 363 /// <param name="gpuAddress">GPU virtual address of the texture</param> 364 /// <param name="xCount">Texture width in bytes</param> 365 /// <param name="yCount">Texture height</param> 366 /// <param name="stride">Texture stride if linear, otherwise ignored</param> 367 /// <param name="isLinear">Indicates if the texture is linear or block linear</param> 368 /// <param name="gobBlocksInY">GOB blocks in Y for block linear textures</param> 369 /// <param name="gobBlocksInZ">GOB blocks in Z for 3D block linear textures</param> 370 /// <returns>The texture</returns> 371 public Texture FindOrCreateTexture( 372 MemoryManager memoryManager, 373 FormatInfo formatInfo, 374 ulong gpuAddress, 375 int xCount, 376 int yCount, 377 int stride, 378 bool isLinear, 379 int gobBlocksInY, 380 int gobBlocksInZ) 381 { 382 TextureInfo info = new( 383 gpuAddress, 384 xCount / formatInfo.BytesPerPixel, 385 yCount, 386 1, 387 1, 388 1, 389 1, 390 stride, 391 isLinear, 392 gobBlocksInY, 393 gobBlocksInZ, 394 1, 395 Target.Texture2D, 396 formatInfo); 397 398 Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.ForCopy, info, 0, sizeHint: new Size(xCount, yCount, 1)); 399 400 texture?.SynchronizeMemory(); 401 402 return texture; 403 } 404 405 /// <summary> 406 /// Tries to find an existing texture, or create a new one if not found. 407 /// </summary> 408 /// <param name="memoryManager">GPU memory manager where the texture is mapped</param> 409 /// <param name="colorState">Color buffer texture to find or create</param> 410 /// <param name="layered">Indicates if the texture might be accessed with a non-zero layer index</param> 411 /// <param name="discard">Indicates that the sizeHint region's data will be overwritten</param> 412 /// <param name="samplesInX">Number of samples in the X direction, for MSAA</param> 413 /// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param> 414 /// <param name="sizeHint">A hint indicating the minimum used size for the texture</param> 415 /// <returns>The texture</returns> 416 public Texture FindOrCreateTexture( 417 MemoryManager memoryManager, 418 RtColorState colorState, 419 bool layered, 420 bool discard, 421 int samplesInX, 422 int samplesInY, 423 Size sizeHint) 424 { 425 bool isLinear = colorState.MemoryLayout.UnpackIsLinear(); 426 427 int gobBlocksInY = colorState.MemoryLayout.UnpackGobBlocksInY(); 428 int gobBlocksInZ = colorState.MemoryLayout.UnpackGobBlocksInZ(); 429 430 Target target; 431 432 if (colorState.MemoryLayout.UnpackIsTarget3D()) 433 { 434 target = Target.Texture3D; 435 } 436 else if ((samplesInX | samplesInY) != 1) 437 { 438 target = colorState.Depth > 1 && layered 439 ? Target.Texture2DMultisampleArray 440 : Target.Texture2DMultisample; 441 } 442 else 443 { 444 target = colorState.Depth > 1 && layered 445 ? Target.Texture2DArray 446 : Target.Texture2D; 447 } 448 449 FormatInfo formatInfo = colorState.Format.Convert(); 450 451 int width, stride; 452 453 // For linear textures, the width value is actually the stride. 454 // We can easily get the width by dividing the stride by the bpp, 455 // since the stride is the total number of bytes occupied by a 456 // line. The stride should also meet alignment constraints however, 457 // so the width we get here is the aligned width. 458 if (isLinear) 459 { 460 width = colorState.WidthOrStride / formatInfo.BytesPerPixel; 461 stride = colorState.WidthOrStride; 462 } 463 else 464 { 465 width = colorState.WidthOrStride; 466 stride = 0; 467 } 468 469 TextureInfo info = new( 470 colorState.Address.Pack(), 471 GetMinimumWidthInGob(width, sizeHint.Width, formatInfo.BytesPerPixel, isLinear), 472 colorState.Height, 473 colorState.Depth, 474 1, 475 samplesInX, 476 samplesInY, 477 stride, 478 isLinear, 479 gobBlocksInY, 480 gobBlocksInZ, 481 1, 482 target, 483 formatInfo); 484 485 int layerSize = !isLinear ? colorState.LayerSize * 4 : 0; 486 487 var flags = TextureSearchFlags.WithUpscale; 488 489 if (discard) 490 { 491 flags |= TextureSearchFlags.DiscardData; 492 } 493 494 Texture texture = FindOrCreateTexture(memoryManager, flags, info, layerSize, sizeHint: sizeHint); 495 496 texture?.SynchronizeMemory(); 497 498 return texture; 499 } 500 501 /// <summary> 502 /// Tries to find an existing texture, or create a new one if not found. 503 /// </summary> 504 /// <param name="memoryManager">GPU memory manager where the texture is mapped</param> 505 /// <param name="dsState">Depth-stencil buffer texture to find or create</param> 506 /// <param name="size">Size of the depth-stencil texture</param> 507 /// <param name="layered">Indicates if the texture might be accessed with a non-zero layer index</param> 508 /// <param name="discard">Indicates that the sizeHint region's data will be overwritten</param> 509 /// <param name="samplesInX">Number of samples in the X direction, for MSAA</param> 510 /// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param> 511 /// <param name="sizeHint">A hint indicating the minimum used size for the texture</param> 512 /// <returns>The texture</returns> 513 public Texture FindOrCreateTexture( 514 MemoryManager memoryManager, 515 RtDepthStencilState dsState, 516 Size3D size, 517 bool layered, 518 bool discard, 519 int samplesInX, 520 int samplesInY, 521 Size sizeHint) 522 { 523 int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY(); 524 int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ(); 525 526 layered &= size.UnpackIsLayered(); 527 528 Target target; 529 530 if ((samplesInX | samplesInY) != 1) 531 { 532 target = size.Depth > 1 && layered 533 ? Target.Texture2DMultisampleArray 534 : Target.Texture2DMultisample; 535 } 536 else 537 { 538 target = size.Depth > 1 && layered 539 ? Target.Texture2DArray 540 : Target.Texture2D; 541 } 542 543 FormatInfo formatInfo = dsState.Format.Convert(); 544 545 TextureInfo info = new( 546 dsState.Address.Pack(), 547 GetMinimumWidthInGob(size.Width, sizeHint.Width, formatInfo.BytesPerPixel, false), 548 size.Height, 549 size.Depth, 550 1, 551 samplesInX, 552 samplesInY, 553 0, 554 false, 555 gobBlocksInY, 556 gobBlocksInZ, 557 1, 558 target, 559 formatInfo); 560 561 var flags = TextureSearchFlags.WithUpscale; 562 563 if (discard) 564 { 565 flags |= TextureSearchFlags.DiscardData; 566 } 567 568 Texture texture = FindOrCreateTexture(memoryManager, flags, info, dsState.LayerSize * 4, sizeHint: sizeHint); 569 570 texture?.SynchronizeMemory(); 571 572 return texture; 573 } 574 575 /// <summary> 576 /// For block linear textures, gets the minimum width of the texture 577 /// that would still have the same number of GOBs per row as the original width. 578 /// </summary> 579 /// <param name="width">The possibly aligned texture width</param> 580 /// <param name="minimumWidth">The minimum width that the texture may have without losing data</param> 581 /// <param name="bytesPerPixel">Bytes per pixel of the texture format</param> 582 /// <param name="isLinear">True if the texture is linear, false for block linear</param> 583 /// <returns>The minimum width of the texture with the same amount of GOBs per row</returns> 584 private static int GetMinimumWidthInGob(int width, int minimumWidth, int bytesPerPixel, bool isLinear) 585 { 586 if (isLinear || (uint)minimumWidth >= (uint)width) 587 { 588 return width; 589 } 590 591 // Calculate the minimum possible that would not cause data loss 592 // and would be still within the same GOB (aligned size would be the same). 593 // This is useful for render and copy operations, where we don't know the 594 // exact width of the texture, but it doesn't matter, as long the texture is 595 // at least as large as the region being rendered or copied. 596 597 int alignment = 64 / bytesPerPixel; 598 int widthAligned = BitUtils.AlignUp(width, alignment); 599 600 return Math.Clamp(widthAligned - alignment + 1, minimumWidth, widthAligned); 601 } 602 603 /// <summary> 604 /// Determines if texture data should be fully discarded 605 /// based on the size hint region and whether it is set to be discarded. 606 /// </summary> 607 /// <param name="discard">Whether the size hint region should be discarded</param> 608 /// <param name="texture">The texture being discarded</param> 609 /// <param name="sizeHint">A hint indicating the minimum used size for the texture</param> 610 /// <returns>True if the data should be discarded, false otherwise</returns> 611 private static bool ShouldDiscard(bool discard, Texture texture, Size? sizeHint) 612 { 613 return discard && 614 texture.Info.DepthOrLayers == 1 && 615 sizeHint != null && 616 texture.Width <= sizeHint.Value.Width && 617 texture.Height <= sizeHint.Value.Height; 618 } 619 620 /// <summary> 621 /// Discards texture data if requested and possible. 622 /// </summary> 623 /// <param name="discard">Whether the size hint region should be discarded</param> 624 /// <param name="texture">The texture being discarded</param> 625 /// <param name="sizeHint">A hint indicating the minimum used size for the texture</param> 626 private static void DiscardIfNeeded(bool discard, Texture texture, Size? sizeHint) 627 { 628 if (ShouldDiscard(discard, texture, sizeHint)) 629 { 630 texture.DiscardData(); 631 } 632 } 633 634 /// <summary> 635 /// Tries to find an existing texture, or create a new one if not found. 636 /// </summary> 637 /// <param name="memoryManager">GPU memory manager where the texture is mapped</param> 638 /// <param name="flags">The texture search flags, defines texture comparison rules</param> 639 /// <param name="info">Texture information of the texture to be found or created</param> 640 /// <param name="layerSize">Size in bytes of a single texture layer</param> 641 /// <param name="sizeHint">A hint indicating the minimum used size for the texture</param> 642 /// <param name="range">Optional ranges of physical memory where the texture data is located</param> 643 /// <returns>The texture</returns> 644 public Texture FindOrCreateTexture( 645 MemoryManager memoryManager, 646 TextureSearchFlags flags, 647 TextureInfo info, 648 int layerSize = 0, 649 Size? sizeHint = null, 650 MultiRange? range = null) 651 { 652 bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0; 653 bool discard = (flags & TextureSearchFlags.DiscardData) != 0; 654 655 TextureScaleMode scaleMode = IsUpscaleCompatible(info, (flags & TextureSearchFlags.WithUpscale) != 0); 656 657 ulong address; 658 659 if (range != null) 660 { 661 address = range.Value.GetSubRange(0).Address; 662 } 663 else 664 { 665 address = memoryManager.Translate(info.GpuAddress); 666 667 // If the start address is unmapped, let's try to find a page of memory that is mapped. 668 if (address == MemoryManager.PteUnmapped) 669 { 670 // Make sure that the dimensions are valid before calculating the texture size. 671 if (info.Width < 1 || info.Height < 1 || info.Levels < 1) 672 { 673 return null; 674 } 675 676 if ((info.Target == Target.Texture3D || 677 info.Target == Target.Texture2DArray || 678 info.Target == Target.Texture2DMultisampleArray || 679 info.Target == Target.CubemapArray) && info.DepthOrLayers < 1) 680 { 681 return null; 682 } 683 684 ulong dataSize = (ulong)info.CalculateSizeInfo(layerSize).TotalSize; 685 686 address = memoryManager.TranslateFirstMapped(info.GpuAddress, dataSize); 687 } 688 689 // If address is still invalid, the texture is fully unmapped, so it has no data, just return null. 690 if (address == MemoryManager.PteUnmapped) 691 { 692 return null; 693 } 694 } 695 696 int sameAddressOverlapsCount; 697 698 _texturesLock.EnterReadLock(); 699 700 try 701 { 702 // Try to find a perfect texture match, with the same address and parameters. 703 sameAddressOverlapsCount = _textures.FindOverlaps(address, ref _textureOverlaps); 704 } 705 finally 706 { 707 _texturesLock.ExitReadLock(); 708 } 709 710 Texture texture = null; 711 712 long bestSequence = 0; 713 714 for (int index = 0; index < sameAddressOverlapsCount; index++) 715 { 716 Texture overlap = _textureOverlaps[index]; 717 718 TextureMatchQuality matchQuality = overlap.IsExactMatch(info, flags); 719 720 if (matchQuality != TextureMatchQuality.NoMatch) 721 { 722 // If the parameters match, we need to make sure the texture is mapped to the same memory regions. 723 if (range != null) 724 { 725 // If a range of memory was supplied, just check if the ranges match. 726 if (!overlap.Range.Equals(range.Value)) 727 { 728 continue; 729 } 730 } 731 else 732 { 733 // If no range was supplied, we can check if the GPU virtual address match. If they do, 734 // we know the textures are located at the same memory region. 735 // If they don't, it may still be mapped to the same physical region, so we 736 // do a more expensive check to tell if they are mapped into the same physical regions. 737 // If the GPU VA for the texture has ever been unmapped, then the range must be checked regardless. 738 if ((overlap.Info.GpuAddress != info.GpuAddress || overlap.ChangedMapping) && 739 !memoryManager.CompareRange(overlap.Range, info.GpuAddress)) 740 { 741 continue; 742 } 743 } 744 745 if (texture == null || overlap.Group.ModifiedSequence - bestSequence > 0) 746 { 747 texture = overlap; 748 bestSequence = overlap.Group.ModifiedSequence; 749 } 750 } 751 } 752 753 if (texture != null) 754 { 755 DiscardIfNeeded(discard, texture, sizeHint); 756 757 texture.SynchronizeMemory(); 758 759 return texture; 760 } 761 else if (flags.HasFlag(TextureSearchFlags.NoCreate)) 762 { 763 return null; 764 } 765 766 // Calculate texture sizes, used to find all overlapping textures. 767 SizeInfo sizeInfo = info.CalculateSizeInfo(layerSize); 768 769 ulong size = (ulong)sizeInfo.TotalSize; 770 bool partiallyMapped = false; 771 772 if (range == null) 773 { 774 range = memoryManager.GetPhysicalRegions(info.GpuAddress, size); 775 776 for (int i = 0; i < range.Value.Count; i++) 777 { 778 if (range.Value.GetSubRange(i).Address == MemoryManager.PteUnmapped) 779 { 780 partiallyMapped = true; 781 break; 782 } 783 } 784 } 785 786 // Find view compatible matches. 787 int overlapsCount = 0; 788 789 if (info.Target != Target.TextureBuffer) 790 { 791 _texturesLock.EnterReadLock(); 792 793 try 794 { 795 overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps); 796 } 797 finally 798 { 799 _texturesLock.ExitReadLock(); 800 } 801 } 802 803 if (_overlapInfo.Length != _textureOverlaps.Length) 804 { 805 Array.Resize(ref _overlapInfo, _textureOverlaps.Length); 806 } 807 808 // =============== Find Texture View of Existing Texture =============== 809 810 int fullyCompatible = 0; 811 812 // Evaluate compatibility of overlaps, add temporary references 813 int preferredOverlap = -1; 814 815 for (int index = 0; index < overlapsCount; index++) 816 { 817 Texture overlap = _textureOverlaps[index]; 818 TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible( 819 info, 820 range.Value, 821 isSamplerTexture, 822 sizeInfo.LayerSize, 823 _context.Capabilities, 824 out int firstLayer, 825 out int firstLevel, 826 flags); 827 828 if (overlapCompatibility >= TextureViewCompatibility.FormatAlias) 829 { 830 if (overlap.IsView) 831 { 832 overlapCompatibility = TextureViewCompatibility.CopyOnly; 833 } 834 else 835 { 836 fullyCompatible++; 837 838 if (preferredOverlap == -1 || overlap.Group.ModifiedSequence - bestSequence > 0) 839 { 840 preferredOverlap = index; 841 bestSequence = overlap.Group.ModifiedSequence; 842 } 843 } 844 } 845 846 _overlapInfo[index] = new OverlapInfo(overlapCompatibility, firstLayer, firstLevel); 847 overlap.IncrementReferenceCount(); 848 } 849 850 // Search through the overlaps to find a compatible view and establish any copy dependencies. 851 852 if (preferredOverlap != -1) 853 { 854 Texture overlap = _textureOverlaps[preferredOverlap]; 855 OverlapInfo oInfo = _overlapInfo[preferredOverlap]; 856 857 bool aliased = oInfo.Compatibility == TextureViewCompatibility.FormatAlias; 858 859 if (!isSamplerTexture) 860 { 861 // If this is not a sampler texture, the size might be different from the requested size, 862 // so we need to make sure the texture information has the correct size for this base texture, 863 // before creating the view. 864 865 info = info.CreateInfoForLevelView(overlap, oInfo.FirstLevel, aliased); 866 } 867 else if (aliased) 868 { 869 // The format must be changed to match the parent. 870 info = info.CreateInfoWithFormat(overlap.Info.FormatInfo); 871 } 872 873 texture = overlap.CreateView(info, sizeInfo, range.Value, oInfo.FirstLayer, oInfo.FirstLevel); 874 texture.SynchronizeMemory(); 875 } 876 else 877 { 878 for (int index = 0; index < overlapsCount; index++) 879 { 880 Texture overlap = _textureOverlaps[index]; 881 OverlapInfo oInfo = _overlapInfo[index]; 882 883 if (oInfo.Compatibility == TextureViewCompatibility.CopyOnly && fullyCompatible == 0) 884 { 885 // Only copy compatible. If there's another choice for a FULLY compatible texture, choose that instead. 886 887 texture = new Texture(_context, _physicalMemory, info, sizeInfo, range.Value, scaleMode); 888 889 // If the new texture is larger than the existing one, we need to fill the remaining space with CPU data, 890 // otherwise we only need the data that is copied from the existing texture, without loading the CPU data. 891 bool updateNewTexture = texture.Width > overlap.Width || texture.Height > overlap.Height; 892 893 texture.InitializeGroup(true, true, new List<TextureIncompatibleOverlap>()); 894 texture.InitializeData(false, updateNewTexture); 895 896 overlap.SynchronizeMemory(); 897 overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true); 898 break; 899 } 900 } 901 } 902 903 if (texture != null) 904 { 905 // This texture could be a view of multiple parent textures with different storages, even if it is a view. 906 // When a texture is created, make sure all possible dependencies to other textures are created as copies. 907 // (even if it could be fulfilled without a copy) 908 909 for (int index = 0; index < overlapsCount; index++) 910 { 911 Texture overlap = _textureOverlaps[index]; 912 OverlapInfo oInfo = _overlapInfo[index]; 913 914 if (oInfo.Compatibility <= TextureViewCompatibility.LayoutIncompatible) 915 { 916 if (!overlap.IsView && texture.DataOverlaps(overlap, oInfo.Compatibility)) 917 { 918 texture.Group.RegisterIncompatibleOverlap(new TextureIncompatibleOverlap(overlap.Group, oInfo.Compatibility), true); 919 } 920 } 921 else if (overlap.Group != texture.Group) 922 { 923 overlap.SynchronizeMemory(); 924 overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true); 925 } 926 } 927 928 texture.SynchronizeMemory(); 929 } 930 931 // =============== Create a New Texture =============== 932 933 // No match, create a new texture. 934 if (texture == null) 935 { 936 texture = new Texture(_context, _physicalMemory, info, sizeInfo, range.Value, scaleMode); 937 938 // Step 1: Find textures that are view compatible with the new texture. 939 // Any textures that are incompatible will contain garbage data, so they should be removed where possible. 940 941 int viewCompatible = 0; 942 fullyCompatible = 0; 943 bool setData = isSamplerTexture || overlapsCount == 0 || flags.HasFlag(TextureSearchFlags.ForCopy); 944 945 bool hasLayerViews = false; 946 bool hasMipViews = false; 947 948 var incompatibleOverlaps = new List<TextureIncompatibleOverlap>(); 949 950 for (int index = 0; index < overlapsCount; index++) 951 { 952 Texture overlap = _textureOverlaps[index]; 953 bool overlapInCache = overlap.CacheNode != null; 954 955 TextureViewCompatibility compatibility = texture.IsViewCompatible( 956 overlap.Info, 957 overlap.Range, 958 exactSize: true, 959 overlap.LayerSize, 960 _context.Capabilities, 961 out int firstLayer, 962 out int firstLevel); 963 964 if (overlap.IsView && compatibility == TextureViewCompatibility.Full) 965 { 966 compatibility = TextureViewCompatibility.CopyOnly; 967 } 968 969 if (compatibility > TextureViewCompatibility.LayoutIncompatible) 970 { 971 _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); 972 _textureOverlaps[index] = _textureOverlaps[viewCompatible]; 973 _textureOverlaps[viewCompatible] = overlap; 974 975 if (compatibility == TextureViewCompatibility.Full) 976 { 977 if (viewCompatible != fullyCompatible) 978 { 979 // Swap overlaps so that the fully compatible views have priority. 980 981 _overlapInfo[viewCompatible] = _overlapInfo[fullyCompatible]; 982 _textureOverlaps[viewCompatible] = _textureOverlaps[fullyCompatible]; 983 984 _overlapInfo[fullyCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); 985 _textureOverlaps[fullyCompatible] = overlap; 986 } 987 988 fullyCompatible++; 989 } 990 991 viewCompatible++; 992 993 hasLayerViews |= overlap.Info.GetSlices() < texture.Info.GetSlices(); 994 hasMipViews |= overlap.Info.Levels < texture.Info.Levels; 995 } 996 else 997 { 998 bool dataOverlaps = texture.DataOverlaps(overlap, compatibility); 999 1000 if (!overlap.IsView && dataOverlaps && !incompatibleOverlaps.Exists(incompatible => incompatible.Group == overlap.Group)) 1001 { 1002 incompatibleOverlaps.Add(new TextureIncompatibleOverlap(overlap.Group, compatibility)); 1003 } 1004 1005 bool removeOverlap; 1006 bool modified = overlap.CheckModified(false); 1007 1008 if (overlapInCache || !setData) 1009 { 1010 if (!dataOverlaps) 1011 { 1012 // Allow textures to overlap if their data does not actually overlap. 1013 // This typically happens when mip level subranges of a layered texture are used. (each texture fills the gaps of the others) 1014 continue; 1015 } 1016 1017 // The overlap texture is going to contain garbage data after we draw, or is generally incompatible. 1018 // The texture group will obtain copy dependencies for any subresources that are compatible between the two textures, 1019 // but sometimes its data must be flushed regardless. 1020 1021 // If the texture was modified since its last use, then that data is probably meant to go into this texture. 1022 // If the data has been modified by the CPU, then it also shouldn't be flushed. 1023 1024 bool flush = overlapInCache && !modified && overlap.AlwaysFlushOnOverlap; 1025 1026 setData |= modified || flush; 1027 1028 if (overlapInCache) 1029 { 1030 if (flush || overlap.HadPoolOwner || overlap.IsView) 1031 { 1032 _cache.Remove(overlap, flush); 1033 } 1034 else 1035 { 1036 // This texture has only ever been referenced in the AutoDeleteCache. 1037 // Keep this texture alive with the short duration cache, as it may be used often but not sampled. 1038 1039 _cache.AddShortCache(overlap); 1040 } 1041 } 1042 1043 removeOverlap = modified; 1044 } 1045 else 1046 { 1047 // If an incompatible overlapping texture has been modified, then it's data is likely destined for this texture, 1048 // and the overlapped texture will contain garbage. In this case, it should be removed to save memory. 1049 removeOverlap = modified; 1050 } 1051 1052 if (removeOverlap && overlap.Info.Target != Target.TextureBuffer) 1053 { 1054 overlap.RemoveFromPools(false); 1055 } 1056 } 1057 } 1058 1059 texture.InitializeGroup(hasLayerViews, hasMipViews, incompatibleOverlaps); 1060 1061 // We need to synchronize before copying the old view data to the texture, 1062 // otherwise the copied data would be overwritten by a future synchronization. 1063 texture.InitializeData(false, setData && !ShouldDiscard(discard, texture, sizeHint)); 1064 1065 texture.Group.InitializeOverlaps(); 1066 1067 for (int index = 0; index < viewCompatible; index++) 1068 { 1069 Texture overlap = _textureOverlaps[index]; 1070 1071 OverlapInfo oInfo = _overlapInfo[index]; 1072 1073 if (overlap.Group == texture.Group) 1074 { 1075 // If the texture group is equal, then this texture (or its parent) is already a view. 1076 continue; 1077 } 1078 1079 // Note: If we allow different sizes for those overlaps, 1080 // we need to make sure that the "info" has the correct size for the parent texture here. 1081 // Since this is not allowed right now, we don't need to do it. 1082 1083 TextureInfo overlapInfo = overlap.Info; 1084 1085 if (texture.ScaleFactor != overlap.ScaleFactor) 1086 { 1087 // A bit tricky, our new texture may need to contain an existing texture that is upscaled, but isn't itself. 1088 // In that case, we prefer the higher scale only if our format is render-target-like, otherwise we scale the view down before copy. 1089 1090 texture.PropagateScale(overlap); 1091 } 1092 1093 if (oInfo.Compatibility != TextureViewCompatibility.Full) 1094 { 1095 // Copy only compatibility, or target texture is already a view. 1096 1097 overlap.SynchronizeMemory(); 1098 texture.CreateCopyDependency(overlap, oInfo.FirstLayer, oInfo.FirstLevel, false); 1099 } 1100 else 1101 { 1102 TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities, overlap.ScaleFactor); 1103 1104 ITexture newView = texture.HostTexture.CreateView(createInfo, oInfo.FirstLayer, oInfo.FirstLevel); 1105 1106 overlap.SynchronizeMemory(); 1107 1108 overlap.HostTexture.CopyTo(newView, 0, 0); 1109 1110 overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel); 1111 } 1112 } 1113 1114 texture.SynchronizeMemory(); 1115 } 1116 1117 // Sampler textures are managed by the texture pool, all other textures 1118 // are managed by the auto delete cache. 1119 if (!isSamplerTexture) 1120 { 1121 _cache.Add(texture); 1122 } 1123 1124 _texturesLock.EnterWriteLock(); 1125 1126 try 1127 { 1128 _textures.Add(texture); 1129 } 1130 finally 1131 { 1132 _texturesLock.ExitWriteLock(); 1133 } 1134 1135 if (partiallyMapped) 1136 { 1137 lock (_partiallyMappedTextures) 1138 { 1139 _partiallyMappedTextures.Add(texture); 1140 } 1141 } 1142 1143 ShrinkOverlapsBufferIfNeeded(); 1144 1145 for (int i = 0; i < overlapsCount; i++) 1146 { 1147 _textureOverlaps[i].DecrementReferenceCount(); 1148 } 1149 1150 return texture; 1151 } 1152 1153 /// <summary> 1154 /// Attempt to find a texture on the short duration cache. 1155 /// </summary> 1156 /// <param name="descriptor">The texture descriptor</param> 1157 /// <returns>The texture if found, null otherwise</returns> 1158 public Texture FindShortCache(in TextureDescriptor descriptor) 1159 { 1160 return _cache.FindShortCache(descriptor); 1161 } 1162 1163 /// <summary> 1164 /// Tries to find an existing texture matching the given buffer copy destination. If none is found, returns null. 1165 /// </summary> 1166 /// <param name="memoryManager">GPU memory manager where the texture is mapped</param> 1167 /// <param name="gpuVa">GPU virtual address of the texture</param> 1168 /// <param name="bpp">Bytes per pixel</param> 1169 /// <param name="stride">If <paramref name="linear"/> is true, should have the texture stride, otherwise ignored</param> 1170 /// <param name="height">If <paramref name="linear"/> is false, should have the texture height, otherwise ignored</param> 1171 /// <param name="xCount">Number of pixels to be copied per line</param> 1172 /// <param name="yCount">Number of lines to be copied</param> 1173 /// <param name="linear">True if the texture has a linear layout, false otherwise</param> 1174 /// <param name="gobBlocksInY">If <paramref name="linear"/> is false, the amount of GOB blocks in the Y axis</param> 1175 /// <param name="gobBlocksInZ">If <paramref name="linear"/> is false, the amount of GOB blocks in the Z axis</param> 1176 /// <returns>A matching texture, or null if there is no match</returns> 1177 public Texture FindTexture( 1178 MemoryManager memoryManager, 1179 ulong gpuVa, 1180 int bpp, 1181 int stride, 1182 int height, 1183 int xCount, 1184 int yCount, 1185 bool linear, 1186 int gobBlocksInY, 1187 int gobBlocksInZ) 1188 { 1189 ulong address = memoryManager.Translate(gpuVa); 1190 1191 if (address == MemoryManager.PteUnmapped) 1192 { 1193 return null; 1194 } 1195 1196 int addressMatches; 1197 1198 _texturesLock.EnterReadLock(); 1199 1200 try 1201 { 1202 addressMatches = _textures.FindOverlaps(address, ref _textureOverlaps); 1203 } 1204 finally 1205 { 1206 _texturesLock.ExitReadLock(); 1207 } 1208 1209 Texture textureMatch = null; 1210 1211 for (int i = 0; i < addressMatches; i++) 1212 { 1213 Texture texture = _textureOverlaps[i]; 1214 FormatInfo format = texture.Info.FormatInfo; 1215 1216 if (texture.Info.DepthOrLayers > 1 || texture.Info.Levels > 1 || texture.Info.FormatInfo.IsCompressed) 1217 { 1218 // Don't support direct buffer copies to anything that isn't a single 2D image, uncompressed. 1219 continue; 1220 } 1221 1222 bool match; 1223 1224 if (linear) 1225 { 1226 // Size is not available for linear textures. Use the stride and end of the copy region instead. 1227 1228 match = texture.Info.IsLinear && texture.Info.Stride == stride && yCount == texture.Info.Height; 1229 } 1230 else 1231 { 1232 // Bpp may be a mismatch between the target texture and the param. 1233 // Due to the way linear strided and block layouts work, widths can be multiplied by Bpp for comparison. 1234 // Note: tex.Width is the aligned texture size. Prefer param.XCount, as the destination should be a texture with that exact size. 1235 1236 bool sizeMatch = xCount * bpp == texture.Info.Width * format.BytesPerPixel && height == texture.Info.Height; 1237 bool formatMatch = !texture.Info.IsLinear && 1238 texture.Info.GobBlocksInY == gobBlocksInY && 1239 texture.Info.GobBlocksInZ == gobBlocksInZ; 1240 1241 match = sizeMatch && formatMatch; 1242 } 1243 1244 if (match) 1245 { 1246 if (textureMatch == null) 1247 { 1248 textureMatch = texture; 1249 } 1250 else if (texture.Group != textureMatch.Group) 1251 { 1252 return null; // It's ambiguous which texture should match between multiple choices, so leave it up to the slow path. 1253 } 1254 } 1255 } 1256 1257 return textureMatch; 1258 } 1259 1260 /// <summary> 1261 /// Resizes the temporary buffer used for range list intersection results, if it has grown too much. 1262 /// </summary> 1263 private void ShrinkOverlapsBufferIfNeeded() 1264 { 1265 if (_textureOverlaps.Length > OverlapsBufferMaxCapacity) 1266 { 1267 Array.Resize(ref _textureOverlaps, OverlapsBufferMaxCapacity); 1268 } 1269 } 1270 1271 /// <summary> 1272 /// Gets a texture creation information from texture information. 1273 /// This can be used to create new host textures. 1274 /// </summary> 1275 /// <param name="info">Texture information</param> 1276 /// <param name="caps">GPU capabilities</param> 1277 /// <param name="scale">Texture scale factor, to be applied to the texture size</param> 1278 /// <returns>The texture creation information</returns> 1279 public static TextureCreateInfo GetCreateInfo(TextureInfo info, Capabilities caps, float scale) 1280 { 1281 FormatInfo formatInfo = TextureCompatibility.ToHostCompatibleFormat(info, caps); 1282 1283 if (info.Target == Target.TextureBuffer && !caps.SupportsSnormBufferTextureFormat) 1284 { 1285 // If the host does not support signed normalized formats, we use a signed integer format instead. 1286 // The shader will need the appropriate conversion code to compensate. 1287 switch (formatInfo.Format) 1288 { 1289 case Format.R8Snorm: 1290 formatInfo = new FormatInfo(Format.R8Sint, 1, 1, 1, 1); 1291 break; 1292 case Format.R16Snorm: 1293 formatInfo = new FormatInfo(Format.R16Sint, 1, 1, 2, 1); 1294 break; 1295 case Format.R8G8Snorm: 1296 formatInfo = new FormatInfo(Format.R8G8Sint, 1, 1, 2, 2); 1297 break; 1298 case Format.R16G16Snorm: 1299 formatInfo = new FormatInfo(Format.R16G16Sint, 1, 1, 4, 2); 1300 break; 1301 case Format.R8G8B8A8Snorm: 1302 formatInfo = new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4, 4); 1303 break; 1304 case Format.R16G16B16A16Snorm: 1305 formatInfo = new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8, 4); 1306 break; 1307 } 1308 } 1309 1310 int width = info.Width / info.SamplesInX; 1311 int height = info.Height / info.SamplesInY; 1312 1313 int depth = info.GetDepth() * info.GetLayers(); 1314 1315 if (scale != 1f) 1316 { 1317 width = (int)MathF.Ceiling(width * scale); 1318 height = (int)MathF.Ceiling(height * scale); 1319 } 1320 1321 return new TextureCreateInfo( 1322 width, 1323 height, 1324 depth, 1325 info.Levels, 1326 info.Samples, 1327 formatInfo.BlockWidth, 1328 formatInfo.BlockHeight, 1329 formatInfo.BytesPerPixel, 1330 formatInfo.Format, 1331 info.DepthStencilMode, 1332 info.Target, 1333 info.SwizzleR, 1334 info.SwizzleG, 1335 info.SwizzleB, 1336 info.SwizzleA); 1337 } 1338 1339 /// <summary> 1340 /// Removes a texture from the cache. 1341 /// </summary> 1342 /// <remarks> 1343 /// This only removes the texture from the internal list, not from the auto-deletion cache. 1344 /// It may still have live references after the removal. 1345 /// </remarks> 1346 /// <param name="texture">The texture to be removed</param> 1347 public void RemoveTextureFromCache(Texture texture) 1348 { 1349 _texturesLock.EnterWriteLock(); 1350 1351 try 1352 { 1353 _textures.Remove(texture); 1354 } 1355 finally 1356 { 1357 _texturesLock.ExitWriteLock(); 1358 } 1359 1360 lock (_partiallyMappedTextures) 1361 { 1362 _partiallyMappedTextures.Remove(texture); 1363 } 1364 } 1365 1366 /// <summary> 1367 /// Queries a texture's memory range and marks it as partially mapped or not. 1368 /// Partially mapped textures re-evaluate their memory range after each time GPU memory is mapped. 1369 /// </summary> 1370 /// <param name="memoryManager">GPU memory manager where the texture is mapped</param> 1371 /// <param name="address">The virtual address of the texture</param> 1372 /// <param name="texture">The texture to be marked</param> 1373 /// <returns>The physical regions for the texture, found when evaluating whether the texture was partially mapped</returns> 1374 public MultiRange UpdatePartiallyMapped(MemoryManager memoryManager, ulong address, Texture texture) 1375 { 1376 MultiRange range; 1377 lock (_partiallyMappedTextures) 1378 { 1379 range = memoryManager.GetPhysicalRegions(address, texture.Size); 1380 bool partiallyMapped = false; 1381 1382 for (int i = 0; i < range.Count; i++) 1383 { 1384 if (range.GetSubRange(i).Address == MemoryManager.PteUnmapped) 1385 { 1386 partiallyMapped = true; 1387 break; 1388 } 1389 } 1390 1391 if (partiallyMapped) 1392 { 1393 _partiallyMappedTextures.Add(texture); 1394 } 1395 else 1396 { 1397 _partiallyMappedTextures.Remove(texture); 1398 } 1399 } 1400 1401 return range; 1402 } 1403 1404 /// <summary> 1405 /// Adds a texture to the short duration cache. This typically keeps it alive for two ticks. 1406 /// </summary> 1407 /// <param name="texture">Texture to add to the short cache</param> 1408 /// <param name="descriptor">Last used texture descriptor</param> 1409 public void AddShortCache(Texture texture, ref TextureDescriptor descriptor) 1410 { 1411 _cache.AddShortCache(texture, ref descriptor); 1412 } 1413 1414 /// <summary> 1415 /// Adds a texture to the short duration cache without a descriptor. This typically keeps it alive for two ticks. 1416 /// On expiry, it will be removed from the AutoDeleteCache. 1417 /// </summary> 1418 /// <param name="texture">Texture to add to the short cache</param> 1419 public void AddShortCache(Texture texture) 1420 { 1421 _cache.AddShortCache(texture); 1422 } 1423 1424 /// <summary> 1425 /// Removes a texture from the short duration cache. 1426 /// </summary> 1427 /// <param name="texture">Texture to remove from the short cache</param> 1428 public void RemoveShortCache(Texture texture) 1429 { 1430 _cache.RemoveShortCache(texture); 1431 } 1432 1433 /// <summary> 1434 /// Ticks periodic elements of the texture cache. 1435 /// </summary> 1436 public void Tick() 1437 { 1438 _cache.ProcessShortCache(); 1439 } 1440 1441 /// <summary> 1442 /// Disposes all textures and samplers in the cache. 1443 /// It's an error to use the texture cache after disposal. 1444 /// </summary> 1445 public void Dispose() 1446 { 1447 _texturesLock.EnterReadLock(); 1448 1449 try 1450 { 1451 foreach (Texture texture in _textures) 1452 { 1453 texture.Dispose(); 1454 } 1455 } 1456 finally 1457 { 1458 _texturesLock.ExitReadLock(); 1459 } 1460 } 1461 } 1462 }