| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445 |
- using Ryujinx.Common.Memory;
- using Ryujinx.Cpu.Tracking;
- using Ryujinx.Graphics.GAL;
- using Ryujinx.Graphics.Gpu.Memory;
- using Ryujinx.Graphics.Texture;
- using Ryujinx.Memory;
- using Ryujinx.Memory.Range;
- using System;
- using System.Collections.Generic;
- using System.Runtime.CompilerServices;
- namespace Ryujinx.Graphics.Gpu.Image
- {
- /// <summary>
- /// An overlapping texture group with a given view compatibility.
- /// </summary>
- struct TextureIncompatibleOverlap
- {
- public readonly TextureGroup Group;
- public readonly TextureViewCompatibility Compatibility;
- /// <summary>
- /// Create a new texture incompatible overlap.
- /// </summary>
- /// <param name="group">The group that is incompatible</param>
- /// <param name="compatibility">The view compatibility for the group</param>
- public TextureIncompatibleOverlap(TextureGroup group, TextureViewCompatibility compatibility)
- {
- Group = group;
- Compatibility = compatibility;
- }
- }
- /// <summary>
- /// A texture group represents a group of textures that belong to the same storage.
- /// When views are created, this class will track memory accesses for them separately.
- /// The group iteratively adds more granular tracking as views of different kinds are added.
- /// Note that a texture group can be absorbed into another when it becomes a view parent.
- /// </summary>
- class TextureGroup : IDisposable
- {
- private delegate void HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false);
- /// <summary>
- /// The storage texture associated with this group.
- /// </summary>
- public Texture Storage { get; }
- /// <summary>
- /// Indicates if the texture has copy dependencies. If true, then all modifications
- /// must be signalled to the group, rather than skipping ones still to be flushed.
- /// </summary>
- public bool HasCopyDependencies { get; set; }
- /// <summary>
- /// Indicates if this texture has any incompatible overlaps alive.
- /// </summary>
- public bool HasIncompatibleOverlaps => _incompatibleOverlaps.Count > 0;
- private readonly GpuContext _context;
- private readonly PhysicalMemory _physicalMemory;
- private int[] _allOffsets;
- private int[] _sliceSizes;
- private bool _is3D;
- private bool _hasMipViews;
- private bool _hasLayerViews;
- private int _layers;
- private int _levels;
- private MultiRange TextureRange => Storage.Range;
- /// <summary>
- /// The views list from the storage texture.
- /// </summary>
- private List<Texture> _views;
- private TextureGroupHandle[] _handles;
- private bool[] _loadNeeded;
- /// <summary>
- /// Other texture groups that have incompatible overlaps with this one.
- /// </summary>
- private List<TextureIncompatibleOverlap> _incompatibleOverlaps;
- private bool _incompatibleOverlapsDirty = true;
- private bool _flushIncompatibleOverlaps;
- /// <summary>
- /// Create a new texture group.
- /// </summary>
- /// <param name="context">GPU context that the texture group belongs to</param>
- /// <param name="physicalMemory">Physical memory where the <paramref name="storage"/> texture is mapped</param>
- /// <param name="storage">The storage texture for this group</param>
- /// <param name="incompatibleOverlaps">Groups that overlap with this one but are incompatible</param>
- public TextureGroup(GpuContext context, PhysicalMemory physicalMemory, Texture storage, List<TextureIncompatibleOverlap> incompatibleOverlaps)
- {
- Storage = storage;
- _context = context;
- _physicalMemory = physicalMemory;
- _is3D = storage.Info.Target == Target.Texture3D;
- _layers = storage.Info.GetSlices();
- _levels = storage.Info.Levels;
- _incompatibleOverlaps = incompatibleOverlaps;
- _flushIncompatibleOverlaps = TextureCompatibility.IsFormatHostIncompatible(storage.Info, context.Capabilities);
- }
- /// <summary>
- /// Initialize a new texture group's dirty regions and offsets.
- /// </summary>
- /// <param name="size">Size info for the storage texture</param>
- /// <param name="hasLayerViews">True if the storage will have layer views</param>
- /// <param name="hasMipViews">True if the storage will have mip views</param>
- public void Initialize(ref SizeInfo size, bool hasLayerViews, bool hasMipViews)
- {
- _allOffsets = size.AllOffsets;
- _sliceSizes = size.SliceSizes;
- (_hasLayerViews, _hasMipViews) = PropagateGranularity(hasLayerViews, hasMipViews);
- RecalculateHandleRegions();
- }
- /// <summary>
- /// Initialize all incompatible overlaps in the list, registering them with the other texture groups
- /// and creating copy dependencies when partially compatible.
- /// </summary>
- public void InitializeOverlaps()
- {
- foreach (TextureIncompatibleOverlap overlap in _incompatibleOverlaps)
- {
- if (overlap.Compatibility == TextureViewCompatibility.LayoutIncompatible)
- {
- CreateCopyDependency(overlap.Group, false);
- }
- overlap.Group._incompatibleOverlaps.Add(new TextureIncompatibleOverlap(this, overlap.Compatibility));
- overlap.Group._incompatibleOverlapsDirty = true;
- }
- if (_incompatibleOverlaps.Count > 0)
- {
- SignalIncompatibleOverlapModified();
- }
- }
- /// <summary>
- /// Signal that the group is dirty to all views and the storage.
- /// </summary>
- private void SignalAllDirty()
- {
- Storage.SignalGroupDirty();
- if (_views != null)
- {
- foreach (Texture texture in _views)
- {
- texture.SignalGroupDirty();
- }
- }
- }
- /// <summary>
- /// Signal that an incompatible overlap has been modified.
- /// If this group must flush incompatible overlaps, the group is signalled as dirty too.
- /// </summary>
- private void SignalIncompatibleOverlapModified()
- {
- _incompatibleOverlapsDirty = true;
- if (_flushIncompatibleOverlaps)
- {
- SignalAllDirty();
- }
- }
- /// <summary>
- /// Flushes incompatible overlaps if the storage format requires it, and they have been modified.
- /// This allows unsupported host formats to accept data written to format aliased textures.
- /// </summary>
- /// <returns>True if data was flushed, false otherwise</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool FlushIncompatibleOverlapsIfNeeded()
- {
- if (_flushIncompatibleOverlaps && _incompatibleOverlapsDirty)
- {
- bool flushed = false;
- foreach (var overlap in _incompatibleOverlaps)
- {
- flushed |= overlap.Group.Storage.FlushModified(true);
- }
- _incompatibleOverlapsDirty = false;
- return flushed;
- }
- else
- {
- return false;
- }
- }
- /// <summary>
- /// Check and optionally consume the dirty flags for a given texture.
- /// The state is shared between views of the same layers and levels.
- /// </summary>
- /// <param name="texture">The texture being used</param>
- /// <param name="consume">True to consume the dirty flags and reprotect, false to leave them as is</param>
- /// <returns>True if a flag was dirty, false otherwise</returns>
- public bool CheckDirty(Texture texture, bool consume)
- {
- bool dirty = false;
- EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
- {
- for (int i = 0; i < regionCount; i++)
- {
- TextureGroupHandle group = _handles[baseHandle + i];
- foreach (CpuRegionHandle handle in group.Handles)
- {
- if (handle.Dirty)
- {
- if (consume)
- {
- handle.Reprotect();
- }
- dirty = true;
- }
- }
- }
- });
- return dirty;
- }
- /// <summary>
- /// Synchronize memory for a given texture.
- /// If overlapping tracking handles are dirty, fully or partially synchronize the texture data.
- /// </summary>
- /// <param name="texture">The texture being used</param>
- public void SynchronizeMemory(Texture texture)
- {
- FlushIncompatibleOverlapsIfNeeded();
- EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
- {
- bool dirty = false;
- bool anyModified = false;
- bool anyUnmapped = false;
- for (int i = 0; i < regionCount; i++)
- {
- TextureGroupHandle group = _handles[baseHandle + i];
- bool modified = group.Modified;
- bool handleDirty = false;
- bool handleUnmapped = false;
- foreach (CpuRegionHandle handle in group.Handles)
- {
- if (handle.Dirty)
- {
- handle.Reprotect();
- handleDirty = true;
- }
- else
- {
- handleUnmapped |= handle.Unmapped;
- }
- }
- // If the modified flag is still present, prefer the data written from gpu.
- // A write from CPU will do a flush before writing its data, which should unset this.
- if (modified)
- {
- handleDirty = false;
- }
- // Evaluate if any copy dependencies need to be fulfilled. A few rules:
- // If the copy handle needs to be synchronized, prefer our own state.
- // If we need to be synchronized and there is a copy present, prefer the copy.
- if (group.NeedsCopy && group.Copy(_context))
- {
- anyModified |= true; // The copy target has been modified.
- handleDirty = false;
- }
- else
- {
- anyModified |= modified;
- dirty |= handleDirty;
- }
- anyUnmapped |= handleUnmapped;
- if (group.NeedsCopy)
- {
- // The texture we copied from is still being written to. Copy from it again the next time this texture is used.
- texture.SignalGroupDirty();
- }
- _loadNeeded[baseHandle + i] = handleDirty && !handleUnmapped;
- }
- if (dirty)
- {
- if (anyUnmapped || (_handles.Length > 1 && (anyModified || split)))
- {
- // Partial texture invalidation. Only update the layers/levels with dirty flags of the storage.
- SynchronizePartial(baseHandle, regionCount);
- }
- else
- {
- // Full texture invalidation.
- texture.SynchronizeFull();
- }
- }
- });
- }
- /// <summary>
- /// Synchronize part of the storage texture, represented by a given range of handles.
- /// Only handles marked by the _loadNeeded array will be synchronized.
- /// </summary>
- /// <param name="baseHandle">The base index of the range of handles</param>
- /// <param name="regionCount">The number of handles to synchronize</param>
- private void SynchronizePartial(int baseHandle, int regionCount)
- {
- for (int i = 0; i < regionCount; i++)
- {
- if (_loadNeeded[baseHandle + i])
- {
- var info = GetHandleInformation(baseHandle + i);
- int offsetIndex = info.Index;
- // Only one of these will be greater than 1, as partial sync is only called when there are sub-image views.
- for (int layer = 0; layer < info.Layers; layer++)
- {
- for (int level = 0; level < info.Levels; level++)
- {
- int offset = _allOffsets[offsetIndex];
- int endOffset = Math.Min(offset + _sliceSizes[info.BaseLevel + level], (int)Storage.Size);
- int size = endOffset - offset;
- ReadOnlySpan<byte> data = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)offset, (ulong)size));
- SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel, true);
- Storage.SetData(result, info.BaseLayer, info.BaseLevel);
- offsetIndex++;
- }
- }
- }
- }
- }
- /// <summary>
- /// Synchronize dependent textures, if any of them have deferred a copy from the given texture.
- /// </summary>
- /// <param name="texture">The texture to synchronize dependents of</param>
- public void SynchronizeDependents(Texture texture)
- {
- EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
- {
- for (int i = 0; i < regionCount; i++)
- {
- TextureGroupHandle group = _handles[baseHandle + i];
- group.SynchronizeDependents();
- }
- });
- }
- /// <summary>
- /// Determines whether flushes in this texture group should be tracked.
- /// Incompatible overlaps may need data from this texture to flush tracked for it to be visible to them.
- /// </summary>
- /// <returns>True if flushes should be tracked, false otherwise</returns>
- private bool ShouldFlushTriggerTracking()
- {
- foreach (var overlap in _incompatibleOverlaps)
- {
- if (overlap.Group._flushIncompatibleOverlaps)
- {
- return true;
- }
- }
- return false;
- }
- /// <summary>
- /// Gets data from the host GPU, and flushes a slice to guest memory.
- /// </summary>
- /// <remarks>
- /// This method should be used to retrieve data that was modified by the host GPU.
- /// This is not cheap, avoid doing that unless strictly needed.
- /// When possible, the data is written directly into guest memory, rather than copied.
- /// </remarks>
- /// <param name="tracked">True if writing the texture data is tracked, false otherwise</param>
- /// <param name="sliceIndex">The index of the slice to flush</param>
- /// <param name="texture">The specific host texture to flush. Defaults to the storage texture</param>
- private void FlushTextureDataSliceToGuest(bool tracked, int sliceIndex, ITexture texture = null)
- {
- (int layer, int level) = GetLayerLevelForView(sliceIndex);
- int offset = _allOffsets[sliceIndex];
- int endOffset = Math.Min(offset + _sliceSizes[level], (int)Storage.Size);
- int size = endOffset - offset;
- using WritableRegion region = _physicalMemory.GetWritableRegion(Storage.Range.GetSlice((ulong)offset, (ulong)size), tracked);
- Storage.GetTextureDataSliceFromGpu(region.Memory.Span, layer, level, tracked, texture);
- }
- /// <summary>
- /// Gets and flushes a number of slices of the storage texture to guest memory.
- /// </summary>
- /// <param name="tracked">True if writing the texture data is tracked, false otherwise</param>
- /// <param name="sliceStart">The first slice to flush</param>
- /// <param name="sliceEnd">The slice to finish flushing on (exclusive)</param>
- /// <param name="texture">The specific host texture to flush. Defaults to the storage texture</param>
- private void FlushSliceRange(bool tracked, int sliceStart, int sliceEnd, ITexture texture = null)
- {
- for (int i = sliceStart; i < sliceEnd; i++)
- {
- FlushTextureDataSliceToGuest(tracked, i, texture);
- }
- }
- /// <summary>
- /// Flush modified ranges for a given texture.
- /// </summary>
- /// <param name="texture">The texture being used</param>
- /// <param name="tracked">True if the flush writes should be tracked, false otherwise</param>
- /// <returns>True if data was flushed, false otherwise</returns>
- public bool FlushModified(Texture texture, bool tracked)
- {
- tracked = tracked || ShouldFlushTriggerTracking();
- bool flushed = false;
- EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
- {
- int startSlice = 0;
- int endSlice = 0;
- bool allModified = true;
- for (int i = 0; i < regionCount; i++)
- {
- TextureGroupHandle group = _handles[baseHandle + i];
- if (group.Modified)
- {
- if (endSlice < group.BaseSlice)
- {
- if (endSlice > startSlice)
- {
- FlushSliceRange(tracked, startSlice, endSlice);
- flushed = true;
- }
- startSlice = group.BaseSlice;
- }
- endSlice = group.BaseSlice + group.SliceCount;
- if (tracked)
- {
- group.Modified = false;
- foreach (Texture texture in group.Overlaps)
- {
- texture.SignalModifiedDirty();
- }
- }
- }
- else
- {
- allModified = false;
- }
- }
- if (endSlice > startSlice)
- {
- if (allModified && !split)
- {
- texture.Flush(tracked);
- }
- else
- {
- FlushSliceRange(tracked, startSlice, endSlice);
- }
- flushed = true;
- }
- });
- Storage.SignalModifiedDirty();
- return flushed;
- }
- /// <summary>
- /// Clears competing modified flags for all incompatible ranges, if they have possibly been modified.
- /// </summary>
- /// <param name="texture">The texture that has been modified</param>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void ClearIncompatibleOverlaps(Texture texture)
- {
- if (_incompatibleOverlapsDirty)
- {
- foreach (TextureIncompatibleOverlap incompatible in _incompatibleOverlaps)
- {
- incompatible.Group.ClearModified(texture.Range, this);
- incompatible.Group.SignalIncompatibleOverlapModified();
- }
- _incompatibleOverlapsDirty = false;
- }
- }
- /// <summary>
- /// Signal that a texture in the group has been modified by the GPU.
- /// </summary>
- /// <param name="texture">The texture that has been modified</param>
- public void SignalModified(Texture texture)
- {
- ClearIncompatibleOverlaps(texture);
- EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
- {
- for (int i = 0; i < regionCount; i++)
- {
- TextureGroupHandle group = _handles[baseHandle + i];
- group.SignalModified(_context);
- }
- });
- }
- /// <summary>
- /// Signal that a texture in the group is actively bound, or has been unbound by the GPU.
- /// </summary>
- /// <param name="texture">The texture that has been modified</param>
- /// <param name="bound">True if this texture is being bound, false if unbound</param>
- public void SignalModifying(Texture texture, bool bound)
- {
- ClearIncompatibleOverlaps(texture);
- EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
- {
- for (int i = 0; i < regionCount; i++)
- {
- TextureGroupHandle group = _handles[baseHandle + i];
- group.SignalModifying(bound, _context);
- }
- });
- }
- /// <summary>
- /// Register a read/write action to flush for a texture group.
- /// </summary>
- /// <param name="group">The group to register an action for</param>
- public void RegisterAction(TextureGroupHandle group)
- {
- foreach (CpuRegionHandle handle in group.Handles)
- {
- handle.RegisterAction((address, size) => FlushAction(group, address, size));
- }
- }
- /// <summary>
- /// Propagates the mip/layer view flags depending on the texture type.
- /// When the most granular type of subresource has views, the other type of subresource must be segmented granularly too.
- /// </summary>
- /// <param name="hasLayerViews">True if the storage has layer views</param>
- /// <param name="hasMipViews">True if the storage has mip views</param>
- /// <returns>The input values after propagation</returns>
- private (bool HasLayerViews, bool HasMipViews) PropagateGranularity(bool hasLayerViews, bool hasMipViews)
- {
- if (_is3D)
- {
- hasMipViews |= hasLayerViews;
- }
- else
- {
- hasLayerViews |= hasMipViews;
- }
- return (hasLayerViews, hasMipViews);
- }
- /// <summary>
- /// Evaluate the range of tracking handles which a view texture overlaps with.
- /// </summary>
- /// <param name="texture">The texture to get handles for</param>
- /// <param name="callback">
- /// 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.
- /// This can be called for multiple disjoint ranges, if required.
- /// </param>
- private void EvaluateRelevantHandles(Texture texture, HandlesCallbackDelegate callback)
- {
- if (texture == Storage || !(_hasMipViews || _hasLayerViews))
- {
- callback(0, _handles.Length);
- return;
- }
- EvaluateRelevantHandles(texture.FirstLayer, texture.FirstLevel, texture.Info.GetSlices(), texture.Info.Levels, callback);
- }
- /// <summary>
- /// Evaluate the range of tracking handles which a view texture overlaps with,
- /// using the view's position and slice/level counts.
- /// </summary>
- /// <param name="firstLayer">The first layer of the texture</param>
- /// <param name="firstLevel">The first level of the texture</param>
- /// <param name="slices">The slice count of the texture</param>
- /// <param name="levels">The level count of the texture</param>
- /// <param name="callback">
- /// 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.
- /// This can be called for multiple disjoint ranges, if required.
- /// </param>
- private void EvaluateRelevantHandles(int firstLayer, int firstLevel, int slices, int levels, HandlesCallbackDelegate callback)
- {
- int targetLayerHandles = _hasLayerViews ? slices : 1;
- int targetLevelHandles = _hasMipViews ? levels : 1;
- if (_is3D)
- {
- // Future mip levels come after all layers of the last mip level. Each mipmap has less layers (depth) than the last.
- if (!_hasLayerViews)
- {
- // When there are no layer views, the mips are at a consistent offset.
- callback(firstLevel, targetLevelHandles);
- }
- else
- {
- (int levelIndex, int layerCount) = Get3DLevelRange(firstLevel);
- if (levels > 1 && slices < _layers)
- {
- // The given texture only covers some of the depth of multiple mips. (a "depth slice")
- // Callback with each mip's range separately.
- // Can assume that the group is fully subdivided (both slices and levels > 1 for storage)
- while (levels-- > 1)
- {
- callback(firstLayer + levelIndex, slices);
- levelIndex += layerCount;
- layerCount = Math.Max(layerCount >> 1, 1);
- slices = Math.Max(layerCount >> 1, 1);
- }
- }
- else
- {
- int totalSize = Math.Min(layerCount, slices);
- while (levels-- > 1)
- {
- layerCount = Math.Max(layerCount >> 1, 1);
- totalSize += layerCount;
- }
- callback(firstLayer + levelIndex, totalSize);
- }
- }
- }
- else
- {
- // Future layers come after all mipmaps of the last.
- int levelHandles = _hasMipViews ? _levels : 1;
- if (slices > 1 && levels < _levels)
- {
- // The given texture only covers some of the mipmaps of multiple slices. (a "mip slice")
- // Callback with each layer's range separately.
- // Can assume that the group is fully subdivided (both slices and levels > 1 for storage)
- for (int i = 0; i < slices; i++)
- {
- callback(firstLevel + (firstLayer + i) * levelHandles, targetLevelHandles, true);
- }
- }
- else
- {
- callback(firstLevel + firstLayer * levelHandles, targetLevelHandles + (targetLayerHandles - 1) * levelHandles);
- }
- }
- }
- /// <summary>
- /// Get the range of offsets for a given mip level of a 3D texture.
- /// </summary>
- /// <param name="level">The level to return</param>
- /// <returns>Start index and count of offsets for the given level</returns>
- private (int Index, int Count) Get3DLevelRange(int level)
- {
- int index = 0;
- int count = _layers; // Depth. Halves with each mip level.
- while (level-- > 0)
- {
- index += count;
- count = Math.Max(count >> 1, 1);
- }
- return (index, count);
- }
- /// <summary>
- /// Get view information for a single tracking handle.
- /// </summary>
- /// <param name="handleIndex">The index of the handle</param>
- /// <returns>The layers and levels that the handle covers, and its index in the offsets array</returns>
- private (int BaseLayer, int BaseLevel, int Levels, int Layers, int Index) GetHandleInformation(int handleIndex)
- {
- int baseLayer;
- int baseLevel;
- int levels = _hasMipViews ? 1 : _levels;
- int layers = _hasLayerViews ? 1 : _layers;
- int index;
- if (_is3D)
- {
- if (_hasLayerViews)
- {
- // NOTE: Will also have mip views, or only one level in storage.
- index = handleIndex;
- baseLevel = 0;
- int levelLayers = _layers;
- while (handleIndex >= levelLayers)
- {
- handleIndex -= levelLayers;
- baseLevel++;
- levelLayers = Math.Max(levelLayers >> 1, 1);
- }
- baseLayer = handleIndex;
- }
- else
- {
- baseLayer = 0;
- baseLevel = handleIndex;
- (index, _) = Get3DLevelRange(baseLevel);
- }
- }
- else
- {
- baseLevel = _hasMipViews ? handleIndex % _levels : 0;
- baseLayer = _hasMipViews ? handleIndex / _levels : handleIndex;
- index = baseLevel + baseLayer * _levels;
- }
- return (baseLayer, baseLevel, levels, layers, index);
- }
- /// <summary>
- /// Gets the layer and level for a given view.
- /// </summary>
- /// <param name="index">The index of the view</param>
- /// <returns>The layer and level of the specified view</returns>
- private (int BaseLayer, int BaseLevel) GetLayerLevelForView(int index)
- {
- if (_is3D)
- {
- int baseLevel = 0;
- int levelLayers = _layers;
- while (index >= levelLayers)
- {
- index -= levelLayers;
- baseLevel++;
- levelLayers = Math.Max(levelLayers >> 1, 1);
- }
- return (index, baseLevel);
- }
- else
- {
- return (index / _levels, index % _levels);
- }
- }
- /// <summary>
- /// Find the byte offset of a given texture relative to the storage.
- /// </summary>
- /// <param name="texture">The texture to locate</param>
- /// <returns>The offset of the texture in bytes</returns>
- public int FindOffset(Texture texture)
- {
- return _allOffsets[GetOffsetIndex(texture.FirstLayer, texture.FirstLevel)];
- }
- /// <summary>
- /// Find the offset index of a given layer and level.
- /// </summary>
- /// <param name="layer">The view layer</param>
- /// <param name="level">The view level</param>
- /// <returns>The offset index of the given layer and level</returns>
- public int GetOffsetIndex(int layer, int level)
- {
- if (_is3D)
- {
- return layer + Get3DLevelRange(level).Index;
- }
- else
- {
- return level + layer * _levels;
- }
- }
- /// <summary>
- /// The action to perform when a memory tracking handle is flipped to dirty.
- /// This notifies overlapping textures that the memory needs to be synchronized.
- /// </summary>
- /// <param name="groupHandle">The handle that a dirty flag was set on</param>
- private void DirtyAction(TextureGroupHandle groupHandle)
- {
- // Notify all textures that belong to this handle.
- Storage.SignalGroupDirty();
- lock (groupHandle.Overlaps)
- {
- foreach (Texture overlap in groupHandle.Overlaps)
- {
- overlap.SignalGroupDirty();
- }
- }
- }
- /// <summary>
- /// Generate a CpuRegionHandle for a given address and size range in CPU VA.
- /// </summary>
- /// <param name="address">The start address of the tracked region</param>
- /// <param name="size">The size of the tracked region</param>
- /// <returns>A CpuRegionHandle covering the given range</returns>
- private CpuRegionHandle GenerateHandle(ulong address, ulong size)
- {
- return _physicalMemory.BeginTracking(address, size);
- }
- /// <summary>
- /// Generate a TextureGroupHandle covering a specified range of views.
- /// </summary>
- /// <param name="viewStart">The start view of the handle</param>
- /// <param name="views">The number of views to cover</param>
- /// <returns>A TextureGroupHandle covering the given views</returns>
- private TextureGroupHandle GenerateHandles(int viewStart, int views)
- {
- int offset = _allOffsets[viewStart];
- int endOffset = (viewStart + views == _allOffsets.Length) ? (int)Storage.Size : _allOffsets[viewStart + views];
- int size = endOffset - offset;
- var result = new List<CpuRegionHandle>();
- for (int i = 0; i < TextureRange.Count; i++)
- {
- MemoryRange item = TextureRange.GetSubRange(i);
- int subRangeSize = (int)item.Size;
- int sliceStart = Math.Clamp(offset, 0, subRangeSize);
- int sliceEnd = Math.Clamp(endOffset, 0, subRangeSize);
- if (sliceStart != sliceEnd && item.Address != MemoryManager.PteUnmapped)
- {
- result.Add(GenerateHandle(item.Address + (ulong)sliceStart, (ulong)(sliceEnd - sliceStart)));
- }
- offset -= subRangeSize;
- endOffset -= subRangeSize;
- if (endOffset <= 0)
- {
- break;
- }
- }
- (int firstLayer, int firstLevel) = GetLayerLevelForView(viewStart);
- if (_hasLayerViews && _hasMipViews)
- {
- size = _sliceSizes[firstLevel];
- }
- offset = _allOffsets[viewStart];
- ulong maxSize = Storage.Size - (ulong)offset;
- var groupHandle = new TextureGroupHandle(
- this,
- offset,
- Math.Min(maxSize, (ulong)size),
- _views,
- firstLayer,
- firstLevel,
- viewStart,
- views,
- result.ToArray());
- foreach (CpuRegionHandle handle in result)
- {
- handle.RegisterDirtyEvent(() => DirtyAction(groupHandle));
- }
- return groupHandle;
- }
- /// <summary>
- /// Update the views in this texture group, rebuilding the memory tracking if required.
- /// </summary>
- /// <param name="views">The views list of the storage texture</param>
- public void UpdateViews(List<Texture> views)
- {
- // This is saved to calculate overlapping views for each handle.
- _views = views;
- bool layerViews = _hasLayerViews;
- bool mipViews = _hasMipViews;
- bool regionsRebuilt = false;
- if (!(layerViews && mipViews))
- {
- foreach (Texture view in views)
- {
- if (view.Info.GetSlices() < _layers)
- {
- layerViews = true;
- }
- if (view.Info.Levels < _levels)
- {
- mipViews = true;
- }
- }
- (layerViews, mipViews) = PropagateGranularity(layerViews, mipViews);
- if (layerViews != _hasLayerViews || mipViews != _hasMipViews)
- {
- _hasLayerViews = layerViews;
- _hasMipViews = mipViews;
- RecalculateHandleRegions();
- regionsRebuilt = true;
- }
- }
- if (!regionsRebuilt)
- {
- // Must update the overlapping views on all handles, but only if they were not just recreated.
- foreach (TextureGroupHandle handle in _handles)
- {
- handle.RecalculateOverlaps(this, views);
- }
- }
- SignalAllDirty();
- }
- /// <summary>
- /// Inherit handle state from an old set of handles, such as modified and dirty flags.
- /// </summary>
- /// <param name="oldHandles">The set of handles to inherit state from</param>
- /// <param name="handles">The set of handles inheriting the state</param>
- /// <param name="relativeOffset">The offset of the old handles in relation to the new ones</param>
- private void InheritHandles(TextureGroupHandle[] oldHandles, TextureGroupHandle[] handles, int relativeOffset)
- {
- foreach (var group in handles)
- {
- foreach (var handle in group.Handles)
- {
- bool dirty = false;
- foreach (var oldGroup in oldHandles)
- {
- if (group.OverlapsWith(oldGroup.Offset + relativeOffset, oldGroup.Size))
- {
- foreach (var oldHandle in oldGroup.Handles)
- {
- if (handle.OverlapsWith(oldHandle.Address, oldHandle.Size))
- {
- dirty |= oldHandle.Dirty;
- }
- }
- group.Inherit(oldGroup, group.Offset == oldGroup.Offset + relativeOffset);
- }
- }
- if (dirty && !handle.Dirty)
- {
- handle.Reprotect(true);
- }
- if (group.Modified)
- {
- handle.RegisterAction((address, size) => FlushAction(group, address, size));
- }
- }
- }
- foreach (var oldGroup in oldHandles)
- {
- oldGroup.Modified = false;
- }
- }
- /// <summary>
- /// Inherit state from another texture group.
- /// </summary>
- /// <param name="other">The texture group to inherit from</param>
- public void Inherit(TextureGroup other)
- {
- bool layerViews = _hasLayerViews || other._hasLayerViews;
- bool mipViews = _hasMipViews || other._hasMipViews;
- if (layerViews != _hasLayerViews || mipViews != _hasMipViews)
- {
- _hasLayerViews = layerViews;
- _hasMipViews = mipViews;
- RecalculateHandleRegions();
- }
- foreach (TextureIncompatibleOverlap incompatible in other._incompatibleOverlaps)
- {
- RegisterIncompatibleOverlap(incompatible, false);
- incompatible.Group._incompatibleOverlaps.RemoveAll(overlap => overlap.Group == other);
- }
- int relativeOffset = Storage.Range.FindOffset(other.Storage.Range);
- InheritHandles(other._handles, _handles, relativeOffset);
- }
- /// <summary>
- /// Replace the current handles with the new handles. It is assumed that the new handles start dirty.
- /// The dirty flags from the previous handles will be kept.
- /// </summary>
- /// <param name="handles">The handles to replace the current handles with</param>
- private void ReplaceHandles(TextureGroupHandle[] handles)
- {
- if (_handles != null)
- {
- // When replacing handles, they should start as non-dirty.
- foreach (TextureGroupHandle groupHandle in handles)
- {
- foreach (CpuRegionHandle handle in groupHandle.Handles)
- {
- handle.Reprotect();
- }
- }
- InheritHandles(_handles, handles, 0);
- foreach (var oldGroup in _handles)
- {
- foreach (var oldHandle in oldGroup.Handles)
- {
- oldHandle.Dispose();
- }
- }
- }
- _handles = handles;
- _loadNeeded = new bool[_handles.Length];
- }
- /// <summary>
- /// Recalculate handle regions for this texture group, and inherit existing state into the new handles.
- /// </summary>
- private void RecalculateHandleRegions()
- {
- TextureGroupHandle[] handles;
- if (!(_hasMipViews || _hasLayerViews))
- {
- // Single dirty region.
- var cpuRegionHandles = new CpuRegionHandle[TextureRange.Count];
- int count = 0;
- for (int i = 0; i < TextureRange.Count; i++)
- {
- var currentRange = TextureRange.GetSubRange(i);
- if (currentRange.Address != MemoryManager.PteUnmapped)
- {
- cpuRegionHandles[count++] = GenerateHandle(currentRange.Address, currentRange.Size);
- }
- }
- if (count != TextureRange.Count)
- {
- Array.Resize(ref cpuRegionHandles, count);
- }
- var groupHandle = new TextureGroupHandle(this, 0, Storage.Size, _views, 0, 0, 0, _allOffsets.Length, cpuRegionHandles);
- foreach (CpuRegionHandle handle in cpuRegionHandles)
- {
- handle.RegisterDirtyEvent(() => DirtyAction(groupHandle));
- }
- handles = new TextureGroupHandle[] { groupHandle };
- }
- else
- {
- // Get views for the host texture.
- // 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.
- // Depending on if the texture is 3d, either the mip views imply that layer views are present (2d) or the other way around (3d).
- // This is enforced by the way the texture matched as a view, so we don't need to check.
- int layerHandles = _hasLayerViews ? _layers : 1;
- int levelHandles = _hasMipViews ? _levels : 1;
- int handleIndex = 0;
- if (_is3D)
- {
- var handlesList = new List<TextureGroupHandle>();
- for (int i = 0; i < levelHandles; i++)
- {
- for (int j = 0; j < layerHandles; j++)
- {
- (int viewStart, int views) = Get3DLevelRange(i);
- viewStart += j;
- views = _hasLayerViews ? 1 : views; // A layer view is also a mip view.
- handlesList.Add(GenerateHandles(viewStart, views));
- }
- layerHandles = Math.Max(1, layerHandles >> 1);
- }
- handles = handlesList.ToArray();
- }
- else
- {
- handles = new TextureGroupHandle[layerHandles * levelHandles];
- for (int i = 0; i < layerHandles; i++)
- {
- for (int j = 0; j < levelHandles; j++)
- {
- int viewStart = j + i * _levels;
- int views = _hasMipViews ? 1 : _levels; // A mip view is also a layer view.
- handles[handleIndex++] = GenerateHandles(viewStart, views);
- }
- }
- }
- }
- ReplaceHandles(handles);
- }
- /// <summary>
- /// Ensure that there is a handle for each potential texture view. Required for copy dependencies to work.
- /// </summary>
- private void EnsureFullSubdivision()
- {
- if (!(_hasLayerViews && _hasMipViews))
- {
- _hasLayerViews = true;
- _hasMipViews = true;
- RecalculateHandleRegions();
- }
- }
- /// <summary>
- /// Create a copy dependency between this texture group, and a texture at a given layer/level offset.
- /// </summary>
- /// <param name="other">The view compatible texture to create a dependency to</param>
- /// <param name="firstLayer">The base layer of the given texture relative to the storage</param>
- /// <param name="firstLevel">The base level of the given texture relative to the storage</param>
- /// <param name="copyTo">True if this texture is first copied to the given one, false for the opposite direction</param>
- public void CreateCopyDependency(Texture other, int firstLayer, int firstLevel, bool copyTo)
- {
- TextureGroup otherGroup = other.Group;
- EnsureFullSubdivision();
- otherGroup.EnsureFullSubdivision();
- // Get the location of each texture within its storage, so we can find the handles to apply the dependency to.
- // This can consist of multiple disjoint regions, for example if this is a mip slice of an array texture.
- var targetRange = new List<(int BaseHandle, int RegionCount)>();
- var otherRange = new List<(int BaseHandle, int RegionCount)>();
- EvaluateRelevantHandles(firstLayer, firstLevel, other.Info.GetSlices(), other.Info.Levels, (baseHandle, regionCount, split) => targetRange.Add((baseHandle, regionCount)));
- otherGroup.EvaluateRelevantHandles(other, (baseHandle, regionCount, split) => otherRange.Add((baseHandle, regionCount)));
- int targetIndex = 0;
- int otherIndex = 0;
- (int Handle, int RegionCount) targetRegion = (0, 0);
- (int Handle, int RegionCount) otherRegion = (0, 0);
- while (true)
- {
- if (targetRegion.RegionCount == 0)
- {
- if (targetIndex >= targetRange.Count)
- {
- break;
- }
- targetRegion = targetRange[targetIndex++];
- }
- if (otherRegion.RegionCount == 0)
- {
- if (otherIndex >= otherRange.Count)
- {
- break;
- }
- otherRegion = otherRange[otherIndex++];
- }
- TextureGroupHandle handle = _handles[targetRegion.Handle++];
- TextureGroupHandle otherHandle = other.Group._handles[otherRegion.Handle++];
- targetRegion.RegionCount--;
- otherRegion.RegionCount--;
- handle.CreateCopyDependency(otherHandle, copyTo);
- // If "copyTo" is true, this texture must copy to the other.
- // Otherwise, it must copy to this texture.
- if (copyTo)
- {
- otherHandle.Copy(_context, handle);
- }
- else
- {
- handle.Copy(_context, otherHandle);
- }
- }
- }
- /// <summary>
- /// Creates a copy dependency to another texture group, where handles overlap.
- /// Scans through all handles to find compatible patches in the other group.
- /// </summary>
- /// <param name="other">The texture group that overlaps this one</param>
- /// <param name="copyTo">True if this texture is first copied to the given one, false for the opposite direction</param>
- public void CreateCopyDependency(TextureGroup other, bool copyTo)
- {
- for (int i = 0; i < _allOffsets.Length; i++)
- {
- (int layer, int level) = GetLayerLevelForView(i);
- MultiRange handleRange = Storage.Range.GetSlice((ulong)_allOffsets[i], 1);
- ulong handleBase = handleRange.GetSubRange(0).Address;
- for (int j = 0; j < other._handles.Length; j++)
- {
- (int otherLayer, int otherLevel) = other.GetLayerLevelForView(j);
- MultiRange otherHandleRange = other.Storage.Range.GetSlice((ulong)other._allOffsets[j], 1);
- ulong otherHandleBase = otherHandleRange.GetSubRange(0).Address;
- if (handleBase == otherHandleBase)
- {
- // Check if the two sizes are compatible.
- TextureInfo info = Storage.Info;
- TextureInfo otherInfo = other.Storage.Info;
- if (TextureCompatibility.ViewLayoutCompatible(info, otherInfo, level, otherLevel) &&
- TextureCompatibility.CopySizeMatches(info, otherInfo, level, otherLevel))
- {
- // These textures are copy compatible. Create the dependency.
- EnsureFullSubdivision();
- other.EnsureFullSubdivision();
- TextureGroupHandle handle = _handles[i];
- TextureGroupHandle otherHandle = other._handles[j];
- handle.CreateCopyDependency(otherHandle, copyTo);
- // If "copyTo" is true, this texture must copy to the other.
- // Otherwise, it must copy to this texture.
- if (copyTo)
- {
- otherHandle.Copy(_context, handle);
- }
- else
- {
- handle.Copy(_context, otherHandle);
- }
- }
- }
- }
- }
- }
- /// <summary>
- /// Registers another texture group as an incompatible overlap, if not already registered.
- /// </summary>
- /// <param name="other">The texture group to add to the incompatible overlaps list</param>
- /// <param name="copy">True if the overlap should register copy dependencies</param>
- public void RegisterIncompatibleOverlap(TextureIncompatibleOverlap other, bool copy)
- {
- if (!_incompatibleOverlaps.Exists(overlap => overlap.Group == other.Group))
- {
- if (copy && other.Compatibility == TextureViewCompatibility.LayoutIncompatible)
- {
- // Any of the group's views may share compatibility, even if the parents do not fully.
- CreateCopyDependency(other.Group, false);
- }
- _incompatibleOverlaps.Add(other);
- other.Group._incompatibleOverlaps.Add(new TextureIncompatibleOverlap(this, other.Compatibility));
- }
- other.Group.SignalIncompatibleOverlapModified();
- SignalIncompatibleOverlapModified();
- }
- /// <summary>
- /// Clear modified flags in the given range.
- /// This will stop any GPU written data from flushing or copying to dependent textures.
- /// </summary>
- /// <param name="range">The range to clear modified flags in</param>
- /// <param name="ignore">Ignore handles that have a copy dependency to the specified group</param>
- public void ClearModified(MultiRange range, TextureGroup ignore = null)
- {
- TextureGroupHandle[] handles = _handles;
- foreach (TextureGroupHandle handle in handles)
- {
- // Handles list is not modified by another thread, only replaced, so this is thread safe.
- // Remove modified flags from all overlapping handles, so that the textures don't flush to unmapped/remapped GPU memory.
- MultiRange subRange = Storage.Range.GetSlice((ulong)handle.Offset, (ulong)handle.Size);
- if (range.OverlapsWith(subRange))
- {
- if ((ignore == null || !handle.HasDependencyTo(ignore)) && handle.Modified)
- {
- handle.Modified = false;
- Storage.SignalModifiedDirty();
- lock (handle.Overlaps)
- {
- foreach (Texture texture in handle.Overlaps)
- {
- texture.SignalModifiedDirty();
- }
- }
- }
- }
- }
- Storage.SignalModifiedDirty();
- if (_views != null)
- {
- foreach (Texture texture in _views)
- {
- texture.SignalModifiedDirty();
- }
- }
- }
- /// <summary>
- /// A flush has been requested on a tracked region. Flush texture data for the given handle.
- /// </summary>
- /// <param name="handle">The handle this flush action is for</param>
- /// <param name="address">The address of the flushing memory access</param>
- /// <param name="size">The size of the flushing memory access</param>
- public void FlushAction(TextureGroupHandle handle, ulong address, ulong size)
- {
- // There is a small gap here where the action is removed but _actionRegistered is still 1.
- // In this case it will skip registering the action, but here we are already handling it,
- // so there shouldn't be any issue as it's the same handler for all actions.
- handle.ClearActionRegistered();
- if (!handle.Modified)
- {
- return;
- }
- _context.Renderer.BackgroundContextAction(() =>
- {
- handle.Sync(_context);
- Storage.SignalModifiedDirty();
- lock (handle.Overlaps)
- {
- foreach (Texture texture in handle.Overlaps)
- {
- texture.SignalModifiedDirty();
- }
- }
- if (TextureCompatibility.CanTextureFlush(Storage.Info, _context.Capabilities))
- {
- FlushSliceRange(false, handle.BaseSlice, handle.BaseSlice + handle.SliceCount, Storage.GetFlushTexture());
- }
- });
- }
- /// <summary>
- /// Dispose this texture group, disposing all related memory tracking handles.
- /// </summary>
- public void Dispose()
- {
- foreach (TextureGroupHandle group in _handles)
- {
- group.Dispose();
- }
- foreach (TextureIncompatibleOverlap incompatible in _incompatibleOverlaps)
- {
- incompatible.Group._incompatibleOverlaps.RemoveAll(overlap => overlap.Group == this);
- }
- }
- }
- }
|