318 lines
12 KiB
C#
318 lines
12 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace UnityEngine.Rendering.RenderGraphModule
|
|
{
|
|
// RendererList is a different case so not represented here.
|
|
internal enum RenderGraphResourceType
|
|
{
|
|
Texture = 0,
|
|
Buffer,
|
|
AccelerationStructure,
|
|
Count
|
|
}
|
|
|
|
// For performance reasons, ResourceHandle is readonly.
|
|
// To update an existing instance with a new version, recreate it using its copy constructor
|
|
internal readonly struct ResourceHandle : IEquatable<ResourceHandle>
|
|
{
|
|
// Note on handles validity.
|
|
// PassData classes used during render graph passes are pooled and because of that, when users don't fill them completely,
|
|
// they can contain stale handles from a previous render graph execution that could still be considered valid if we only checked the index.
|
|
// In order to avoid using those, we incorporate the execution index in a hash to make sure the handle is coming from the current execution.
|
|
// If not, it's considered invalid.
|
|
// The validity is stored in a separate field, allowing for a full 32-bit validity check. (2^32 =~ 4B (at 60fps it is more than 2 years) 4,294,967,295 unique execution indices before a collision)
|
|
// The m_VersionIndex field contains both index and version:
|
|
// - Index (lower 16 bits = 65,535 unique indices)
|
|
// - Version (upper 16 bits with one for not versioned. 15 bits = 32767 unique versions).
|
|
// - Bit 31 is used as a "not versioned" flag for faster checking.
|
|
const uint kIndexMask = 0xFFFF;
|
|
const uint kVersionMask = 0x7FFF0000;
|
|
const int kVersionShift = 16;
|
|
const uint kNotVersionedBit = 0x80000000;
|
|
|
|
private readonly uint m_VersionIndex;
|
|
private readonly uint m_Validity;
|
|
private readonly RenderGraphResourceType m_Type;
|
|
|
|
static uint s_CurrentValidBit = 1;
|
|
|
|
#if UNITY_EDITOR
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
|
|
static void ResetStaticsOnLoad()
|
|
{
|
|
s_CurrentValidBit = 1;
|
|
}
|
|
#endif
|
|
|
|
public int index
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get { return (int)(m_VersionIndex & kIndexMask); }
|
|
}
|
|
public int iType
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get { return (int)type; }
|
|
}
|
|
public int version
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get
|
|
{
|
|
return (m_VersionIndex & kNotVersionedBit) != 0 ? -1 : (int)((m_VersionIndex & kVersionMask) >> kVersionShift);
|
|
}
|
|
}
|
|
public RenderGraphResourceType type
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get { return m_Type; }
|
|
}
|
|
|
|
internal ResourceHandle(int index, RenderGraphResourceType type, bool shared)
|
|
{
|
|
Debug.Assert(index > 0 && index <= 0xFFFF, "ResourceHandle: Invalid index, values should be >0 && <65536");
|
|
m_VersionIndex = ((uint)index & kIndexMask) | kNotVersionedBit;
|
|
m_Validity = s_CurrentValidBit;
|
|
m_Type = type;
|
|
}
|
|
|
|
internal ResourceHandle(in ResourceHandle h, int version)
|
|
{
|
|
Debug.Assert(version >= 0 && version <= 0x7FFF, "ResourceHandle: Invalid version, values should be >=0 && <32768");
|
|
uint versionBits = ((uint)version << kVersionShift) & kVersionMask;
|
|
m_VersionIndex = (h.m_VersionIndex & kIndexMask) | versionBits;
|
|
m_Validity = h.m_Validity;
|
|
m_Type = h.type;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public bool IsValid()
|
|
{
|
|
return m_Validity != 0 && (m_Validity == s_CurrentValidBit);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public bool IsNull()
|
|
{
|
|
if (index == 0)
|
|
{
|
|
// Make sure everything is zero
|
|
Debug.Assert(m_VersionIndex == 0);
|
|
Debug.Assert(m_Validity == 0);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static void NewFrame(int executionIndex)
|
|
{
|
|
uint previousValidBit = s_CurrentValidBit;
|
|
|
|
var hasher = HashFNV1A32.Create();
|
|
hasher.Append(executionIndex);
|
|
s_CurrentValidBit = (uint)hasher.value;
|
|
// In case the current valid bit is 0, even though perfectly valid, 0 represents an invalid handle, hence we'll
|
|
// trigger an invalid state incorrectly. To account for this, we actually skip 0 as a viable s_CurrentValidBit and
|
|
// start from 1 again.
|
|
if (s_CurrentValidBit == 0)
|
|
{
|
|
// We need to make sure we don't pick the same value twice.
|
|
uint value = 1;
|
|
while (previousValidBit == value)
|
|
value++;
|
|
s_CurrentValidBit = value;
|
|
}
|
|
}
|
|
|
|
public bool IsVersioned
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get
|
|
{
|
|
return (m_VersionIndex & kNotVersionedBit) == 0;
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public bool Equals(ResourceHandle hdl)
|
|
{
|
|
return hdl.m_VersionIndex == this.m_VersionIndex && hdl.m_Validity == this.m_Validity && hdl.type == this.type;
|
|
}
|
|
|
|
public static bool operator ==(ResourceHandle lhs, ResourceHandle rhs) => lhs.Equals(rhs);
|
|
|
|
public static bool operator !=(ResourceHandle lhs, ResourceHandle rhs) => !lhs.Equals(rhs);
|
|
|
|
public override bool Equals(object obj) => obj is ResourceHandle other && Equals(other);
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
var hashCode = HashFNV1A32.Create();
|
|
hashCode.Append(m_VersionIndex);
|
|
hashCode.Append(m_Validity);
|
|
hashCode.Append(m_Type);
|
|
return hashCode.value;
|
|
}
|
|
}
|
|
|
|
class IRenderGraphResource
|
|
{
|
|
public bool imported;
|
|
public bool shared;
|
|
public bool sharedExplicitRelease;
|
|
public bool requestFallBack;
|
|
public uint writeCount;
|
|
public uint readCount;
|
|
public int cachedHash;
|
|
public int transientPassIndex;
|
|
public int sharedResourceLastFrameUsed;
|
|
public bool isBackBuffer;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public virtual void Reset(IRenderGraphResourcePool _ = null)
|
|
{
|
|
imported = false;
|
|
shared = false;
|
|
sharedExplicitRelease = false;
|
|
cachedHash = -1;
|
|
transientPassIndex = -1;
|
|
sharedResourceLastFrameUsed = -1;
|
|
requestFallBack = false;
|
|
writeCount = 0;
|
|
readCount = 0;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public virtual string GetName()
|
|
{
|
|
return "";
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public virtual bool IsCreated()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public virtual uint IncrementWriteCount()
|
|
{
|
|
writeCount++;
|
|
return writeCount;
|
|
}
|
|
|
|
// readCount is currently not used in the HDRP Compiler.
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public virtual void IncrementReadCount()
|
|
{
|
|
readCount++;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public virtual bool NeedsFallBack()
|
|
{
|
|
return requestFallBack && writeCount == 0;
|
|
}
|
|
|
|
public virtual void CreatePooledGraphicsResource(int frameIndex, int executionCount, bool usedByAsyncComputePass) { }
|
|
public virtual void CreateGraphicsResource() { }
|
|
public virtual void UpdateGraphicsResource() { }
|
|
public virtual void ReleasePooledGraphicsResource(int frameIndex, int executionCount) { }
|
|
public virtual void ReleaseGraphicsResource() { }
|
|
public virtual int GetSortIndex() { return 0; }
|
|
public virtual HashFNV1A32 GetDescHash() { return HashFNV1A32.Create(); }
|
|
}
|
|
|
|
[DebuggerDisplay("Resource ({GetType().Name}:{GetName()})")]
|
|
abstract class RenderGraphResource<DescType, ResType>
|
|
: IRenderGraphResource
|
|
where DescType : struct
|
|
where ResType : class
|
|
{
|
|
public DescType desc;
|
|
public bool validDesc; // Does the descriptor contain valid data (this is not always the case for imported resources)
|
|
public ResType graphicsResource;
|
|
|
|
protected RenderGraphResourcePool<ResType> m_Pool;
|
|
|
|
protected RenderGraphResource()
|
|
{
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override void Reset(IRenderGraphResourcePool pool = null)
|
|
{
|
|
base.Reset();
|
|
m_Pool = pool as RenderGraphResourcePool<ResType>;
|
|
graphicsResource = null;
|
|
validDesc = false;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override bool IsCreated()
|
|
{
|
|
return graphicsResource != null;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override void ReleaseGraphicsResource()
|
|
{
|
|
graphicsResource = null;
|
|
}
|
|
|
|
public override void CreatePooledGraphicsResource(int frameIndex, int executionCount, bool usedByAsyncComputePass)
|
|
{
|
|
Debug.Assert(m_Pool != null, "RenderGraphResource: CreatePooledGraphicsResource should only be called for regular pooled resources");
|
|
|
|
var hash = GetDescHash();
|
|
// Resource pooling is a problem with async compute because recycling a pooled resource introduces a
|
|
// hidden data dependency that is not visible to the compiler, causing data races between graphics and compute
|
|
// queues. In the general case this is solved by the "delayed last use" logic in NativePassCompiler.TryGetDelayedLastUsePassId(),
|
|
// which delays returning of the resource to the pool to a point where its memory can be safely reused.
|
|
//
|
|
// By mixing async usage into the pool hash, we split "graphics queue only" and "touched from async queue"
|
|
// into two separate pool buckets, across which memory is never shared. This allows graphics-only resources
|
|
// to be reused safely with each other within a frame without needing to delay their return to the pool.
|
|
// If we were to NOT do this, the delay logic would have to run for every graphics-only resource as well,
|
|
// extending their lifetimes in any graph that uses async compute at all and severely reducing the
|
|
// effectiveness of the pooling.
|
|
hash.Append(usedByAsyncComputePass);
|
|
int hashCode = hash.value;
|
|
|
|
if (graphicsResource != null)
|
|
throw new InvalidOperationException($"RenderGraphResource: Trying to create an already created resource ({GetName()}). Resource was probably declared for writing more than once in the same pass.");
|
|
|
|
// If the pool doesn't have any available resource that we can use, we will create one
|
|
// In any case, we will update the graphicsResource name based on the RenderGraph resource name
|
|
if (!m_Pool.TryGetResource(hashCode, out graphicsResource, frameIndex, executionCount))
|
|
{
|
|
CreateGraphicsResource();
|
|
}
|
|
else
|
|
{
|
|
UpdateGraphicsResource();
|
|
}
|
|
|
|
cachedHash = hashCode;
|
|
m_Pool.RegisterFrameAllocation(cachedHash, graphicsResource);
|
|
}
|
|
|
|
public override void ReleasePooledGraphicsResource(int frameIndex, int executionCount)
|
|
{
|
|
if (graphicsResource == null)
|
|
throw new InvalidOperationException($"RenderGraphResource: Tried to release a resource ({GetName()}) that was never created. Check that there is at least one pass writing to it first.");
|
|
|
|
// Shared resources don't use the pool
|
|
if (m_Pool != null)
|
|
{
|
|
m_Pool.ReleaseResource(cachedHash, graphicsResource, frameIndex, executionCount);
|
|
m_Pool.UnregisterFrameAllocation(cachedHash, graphicsResource);
|
|
}
|
|
|
|
Reset();
|
|
}
|
|
}
|
|
}
|