using System;
using System.Diagnostics;
using UnityEngine.Rendering;
namespace UnityEngine.Experimental.Rendering.RenderGraphModule
{
///
/// Texture resource handle.
///
[DebuggerDisplay("Texture ({handle.index})")]
public struct TextureHandle
{
private static TextureHandle s_NullHandle = new TextureHandle();
///
/// Returns a null texture handle
///
/// A null texture handle.
public static TextureHandle nullHandle { get { return s_NullHandle; } }
internal ResourceHandle handle;
internal ResourceHandle fallBackResource;
internal TextureHandle(int handle, bool shared = false) { this.handle = new ResourceHandle(handle, RenderGraphResourceType.Texture, shared); fallBackResource = s_NullHandle.handle; }
///
/// Cast to RenderTargetIdentifier
///
/// Input TextureHandle.
/// Resource as a RenderTargetIdentifier.
public static implicit operator RenderTargetIdentifier(TextureHandle texture) => texture.IsValid() ? RenderGraphResourceRegistry.current.GetTexture(texture) : default(RenderTargetIdentifier);
///
/// Cast to Texture
///
/// Input TextureHandle.
/// Resource as a Texture.
public static implicit operator Texture(TextureHandle texture) => texture.IsValid() ? RenderGraphResourceRegistry.current.GetTexture(texture) : null;
///
/// Cast to RenderTexture
///
/// Input TextureHandle.
/// Resource as a RenderTexture.
public static implicit operator RenderTexture(TextureHandle texture) => texture.IsValid() ? RenderGraphResourceRegistry.current.GetTexture(texture) : null;
///
/// Cast to RTHandle
///
/// Input TextureHandle.
/// Resource as a RTHandle.
public static implicit operator RTHandle(TextureHandle texture) => texture.IsValid() ? RenderGraphResourceRegistry.current.GetTexture(texture) : null;
///
/// Return true if the handle is valid.
///
/// True if the handle is valid.
public bool IsValid() => handle.IsValid();
///
/// Sets the fallback resource
///
/// The texture handle
public void SetFallBackResource(TextureHandle texture) { fallBackResource = texture.handle; }
}
///
/// The mode that determines the size of a Texture.
///
public enum TextureSizeMode
{
///Explicit size.
Explicit,
///Size automatically scaled by a Vector.
Scale,
///Size automatically scaled by a Functor.
Functor
}
#if UNITY_2020_2_OR_NEWER
///
/// Subset of the texture desc containing information for fast memory allocation (when platform supports it)
///
public struct FastMemoryDesc
{
///Whether the texture will be in fast memory.
public bool inFastMemory;
///Flag to determine what parts of the render target is spilled if not fully resident in fast memory.
public FastMemoryFlags flags;
///How much of the render target is to be switched into fast memory (between 0 and 1).
public float residencyFraction;
}
#endif
///
/// Descriptor used to create texture resources
///
public struct TextureDesc
{
///Texture sizing mode.
public TextureSizeMode sizeMode;
///Texture width.
public int width;
///Texture height.
public int height;
///Number of texture slices..
public int slices;
///Texture scale.
public Vector2 scale;
///Texture scale function.
public ScaleFunc func;
///Depth buffer bit depth.
public DepthBits depthBufferBits;
///Color format.
public GraphicsFormat colorFormat;
///Filtering mode.
public FilterMode filterMode;
///Addressing mode.
public TextureWrapMode wrapMode;
///Texture dimension.
public TextureDimension dimension;
///Enable random UAV read/write on the texture.
public bool enableRandomWrite;
///Texture needs mip maps.
public bool useMipMap;
///Automatically generate mip maps.
public bool autoGenerateMips;
///Texture is a shadow map.
public bool isShadowMap;
///Anisotropic filtering level.
public int anisoLevel;
///Mip map bias.
public float mipMapBias;
///Number of MSAA samples.
public MSAASamples msaaSamples;
///Bind texture multi sampled.
public bool bindTextureMS;
///Texture uses dynamic scaling.
public bool useDynamicScale;
///Memory less flag.
public RenderTextureMemoryless memoryless;
///Texture name.
public string name;
#if UNITY_2020_2_OR_NEWER
///Descriptor to determine how the texture will be in fast memory on platform that supports it.
public FastMemoryDesc fastMemoryDesc;
#endif
///Determines whether the texture will fallback to a black texture if it is read without ever writing to it.
public bool fallBackToBlackTexture;
// Initial state. Those should not be used in the hash
///Texture needs to be cleared on first use.
public bool clearBuffer;
///Clear color.
public Color clearColor;
void InitDefaultValues(bool dynamicResolution, bool xrReady)
{
useDynamicScale = dynamicResolution;
// XR Ready
if (xrReady)
{
slices = TextureXR.slices;
dimension = TextureXR.dimension;
}
else
{
slices = 1;
dimension = TextureDimension.Tex2D;
}
}
///
/// TextureDesc constructor for a texture using explicit size
///
/// Texture width
/// Texture height
/// Use dynamic resolution
/// Set this to true if the Texture is a render texture in an XR setting.
public TextureDesc(int width, int height, bool dynamicResolution = false, bool xrReady = false)
: this()
{
// Size related init
sizeMode = TextureSizeMode.Explicit;
this.width = width;
this.height = height;
// Important default values not handled by zero construction in this()
msaaSamples = MSAASamples.None;
InitDefaultValues(dynamicResolution, xrReady);
}
///
/// TextureDesc constructor for a texture using a fixed scaling
///
/// RTHandle scale used for this texture
/// Use dynamic resolution
/// Set this to true if the Texture is a render texture in an XR setting.
public TextureDesc(Vector2 scale, bool dynamicResolution = false, bool xrReady = false)
: this()
{
// Size related init
sizeMode = TextureSizeMode.Scale;
this.scale = scale;
// Important default values not handled by zero construction in this()
msaaSamples = MSAASamples.None;
dimension = TextureDimension.Tex2D;
InitDefaultValues(dynamicResolution, xrReady);
}
///
/// TextureDesc constructor for a texture using a functor for scaling
///
/// Function used to determnine the texture size
/// Use dynamic resolution
/// Set this to true if the Texture is a render texture in an XR setting.
public TextureDesc(ScaleFunc func, bool dynamicResolution = false, bool xrReady = false)
: this()
{
// Size related init
sizeMode = TextureSizeMode.Functor;
this.func = func;
// Important default values not handled by zero construction in this()
msaaSamples = MSAASamples.None;
dimension = TextureDimension.Tex2D;
InitDefaultValues(dynamicResolution, xrReady);
}
///
/// Copy constructor
///
///
public TextureDesc(TextureDesc input)
{
this = input;
}
///
/// Hash function
///
/// The texture descriptor hash.
public override int GetHashCode()
{
int hashCode = 17;
unchecked
{
switch (sizeMode)
{
case TextureSizeMode.Explicit:
hashCode = hashCode * 23 + width;
hashCode = hashCode * 23 + height;
break;
case TextureSizeMode.Functor:
if (func != null)
hashCode = hashCode * 23 + func.GetHashCode();
break;
case TextureSizeMode.Scale:
hashCode = hashCode * 23 + scale.x.GetHashCode();
hashCode = hashCode * 23 + scale.y.GetHashCode();
break;
}
hashCode = hashCode * 23 + mipMapBias.GetHashCode();
hashCode = hashCode * 23 + slices;
hashCode = hashCode * 23 + (int)depthBufferBits;
hashCode = hashCode * 23 + (int)colorFormat;
hashCode = hashCode * 23 + (int)filterMode;
hashCode = hashCode * 23 + (int)wrapMode;
hashCode = hashCode * 23 + (int)dimension;
hashCode = hashCode * 23 + (int)memoryless;
hashCode = hashCode * 23 + anisoLevel;
hashCode = hashCode * 23 + (enableRandomWrite ? 1 : 0);
hashCode = hashCode * 23 + (useMipMap ? 1 : 0);
hashCode = hashCode * 23 + (autoGenerateMips ? 1 : 0);
hashCode = hashCode * 23 + (isShadowMap ? 1 : 0);
hashCode = hashCode * 23 + (bindTextureMS ? 1 : 0);
hashCode = hashCode * 23 + (useDynamicScale ? 1 : 0);
hashCode = hashCode * 23 + (int)msaaSamples;
#if UNITY_2020_2_OR_NEWER
hashCode = hashCode * 23 + (fastMemoryDesc.inFastMemory ? 1 : 0);
#endif
}
return hashCode;
}
}
[DebuggerDisplay("TextureResource ({desc.name})")]
class TextureResource : RenderGraphResource
{
static int m_TextureCreationIndex;
public override string GetName()
{
if (imported && !shared)
return graphicsResource != null ? graphicsResource.name : "null resource";
else
return desc.name;
}
// NOTE:
// Next two functions should have been implemented in RenderGraphResource but for some reason,
// when doing so, it's impossible to break in the Texture version of the virtual function (with VS2017 at least), making this completely un-debuggable.
// To work around this, we just copy/pasted the implementation in each final class...
public override void CreatePooledGraphicsResource()
{
Debug.Assert(m_Pool != null, "CreatePooledGraphicsResource should only be called for regular pooled resources");
int hashCode = desc.GetHashCode();
if (graphicsResource != null)
throw new InvalidOperationException(string.Format("Trying to create an already created resource ({0}). Resource was probably declared for writing more than once in the same pass.", GetName()));
var pool = m_Pool as TexturePool;
if (!pool.TryGetResource(hashCode, out graphicsResource))
{
CreateGraphicsResource();
}
cachedHash = hashCode;
pool.RegisterFrameAllocation(cachedHash, graphicsResource);
}
public override void ReleasePooledGraphicsResource(int frameIndex)
{
if (graphicsResource == null)
throw new InvalidOperationException($"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
var pool = m_Pool as TexturePool;
if (pool != null)
{
pool.ReleaseResource(cachedHash, graphicsResource, frameIndex);
pool.UnregisterFrameAllocation(cachedHash, graphicsResource);
}
Reset(null);
}
public override void CreateGraphicsResource(string name = "")
{
// Textures are going to be reused under different aliases along the frame so we can't provide a specific name upon creation.
// The name in the desc is going to be used for debugging purpose and render graph visualization.
if (name == "")
name = $"RenderGraphTexture_{m_TextureCreationIndex++}";
switch (desc.sizeMode)
{
case TextureSizeMode.Explicit:
graphicsResource = RTHandles.Alloc(desc.width, desc.height, desc.slices, desc.depthBufferBits, desc.colorFormat, desc.filterMode, desc.wrapMode, desc.dimension, desc.enableRandomWrite,
desc.useMipMap, desc.autoGenerateMips, desc.isShadowMap, desc.anisoLevel, desc.mipMapBias, desc.msaaSamples, desc.bindTextureMS, desc.useDynamicScale, desc.memoryless, name);
break;
case TextureSizeMode.Scale:
graphicsResource = RTHandles.Alloc(desc.scale, desc.slices, desc.depthBufferBits, desc.colorFormat, desc.filterMode, desc.wrapMode, desc.dimension, desc.enableRandomWrite,
desc.useMipMap, desc.autoGenerateMips, desc.isShadowMap, desc.anisoLevel, desc.mipMapBias, desc.msaaSamples, desc.bindTextureMS, desc.useDynamicScale, desc.memoryless, name);
break;
case TextureSizeMode.Functor:
graphicsResource = RTHandles.Alloc(desc.func, desc.slices, desc.depthBufferBits, desc.colorFormat, desc.filterMode, desc.wrapMode, desc.dimension, desc.enableRandomWrite,
desc.useMipMap, desc.autoGenerateMips, desc.isShadowMap, desc.anisoLevel, desc.mipMapBias, desc.msaaSamples, desc.bindTextureMS, desc.useDynamicScale, desc.memoryless, name);
break;
}
}
public override void ReleaseGraphicsResource()
{
if (graphicsResource != null)
graphicsResource.Release();
base.ReleaseGraphicsResource();
}
public override void LogCreation(RenderGraphLogger logger)
{
logger.LogLine($"Created Texture: {desc.name} (Cleared: {desc.clearBuffer})");
}
public override void LogRelease(RenderGraphLogger logger)
{
logger.LogLine($"Released Texture: {desc.name}");
}
}
class TexturePool : RenderGraphResourcePool
{
protected override void ReleaseInternalResource(RTHandle res)
{
res.Release();
}
protected override string GetResourceName(RTHandle res)
{
return res.rt.name;
}
protected override long GetResourceSize(RTHandle res)
{
return Profiling.Profiler.GetRuntimeMemorySizeLong(res.rt);
}
override protected string GetResourceTypeName()
{
return "Texture";
}
override protected int GetSortIndex(RTHandle res)
{
return res.GetInstanceID();
}
// Another C# nicety.
// We need to re-implement the whole thing every time because:
// - obj.resource.Release is Type specific so it cannot be called on a generic (and there's no shared interface for resources like RTHandle, ComputeBuffers etc)
// - We can't use a virtual release function because it will capture 'this' in the lambda for RemoveAll generating GCAlloc in the process.
override public void PurgeUnusedResources(int currentFrameIndex)
{
// Update the frame index for the lambda. Static because we don't want to capture.
s_CurrentFrameIndex = currentFrameIndex;
m_RemoveList.Clear();
foreach (var kvp in m_ResourcePool)
{
// WARNING: No foreach here. Sorted list GetEnumerator generates garbage...
var list = kvp.Value;
var keys = list.Keys;
var values = list.Values;
for (int i = 0; i < list.Count; ++i)
{
var value = values[i];
if (ShouldReleaseResource(value.frameIndex, s_CurrentFrameIndex))
{
value.resource.Release();
m_RemoveList.Add(keys[i]);
}
}
foreach (var key in m_RemoveList)
list.Remove(key);
}
}
}
}