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

Hi @AMoulin , thank you for your reply!

I’ve investigated the AddBlitPass() and ImportTexture() method and the other high-level APIs.

After a while I’ve succeeded to do a copy with both eyes. But it’s a very messy and dirty code. I’ve taken the FinalBlitPass as an example to achieve that. I noticed also that even if I put the renderPassEvent as renderPassEvent = RenderPassEvent.AfterRendering + 30 it will never occur after the FinalBlit. This is fine for the copy I guess, but since my camera is only needed for the copy, is there a way to not do the FinalBlit + XRDepthCopy? Because it’s useless in my copy case, right?

Here’s a look of my render graph. Is it how it’s supposed to look? (The RT I am copying data is called XRCamera)

Also, I’ve battled a lot with the RenderingUtils.ReAllocateHandleIfNeeded, calling it break the RenderTexture (The one I am doing the copy, XRCamera), looks like that re-allocating a texture, even if it’s because of its name, breaks the Blitter.BlitTexture.

Here’s the code to acheive the copy, inspired from FinalBlitPass. Is it a good approach?

public class XRCopyPass : ScriptableRenderPass
{
    // From FinalBlitPass
    private class PassData
    {
        internal TextureHandle source;
        internal TextureHandle destination;
        internal int sourceID;
        internal bool requireSrgbConversion;
        internal bool enableAlphaOutput;
        internal BlitMaterialData blitMaterialData;
        internal UniversalCameraData cameraData;
    }

    // From FinalBlitPass
    private struct BlitMaterialData
    {
        public Material material;
        public int nearestSamplerPass;
        public int bilinearSamplerPass;
    }

    // From FinalBlitPass
    private static class BlitPassNames
    {
        public const string NearestSampler = "NearestDebugDraw";
        public const string BilinearSampler = "BilinearDebugDraw";
    }

    private readonly string _passName;
    private readonly int _sourceTexId = Shader.PropertyToID("_SourceTex");
    private readonly int _id = 0;


    private BlitMaterialData _blitMaterialData;
    private readonly RenderTexture _destinationRenderTexture;
    private TextureHandle _destination;
    private RTHandle _destinationHandle = null;

    public XRCopyPass(Camera camera, int index, RenderTexture destinationRenderTexture = null)
    {
        requiresIntermediateTexture = true;
        renderPassEvent = RenderPassEvent.AfterRendering + 30; // The max range is + 50 !
        _id = index;
        _passName = string.Format("XRCopyPass_{1}", passName, _id);
        _destinationRenderTexture = destinationRenderTexture;

        GraphicsSettings.TryGetRenderPipelineSettings<UniversalRenderPipelineRuntimeShaders>(out var shadersResources);
        Material blitMaterial = CoreUtils.CreateEngineMaterial(shadersResources.coreBlitPS);
        _blitMaterialData.material = blitMaterial;
        _blitMaterialData.nearestSamplerPass = blitMaterial?.FindPass(BlitPassNames.NearestSampler) ?? -1;
        _blitMaterialData.bilinearSamplerPass = blitMaterial?.FindPass(BlitPassNames.BilinearSampler) ?? -1;

        UniversalAdditionalCameraData universalAdditionalCameraData = camera.GetComponent<UniversalAdditionalCameraData>();
        universalAdditionalCameraData.scriptableRenderer.EnqueuePass(this);
    }

    public void Render(RenderGraph renderGraph, ContextContainer frameData)
    {
        UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
        if (cameraData.xr.enabled)
        {
            using (IRasterRenderGraphBuilder builder = renderGraph.AddRasterRenderPass(_passName, out PassData passData))
            {
                UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
                if (_destinationHandle == null)
                {
                    _destinationHandle = RTHandles.Alloc(_destinationRenderTexture);
                }

                // When the _destinationRenderTexture contains depthStencilFormat, we need to remove otherwise the ImportTexture will fail
                // But this doesn't seem to work, the output texture is empty
                /*RenderTextureDescriptor descriptor = new RenderTextureDescriptor(
                    _destinationRenderTexture.width,
                    _destinationRenderTexture.height,
                    _destinationRenderTexture.format,
                    _destinationRenderTexture.mipmapCount)
                {
                    stencilFormat = GraphicsFormat.None,
                    depthStencilFormat = GraphicsFormat.None,
                };

                RenderingUtils.ReAllocateHandleIfNeeded(
                    ref _destinationHandle, descriptor, FilterMode.Bilinear, TextureWrapMode.Clamp,
                    _destinationRenderTexture.anisoLevel, _destinationRenderTexture.mipMapBias, _destinationRenderTexture.name);*/

                _destination = renderGraph.ImportTexture(_destinationHandle);

                InitPassData(cameraData, ref passData);

                passData.sourceID = _sourceTexId;
                passData.source = resourceData.cameraColor;
                builder.UseTexture(passData.source, AccessFlags.Read);
                passData.destination = _destination;
                builder.SetRenderAttachment(_destination, 0, AccessFlags.Write); // Write or WriteAll?

                // This is a screen-space pass, make sure foveated rendering is disabled for non-uniform renders
                bool passSupportsFoveation = !XRSystem.foveatedRenderingCaps.HasFlag(FoveatedRenderingCaps.NonUniformRaster);
                builder.EnableFoveatedRasterization(cameraData.xr.supportsFoveatedRendering && passSupportsFoveation);

                // Removing this line will cause the whole editor to...display crap. Unity needs to be restarted after
                builder.AllowGlobalStateModification(true);

                builder.AllowPassCulling(false);

                builder.SetRenderFunc((PassData data, RasterGraphContext context) =>
                {
                    // Important to have good colors
                    context.cmd.SetKeyword(ShaderGlobalKeywords.LinearToSRGBConversion, data.requireSrgbConversion);

                    data.blitMaterialData.material.SetTexture(data.sourceID, data.source);

                    ExecutePass(context.cmd, data, data.source, data.destination, data.cameraData);
                });
            }
        }
    }

    private void InitPassData(UniversalCameraData cameraData, ref PassData passData)
    {
        passData.cameraData = cameraData;
        // This is forced to true because we are in B8G8R8A8_UNorm, not sRGB and linear (cameraData.requireSrgbConversion is not accessible)
        passData.requireSrgbConversion = true;// cameraData.requireSrgbConversion;
        passData.enableAlphaOutput = cameraData.isAlphaOutputEnabled;
        passData.blitMaterialData = _blitMaterialData;
    }

    private static void ExecutePass(RasterCommandBuffer cmd, PassData data, RTHandle source, RTHandle destination, UniversalCameraData cameraData)
    {
        Vector4 scaleBias = GetFinalBlitScaleBias(source, destination, cameraData);

        CoreUtils.SetKeyword(data.blitMaterialData.material, ShaderKeywordStrings._ENABLE_ALPHA_OUTPUT, data.enableAlphaOutput);

        if (source.rt != null)
        {
            int shaderPassIndex = source.rt.filterMode == FilterMode.Bilinear ? data.blitMaterialData.bilinearSamplerPass : data.blitMaterialData.nearestSamplerPass;
            Blitter.BlitTexture(cmd, source, scaleBias, data.blitMaterialData.material, shaderPassIndex);
        }
    }

    // From RenderingUtils
    private static Vector4 GetFinalBlitScaleBias(RTHandle source, RTHandle destination, UniversalCameraData cameraData)
    {
        Vector2 viewportScale = source.useScaling ? new Vector2(source.rtHandleProperties.rtHandleScale.x, source.rtHandleProperties.rtHandleScale.y) : Vector2.one;
        var yflip = cameraData.IsRenderTargetProjectionMatrixFlipped(destination);
        Vector4 scaleBias = !yflip ? new Vector4(viewportScale.x, -viewportScale.y, 0, viewportScale.y) : new Vector4(viewportScale.x, viewportScale.y, 0, 0);

        return scaleBias;
    }

    public void Dispose()
    {
        _destinationHandle?.Release();
    }

    public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    {
        Render(renderGraph, frameData);
    }
}