A Frame’s Life https://dev.epicgames.com/community/learning/tutorials/DEwL/unreal-engine-a-frame-s-life-frame-timing-synchronization-and-latency-in-ue
Epic is working on new frame pacing mode which is hardware agnostic adaptive game-thread throttling mode - it’s still being worked on as of UE5.8. until then Use r.GTSyncType=2 on consoles ( which is sync the game thread with swap chain present +/- an offset in milliseconds. CVar rhi.SyncSlackMS drives the offset)
Maximum Hitch Resiliency You can use r.GTSyncType=0 (Game thread syncs with rendering thread (old behaviour, and default).) for maximum hitch resiliency. That’s currently the default in Unreal. This is at the cost of much greater input latency; only use this option if you absolutely have to. https://dev.epicgames.com/documentation/unreal-engine/low-latency-frame-syncing-in-unreal-engine
there is a plugin (from epic’s developer) : https://github.com/Flassari/DeltaTimeSmootherPlugin. which work best with r.GTSyncType=0 and helps in removing jitters(animation error and micro-hitches) which comes with the cost of high input latency and only works when VSync is enabled.
on consoles Double Buffering is default, but triple buffering(default on PC) increases hitch resiliency but increases input latency and memory usage(GPU VRAM).
turning off VSync introduces tearing but reduces latency but lowering the hitch resiliency. on consoles it’s better to have VSync on by default. Compositing takes an extra frame (16.66ms at 60Hz), so we want to skip it wherever we can. If the game is the only thing drawn, we don’t need compositing!
Unreal has two upscaling stages, the Primary Upscale and the Secondary Upscale. The Primary Upscale is for the main temporal upscaling, such as TSR and third-party upscalers. The Secondary Upscale is for optional spatial upscaling.
For the Geometry and Depth of Field stages, Unreal uses Dynamic Resolution (supported on Windows and consoles) After the Geometry and Depth of Field stages, Unreal runs the Primary Upscale. It then applies effects to the already upscaled content to achieve the best results, such as Motion Blur, Bloom, and the Tone-mapper.
Then the optional Secondary Upscale runs, scaling up once more using a cheaper spatial upscaler to the final output resolution. This is good for very high resolutions, such as 4k, as using the Primary Upscale all the way to 4k can take too much time on the GPU. The cool thing about using the Secondary Upscale is that the UI is rendered after it, so it’s rendered at native resolution. It’s better to give users the option of using the Secondary Upscale instead of changing the desktop resolution. In both cases, a spatial upscale is applied, but Unreal offers higher-quality spatial upscaling, which uses a more powerful chip (the user’s GPU), AND you get a perfectly crisp UI. by default r.Upscale.Quality=3 (5-tap Catmull-Rom bicubic, approximating Lanczos 2) Fun fact: r.Upscale.Quality=0 uses Nearest Neighbor filtering, which can be perfect for pixel-look 2D games. Secondary Upscale is disabled by default on PC(unless DPIScale exceeds 100%). Some mobile/console platforms set it in their device profiles. The DPI scale affecting Secondary Upscale only happens in the Editor when r.Editor.Viewport.HighDPI is disabled (it’s enabled by default).
If we enqueue more Present calls in DirectX than there are back buffers available, the Present() call will block the thread.
if there is any Non-Nanite mesh in the scene need to profile before and after disabling Hardware Occlusion Culling for them via r.AllowOcclusionQueries because there is a round trip to CPU and it stalls the render thread, so that we know if Hardware Occlusion Queries worth turning on or not. If the previous GPU frame hasn’t finished that stage yet, the Render thread blocks until it does which can be profiled in insghts (this is shown as ProcessVisibilityTasks -> WaitForVisibilityTasks on the Render thread, and SyncPoint_Wait - GPUOcclusion on a worker thread).
When a worker thread needs to wait for something, that part of the CPU is basically wasted, as we could’ve been doing some other work on it. So the worker thread can optionally allow “oversubscription” when it’s idling, waiting for something. Oversubscription tells the engine to wake up a temporary “standby” worker thread for that duration. This keeps all the CPU cores (hardware threads) busy, even when a worker thread idles while waiting for something.
The Game thread will only start a new frame when the previous Render thread’s frame has also started. To prevent the Game thread and Render thread from racing ahead together, there’s another fence tying them to the rest of the pipeline; the game thread will always also wait for the N-2 (two frames before) frame’s RHI thread. (this is r.GTSyncType=0, which is the default in Unreal as of 5.8.)
Only sacrifice Hitch Resiliency for input latency when consistently under frame budget and don’t need all that hitch resiliency.