/// <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> /// 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> /// Signal that a copy dependent texture has been modified, and must have its data copied to this one. /// </summary> /// <param name="copyFrom">The texture handle that must defer a copy to this one</param> public void DeferCopy(TextureGroupHandle copyFrom) { DeferredCopy = copyFrom; _group.Storage.SignalGroupDirty(); foreach (Texture overlap in Overlaps) { overlap.SignalGroupDirty(); } }
/// <summary> /// Synchronize dependent textures, if any of them have deferred a copy from this texture. /// </summary> public void SynchronizeDependents() { foreach (TextureDependency dependency in Dependencies) { TextureGroupHandle otherHandle = dependency.Other.Handle; if (otherHandle.DeferredCopy == this) { otherHandle._group.Storage.SynchronizeMemory(); } } }
/// <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> /// A flush has been requested on a tracked region. Find an appropriate view to flush. /// </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) { Storage.ExternalFlush(address, size); lock (handle.Overlaps) { foreach (Texture overlap in handle.Overlaps) { overlap.ExternalFlush(address, size); } } handle.Modified = false; }
/// <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> /// Create a copy dependency between this handle, and another. /// </summary> /// <param name="other">The handle to create a copy dependency to</param> /// <param name="copyToOther">True if a copy should be deferred to all of the other handle's dependencies</param> public void CreateCopyDependency(TextureGroupHandle other, bool copyToOther = false) { // Does this dependency already exist? foreach (TextureDependency existing in Dependencies) { if (existing.Other.Handle == other) { // Do not need to create it again. May need to set the dirty flag. return; } } _group.HasCopyDependencies = true; other._group.HasCopyDependencies = true; TextureDependency dependency = new TextureDependency(this); TextureDependency otherDependency = new TextureDependency(other); dependency.Other = otherDependency; otherDependency.Other = dependency; Dependencies.Add(dependency); other.Dependencies.Add(otherDependency); // Recursively create dependency: // All of this handle's dependencies must depend on the other. foreach (TextureDependency existing in Dependencies.ToArray()) { if (existing != dependency && existing.Other.Handle != other) { existing.Other.Handle.CreateCopyDependency(other); } } // All of the other handle's dependencies must depend on this. foreach (TextureDependency existing in other.Dependencies.ToArray()) { if (existing != otherDependency && existing.Other.Handle != this) { existing.Other.Handle.CreateCopyDependency(this); if (copyToOther) { existing.Other.Handle.DeferCopy(this); } } } }
/// <summary> /// Inherit modified flags and dependencies from another texture handle. /// </summary> /// <param name="old">The texture handle to inherit from</param> public void Inherit(TextureGroupHandle old) { Modified |= old.Modified; foreach (TextureDependency dependency in old.Dependencies.ToArray()) { CreateCopyDependency(dependency.Other.Handle); if (dependency.Other.Handle.DeferredCopy == old) { dependency.Other.Handle.DeferredCopy = this; } } DeferredCopy = old.DeferredCopy; }
/// <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) { 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]; } var groupHandle = new TextureGroupHandle(this, _allOffsets[viewStart], (ulong)size, _views, firstLayer, firstLevel, result.ToArray()); foreach (CpuRegionHandle handle in result) { handle.RegisterDirtyEvent(() => DirtyAction(groupHandle)); } return(groupHandle); }
/// <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> /// <param name="registerAction">True if the flushing read action should be registered, false otherwise</param> public void SignalModifying(Texture texture, bool bound, bool registerAction) { EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => { for (int i = 0; i < regionCount; i++) { TextureGroupHandle group = _handles[baseHandle + i]; group.SignalModifying(bound); if (registerAction) { RegisterAction(group); } } }); }
/// <summary> /// Perform a copy from the provided handle to this one, or perform a deferred copy if none is provided. /// </summary> /// <param name="fromHandle">The handle to copy from. If not provided, this method will copy from and clear the deferred copy instead</param> /// <returns>True if the copy was performed, false otherwise</returns> public bool Copy(TextureGroupHandle fromHandle = null) { bool result = false; if (fromHandle == null) { fromHandle = DeferredCopy; if (fromHandle != null && fromHandle._bindCount == 0) { // Repeat the copy in future if the bind count is greater than 0. DeferredCopy = null; } } if (fromHandle != null) { // If the copy texture is dirty, do not copy. Its data no longer matters, and this handle should also be dirty. if (!fromHandle.CheckDirty()) { Texture from = fromHandle._group.Storage; Texture to = _group.Storage; if (from.ScaleFactor != to.ScaleFactor) { to.PropagateScale(from); } from.HostTexture.CopyTo( to.HostTexture, fromHandle._firstLayer, _firstLayer, fromHandle._firstLevel, _firstLevel); Modified = true; _group.RegisterAction(this); result = true; } } return(result); }
/// <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(handle); } else { handle.Copy(otherHandle); } } }
/// <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]; for (int i = 0; i < TextureRange.Count; i++) { var currentRange = TextureRange.GetSubRange(i); cpuRegionHandles[i] = GenerateHandle(currentRange.Address, currentRange.Size); } var groupHandle = new TextureGroupHandle(this, 0, Storage.Size, _views, 0, 0, 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> /// 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) { 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 handleModified = false; bool handleUnmapped = false; foreach (CpuRegionHandle handle in group.Handles) { if (handle.Dirty) { handle.Reprotect(); handleDirty = true; } else { handleUnmapped |= handle.Unmapped; handleModified |= modified; } } // 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()) { anyModified |= true; // The copy target has been modified. handleDirty = false; } else { anyModified |= handleModified; 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> /// Perform a copy from the provided handle to this one, or perform a deferred copy if none is provided. /// </summary> /// <param name="context">GPU context to register sync for modified handles</param> /// <param name="fromHandle">The handle to copy from. If not provided, this method will copy from and clear the deferred copy instead</param> /// <returns>True if the copy was performed, false otherwise</returns> public bool Copy(GpuContext context, TextureGroupHandle fromHandle = null) { bool result = false; bool shouldCopy = false; if (fromHandle == null) { fromHandle = DeferredCopy; if (fromHandle != null) { // Only copy if the copy texture is still modified. // It will be set as unmodified if new data is written from CPU, as the data previously in the texture will flush. // It will also set as unmodified if a copy is deferred to it. shouldCopy = fromHandle.Modified; if (fromHandle._bindCount == 0) { // Repeat the copy in future if the bind count is greater than 0. DeferredCopy = null; } } } else { // Copies happen directly when initializing a copy dependency. // If dirty, do not copy. Its data no longer matters, and this handle should also be dirty. // Also, only direct copy if the data in this handle is not already modified (can be set by copies from modified handles). shouldCopy = !fromHandle.CheckDirty() && (fromHandle.Modified || !Modified); } if (shouldCopy) { Texture from = fromHandle._group.Storage; Texture to = _group.Storage; if (from.ScaleFactor != to.ScaleFactor) { to.PropagateScale(from); } from.HostTexture.CopyTo( to.HostTexture, fromHandle._firstLayer, _firstLayer, fromHandle._firstLevel, _firstLevel); if (fromHandle.Modified) { Modified = true; RegisterSync(context); } result = true; } return(result); }