/ src / Ryujinx.Graphics.Gpu / Image / Texture.cs
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  }