Texture.cs
1 using Ryujinx.Common.Logging; 2 using Ryujinx.Common.Memory; 3 using Ryujinx.Graphics.GAL; 4 using Ryujinx.Graphics.Gpu.Memory; 5 using Ryujinx.Graphics.Texture; 6 using Ryujinx.Graphics.Texture.Astc; 7 using Ryujinx.Memory; 8 using Ryujinx.Memory.Range; 9 using System; 10 using System.Collections.Generic; 11 using System.Diagnostics; 12 using System.Linq; 13 using System.Numerics; 14 15 namespace Ryujinx.Graphics.Gpu.Image 16 { 17 /// <summary> 18 /// Represents a cached GPU texture. 19 /// </summary> 20 class Texture : IMultiRangeItem, IDisposable 21 { 22 // How many updates we need before switching to the byte-by-byte comparison 23 // modification check method. 24 // This method uses much more memory so we want to avoid it if possible. 25 private const int ByteComparisonSwitchThreshold = 4; 26 27 // Tuning for blacklisting textures from scaling when their data is updated from CPU. 28 // Each write adds the weight, each GPU modification subtracts 1. 29 // Exceeding the threshold blacklists the texture. 30 private const int ScaledSetWeight = 10; 31 private const int ScaledSetThreshold = 30; 32 33 private const int MinLevelsForForceAnisotropy = 5; 34 35 private struct TexturePoolOwner 36 { 37 public TexturePool Pool; 38 public int ID; 39 public ulong GpuAddress; 40 } 41 42 private GpuContext _context; 43 private PhysicalMemory _physicalMemory; 44 45 private SizeInfo _sizeInfo; 46 47 /// <summary> 48 /// Texture format. 49 /// </summary> 50 public Format Format => Info.FormatInfo.Format; 51 52 /// <summary> 53 /// Texture target. 54 /// </summary> 55 public Target Target { get; private set; } 56 57 /// <summary> 58 /// Texture width. 59 /// </summary> 60 public int Width { get; private set; } 61 62 /// <summary> 63 /// Texture height. 64 /// </summary> 65 public int Height { get; private set; } 66 67 /// <summary> 68 /// Texture information. 69 /// </summary> 70 public TextureInfo Info { get; private set; } 71 72 /// <summary> 73 /// Set when anisotropic filtering can be forced on the given texture. 74 /// </summary> 75 public bool CanForceAnisotropy { get; private set; } 76 77 /// <summary> 78 /// Host scale factor. 79 /// </summary> 80 public float ScaleFactor { get; private set; } 81 82 /// <summary> 83 /// Upscaling mode. Informs if a texture is scaled, or is eligible for scaling. 84 /// </summary> 85 public TextureScaleMode ScaleMode { get; private set; } 86 87 /// <summary> 88 /// Group that this texture belongs to. Manages read/write memory tracking. 89 /// </summary> 90 public TextureGroup Group { get; private set; } 91 92 /// <summary> 93 /// Set when a texture's GPU VA has ever been partially or fully unmapped. 94 /// This indicates that the range must be fully checked when matching the texture. 95 /// </summary> 96 public bool ChangedMapping { get; private set; } 97 98 /// <summary> 99 /// True if the data for this texture must always be flushed when an overlap appears. 100 /// This is useful if SetData is called directly on this texture, but the data is meant for a future texture. 101 /// </summary> 102 public bool AlwaysFlushOnOverlap { get; private set; } 103 104 /// <summary> 105 /// Increments when the host texture is swapped, or when the texture is removed from all pools. 106 /// </summary> 107 public int InvalidatedSequence { get; private set; } 108 109 private int _depth; 110 private int _layers; 111 public int FirstLayer { get; private set; } 112 public int FirstLevel { get; private set; } 113 114 private bool _hasData; 115 private bool _dirty = true; 116 private int _updateCount; 117 private byte[] _currentData; 118 119 private bool _modifiedStale = true; 120 121 private ITexture _arrayViewTexture; 122 private Target _arrayViewTarget; 123 124 private ITexture _flushHostTexture; 125 private ITexture _setHostTexture; 126 private int _scaledSetScore; 127 128 private Texture _viewStorage; 129 130 private List<Texture> _views; 131 132 /// <summary> 133 /// Host texture. 134 /// </summary> 135 public ITexture HostTexture { get; private set; } 136 137 /// <summary> 138 /// Intrusive linked list node used on the auto deletion texture cache. 139 /// </summary> 140 public LinkedListNode<Texture> CacheNode { get; set; } 141 142 /// <summary> 143 /// Entry for this texture in the short duration cache, if present. 144 /// </summary> 145 public ShortTextureCacheEntry ShortCacheEntry { get; set; } 146 147 /// <summary> 148 /// Whether this texture has ever been referenced by a pool. 149 /// </summary> 150 public bool HadPoolOwner { get; private set; } 151 152 /// <summary> 153 /// Physical memory ranges where the texture data is located. 154 /// </summary> 155 public MultiRange Range { get; private set; } 156 157 /// <summary> 158 /// Layer size in bytes. 159 /// </summary> 160 public int LayerSize => _sizeInfo.LayerSize; 161 162 /// <summary> 163 /// Texture size in bytes. 164 /// </summary> 165 public ulong Size => (ulong)_sizeInfo.TotalSize; 166 167 /// <summary> 168 /// Whether or not the texture belongs is a view. 169 /// </summary> 170 public bool IsView => _viewStorage != this; 171 172 /// <summary> 173 /// Whether or not this texture has views. 174 /// </summary> 175 public bool HasViews => _views.Count > 0; 176 177 private int _referenceCount; 178 private List<TexturePoolOwner> _poolOwners; 179 180 /// <summary> 181 /// Constructs a new instance of the cached GPU texture. 182 /// </summary> 183 /// <param name="context">GPU context that the texture belongs to</param> 184 /// <param name="physicalMemory">Physical memory where the texture is mapped</param> 185 /// <param name="info">Texture information</param> 186 /// <param name="sizeInfo">Size information of the texture</param> 187 /// <param name="range">Physical memory ranges where the texture data is located</param> 188 /// <param name="firstLayer">The first layer of the texture, or 0 if the texture has no parent</param> 189 /// <param name="firstLevel">The first mipmap level of the texture, or 0 if the texture has no parent</param> 190 /// <param name="scaleFactor">The floating point scale factor to initialize with</param> 191 /// <param name="scaleMode">The scale mode to initialize with</param> 192 private Texture( 193 GpuContext context, 194 PhysicalMemory physicalMemory, 195 TextureInfo info, 196 SizeInfo sizeInfo, 197 MultiRange range, 198 int firstLayer, 199 int firstLevel, 200 float scaleFactor, 201 TextureScaleMode scaleMode) 202 { 203 InitializeTexture(context, physicalMemory, info, sizeInfo, range); 204 205 FirstLayer = firstLayer; 206 FirstLevel = firstLevel; 207 208 ScaleFactor = scaleFactor; 209 ScaleMode = scaleMode; 210 211 InitializeData(true); 212 } 213 214 /// <summary> 215 /// Constructs a new instance of the cached GPU texture. 216 /// </summary> 217 /// <param name="context">GPU context that the texture belongs to</param> 218 /// <param name="physicalMemory">Physical memory where the texture is mapped</param> 219 /// <param name="info">Texture information</param> 220 /// <param name="sizeInfo">Size information of the texture</param> 221 /// <param name="range">Physical memory ranges where the texture data is located</param> 222 /// <param name="scaleMode">The scale mode to initialize with. If scaled, the texture's data is loaded immediately and scaled up</param> 223 public Texture( 224 GpuContext context, 225 PhysicalMemory physicalMemory, 226 TextureInfo info, 227 SizeInfo sizeInfo, 228 MultiRange range, 229 TextureScaleMode scaleMode) 230 { 231 ScaleFactor = 1f; // Texture is first loaded at scale 1x. 232 ScaleMode = scaleMode; 233 234 InitializeTexture(context, physicalMemory, info, sizeInfo, range); 235 } 236 237 /// <summary> 238 /// Common texture initialization method. 239 /// This sets the context, info and sizeInfo fields. 240 /// Other fields are initialized with their default values. 241 /// </summary> 242 /// <param name="context">GPU context that the texture belongs to</param> 243 /// <param name="physicalMemory">Physical memory where the texture is mapped</param> 244 /// <param name="info">Texture information</param> 245 /// <param name="sizeInfo">Size information of the texture</param> 246 /// <param name="range">Physical memory ranges where the texture data is located</param> 247 private void InitializeTexture( 248 GpuContext context, 249 PhysicalMemory physicalMemory, 250 TextureInfo info, 251 SizeInfo sizeInfo, 252 MultiRange range) 253 { 254 _context = context; 255 _physicalMemory = physicalMemory; 256 _sizeInfo = sizeInfo; 257 Range = range; 258 259 SetInfo(info); 260 261 _viewStorage = this; 262 263 _views = new List<Texture>(); 264 _poolOwners = new List<TexturePoolOwner>(); 265 } 266 267 /// <summary> 268 /// Initializes the data for a texture. Can optionally initialize the texture with or without data. 269 /// If the texture is a view, it will initialize memory tracking to be non-dirty. 270 /// </summary> 271 /// <param name="isView">True if the texture is a view, false otherwise</param> 272 /// <param name="withData">True if the texture is to be initialized with data</param> 273 public void InitializeData(bool isView, bool withData = false) 274 { 275 withData |= Group != null && Group.FlushIncompatibleOverlapsIfNeeded(); 276 277 if (withData) 278 { 279 Debug.Assert(!isView); 280 281 TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, ScaleFactor); 282 HostTexture = _context.Renderer.CreateTexture(createInfo); 283 284 SynchronizeMemory(); // Load the data. 285 if (ScaleMode == TextureScaleMode.Scaled) 286 { 287 SetScale(GraphicsConfig.ResScale); // Scale the data up. 288 } 289 } 290 else 291 { 292 _hasData = true; 293 294 if (!isView) 295 { 296 // Don't update this texture the next time we synchronize. 297 CheckModified(true); 298 299 if (ScaleMode == TextureScaleMode.Scaled) 300 { 301 // Don't need to start at 1x as there is no data to scale, just go straight to the target scale. 302 ScaleFactor = GraphicsConfig.ResScale; 303 } 304 305 TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, ScaleFactor); 306 HostTexture = _context.Renderer.CreateTexture(createInfo); 307 } 308 } 309 } 310 311 /// <summary> 312 /// Initialize a new texture group with this texture as storage. 313 /// </summary> 314 /// <param name="hasLayerViews">True if the texture will have layer views</param> 315 /// <param name="hasMipViews">True if the texture will have mip views</param> 316 /// <param name="incompatibleOverlaps">Groups that overlap with this one but are incompatible</param> 317 public void InitializeGroup(bool hasLayerViews, bool hasMipViews, List<TextureIncompatibleOverlap> incompatibleOverlaps) 318 { 319 Group = new TextureGroup(_context, _physicalMemory, this, incompatibleOverlaps); 320 321 Group.Initialize(ref _sizeInfo, hasLayerViews, hasMipViews); 322 } 323 324 /// <summary> 325 /// Create a texture view from this texture. 326 /// A texture view is defined as a child texture, from a sub-range of their parent texture. 327 /// For example, the initial layer and mipmap level of the view can be defined, so the texture 328 /// will start at the given layer/level of the parent texture. 329 /// </summary> 330 /// <param name="info">Child texture information</param> 331 /// <param name="sizeInfo">Child texture size information</param> 332 /// <param name="range">Physical memory ranges where the texture data is located</param> 333 /// <param name="firstLayer">Start layer of the child texture on the parent texture</param> 334 /// <param name="firstLevel">Start mipmap level of the child texture on the parent texture</param> 335 /// <returns>The child texture</returns> 336 public Texture CreateView(TextureInfo info, SizeInfo sizeInfo, MultiRange range, int firstLayer, int firstLevel) 337 { 338 Texture texture = new( 339 _context, 340 _physicalMemory, 341 info, 342 sizeInfo, 343 range, 344 FirstLayer + firstLayer, 345 FirstLevel + firstLevel, 346 ScaleFactor, 347 ScaleMode); 348 349 TextureCreateInfo createInfo = TextureCache.GetCreateInfo(info, _context.Capabilities, ScaleFactor); 350 texture.HostTexture = HostTexture.CreateView(createInfo, firstLayer, firstLevel); 351 352 _viewStorage.AddView(texture); 353 354 return texture; 355 } 356 357 /// <summary> 358 /// Adds a child texture to this texture. 359 /// </summary> 360 /// <param name="texture">The child texture</param> 361 private void AddView(Texture texture) 362 { 363 IncrementReferenceCount(); 364 365 _views.Add(texture); 366 367 texture._viewStorage = this; 368 369 Group.UpdateViews(_views, texture); 370 371 if (texture.Group != null && texture.Group != Group) 372 { 373 if (texture.Group.Storage == texture) 374 { 375 // This texture's group is no longer used. 376 Group.Inherit(texture.Group); 377 378 texture.Group.Dispose(); 379 } 380 } 381 382 texture.Group = Group; 383 } 384 385 /// <summary> 386 /// Removes a child texture from this texture. 387 /// </summary> 388 /// <param name="texture">The child texture</param> 389 private void RemoveView(Texture texture) 390 { 391 _views.Remove(texture); 392 393 Group.RemoveView(_views, texture); 394 395 texture._viewStorage = texture; 396 397 DecrementReferenceCount(); 398 } 399 400 /// <summary> 401 /// Replaces the texture's physical memory range. This forces tracking to regenerate. 402 /// </summary> 403 /// <param name="range">New physical memory range backing the texture</param> 404 public void ReplaceRange(MultiRange range) 405 { 406 Range = range; 407 408 Group.RangeChanged(); 409 } 410 411 /// <summary> 412 /// Create a copy dependency to a texture that is view compatible with this one. 413 /// When either texture is modified, the texture data will be copied to the other to keep them in sync. 414 /// This is essentially an emulated view, useful for handling multiple view parents or format incompatibility. 415 /// This also forces a copy on creation, to or from the given texture to get them in sync immediately. 416 /// </summary> 417 /// <param name="contained">The view compatible texture to create a dependency to</param> 418 /// <param name="layer">The base layer of the given texture relative to this one</param> 419 /// <param name="level">The base level of the given texture relative to this one</param> 420 /// <param name="copyTo">True if this texture is first copied to the given one, false for the opposite direction</param> 421 public void CreateCopyDependency(Texture contained, int layer, int level, bool copyTo) 422 { 423 if (contained.Group == Group) 424 { 425 return; 426 } 427 428 Group.CreateCopyDependency(contained, FirstLayer + layer, FirstLevel + level, copyTo); 429 } 430 431 /// <summary> 432 /// Registers when a texture has had its data set after being scaled, and 433 /// determines if it should be blacklisted from scaling to improve performance. 434 /// </summary> 435 /// <returns>True if setting data for a scaled texture is allowed, false if the texture has been blacklisted</returns> 436 private bool AllowScaledSetData() 437 { 438 _scaledSetScore += ScaledSetWeight; 439 440 if (_scaledSetScore >= ScaledSetThreshold) 441 { 442 BlacklistScale(); 443 444 return false; 445 } 446 447 return true; 448 } 449 450 /// <summary> 451 /// Blacklists this texture from being scaled. Resets its scale to 1 if needed. 452 /// </summary> 453 public void BlacklistScale() 454 { 455 ScaleMode = TextureScaleMode.Blacklisted; 456 SetScale(1f); 457 } 458 459 /// <summary> 460 /// Propagates the scale between this texture and another to ensure they have the same scale. 461 /// If one texture is blacklisted from scaling, the other will become blacklisted too. 462 /// </summary> 463 /// <param name="other">The other texture</param> 464 public void PropagateScale(Texture other) 465 { 466 if (other.ScaleMode == TextureScaleMode.Blacklisted || ScaleMode == TextureScaleMode.Blacklisted) 467 { 468 BlacklistScale(); 469 other.BlacklistScale(); 470 } 471 else 472 { 473 // Prefer the configured scale if present. If not, prefer the max. 474 float targetScale = GraphicsConfig.ResScale; 475 float sharedScale = (ScaleFactor == targetScale || other.ScaleFactor == targetScale) ? targetScale : Math.Max(ScaleFactor, other.ScaleFactor); 476 477 SetScale(sharedScale); 478 other.SetScale(sharedScale); 479 } 480 } 481 482 /// <summary> 483 /// Copy the host texture to a scaled one. If a texture is not provided, create it with the given scale. 484 /// </summary> 485 /// <param name="scale">Scale factor</param> 486 /// <param name="copy">True if the data should be copied to the texture, false otherwise</param> 487 /// <param name="storage">Texture to use instead of creating one</param> 488 /// <returns>A host texture containing a scaled version of this texture</returns> 489 private ITexture GetScaledHostTexture(float scale, bool copy, ITexture storage = null) 490 { 491 if (storage == null) 492 { 493 TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, scale); 494 storage = _context.Renderer.CreateTexture(createInfo); 495 } 496 497 if (copy) 498 { 499 HostTexture.CopyTo(storage, new Extents2D(0, 0, HostTexture.Width, HostTexture.Height), new Extents2D(0, 0, storage.Width, storage.Height), true); 500 } 501 502 return storage; 503 } 504 505 /// <summary> 506 /// Sets the Scale Factor on this texture, and immediately recreates it at the correct size. 507 /// When a texture is resized, a scaled copy is performed from the old texture to the new one, to ensure no data is lost. 508 /// If scale is equivalent, this only propagates the blacklisted/scaled mode. 509 /// If called on a view, its storage is resized instead. 510 /// When resizing storage, all texture views are recreated. 511 /// </summary> 512 /// <param name="scale">The new scale factor for this texture</param> 513 public void SetScale(float scale) 514 { 515 bool unscaled = ScaleMode == TextureScaleMode.Blacklisted || (ScaleMode == TextureScaleMode.Undesired && scale == 1); 516 TextureScaleMode newScaleMode = unscaled ? ScaleMode : TextureScaleMode.Scaled; 517 518 if (_viewStorage != this) 519 { 520 _viewStorage.ScaleMode = newScaleMode; 521 _viewStorage.SetScale(scale); 522 return; 523 } 524 525 if (ScaleFactor != scale) 526 { 527 Logger.Debug?.Print(LogClass.Gpu, $"Rescaling {Info.Width}x{Info.Height} {Info.FormatInfo.Format} to ({ScaleFactor} to {scale}). "); 528 529 ScaleFactor = scale; 530 531 ITexture newStorage = GetScaledHostTexture(ScaleFactor, true); 532 533 Logger.Debug?.Print(LogClass.Gpu, $" Copy performed: {HostTexture.Width}x{HostTexture.Height} to {newStorage.Width}x{newStorage.Height}"); 534 535 ReplaceStorage(newStorage); 536 537 // All views must be recreated against the new storage. 538 539 foreach (var view in _views) 540 { 541 Logger.Debug?.Print(LogClass.Gpu, $" Recreating view {Info.Width}x{Info.Height} {Info.FormatInfo.Format}."); 542 view.ScaleFactor = scale; 543 544 TextureCreateInfo viewCreateInfo = TextureCache.GetCreateInfo(view.Info, _context.Capabilities, scale); 545 ITexture newView = HostTexture.CreateView(viewCreateInfo, view.FirstLayer - FirstLayer, view.FirstLevel - FirstLevel); 546 547 view.ReplaceStorage(newView); 548 view.ScaleMode = newScaleMode; 549 } 550 } 551 552 if (ScaleMode != newScaleMode) 553 { 554 ScaleMode = newScaleMode; 555 556 foreach (var view in _views) 557 { 558 view.ScaleMode = newScaleMode; 559 } 560 } 561 } 562 563 /// <summary> 564 /// Checks if the memory for this texture was modified, and returns true if it was. 565 /// The modified flags are optionally consumed as a result. 566 /// </summary> 567 /// <param name="consume">True to consume the dirty flags and reprotect, false to leave them as is</param> 568 /// <returns>True if the texture was modified, false otherwise.</returns> 569 public bool CheckModified(bool consume) 570 { 571 return Group.CheckDirty(this, consume); 572 } 573 574 /// <summary> 575 /// Discards all data for this texture. 576 /// This clears all dirty flags and pending copies from other textures. 577 /// It should be used if the texture data will be fully overwritten by the next use. 578 /// </summary> 579 public void DiscardData() 580 { 581 Group.DiscardData(this); 582 583 _dirty = false; 584 } 585 586 /// <summary> 587 /// Synchronizes guest and host memory. 588 /// This will overwrite the texture data with the texture data on the guest memory, if a CPU 589 /// modification is detected. 590 /// Be aware that this can cause texture data written by the GPU to be lost, this is just a 591 /// one way copy (from CPU owned to GPU owned memory). 592 /// </summary> 593 public void SynchronizeMemory() 594 { 595 if (Target == Target.TextureBuffer) 596 { 597 return; 598 } 599 600 if (!_dirty) 601 { 602 return; 603 } 604 605 _dirty = false; 606 607 if (_hasData) 608 { 609 Group.SynchronizeMemory(this); 610 } 611 else 612 { 613 Group.CheckDirty(this, true); 614 SynchronizeFull(); 615 } 616 } 617 618 /// <summary> 619 /// Signal that this texture is dirty, indicating that the texture group must be checked. 620 /// </summary> 621 public void SignalGroupDirty() 622 { 623 _dirty = true; 624 } 625 626 /// <summary> 627 /// Signal that the modified state is dirty, indicating that the texture group should be notified when it changes. 628 /// </summary> 629 public void SignalModifiedDirty() 630 { 631 _modifiedStale = true; 632 } 633 634 /// <summary> 635 /// Fully synchronizes guest and host memory. 636 /// This will replace the entire texture with the data present in guest memory. 637 /// </summary> 638 public void SynchronizeFull() 639 { 640 ReadOnlySpan<byte> data = _physicalMemory.GetSpan(Range); 641 642 // If the host does not support ASTC compression, we need to do the decompression. 643 // The decompression is slow, so we want to avoid it as much as possible. 644 // This does a byte-by-byte check and skips the update if the data is equal in this case. 645 // This improves the speed on applications that overwrites ASTC data without changing anything. 646 if (Info.FormatInfo.Format.IsAstc() && !_context.Capabilities.SupportsAstcCompression) 647 { 648 if (_updateCount < ByteComparisonSwitchThreshold) 649 { 650 _updateCount++; 651 } 652 else 653 { 654 bool dataMatches = _currentData != null && data.SequenceEqual(_currentData); 655 if (dataMatches) 656 { 657 return; 658 } 659 660 _currentData = data.ToArray(); 661 } 662 } 663 664 MemoryOwner<byte> result = ConvertToHostCompatibleFormat(data); 665 666 if (ScaleFactor != 1f && AllowScaledSetData()) 667 { 668 // If needed, create a texture to load from 1x scale. 669 ITexture texture = _setHostTexture = GetScaledHostTexture(1f, false, _setHostTexture); 670 671 texture.SetData(result); 672 673 texture.CopyTo(HostTexture, new Extents2D(0, 0, texture.Width, texture.Height), new Extents2D(0, 0, HostTexture.Width, HostTexture.Height), true); 674 } 675 else 676 { 677 HostTexture.SetData(result); 678 } 679 680 _hasData = true; 681 } 682 683 /// <summary> 684 /// Uploads new texture data to the host GPU. 685 /// </summary> 686 /// <param name="data">New data</param> 687 public void SetData(MemoryOwner<byte> data) 688 { 689 BlacklistScale(); 690 691 Group.CheckDirty(this, true); 692 693 AlwaysFlushOnOverlap = true; 694 695 HostTexture.SetData(data); 696 697 _hasData = true; 698 } 699 700 /// <summary> 701 /// Uploads new texture data to the host GPU for a specific layer/level. 702 /// </summary> 703 /// <param name="data">New data</param> 704 /// <param name="layer">Target layer</param> 705 /// <param name="level">Target level</param> 706 public void SetData(MemoryOwner<byte> data, int layer, int level) 707 { 708 BlacklistScale(); 709 710 HostTexture.SetData(data, layer, level); 711 712 _currentData = null; 713 714 _hasData = true; 715 } 716 717 /// <summary> 718 /// Uploads new texture data to the host GPU for a specific layer/level and 2D sub-region. 719 /// </summary> 720 /// <param name="data">New data</param> 721 /// <param name="layer">Target layer</param> 722 /// <param name="level">Target level</param> 723 /// <param name="region">Target sub-region of the texture to update</param> 724 public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region) 725 { 726 BlacklistScale(); 727 728 HostTexture.SetData(data, layer, level, region); 729 730 _currentData = null; 731 732 _hasData = true; 733 } 734 735 /// <summary> 736 /// Converts texture data to a format and layout that is supported by the host GPU. 737 /// </summary> 738 /// <param name="data">Data to be converted</param> 739 /// <param name="level">Mip level to convert</param> 740 /// <param name="single">True to convert a single slice</param> 741 /// <returns>Converted data</returns> 742 public MemoryOwner<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data, int level = 0, bool single = false) 743 { 744 int width = Info.Width; 745 int height = Info.Height; 746 747 int depth = _depth; 748 int layers = single ? 1 : _layers; 749 int levels = single ? 1 : (Info.Levels - level); 750 751 width = Math.Max(width >> level, 1); 752 height = Math.Max(height >> level, 1); 753 depth = Math.Max(depth >> level, 1); 754 755 int sliceDepth = single ? 1 : depth; 756 757 MemoryOwner<byte> linear; 758 759 if (Info.IsLinear) 760 { 761 linear = LayoutConverter.ConvertLinearStridedToLinear( 762 width, 763 height, 764 Info.FormatInfo.BlockWidth, 765 Info.FormatInfo.BlockHeight, 766 Info.Stride, 767 Info.Stride, 768 Info.FormatInfo.BytesPerPixel, 769 data); 770 } 771 else 772 { 773 linear = LayoutConverter.ConvertBlockLinearToLinear( 774 width, 775 height, 776 depth, 777 sliceDepth, 778 levels, 779 layers, 780 Info.FormatInfo.BlockWidth, 781 Info.FormatInfo.BlockHeight, 782 Info.FormatInfo.BytesPerPixel, 783 Info.GobBlocksInY, 784 Info.GobBlocksInZ, 785 Info.GobBlocksInTileX, 786 _sizeInfo, 787 data); 788 } 789 790 MemoryOwner<byte> result = linear; 791 792 // Handle compressed cases not supported by the host: 793 // - ASTC is usually not supported on desktop cards. 794 // - BC4/BC5 is not supported on 3D textures. 795 if (!_context.Capabilities.SupportsAstcCompression && Format.IsAstc()) 796 { 797 using (result) 798 { 799 if (!AstcDecoder.TryDecodeToRgba8P( 800 result.Memory, 801 Info.FormatInfo.BlockWidth, 802 Info.FormatInfo.BlockHeight, 803 width, 804 height, 805 sliceDepth, 806 levels, 807 layers, 808 out MemoryOwner<byte> decoded)) 809 { 810 string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}"; 811 812 Logger.Debug?.Print(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.GpuAddress:X} ({texInfo})."); 813 } 814 815 if (GraphicsConfig.EnableTextureRecompression) 816 { 817 using (decoded) 818 { 819 return BCnEncoder.EncodeBC7(decoded.Memory, width, height, sliceDepth, levels, layers); 820 } 821 } 822 823 return decoded; 824 } 825 } 826 else if (!_context.Capabilities.SupportsEtc2Compression && Format.IsEtc2()) 827 { 828 switch (Format) 829 { 830 case Format.Etc2RgbaSrgb: 831 case Format.Etc2RgbaUnorm: 832 using (result) 833 { 834 return ETC2Decoder.DecodeRgba(result.Span, width, height, sliceDepth, levels, layers); 835 } 836 case Format.Etc2RgbPtaSrgb: 837 case Format.Etc2RgbPtaUnorm: 838 using (result) 839 { 840 return ETC2Decoder.DecodePta(result.Span, width, height, sliceDepth, levels, layers); 841 } 842 case Format.Etc2RgbSrgb: 843 case Format.Etc2RgbUnorm: 844 using (result) 845 { 846 return ETC2Decoder.DecodeRgb(result.Span, width, height, sliceDepth, levels, layers); 847 } 848 } 849 } 850 else if (!TextureCompatibility.HostSupportsBcFormat(Format, Target, _context.Capabilities)) 851 { 852 switch (Format) 853 { 854 case Format.Bc1RgbaSrgb: 855 case Format.Bc1RgbaUnorm: 856 using (result) 857 { 858 return BCnDecoder.DecodeBC1(result.Span, width, height, sliceDepth, levels, layers); 859 } 860 case Format.Bc2Srgb: 861 case Format.Bc2Unorm: 862 using (result) 863 { 864 return BCnDecoder.DecodeBC2(result.Span, width, height, sliceDepth, levels, layers); 865 } 866 case Format.Bc3Srgb: 867 case Format.Bc3Unorm: 868 using (result) 869 { 870 return BCnDecoder.DecodeBC3(result.Span, width, height, sliceDepth, levels, layers); 871 } 872 case Format.Bc4Snorm: 873 case Format.Bc4Unorm: 874 using (result) 875 { 876 return BCnDecoder.DecodeBC4(result.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm); 877 } 878 case Format.Bc5Snorm: 879 case Format.Bc5Unorm: 880 using (result) 881 { 882 return BCnDecoder.DecodeBC5(result.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm); 883 } 884 case Format.Bc6HSfloat: 885 case Format.Bc6HUfloat: 886 using (result) 887 { 888 return BCnDecoder.DecodeBC6(result.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat); 889 } 890 case Format.Bc7Srgb: 891 case Format.Bc7Unorm: 892 using (result) 893 { 894 return BCnDecoder.DecodeBC7(result.Span, width, height, sliceDepth, levels, layers); 895 } 896 } 897 } 898 else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm) 899 { 900 using (result) 901 { 902 var converted = PixelConverter.ConvertR4G4ToR4G4B4A4(result.Span, width); 903 904 if (_context.Capabilities.SupportsR4G4B4A4Format) 905 { 906 return converted; 907 } 908 else 909 { 910 using (converted) 911 { 912 return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(converted.Span, width); 913 } 914 } 915 } 916 } 917 else if (Format == Format.R4G4B4A4Unorm) 918 { 919 if (!_context.Capabilities.SupportsR4G4B4A4Format) 920 { 921 using (result) 922 { 923 return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Span, width); 924 } 925 } 926 } 927 else if (!_context.Capabilities.Supports5BitComponentFormat && Format.Is16BitPacked()) 928 { 929 switch (Format) 930 { 931 case Format.B5G6R5Unorm: 932 case Format.R5G6B5Unorm: 933 using (result) 934 { 935 return PixelConverter.ConvertR5G6B5ToR8G8B8A8(result.Span, width); 936 } 937 case Format.B5G5R5A1Unorm: 938 case Format.R5G5B5X1Unorm: 939 case Format.R5G5B5A1Unorm: 940 using (result) 941 { 942 return PixelConverter.ConvertR5G5B5ToR8G8B8A8(result.Span, width, Format == Format.R5G5B5X1Unorm); 943 } 944 case Format.A1B5G5R5Unorm: 945 using (result) 946 { 947 return PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result.Span, width); 948 } 949 case Format.R4G4B4A4Unorm: 950 using (result) 951 { 952 return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Span, width); 953 } 954 } 955 } 956 957 return result; 958 } 959 960 /// <summary> 961 /// Converts texture data from a format and layout that is supported by the host GPU, back into the intended format on the guest GPU. 962 /// </summary> 963 /// <param name="output">Optional output span to convert into</param> 964 /// <param name="data">Data to be converted</param> 965 /// <param name="level">Mip level to convert</param> 966 /// <param name="single">True to convert a single slice</param> 967 /// <returns>Converted data</returns> 968 public ReadOnlySpan<byte> ConvertFromHostCompatibleFormat(Span<byte> output, ReadOnlySpan<byte> data, int level = 0, bool single = false) 969 { 970 if (Target != Target.TextureBuffer) 971 { 972 int width = Info.Width; 973 int height = Info.Height; 974 975 int depth = _depth; 976 int layers = single ? 1 : _layers; 977 int levels = single ? 1 : (Info.Levels - level); 978 979 width = Math.Max(width >> level, 1); 980 height = Math.Max(height >> level, 1); 981 depth = Math.Max(depth >> level, 1); 982 983 if (Info.IsLinear) 984 { 985 data = LayoutConverter.ConvertLinearToLinearStrided( 986 output, 987 Info.Width, 988 Info.Height, 989 Info.FormatInfo.BlockWidth, 990 Info.FormatInfo.BlockHeight, 991 Info.Stride, 992 Info.FormatInfo.BytesPerPixel, 993 data); 994 } 995 else 996 { 997 data = LayoutConverter.ConvertLinearToBlockLinear( 998 output, 999 width, 1000 height, 1001 depth, 1002 single ? 1 : depth, 1003 levels, 1004 layers, 1005 Info.FormatInfo.BlockWidth, 1006 Info.FormatInfo.BlockHeight, 1007 Info.FormatInfo.BytesPerPixel, 1008 Info.GobBlocksInY, 1009 Info.GobBlocksInZ, 1010 Info.GobBlocksInTileX, 1011 _sizeInfo, 1012 data); 1013 } 1014 } 1015 1016 return data; 1017 } 1018 1019 /// <summary> 1020 /// Flushes the texture data. 1021 /// This causes the texture data to be written back to guest memory. 1022 /// If the texture was written by the GPU, this includes all modification made by the GPU 1023 /// up to this point. 1024 /// Be aware that this is an expensive operation, avoid calling it unless strictly needed. 1025 /// This may cause data corruption if the memory is already being used for something else on the CPU side. 1026 /// </summary> 1027 /// <param name="tracked">Whether or not the flush triggers write tracking. If it doesn't, the texture will not be blacklisted for scaling either.</param> 1028 /// <returns>True if data was flushed, false otherwise</returns> 1029 public bool FlushModified(bool tracked = true) 1030 { 1031 return TextureCompatibility.CanTextureFlush(Info, _context.Capabilities) && Group.FlushModified(this, tracked); 1032 } 1033 1034 /// <summary> 1035 /// Flushes the texture data. 1036 /// This causes the texture data to be written back to guest memory. 1037 /// If the texture was written by the GPU, this includes all modification made by the GPU 1038 /// up to this point. 1039 /// Be aware that this is an expensive operation, avoid calling it unless strictly needed. 1040 /// This may cause data corruption if the memory is already being used for something else on the CPU side. 1041 /// </summary> 1042 /// <param name="tracked">Whether or not the flush triggers write tracking. If it doesn't, the texture will not be blacklisted for scaling either.</param> 1043 public void Flush(bool tracked) 1044 { 1045 if (TextureCompatibility.CanTextureFlush(Info, _context.Capabilities)) 1046 { 1047 FlushTextureDataToGuest(tracked); 1048 } 1049 } 1050 1051 /// <summary> 1052 /// Gets a host texture to use for flushing the texture, at 1x resolution. 1053 /// If the HostTexture is already at 1x resolution, it is returned directly. 1054 /// </summary> 1055 /// <returns>The host texture to flush</returns> 1056 public ITexture GetFlushTexture() 1057 { 1058 ITexture texture = HostTexture; 1059 if (ScaleFactor != 1f) 1060 { 1061 // If needed, create a texture to flush back to host at 1x scale. 1062 texture = _flushHostTexture = GetScaledHostTexture(1f, true, _flushHostTexture); 1063 } 1064 1065 return texture; 1066 } 1067 1068 /// <summary> 1069 /// Gets data from the host GPU, and flushes it all to guest memory. 1070 /// </summary> 1071 /// <remarks> 1072 /// This method should be used to retrieve data that was modified by the host GPU. 1073 /// This is not cheap, avoid doing that unless strictly needed. 1074 /// When possible, the data is written directly into guest memory, rather than copied. 1075 /// </remarks> 1076 /// <param name="tracked">True if writing the texture data is tracked, false otherwise</param> 1077 /// <param name="texture">The specific host texture to flush. Defaults to this texture</param> 1078 public void FlushTextureDataToGuest(bool tracked, ITexture texture = null) 1079 { 1080 using WritableRegion region = _physicalMemory.GetWritableRegion(Range, tracked); 1081 1082 GetTextureDataFromGpu(region.Memory.Span, tracked, texture); 1083 } 1084 1085 /// <summary> 1086 /// Gets data from the host GPU. 1087 /// </summary> 1088 /// <remarks> 1089 /// This method should be used to retrieve data that was modified by the host GPU. 1090 /// This is not cheap, avoid doing that unless strictly needed. 1091 /// </remarks> 1092 /// <param name="output">An output span to place the texture data into</param> 1093 /// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param> 1094 /// <param name="texture">The specific host texture to flush. Defaults to this texture</param> 1095 private void GetTextureDataFromGpu(Span<byte> output, bool blacklist, ITexture texture = null) 1096 { 1097 PinnedSpan<byte> data; 1098 1099 if (texture != null) 1100 { 1101 data = texture.GetData(); 1102 } 1103 else 1104 { 1105 if (blacklist) 1106 { 1107 BlacklistScale(); 1108 data = HostTexture.GetData(); 1109 } 1110 else if (ScaleFactor != 1f) 1111 { 1112 float scale = ScaleFactor; 1113 SetScale(1f); 1114 data = HostTexture.GetData(); 1115 SetScale(scale); 1116 } 1117 else 1118 { 1119 data = HostTexture.GetData(); 1120 } 1121 } 1122 1123 ConvertFromHostCompatibleFormat(output, data.Get()); 1124 1125 data.Dispose(); 1126 } 1127 1128 /// <summary> 1129 /// Gets data from the host GPU for a single slice. 1130 /// </summary> 1131 /// <remarks> 1132 /// This method should be used to retrieve data that was modified by the host GPU. 1133 /// This is not cheap, avoid doing that unless strictly needed. 1134 /// </remarks> 1135 /// <param name="output">An output span to place the texture data into. If empty, one is generated</param> 1136 /// <param name="layer">The layer of the texture to flush</param> 1137 /// <param name="level">The level of the texture to flush</param> 1138 /// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param> 1139 /// <param name="texture">The specific host texture to flush. Defaults to this texture</param> 1140 public void GetTextureDataSliceFromGpu(Span<byte> output, int layer, int level, bool blacklist, ITexture texture = null) 1141 { 1142 PinnedSpan<byte> data; 1143 1144 if (texture != null) 1145 { 1146 data = texture.GetData(layer, level); 1147 } 1148 else 1149 { 1150 if (blacklist) 1151 { 1152 BlacklistScale(); 1153 data = HostTexture.GetData(layer, level); 1154 } 1155 else if (ScaleFactor != 1f) 1156 { 1157 float scale = ScaleFactor; 1158 SetScale(1f); 1159 data = HostTexture.GetData(layer, level); 1160 SetScale(scale); 1161 } 1162 else 1163 { 1164 data = HostTexture.GetData(layer, level); 1165 } 1166 } 1167 1168 ConvertFromHostCompatibleFormat(output, data.Get(), level, true); 1169 1170 data.Dispose(); 1171 } 1172 1173 /// <summary> 1174 /// This performs a strict comparison, used to check if this texture is equal to the one supplied. 1175 /// </summary> 1176 /// <param name="info">Texture information to compare against</param> 1177 /// <param name="flags">Comparison flags</param> 1178 /// <returns>A value indicating how well this texture matches the given info</returns> 1179 public TextureMatchQuality IsExactMatch(TextureInfo info, TextureSearchFlags flags) 1180 { 1181 bool forSampler = (flags & TextureSearchFlags.ForSampler) != 0; 1182 1183 TextureMatchQuality matchQuality = TextureCompatibility.FormatMatches(Info, info, forSampler, (flags & TextureSearchFlags.DepthAlias) != 0); 1184 1185 if (matchQuality == TextureMatchQuality.NoMatch) 1186 { 1187 return matchQuality; 1188 } 1189 1190 if (!TextureCompatibility.LayoutMatches(Info, info)) 1191 { 1192 return TextureMatchQuality.NoMatch; 1193 } 1194 1195 if (!TextureCompatibility.SizeMatches(Info, info, forSampler)) 1196 { 1197 return TextureMatchQuality.NoMatch; 1198 } 1199 1200 if ((flags & TextureSearchFlags.ForSampler) != 0) 1201 { 1202 if (!TextureCompatibility.SamplerParamsMatches(Info, info)) 1203 { 1204 return TextureMatchQuality.NoMatch; 1205 } 1206 } 1207 1208 if ((flags & TextureSearchFlags.ForCopy) != 0) 1209 { 1210 bool msTargetCompatible = Info.Target == Target.Texture2DMultisample && info.Target == Target.Texture2D; 1211 1212 if (!msTargetCompatible && !TextureCompatibility.TargetAndSamplesCompatible(Info, info)) 1213 { 1214 return TextureMatchQuality.NoMatch; 1215 } 1216 } 1217 else if (!TextureCompatibility.TargetAndSamplesCompatible(Info, info)) 1218 { 1219 return TextureMatchQuality.NoMatch; 1220 } 1221 1222 return Info.Levels == info.Levels ? matchQuality : TextureMatchQuality.NoMatch; 1223 } 1224 1225 /// <summary> 1226 /// Check if it's possible to create a view, with the given parameters, from this texture. 1227 /// </summary> 1228 /// <param name="info">Texture view information</param> 1229 /// <param name="range">Texture view physical memory ranges</param> 1230 /// <param name="exactSize">Indicates if the texture sizes must be exactly equal, or width is allowed to differ</param> 1231 /// <param name="layerSize">Layer size on the given texture</param> 1232 /// <param name="caps">Host GPU capabilities</param> 1233 /// <param name="firstLayer">Texture view initial layer on this texture</param> 1234 /// <param name="firstLevel">Texture view first mipmap level on this texture</param> 1235 /// <param name="flags">Texture search flags</param> 1236 /// <returns>The level of compatiblilty a view with the given parameters created from this texture has</returns> 1237 public TextureViewCompatibility IsViewCompatible( 1238 TextureInfo info, 1239 MultiRange range, 1240 bool exactSize, 1241 int layerSize, 1242 Capabilities caps, 1243 out int firstLayer, 1244 out int firstLevel, 1245 TextureSearchFlags flags = TextureSearchFlags.None) 1246 { 1247 TextureViewCompatibility result = TextureViewCompatibility.Full; 1248 1249 result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps, flags)); 1250 if (result != TextureViewCompatibility.Incompatible) 1251 { 1252 result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info, ref caps)); 1253 1254 bool bothMs = Info.Target.IsMultisample() && info.Target.IsMultisample(); 1255 if (bothMs && (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY)) 1256 { 1257 result = TextureViewCompatibility.Incompatible; 1258 } 1259 1260 if (result == TextureViewCompatibility.Full && Info.FormatInfo.Format != info.FormatInfo.Format && !_context.Capabilities.SupportsMismatchingViewFormat) 1261 { 1262 // AMD and Intel have a bug where the view format is always ignored; 1263 // they use the parent format instead. 1264 // Create a copy dependency to avoid this issue. 1265 1266 result = TextureViewCompatibility.CopyOnly; 1267 } 1268 } 1269 1270 firstLayer = 0; 1271 firstLevel = 0; 1272 1273 if (result == TextureViewCompatibility.Incompatible) 1274 { 1275 return TextureViewCompatibility.Incompatible; 1276 } 1277 1278 int offset = Range.FindOffset(range); 1279 1280 if (offset < 0 || !_sizeInfo.FindView(offset, out firstLayer, out firstLevel)) 1281 { 1282 return TextureViewCompatibility.LayoutIncompatible; 1283 } 1284 1285 if (!TextureCompatibility.ViewLayoutCompatible(Info, info, firstLevel)) 1286 { 1287 return TextureViewCompatibility.LayoutIncompatible; 1288 } 1289 1290 if (info.GetSlices() > 1 && LayerSize != layerSize) 1291 { 1292 return TextureViewCompatibility.LayoutIncompatible; 1293 } 1294 1295 result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSizeMatches(Info, info, exactSize, firstLevel)); 1296 result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSubImagesInBounds(Info, info, firstLayer, firstLevel)); 1297 1298 return result; 1299 } 1300 1301 /// <summary> 1302 /// Gets a texture of the specified target type from this texture. 1303 /// This can be used to get an array texture from a non-array texture and vice-versa. 1304 /// If this texture and the requested targets are equal, then this texture Host texture is returned directly. 1305 /// </summary> 1306 /// <param name="target">The desired target type</param> 1307 /// <returns>A view of this texture with the requested target, or null if the target is invalid for this texture</returns> 1308 public ITexture GetTargetTexture(Target target) 1309 { 1310 if (target == Target) 1311 { 1312 return HostTexture; 1313 } 1314 1315 if (_arrayViewTexture == null && IsSameDimensionsTarget(target)) 1316 { 1317 FormatInfo formatInfo = TextureCompatibility.ToHostCompatibleFormat(Info, _context.Capabilities); 1318 1319 TextureCreateInfo createInfo = new( 1320 Info.Width, 1321 Info.Height, 1322 target == Target.CubemapArray ? 6 : 1, 1323 Info.Levels, 1324 Info.Samples, 1325 formatInfo.BlockWidth, 1326 formatInfo.BlockHeight, 1327 formatInfo.BytesPerPixel, 1328 formatInfo.Format, 1329 Info.DepthStencilMode, 1330 target, 1331 Info.SwizzleR, 1332 Info.SwizzleG, 1333 Info.SwizzleB, 1334 Info.SwizzleA); 1335 1336 ITexture viewTexture = HostTexture.CreateView(createInfo, 0, 0); 1337 1338 _arrayViewTexture = viewTexture; 1339 _arrayViewTarget = target; 1340 1341 return viewTexture; 1342 } 1343 else if (_arrayViewTarget == target) 1344 { 1345 return _arrayViewTexture; 1346 } 1347 1348 return null; 1349 } 1350 1351 /// <summary> 1352 /// Determine if this texture can have anisotropic filtering forced. 1353 /// Filtered textures that we might want to force anisotropy on should have a lot of mip levels. 1354 /// </summary> 1355 /// <returns>True if anisotropic filtering can be forced, false otherwise</returns> 1356 private bool CanTextureForceAnisotropy() 1357 { 1358 if (!(Target == Target.Texture2D || Target == Target.Texture2DArray)) 1359 { 1360 return false; 1361 } 1362 1363 int maxSize = Math.Max(Info.Width, Info.Height); 1364 int maxLevels = BitOperations.Log2((uint)maxSize) + 1; 1365 1366 return Info.Levels >= Math.Min(MinLevelsForForceAnisotropy, maxLevels); 1367 } 1368 1369 /// <summary> 1370 /// Check if this texture and the specified target have the same number of dimensions. 1371 /// For the purposes of this comparison, 2D and 2D Multisample textures are not considered to have 1372 /// the same number of dimensions. Same for Cubemap and 3D textures. 1373 /// </summary> 1374 /// <param name="target">The target to compare with</param> 1375 /// <returns>True if both targets have the same number of dimensions, false otherwise</returns> 1376 private bool IsSameDimensionsTarget(Target target) 1377 { 1378 switch (Info.Target) 1379 { 1380 case Target.Texture1D: 1381 case Target.Texture1DArray: 1382 return target == Target.Texture1D || target == Target.Texture1DArray; 1383 case Target.Texture2D: 1384 case Target.Texture2DArray: 1385 return target == Target.Texture2D || target == Target.Texture2DArray; 1386 case Target.Cubemap: 1387 case Target.CubemapArray: 1388 return target == Target.Cubemap || target == Target.CubemapArray; 1389 case Target.Texture2DMultisample: 1390 case Target.Texture2DMultisampleArray: 1391 return target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray; 1392 case Target.Texture3D: 1393 return target == Target.Texture3D; 1394 default: 1395 return false; 1396 } 1397 } 1398 1399 /// <summary> 1400 /// Replaces view texture information. 1401 /// This should only be used for child textures with a parent. 1402 /// </summary> 1403 /// <param name="parent">The parent texture</param> 1404 /// <param name="info">The new view texture information</param> 1405 /// <param name="hostTexture">The new host texture</param> 1406 /// <param name="firstLayer">The first layer of the view</param> 1407 /// <param name="firstLevel">The first level of the view</param> 1408 public void ReplaceView(Texture parent, TextureInfo info, ITexture hostTexture, int firstLayer, int firstLevel) 1409 { 1410 IncrementReferenceCount(); 1411 parent._viewStorage.SynchronizeMemory(); 1412 1413 // If this texture has views, they must be given to the new parent. 1414 if (_views.Count > 0) 1415 { 1416 Texture[] viewCopy = _views.ToArray(); 1417 1418 foreach (Texture view in viewCopy) 1419 { 1420 TextureCreateInfo createInfo = TextureCache.GetCreateInfo(view.Info, _context.Capabilities, ScaleFactor); 1421 1422 ITexture newView = parent.HostTexture.CreateView(createInfo, view.FirstLayer + firstLayer, view.FirstLevel + firstLevel); 1423 1424 view.ReplaceView(parent, view.Info, newView, view.FirstLayer + firstLayer, view.FirstLevel + firstLevel); 1425 } 1426 } 1427 1428 ReplaceStorage(hostTexture); 1429 1430 if (_viewStorage != this) 1431 { 1432 _viewStorage.RemoveView(this); 1433 } 1434 1435 FirstLayer = parent.FirstLayer + firstLayer; 1436 FirstLevel = parent.FirstLevel + firstLevel; 1437 parent._viewStorage.AddView(this); 1438 1439 SetInfo(info); 1440 DecrementReferenceCount(); 1441 } 1442 1443 /// <summary> 1444 /// Sets the internal texture information structure. 1445 /// </summary> 1446 /// <param name="info">The new texture information</param> 1447 private void SetInfo(TextureInfo info) 1448 { 1449 Info = info; 1450 Target = info.Target; 1451 Width = info.Width; 1452 Height = info.Height; 1453 CanForceAnisotropy = CanTextureForceAnisotropy(); 1454 1455 _depth = info.GetDepth(); 1456 _layers = info.GetLayers(); 1457 } 1458 1459 /// <summary> 1460 /// Signals that the texture has been modified. 1461 /// </summary> 1462 public void SignalModified() 1463 { 1464 _scaledSetScore = Math.Max(0, _scaledSetScore - 1); 1465 1466 if (_modifiedStale || Group.HasCopyDependencies) 1467 { 1468 _modifiedStale = false; 1469 Group.SignalModified(this); 1470 } 1471 1472 _physicalMemory.TextureCache.Lift(this); 1473 } 1474 1475 /// <summary> 1476 /// Signals that a texture has been bound, or has been unbound. 1477 /// During this time, lazy copies will not clear the dirty flag. 1478 /// </summary> 1479 /// <param name="bound">True if the texture has been bound, false if it has been unbound</param> 1480 public void SignalModifying(bool bound) 1481 { 1482 if (bound) 1483 { 1484 _scaledSetScore = Math.Max(0, _scaledSetScore - 1); 1485 } 1486 1487 if (_modifiedStale || Group.HasCopyDependencies || Group.HasFlushBuffer) 1488 { 1489 _modifiedStale = false; 1490 Group.SignalModifying(this, bound); 1491 } 1492 1493 _physicalMemory.TextureCache.Lift(this); 1494 1495 if (bound) 1496 { 1497 IncrementReferenceCount(); 1498 } 1499 else 1500 { 1501 DecrementReferenceCount(); 1502 } 1503 } 1504 1505 /// <summary> 1506 /// Replaces the host texture, while disposing of the old one if needed. 1507 /// </summary> 1508 /// <param name="hostTexture">The new host texture</param> 1509 private void ReplaceStorage(ITexture hostTexture) 1510 { 1511 DisposeTextures(); 1512 1513 HostTexture = hostTexture; 1514 } 1515 1516 /// <summary> 1517 /// Determine if any of this texture's data overlaps with another. 1518 /// </summary> 1519 /// <param name="texture">The texture to check against</param> 1520 /// <param name="compatibility">The view compatibility of the two textures</param> 1521 /// <returns>True if any slice of the textures overlap, false otherwise</returns> 1522 public bool DataOverlaps(Texture texture, TextureViewCompatibility compatibility) 1523 { 1524 if (compatibility == TextureViewCompatibility.LayoutIncompatible && Info.GobBlocksInZ > 1 && Info.GobBlocksInZ == texture.Info.GobBlocksInZ) 1525 { 1526 // Allow overlapping slices of layout compatible 3D textures with matching GobBlocksInZ, as they are interleaved. 1527 return false; 1528 } 1529 1530 if (texture._sizeInfo.AllOffsets.Length == 1 && _sizeInfo.AllOffsets.Length == 1) 1531 { 1532 return Range.OverlapsWith(texture.Range); 1533 } 1534 1535 MultiRange otherRange = texture.Range; 1536 1537 IEnumerable<MultiRange> regions = _sizeInfo.AllRegions().Select((region) => Range.Slice((ulong)region.Offset, (ulong)region.Size)); 1538 IEnumerable<MultiRange> otherRegions = texture._sizeInfo.AllRegions().Select((region) => otherRange.Slice((ulong)region.Offset, (ulong)region.Size)); 1539 1540 foreach (MultiRange region in regions) 1541 { 1542 foreach (MultiRange otherRegion in otherRegions) 1543 { 1544 if (region.OverlapsWith(otherRegion)) 1545 { 1546 return true; 1547 } 1548 } 1549 } 1550 1551 return false; 1552 } 1553 1554 /// <summary> 1555 /// Increments the texture reference count. 1556 /// </summary> 1557 public void IncrementReferenceCount() 1558 { 1559 _referenceCount++; 1560 } 1561 1562 /// <summary> 1563 /// Increments the reference count and records the given texture pool and ID as a pool owner. 1564 /// </summary> 1565 /// <param name="pool">The texture pool this texture has been added to</param> 1566 /// <param name="id">The ID of the reference to this texture in the pool</param> 1567 /// <param name="gpuVa">GPU VA of the pool reference</param> 1568 public void IncrementReferenceCount(TexturePool pool, int id, ulong gpuVa) 1569 { 1570 HadPoolOwner = true; 1571 1572 lock (_poolOwners) 1573 { 1574 _poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id, GpuAddress = gpuVa }); 1575 } 1576 1577 _referenceCount++; 1578 1579 if (ShortCacheEntry != null) 1580 { 1581 _physicalMemory.TextureCache.RemoveShortCache(this); 1582 } 1583 } 1584 1585 /// <summary> 1586 /// Indicates that the texture has one reference left, and will delete on reference decrement. 1587 /// </summary> 1588 /// <returns>True if there is one reference remaining, false otherwise</returns> 1589 public bool HasOneReference() 1590 { 1591 return _referenceCount == 1; 1592 } 1593 1594 /// <summary> 1595 /// Decrements the texture reference count. 1596 /// When the reference count hits zero, the texture may be deleted and can't be used anymore. 1597 /// </summary> 1598 /// <returns>True if the texture is now referenceless, false otherwise</returns> 1599 public bool DecrementReferenceCount() 1600 { 1601 int newRefCount = --_referenceCount; 1602 1603 if (newRefCount == 0) 1604 { 1605 if (_viewStorage != this) 1606 { 1607 _viewStorage.RemoveView(this); 1608 } 1609 1610 _physicalMemory.TextureCache.RemoveTextureFromCache(this); 1611 } 1612 1613 Debug.Assert(newRefCount >= 0); 1614 1615 DeleteIfNotUsed(); 1616 1617 return newRefCount <= 0; 1618 } 1619 1620 /// <summary> 1621 /// Decrements the texture reference count, also removing an associated pool owner reference. 1622 /// When the reference count hits zero, the texture may be deleted and can't be used anymore. 1623 /// </summary> 1624 /// <param name="pool">The texture pool this texture is being removed from</param> 1625 /// <param name="id">The ID of the reference to this texture in the pool</param> 1626 /// <returns>True if the texture is now referenceless, false otherwise</returns> 1627 public bool DecrementReferenceCount(TexturePool pool, int id = -1) 1628 { 1629 lock (_poolOwners) 1630 { 1631 int references = _poolOwners.RemoveAll(entry => entry.Pool == pool && entry.ID == id || id == -1); 1632 1633 if (references == 0) 1634 { 1635 // This reference has already been removed. 1636 return _referenceCount <= 0; 1637 } 1638 1639 Debug.Assert(references == 1); 1640 } 1641 1642 return DecrementReferenceCount(); 1643 } 1644 1645 /// <summary> 1646 /// Forcibly remove this texture from all pools that reference it. 1647 /// </summary> 1648 /// <param name="deferred">Indicates if the removal is being done from another thread.</param> 1649 public void RemoveFromPools(bool deferred) 1650 { 1651 lock (_poolOwners) 1652 { 1653 foreach (var owner in _poolOwners) 1654 { 1655 owner.Pool.ForceRemove(this, owner.ID, deferred); 1656 } 1657 1658 _poolOwners.Clear(); 1659 } 1660 1661 if (ShortCacheEntry != null && !ShortCacheEntry.IsAutoDelete && _context.IsGpuThread()) 1662 { 1663 // If this is called from another thread (unmapped), the short cache will 1664 // have to remove this texture on a future tick. 1665 1666 _physicalMemory.TextureCache.RemoveShortCache(this); 1667 } 1668 1669 InvalidatedSequence++; 1670 } 1671 1672 /// <summary> 1673 /// Queue updating texture mappings on the pool. Happens from another thread. 1674 /// </summary> 1675 public void UpdatePoolMappings() 1676 { 1677 ChangedMapping = true; 1678 1679 lock (_poolOwners) 1680 { 1681 ulong address = 0; 1682 1683 foreach (var owner in _poolOwners) 1684 { 1685 if (address == 0 || address == owner.GpuAddress) 1686 { 1687 address = owner.GpuAddress; 1688 1689 owner.Pool.QueueUpdateMapping(this, owner.ID); 1690 } 1691 else 1692 { 1693 // If there is a different GPU VA mapping, prefer the first and delete the others. 1694 owner.Pool.ForceRemove(this, owner.ID, true); 1695 } 1696 } 1697 1698 _poolOwners.Clear(); 1699 } 1700 1701 InvalidatedSequence++; 1702 } 1703 1704 /// <summary> 1705 /// Delete the texture if it is not used anymore. 1706 /// The texture is considered unused when the reference count is zero, 1707 /// and it has no child views. 1708 /// </summary> 1709 private void DeleteIfNotUsed() 1710 { 1711 // We can delete the texture as long it is not being used 1712 // in any cache (the reference count is 0 in this case), and 1713 // also all views that may be created from this texture were 1714 // already deleted (views count is 0). 1715 if (_referenceCount == 0 && _views.Count == 0) 1716 { 1717 Dispose(); 1718 } 1719 } 1720 1721 /// <summary> 1722 /// Performs texture disposal, deleting the texture. 1723 /// </summary> 1724 private void DisposeTextures() 1725 { 1726 InvalidatedSequence++; 1727 1728 _currentData = null; 1729 HostTexture.Release(); 1730 1731 _arrayViewTexture?.Release(); 1732 _arrayViewTexture = null; 1733 1734 _flushHostTexture?.Release(); 1735 _flushHostTexture = null; 1736 1737 _setHostTexture?.Release(); 1738 _setHostTexture = null; 1739 } 1740 1741 /// <summary> 1742 /// Called when the memory for this texture has been unmapped. 1743 /// Calls are from non-gpu threads. 1744 /// </summary> 1745 /// <param name="unmapRange">The range of memory being unmapped</param> 1746 public void Unmapped(MultiRange unmapRange) 1747 { 1748 ChangedMapping = true; 1749 1750 if (Group.Storage == this) 1751 { 1752 Group.Unmapped(); 1753 Group.ClearModified(unmapRange); 1754 } 1755 } 1756 1757 /// <summary> 1758 /// Performs texture disposal, deleting the texture. 1759 /// </summary> 1760 public void Dispose() 1761 { 1762 DisposeTextures(); 1763 1764 if (Group.Storage == this) 1765 { 1766 Group.Dispose(); 1767 } 1768 } 1769 } 1770 }