Ever wonder if it’s possible to make Unity follow a precise frame rate and potentially even follow an external clock source (commonly known as genlock)? This post will discuss how Unity natively maintains frame rates and how user code can be added to tightly control it. This can be vital in an environment like a broadcast studio where synchronization between Unity and other equipment is crucial.

Normally, out of the box, a Unity project will attempt to run your project as fast as possible. Frames will be rendered as quickly as they can while generally being limited by your display device’s refresh rate (see V Sync Count). The simplest way to start controlling frame rate is to explicitly set the QualitySettings.vSyncCount so that rendering will occur at an interval related to the display device’s refresh rate (e.g., for a 60Hz display, setting vSyncCount=2 will cause Unity to render at 30fps in sync with the display). This may not give granular enough control, however, as you are limited to submultiples of the display refresh rate.

The next simplest solution would be to set QualitySettings.vSyncCount=0 and use Application.targetFrameRate to target a frame rate independent of the display’s refresh rate. With this set, Unity will throttle back its rendering loop to approximately this rate (note that tearing may occur since Unity will no longer be rendering in sync with the display). This is done in a low-cost manner so as not to unnecessarily burn CPU resources. The downside is that this approach may not yield the required precision for every use case.

Fear not, coroutines can help improve precision. Rather than rely on Unity’s built-in frame rate throttling, you can control it yourself via script code. In order to do so, you must let Unity try to run as fast as possible by setting QualitySettings.vSyncCount=0 and Application.targetFrameRate to a very high value, and then, using a WaitForEndOfFrame coroutine, slow it down to precisely the rate you are looking for by refusing to allow the next frame to start rendering until you say so. To do this precisely, we suggest you use a combination of Thread.Sleep to conservatively delay the Unity rendering loop without eating up CPU resources, and then for the last few milliseconds, spin the CPU while checking for exactly the right time to allow the next frame to start. If you are trying to coordinate Unity’s frame rate with an external clock (genlock) you should break out of this CPU spinning loop as soon as an external sync signal is received.

using System.Collections; using System.Threading; using UnityEngine; public class ForceRenderRate : MonoBehaviour { public float Rate = 50.0f; float currentFrameTime; void Start() { QualitySettings.vSyncCount = 0; Application.targetFrameRate = 9999; currentFrameTime = Time.realtimeSinceStartup; StartCoroutine("WaitForNextFrame"); } IEnumerator WaitForNextFrame() { while (true) { yield return new WaitForEndOfFrame(); currentFrameTime += 1.0f / Rate; var t = Time.realtimeSinceStartup; var sleepTime = currentFrameTime - t - 0.01f; if (sleepTime > 0) Thread.Sleep((int)(sleepTime * 1000)); while (t < currentFrameTime) t = Time.realtimeSinceStartup; } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 using System . Collections ; using System . Threading ; using UnityEngine ; public class ForceRenderRate : MonoBehaviour { public float Rate = 50.0f ; float currentFrameTime ; void Start ( ) { QualitySettings . vSyncCount = 0 ; Application . targetFrameRate = 9999 ; currentFrameTime = Time . realtimeSinceStartup ; StartCoroutine ( "WaitForNextFrame" ) ; } IEnumerator WaitForNextFrame ( ) { while ( true ) { yield return new WaitForEndOfFrame ( ) ; currentFrameTime += 1.0f / Rate ; var t = Time . realtimeSinceStartup ; var sleepTime = currentFrameTime - t - 0.01f ; if ( sleepTime > 0 ) Thread . Sleep ( ( int ) ( sleepTime * 1000 ) ) ; while ( t < currentFrameTime ) t = Time . realtimeSinceStartup ; } } }

Finally, on a related topic, if trying to coordinate time with an external clock source, you may also want Unity’s internal game time to advance at the same pace (rather than follow the CPUs clock which may drift over time relative to the external source). This is can be achieved by setting Time.captureFramerate to the same rate as the external clock. For example, if your external genlock signal is operating at 60fps, setting captureFramerate to 60 instructs Unity to allow exactly 1/60th of a second of game time to elapse between frame renders regardless of exactly how much real time has passed. As of Unity 2019.2 beta, it is possible to set a floating point capture frame rate by setting Time.captureDeltaTime. For older Unity versions, if your external signal is not operating at an integer based rate (like say 59.94), you can vary captureFramerate at every frame rendering to achieve the desired average rate for the advancement of time.

A sample proof-of-concept Unity project exploring the above topic is available at our GitHub project page. Specifically, precise control of frame rate using the WaitForEndFrame coroutine is given in ForceRenderRate.cs. A more complex example which emulates an external genlock can be found in GenLockedRecorder.cs. Although Unity does not natively support any vendor-specific genock implementation, the latter is a good starting point for integration with a third-party SDK offering this feature.

Please note that all above techniques yield the most stable frame rates when part of a Unity standalone player build. They are functional nonetheless when in Play Mode inside the Unity Editor but you may experience momentary fluctuations from time to time.