Blog

We moved the encoder into the display driver. Windows streams now have half the lag.

punktfunk's Windows host used to copy every frame out of our display driver, convert it, and encode it in a second process. Since 0.35 the driver encodes each frame the moment Windows finishes drawing it. On our RTX 4090 test box, 4K120 HDR latency dropped from 9–17 ms to under 5.

Every millisecond in a game stream has an owner. The game, the encoder, the network, the decoder, the screen. For a long time a surprising chunk of ours sat somewhere you'd never think to look: a handoff between two programs on the same PC.

Where we started

A while back we explained IDD-push. punktfunk streams onto its own virtual display, and because we own the driver, it pushes each finished frame to the host. No Desktop Duplication, no screen capture API.

That was a big step. But the frame still took the scenic route:

  • the driver copied it into a shared ring of GPU textures,
  • the host process picked it up and converted it into the format the encoder wants,
  • then the host encoded it.

Two full-frame passes on the GPU, one hop between processes, and each step waited for the one before to finish. At 4K and 120 fps, the host's slice alone cost 6 to 9 ms.

Why the copy existed at all

It wasn't an accident. Windows runs display drivers like ours inside a system process. If that process stalls, Windows kills it within about two seconds, and your display goes with it. Keeping the encoder in a separate process meant a stuck encoder couldn't take the display down.

So you get to pick. One process gives you speed. Two processes give you a safety net. We had picked the net.

Then we took a second look at how Windows' virtual display model is meant to be used. It expects the driver to hand frames off right where they're drawn. Shipping raw frames across a process boundary was the unusual part, and we were the ones doing it.

What changed in 0.35

The encoder now lives inside the driver. When Windows finishes a frame, the driver does one pass straight into a small pool of three frames, and an encode thread takes it from there. NVIDIA's NVENC, AMD's AMF, Intel's Quick Sync and our own PyroWave all run in there.

The host doesn't touch pixels anymore. It reads finished, compressed frames out of shared memory and sends them over the network.

And the safety net? The pool is the new one. If the encoder falls behind, the pool throws away the oldest frame and keeps the newest. Windows never waits on us, so a slow encoder can't freeze your display.

The part we got wrong first

Our first version of the new loop checked on the encoder on a timer: done yet? How about now? On average that was fine, a few milliseconds. But in one test window a frame sat inside the driver for 48 ms before it left. At 120 fps that's almost six frames of lag, out of nowhere.

The fix was to stop asking and start listening. Encoders can raise a signal when a frame is finished, so the loop now sleeps until that signal fires. Worst case dropped to 6.2 ms, and the average to 0.7–3.1 ms.

There was a second timer trap hiding in there. Windows' default timer only ticks every 15.6 ms, which quietly capped mouse-cursor updates at about 64 per second. A high-resolution timer fixed that too.

The numbers

Measured on one RTX 4090 box with the client running on the same machine, so the network doesn't blur the picture:

  • 4K, 120 fps, HDR: 9–17 ms before, 4.7 ms median after (5.7 ms at the 95th percentile).
  • 1080p, 60 fps: 12–17 ms before, 4.1–5.8 ms after.
  • The host's own slice: 6–9 ms before, effectively zero after.

One honest note: frame rate didn't change. The old path already delivered every frame at 4K120. This is about how old each frame is when it reaches you, and that's the part you feel on a mouse flick.

A tangent: RivaTuner in session zero

Our driver runs in a Windows session with no desktop and no user. Moving PyroWave in there meant starting Vulkan in that session, and on one of our test boxes it simply hung.

The culprit was RivaTuner. Its Vulkan overlay layer loads into every Vulkan program on the machine, including ours, and it didn't cope with a session that has no desktop. The standard "turn off all implicit layers" switch wasn't enough with six layers installed, so the driver now also flips each layer's own off switch. If you run an overlay tool on your streaming PC, you're covered.

What you need to do

Update to 0.35. The installer updates the host and the driver together, with a short black flicker while the driver reloads. You need Windows 11 22H2 or newer. There's no setting to turn on.

If you stream from Windows, update. And if you measured your latency before, measure it again.

For the rest of the latency story, see how we keep the encoder fast when a game maxes out the GPU.