# Unreal Engine 5 shader compilation stutter: find it and fix it

> Why an Unreal Engine 5 game hitches the first time a material or effect appears, and how to fix it: PSO precaching, a bundled PSO cache, finding the materials with the PSO hitch log, and the stutters that look like it (level streaming, garbage collection, asset loads).

Website: https://www.glitchless.gg/unreal-engine-5-shader-compilation-stutter.html · Unreal Engine 5, checked against 5.8

The short freeze the first time a material, mesh or effect appears in an Unreal Engine 5 game is usually a pipeline state object (PSO) being created on the spot. On DirectX 12, Vulkan and Metal, the GPU needs a PSO for each mix of shaders and render state, and when one is missing the frame waits while the driver builds it. It happens once per PSO per install, which is why players see it most in their first hour and after driver updates.

## The short answer

1. **PSOs created at draw time**: keep PSO precaching on, so components compile their PSOs while they load
2. **What precaching misses**: record a bundled PSO cache by playing a Development build with `-logPSO`, and ship it
3. **Effects spawned for the first time**: spawn each first-use effect once behind the loading screen
4. **Hitches walking into a new area**: stream levels in earlier and spread their loading over frames
5. **Hitches every so often that grow over a session**: pool actors and objects instead of spawning and destroying them

## First, confirm it's a PSO hitch

Measure a packaged **Development** or **Test** build, not Play In Editor. In the editor, component PSO precaching is switched off and shaders compile on demand, so its hitches aren't the ones players get. Start the build with `-clearPSODriverCache`, or the driver's own disk cache hides the first-run hitches you're looking for.

`stat unit` shows which thread a long frame waited on. For PSO hitches specifically, turn on the `LogPSOHitching` log: it writes a line for every PSO created at draw time that took longer than 20 ms (`r.PSO.RuntimeCreationHitchThreshold`), with the time, the asset (usually the mesh) and the render pass. To see the material behind a miss, set `r.PSOPrecache.Validation=2`, which logs each "PSO PRECACHING MISS" with its material and vertex factory; `stat PSOPrecache` counts them. Unreal Insights (`-trace=default`) shows the same frames on a timeline.

If the log stays quiet while the game still hitches, it's one of the other stutters further down: level streaming, garbage collection or a blocking asset load.

Command line for a Development build:

```sh
MyGame.exe -clearPSODriverCache -LogCmds="LogPSOHitching Verbose" -trace=default
```

## PSOs created at draw time

**What you see.** A freeze of tens to hundreds of milliseconds the first time something is drawn, logged by the PSO hitch log, and gone the second time.

**Why.** Without a PSO ready, the render thread asks the driver to create one in the middle of the frame. Precaching creates PSOs when a component loads instead, before it's drawn, so the work happens during loading.

Keep PSO precaching on (`r.PSOPrecaching 1`). It's on by default in current UE5 versions on Direct3D 12, Vulkan and Metal; check that nothing in your config turns it off. Components precache their PSOs right after they load, and the engine's global shaders at startup.

Precaching works from what's loaded, so it can't cover materials you set at runtime, dynamic material instances that switch features, or effects spawned later from code. Those need the bundled cache below or a warm-up.

## What precaching misses

**What you see.** Hitches the PSO hitch log still reports with precaching on, often on materials set at runtime and effects spawned later.

**Why.** Precaching only knows about components as they load, and it doesn't cover global graphics PSOs at all. A bundled PSO cache is a recorded list of the PSOs a real playthrough needed, compiled while the game starts or a loading screen is up. Epic suggests a small one even with precaching on.

Turn on the shader pipeline cache and stable shader keys (the ini settings below), package, and play the packaged build with `-logPSO` through as much of the game as you can. It writes `*.rec.upipelinecache` files to `Saved/CollectedPSOs`. Packaging writes the stable shader keys, `*.shk` files, to `Saved/Cooked/<Platform>/<Project>/Metadata/PipelineCaches`.

Combine them into a `.spc` file with the `ShaderPipelineCacheTools` commandlet, put it in `Build/<Platform>/PipelineCaches` in your project, and package again. The project name and shader format in the file name must match exactly (`PCD3D_SM6` for Direct3D 12), or the cache is ignored. Caches are per platform and graphics API; Direct3D 11 isn't supported and doesn't need one. Re-record when materials change a lot.

Settings, then the commandlet:

```ini
; Config/DefaultEngine.ini
[SystemSettings]
r.ShaderPipelineCache.Enabled=1

[DevOptions.Shaders]
NeedsShaderStableKeys=true

; Config/DefaultGame.ini
[/Script/UnrealEd.ProjectPackagingSettings]
bShareMaterialShaderCode=True
bSharedMaterialNativeLibraries=True

; Then, with the recorded and key files copied to C:\PSOCache:
; UnrealEditor-Cmd.exe MyGame -run=ShaderPipelineCacheTools expand C:\PSOCache\*.rec.upipelinecache C:\PSOCache\*.shk C:\PSOCache\MyGame_PCD3D_SM6.spc
```

## Effects spawned for the first time

**What you see.** A hitch the first time a Niagara system, weapon effect or material swap happens mid-game.

**Why.** Effects spawned from code later in the game aren't loaded when precaching runs, and an effect that's never been drawn has no PSOs yet.

Spawn the effects a level will use once while the loading screen covers the view (in front of the camera, so they're drawn), then remove them. Load them with the level, through the Asset Manager or soft references loaded ahead of time, so they're precached with everything else.

## Hitches walking into a new area

**What you see.** A hitch at the same spot each time you cross into a new part of the world, even after the first visit.

**Why.** Adding a streamed level or World Partition cell to the world registers all its components in a short time on the game thread, and blocking on a load stops the frame.

Request streamed levels before the player gets there (a larger loading range or streaming volumes), never block on them (`bShouldBlockOnLoad`, `FlushLevelStreaming`), and spread registration over frames with `s.LevelStreamingComponentsRegistrationGranularity` and the async loading budget (`s.AsyncLoadingTimeLimit`).

## Hitches every so often that grow over a session

**What you see.** Periodic hitches with garbage collection in Unreal Insights, getting longer the longer you play.

**Why.** Garbage collection's cost grows with the number of UObjects. Spawning actors or calling `NewObject` for every shot, spark or damage number adds up.

Pool actors and objects (hide and reuse them) instead of `SpawnActor` and `Destroy` for short-lived things, keep short-lived data in structs rather than UObjects, and run full collections where players won't notice, such as behind a loading screen.

## Check the fix

Run the same route in a packaged Development build before and after, each time with `-clearPSODriverCache`, and compare the worst frames and the number of lines in the PSO hitch log. Without that flag the second run on the same machine is always smoother, because the driver keeps its own cache, and the fix looks better than it is.

## Hitches on your players' machines

Your development PC has compiled most of your PSOs already, so it hides the hitches every new player gets. A Steam Deck, an older GPU or a new driver version shows them again.

[Glitchless](https://www.glitchless.gg/index.md) is an open-source plugin for Unreal Engine 5.7 and 5.8 that records every player's frame times and names each hitch's cause: a PSO created at draw time, garbage collection, a map load, a streamed level or a blocking asset load, split by GPU, version and map. Your coding agent reads the same data and can check its own fix: package a Development build and run it with `-glitchless-measure=10`.

Ask your coding agent:

```
Use Glitchless to find the PSO and traversal hitches my Unreal game's players hit: list them by map, cause and GPU, fix the top one, then measure a packaged Development build with -glitchless-measure=10 to show the difference.
```

## Frequently asked questions

### Why does my Unreal game only stutter the first time?

PSOs are created once and then kept by the driver's own cache, so the second run is smooth on the same machine. Every new player, and every player after a driver update, gets the first run again. That's why it has to be fixed in the game, not on your PC.

### Why does Play In Editor stutter more than the packaged game?

The editor compiles shaders on first use as you play, which a cooked build doesn't need to. Measure a packaged Development build before fixing PSO hitches.

### Does DirectX 11 have this problem?

Much less: on DirectX 11 the driver handles pipeline state itself. DirectX 12, Vulkan and Metal hand that work to the game, which is why PSO caching matters there.

### Is it shader compilation or traversal stutter?

Shader (PSO) hitches happen the first time something is drawn and disappear on the next visit. Traversal hitches happen at the same place in the world every time. The PSO hitch log tells them apart.

## Sources

- [Unreal Engine docs: PSO precaching](https://dev.epicgames.com/documentation/en-us/unreal-engine/pso-precaching-for-unreal-engine)
- [Unreal Engine docs: Optimizing rendering with PSO caches](https://dev.epicgames.com/documentation/en-us/unreal-engine/optimizing-rendering-with-pso-caches-in-unreal-engine)
- [Unreal Engine docs: Manually creating bundled PSO caches](https://dev.epicgames.com/documentation/en-us/unreal-engine/manually-creating-bundled-pso-caches-in-unreal-engine)
- [Unreal Engine docs: Streaming settings](https://dev.epicgames.com/documentation/en-us/unreal-engine/streaming-settings-of-the-unreal-engine-project-settings)
- [Unreal Engine docs: Unreal Insights](https://dev.epicgames.com/documentation/en-us/unreal-engine/unreal-insights-in-unreal-engine)
