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 }