using System; using System.Diagnostics; using UnityEngine.Experimental.Rendering; using static UnityEngine.Rendering.RenderGraphModule.RenderGraph; namespace UnityEngine.Rendering.RenderGraphModule { // This is a class making it a struct wouldn't help as we pas it around as an interface which means it would be boxed/unboxed anyway // Publicly this class has different faces to help the users with different pass types through type safety but internally // we just have a single implementation for all builders internal class RenderGraphBuilders : IBaseRenderGraphBuilder, IComputeRenderGraphBuilder, IRasterRenderGraphBuilder, IUnsafeRenderGraphBuilder { RenderGraphPass m_RenderPass; RenderGraphResourceRegistry m_Resources; RenderGraph m_RenderGraph; bool m_Disposed; public RenderGraphBuilders() { m_RenderPass = null; m_Resources = null; m_RenderGraph = null; m_Disposed = true; } public void Setup(RenderGraphPass renderPass, RenderGraphResourceRegistry resources, RenderGraph renderGraph) { #if DEVELOPMENT_BUILD || UNITY_EDITOR // If the object is not disposed yet this is an error as the pass is not finished (only in the dispose we register it with the rendergraph) // This is likely cause by a user not doing a clean using and then forgetting to manually dispose the object. if (m_Disposed != true) { throw new Exception(RenderGraph.RenderGraphExceptionMessages.k_UndisposedBuilderPreviousPass); } #endif m_RenderPass = renderPass; m_Resources = resources; m_RenderGraph = renderGraph; m_Disposed = false; renderPass.useAllGlobalTextures = false; if (renderPass.type == RenderGraphPassType.Raster) { CommandBuffer.ThrowOnSetRenderTarget = true; } } public void EnableAsyncCompute(bool value) { m_RenderPass.EnableAsyncCompute(value); } public void AllowPassCulling(bool value) { // This pass cannot be culled if it allows global state modifications if (value && m_RenderPass.allowGlobalState) return; m_RenderPass.AllowPassCulling(value); } public void AllowGlobalStateModification(bool value) { m_RenderPass.AllowGlobalState(value); // This pass cannot be culled if it allows global state modifications if (value) { AllowPassCulling(false); } } /// /// Enable foveated rendering for this pass. /// /// True to enable foveated rendering. public void EnableFoveatedRasterization(bool value) { m_RenderPass.EnableFoveatedRasterization(value); } public BufferHandle CreateTransientBuffer(in BufferDesc desc) { var result = m_Resources.CreateBuffer(desc, m_RenderPass.index); UseTransientResource(result.handle); return result; } public BufferHandle CreateTransientBuffer(in BufferHandle computebuffer) { ref readonly var desc = ref m_Resources.GetBufferResourceDesc(computebuffer.handle); return CreateTransientBuffer(desc); } public TextureHandle CreateTransientTexture(in TextureDesc desc) { var result = m_Resources.CreateTexture(desc, m_RenderPass.index); UseTransientResource(result.handle); return result; } public TextureHandle CreateTransientTexture(in TextureHandle texture) { ref readonly var desc = ref m_Resources.GetTextureResourceDesc(texture.handle); return CreateTransientTexture(desc); } public void GenerateDebugData(bool value) { m_RenderPass.GenerateDebugData(value); } public void Dispose() { Dispose(true); } protected virtual void Dispose(bool disposing) { if (m_Disposed) return; try { if (disposing) { m_RenderGraph.RenderGraphState = RenderGraphState.RecordingGraph; // Use all globals simply means this... we do a UseTexture on all globals so the pass has the correct dependencies. // This of course goes to show how bad an idea shader-system wide globals really are dependency/lifetime tracking wise :-) if (m_RenderPass.useAllGlobalTextures) { foreach (var texture in m_RenderGraph.AllGlobals()) { if (texture.IsValid()) this.UseTexture(texture, AccessFlags.Read); } } // Set globals on the graph fronted side so subsequent passes can have pass dependencies on these global texture handles foreach (var t in m_RenderPass.setGlobalsList) { m_RenderGraph.SetGlobal(t.Item1, t.Item2); } m_RenderGraph.OnPassAdded(m_RenderPass); } } finally { if (m_RenderPass.type == RenderGraphPassType.Raster) { CommandBuffer.ThrowOnSetRenderTarget = false; } m_RenderPass = null; m_Resources = null; m_RenderGraph = null; m_Disposed = true; } } [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] private void CheckWriteTo(in ResourceHandle handle) { if (RenderGraph.enableValidityChecks) { // Write by design generates a new version of the resource. However // you could in theory write to v2 of the resource while there is already // a v3 so this write would then introduce a new v4 of the resource. // This would mean a divergence in the versioning history subsequent versions based on v2 and other subsequent versions based on v3 // this would be very confusing as they are all still refered to by the same texture just different versions // so we decide to disallow this. It can always be (at zero cost) handled by using a "Move" pass to move the divergent version // so it's own texture resource which again has it's own single history of versions. if (handle.IsVersioned) { var name = m_Resources.GetRenderGraphResourceName(handle); throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {handle.type} at index {handle.index} - " + RenderGraph.RenderGraphExceptionMessages.k_WriteToVersionedResource); } if (m_RenderPass.IsWritten(handle)) { // We bump the version and write count if you call USeResource with writing flags. So calling this several times // would lead to the side effect of new versions for every call to UseResource leading to incorrect versions. // In theory we could detect and ignore the second UseResource but we decided to just disallow is as it might also // Stem from user confusion. // It seems the most likely cause of such a situation would be something like: // TextureHandle b = a; // ... // much much code in between, user lost track that a=b // ... // builder.WriteTexture(a) // builder.WriteTexture(b) // > Get this error they were probably thinking they were writing two separate outputs... but they are just two versions of resource 'a' // where they can only differ between by careful management of versioned resources. var name = m_Resources.GetRenderGraphResourceName(handle); throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {handle.type} at index {handle.index} - " + RenderGraph.RenderGraphExceptionMessages.k_WriteToResourceTwice); } } } // Lifetime of a transient resource is one render graph pass private ResourceHandle UseTransientResource(in ResourceHandle inputHandle) { CheckResource(inputHandle); ResourceHandle versionedHandle = inputHandle.IsVersioned ? inputHandle : m_Resources.GetLatestVersionHandle(inputHandle); // Transient resources are always considered written and read in the render graph pass where they are used // Compiler will take it into account later, no need to add them to read and write lists m_RenderPass.AddTransientResource(versionedHandle); return versionedHandle; } private ResourceHandle UseResource(in ResourceHandle inputHandle, AccessFlags flags) { CheckResource(inputHandle); bool discard = (flags & AccessFlags.Discard) != 0; bool read = (flags & AccessFlags.Read) != 0; bool write = (flags & AccessFlags.Write) != 0; ResourceHandle versionedHandle = inputHandle.IsVersioned ? inputHandle : m_Resources.GetLatestVersionHandle(inputHandle); // If we are not discarding the current version and its data, add a "read" dependency on it // this is a bit of a misnomer it really means more like "Preserve existing content or read" if (!discard) { m_Resources.IncrementReadCount(versionedHandle); m_RenderPass.AddResourceRead(versionedHandle); // Implicit read - not user-specified if (!read) { m_RenderPass.implicitReadsList.Add(versionedHandle); } } else { // We are discarding it but we still read it, so we add a dependency on version "0" of this resource if (read) { var zeroVersionHandle = m_Resources.GetZeroVersionHandle(versionedHandle); m_Resources.IncrementReadCount(zeroVersionHandle); m_RenderPass.AddResourceRead(zeroVersionHandle); } } if (write) { CheckWriteTo(inputHandle); // New versioned written by this render graph pass versionedHandle = m_Resources.IncrementWriteCount(inputHandle); m_RenderPass.AddResourceWrite(versionedHandle); } return versionedHandle; } public BufferHandle UseBuffer(in BufferHandle input, AccessFlags flags) { UseResource(input.handle, flags); return input; } // UseTexture and SetRenderAttachment are currently forced to be mutually exclusive in the same pass // check this. // We currently ignore the version. In theory there might be some cases that are actually allowed with versioning // for ample UseTexture(myTexV1, read) UseFragment(myTexV2, ReadWrite) as they are different versions // but for now we don't allow any of that. [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] private void CheckNotUseFragment(in TextureHandle tex) { if (RenderGraph.enableValidityChecks) { bool usedAsFragment = (m_RenderPass.depthAccess.textureHandle.IsValid() && m_RenderPass.depthAccess.textureHandle.handle.index == tex.handle.index); if (!usedAsFragment) { for (int i = 0; i <= m_RenderPass.colorBufferMaxIndex; i++) { if (m_RenderPass.colorBufferAccess[i].textureHandle.IsValid() && m_RenderPass.colorBufferAccess[i].textureHandle.handle.index == tex.handle.index) { usedAsFragment = true; break; } } } if (usedAsFragment) { var name = m_Resources.GetRenderGraphResourceName(tex.handle); throw new ArgumentException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.k_TextureAlreadyBeingUsedThroughSetAttachment); } } } [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] private void CheckTextureUVOriginIsValid(in ResourceHandle handle, TextureResource texRes) { if (texRes.textureUVOrigin == TextureUVOriginSelection.TopLeft) { var name = m_Resources.GetRenderGraphResourceName(handle); throw new ArgumentException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type `{handle.type}` at index `{handle.index}` - " + RenderGraph.RenderGraphExceptionMessages.IncompatibleTextureUVOriginUseTexture(texRes.textureUVOrigin)); } } public void UseTexture(in TextureHandle input, AccessFlags flags) { CheckNotUseFragment(input); UseResource(input.handle, flags); if ((flags & AccessFlags.Read) == AccessFlags.Read) { if (m_RenderGraph.renderTextureUVOriginStrategy == RenderTextureUVOriginStrategy.PropagateAttachmentOrientation) { TextureResource texRes = m_Resources.GetTextureResource(input.handle); CheckTextureUVOriginIsValid(input.handle, texRes); texRes.textureUVOrigin = TextureUVOriginSelection.BottomLeft; } } } public void UseGlobalTexture(int propertyId, AccessFlags flags) { var h = m_RenderGraph.GetGlobal(propertyId); if (h.IsValid()) { UseTexture(h, flags); } else { // rose test this path var name = m_Resources.GetRenderGraphResourceName(h.handle); throw new ArgumentException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {h.handle.type} at index {h.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.NoGlobalTextureAtPropertyID(propertyId)); } } public void UseAllGlobalTextures(bool enable) { m_RenderPass.useAllGlobalTextures = enable; } public void SetGlobalTextureAfterPass(in TextureHandle input, int propertyId) { m_RenderPass.setGlobalsList.Add(ValueTuple.Create(input, propertyId)); } // Shared validation between SetRenderAttachment/SetRenderAttachmentDepth [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] private void CheckUseFragment(in TextureHandle tex, bool isDepth) { if (RenderGraph.enableValidityChecks) { // We ignore the version as we don't allow mixing UseTexture/UseFragment between different versions // even though it should theoretically work (and we might do so in the future) for now we're overly strict. bool alreadyUsed = false; //TODO: Check grab textures here and allow if it's grabbed. For now // SetRenderAttachment() // UseTexture(grab) // will work but not the other way around for (int i = 0; i < m_RenderPass.resourceReadLists[tex.handle.iType].Count; i++) { if (m_RenderPass.resourceReadLists[tex.handle.iType][i].index == tex.handle.index) { alreadyUsed = true; break; } } for (int i = 0; i < m_RenderPass.resourceWriteLists[tex.handle.iType].Count; i++) { if (m_RenderPass.resourceWriteLists[tex.handle.iType][i].index == tex.handle.index) { alreadyUsed = true; break; } } if (alreadyUsed) { var name = m_Resources.GetRenderGraphResourceName(tex.handle); throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.k_SetRenderAttachmentTextureAlreadyUsed); } m_Resources.GetRenderTargetInfo(tex.handle, out var info); // The old path is full of invalid uses that somehow work (or seem to work) so we skip validation if not using actual native renderpass if (m_RenderGraph.nativeRenderPassesEnabled) { if (isDepth) { if (!GraphicsFormatUtility.IsDepthFormat(info.format)) { var name = m_Resources.GetRenderGraphResourceName(tex.handle); throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.UseDepthWithColorFormat(info.format)); } } else { if (GraphicsFormatUtility.IsDepthFormat(info.format)) { var name = m_Resources.GetRenderGraphResourceName(tex.handle); throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.k_SetRenderAttachmentOnDepthTexture); } } if (m_RenderGraph.renderTextureUVOriginStrategy == RenderTextureUVOriginStrategy.PropagateAttachmentOrientation) { var texResource = m_Resources.GetTextureResource(tex.handle); TextureResource checkTexResource = null; for (int i = 0; i < m_RenderPass.fragmentInputMaxIndex + 1; ++i) { if (m_RenderPass.fragmentInputAccess[i].textureHandle.IsValid()) { ref readonly TextureHandle texCheck = ref m_RenderPass.fragmentInputAccess[i].textureHandle; checkTexResource = m_Resources.GetTextureResource(texCheck.handle); if (texResource.textureUVOrigin != TextureUVOriginSelection.Unknown && checkTexResource.textureUVOrigin != TextureUVOriginSelection.Unknown && texResource.textureUVOrigin != checkTexResource.textureUVOrigin) { var name = m_Resources.GetRenderGraphResourceName(tex.handle); var checkName = m_Resources.GetRenderGraphResourceName(texCheck.handle); throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.IncompatibleTextureUVOrigin(texResource.textureUVOrigin, "input", checkName, texCheck.handle.type, texCheck.handle.index, checkTexResource.textureUVOrigin)); } } } for (int i = 0; i < m_RenderPass.colorBufferMaxIndex + 1; ++i) { if (m_RenderPass.colorBufferAccess[i].textureHandle.IsValid()) { ref readonly TextureHandle texCheck = ref m_RenderPass.colorBufferAccess[i].textureHandle; checkTexResource = m_Resources.GetTextureResource(texCheck.handle); if (texResource.textureUVOrigin != TextureUVOriginSelection.Unknown && checkTexResource.textureUVOrigin != TextureUVOriginSelection.Unknown && texResource.textureUVOrigin != checkTexResource.textureUVOrigin) { var name = m_Resources.GetRenderGraphResourceName(tex.handle); var checkName = m_Resources.GetRenderGraphResourceName(texCheck.handle); throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.IncompatibleTextureUVOrigin(texResource.textureUVOrigin, "render", checkName, texCheck.handle.type, texCheck.handle.index, checkTexResource.textureUVOrigin)); } } } if (!isDepth && m_RenderPass.depthAccess.textureHandle.IsValid()) { TextureHandle texCheck = m_RenderPass.depthAccess.textureHandle; checkTexResource = m_Resources.GetTextureResource(texCheck.handle); if (texResource.textureUVOrigin != TextureUVOriginSelection.Unknown && checkTexResource.textureUVOrigin != TextureUVOriginSelection.Unknown && texResource.textureUVOrigin != checkTexResource.textureUVOrigin) { var name = m_Resources.GetRenderGraphResourceName(tex.handle); var checkName = m_Resources.GetRenderGraphResourceName(texCheck.handle); throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.IncompatibleTextureUVOrigin(texResource.textureUVOrigin, "depth", checkName, texCheck.handle.type, texCheck.handle.index, checkTexResource.textureUVOrigin)); } } } } foreach (var globalTex in m_RenderPass.setGlobalsList) { if (globalTex.Item1.handle.index == tex.handle.index) { var name = m_Resources.GetRenderGraphResourceName(tex.handle); throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.k_SetRenderAttachmentOnGlobalTexture); } } } } public void SetRenderAttachment(TextureHandle tex, int index, AccessFlags flags, int mipLevel, int depthSlice) { CheckUseFragment(tex, false); var versionedTextureHandle = new TextureHandle(UseResource(tex.handle, flags)); m_RenderPass.SetColorBufferRaw(versionedTextureHandle, index, flags, mipLevel, depthSlice); } public void SetInputAttachment(TextureHandle tex, int index, AccessFlags flags, int mipLevel, int depthSlice) { CheckFrameBufferFetchEmulationIsSupported(tex); CheckUseFragment(tex, false); var versionedTextureHandle = new TextureHandle(UseResource(tex.handle, flags)); m_RenderPass.SetFragmentInputRaw(versionedTextureHandle, index, flags, mipLevel, depthSlice); } public void SetRenderAttachmentDepth(TextureHandle tex, AccessFlags flags, int mipLevel, int depthSlice) { CheckUseFragment(tex, true); var versionedTextureHandle = new TextureHandle(UseResource(tex.handle, flags)); m_RenderPass.SetDepthBufferRaw(versionedTextureHandle, flags, mipLevel, depthSlice); } public TextureHandle SetRandomAccessAttachment(TextureHandle input, int index, AccessFlags flags = AccessFlags.Read) { CheckNotUseFragment(input); ResourceHandle result = UseResource(input.handle, flags); m_RenderPass.SetRandomWriteResourceRaw(result, index, false, flags); return input; } public void SetShadingRateImageAttachment(in TextureHandle tex) { CheckNotUseFragment(tex); var versionedTextureHandle = new TextureHandle(UseResource(tex.handle, AccessFlags.Read)); m_RenderPass.SetShadingRateImageRaw(versionedTextureHandle); } public BufferHandle UseBufferRandomAccess(BufferHandle input, int index, AccessFlags flags = AccessFlags.Read) { var h = UseBuffer(input, flags); m_RenderPass.SetRandomWriteResourceRaw(h.handle, index, true, flags); return input; } public BufferHandle UseBufferRandomAccess(BufferHandle input, int index, bool preserveCounterValue, AccessFlags flags = AccessFlags.Read) { var h = UseBuffer(input, flags); m_RenderPass.SetRandomWriteResourceRaw(h.handle, index, preserveCounterValue, flags); return input; } public void SetRenderFunc(BaseRenderFunc renderFunc) where PassData : class, new() { ((ComputeRenderGraphPass)m_RenderPass).renderFunc = renderFunc; } public void SetRenderFunc(BaseRenderFunc renderFunc) where PassData : class, new() { ((RasterRenderGraphPass)m_RenderPass).renderFunc = renderFunc; } public void SetRenderFunc(BaseRenderFunc renderFunc) where PassData : class, new() { ((UnsafeRenderGraphPass)m_RenderPass).renderFunc = renderFunc; } public void UseRendererList(in RendererListHandle input) { m_RenderPass.UseRendererList(input); } [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] void CheckResource(in ResourceHandle res, bool checkTransientReadWrite = false) { if (RenderGraph.enableValidityChecks) { if (res.IsValid()) { int transientIndex = m_Resources.GetRenderGraphResourceTransientIndex(res); // We have dontCheckTransientReadWrite here because users may want to use UseColorBuffer/UseDepthBuffer API to benefit from render target auto binding. In this case we don't want to raise the error. if (transientIndex == m_RenderPass.index && checkTransientReadWrite) { var name = m_Resources.GetRenderGraphResourceName(res); Debug.LogError($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {res.type} at index {res.index} - " + RenderGraph.RenderGraphExceptionMessages.k_ReadWriteTransient); } if (transientIndex != -1 && transientIndex != m_RenderPass.index) { var name = m_Resources.GetRenderGraphResourceName(res); throw new ArgumentException( $"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {res.type} at index {res.index} - " + RenderGraph.RenderGraphExceptionMessages.UseTransientTextureInWrongPass(transientIndex)); } } else { var name = m_Resources.GetRenderGraphResourceName(res); throw new Exception($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {res.type} at index {res.index} - " + RenderGraph.RenderGraphExceptionMessages.k_InvalidResource); } } } [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] void CheckFrameBufferFetchEmulationIsSupported(in TextureHandle tex) { if (enableValidityChecks) { if (!Util.RenderGraphUtils.IsFramebufferFetchEmulationSupportedOnCurrentPlatform()) { throw new InvalidOperationException($"This API is not supported on the current platform: {SystemInfo.graphicsDeviceType}"); } if (!Util.RenderGraphUtils.IsFramebufferFetchEmulationMSAASupportedOnCurrentPlatform()) { var sourceInfo = m_RenderGraph.GetRenderTargetInfo(tex); if (sourceInfo.bindMS) throw new InvalidOperationException($"This API is not supported with MSAA attachments on the current platform: {SystemInfo.graphicsDeviceType}"); } } } public void SetShadingRateFragmentSize(ShadingRateFragmentSize shadingRateFragmentSize) { m_RenderPass.SetShadingRateFragmentSize(shadingRateFragmentSize); } public void SetShadingRateCombiner(ShadingRateCombinerStage stage, ShadingRateCombiner combiner) { m_RenderPass.SetShadingRateCombiner(stage, combiner); } public void SetExtendedFeatureFlags(ExtendedFeatureFlags extendedFeatureFlags) { m_RenderPass.SetExtendedFeatureFlags(extendedFeatureFlags); } } }