Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Official Introduction of Render Graph in the Universal Render Pipeline (URP)

Discussion in 'Universal Render Pipeline' started by oliverschnabel, Oct 2, 2023.

  1. oliverschnabel

    oliverschnabel

    Unity Technologies

    Joined:
    Mar 13, 2018
    Posts:
    2
    Hello Unity community,

    We are ready to share the new RenderGraph based version of URP with you! You might have seen it on our roadmap over the last year, and many PRs landing into the Graphics repo. We are close to shipping it and you can now start to try it out using the latest 23.3 alpha release! You can expect some changes during the alpha, especially based on your feedback. Currently, it is still hidden behind a scripting define (see the documentation below) to try it out.

    With this post, we aim to share our work early and discuss with you the next steps. So let us know what you think!

    Why RenderGraph?
    Render Graph is a foundational system that automatically optimizes runtime resources for rendering. This simplifies the development of render features in our render pipelines while improving performance over a wide range of potential pipeline configurations. This also reduces the likelihood of bugs when features are manually optimized.

    URP is highly extensible and with RenderGraph, performance can now be automatically optimized for ScriptableRenderPasses that are added to your project. This will lead to better GPU performance when you are extending URP. As part of this project, we’ve improved RenderGraph to apply the NativeRenderPass API that optimizes GPU bandwidth on tile-based (mobile) GPUs.

    The benefits for you are:
    • Enhanced Extensibility and Customization: The Render Graph API allows you to access more frame resources in your custom passes and share data between passes. For example you can now get access to the G-buffer for your effects.

    • Stricter and Safer API: The new APIs support you to ensure your Renderer Features/Custom Passes are both robust on many platforms and optimized automatically. This prevents you from making mistakes that would lead to rendering issues or performance problems

    • Optimized GPU Performance: While this release is about the foundation and we have more potential to improve performance even further in future releases, current results show an average of 1ms improvement in GPU performance per frame, significantly reducing bandwidth waste and enhancing both device thermal states and battery life. You can now customize URP yourself more easily to get more performance out of it.
    What Changes?
    All URP features have been converted to using RenderGraph under the hood. Apart from a slight difference in performance, nothing changes in your project if you haven’t extended URP.

    The main difference is your access to RenderGraph in the modified ScriptableRenderPass class. This allows you to benefit from the automatic performance optimization that RenderGraph offers when extending URP. However, this new API is tightly coupled to the new foundation so you’ll need to upgrade your RenderFeatures and ScriptableRenderPass classes. The previous API will not work with RenderGraph.

    We are creating documentation and samples to help you to benefit from the new system. To let you experiment with the feature now, we have created a document explaining the details for this alpha version.

    Love to hear from you!
    You can enable RenderGraph in the 2023.3 alpha release (Unity 2023.3.0a8 or later, see the documentation above) and see how it works. We’d love to hear from you how it can benefit your project.

    We encourage thoughts, questions, and constructive feedback as we progress towards the final stages of this feature. Your input is vital to us!

    Stay informed of upcoming details, updates, and insights related to this feature.

    The render pipeline team

    -----
    Updates:
    09-Oct 2023: Edited min version number to 2023.3.0a8, since this reflects changes shown in the alpha documentation. Added a link to the "Perform a full screen blit in URP" file to the documentation.
     
    Last edited: Oct 9, 2023
  2. ElliotB

    ElliotB

    Joined:
    Aug 11, 2013
    Posts:
    215
    I'm excited for the RenderGraph changes but also nervous of the work involved. Are there any examples/tutorials that show how to modify an existing simple render feature, like a full screen blit? Also, do you have a very rough eta of when this feature will be 'on by default' so that we can plan support for it?

    Cheers,
    Elliot
     
  3. ManueleB

    ManueleB

    Unity Technologies

    Joined:
    Jul 6, 2020
    Posts:
    98
    We are working on adding simple code examples for different common use case scenarios. For a simple Blit this is a simple example render feature:


    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Experimental.Rendering.RenderGraphModule;
    3. using UnityEngine.Rendering;
    4. using UnityEngine.Rendering.Universal;
    5.  
    6. public class CopyRenderFeature : ScriptableRendererFeature
    7. {
    8.     class CopyRenderPass : ScriptableRenderPass
    9.     {
    10.         // This class stores the data needed by the pass, passed as parameter to the delegate function that executes the pass
    11.         private class PassData
    12.         {
    13.             internal TextureHandle src;
    14.         }
    15.  
    16.         // This static method is used to execute the pass and passed as the RenderFunc delegate to the RenderGraph render pass
    17.         static void ExecutePass(PassData data, RasterGraphContext context)
    18.         {
    19.             Blitter.BlitTexture(context.cmd, data.src, new Vector4(1,1,0,0), 0, false);
    20.         }
    21.      
    22.         // This is where the renderGraph handle can be accessed.
    23.         // Each ScriptableRenderPass can use the RenderGraph handle to add multiple render passes to the render graph
    24.         public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    25.         {
    26.             string passName = "Copy To Debug Texture";
    27.          
    28.             // This simple pass copies the active color texture to a new texture. This sample is for API demonstrative purposes,
    29.             // so the new texture is not used anywhere else in the frame, you can use the frame debugger to verify its contents.
    30.  
    31.             // add a raster render pass to the render graph, specifying the name and the data type that will be passed to the ExecutePass function
    32.             using (var builder = renderGraph.AddRasterRenderPass<PassData>(passName, out var passData))
    33.             {
    34.                 // UniversalResourceData contains all the texture handles used by the renderer, including the active color and depth textures
    35.                 // The active color and depth textures are the main color and depth buffers that the camera renders into
    36.                 UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
    37.              
    38.                 // Fill up the passData with the data needed by the pass
    39.              
    40.                 // Get the active color texture through the frame data, and set it as the source texture for the blit
    41.                 passData.src = resourceData.activeColorTexture;
    42.              
    43.                 // The destination texture is created here,
    44.                 // the texture is created with the same dimensions as the active color texture, but with no depth buffer, being a copy of the color texture
    45.                 // we also disable MSAA as we don't need multisampled textures for this sample
    46.              
    47.                 UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
    48.                 RenderTextureDescriptor desc = cameraData.cameraTargetDescriptor;
    49.                 desc.msaaSamples = 1;
    50.                 desc.depthBufferBits = 0;
    51.              
    52.                 TextureHandle destination = UniversalRenderer.CreateRenderGraphTexture(renderGraph, desc, "CopyTexture", false);
    53.              
    54.                 // We declare the src texture as an input dependency to this pass, via UseTexture()
    55.                 builder.UseTexture(passData.src);
    56.  
    57.                 // Setup as a render target via UseTextureFragment, which is the equivalent of using the old cmd.SetRenderTarget
    58.                 builder.UseTextureFragment(destination, 0);
    59.              
    60.                 // We disable culling for this pass for the demonstrative purpose of this sampe, as normally this pass would be culled,
    61.                 // since the destination texture is not used anywhere else
    62.                 builder.AllowPassCulling(false);
    63.  
    64.                 // Assign the ExecutePass function to the render pass delegate, which will be called by the render graph when executing the pass
    65.                 builder.SetRenderFunc((PassData data, RasterGraphContext context) => ExecutePass(data, context));
    66.             }
    67.         }
    68.     }
    69.  
    70.     CopyRenderPass m_CopyRenderPass;
    71.  
    72.     /// <inheritdoc/>
    73.     public override void Create()
    74.     {
    75.         m_CopyRenderPass = new CopyRenderPass();
    76.  
    77.         // Configures where the render pass should be injected.
    78.         m_CopyRenderPass.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
    79.     }
    80.  
    81.     // Here you can inject one or multiple render passes in the renderer.
    82.     // This method is called when setting up the renderer once per-camera.
    83.     public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    84.     {
    85.         renderer.EnqueuePass(m_CopyRenderPass);
    86.     }
    87. }
     
  4. ManueleB

    ManueleB

    Unity Technologies

    Joined:
    Jul 6, 2020
    Posts:
    98
    And this one is a Blit pass using a custom material/shader:

    Render Feature:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEngine.Experimental.Rendering.RenderGraphModule;
    4. using UnityEngine.Rendering;
    5. using UnityEngine.Rendering.Universal;
    6. using UnityEngine.Serialization;
    7.  
    8. public class BlitWithMaterialRenderFeature : ScriptableRendererFeature
    9. {
    10.     class BlitWithMaterialPass : ScriptableRenderPass
    11.     {
    12.         private Material m_BlitMaterial;
    13.        
    14.         public BlitWithMaterialPass(Material blitMaterial)
    15.         {
    16.             m_BlitMaterial = blitMaterial;
    17.         }
    18.        
    19.         // This class stores the data needed by the pass, passed as parameter to the delegate function that executes the pass
    20.         private class PassData
    21.         {
    22.             internal TextureHandle src;
    23.             internal TextureHandle dst;
    24.             internal Material blitMaterial;
    25.         }
    26.  
    27.         // This static method is used to execute the pass and passed as the RenderFunc delegate to the RenderGraph render pass
    28.         static void ExecutePass(PassData data, RasterGraphContext context)
    29.         {
    30.             Blitter.BlitTexture(context.cmd, data.src, new Vector4(1, 1, 0, 0), data.blitMaterial, 0);
    31.         }
    32.  
    33.         private void InitPassData(RenderGraph renderGraph, ContextContainer frameData, ref PassData passData)
    34.         {
    35.             // Fill up the passData with the data needed by the passes
    36.            
    37.             // UniversalResourceData contains all the texture handles used by the renderer, including the active color and depth textures
    38.             // The active color and depth textures are the main color and depth buffers that the camera renders into
    39.             UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
    40.            
    41.             // The destination texture is created here,
    42.             // the texture is created with the same dimensions as the active color texture, but with no depth buffer, being a copy of the color texture
    43.             // we also disable MSAA as we don't need multisampled textures for this sample
    44.                
    45.             UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
    46.             RenderTextureDescriptor desc = cameraData.cameraTargetDescriptor;
    47.             desc.msaaSamples = 1;
    48.             desc.depthBufferBits = 0;
    49.                
    50.             TextureHandle destination = UniversalRenderer.CreateRenderGraphTexture(renderGraph, desc, "BlitMaterialTexture", false);
    51.            
    52.             passData.src = resourceData.activeColorTexture;
    53.             passData.dst = destination;
    54.             passData.blitMaterial = m_BlitMaterial;
    55.         }
    56.        
    57.         // This is where the renderGraph handle can be accessed.
    58.         // Each ScriptableRenderPass can use the RenderGraph handle to add multiple render passes to the render graph
    59.         public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    60.         {
    61.             string passName = "Blit With Material";
    62.            
    63.             // This simple pass copies the active color texture to a new texture using a custom material. This sample is for API demonstrative purposes,
    64.             // so the new texture is not used anywhere else in the frame, you can use the frame debugger to verify its contents.
    65.  
    66.             // add a raster render pass to the render graph, specifying the name and the data type that will be passed to the ExecutePass function
    67.             using (var builder = renderGraph.AddRasterRenderPass<PassData>(passName, out var passData))
    68.             {
    69.                 // Initialize the pass data
    70.                 InitPassData(renderGraph, frameData, ref passData);
    71.  
    72.                 // We declare the src texture as an input dependency to this pass, via UseTexture()
    73.                 builder.UseTexture(passData.src);
    74.  
    75.                 // Setup as a render target via UseTextureFragment, which is the equivalent of using the old cmd.SetRenderTarget
    76.                 builder.UseTextureFragment(passData.dst, 0);
    77.                
    78.                 // We disable culling for this pass for the demonstrative purpose of this sampe, as normally this pass would be culled,
    79.                 // since the destination texture is not used anywhere else
    80.                 builder.AllowPassCulling(false);
    81.  
    82.                 // Assign the ExecutePass function to the render pass delegate, which will be called by the render graph when executing the pass
    83.                 builder.SetRenderFunc((PassData data, RasterGraphContext context) => ExecutePass(data, context));
    84.             }
    85.         }
    86.     }
    87.  
    88.     BlitWithMaterialPass m_BlitWithMaterialPass;
    89.    
    90.     public Material m_BlitColorMaterial;
    91.  
    92.     /// <inheritdoc/>
    93.     public override void Create()
    94.     {
    95.         m_BlitWithMaterialPass = new BlitWithMaterialPass(m_BlitColorMaterial);
    96.  
    97.         // Configures where the render pass should be injected.
    98.         m_BlitWithMaterialPass.renderPassEvent = RenderPassEvent.BeforeRenderingTransparents;
    99.     }
    100.  
    101.     // Here you can inject one or multiple render passes in the renderer.
    102.     // This method is called when setting up the renderer once per-camera.
    103.     public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    104.     {
    105.         renderer.EnqueuePass(m_BlitWithMaterialPass);
    106.     }
    107. }
    108.  
    109.  
    110.  
    Shader:

    Code (CSharp):
    1. Shader "BlitWithMaterial"
    2. {
    3.    SubShader
    4.    {
    5.        Tags { "RenderType"="Opaque" "RenderPipeline" = "UniversalPipeline"}
    6.        ZWrite Off Cull Off
    7.        Pass
    8.        {
    9.            Name "BlitWithMaterialPass"
    10.  
    11.            HLSLPROGRAM
    12.            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
    13.            #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
    14.  
    15.            #pragma vertex Vert
    16.            #pragma fragment Frag
    17.  
    18.            // Out frag function takes as input a struct that contains the screen space coordinate we are going to use to sample our texture. It also writes to SV_Target0, this has to match the index set in the UseTextureFragment(sourceTexture, 0, …) we defined in our render pass script.
    19.            float4 Frag(Varyings input) : SV_Target0
    20.            {
    21.                // this is needed so we account XR platform differences in how they handle texture arrays
    22.                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
    23.  
    24.                // sample the texture using the SAMPLE_TEXTURE2D_X_LOD
    25.                float2 uv = input.texcoord.xy;
    26.                half4 color = SAMPLE_TEXTURE2D_X_LOD(_BlitTexture, sampler_LinearRepeat, uv, _BlitMipLevel);
    27.            
    28.                // Modify the sampled color
    29.                return half4(0, 1, 0, 1) * color;
    30.            }
    31.  
    32.            ENDHLSL
    33.        }
    34.    }
    35. }
     
    Last edited: Oct 2, 2023
    Kirsche, AljoshaD and ElliotB like this.
  5. ElliotB

    ElliotB

    Joined:
    Aug 11, 2013
    Posts:
    215
    Is there going to be a period where both routes are supported, or is the intention to move URP wholly to rendergraph when the time comes? I'd seen the github repo previously where it had elements of both, but it wasnt clear if that was just while getting things running (there was a fair bit of duplication as a result)
     
  6. AljoshaD

    AljoshaD

    Unity Technologies

    Joined:
    May 27, 2019
    Posts:
    172
    You can also try out the KeepFrame sample in the URP package samples. It's upgraded to RenderGraph.

    The idea is to have this on by default in 23.3. We're still building confidence to make this decision though.

    It's indeed the intention to move URP wholly to RenderGraph when the time comes.
     
  7. kripto289

    kripto289

    Joined:
    Feb 21, 2013
    Posts:
    466
    I hope "ScriptableRendererFeature" will be removed? Because in HDRP I can use simple "volume" feature in runtime, without manual adding 100500 features through editor.
    Ps, right now the only way it's use "UniversalAdditionalCameraData.scriptableRenderer.EnqueuePass"

    I hope with render graph I can use the same universal custompass API for urp/hdrp?
    or will there be 2 different versions again ?

    if you plan to completely break the old URP API, I will be glad if it is a single API for URP and HDRP. I'm begging.
     
  8. phil_lira

    phil_lira

    Unity Technologies

    Joined:
    Dec 17, 2014
    Posts:
    584
    Hello everyone, here's a link to an example on How to Blit using Render Graph API and Blitter API. Let us know about any API feedback and we will update the API and docs.
     

    Attached Files:

    AljoshaD and ElliotB like this.
  9. ElliotB

    ElliotB

    Joined:
    Aug 11, 2013
    Posts:
    215
    The thing that worries me most about API changes to URP is if it causes a loss of functionality. If we identify things that you can't do with the new API that you could do with the old, will the team be receptive to those changes? Historically it feels like most URP suggestions are ignored - like the pipeline is going wherever it's been decided to go, regardless of what users expect from it.
     
  10. AljoshaD

    AljoshaD

    Unity Technologies

    Joined:
    May 27, 2019
    Posts:
    172
    The render pass interface RecordRenderGraph(RG, ContextContainer FrameData) has been designed so it can be adopted by HDRP. HDRP currently doesn't expose RenderGraph in the HDRP CustomPass. In 23, HDRP will not adopt it yet but for the next version we have planned to unify the extension APIs indeed using this new interface.
     
    kripto289 likes this.
  11. AljoshaD

    AljoshaD

    Unity Technologies

    Joined:
    May 27, 2019
    Posts:
    172
    Yes very much so. Our goal is to not have any functional regressions, you should be able to do more with the new API, not less. It's a top priority to fix if you would find some regression. However, the old API was not as thoroughly designed and offers much less guardrails. So some things might have worked by accident (on some platforms) that now the more strict API could prevent.
     
    ElliotB likes this.
  12. ManueleB

    ManueleB

    Unity Technologies

    Joined:
    Jul 6, 2020
    Posts:
    98
    you should be able to do more things with the new API that before were not possible, i.e. accessing the actual RTHandle of every single resource, or using frame buffer fetch by and native render passes enabled by default on TBDR devices.

    As Aljosha said, there might have been "undefined behaviours"/hacks that worked out of luck before, being undefined or technically incorrect. In those cases you would need to find a proper way to implement it, since the API now is much more safe and as a consequence more strict.

    Of course if there would be any missing valid functionality our priority is to fix it ASAP and that's why we are asking for feedback ahead of time
     
    Last edited: Oct 3, 2023
    ElliotB likes this.
  13. ManueleB

    ManueleB

    Unity Technologies

    Joined:
    Jul 6, 2020
    Posts:
    98
    Adding few more preview samples:

    How to draw geometry using RendererLists + RenderGraph (replacing the old cmd.DrawRenderers)


    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using UnityEngine.Experimental.Rendering.RenderGraphModule;
    4. using UnityEngine.Rendering;
    5. using UnityEngine.Rendering.RendererUtils;
    6. using UnityEngine.Rendering.Universal;
    7.  
    8. public class RenderListRenderFeature : ScriptableRendererFeature
    9. {
    10.     class RendererListPass : ScriptableRenderPass
    11.     {
    12.         // Layer mask used to filter objects to put in the renderer list
    13.         private LayerMask m_LayerMask;
    14.        
    15.         // List of shader tags used to build the renderer list
    16.         private List<ShaderTagId> m_ShaderTagIdList = new List<ShaderTagId>();
    17.  
    18.         public RendererListPass(LayerMask layerMask)
    19.         {
    20.             m_LayerMask = layerMask;
    21.         }
    22.        
    23.         // This class stores the data needed by the pass, passed as parameter to the delegate function that executes the pass
    24.         private class PassData
    25.         {
    26.             public RendererListHandle rendererListHandle;
    27.         }
    28.  
    29.         // Sample utility method that showcases how to create a renderer list via the RenderGraph API
    30.         private void InitRendererLists(ContextContainer frameData, ref PassData passData, RenderGraph renderGraph)
    31.         {
    32.             // Access the relevant frame data from the Universal Render Pipeline
    33.             UniversalRenderingData universalRenderingData = frameData.Get<UniversalRenderingData>();
    34.             UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
    35.             UniversalLightData lightData = frameData.Get<UniversalLightData>();
    36.            
    37.             var sortFlags = cameraData.defaultOpaqueSortFlags;
    38.             RenderQueueRange renderQueueRange = RenderQueueRange.opaque;
    39.             FilteringSettings filterSettings = new FilteringSettings(renderQueueRange, m_LayerMask);
    40.            
    41.             ShaderTagId[] forwardOnlyShaderTagIds = new ShaderTagId[]
    42.             {
    43.                 new ShaderTagId("UniversalForwardOnly"),
    44.                 new ShaderTagId("UniversalForward"),
    45.                 new ShaderTagId("SRPDefaultUnlit"), // Legacy shaders (do not have a gbuffer pass) are considered forward-only for backward compatibility
    46.                 new ShaderTagId("LightweightForward") // Legacy shaders (do not have a gbuffer pass) are considered forward-only for backward compatibility
    47.             };
    48.            
    49.             m_ShaderTagIdList.Clear();
    50.            
    51.             foreach (ShaderTagId sid in forwardOnlyShaderTagIds)
    52.                 m_ShaderTagIdList.Add(sid);
    53.            
    54.             DrawingSettings drawSettings = RenderingUtils.CreateDrawingSettings(m_ShaderTagIdList, universalRenderingData, cameraData, lightData, sortFlags);
    55.  
    56.             var param = new RendererListParams(universalRenderingData.cullResults, drawSettings, filterSettings);
    57.             passData.rendererListHandle = renderGraph.CreateRendererList(param);
    58.         }
    59.  
    60.         // This static method is used to execute the pass and passed as the RenderFunc delegate to the RenderGraph render pass
    61.         static void ExecutePass(PassData data, RasterGraphContext context)
    62.         {
    63.             context.cmd.ClearRenderTarget(RTClearFlags.Color, Color.green, 1,0);
    64.            
    65.             context.cmd.DrawRendererList(data.rendererListHandle);
    66.         }
    67.        
    68.         // This is where the renderGraph handle can be accessed.
    69.         // Each ScriptableRenderPass can use the RenderGraph handle to add multiple render passes to the render graph
    70.         public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    71.         {
    72.             string passName = "RenderList Render Pass";
    73.            
    74.             // This simple pass clears the current active color texture, then renders the scene geometry associated to the m_LayerMask layer.
    75.             // Add scene geometry to your own custom layers and experiment switching the layer mask in the render feature UI.
    76.             // You can use the frame debugger to inspect the pass output
    77.  
    78.             // add a raster render pass to the render graph, specifying the name and the data type that will be passed to the ExecutePass function
    79.             using (var builder = renderGraph.AddRasterRenderPass<PassData>(passName, out var passData))
    80.             {
    81.                 // UniversalResourceData contains all the texture handles used by the renderer, including the active color and depth textures
    82.                 // The active color and depth textures are the main color and depth buffers that the camera renders into
    83.                 UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
    84.                
    85.                 // Fill up the passData with the data needed by the pass
    86.                 InitRendererLists(frameData, ref passData, renderGraph);
    87.                
    88.                 // Make sure the renderer list is valid
    89.                 if (!passData.rendererListHandle.IsValid())
    90.                     return;
    91.                
    92.                 // We declare the RendererList we just created as an input dependency to this pass, via UseRendererList()
    93.                 builder.UseRendererList(passData.rendererListHandle);
    94.                
    95.                 // Setup as a render target via UseTextureFragment and UseTextureFragmentDepth, which are the equivalent of using the old cmd.SetRenderTarget(color,depth)
    96.                 builder.UseTextureFragment(resourceData.activeColorTexture, 0);
    97.                 builder.UseTextureFragmentDepth(resourceData.activeDepthTexture, IBaseRenderGraphBuilder.AccessFlags.Write);
    98.  
    99.                 // Assign the ExecutePass function to the render pass delegate, which will be called by the render graph when executing the pass
    100.                 builder.SetRenderFunc((PassData data, RasterGraphContext context) => ExecutePass(data, context));
    101.             }
    102.         }
    103.     }
    104.  
    105.     RendererListPass m_ScriptablePass;
    106.  
    107.     public LayerMask m_LayerMask;
    108.  
    109.     /// <inheritdoc/>
    110.     public override void Create()
    111.     {
    112.         m_ScriptablePass = new RendererListPass(m_LayerMask);
    113.  
    114.         // Configures where the render pass should be injected.
    115.         m_ScriptablePass.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
    116.     }
    117.  
    118.     // Here you can inject one or multiple render passes in the renderer.
    119.     // This method is called when setting up the renderer once per-camera.
    120.     public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    121.     {
    122.         renderer.EnqueuePass(m_ScriptablePass);
    123.     }
    124. }
    125.  
    126.  
    127.  
     
  14. ManueleB

    ManueleB

    Unity Technologies

    Joined:
    Jul 6, 2020
    Posts:
    98
    Framebuffer fetch sample:

    Feature:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine;
    3. using UnityEngine.Experimental.Rendering.RenderGraphModule;
    4. using UnityEngine.Rendering;
    5. using UnityEngine.Rendering.Universal;
    6. using UnityEngine.Serialization;
    7.  
    8. public class FrameBufferFetchRenderFeature : ScriptableRendererFeature
    9. {
    10.     class FrameBufferFetchPass : ScriptableRenderPass
    11.     {
    12.         private Material m_BlitMaterial;
    13.         private Material m_FBFetchMaterial;
    14.        
    15.         public FrameBufferFetchPass(Material blitMaterial, Material fbFetchMaterial)
    16.         {
    17.             m_BlitMaterial = blitMaterial;
    18.             m_FBFetchMaterial = fbFetchMaterial;
    19.         }
    20.        
    21.         // This class stores the data needed by the pass, passed as parameter to the delegate function that executes the pass
    22.         private class PassData
    23.         {
    24.             internal TextureHandle src;
    25.             internal Material material;
    26.         }
    27.  
    28.         // This static method is used to execute the pass and passed as the RenderFunc delegate to the RenderGraph render pass
    29.         static void ExecuteBlitPass(PassData data, RasterGraphContext context)
    30.         {
    31.             Blitter.BlitTexture(context.cmd, data.src, new Vector4(1, 1, 0, 0), data.material, 0);
    32.         }
    33.        
    34.         // This static method is used to execute the pass and passed as the RenderFunc delegate to the RenderGraph render pass
    35.         static void ExecuteFBFetchPass(PassData data, RasterGraphContext context)
    36.         {
    37.             context.cmd.DrawProcedural(Matrix4x4.identity, data.material, 1, MeshTopology.Triangles, 3, 1, null);
    38.            
    39.             // other ways to draw a fullscreen triangle/quad:
    40.             //CoreUtils.DrawFullScreen(context.cmd, data.fbFetchMaterial, null, 1);
    41.             //Blitter.BlitTexture(context.cmd, new Vector4(1, 1, 0, 0), data.fbFetchMaterial, 1);
    42.         }
    43.  
    44.         private void BlitPass(RenderGraph renderGraph, ContextContainer frameData, TextureHandle destination)
    45.         {
    46.             string passName = "InitialBlitPass";
    47.            
    48.             // This simple pass copies the active color texture to a new texture using a custom material. This sample is for API demonstrative purposes,
    49.             // so the new texture is not used anywhere else in the frame, you can use the frame debugger to verify its contents.
    50.  
    51.             // add a raster render pass to the render graph, specifying the name and the data type that will be passed to the ExecutePass function
    52.             using (var builder = renderGraph.AddRasterRenderPass<PassData>(passName, out var passData))
    53.             {
    54.                 // UniversalResourceData contains all the texture handles used by the renderer, including the active color and depth textures
    55.                 // The active color and depth textures are the main color and depth buffers that the camera renders into
    56.                 UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
    57.                
    58.                 // Get the active color texture through the frame data, and set it as the source texture for the blit
    59.                 passData.src = resourceData.activeColorTexture;
    60.                 passData.material = m_BlitMaterial;
    61.                
    62.                 // We declare the src texture as an input dependency to this pass, via UseTexture()
    63.                 builder.UseTexture(passData.src);
    64.  
    65.                 // Setup as a render target via UseTextureFragment, which is the equivalent of using the old cmd.SetRenderTarget
    66.                 builder.UseTextureFragment(destination, 0);
    67.                
    68.                 // We disable culling for this pass for the demonstrative purpose of this sample, as normally this pass would be culled,
    69.                 // since the destination texture is not used anywhere else
    70.                 builder.AllowPassCulling(false);
    71.  
    72.                 // Assign the ExecutePass function to the render pass delegate, which will be called by the render graph when executing the pass
    73.                 builder.SetRenderFunc((PassData data, RasterGraphContext context) => ExecuteBlitPass(data, context));
    74.             }
    75.         }
    76.        
    77.         private void FBFetchPass(RenderGraph renderGraph, ContextContainer frameData, TextureHandle source, TextureHandle destination)
    78.         {
    79.             string passName = "FrameBufferFetchPass";
    80.            
    81.             // This simple pass copies the target of the previous pass to a new texture using a custom material and framebuffer fetch. This sample is for API demonstrative purposes,
    82.             // so the new texture is not used anywhere else in the frame, you can use the frame debugger to verify its contents.
    83.  
    84.             // add a raster render pass to the render graph, specifying the name and the data type that will be passed to the ExecutePass function
    85.             using (var builder = renderGraph.AddRasterRenderPass<PassData>(passName, out var passData))
    86.             {
    87.                 // Fill the pass data
    88.                 passData.material = m_FBFetchMaterial;
    89.                
    90.                 // We declare the src texture as an input dependency to this pass, via UseTexture()
    91.                 //builder.UseTexture(passData.blitDest);
    92.                 builder.UseTextureFragmentInput(source, 0, IBaseRenderGraphBuilder.AccessFlags.Read);
    93.  
    94.                 // Setup as a render target via UseTextureFragment, which is the equivalent of using the old cmd.SetRenderTarget
    95.                 builder.UseTextureFragment(destination, 0);
    96.                
    97.                 // We disable culling for this pass for the demonstrative purpose of this sample, as normally this pass would be culled,
    98.                 // since the destination texture is not used anywhere else
    99.                 builder.AllowPassCulling(false);
    100.  
    101.                 // Assign the ExecutePass function to the render pass delegate, which will be called by the render graph when executing the pass
    102.                 builder.SetRenderFunc((PassData data, RasterGraphContext context) => ExecuteFBFetchPass(data, context));
    103.             }
    104.         }
    105.        
    106.         // This is where the renderGraph handle can be accessed.
    107.         // Each ScriptableRenderPass can use the RenderGraph handle to add multiple render passes to the render graph
    108.         public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    109.         {
    110.             // This pass showcases how to implement framebuffer fetch: this is an advanced TBDR GPU optimization
    111.             // that allows subpasses to read the output of previous subpasses directly from the framebuffer, reducing greatly the bandwidth usage.
    112.             // The first pass BlitPass simply copies the Camera Color in a temporary render target, the second pass FBFetchPass copies the temporary render target
    113.             // to another render target using framebuffer fetch.
    114.             // As a result, the passes are merged (you can verify in the RenderGraph Visualizer) and the bandwidth usage is reduced, since we can discard the temporary render target.
    115.  
    116.             // The destination textures are created here,
    117.             // the texture is created with the same dimensions as the active color texture, but with no depth buffer, being a copy of the color texture
    118.             // we also disable MSAA as we don't need multisampled textures for this sample.
    119.                
    120.             UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
    121.             RenderTextureDescriptor desc = cameraData.cameraTargetDescriptor;
    122.             desc.msaaSamples = 1;
    123.             desc.depthBufferBits = 0;
    124.                
    125.             TextureHandle blitDestination = UniversalRenderer.CreateRenderGraphTexture(renderGraph, desc, "BlitDestTexture", false);
    126.             TextureHandle fbFetchDestination = UniversalRenderer.CreateRenderGraphTexture(renderGraph, desc, "FBFetchDestTextureTexture", false);
    127.            
    128.             BlitPass(renderGraph, frameData, blitDestination);
    129.            
    130.             FBFetchPass(renderGraph, frameData, blitDestination, fbFetchDestination);
    131.         }
    132.     }
    133.  
    134.     FrameBufferFetchPass m_FbFetchPass;
    135.    
    136.     public Material m_BlitColorMaterial;
    137.     public Material m_FBFetchMaterial;
    138.  
    139.     /// <inheritdoc/>
    140.     public override void Create()
    141.     {
    142.         m_FbFetchPass = new FrameBufferFetchPass(m_BlitColorMaterial, m_FBFetchMaterial);
    143.  
    144.         // Configures where the render pass should be injected.
    145.         m_FbFetchPass.renderPassEvent = RenderPassEvent.BeforeRenderingTransparents;
    146.     }
    147.  
    148.     // Here you can inject one or multiple render passes in the renderer.
    149.     // This method is called when setting up the renderer once per-camera.
    150.     public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    151.     {
    152.         renderer.EnqueuePass(m_FbFetchPass);
    153.     }
    154. }
    155.  
    156.  
    157.  
    Shader:


    Code (CSharp):
    1. Shader "FrameBufferFetch"
    2. Shader "FrameBufferFetch"
    3. {
    4.    SubShader
    5.    {
    6.        Tags { "RenderType"="Opaque" "RenderPipeline" = "UniversalPipeline"}
    7.        ZWrite Off Cull Off
    8.        Pass
    9.        {
    10.            Name "InitialBlit"
    11.  
    12.            HLSLPROGRAM
    13.            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
    14.            #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
    15.  
    16.            #pragma vertex Vert
    17.            #pragma fragment Frag
    18.  
    19.            // Out frag function takes as input a struct that contains the screen space coordinate we are going to use to sample our texture. It also writes to SV_Target0, this has to match the index set in the UseTextureFragment(sourceTexture, 0, …) we defined in our render pass script.  
    20.            float4 Frag(Varyings input) : SV_Target0
    21.            {
    22.                // this is needed so we account XR platform differences in how they handle texture arrays
    23.                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
    24.  
    25.                // sample the texture using the SAMPLE_TEXTURE2D_X_LOD
    26.                float2 uv = input.texcoord.xy;
    27.                half4 color = SAMPLE_TEXTURE2D_X_LOD(_BlitTexture, sampler_LinearRepeat, uv, _BlitMipLevel);
    28.              
    29.                // Modify the sampled color
    30.                return color;
    31.            }
    32.  
    33.            ENDHLSL
    34.        }
    35.      
    36.        Tags { "RenderType"="Opaque" "RenderPipeline" = "UniversalPipeline"}
    37.        ZWrite Off Cull Off
    38.        Pass
    39.        {
    40.            Name "FrameBufferFetch"
    41.  
    42.            HLSLPROGRAM
    43.            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
    44.            #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
    45.  
    46.            #pragma vertex Vert
    47.            #pragma fragment Frag
    48.  
    49.            FRAMEBUFFER_INPUT_X_HALF(0);
    50.  
    51.            // Out frag function takes as input a struct that contains the screen space coordinate we are going to use to sample our texture. It also writes to SV_Target0, this has to match the index set in the UseTextureFragment(sourceTexture, 0, …) we defined in our render pass script.  
    52.            float4 Frag(Varyings input) : SV_Target0
    53.            {
    54.                // this is needed so we account XR platform differences in how they handle texture arrays
    55.                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
    56.  
    57.                // read the current pixel from the framebuffer
    58.                float2 uv = input.texcoord.xy;
    59.                half4 color = LOAD_FRAMEBUFFER_X_INPUT(0, input.positionCS.xy);
    60.              
    61.                // Modify the sampled color
    62.                return half4(0,0,1,1) * color;
    63.            }
    64.  
    65.            ENDHLSL
    66.        }
    67.    }
    68. }
    69.  
     
  15. JesOb

    JesOb

    Joined:
    Sep 3, 2012
    Posts:
    1,081
    how to draw geometry without hardcoded culling? How to supply our own list or renderers/meshes/submeshes?
     
  16. ManueleB

    ManueleB

    Unity Technologies

    Joined:
    Jul 6, 2020
    Posts:
    98
  17. JesOb

    JesOb

    Joined:
    Sep 3, 2012
    Posts:
    1,081
    it will not add any lighting or anything from scene it is not replacement to draw renderers
     
  18. ManueleB

    ManueleB

    Unity Technologies

    Joined:
    Jul 6, 2020
    Posts:
    98
    RendererList is the new API used for this by both URP and HDRP and gives you the same functionality of DrawRednderers

    https://docs.unity3d.com/ScriptReference/Rendering.RendererList.html

    note that this is not a RG related change, URP has been using RendererLists since 22
     
  19. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,194
    Is there any plan to make any API alternatives that are a bit less boilerplaty? I guess I could just copy-paste your example, but it feels like a bit much to require over 50 lines of code (without whitespace or comments!) to implement "get a named asset that blits a material to the screen".

    All in all this looks good, but I'd love to see some higher level features. That'd achieve two things:
    - Easier to find simple versions of the feature, so using this is achievable without deep knowledge of a pretty low-level API
    - A requirement for you to maintain the high level feature so we don't have to rewrite our code every Unity version update if we just want a simple blit to screen.
     
    Kronnect, LightJockey and kripto289 like this.
  20. ManueleB

    ManueleB

    Unity Technologies

    Joined:
    Jul 6, 2020
    Posts:
    98
    yeah, as you can see most of the RG setup code across the different samples I posted is 90% the same. We plan to add as many as possible high level wrappers to do the most common operations, so eventually the average user render feature should become few lines of code, i.e. Blit(rg, source, target, material). This way "high level" non advanced users ideally shouldn't even be exposed to the RG itself at all.

    The low level API is more verbose and powerful and allows for much more customization, but for sure the next step on our side will be about making it more user friendly

    Being this a call for early feedback we just want users to start using the low level API and give feedback on that
     
    saskenergy and Baste like this.
  21. sacb0y

    sacb0y

    Joined:
    May 9, 2016
    Posts:
    776
    Yay more coexistence!
     
    arkano22 and LightJockey like this.
  22. kripto289

    kripto289

    Joined:
    Feb 21, 2013
    Posts:
    466
    Is it possible to rasterize compute shader + other raster command at the same time?
    I don't know why here is different "RasterCommandBuffer" and "ComputeCommandBuffer" and no examples about "ComputeCommandBuffer"

    for example, the pseudocode of what I am currently using

    cmd.DrawProcedural //rendering shoreline mask
    cmd.DispatchCompute //compute foam relative to the shoreline mask
    cmd.DispatchCompute //compute blur pass
    cmd.BlitTriangle //render foam to screen
     
  23. ManueleB

    ManueleB

    Unity Technologies

    Joined:
    Jul 6, 2020
    Posts:
    98
    For us to be able to merge subpasses optimally and guarantee optimal RTs setup, we have Raster passes that only allow rasterization operations (Draw, etc) and compute passes that allow dispatches, so you shouldn't be able in a raster pass to do a dispatch, which would break the RenderPass setup (talking in terms of Vulkan subpasses, where a render pass is made of a set of subpasses). So the API is more strict and requires to use the type of pass for your need.

    In your case you can do what you need by scheduling RasterPass->ComputePass->RasterPass

    pseudo code:

    Code (CSharp):
    1. using (var builder = renderGraph.AddRasterRenderPass<PassData1>("DrawProceduralPass", out var passData))
    2.             {
    3.                 // initialize pass
    4.                
    5.                 // ...
    6.                
    7.                 builder.SetRenderFunc((PassData1 data, RasterGraphContext context) =>
    8.                 {
    9.                     context.cmd.DrawProcedural(...);
    10.                 });
    11.             }
    12.  
    13.             using (var builder = renderGraph.AddComputePass<PassData2>("DispatchesPass", out var passData))
    14.             {
    15.                 // initialize pass
    16.                
    17.                 // ...
    18.                
    19.                 builder.SetRenderFunc((PassData2 data, ComputeGraphContext context) =>
    20.                 {
    21.                     // do stuff
    22.                    
    23.                     context.cmd.DispatchCompute(...);
    24.                    
    25.                     // do more stuff
    26.                    
    27.                     context.cmd.DispatchCompute(...);
    28.                 });
    29.             }
    30.  
    31.             using (var builder = renderGraph.AddRasterRenderPass<PassData3>("BlitPass", out var passData))
    32.             {
    33.                 // initialize pass
    34.                
    35.                 // ...
    36.                
    37.                 builder.SetRenderFunc((PassData3 data, RasterGraphContext context) =>
    38.                 {
    39.                     Blitter.BlitTexture(...);
    40.                 });
    41.             }

    We are also adding samples to show ComputePass usages
     
    kripto289 and AljoshaD like this.
  24. optimise

    optimise

    Joined:
    Jan 22, 2014
    Posts:
    2,029
    Hi. Any plan to utilize burst or even better utilize more dots tech to improve performance of Render Graph to next level?
     
  25. optimise

    optimise

    Joined:
    Jan 22, 2014
    Posts:
    2,029
    GPU Performance still need extremely more optimization. Currently it's still extremely slow at mobile platform specially Android. At Android Mi 9T Pro (Snagdragon 855), GPU stalling is around 13ms+. See CASE IN-56966 for repro project.

    upload_2023-10-7_21-48-54.png
     
  26. AljoshaD

    AljoshaD

    Unity Technologies

    Joined:
    May 27, 2019
    Posts:
    172
    No. We are adding caching of the compiled graph so the RG compiler only needs to run once you modify the graph, and not every frame. This removes most of the RG CPU cost per frame.
     
    optimise, JesOb, ElliotB and 3 others like this.
  27. optimise

    optimise

    Joined:
    Jan 22, 2014
    Posts:
    2,029
    I see. How about GPU cost? Is that possible to reduce GPU cost significantly for Case IN-56966 at previous post #25 too?

     
  28. AljoshaD

    AljoshaD

    Unity Technologies

    Joined:
    May 27, 2019
    Posts:
    172
    That case seems to be specific to entities graphics. It is unrelated to RenderGraph. The benefits should be the same.
     
  29. Kabinet13

    Kabinet13

    Joined:
    Jun 13, 2019
    Posts:
    63
    Is this in HDRP too? Seems like more or less free performance.
     
  30. AljoshaD

    AljoshaD

    Unity Technologies

    Joined:
    May 27, 2019
    Posts:
    172
    Yes, the compiler caching will work for both URP and HDRP. The compiler time that is removed is not that significant on high end platforms with fast CPUs though, somewhere between 0.1-0.3ms, but every bit helps of course.
     
    ElevenGame likes this.