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 TextureHandle(int handle, bool shared = false) { this.handle = new ResourceHandle(handle, RenderGraphResourceType.Texture, shared); } /// /// 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(); } /// /// 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; ///Special treatment of the VR eye texture used in stereoscopic rendering. public VRTextureUsage vrUsage; ///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; /// ///If all passes writing to a texture are culled by Dynamic Render Pass Culling, it will automatically fallback to a similar preallocated texture.\n ///Set this to false to force the allocation. /// public bool disableFallBackToImportedTexture; // 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; vrUsage = VRTextureUsage.None; // 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 determine 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 + (int)vrUsage; 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, "TextureResource: CreatePooledGraphicsResource should only be called for regular pooled resources"); int hashCode = desc.GetHashCode(); if (graphicsResource != null) throw new InvalidOperationException(string.Format("TextureResource: 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(desc.name); } cachedHash = hashCode; pool.RegisterFrameAllocation(cachedHash, graphicsResource); graphicsResource.m_Name = desc.name; } public override void ReleasePooledGraphicsResource(int frameIndex) { if (graphicsResource == null) throw new InvalidOperationException($"TextureResource: 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, desc.vrUsage, 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, desc.vrUsage, 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, desc.vrUsage, 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); } } } }