Black output in "ScriptableRenderPass" with "AddCopyPass" if MSAA enabled

I’m trying to port an old render feature to URP 17.0.3 using Render Graph on Unity 6.0.33, but all I got was a black color output. So I tried one of the examples from the URP package samples, CopyRenderFeature, and turns out it also outputs black, but only when MSAA is enabled. It happens both in the editor and a Windows build.

I’m new to Render Graph, but the example should be simple enough to figure out what is wrong, and I can’t see why it fails. This is the code:

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.RenderGraphModule.Util;
using UnityEngine.Rendering.Universal;

// This example copies the active color texture to a new texture. This example is for API demonstrative purposes,
// so the new texture is not used anywhere else in the frame, you can use the frame debugger to verify its contents.
public class CopyRenderFeature : ScriptableRendererFeature
{
    class CopyRenderPass : ScriptableRenderPass
    {
        public CopyRenderPass()
        {
            //The pass will read the current color texture. That needs to be an intermediate texture. It's not supported to use the BackBuffer as input texture. 
            //By setting this property, URP will automatically create an intermediate texture. 
            //It's good practice to set it here and not from the RenderFeature. This way, the pass is selfcontaining and you can use it to directly enqueue the pass from a monobehaviour without a RenderFeature.
            requiresIntermediateTexture = true;
        }

        // This is where the renderGraph handle can be accessed.
        // Each ScriptableRenderPass can use the RenderGraph handle to add multiple render passes to the render graph
        public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
        {
            const string passName = "Copy To or From Temp Texture";

            // UniversalResourceData contains all the texture handles used by the renderer, including the active color and depth textures
            // The active color and depth textures are the main color and depth buffers that the camera renders into
            UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();

            // The destination texture is created here, 
            // the texture is created with the same dimensions as the active color texture
            var source = resourceData.activeColorTexture;

            var destinationDesc = renderGraph.GetTextureDesc(source);
            destinationDesc.name = $"CameraColor-{passName}";
            destinationDesc.clearBuffer = false;

            TextureHandle destination = renderGraph.CreateTexture(destinationDesc);  
           
            if (RenderGraphUtils.CanAddCopyPassMSAA())
            {
                // This simple pass copies the active color texture to a new texture. 
                renderGraph.AddCopyPass(resourceData.activeColorTexture, destination, passName: passName);

                //Need to copy back otherwise the pass gets culled since the result of the previous copy is not read. This is just for demonstration purposes.
                renderGraph.AddCopyPass(destination, resourceData.activeColorTexture, passName: passName);
            }
            else
            {
                Debug.Log("Can't add the copy pass due to MSAA");
            }
        }
    }

    CopyRenderPass m_CopyRenderPass;

    /// <inheritdoc/>
    public override void Create()
    {
        m_CopyRenderPass = new CopyRenderPass();

        // Configures where the render pass should be injected.
        m_CopyRenderPass.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
    }

    // Here you can inject one or multiple render passes in the renderer.
    // This method is called when setting up the renderer once per-camera.
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(m_CopyRenderPass);
    }
}

(RenderGraphUtils.CanAddCopyPassMSAA returns true).

I’m trying it on a completely new project, on Windows. These are the settings:



And this is the output (the sphere should be lit):

The same happens on MacOS.

This is a bug indeed. CopyPass uses the Framebuffer Input macro so it can be merged with other render passes. This only works natively on certain graphics APIs (vulkan, …). There is an automatic fallback path that uses a regular texture sample but this is currently broken for MSAA. We are working on a fix for a while, it’s quite complicated and deep in the tech stack. We hope to land a fix in a few weeks.

2 Likes