Camera.Render() seems to trigger Canvas.SendWillRenderCanvases()

Hey there. I’ve got a background camera rendering out some meshes to a RenderTexture. There is also a complex uGUI doing stuff with the user. Both of these have to run in parallel. I’ve found through profiling that calling a Camera.Render() on this background camera triggers Canvas.SendWillRenderCanvases() on the rest of the UI.


What in tarnation! The background camera is set to have an empty event mask, and a culling mask of only one layer where it renders what it needs to. There are no UI elements on this layer. Has anyone seen this before? How do you tell a camera that it doesn’t need to care about UI in any way, shape or form?

5 Likes

For anyone’s interest, I did end up solving this problem with some reflection. Basically I got the delegate, cached the value, nulled the value, did the camera render, and then set the delegate again from the cached value.

var canvasHackField = typeof(Canvas).GetField("willRenderCanvases", BindingFlags.NonPublic | BindingFlags.Static);
var canvasHackObject = canvasHackField.GetValue(null);
canvasHackField.SetValue(null, null);
Camera.Render();
canvasHackField.SetValue(null, canvasHackObject );
17 Likes

This is still an issue in 2017.3. Thanks @cowtrix1 for figuring out this hacky solution, though this really needs to be addressed officially. It makes no sense that this much performance is wasted - cameras that don’t render any UI should not have to do expensive calculations on whether or not they will render canvases. I have a set-up with many cameras rendering to render textures, and this eats away 50 milliseconds per frame for nothing.

2 Likes

We noticed the same issue in 2018.2.6f1 recently. It drastically impacts performance on consoles for us, so I’m happy to have found this workaround, thank you @cowtrix1 , I hope it still works in 2018.2.

I’ve also reported this as a bug (ID 1090213), but have not heard back from QA for a few days.

1 Like

We actually heard back from QA after a while and had a bit of back-and-forth about this issue (ID 1090213)
The result was:

“After contacting developers about it, this seems to be expected behaviour. Consider all the separate Camera.Render() calls as different screenshots - the UI has to be rendered.
We’re sorry that it’s causing you inconveniences.”

Which seems absolutely ridiculous to me. This would imply Unity expects us to use Camera.Render() only for creating screenshots? If I want a screenshot that includes the UI, I can simply enable the UI layer in the CullingMask of my camera. But if I want to render literally anything else, why am I forced to endure this useless UIEvents.WillRenderCanvases call even though no canvases are actually rendered?
Sure, there is the workaround Hack posted above, but this shouldn’t be the only solution to such an inherently wrong behaviour.

Attached profiler screenshot shows a simple scene with some dynamic UI and one disabled camera (in addition to the main one) that is rendered from the Update function of a custom script. You can see almost 1ms being wasted on stuff that is simply not needed. The project this profiler screenshot has been taken in is also attached (2018.3.0b12).


3986656–342937–willrendercanvases_update.zip (315 KB)

5 Likes

It is definitely ridiculous. The post I made at the beginning of this year is about a running project, so we have the same requirements to this day. The cameras only render depth to render textures for use in a custom shader, they will never have anything to do with UI. If it wasn’t for cowtrix, Unity would not even have been viable for this project. It is such an easy thing to bypass and it hogs so much performance doing absolutely nothing. I see no reason why Unity is pulling the “intended behaviour” card here.

3 Likes

When I disabled Pixel Perfect on the canvas the frame rate drop went away.

Camera.Render() calls so much crap.
I have some that does 1ms while there’s nothing to render.

I decided to replace it fully with custom draw calls using CommandBuffer.DrawMesh (or DrawMeshInstanced). Luckily this was possible in my project because the camera’s only render a limited amount of meshes, so I don’t need occlusion culling. Frustum culling is easy to set up with GeometryUtility.TestPlanesAABB.

Mind sharing how? I can’t figure how to reproduce a proper rendering. I always end up with messed up matrices.

Here, this should cover it: Instanced Drawing With Unity, Part 1 – JavDev Coding

Work if you don’t have the notion of a near/far clip, frustum and perspective, which I need.

Up. Does Unity handle it in some way? I have this issue in Unity 2019.4.11f1

Up as well. It´s a really annoying behaviour and costs tons of performance.

Hello from 2024 and this issue is still a problem! (Unity 2023.1.16f1)

I have 3 cameras that I must render with Camera.Render() and combine them with Graphics.Blit() with custom shader. None of those cameras render UI (UI is on a culled layer).
I need to render them manually so the XR inputs don’t unsync camera’s position between cameras writing to render textures and the other.

Why not give use a Camera.Render(bool updateCanvas = true) that we can choose to set to false? Or any other easy way to disable useless expensive canvas update?

Such a waste of performance!


(there is another expected Rendering.UpdateBatches() call at the end of update not visible in this screenshot)

Edit: just realized I have the same issue with the generation of miniature images (that don’t have UI) taking more time for useless update to the UI than actually render the object to a texture!

Edit 2: I can’t get the hacky solution to work, it seems the call is now from UIEvents.WillRenderCanvases and I can’t find it on the C# side throught reflection.

4 Likes

also having this issue

I’m so happy I found this thread

I’ve been doing some profiling today and lo and behold. A whopping 22% of my frame time in a pretty tame game state, is going to updating canvases because the water reflection camera rendered.

Unity 2021.3.31f1. (still LTS as far as I know)

But I have to say, incredibly many thanks to cowtrix for sharing his hack around the problem. It’s actually pretty beautiful.

2 Likes

It seems we are back in the trenches of this issue once again with newer versions of Unity now using UIEvents, and my solution is no longer a possibility. Bump - it’s been 7 years, folks. Devs need to be able to use cameras for many tasks entirely unrelated to taking screenshots.

4 Likes

This definitely isn’t a perfect solution but I think does remove the brunt of UIEvents.WillRenderCanvases by clearing the rebuild queues as the camera renders

private static IList<ICanvasElement> _layoutList;
private static IList<ICanvasElement> _graphicsList;

public static void RenderWithoutUpdatingUI(Camera camera)
{
    Canvas.preWillRenderCanvases += CancelRebuild;
    camera.Render();
    Canvas.preWillRenderCanvases -= CancelRebuild;

    void CancelRebuild()
    {
        if (_layoutList == null)
        {
            var layoutQueueField = typeof(CanvasUpdateRegistry).GetField("m_LayoutRebuildQueue", BindingFlags.Instance | BindingFlags.NonPublic);
            _layoutList = layoutQueueField.GetValue(CanvasUpdateRegistry.instance) as IList<ICanvasElement>;
        }
        _layoutList.Clear();

        if (_graphicsList == null)
        {
            var graphicsQueueField = typeof(CanvasUpdateRegistry).GetField("m_GraphicRebuildQueue", BindingFlags.Instance | BindingFlags.NonPublic);
            _graphicsList = graphicsQueueField.GetValue(CanvasUpdateRegistry.instance) as IList<ICanvasElement>;
        }
        _graphicsList.Clear();
    }
}

Unfortunately, this didn’t work for me in Unity 2022.3.29f, are there any other options?

1 Like