/ src / Ryujinx.Graphics.Gpu / Image / TextureGroup.cs
TextureGroup.cs
   1  using Ryujinx.Common.Memory;
   2  using Ryujinx.Graphics.GAL;
   3  using Ryujinx.Graphics.Gpu.Memory;
   4  using Ryujinx.Graphics.Texture;
   5  using Ryujinx.Memory;
   6  using Ryujinx.Memory.Range;
   7  using Ryujinx.Memory.Tracking;
   8  using System;
   9  using System.Collections.Generic;
  10  using System.Runtime.CompilerServices;
  11  
  12  namespace Ryujinx.Graphics.Gpu.Image
  13  {
  14      /// <summary>
  15      /// An overlapping texture group with a given view compatibility.
  16      /// </summary>
  17      readonly struct TextureIncompatibleOverlap
  18      {
  19          public readonly TextureGroup Group;
  20          public readonly TextureViewCompatibility Compatibility;
  21  
  22          /// <summary>
  23          /// Create a new texture incompatible overlap.
  24          /// </summary>
  25          /// <param name="group">The group that is incompatible</param>
  26          /// <param name="compatibility">The view compatibility for the group</param>
  27          public TextureIncompatibleOverlap(TextureGroup group, TextureViewCompatibility compatibility)
  28          {
  29              Group = group;
  30              Compatibility = compatibility;
  31          }
  32      }
  33  
  34      /// <summary>
  35      /// A texture group represents a group of textures that belong to the same storage.
  36      /// When views are created, this class will track memory accesses for them separately.
  37      /// The group iteratively adds more granular tracking as views of different kinds are added.
  38      /// Note that a texture group can be absorbed into another when it becomes a view parent.
  39      /// </summary>
  40      class TextureGroup : IDisposable
  41      {
  42          /// <summary>
  43          /// Threshold of layers to force granular handles (and thus partial loading) on array/3D textures.
  44          /// </summary>
  45          private const int GranularLayerThreshold = 8;
  46  
  47          private delegate void HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false);
  48  
  49          /// <summary>
  50          /// The storage texture associated with this group.
  51          /// </summary>
  52          public Texture Storage { get; }
  53  
  54          /// <summary>
  55          /// Indicates if the texture has copy dependencies. If true, then all modifications
  56          /// must be signalled to the group, rather than skipping ones still to be flushed.
  57          /// </summary>
  58          public bool HasCopyDependencies { get; set; }
  59  
  60          /// <summary>
  61          /// Indicates if the texture group has a pre-emptive flush buffer.
  62          /// When one is present, the group must always be notified on unbind.
  63          /// </summary>
  64          public bool HasFlushBuffer => _flushBuffer != BufferHandle.Null;
  65  
  66          /// <summary>
  67          /// Indicates if this texture has any incompatible overlaps alive.
  68          /// </summary>
  69          public bool HasIncompatibleOverlaps => _incompatibleOverlaps.Count > 0;
  70  
  71          /// <summary>
  72          /// Number indicating the order this texture group was modified relative to others.
  73          /// </summary>
  74          public long ModifiedSequence { get; private set; }
  75  
  76          private readonly GpuContext _context;
  77          private readonly PhysicalMemory _physicalMemory;
  78  
  79          private int[] _allOffsets;
  80          private int[] _sliceSizes;
  81          private readonly bool _is3D;
  82          private readonly bool _isBuffer;
  83          private bool _hasMipViews;
  84          private bool _hasLayerViews;
  85          private readonly int _layers;
  86          private readonly int _levels;
  87  
  88          private MultiRange TextureRange => Storage.Range;
  89  
  90          /// <summary>
  91          /// The views array from the storage texture.
  92          /// </summary>
  93          private Texture[] _views;
  94          private TextureGroupHandle[] _handles;
  95          private bool[] _loadNeeded;
  96  
  97          /// <summary>
  98          /// Other texture groups that have incompatible overlaps with this one.
  99          /// </summary>
 100          private readonly List<TextureIncompatibleOverlap> _incompatibleOverlaps;
 101          private bool _incompatibleOverlapsDirty = true;
 102          private readonly bool _flushIncompatibleOverlaps;
 103  
 104          private BufferHandle _flushBuffer;
 105          private bool _flushBufferImported;
 106          private bool _flushBufferInvalid;
 107  
 108          /// <summary>
 109          /// Create a new texture group.
 110          /// </summary>
 111          /// <param name="context">GPU context that the texture group belongs to</param>
 112          /// <param name="physicalMemory">Physical memory where the <paramref name="storage"/> texture is mapped</param>
 113          /// <param name="storage">The storage texture for this group</param>
 114          /// <param name="incompatibleOverlaps">Groups that overlap with this one but are incompatible</param>
 115          public TextureGroup(GpuContext context, PhysicalMemory physicalMemory, Texture storage, List<TextureIncompatibleOverlap> incompatibleOverlaps)
 116          {
 117              Storage = storage;
 118              _context = context;
 119              _physicalMemory = physicalMemory;
 120  
 121              _is3D = storage.Info.Target == Target.Texture3D;
 122              _isBuffer = storage.Info.Target == Target.TextureBuffer;
 123              _layers = storage.Info.GetSlices();
 124              _levels = storage.Info.Levels;
 125  
 126              _incompatibleOverlaps = incompatibleOverlaps;
 127              _flushIncompatibleOverlaps = TextureCompatibility.IsFormatHostIncompatible(storage.Info, context.Capabilities);
 128          }
 129  
 130          /// <summary>
 131          /// Initialize a new texture group's dirty regions and offsets.
 132          /// </summary>
 133          /// <param name="size">Size info for the storage texture</param>
 134          /// <param name="hasLayerViews">True if the storage will have layer views</param>
 135          /// <param name="hasMipViews">True if the storage will have mip views</param>
 136          public void Initialize(ref SizeInfo size, bool hasLayerViews, bool hasMipViews)
 137          {
 138              _allOffsets = size.AllOffsets;
 139              _sliceSizes = size.SliceSizes;
 140  
 141              if (Storage.Target.HasDepthOrLayers() && Storage.Info.GetSlices() > GranularLayerThreshold)
 142              {
 143                  _hasLayerViews = true;
 144                  _hasMipViews = true;
 145              }
 146              else
 147              {
 148                  (_hasLayerViews, _hasMipViews) = PropagateGranularity(hasLayerViews, hasMipViews);
 149  
 150                  // If the texture is partially mapped, fully subdivide handles immediately.
 151  
 152                  MultiRange range = Storage.Range;
 153                  for (int i = 0; i < range.Count; i++)
 154                  {
 155                      if (range.GetSubRange(i).Address == MemoryManager.PteUnmapped)
 156                      {
 157                          _hasLayerViews = true;
 158                          _hasMipViews = true;
 159  
 160                          break;
 161                      }
 162                  }
 163              }
 164  
 165              RecalculateHandleRegions();
 166          }
 167  
 168          /// <summary>
 169          /// Initialize all incompatible overlaps in the list, registering them with the other texture groups
 170          /// and creating copy dependencies when partially compatible.
 171          /// </summary>
 172          public void InitializeOverlaps()
 173          {
 174              foreach (TextureIncompatibleOverlap overlap in _incompatibleOverlaps)
 175              {
 176                  if (overlap.Compatibility == TextureViewCompatibility.LayoutIncompatible)
 177                  {
 178                      CreateCopyDependency(overlap.Group, false);
 179                  }
 180  
 181                  overlap.Group._incompatibleOverlaps.Add(new TextureIncompatibleOverlap(this, overlap.Compatibility));
 182                  overlap.Group._incompatibleOverlapsDirty = true;
 183              }
 184  
 185              if (_incompatibleOverlaps.Count > 0)
 186              {
 187                  SignalIncompatibleOverlapModified();
 188              }
 189          }
 190  
 191          /// <summary>
 192          /// Signal that the group is dirty to all views and the storage.
 193          /// </summary>
 194          private void SignalAllDirty()
 195          {
 196              Storage.SignalGroupDirty();
 197              if (_views != null)
 198              {
 199                  foreach (Texture texture in _views)
 200                  {
 201                      texture.SignalGroupDirty();
 202                  }
 203              }
 204          }
 205  
 206          /// <summary>
 207          /// Signal that an incompatible overlap has been modified.
 208          /// If this group must flush incompatible overlaps, the group is signalled as dirty too.
 209          /// </summary>
 210          private void SignalIncompatibleOverlapModified()
 211          {
 212              _incompatibleOverlapsDirty = true;
 213  
 214              if (_flushIncompatibleOverlaps)
 215              {
 216                  SignalAllDirty();
 217              }
 218          }
 219  
 220  
 221          /// <summary>
 222          /// Flushes incompatible overlaps if the storage format requires it, and they have been modified.
 223          /// This allows unsupported host formats to accept data written to format aliased textures.
 224          /// </summary>
 225          /// <returns>True if data was flushed, false otherwise</returns>
 226          [MethodImpl(MethodImplOptions.AggressiveInlining)]
 227          public bool FlushIncompatibleOverlapsIfNeeded()
 228          {
 229              if (_flushIncompatibleOverlaps && _incompatibleOverlapsDirty)
 230              {
 231                  bool flushed = false;
 232  
 233                  foreach (var overlap in _incompatibleOverlaps)
 234                  {
 235                      flushed |= overlap.Group.Storage.FlushModified(true);
 236                  }
 237  
 238                  _incompatibleOverlapsDirty = false;
 239  
 240                  return flushed;
 241              }
 242              else
 243              {
 244                  return false;
 245              }
 246          }
 247  
 248          /// <summary>
 249          /// Check and optionally consume the dirty flags for a given texture.
 250          /// The state is shared between views of the same layers and levels.
 251          /// </summary>
 252          /// <param name="texture">The texture being used</param>
 253          /// <param name="consume">True to consume the dirty flags and reprotect, false to leave them as is</param>
 254          /// <returns>True if a flag was dirty, false otherwise</returns>
 255          public bool CheckDirty(Texture texture, bool consume)
 256          {
 257              bool dirty = false;
 258  
 259              EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
 260              {
 261                  for (int i = 0; i < regionCount; i++)
 262                  {
 263                      TextureGroupHandle group = _handles[baseHandle + i];
 264  
 265                      foreach (RegionHandle handle in group.Handles)
 266                      {
 267                          if (handle.Dirty)
 268                          {
 269                              if (consume)
 270                              {
 271                                  handle.Reprotect();
 272                              }
 273  
 274                              dirty = true;
 275                          }
 276                      }
 277                  }
 278              });
 279  
 280              return dirty;
 281          }
 282  
 283          /// <summary>
 284          /// Discards all data for a given texture.
 285          /// This clears all dirty flags and pending copies from other textures.
 286          /// </summary>
 287          /// <param name="texture">The texture being discarded</param>
 288          public void DiscardData(Texture texture)
 289          {
 290              EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
 291              {
 292                  for (int i = 0; i < regionCount; i++)
 293                  {
 294                      TextureGroupHandle group = _handles[baseHandle + i];
 295  
 296                      group.DiscardData();
 297                  }
 298              });
 299          }
 300  
 301          /// <summary>
 302          /// Synchronize memory for a given texture.
 303          /// If overlapping tracking handles are dirty, fully or partially synchronize the texture data.
 304          /// </summary>
 305          /// <param name="texture">The texture being used</param>
 306          public void SynchronizeMemory(Texture texture)
 307          {
 308              FlushIncompatibleOverlapsIfNeeded();
 309  
 310              EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
 311              {
 312                  bool dirty = false;
 313                  bool anyModified = false;
 314                  bool anyNotDirty = false;
 315  
 316                  for (int i = 0; i < regionCount; i++)
 317                  {
 318                      TextureGroupHandle group = _handles[baseHandle + i];
 319  
 320                      bool modified = group.Modified;
 321                      bool handleDirty = false;
 322                      bool handleUnmapped = false;
 323  
 324                      foreach (RegionHandle handle in group.Handles)
 325                      {
 326                          if (handle.Dirty)
 327                          {
 328                              handle.Reprotect();
 329                              handleDirty = true;
 330                          }
 331                          else
 332                          {
 333                              handleUnmapped |= handle.Unmapped;
 334                          }
 335                      }
 336  
 337                      // If the modified flag is still present, prefer the data written from gpu.
 338                      // A write from CPU will do a flush before writing its data, which should unset this.
 339                      if (modified)
 340                      {
 341                          handleDirty = false;
 342                      }
 343  
 344                      // Evaluate if any copy dependencies need to be fulfilled. A few rules:
 345                      // If the copy handle needs to be synchronized, prefer our own state.
 346                      // If we need to be synchronized and there is a copy present, prefer the copy.
 347  
 348                      if (group.NeedsCopy && group.Copy(_context))
 349                      {
 350                          anyModified |= true; // The copy target has been modified.
 351                          handleDirty = false;
 352                      }
 353                      else
 354                      {
 355                          anyModified |= modified;
 356                          dirty |= handleDirty;
 357                      }
 358  
 359                      if (group.NeedsCopy)
 360                      {
 361                          // The texture we copied from is still being written to. Copy from it again the next time this texture is used.
 362                          texture.SignalGroupDirty();
 363                      }
 364  
 365                      bool loadNeeded = handleDirty && !handleUnmapped;
 366  
 367                      anyNotDirty |= !loadNeeded;
 368                      _loadNeeded[baseHandle + i] = loadNeeded;
 369                  }
 370  
 371                  if (dirty)
 372                  {
 373                      if (anyNotDirty || (_handles.Length > 1 && (anyModified || split)))
 374                      {
 375                          // Partial texture invalidation. Only update the layers/levels with dirty flags of the storage.
 376  
 377                          SynchronizePartial(baseHandle, regionCount);
 378                      }
 379                      else
 380                      {
 381                          // Full texture invalidation.
 382  
 383                          texture.SynchronizeFull();
 384                      }
 385                  }
 386              });
 387          }
 388  
 389          /// <summary>
 390          /// Synchronize part of the storage texture, represented by a given range of handles.
 391          /// Only handles marked by the _loadNeeded array will be synchronized.
 392          /// </summary>
 393          /// <param name="baseHandle">The base index of the range of handles</param>
 394          /// <param name="regionCount">The number of handles to synchronize</param>
 395          private void SynchronizePartial(int baseHandle, int regionCount)
 396          {
 397              int spanEndIndex = -1;
 398              int spanBase = 0;
 399              ReadOnlySpan<byte> dataSpan = ReadOnlySpan<byte>.Empty;
 400  
 401              for (int i = 0; i < regionCount; i++)
 402              {
 403                  if (_loadNeeded[baseHandle + i])
 404                  {
 405                      var info = GetHandleInformation(baseHandle + i);
 406  
 407                      // Ensure the data for this handle is loaded in the span.
 408                      if (spanEndIndex <= i - 1)
 409                      {
 410                          spanEndIndex = i;
 411  
 412                          if (_is3D)
 413                          {
 414                              // Look ahead to see how many handles need to be loaded.
 415                              for (int j = i + 1; j < regionCount; j++)
 416                              {
 417                                  if (_loadNeeded[baseHandle + j])
 418                                  {
 419                                      spanEndIndex = j;
 420                                  }
 421                                  else
 422                                  {
 423                                      break;
 424                                  }
 425                              }
 426                          }
 427  
 428                          var endInfo = spanEndIndex == i ? info : GetHandleInformation(baseHandle + spanEndIndex);
 429  
 430                          spanBase = _allOffsets[info.Index];
 431                          int spanLast = _allOffsets[endInfo.Index + endInfo.Layers * endInfo.Levels - 1];
 432                          int endOffset = Math.Min(spanLast + _sliceSizes[endInfo.BaseLevel + endInfo.Levels - 1], (int)Storage.Size);
 433                          int size = endOffset - spanBase;
 434  
 435                          dataSpan = _physicalMemory.GetSpan(Storage.Range.Slice((ulong)spanBase, (ulong)size));
 436                      }
 437  
 438                      // Only one of these will be greater than 1, as partial sync is only called when there are sub-image views.
 439                      for (int layer = 0; layer < info.Layers; layer++)
 440                      {
 441                          for (int level = 0; level < info.Levels; level++)
 442                          {
 443                              int offsetIndex = GetOffsetIndex(info.BaseLayer + layer, info.BaseLevel + level);
 444                              int offset = _allOffsets[offsetIndex];
 445  
 446                              ReadOnlySpan<byte> data = dataSpan[(offset - spanBase)..];
 447  
 448                              MemoryOwner<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
 449  
 450                              Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level);
 451                          }
 452                      }
 453                  }
 454              }
 455          }
 456  
 457          /// <summary>
 458          /// Synchronize dependent textures, if any of them have deferred a copy from the given texture.
 459          /// </summary>
 460          /// <param name="texture">The texture to synchronize dependents of</param>
 461          public void SynchronizeDependents(Texture texture)
 462          {
 463              EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
 464              {
 465                  for (int i = 0; i < regionCount; i++)
 466                  {
 467                      TextureGroupHandle group = _handles[baseHandle + i];
 468  
 469                      group.SynchronizeDependents();
 470                  }
 471              });
 472          }
 473  
 474          /// <summary>
 475          /// Determines whether flushes in this texture group should be tracked.
 476          /// Incompatible overlaps may need data from this texture to flush tracked for it to be visible to them.
 477          /// </summary>
 478          /// <returns>True if flushes should be tracked, false otherwise</returns>
 479          private bool ShouldFlushTriggerTracking()
 480          {
 481              foreach (var overlap in _incompatibleOverlaps)
 482              {
 483                  if (overlap.Group._flushIncompatibleOverlaps)
 484                  {
 485                      return true;
 486                  }
 487              }
 488  
 489              return false;
 490          }
 491  
 492          /// <summary>
 493          /// Gets data from the host GPU, and flushes a slice to guest memory.
 494          /// </summary>
 495          /// <remarks>
 496          /// This method should be used to retrieve data that was modified by the host GPU.
 497          /// This is not cheap, avoid doing that unless strictly needed.
 498          /// When possible, the data is written directly into guest memory, rather than copied.
 499          /// </remarks>
 500          /// <param name="tracked">True if writing the texture data is tracked, false otherwise</param>
 501          /// <param name="sliceIndex">The index of the slice to flush</param>
 502          /// <param name="inBuffer">Whether the flushed texture data is up to date in the flush buffer</param>
 503          /// <param name="texture">The specific host texture to flush. Defaults to the storage texture</param>
 504          private void FlushTextureDataSliceToGuest(bool tracked, int sliceIndex, bool inBuffer, ITexture texture = null)
 505          {
 506              (int layer, int level) = GetLayerLevelForView(sliceIndex);
 507  
 508              int offset = _allOffsets[sliceIndex];
 509              int endOffset = Math.Min(offset + _sliceSizes[level], (int)Storage.Size);
 510              int size = endOffset - offset;
 511  
 512              using WritableRegion region = _physicalMemory.GetWritableRegion(Storage.Range.Slice((ulong)offset, (ulong)size), tracked);
 513  
 514              if (inBuffer)
 515              {
 516                  using PinnedSpan<byte> data = _context.Renderer.GetBufferData(_flushBuffer, offset, size);
 517  
 518                  Storage.ConvertFromHostCompatibleFormat(region.Memory.Span, data.Get(), level, true);
 519              }
 520              else
 521              {
 522                  Storage.GetTextureDataSliceFromGpu(region.Memory.Span, layer, level, tracked, texture);
 523              }
 524          }
 525  
 526          /// <summary>
 527          /// Gets and flushes a number of slices of the storage texture to guest memory.
 528          /// </summary>
 529          /// <param name="tracked">True if writing the texture data is tracked, false otherwise</param>
 530          /// <param name="sliceStart">The first slice to flush</param>
 531          /// <param name="sliceEnd">The slice to finish flushing on (exclusive)</param>
 532          /// <param name="inBuffer">Whether the flushed texture data is up to date in the flush buffer</param>
 533          /// <param name="texture">The specific host texture to flush. Defaults to the storage texture</param>
 534          private void FlushSliceRange(bool tracked, int sliceStart, int sliceEnd, bool inBuffer, ITexture texture = null)
 535          {
 536              for (int i = sliceStart; i < sliceEnd; i++)
 537              {
 538                  FlushTextureDataSliceToGuest(tracked, i, inBuffer, texture);
 539              }
 540          }
 541  
 542          /// <summary>
 543          /// Flush modified ranges for a given texture.
 544          /// </summary>
 545          /// <param name="texture">The texture being used</param>
 546          /// <param name="tracked">True if the flush writes should be tracked, false otherwise</param>
 547          /// <returns>True if data was flushed, false otherwise</returns>
 548          public bool FlushModified(Texture texture, bool tracked)
 549          {
 550              tracked = tracked || ShouldFlushTriggerTracking();
 551              bool flushed = false;
 552  
 553              EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
 554              {
 555                  int startSlice = 0;
 556                  int endSlice = 0;
 557                  bool allModified = true;
 558  
 559                  for (int i = 0; i < regionCount; i++)
 560                  {
 561                      TextureGroupHandle group = _handles[baseHandle + i];
 562  
 563                      if (group.Modified)
 564                      {
 565                          if (endSlice < group.BaseSlice)
 566                          {
 567                              if (endSlice > startSlice)
 568                              {
 569                                  FlushSliceRange(tracked, startSlice, endSlice, false);
 570                                  flushed = true;
 571                              }
 572  
 573                              startSlice = group.BaseSlice;
 574                          }
 575  
 576                          endSlice = group.BaseSlice + group.SliceCount;
 577  
 578                          if (tracked)
 579                          {
 580                              group.Modified = false;
 581  
 582                              foreach (Texture texture in group.Overlaps)
 583                              {
 584                                  texture.SignalModifiedDirty();
 585                              }
 586                          }
 587                      }
 588                      else
 589                      {
 590                          allModified = false;
 591                      }
 592                  }
 593  
 594                  if (endSlice > startSlice)
 595                  {
 596                      if (allModified && !split)
 597                      {
 598                          texture.Flush(tracked);
 599                      }
 600                      else
 601                      {
 602                          FlushSliceRange(tracked, startSlice, endSlice, false);
 603                      }
 604  
 605                      flushed = true;
 606                  }
 607              });
 608  
 609              Storage.SignalModifiedDirty();
 610  
 611              return flushed;
 612          }
 613  
 614          /// <summary>
 615          /// Flush the texture data into a persistently mapped buffer.
 616          /// If the buffer does not exist, this method will create it.
 617          /// </summary>
 618          /// <param name="handle">Handle of the texture group to flush slices of</param>
 619          public void FlushIntoBuffer(TextureGroupHandle handle)
 620          {
 621              // Ensure that the buffer exists.
 622  
 623              if (_flushBufferInvalid && _flushBuffer != BufferHandle.Null)
 624              {
 625                  _flushBufferInvalid = false;
 626                  _context.Renderer.DeleteBuffer(_flushBuffer);
 627                  _flushBuffer = BufferHandle.Null;
 628              }
 629  
 630              if (_flushBuffer == BufferHandle.Null)
 631              {
 632                  if (!TextureCompatibility.CanTextureFlush(Storage.Info, _context.Capabilities))
 633                  {
 634                      return;
 635                  }
 636  
 637                  bool canImport = Storage.Info.IsLinear && Storage.Info.Stride >= Storage.Info.Width * Storage.Info.FormatInfo.BytesPerPixel;
 638  
 639                  var hostPointer = canImport ? _physicalMemory.GetHostPointer(Storage.Range) : 0;
 640  
 641                  if (hostPointer != 0 && _context.Renderer.PrepareHostMapping(hostPointer, Storage.Size))
 642                  {
 643                      _flushBuffer = _context.Renderer.CreateBuffer(hostPointer, (int)Storage.Size);
 644                      _flushBufferImported = true;
 645                  }
 646                  else
 647                  {
 648                      _flushBuffer = _context.Renderer.CreateBuffer((int)Storage.Size, BufferAccess.HostMemory);
 649                      _flushBufferImported = false;
 650                  }
 651  
 652                  Storage.BlacklistScale();
 653              }
 654  
 655              int sliceStart = handle.BaseSlice;
 656              int sliceEnd = sliceStart + handle.SliceCount;
 657  
 658              for (int i = sliceStart; i < sliceEnd; i++)
 659              {
 660                  (int layer, int level) = GetLayerLevelForView(i);
 661  
 662                  Storage.GetFlushTexture().CopyTo(new BufferRange(_flushBuffer, _allOffsets[i], _sliceSizes[level]), layer, level, _flushBufferImported ? Storage.Info.Stride : 0);
 663              }
 664          }
 665  
 666          /// <summary>
 667          /// Clears competing modified flags for all incompatible ranges, if they have possibly been modified.
 668          /// </summary>
 669          /// <param name="texture">The texture that has been modified</param>
 670          [MethodImpl(MethodImplOptions.AggressiveInlining)]
 671          private void ClearIncompatibleOverlaps(Texture texture)
 672          {
 673              if (_incompatibleOverlapsDirty)
 674              {
 675                  foreach (TextureIncompatibleOverlap incompatible in _incompatibleOverlaps)
 676                  {
 677                      incompatible.Group.ClearModified(texture.Range, this);
 678  
 679                      incompatible.Group.SignalIncompatibleOverlapModified();
 680                  }
 681  
 682                  _incompatibleOverlapsDirty = false;
 683              }
 684          }
 685  
 686          /// <summary>
 687          /// Signal that a texture in the group has been modified by the GPU.
 688          /// </summary>
 689          /// <param name="texture">The texture that has been modified</param>
 690          public void SignalModified(Texture texture)
 691          {
 692              ModifiedSequence = _context.GetModifiedSequence();
 693  
 694              ClearIncompatibleOverlaps(texture);
 695  
 696              EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
 697              {
 698                  for (int i = 0; i < regionCount; i++)
 699                  {
 700                      TextureGroupHandle group = _handles[baseHandle + i];
 701  
 702                      group.SignalModified(_context);
 703                  }
 704              });
 705          }
 706  
 707          /// <summary>
 708          /// Signal that a texture in the group is actively bound, or has been unbound by the GPU.
 709          /// </summary>
 710          /// <param name="texture">The texture that has been modified</param>
 711          /// <param name="bound">True if this texture is being bound, false if unbound</param>
 712          public void SignalModifying(Texture texture, bool bound)
 713          {
 714              ModifiedSequence = _context.GetModifiedSequence();
 715  
 716              ClearIncompatibleOverlaps(texture);
 717  
 718              EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
 719              {
 720                  for (int i = 0; i < regionCount; i++)
 721                  {
 722                      TextureGroupHandle group = _handles[baseHandle + i];
 723  
 724                      group.SignalModifying(bound, _context);
 725                  }
 726              });
 727          }
 728  
 729          /// <summary>
 730          /// Register a read/write action to flush for a texture group.
 731          /// </summary>
 732          /// <param name="group">The group to register an action for</param>
 733          public void RegisterAction(TextureGroupHandle group)
 734          {
 735              foreach (RegionHandle handle in group.Handles)
 736              {
 737                  handle.RegisterAction((address, size) => FlushAction(group, address, size));
 738              }
 739          }
 740  
 741          /// <summary>
 742          /// Propagates the mip/layer view flags depending on the texture type.
 743          /// When the most granular type of subresource has views, the other type of subresource must be segmented granularly too.
 744          /// </summary>
 745          /// <param name="hasLayerViews">True if the storage has layer views</param>
 746          /// <param name="hasMipViews">True if the storage has mip views</param>
 747          /// <returns>The input values after propagation</returns>
 748          private (bool HasLayerViews, bool HasMipViews) PropagateGranularity(bool hasLayerViews, bool hasMipViews)
 749          {
 750              if (_is3D)
 751              {
 752                  hasMipViews |= hasLayerViews;
 753              }
 754              else
 755              {
 756                  hasLayerViews |= hasMipViews;
 757              }
 758  
 759              return (hasLayerViews, hasMipViews);
 760          }
 761  
 762          /// <summary>
 763          /// Evaluate the range of tracking handles which a view texture overlaps with.
 764          /// </summary>
 765          /// <param name="texture">The texture to get handles for</param>
 766          /// <param name="callback">
 767          /// A function to be called with the base index of the range of handles for the given texture, and the number of handles it covers.
 768          /// This can be called for multiple disjoint ranges, if required.
 769          /// </param>
 770          private void EvaluateRelevantHandles(Texture texture, HandlesCallbackDelegate callback)
 771          {
 772              if (texture == Storage || !(_hasMipViews || _hasLayerViews))
 773              {
 774                  callback(0, _handles.Length);
 775  
 776                  return;
 777              }
 778  
 779              EvaluateRelevantHandles(texture.FirstLayer, texture.FirstLevel, texture.Info.GetSlices(), texture.Info.Levels, callback);
 780          }
 781  
 782          /// <summary>
 783          /// Evaluate the range of tracking handles which a view texture overlaps with,
 784          /// using the view's position and slice/level counts.
 785          /// </summary>
 786          /// <param name="firstLayer">The first layer of the texture</param>
 787          /// <param name="firstLevel">The first level of the texture</param>
 788          /// <param name="slices">The slice count of the texture</param>
 789          /// <param name="levels">The level count of the texture</param>
 790          /// <param name="callback">
 791          /// A function to be called with the base index of the range of handles for the given texture, and the number of handles it covers.
 792          /// This can be called for multiple disjoint ranges, if required.
 793          /// </param>
 794          private void EvaluateRelevantHandles(int firstLayer, int firstLevel, int slices, int levels, HandlesCallbackDelegate callback)
 795          {
 796              int targetLayerHandles = _hasLayerViews ? slices : 1;
 797              int targetLevelHandles = _hasMipViews ? levels : 1;
 798  
 799              if (_isBuffer)
 800              {
 801                  return;
 802              }
 803              else if (_is3D)
 804              {
 805                  // Future mip levels come after all layers of the last mip level. Each mipmap has less layers (depth) than the last.
 806  
 807                  if (!_hasLayerViews)
 808                  {
 809                      // When there are no layer views, the mips are at a consistent offset.
 810  
 811                      callback(firstLevel, targetLevelHandles);
 812                  }
 813                  else
 814                  {
 815                      (int levelIndex, int layerCount) = Get3DLevelRange(firstLevel);
 816  
 817                      if (levels > 1 && slices < _layers)
 818                      {
 819                          // The given texture only covers some of the depth of multiple mips. (a "depth slice")
 820                          // Callback with each mip's range separately.
 821                          // Can assume that the group is fully subdivided (both slices and levels > 1 for storage)
 822  
 823                          while (levels-- > 1)
 824                          {
 825                              callback(firstLayer + levelIndex, slices);
 826  
 827                              levelIndex += layerCount;
 828                              layerCount = Math.Max(layerCount >> 1, 1);
 829                              slices = Math.Max(layerCount >> 1, 1);
 830                          }
 831                      }
 832                      else
 833                      {
 834                          int totalSize = Math.Min(layerCount, slices);
 835  
 836                          while (levels-- > 1)
 837                          {
 838                              layerCount = Math.Max(layerCount >> 1, 1);
 839                              totalSize += layerCount;
 840                          }
 841  
 842                          callback(firstLayer + levelIndex, totalSize);
 843                      }
 844                  }
 845              }
 846              else
 847              {
 848                  // Future layers come after all mipmaps of the last.
 849                  int levelHandles = _hasMipViews ? _levels : 1;
 850  
 851                  if (slices > 1 && levels < _levels)
 852                  {
 853                      // The given texture only covers some of the mipmaps of multiple slices. (a "mip slice")
 854                      // Callback with each layer's range separately.
 855                      // Can assume that the group is fully subdivided (both slices and levels > 1 for storage)
 856  
 857                      for (int i = 0; i < slices; i++)
 858                      {
 859                          callback(firstLevel + (firstLayer + i) * levelHandles, targetLevelHandles, true);
 860                      }
 861                  }
 862                  else
 863                  {
 864                      callback(firstLevel + firstLayer * levelHandles, targetLevelHandles + (targetLayerHandles - 1) * levelHandles);
 865                  }
 866              }
 867          }
 868  
 869          /// <summary>
 870          /// Get the range of offsets for a given mip level of a 3D texture.
 871          /// </summary>
 872          /// <param name="level">The level to return</param>
 873          /// <returns>Start index and count of offsets for the given level</returns>
 874          private (int Index, int Count) Get3DLevelRange(int level)
 875          {
 876              int index = 0;
 877              int count = _layers; // Depth. Halves with each mip level.
 878  
 879              while (level-- > 0)
 880              {
 881                  index += count;
 882                  count = Math.Max(count >> 1, 1);
 883              }
 884  
 885              return (index, count);
 886          }
 887  
 888          /// <summary>
 889          /// Get view information for a single tracking handle.
 890          /// </summary>
 891          /// <param name="handleIndex">The index of the handle</param>
 892          /// <returns>The layers and levels that the handle covers, and its index in the offsets array</returns>
 893          private (int BaseLayer, int BaseLevel, int Levels, int Layers, int Index) GetHandleInformation(int handleIndex)
 894          {
 895              int baseLayer;
 896              int baseLevel;
 897              int levels = _hasMipViews ? 1 : _levels;
 898              int layers = _hasLayerViews ? 1 : _layers;
 899              int index;
 900  
 901              if (_is3D)
 902              {
 903                  if (_hasLayerViews)
 904                  {
 905                      // NOTE: Will also have mip views, or only one level in storage.
 906  
 907                      index = handleIndex;
 908                      baseLevel = 0;
 909  
 910                      int levelLayers = _layers;
 911  
 912                      while (handleIndex >= levelLayers)
 913                      {
 914                          handleIndex -= levelLayers;
 915                          baseLevel++;
 916                          levelLayers = Math.Max(levelLayers >> 1, 1);
 917                      }
 918  
 919                      baseLayer = handleIndex;
 920                  }
 921                  else
 922                  {
 923                      baseLayer = 0;
 924                      baseLevel = handleIndex;
 925  
 926                      (index, _) = Get3DLevelRange(baseLevel);
 927                  }
 928              }
 929              else
 930              {
 931                  baseLevel = _hasMipViews ? handleIndex % _levels : 0;
 932                  baseLayer = _hasMipViews ? handleIndex / _levels : handleIndex;
 933                  index = baseLevel + baseLayer * _levels;
 934              }
 935  
 936              return (baseLayer, baseLevel, levels, layers, index);
 937          }
 938  
 939          /// <summary>
 940          /// Gets the layer and level for a given view.
 941          /// </summary>
 942          /// <param name="index">The index of the view</param>
 943          /// <returns>The layer and level of the specified view</returns>
 944          private (int BaseLayer, int BaseLevel) GetLayerLevelForView(int index)
 945          {
 946              if (_is3D)
 947              {
 948                  int baseLevel = 0;
 949  
 950                  int levelLayers = _layers;
 951  
 952                  while (index >= levelLayers)
 953                  {
 954                      index -= levelLayers;
 955                      baseLevel++;
 956                      levelLayers = Math.Max(levelLayers >> 1, 1);
 957                  }
 958  
 959                  return (index, baseLevel);
 960              }
 961              else
 962              {
 963                  return (index / _levels, index % _levels);
 964              }
 965          }
 966  
 967          /// <summary>
 968          /// Find the byte offset of a given texture relative to the storage.
 969          /// </summary>
 970          /// <param name="texture">The texture to locate</param>
 971          /// <returns>The offset of the texture in bytes</returns>
 972          public int FindOffset(Texture texture)
 973          {
 974              return _allOffsets[GetOffsetIndex(texture.FirstLayer, texture.FirstLevel)];
 975          }
 976  
 977          /// <summary>
 978          /// Find the offset index of a given layer and level.
 979          /// </summary>
 980          /// <param name="layer">The view layer</param>
 981          /// <param name="level">The view level</param>
 982          /// <returns>The offset index of the given layer and level</returns>
 983          public int GetOffsetIndex(int layer, int level)
 984          {
 985              if (_is3D)
 986              {
 987                  return layer + Get3DLevelRange(level).Index;
 988              }
 989              else
 990              {
 991                  return level + layer * _levels;
 992              }
 993          }
 994  
 995          /// <summary>
 996          /// Generate a CpuRegionHandle for a given address and size range in CPU VA.
 997          /// </summary>
 998          /// <param name="address">The start address of the tracked region</param>
 999          /// <param name="size">The size of the tracked region</param>
1000          /// <returns>A CpuRegionHandle covering the given range</returns>
1001          private RegionHandle GenerateHandle(ulong address, ulong size)
1002          {
1003              return _physicalMemory.BeginTracking(address, size, ResourceKind.Texture);
1004          }
1005  
1006          /// <summary>
1007          /// Generate a TextureGroupHandle covering a specified range of views.
1008          /// </summary>
1009          /// <param name="viewStart">The start view of the handle</param>
1010          /// <param name="views">The number of views to cover</param>
1011          /// <returns>A TextureGroupHandle covering the given views</returns>
1012          private TextureGroupHandle GenerateHandles(int viewStart, int views)
1013          {
1014              int viewEnd = viewStart + views - 1;
1015              (_, int lastLevel) = GetLayerLevelForView(viewEnd);
1016  
1017              int offset = _allOffsets[viewStart];
1018              int endOffset = _allOffsets[viewEnd] + _sliceSizes[lastLevel];
1019              int size = endOffset - offset;
1020  
1021              var result = new List<RegionHandle>();
1022  
1023              for (int i = 0; i < TextureRange.Count; i++)
1024              {
1025                  MemoryRange item = TextureRange.GetSubRange(i);
1026                  int subRangeSize = (int)item.Size;
1027  
1028                  int sliceStart = Math.Clamp(offset, 0, subRangeSize);
1029                  int sliceEnd = Math.Clamp(endOffset, 0, subRangeSize);
1030  
1031                  if (sliceStart != sliceEnd && item.Address != MemoryManager.PteUnmapped)
1032                  {
1033                      result.Add(GenerateHandle(item.Address + (ulong)sliceStart, (ulong)(sliceEnd - sliceStart)));
1034                  }
1035  
1036                  offset -= subRangeSize;
1037                  endOffset -= subRangeSize;
1038  
1039                  if (endOffset <= 0)
1040                  {
1041                      break;
1042                  }
1043              }
1044  
1045              (int firstLayer, int firstLevel) = GetLayerLevelForView(viewStart);
1046  
1047              if (_hasLayerViews && _hasMipViews)
1048              {
1049                  size = _sliceSizes[firstLevel];
1050              }
1051  
1052              offset = _allOffsets[viewStart];
1053              ulong maxSize = Storage.Size - (ulong)offset;
1054  
1055              var groupHandle = new TextureGroupHandle(
1056                  this,
1057                  offset,
1058                  Math.Min(maxSize, (ulong)size),
1059                  _views,
1060                  firstLayer,
1061                  firstLevel,
1062                  viewStart,
1063                  views,
1064                  result.ToArray());
1065  
1066              return groupHandle;
1067          }
1068  
1069          /// <summary>
1070          /// Update the views in this texture group, rebuilding the memory tracking if required.
1071          /// </summary>
1072          /// <param name="views">The views list of the storage texture</param>
1073          /// <param name="texture">The texture that has been added, if that is the only change, otherwise null</param>
1074          public void UpdateViews(List<Texture> views, Texture texture)
1075          {
1076              // This is saved to calculate overlapping views for each handle.
1077              _views = views.ToArray();
1078  
1079              bool layerViews = _hasLayerViews;
1080              bool mipViews = _hasMipViews;
1081              bool regionsRebuilt = false;
1082  
1083              if (!(layerViews && mipViews))
1084              {
1085                  foreach (Texture view in views)
1086                  {
1087                      if (view.Info.GetSlices() < _layers)
1088                      {
1089                          layerViews = true;
1090                      }
1091  
1092                      if (view.Info.Levels < _levels)
1093                      {
1094                          mipViews = true;
1095                      }
1096                  }
1097  
1098                  (layerViews, mipViews) = PropagateGranularity(layerViews, mipViews);
1099  
1100                  if (layerViews != _hasLayerViews || mipViews != _hasMipViews)
1101                  {
1102                      _hasLayerViews = layerViews;
1103                      _hasMipViews = mipViews;
1104  
1105                      RecalculateHandleRegions();
1106                      regionsRebuilt = true;
1107                  }
1108              }
1109  
1110              if (!regionsRebuilt)
1111              {
1112                  if (texture != null)
1113                  {
1114                      int offset = FindOffset(texture);
1115  
1116                      foreach (TextureGroupHandle handle in _handles)
1117                      {
1118                          handle.AddOverlap(offset, texture);
1119                      }
1120                  }
1121                  else
1122                  {
1123                      // Must update the overlapping views on all handles, but only if they were not just recreated.
1124  
1125                      foreach (TextureGroupHandle handle in _handles)
1126                      {
1127                          handle.RecalculateOverlaps(this, views);
1128                      }
1129                  }
1130              }
1131  
1132              SignalAllDirty();
1133          }
1134  
1135  
1136          /// <summary>
1137          /// Removes a view from the group, removing it from all overlap lists.
1138          /// </summary>
1139          /// <param name="views">The views list of the storage texture</param>
1140          /// <param name="view">View to remove from the group</param>
1141          public void RemoveView(List<Texture> views, Texture view)
1142          {
1143              // This is saved to calculate overlapping views for each handle.
1144              _views = views.ToArray();
1145  
1146              int offset = FindOffset(view);
1147  
1148              foreach (TextureGroupHandle handle in _handles)
1149              {
1150                  handle.RemoveOverlap(offset, view);
1151              }
1152          }
1153  
1154          /// <summary>
1155          /// Inherit handle state from an old set of handles, such as modified and dirty flags.
1156          /// </summary>
1157          /// <param name="oldHandles">The set of handles to inherit state from</param>
1158          /// <param name="handles">The set of handles inheriting the state</param>
1159          /// <param name="relativeOffset">The offset of the old handles in relation to the new ones</param>
1160          private void InheritHandles(TextureGroupHandle[] oldHandles, TextureGroupHandle[] handles, int relativeOffset)
1161          {
1162              foreach (var group in handles)
1163              {
1164                  foreach (var handle in group.Handles)
1165                  {
1166                      bool dirty = false;
1167  
1168                      foreach (var oldGroup in oldHandles)
1169                      {
1170                          if (group.OverlapsWith(oldGroup.Offset + relativeOffset, oldGroup.Size))
1171                          {
1172                              foreach (var oldHandle in oldGroup.Handles)
1173                              {
1174                                  if (handle.OverlapsWith(oldHandle.Address, oldHandle.Size))
1175                                  {
1176                                      dirty |= oldHandle.Dirty;
1177                                  }
1178                              }
1179  
1180                              group.Inherit(oldGroup, group.Offset == oldGroup.Offset + relativeOffset);
1181                          }
1182                      }
1183  
1184                      if (dirty && !handle.Dirty)
1185                      {
1186                          handle.Reprotect(true);
1187                      }
1188  
1189                      if (group.Modified)
1190                      {
1191                          handle.RegisterAction((address, size) => FlushAction(group, address, size));
1192                      }
1193                  }
1194              }
1195  
1196              foreach (var oldGroup in oldHandles)
1197              {
1198                  oldGroup.Modified = false;
1199              }
1200          }
1201  
1202          /// <summary>
1203          /// Inherit state from another texture group.
1204          /// </summary>
1205          /// <param name="other">The texture group to inherit from</param>
1206          public void Inherit(TextureGroup other)
1207          {
1208              bool layerViews = _hasLayerViews || other._hasLayerViews;
1209              bool mipViews = _hasMipViews || other._hasMipViews;
1210  
1211              if (layerViews != _hasLayerViews || mipViews != _hasMipViews)
1212              {
1213                  _hasLayerViews = layerViews;
1214                  _hasMipViews = mipViews;
1215  
1216                  RecalculateHandleRegions();
1217              }
1218  
1219              foreach (TextureIncompatibleOverlap incompatible in other._incompatibleOverlaps)
1220              {
1221                  RegisterIncompatibleOverlap(incompatible, false);
1222  
1223                  incompatible.Group._incompatibleOverlaps.RemoveAll(overlap => overlap.Group == other);
1224              }
1225  
1226              int relativeOffset = Storage.Range.FindOffset(other.Storage.Range);
1227  
1228              InheritHandles(other._handles, _handles, relativeOffset);
1229          }
1230  
1231          /// <summary>
1232          /// Replace the current handles with the new handles. It is assumed that the new handles start dirty.
1233          /// The dirty flags from the previous handles will be kept.
1234          /// </summary>
1235          /// <param name="handles">The handles to replace the current handles with</param>
1236          /// <param name="rangeChanged">True if the storage memory range changed since the last region handle generation</param>
1237          private void ReplaceHandles(TextureGroupHandle[] handles, bool rangeChanged)
1238          {
1239              if (_handles != null)
1240              {
1241                  // When replacing handles, they should start as non-dirty.
1242  
1243                  foreach (TextureGroupHandle groupHandle in handles)
1244                  {
1245                      if (rangeChanged)
1246                      {
1247                          // When the storage range changes, this becomes a little different.
1248                          // If a range does not match one in the original, treat it as modified.
1249                          // It has been newly mapped and its data must be synchronized.
1250  
1251                          if (groupHandle.Handles.Length == 0)
1252                          {
1253                              continue;
1254                          }
1255  
1256                          foreach (var oldGroup in _handles)
1257                          {
1258                              if (!groupHandle.OverlapsWith(oldGroup.Offset, oldGroup.Size))
1259                              {
1260                                  continue;
1261                              }
1262  
1263                              foreach (RegionHandle handle in groupHandle.Handles)
1264                              {
1265                                  bool hasMatch = false;
1266  
1267                                  foreach (var oldHandle in oldGroup.Handles)
1268                                  {
1269                                      if (oldHandle.RangeEquals(handle))
1270                                      {
1271                                          hasMatch = true;
1272                                          break;
1273                                      }
1274                                  }
1275  
1276                                  if (hasMatch)
1277                                  {
1278                                      handle.Reprotect();
1279                                  }
1280                              }
1281                          }
1282                      }
1283                      else
1284                      {
1285                          foreach (RegionHandle handle in groupHandle.Handles)
1286                          {
1287                              handle.Reprotect();
1288                          }
1289                      }
1290                  }
1291  
1292                  InheritHandles(_handles, handles, 0);
1293  
1294                  foreach (var oldGroup in _handles)
1295                  {
1296                      foreach (var oldHandle in oldGroup.Handles)
1297                      {
1298                          oldHandle.Dispose();
1299                      }
1300                  }
1301              }
1302  
1303              _handles = handles;
1304              _loadNeeded = new bool[_handles.Length];
1305          }
1306  
1307          /// <summary>
1308          /// Recalculate handle regions for this texture group, and inherit existing state into the new handles.
1309          /// </summary>
1310          /// <param name="rangeChanged">True if the storage memory range changed since the last region handle generation</param>
1311          private void RecalculateHandleRegions(bool rangeChanged = false)
1312          {
1313              TextureGroupHandle[] handles;
1314  
1315              if (_isBuffer)
1316              {
1317                  handles = Array.Empty<TextureGroupHandle>();
1318              }
1319              else if (!(_hasMipViews || _hasLayerViews))
1320              {
1321                  // Single dirty region.
1322                  var cpuRegionHandles = new RegionHandle[TextureRange.Count];
1323                  int count = 0;
1324  
1325                  for (int i = 0; i < TextureRange.Count; i++)
1326                  {
1327                      var currentRange = TextureRange.GetSubRange(i);
1328                      if (currentRange.Address != MemoryManager.PteUnmapped)
1329                      {
1330                          cpuRegionHandles[count++] = GenerateHandle(currentRange.Address, currentRange.Size);
1331                      }
1332                  }
1333  
1334                  if (count != TextureRange.Count)
1335                  {
1336                      Array.Resize(ref cpuRegionHandles, count);
1337                  }
1338  
1339                  var groupHandle = new TextureGroupHandle(this, 0, Storage.Size, _views, 0, 0, 0, _allOffsets.Length, cpuRegionHandles);
1340  
1341                  handles = new TextureGroupHandle[] { groupHandle };
1342              }
1343              else
1344              {
1345                  // Get views for the host texture.
1346                  // It's worth noting that either the texture has layer views or mip views when getting to this point, which simplifies the logic a little.
1347                  // Depending on if the texture is 3d, either the mip views imply that layer views are present (2d) or the other way around (3d).
1348                  // This is enforced by the way the texture matched as a view, so we don't need to check.
1349  
1350                  int layerHandles = _hasLayerViews ? _layers : 1;
1351                  int levelHandles = _hasMipViews ? _levels : 1;
1352  
1353                  int handleIndex = 0;
1354  
1355                  if (_is3D)
1356                  {
1357                      var handlesList = new List<TextureGroupHandle>();
1358  
1359                      for (int i = 0; i < levelHandles; i++)
1360                      {
1361                          for (int j = 0; j < layerHandles; j++)
1362                          {
1363                              (int viewStart, int views) = Get3DLevelRange(i);
1364                              viewStart += j;
1365                              views = _hasLayerViews ? 1 : views; // A layer view is also a mip view.
1366  
1367                              handlesList.Add(GenerateHandles(viewStart, views));
1368                          }
1369  
1370                          layerHandles = Math.Max(1, layerHandles >> 1);
1371                      }
1372  
1373                      handles = handlesList.ToArray();
1374                  }
1375                  else
1376                  {
1377                      handles = new TextureGroupHandle[layerHandles * levelHandles];
1378  
1379                      for (int i = 0; i < layerHandles; i++)
1380                      {
1381                          for (int j = 0; j < levelHandles; j++)
1382                          {
1383                              int viewStart = j + i * _levels;
1384                              int views = _hasMipViews ? 1 : _levels; // A mip view is also a layer view.
1385  
1386                              handles[handleIndex++] = GenerateHandles(viewStart, views);
1387                          }
1388                      }
1389                  }
1390              }
1391  
1392              ReplaceHandles(handles, rangeChanged);
1393          }
1394  
1395          /// <summary>
1396          /// Regenerates handles when the storage range has been remapped.
1397          /// This forces the regions to be fully subdivided.
1398          /// </summary>
1399          public void RangeChanged()
1400          {
1401              _hasLayerViews = true;
1402              _hasMipViews = true;
1403  
1404              RecalculateHandleRegions(true);
1405  
1406              SignalAllDirty();
1407          }
1408  
1409          /// <summary>
1410          /// Ensure that there is a handle for each potential texture view. Required for copy dependencies to work.
1411          /// </summary>
1412          private void EnsureFullSubdivision()
1413          {
1414              if (!(_hasLayerViews && _hasMipViews))
1415              {
1416                  _hasLayerViews = true;
1417                  _hasMipViews = true;
1418  
1419                  RecalculateHandleRegions();
1420              }
1421          }
1422  
1423          /// <summary>
1424          /// Create a copy dependency between this texture group, and a texture at a given layer/level offset.
1425          /// </summary>
1426          /// <param name="other">The view compatible texture to create a dependency to</param>
1427          /// <param name="firstLayer">The base layer of the given texture relative to the storage</param>
1428          /// <param name="firstLevel">The base level of the given texture relative to the storage</param>
1429          /// <param name="copyTo">True if this texture is first copied to the given one, false for the opposite direction</param>
1430          public void CreateCopyDependency(Texture other, int firstLayer, int firstLevel, bool copyTo)
1431          {
1432              TextureGroup otherGroup = other.Group;
1433  
1434              EnsureFullSubdivision();
1435              otherGroup.EnsureFullSubdivision();
1436  
1437              // Get the location of each texture within its storage, so we can find the handles to apply the dependency to.
1438              // This can consist of multiple disjoint regions, for example if this is a mip slice of an array texture.
1439  
1440              var targetRange = new List<(int BaseHandle, int RegionCount)>();
1441              var otherRange = new List<(int BaseHandle, int RegionCount)>();
1442  
1443              EvaluateRelevantHandles(firstLayer, firstLevel, other.Info.GetSlices(), other.Info.Levels, (baseHandle, regionCount, split) => targetRange.Add((baseHandle, regionCount)));
1444              otherGroup.EvaluateRelevantHandles(other, (baseHandle, regionCount, split) => otherRange.Add((baseHandle, regionCount)));
1445  
1446              int targetIndex = 0;
1447              int otherIndex = 0;
1448              (int Handle, int RegionCount) targetRegion = (0, 0);
1449              (int Handle, int RegionCount) otherRegion = (0, 0);
1450  
1451              while (true)
1452              {
1453                  if (targetRegion.RegionCount == 0)
1454                  {
1455                      if (targetIndex >= targetRange.Count)
1456                      {
1457                          break;
1458                      }
1459  
1460                      targetRegion = targetRange[targetIndex++];
1461                  }
1462  
1463                  if (otherRegion.RegionCount == 0)
1464                  {
1465                      if (otherIndex >= otherRange.Count)
1466                      {
1467                          break;
1468                      }
1469  
1470                      otherRegion = otherRange[otherIndex++];
1471                  }
1472  
1473                  TextureGroupHandle handle = _handles[targetRegion.Handle++];
1474                  TextureGroupHandle otherHandle = other.Group._handles[otherRegion.Handle++];
1475  
1476                  targetRegion.RegionCount--;
1477                  otherRegion.RegionCount--;
1478  
1479                  handle.CreateCopyDependency(otherHandle, copyTo);
1480  
1481                  // If "copyTo" is true, this texture must copy to the other.
1482                  // Otherwise, it must copy to this texture.
1483  
1484                  if (copyTo)
1485                  {
1486                      otherHandle.Copy(_context, handle);
1487                  }
1488                  else
1489                  {
1490                      handle.Copy(_context, otherHandle);
1491                  }
1492              }
1493          }
1494  
1495          /// <summary>
1496          /// Creates a copy dependency to another texture group, where handles overlap.
1497          /// Scans through all handles to find compatible patches in the other group.
1498          /// </summary>
1499          /// <param name="other">The texture group that overlaps this one</param>
1500          /// <param name="copyTo">True if this texture is first copied to the given one, false for the opposite direction</param>
1501          public void CreateCopyDependency(TextureGroup other, bool copyTo)
1502          {
1503              for (int i = 0; i < _allOffsets.Length; i++)
1504              {
1505                  (_, int level) = GetLayerLevelForView(i);
1506                  MultiRange handleRange = Storage.Range.Slice((ulong)_allOffsets[i], 1);
1507                  ulong handleBase = handleRange.GetSubRange(0).Address;
1508  
1509                  for (int j = 0; j < other._handles.Length; j++)
1510                  {
1511                      (_, int otherLevel) = other.GetLayerLevelForView(j);
1512                      MultiRange otherHandleRange = other.Storage.Range.Slice((ulong)other._allOffsets[j], 1);
1513                      ulong otherHandleBase = otherHandleRange.GetSubRange(0).Address;
1514  
1515                      if (handleBase == otherHandleBase)
1516                      {
1517                          // Check if the two sizes are compatible.
1518                          TextureInfo info = Storage.Info;
1519                          TextureInfo otherInfo = other.Storage.Info;
1520  
1521                          if (TextureCompatibility.ViewLayoutCompatible(info, otherInfo, level, otherLevel) &&
1522                              TextureCompatibility.CopySizeMatches(info, otherInfo, level, otherLevel))
1523                          {
1524                              // These textures are copy compatible. Create the dependency.
1525  
1526                              EnsureFullSubdivision();
1527                              other.EnsureFullSubdivision();
1528  
1529                              TextureGroupHandle handle = _handles[i];
1530                              TextureGroupHandle otherHandle = other._handles[j];
1531  
1532                              handle.CreateCopyDependency(otherHandle, copyTo);
1533  
1534                              // If "copyTo" is true, this texture must copy to the other.
1535                              // Otherwise, it must copy to this texture.
1536  
1537                              if (copyTo)
1538                              {
1539                                  otherHandle.Copy(_context, handle);
1540                              }
1541                              else
1542                              {
1543                                  handle.Copy(_context, otherHandle);
1544                              }
1545                          }
1546                      }
1547                  }
1548              }
1549          }
1550  
1551          /// <summary>
1552          /// Registers another texture group as an incompatible overlap, if not already registered.
1553          /// </summary>
1554          /// <param name="other">The texture group to add to the incompatible overlaps list</param>
1555          /// <param name="copy">True if the overlap should register copy dependencies</param>
1556          public void RegisterIncompatibleOverlap(TextureIncompatibleOverlap other, bool copy)
1557          {
1558              if (!_incompatibleOverlaps.Exists(overlap => overlap.Group == other.Group))
1559              {
1560                  if (copy && other.Compatibility == TextureViewCompatibility.LayoutIncompatible)
1561                  {
1562                      // Any of the group's views may share compatibility, even if the parents do not fully.
1563                      CreateCopyDependency(other.Group, false);
1564                  }
1565  
1566                  _incompatibleOverlaps.Add(other);
1567                  other.Group._incompatibleOverlaps.Add(new TextureIncompatibleOverlap(this, other.Compatibility));
1568              }
1569  
1570              other.Group.SignalIncompatibleOverlapModified();
1571              SignalIncompatibleOverlapModified();
1572          }
1573  
1574          /// <summary>
1575          /// Clear modified flags in the given range.
1576          /// This will stop any GPU written data from flushing or copying to dependent textures.
1577          /// </summary>
1578          /// <param name="range">The range to clear modified flags in</param>
1579          /// <param name="ignore">Ignore handles that have a copy dependency to the specified group</param>
1580          public void ClearModified(MultiRange range, TextureGroup ignore = null)
1581          {
1582              TextureGroupHandle[] handles = _handles;
1583  
1584              foreach (TextureGroupHandle handle in handles)
1585              {
1586                  // Handles list is not modified by another thread, only replaced, so this is thread safe.
1587                  // Remove modified flags from all overlapping handles, so that the textures don't flush to unmapped/remapped GPU memory.
1588  
1589                  MultiRange subRange = Storage.Range.Slice((ulong)handle.Offset, (ulong)handle.Size);
1590  
1591                  if (range.OverlapsWith(subRange))
1592                  {
1593                      if ((ignore == null || !handle.HasDependencyTo(ignore)) && handle.Modified)
1594                      {
1595                          handle.Modified = false;
1596                          handle.DeferredCopy = null;
1597                          Storage.SignalModifiedDirty();
1598  
1599                          lock (handle.Overlaps)
1600                          {
1601                              foreach (Texture texture in handle.Overlaps)
1602                              {
1603                                  texture.SignalModifiedDirty();
1604                              }
1605                          }
1606                      }
1607                  }
1608              }
1609  
1610              Storage.SignalModifiedDirty();
1611  
1612              Texture[] views = _views;
1613  
1614              if (views != null)
1615              {
1616                  foreach (Texture texture in views)
1617                  {
1618                      texture.SignalModifiedDirty();
1619                  }
1620              }
1621          }
1622  
1623          /// <summary>
1624          /// A flush has been requested on a tracked region. Flush texture data for the given handle.
1625          /// </summary>
1626          /// <param name="handle">The handle this flush action is for</param>
1627          /// <param name="address">The address of the flushing memory access</param>
1628          /// <param name="size">The size of the flushing memory access</param>
1629          public void FlushAction(TextureGroupHandle handle, ulong address, ulong size)
1630          {
1631              // There is a small gap here where the action is removed but _actionRegistered is still 1.
1632              // In this case it will skip registering the action, but here we are already handling it,
1633              // so there shouldn't be any issue as it's the same handler for all actions.
1634  
1635              handle.ClearActionRegistered();
1636  
1637              if (!handle.Modified)
1638              {
1639                  return;
1640              }
1641  
1642              bool isGpuThread = _context.IsGpuThread();
1643  
1644              if (isGpuThread)
1645              {
1646                  // No need to wait if we're on the GPU thread, we can just clear the modified flag immediately.
1647                  handle.Modified = false;
1648              }
1649  
1650              _context.Renderer.BackgroundContextAction(() =>
1651              {
1652                  bool inBuffer = !isGpuThread && handle.Sync(_context);
1653  
1654                  Storage.SignalModifiedDirty();
1655  
1656                  lock (handle.Overlaps)
1657                  {
1658                      foreach (Texture texture in handle.Overlaps)
1659                      {
1660                          texture.SignalModifiedDirty();
1661                      }
1662                  }
1663  
1664                  if (TextureCompatibility.CanTextureFlush(Storage.Info, _context.Capabilities) && !(inBuffer && _flushBufferImported))
1665                  {
1666                      FlushSliceRange(false, handle.BaseSlice, handle.BaseSlice + handle.SliceCount, inBuffer, Storage.GetFlushTexture());
1667                  }
1668              });
1669          }
1670  
1671          /// <summary>
1672          /// Called if any part of the storage texture is unmapped.
1673          /// </summary>
1674          public void Unmapped()
1675          {
1676              if (_flushBufferImported)
1677              {
1678                  _flushBufferInvalid = true;
1679              }
1680          }
1681  
1682          /// <summary>
1683          /// Dispose this texture group, disposing all related memory tracking handles.
1684          /// </summary>
1685          public void Dispose()
1686          {
1687              foreach (TextureGroupHandle group in _handles)
1688              {
1689                  group.Dispose();
1690              }
1691  
1692              foreach (TextureIncompatibleOverlap incompatible in _incompatibleOverlaps)
1693              {
1694                  incompatible.Group._incompatibleOverlaps.RemoveAll(overlap => overlap.Group == this);
1695              }
1696  
1697              if (_flushBuffer != BufferHandle.Null)
1698              {
1699                  _context.Renderer.DeleteBuffer(_flushBuffer);
1700              }
1701          }
1702      }
1703  }