Dynamic Resolution is a feature in the Horizon OS which allows applications to automatically increase their render scale during frames where the GPU is not fully utilized, and decrease their render scale during frames where the GPU is fully utilized.
This means that applications using Dynamic Resolution automatically strike a balance between maintaining the frame rate of their application, and rendering at an optimal resolution.
In scenes that do not fully utilize the GPU (for example, simple corridors), Dynamic Resolution will increase render scale, improving visual quality.
In scenes that fully utilize the GPU (for example, large open viewpoints, cinematic character rendering), Dynamic Resolution will decrease render scale, to the extent needed to preserve framerate.
Additionally, for Meta Quest 2 headsets and later, enabling dynamic resolution is a prerequisite for accessing the highest GPU levels. For instance, GPU Level 5 on the Meta Quest 3 is available if dynamic resolution is enabled.
How does this work
Scenes in an application often have varying levels of complexity, making it challenging to optimize for each one. Dynamic Resolution tackles this by scaling the rendered viewport based on GPU utilization to maintain the targeted frame rate.
If the application starts dropping frames, Dynamic Resolution transitions to a lower resolution. This reduces stale frames and maintains the target frame rate in heavy GPU scenes.
If the application isn’t fully utilizing the GPU, Dynamic Resolution transitions to a higher resolution to improve image quality while maintaining the frame rate.
To avoid reallocating the eye textures every time the recommended resolution changes, the eye textures are allocated once at the maximum resolution. The application will then scale the viewport to the recommended resolution, avoiding rendering outside of the viewport.
Enabling dynamic resolution also allows Horizon OS to expose higher GPU levels to the running application. Accessing these GPU levels is contingent upon using dynamic resolution, because these higher GPU levels consume more power, and therefore generate more heat -- increasing the chance of reaching the headset’s thermal limits.
By enabling dynamic resolution, the Horizon OS is able to dynamically throttle your application’s render scale down during a thermal event, restoring thermal limits in a manner which avoids dropped frames. Since maintaining a consistent framerate is an essential part of comfortable VR experiences, the Horizon OS only allows access to these higher GPU levels if dynamic resolution is enabled.
Prerequisites
Before proceeding with this tutorial, complete the setup steps outlined in the following sections:
Call EnableDynamicResolution (available since Meta XR Core SDK v209) to enable Dynamic Resolution at runtime. Pass true to also set XRSettings.eyeTextureResolutionScale and, with the Universal Render Pipeline, the active pipeline asset’s render scale to maxDynamicResolutionScale. The optional argument defaults to false.
The reallocation runs only for Android builds that use XR Plug-in Management with either the Oculus XR Plugin or OpenXR Plugin. In other configurations, the method enables Dynamic Resolution without reallocating the eye texture.
While Dynamic Resolution is enabled through OVRManager, OVRCameraRig turns on Camera.allowDynamicResolution for its center, left, and right eye cameras. When you disable Dynamic Resolution through OVRManager, the rig resets only the camera settings that it changed.
When disabling Dynamic Resolution, you can manually specify which resolution to render to with the following:
If you set XRSettings.renderViewportScale yourself after disabling Dynamic Resolution, OVRCameraRig no longer enables allowDynamicResolution on the eye cameras. This can cause the lighting problem described in Misaligned additional lighting when Dynamic Resolution is active in the Known Issues section, along with the fix.
Changing the eye texture resolution
When the application starts with Dynamic Resolution enabled in OVRManager, it allocates the eye textures at its maximum resolution scale factor. Disabling Dynamic Resolution does not reallocate them to 1.0x. At runtime, setting the enableDynamicResolution property also leaves the allocation unchanged. To reallocate at the maximum scale factor on a supported Android configuration, call EnableDynamicResolution(reallocateToMaxRes: true).
Dynamic Resolution can be a great tool to easily optimize your application depending on the content of your scenes. It’s important to understand the trade-offs when enabling it:
The maximum resolution scaling factor will be used to allocate the eye buffer at startup. This means that your application will be using more memory if you specify a maximum render scaling factor higher than 1.0x.
Setting a low minimum render scaling factor might result in quality degradation when frame rate can’t be maintained.
If you can afford the extra memory cost, consider increasing the maximum scaling factor to enhance image quality in scenes with low GPU usage. However, exercise caution when setting the minimum resolution scaling factor. Setting it too high might cause frame drops in GPU-intensive scenes, while setting it too low could lead to noticeable quality degradation when GPU utilization rises. This degradation tends to be less evident in action-packed scenes with FX and more noticeable in slower-paced sequences.
Profiling an application with Dynamic Resolution
It’s really important to disable Dynamic Resolution when profiling your application because it will have an impact on the performance of the application.
Remember that Dynamic Resolution isn’t a fix for all performance issues. Dynamic Resolution lowers the resolution of your application if it is poorly optimized to maintain frame rate. In order to maintain a high resolution at the targeted frame rate, you should always use the appropriate tools to profile and optimize your application to the fullest before enabling the feature.
Finally, your application will also need to respect the requirements regarding performance, which prevent applications from running at a low resolution most of the time. You can find the details for the corresponding VRC here.
Dynamic Foveated Rendering
It is possible to enable Dynamic Foveated Rendering (both Fixed Foveated Rendering (FFR) and Eye Tracked Foveated Rendering (ETFR)) with Dynamic Resolution. The runtime will try to increase the foveated rendering level first before starting to scale the resolution down if the GPU utilization gets too high.
Check the recommended resolution with VrApi
You can verify what the recommended runtime resolution is by looking at the VrApi logs:
Looking at the GPU utilization (GPU%), you can see that it’s starting to get quite high. The runtime will recommend a lower scaling factor (SF) to reduce the number of stale frames (Stale) and lower the GPU pressure. SF is a multiplier factor on top of the system’s default recommended eye buffer resolution.
Known Issues
Currently known issues are fixed in later versions of Unity and Meta XR SDK. If you cannot upgrade to newer versions, here are some fixes you can apply locally.
Misaligned additional lighting when Dynamic Resolution is active
With the Universal Render Pipeline, point and spot lights stop lining up with the image once Dynamic Resolution scales the viewport down. The effect grows as the resolution scale drops and disappears at a scale of 1.0. The symptom depends on the renderer:
Forward+: lights render as rectangular blocks with hard edges.
Deferred and Deferred+: lights keep soft edges but drift away from the surfaces they light. The shift grows toward the edges of the view.
Unity scales the _ScaledScreenParams and _ScreenSize shader constants only when the camera has Dynamic Resolution enabled. Without that setting, the constants describe the full eye texture instead of the smaller region the frame was rendered into. Forward+ and Deferred+ use those constants to map pixels to light tiles, so the tile grid no longer lines up with the image. Deferred and Deferred+ also rebuild world positions from the depth buffer against that same full-size viewport, which displaces the lighting.
Starting with Meta XR Core SDK v209, OVRCameraRig turns this setting on for its eye cameras while Dynamic Resolution is enabled through OVRManager, so that path needs no fix. In earlier versions, or when you set XRSettings.renderViewportScale yourself with Dynamic Resolution disabled in OVRManager, check Dynamic Resolution on the CenterEyeAnchor camera under OVRCameraRig. You can also enable the camera’s allowDynamicResolution property from a script, as long as it runs before rendering starts.
This applies to all three renderers.
This workaround needs the Vulkan graphics API. Unity doesn’t support dynamic resolution on OpenGL ES, so the camera’s allowDynamicResolution stays off and the lighting stays misaligned. If your project uses OpenGL ES, switch to Vulkan.
Incompatibility with the RenderObjects renderer feature (Fixed in Unity 6000.0.81f1+, 6000.3.21f1+, 6000.5.6f1+, 6000.6.0b5+, and 6000.7.0a3+)
When using dynamic resolution with the RenderObjects renderer feature in Unity, this will cause 2 issues.
Objects rendered with the feature will be rendered with the wrong viewport, causing them to move on screen when the dynamic resolution scaler changes.
When used with foveation, Unity will split each RenderObjects pass into individual vulkan render passes despite writing to the same vulkan attachments, causing a degradation in performance.
Currently there is no workaround for this behavior.
TempRT reallocation leading to OOM (Fixed in Unity 6000.0.1f1+ and 2022.3.15f1+)
Temporary render targets get reallocated when assigning a value to XRSettings.renderViewportScale. This can potentially lead to an out-of-memory. To avoid reallocating tempRT, you can do the following:
In XRSystem.cs (for Unity 2021), in UpdateCameraData() change the following lines:
This will make sure that all render passes using the cameraTargetDescriptor to configure themselves will not be changing resolution when the viewport changes. Enabling dynamic scaling on the descriptor will also set the dynamic scaling flag on all temporary render targets using that descriptor.
If using Unity 2022+ and the Universal Render Pipeline, implement this patch from Unity’s Universal Render Pipeline, or sync to a URP version greater than or equal to 14.0.9. If using the Oculus-VR branches of URP, you still need to sync to a URP version greater than or equal to 14.0.9.
The other change needed is in OVRManager.cs. Instead of setting just the XRSettings.renderViewportScale, we’ll also set the ScalableBufferManager width and height:
XRSettings.renderViewportScale = scalingFactor;
ScalableBufferManager.ResizeBuffers(scalingFactor, scalingFactor); // add this line to also scale the temporary render targets WITHOUT reallocating them.
Additional RP when scaling is not equal to 1 (Fixed in Unity 6000.0.1f1+, 2022.3.22f1+ and 2021.3.36f1+)
If your application is rendering directly to the XRTarget (when your pipeline doesn’t have any intermediate passes that renders to the camera), when scaling factor is not 1, URP will force an additional intermediate render pass which will cause a performance hit. To avoid the additional render pass, you can do the following:
In UniversalRenderer.cs in RequiresIntermediateColorTexture(), you can comment out the condition that forces the intermediate pass !cameraData.isDefaultViewport. Unity will now render directly to the XR Target when scaling != 1, but the viewport won’t be set correctly. You can fix this by adding a SetViewport command in your final renderpass in Execute, just before calling context.ExecuteCommandBuffer(cmd);:
Rect rect = new Rect() {x=0,y=0, width=renderingData.cameraData.pixelWidth, height=renderingData.cameraData.pixelHeight };
cmd.SetViewport(rect);
Screen Distortion in Unity 6000.0.22f1 through 6000.0.24f1 (Fixed in Unity 6000.0.25f1+)
Enabling Dynamic Resolution in 6000.0.22f1, 6000.0.23f1, and 6000.0.24f1 will cause screen distortion. It’s recommended to update to version 6000.0.25f1 or greater.