Skip to content

Transmissive objects cause inefficient rendering on VR headsets #22594

@cabanier

Description

@cabanier

Transmissive objects create a new render target for each render pass of a scene.
When rendering a WebXR scene in a headset, there is a pass for the left eye and one for the right eye.

Looking at the output with the oculus profiling tool, you will see something like this:

Surface 1    | 1024x1024 | color 64bit, depth 24bit, stencil 0 bit, MSAA 4 | 55  96 x208 bins ( 55  rendered) | 4.26 ms | 169 stages :  Binning : 0.042ms Render : 0.955ms StoreColor : 0.862ms Blit : 0.347ms Preempt : 1.439ms StoreDepthStencil : 0.234ms
Surface 2    | 2880x1584 | color 32bit, depth 24bit, stencil 8 bit, MSAA 4 | 135 192x176 bins ( 135 rendered) | 9.90 ms | 410 stages :  Binning : 0.1ms Render : 4.332ms StoreColor : 0.446ms Blit : 0.107ms Preempt : 3.19ms StoreDepthStencil : 0.833ms
Surface 3    | 1024x1024 | color 64bit, depth 24bit, stencil 0 bit, MSAA 4 | 55  96 x208 bins ( 55  rendered) | 4.67 ms | 170 stages :  Binning : 0.042ms Render : 0.972ms StoreColor : 0.861ms Blit : 0.384ms Preempt : 1.751ms StoreDepthStencil : 0.243ms
Surface 4    | 2880x1584 | color 32bit, depth 24bit, stencil 8 bit, MSAA 4 | 135 192x176 bins ( 135 rendered) | 6.10 ms | 676 stages :  Binning : 0.1ms LoadColor : 0.237ms Render : 2.456ms StoreColor : 0.205ms Preempt : 1.451ms LoadDepthStencil : 0.534ms

This shows that the scene is rendered to a 64 bit buffer (1), then everything is drawn to the left eye (2).
After that, the scene is again rendered to a 64 bit buffer (3), and drawn to the right eye (4).
(Note that this trace is running with the EXT_multisampled_render_to_texture extension for easier reading. Regular multisampling looks more complicated but will have a similar timing.)

  • Surface 1 is rendering as usual.
  • Surface 2 is the texture provided by WebXR. Because there's a switch to another surface at the end, the GPU has to save the color and depth information so it flushes it out to main memory (= StoreColor : 0.446ms, StoreDepthStencil : 0.833ms)
  • Surface 3 is again rendering as usual.
  • Surface 4 is the same texture as surface 2. The GPU now has to load the color and depth data from main memory (= LoadColor : 0.237ms, LoadDepthStencil : 0.534ms) before it can continue rendering.

This flushing is causing a 2 ms overhead per frame. If the session is running at 90fps, this would waste almost 20% of your frame budget.

This flush can be avoided by rendering surface 1 and 3 before using them in the scene. I made a small change to WebGLRenderer.js to do so and got the following result:

Surface 1    | 1024x1024 | color 64bit, depth 24bit, stencil 0 bit, MSAA 4 | 55  96 x208 bins ( 55  rendered) | 4.29 ms | 169 stages :  Binning : 0.041ms Render : 0.968ms StoreColor : 0.863ms Blit : 0.342ms Preempt : 1.45ms StoreDepthStencil : 0.231ms
Surface 2    | 1024x1024 | color 64bit, depth 24bit, stencil 0 bit, MSAA 4 | 55  96 x208 bins ( 55  rendered) | 4.08 ms | 113 stages :  Binning : 0.043ms Render : 0.956ms StoreColor : 0.872ms Blit : 0.397ms Preempt : 1.474ms
Surface 3    | 2880x1584 | color 32bit, depth 24bit, stencil 8 bit, MSAA 4 | 135 192x176 bins ( 135 rendered) | 11.73 ms | 277 stages :  Binning : 0.158ms Render : 6.609ms StoreColor : 0.437ms Blit : 0.269ms Preempt : 3.504ms

As you can see, surface 1 and 2 render each eye to a buffer and surface 3 (which is the webxr framebuffer) no longer has to store/load depth or load color.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions