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

Aye, greatly appreciated!

When do you plan to release the tools that will make it easier to upgrade? hopefully sooner rather than later, id like to have my asset render graph ready before april :slight_smile:

I see, thanks for the feedback on this, that way seems better.

But if a user has started a 2023.3 project and already switched Render Graph and then buy and insert one of my assets that not yet support it, how is this case handled ? I assume it wont switch to the previous system, that is the main issue.

I really cant think of a way to save this, some users will just get confused and give low ratings, there is no working around that, unless the URP is renamed to URP RG, to showcase is a new pipeline perhaps.

Another way would be having default the old method for all projects, new or imported and a big warning when trying to switch to the Render Graph that some effects wont work. That would be a big help i suppose.

1 Like

Probably education and making the most of the new tooling to make users aware of the differences. You could chat with the asset store folks to explore additional compatibility flags like ā€˜supports RG’ etc. I haven’t removed reviews in the past because I find it easier to just explain the issue in the reply and then future users can see it easily.

Out of interest, what does the resource viewer show when in compatibility mode?

1 Like

It does not work with compatibility mode. There’s no RenderGraph to show.

1 Like

Apologies if this was said somewhere - is there a define statement for rendergraph/2023.3 ā€˜not compatibility mode’?

I guess the solution for asset store devs would be to have a scripting define so both APIs can be used, and the compatibility mode option could be removed alltogehter. I didn’t use any of them so I don’t know if that would be possible.

What if we create a setting on the RenderFeature that you can turn on to show a standard Unity message about RenderGraph when your non-compatible asset is used with RG, that links to the documentation and that clarifies that it’s expected that not all assets support RG yet. We can - as Unity - help to set the expectations with our users so that your asset doesn’t get blamed for that. Just brainstorming here but happy to hear your thoughts.

You want to check in C#? You can check on the global settings, the API is shared in one of the earlier messages.

I was wondering if there was a way to check via #if statements, as there is for UNITY_2023_3_OR_NEWER for instance?

I don’t think so. I’m curious about your use case. I assume using UNITY_2023_3_OR_NEWER and the global setting to check for compatibility mode should cover your needs.

If compatibility mode is more equivalent to pre 2023.3 behavior, then it would reduce complexity of the code. I can check ā€˜if 2023.3 and not compatibility mode’, rather than check 2023.3, check global settings, then check <2023.3 to switch between the different behaviours.

(Disclaimer that i still haven’t ported my asset yet due to time, so I’m running on some assumptions here)

I haven’t really been following this whole thread, just last few pages so this might have been mentioned already, if so please ignore. What about asking developers in asset store what version of unity they’re using, or what pipeline and then if they’re searching for assets only show those that meet that criteria? Or if they select an asset they want to purchase, before they buy it gives a compatibility message, like it will only work with Unity 202x or higher and with URP RG. Or ask them to put in the version of unity they’re using and pipeline before pressing purchase. I know that this info is in asset descriptions already but a popup before purchase reiterating this information might stop someone from purchasing an asset that will not work in their project.

Hello dear Unity Team,
First, I would like to express my gratitude for the examples and links to documentation files provided in this thread. They have been immensely helpful in understanding the Render Graph API and the Render Graph pattern itself. The approach appears very user-friendly, and I particularly enjoyed using the render graph viewer. The concept of separating resource handling from operations on those resources seems promising. We have encountered numerous bugs due to such errors, and the idea of separating these operations presents itself as a straightforward solution.

While using it, I have two questions regarding the debugging process of render features.

  1. Is there a static analyzer or perhaps a tool to debug situations when a user makes a mistake?

For example, in the
RecordRenderGraph:

  1. we set the render target but forget to call builder.UseRendererList(passData.RenderersList) at 39 line of example code.

During execution:

  1. We clear the render target with red.
  2. Call DrawRendererList.
    As a result, the DrawRendererList is ignored, and we get a red screen, but we receive no warnings to easily identify the cause. Is a special tool planned to find such errors? (of course this one is simple to find, but the situation can be more complex)
    Here is an example of such render feature code:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.Rendering.RenderGraphModule;

public class OutlineRenderGraphFeature : ScriptableRendererFeature
{
    class CustomRenderPass : ScriptableRenderPass
    {
        private class PassData
        {
            internal RendererListHandle RenderersList;
        }

        private readonly List<ShaderTagId> m_ShaderTagIdList = new();
        private readonly LayerMask m_LayerMask;
        private readonly Material m_MaskMaterial;

        public CustomRenderPass(LayerMask layerMask, Material maskMaterial)
        {
            m_LayerMask = layerMask;
            m_MaskMaterial = maskMaterial;
        }

        static void ExecutePass(PassData data, RasterGraphContext context)
        {
            context.cmd.ClearRenderTarget(true, true, Color.red);
            context.cmd.DrawRendererList(data.RenderersList);
        }

        public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
        {
            const string passName = "Custom Render Pass";
            using var builder = renderGraph.AddRasterRenderPass<PassData>(passName, out var passData);
            UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();

            PrepareRenderList(renderGraph, frameData, ref passData);
            //We break everything by forgetting to use RenderList, but how can we debug this?
            //builder.UseRendererList(passData.RenderersList);

            builder.SetRenderAttachment(resourceData.activeColorTexture, 0);
            builder.SetRenderAttachmentDepth(resourceData.activeDepthTexture);

            builder.SetRenderFunc((PassData data, RasterGraphContext context) => ExecutePass(data, context));
        }

        private void PrepareRenderList(RenderGraph renderGraph, ContextContainer frameData, ref PassData passData)
        {
            // Access the relevant frame data from the Universal Render Pipeline
            var universalRenderingData = frameData.Get<UniversalRenderingData>();
            var cameraData = frameData.Get<UniversalCameraData>();
            var lightData = frameData.Get<UniversalLightData>();

            var sortingCriteria = cameraData.defaultOpaqueSortFlags;
            var renderQueueRange = RenderQueueRange.opaque;
            var filterSettings = new FilteringSettings(renderQueueRange, m_LayerMask);

            ShaderTagId[] forwardOnlyShaderTagIds =
            {
                new("UniversalForwardOnly"),
                new("UniversalForward"),
                new("SRPDefaultUnlit"), // Legacy shaders (do not have a gbuffer pass) are considered forward-only for backward compatibility
                new("LightweightForward") // Legacy shaders (do not have a gbuffer pass) are considered forward-only for backward compatibility
            };
            m_ShaderTagIdList.Clear();

            foreach (ShaderTagId sid in forwardOnlyShaderTagIds)
                m_ShaderTagIdList.Add(sid);

            DrawingSettings drawSettings = RenderingUtils.CreateDrawingSettings(m_ShaderTagIdList, universalRenderingData, cameraData, lightData, sortingCriteria);
            drawSettings.overrideMaterial = m_MaskMaterial;
            var param = new RendererListParams(universalRenderingData.cullResults, drawSettings, filterSettings);
            passData.RenderersList = renderGraph.CreateRendererList(param);
        }
    }

    [SerializeField] private LayerMask m_LayerMask;
    [SerializeField] private Shader m_MaskShader;

    private Material m_MaskMaterial;

    private CustomRenderPass m_ScriptablePass;

    public override void Create()
    {
        if (m_MaskShader != null)
            m_MaskMaterial = CoreUtils.CreateEngineMaterial(m_MaskShader);

        m_ScriptablePass = new CustomRenderPass(m_LayerMask, m_MaskMaterial)
        {
            renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing
        };
    }

    protected override void Dispose(bool disposing)
    {
        if (m_MaskMaterial != null)
        {
            CoreUtils.Destroy(m_MaskMaterial);
            m_MaskMaterial = null;
        }
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(m_ScriptablePass);
    }
}
  1. My second question concerns the preparation of such RenderLists. This process was somewhat cumbersome in previous versions and has become even more challenging now.
    For example, to prepare a list and render it with a special material, we have to:
  2. Gather various types of data, depending on our task.
  3. Prepare filter settings.
  4. Prepare ShaderTagIds.
  5. Prepare drawing settings.
  6. Create RendererListParams.
  7. Convert all this into a RenderListHandle.
private void PrepareRenderList(RenderGraph renderGraph, ContextContainer frameData, ref PassData passData)
{
    // Access the relevant frame data from the Universal Render Pipeline
    var universalRenderingData = frameData.Get<UniversalRenderingData>();
    var cameraData = frameData.Get<UniversalCameraData>();
    var lightData = frameData.Get<UniversalLightData>();
    var sortingCriteria = cameraData.defaultOpaqueSortFlags;
    var renderQueueRange = RenderQueueRange.opaque;
    var filterSettings = new FilteringSettings(renderQueueRange, m_LayerMask);
    ShaderTagId[] forwardOnlyShaderTagIds =
    {
        new ShaderTagId("UniversalForwardOnly"),
        new ShaderTagId("UniversalForward"),
        new ShaderTagId("SRPDefaultUnlit"), // Legacy shaders (do not have a gbuffer pass) are considered forward-only for backward compatibility
        new ShaderTagId("LightweightForward") // Legacy shaders (do not have a gbuffer pass) are considered forward-only for backward compatibility
    };
    m_ShaderTagIdList.Clear();
    foreach (ShaderTagId sid in forwardOnlyShaderTagIds)
        m_ShaderTagIdList.Add(sid);
    DrawingSettings drawSettings = RenderingUtils.CreateDrawingSettings(m_ShaderTagIdList, universalRenderingData, cameraData, lightData, sortingCriteria);
    drawSettings.overrideMaterial = m_MaskMaterial;
    var param = new RendererListParams(universalRenderingData.cullResults, drawSettings, filterSettings);
    passData.RenderersList = renderGraph.CreateRendererList(param);
}

In previous versions, I could accomplish this in a more straightforward (albeit still cumbersome) manner:

var desc = new RendererListDesc(m_ShaderTagIdList.ToArray(), renderingData.cullResults, renderingData.cameraData.camera)
{
    overrideMaterial = m_MaskRenderObjectMat,
    sortingCriteria = renderingData.cameraData.defaultOpaqueSortFlags,
    renderQueueRange = RenderQueueRange.all,
    layerMask = m_Feature.Settings.LayerMask,
};
var rendererList = context.CreateRendererList(desc);

So, is there a way to prepare such render lists in a more concise manner, especially in situations when I simply want to render something on a special layer with a special mask?

@wwWwwwW1 New CreateSkyboxRendererList() function just landed in RenderGraph API, it will be released in 2023.3.0b10. Thanks for the feedback!

3 Likes

@DrViJ
Thanks for the feedback it’s very valuable to be able to improve the API.

On (1) yes this is very unfortunate behavior. If anything goes wrong with the renderer list creation of access it will currently simply render an empty list. It’s easy to detect this case so we’ll update the error handling to return this error to the user instead of simply doing nothing.

On (2) this is indeed a bit verbose. I agree we should probably have a few helper functions for common use case but I still think you’ll occasionally have to use the ā€œfullā€ API. This is a very powerful API as it’s essentially the main ā€œrenderingā€ function we have to trigger any rendering on the scene graph. It’s very difficult to predict all use cases. Layers, shader tags, material or shader replacement, sorting, which parameters to set up, … are all very specific to one particular use case. But I agree it probably makes sense to rethink the helpers we have in the new render graph context, that would mean directly returning RendererListParams instead of separate DrawingSettings and directly taking the ContextContainer as an argument. This would probably almost halve the code needed to set things up.

Also note that if you prefer the RendererListDesc you can still use it. It internally gets translated into a RendererListParams so it’s essentially a slightly higher level API for the same thing. But the RendererListParams is the more powerful of the two and allows you to do everything you could do with the old ScriptalbeRenderContext.DrawRenderers.

3 Likes

I found out that this feature is already available in urp14, will it work? or do I have to choose to use it in 2023?

RenderGraph is only supported in 23.3. Although the code is there, RG is hidden in older versions. It’s not tested and we don’t backport fixes.

1 Like

I’m on 2023.3.0b8 and it seems like having bloom enabled in a post processing volume stops my UI toolkit documents from rendering.

Turning off Render Graph (Graphics → Compatibility Mode) makes the UI show up again.

Anyone encountering this?

Hi @JackyMooc , we have currently one bug fix related to Screen Space UI + Render Graph that should land soon and that might be related. When you open the Render Graph Viewer, do you see the Screen Space UIToolkit pass at the end of the pipeline?