An unofficial beta built on
tig3rmast3r's v0.2.1.
Use the NVIDIA backend if you have an NVIDIA card
Recommended: set the backend to NVIDIA in the tray. The shipped default
is still FidelityFX, which works on any card, but almost everything gained in
this release is on the NVIDIA path.
The reason is where the optical flow runs. NVIDIA cards have a dedicated
hardware engine for it - the Optical Flow Accelerator - which is idle while
your game renders, so frame generation costs you almost none of the GPU the
game is using. About 86% of the work goes there. FidelityFX computes the same
flow in ordinary compute shaders, on the very same shader cores the game is
drawing with, so it competes with your frame rate instead of hiding behind it.
That is why the headroom improvement below - needing roughly 48 FPS natively
rather than 57 to hold a steady 90 - is NVIDIA-only. On FidelityFX the work
cannot overlap, so there is nothing to win by moving it.
New in beta6
Direct3D 11 titles keep the pacing thread, and no longer crash. beta5
stopped those crashes by taking the pacing thread away from D3D11 games. That
worked, and it cost them: on Cyberpunk 2077 with the Luke Ross mod it turned a
steady 90 into a laggy 55-60. beta6 fixes the crash properly and gives the
thread back.
The crash was two threads inside the runtime at the same moment. On Direct3D 11
the runtime drives your game's one graphics device, and it does that inside its
own calls - the frame submission on one thread, the swapchain calls on the
other. Each individual driver call was already protected; the sequences were
not, so two of them interleaved and the NVIDIA driver followed a chain that had
moved underneath it.
beta5 read that as out of reach, because an earlier attempt to lock it had
reported no conflict at all. That lock had been placed around the bridge's own
use of the device - but the work that collides is the runtime's, inside the
runtime's own calls, which is why it saw nothing. beta6 puts the lock where the
runtime's work is actually reachable from: around the bridge's entry into each
of those calls. Both threads keep running. They simply can no longer be inside
the runtime together.
The measurements, if you want them. An uncontended pass through the new gate
costs about 250 nanoseconds. Across three sessions it caught the two threads
arriving together 170 times - roughly 1% of entries on both Cyberpunk 2077 and
Assetto Corsa - so the conflict beta5 could not find is real, and is now
excluded. 268 seconds of play produced no crash, against a rate that had
previously been killing a session every 42 seconds. Both titles hold a steady
90 with the pacing thread running.
If you were on beta5 and a D3D11 game felt less smooth than it did on beta4,
this is the build to take.
Only half the generated frames reaching the headset, at high per-eye
resolution on SteamVR. The overlay read 90, every counter the bridge owns
read 90, and the headset showed 45.
The two frames of a pair are not interchangeable. The generated frame is a
computation and the rendered frame is a copy, so the generated one finishes
later, and SteamVR waits for it inside the call that hands it over. That wait
pushes the generated frame late while the rendered one stays on schedule,
until both land in the same refresh and the compositor keeps only one. The
bridge now spaces the pair unevenly to pay for that wait - shorter after the
generated frame, longer before it - and tunes the amount continuously against
what your machine is actually doing, so it costs nothing on hardware and
runtimes that do not need it. On the title this was found in, a steady 45
became a steady 90.
There is a ceiling to this, and it is worth knowing: past roughly 9 megapixels
per eye the generated frame cannot be finished early enough for any spacing to
help. Resolution is the lever there, not settings.
Jitter in heavy scenes after the bridge had been running well. beta4's
pacing could get into a loop where it pulled its own schedule back and then
had to push it forward again, several times a second. It now only corrects a
schedule that is already keeping rate.
For testers
With logging_enabled=1 the rendered frame now carries a green marker
beside the generated frame's purple one. Both flashing means both are reaching
your headset; a steady purple square means only the generated frames are, and
that is worth reporting. Nothing in a log can tell those apart - this is the
only thing that can.
The log also carries a new runtime_entry_section record, one per 64 entries
into the runtime, saying how often the two threads arrived together and how
long either was held up. On a D3D11 title it should show occasional conflicts,
with waits measured in nanoseconds. If it shows none at all across a whole
session, then the lock is not what kept that session alive - which is worth
knowing too.
New in beta4
Jitter and dropped frames in demanding areas, after a minute of playing
well. If you are on beta3, this is worth taking.
The mod paces itself against your headset's refresh, and the moment in each
refresh when it hands its frame over is also the window your runtime measures
the game across. Beta3 stopped that moment drifting to the very end of the
refresh, which was the beta2 problem. It did not stop it drifting to the very
beginning — and there the window closes to nothing, so the runtime is told the
game costs about a millisecond. It then schedules as if the game were idle:
asks for each frame late, assumes it will be ready, and reprojects when it is
not. That reads as jitter and dropped frames while the counter still says 90.
Two things made it hard to spot. It only happens when the mod is pacing
well — the drift needs a long clean run of frames to accumulate, so a game
that hitches never gets there, and the best-behaved sessions are the ones that
slide into it. And it only hurts when the scene is genuinely demanding, so it
can be invisible in one area and obvious in the next.
Measured in The Witcher 3 in Novigrad: the measured window fell from 8.0 ms to
0.06 ms over fifteen seconds, with 97.3% of frames reporting under a
millisecond — while the mod's own view was a flawless 45 frames in, 90 out,
nothing skipped and nothing bunched. The moment is now held in a band, away
from both ends of the refresh.
How to tell it is working: in fpsVR or any overlay, the CPU frame time
should read several milliseconds rather than about one. If you see roughly a
millisecond while the game is visibly working hard, that is the fault, and a
flight log would be very welcome.
New in beta3
- Generated frames now actually reach your headset. The mod hands the
headset two frames per game frame, and they have to land one refresh apart.
Its internal schedule for that could only ever slide later — every hiccup
nudged it back and nothing brought it forward — until it was handing each
frame over a fraction of a millisecond before the display needed it. At that
point the headset has already committed to the refresh, so the generated
frame waits for the next one and collides with the real frame. You see one of
the two. The result was a game that reported the full rate, showed half of
it, and could be slower than running without the mod at all. - Games that got nothing from the mod, or got less than nothing. A related
case with a different cause: on some titles the mod handed both frames over a
few milliseconds apart from the very start, with the same outcome. It now
measures how far apart the two actually arrive and switches delivery methods
when they are too close, which takes about a quarter of a second at the start
of a session. Confirmed on Atomic Heart, which was affected on every runtime
tested. - Steadier frame times, and no more regular spikes. The same schedule could
also drift slowly out of step with the display and then snap back by a whole
frame, losing a slot each time it did. On a frame-time graph that showed up
as a repeating sawtooth or a regular spike.
Measured on a 45-second session after these changes: 45 real frames in, 90
delivered out, 99.7% of them landing on the display's own cadence, 0.2%
arriving a whole refresh late, and none of them bunched together.
Who beta3 and beta4 affect most: anyone on a runtime other than SteamVR —
Meta and Virtual Desktop included — anyone whose game drives VR from a single
thread, and anyone who saw the frame counter say 90 while the headset plainly
showed less. If beta2 seemed to do nothing for you, or made things worse, try
again.
Also new since the first beta
- It recovers. The most common complaint about beta1 was that a demanding
scene would knock it down and it would stay down — back in an easy scene, or
even back at the menu, it would sit at a lower frame rate until you restarted
the game. The layer paced its own frames against an internal schedule that
could drift out of step with the headset's refresh and had no way to drift
back. It now corrects itself against the timing the runtime reports, every
frame, so a bad patch lasts seconds rather than the rest of the session. - More headroom before it gives up. The frame generation work used to be
queued behind the game's own rendering, so the game had to reach roughly
57 FPS on its own before the layer could hold 90. On NVIDIA it now runs
alongside the game's rendering instead, which brings that down to roughly
48 FPS. If you were just short of a steady 90, this is the change most
likely to get you there. - Fewer frames thrown away. When a frame ran long the layer would often
skip the next display slot entirely rather than fill it, which reads as a
hitch. It now only skips when a submission would genuinely land on top of
the previous one. - Synthetic frames placed where they are actually shown. The generated
frame was always the exact midpoint between two rendered frames, which is
only correct when the game runs at exactly half the refresh rate. Away from
that it was shown slightly early or late, and — because the error follows
your frame time — alternately early and late as frame times swing. A
constant offset would be invisible; an alternating one reads as judder.
It is now placed to match when it is actually displayed. - Stability fixes in the new NVIDIA path: GPU work is properly drained when
a swapchain or session goes away, and the generated frame is prepared in the
right resource state rather than relying on the driver to tolerate it.
Still in from beta1
- Steady 90 FPS at high headset resolution. At 27 Mpx per eye the layer
previously produced fewer frames per second than running without it. The
runtime was being told the layer's frame cost about a millisecond when the
real work was around 15 ms, so it scheduled against numbers that described an
empty loop. It now measures the real work, which is also what makes fpsVR's
GPU and CPU frame times meaningful again. - Frame generation engages immediately. It could previously sit idle for
minutes on a demanding scene — generating frames the whole time, at full GPU
cost, with none of them being displayed — and then suddenly start working when
you looked at something simple. The layer now decides it needs to pace itself
by measuring the runtime rather than by guessing from the game's frame loop. - No more one-second dropouts. Changing settings, loading a world, or the
game recreating its swapchains each cost a full second of generation.
Transitions are now seamless. - Generation no longer stops permanently after a menu or loading screen.
A clock inside the layer could drift a second per second until the game was
predicting poses ten minutes into the future; once there, it never recovered
for the rest of the session. - Games that would not start in VR now start. Titles that create many
swapchains — The Callisto Protocol under UEVR, for example — exhausted the
runtime's swapchain budget and failed at session creation. The layer now only
takes resources for swapchains that actually carry the image you see. - Luke Ross mods: black flashes and double vision fixed. Cyberpunk 2077 was
tearing down and restarting the layer's internals about three times a second,
and separately publishing the previous pair's frame as the current one. - Higher resolution ceiling. Roughly 340 MB of per-frame copying moved off
the critical path, taking the workable ceiling from about 15 to about
24.5 Mpx.
Changed default
Unchanged from beta1, repeated here because it still catches people out:
FidelityFX honours the Optical flow resolution tray setting, which it
previously ignored and ran at full resolution. With the shipped default of 50%
this makes FidelityFX noticeably cheaper, at some cost in motion-vector detail.
Set nvidia_input_scale=100 in ofxr_bridge.ini to restore the old behaviour.
The tray menu entry is called Optical flow resolution rather than
"NVIDIA OFA resolution", because it applies to both backends. The ini key name
is unchanged.
A note on frame times in fpsVR
The mod deliberately holds your game to half the refresh rate and fills the
gaps. That hold happens inside a call fpsVR counts as the game's own CPU time,
so the CPU frame time it reports goes up when OFXR is on, and moves around,
without your game having got any slower. Whatever your game does not spend
rendering, the hold absorbs. What matters is that the game's own frame rate
sits at half your refresh and stays there.
The GPU frame time reads low for a different reason, and is also expected:
on the NVIDIA backend the frame generation work runs on its own queue and on
the optical flow engine, neither of which your runtime measures. That is the
same separation that buys the headroom improvement above.
Installing
Unzip anywhere and run OFXRBridgeTray.exe, then Arm bridge. Keep
OFXRBridgeTray.exe and the ofxr folder together. If you have an earlier
version armed, disarm it first.
Known issues
- The frame generation work sharing the GPU with your game is new. If a game
that was steady on beta1 is worse here, please say so and include a flight
log — that is the case most worth hearing about. - Very high per-eye resolutions remain the limit, and past a point nothing
in the settings helps. Above roughly 9 megapixels per eye the generated
frame cannot be produced early enough to be shown, and the symptom is a hard
one to read: the bridge reports full rate while the headset shows half, or
the game drops to a couple of frames a second and stays there. Drop the
resolution first before changing anything else; the layer's cost scales with
it. - Direct3D 11 titles run two threads inside the runtime again, with a new
lock keeping them out of each other's way. That lock is the newest thing in
this build. If a D3D11 game crashes within a minute or two of the bridge
engaging, that is the case most worth reporting, with a flight log - the
runtime_entry_sectionrecords in it say how hard the lock was working when
it happened. - Selecting the NVIDIA backend without an NVIDIA card silently does
nothing. The NVIDIA option drives a hardware engine that only exists on
NVIDIA GPUs; on any other card the layer cannot start and passes every frame
through untouched, with no warning. If you are on AMD or Intel, leave the
backend on FidelityFX, which is the shipped default.
Tested
Microsoft Flight Simulator 2024, The Witcher 3 (tig3rmast3r's VR mod),
Assetto Corsa, Cyberpunk 2077 (Luke Ross mod), and UEVR titles including The
Callisto Protocol, on SteamVR with a Pimax Crystal Super at up to 4172×3268
per eye, on both the NVIDIA OFA and FidelityFX backends. VDXR is covered by the earlier
work this builds on; PimaxXR is not tested.
The Direct3D 11 change specifically: Cyberpunk 2077 under the Luke Ross mod and
Assetto Corsa, three sessions of 39, 85 and 144 seconds with the pacing thread
running, no crash, and a steady 90 in the headset throughout.
This is an experimental beta and compatibility is not universal. Please report
results — working and not — on this fork's
GitHub Issues, with an OFXR flight log where you can. Enable one
by setting logging_enabled=1 under [diagnostics] in ofxr_bridge.ini; logs
are written next to the armed layer under
%LOCALAPPDATA%\OFXR Bridge\RuntimeLayer\v183\.
Upstream results are collected in the
OFXR Bridge Compatibility Chart.