In the Vulkan API, a single render pass consists of one or more subpasses. Instead of writing the rendering result from GPU memory to system memory between each rendering operation, subpasses conserve memory bandwidth by utilizing the contents of the framebuffers from previous subpasses within the same render pass. For the Meta Quest’s tile-based GPUs, subpasses can improve performance for certain rendering operations like post-processing effects and depth lookup.
Prerequisites
To use subpasses in Meta Horizon apps, there are a few requirements:
You must be on Unity 2022.3.42f1+ or Unity 6000.0.23f1+
Vulkan API must be enabled for Android. See here for more info.
You must use the correct branch of Meta’s URP fork, which contains some code changes needed to run subpasses. See the setup instructions below for your version to apply the corresponding changes.
Project setup
Unity 6
Add the following lines to your project’s manifest.json file, removing any lines with the same keys:
Navigate to Edit > Project Settings > Graphics. Under Pipeline Specific Settings, scroll down to the bottom and make sure Compatibility Mode (Render Graph Disabled) is unchecked.
Unity 2022 LTS
Add the following lines to your project’s manifest.json file, removing any lines with the same keys:
In the URP Renderer used by your URP Asset, make sure that Native RenderPass is checked.
Details
Instead of rendering the entire buffer and storing the result to system memory for every render pass, using subpasses will keep the result of a previous subpass in tile memory so that it can be reused for subsequent subpasses. Since our Meta Quest devices are equipped with mobile GPUs (using tile-based rendering), this results in less transfer between the GPU memory (tile memory) and RAM (system memory), which in turn means better GPU performance.
There are some limitations with subpasses, notably that the frame buffers of each subpass need to have the same dimensions. Another limitation is that only the current pixel coordinates can be used to sample the result of previous subpasses. Because of this, effects like bloom or blurs cannot be implemented using subpasses.
Use post-processing effects
Post-processing effects that are tile compatible (Channel Mixer, Color Adjustments, Color Curves, Color Lookup, Film Grain, Lift Gamma Gain, Shadows Midtones Highlights, Split Toning, Tonemapping, Vignette, WhiteBalance) can be done more efficiently with subpasses. To use post-processing:
In the URP Renderer, check the Post-processing > Enabled checkbox.
In the Camera settings, check the Rendering > Post Processing checkbox.
Subpasses are useful for shaders that require depth sampling. By dividing the object rendering process into two subpasses, you can handle all opaque objects in the first subpass and then render objects that require depth in the second subpass. To achieve this, you will need to properly configure the URP passes and make several modifications to the shader graph / shader code.
Configure depth input pass
Add a new layer called “DepthInputSubpass”. Set this layer on any GameObjects that you want to use a depth lookup shader.
In the URP Asset, make sure Depth Texture and Opaque Texture are unchecked.
Usually these options would need to be enabled to supply shaders with depth information. In the case of Vulkan subpasses, however, enabling these options will create an unwanted copy pass, preventing the opaque and depth input subpasses from merging together and breaking rendering.
Under the Filtering section, expand the Opaque Layer Mask dropdown and uncheck the “DepthInputSubpass” layer that was added in step 1. Do the same for Transparent Layer Mask.
Set Compatibility > Intermediate Texture to “Auto”.
At the bottom, select Add Renderer Feature > Render Objects. Set up the feature as follows:
Set Event to either “AfterRenderingTransparents” or “AfterRenderingOpaques”.
Set Layer Mask to the “DepthInputSubpass” layer that was added in step 1.
Under Overrides, check Depth and Depth Input. Make sure that Write Depth is unchecked.
Use depth input in shader graph
To use depth input in a shader graph, you will need to add a special keyword so that the Scene Depth node will read from an input attachment instead of doing texture fetching:
Within your shader graph, click the plus icon and select Keyword > Boolean.
Name the new keyword DEPTH_INPUT_ATTACHMENT. Do not change the Reference – it must be _DEPTH_INPUT_ATTACHMENT.
You can also define the depth input attachment and then load from it in HLSL. When using the render object pass, the attachment index for depth will be set to 0. Define the input attachment inside a shader with the following:
Renderdoc Meta Fork can be a valuable tool for verifying if URP passes are merged as subpasses or not. For instance, when using post-processing with subpasses, you can confirm that the subpasses have been merged correctly by checking if they are part of the same Vulkan render pass and by observing that vkCmdNextSubpass() is called between subpasses.
Using Render Graph Viewer with Meta XR Simulator
In Unity 6, the new Render Graph Viewer can also be used for inspecting URP subpasses. This requires using the Unity OpenXR Plugin, Meta XR Simulator, and setting Vulkan as the graphics API for Windows/Mac/Linux. When using XR Sim in editor, you can check if the render passes are merged or not. In the image below, the blue bar under the render passes indicates that those have been merged into a single render pass as subpasses. The graph also shows useful information about the access and use of pipeline resources (textures, attachments, etc.).
Limitations
OnRenderObjectCallbackPass will prevent the post processing pass from being merged with the draw object pass. In the changes within Meta’s URP fork, this pass is commented out.
The Depth Texture and Opaque Texture settings in the URP Asset will trigger a depth/color copy pass and break the rendering. When using Vulkan subpasses, these should always be unchecked.
Due to the limitation that subpasses can only sample the current pixel, the following post-processing effects can’t be implemented with subpasses: Bloom, Chromatic Aberration, Depth of Field, Lens Distortion, Motion Blur, and Panini Projection.
Due to the requirement that render targets be the same size, Dynamic Resolution is currently incompatible with the use of subpasses.
Known Issues
When using post processing with subpass, the “Use Recommended MSAA Level” setting under OVR Manager will cause a glitch in the first frame. It’s recommended to uncheck it.
In Unity 6, when playing in the editor with Vulkan as the graphics API and using the Unity OpenXR Plugin, MSAA will cause the rendered result to appear black. To resolve this issue, it is recommended to disable MSAA while playing in the editor.