An error in the way frames were submitted to XR resulted in intermittent
slowdown that fooled me for a while as being shader compilation stutter,
and had me down the wrong track of investigating that.
However this was actually the root cause. This should bring Baiji up to
performance parity with the current Dolphin release, as near as I can tell.
Resolves Issue #13.
StereoScreen previously submitted and released each Vulkan eye separately
while its image writes could still be in flight. On native Steam Frame
this produced conspicuous doubled/stale-image hiccups and apparent
backward jumps, even though API-side frame identities remained ordered.
A capture covering 16,699 XFB copies found no gameplay source regression,
GPU-submit reversal, XR display-time reversal, or eye-ID mismatch.
SteamVR motion smoothing was already forced off.
For Baiji StereoScreen, defer eye releases until both blits are recorded,
submit their GPU work together, wait once for completion, release both
images under the graphics-queue lock, then publish the quad layers.
Keep this wait even with timeline semaphores enabled: submitted writes
alone were insufficient for correct image delivery on Frame. The wait
trades some CPU/GPU overlap for a completed stereo pair.
Also exclude xrEndFrame from the video blit/completion/release/publish
transaction with a handoff mutex. The previous atomic flag had a
check-to-submit race. Take the handoff lock before publish/queue locks,
and never hold the graphics-queue lock during the completion wait.
PresentBackbuffer already advances frame resources; the stereo submit
must not advance them again.
CPU-side handoff locking alone did not cure the artifact. Headset tests
with the completed-pair handoff in Super Mario Galaxy 2 at 2x/120 Hz
confirmed that remaining misses look like ordinary dropped frames,
rather than doubled images or huge hiccups. The clean Frame SDK build
and launcher CLI smoke check passed. Remove the temporary timing/order
capture code and retain the findings and lock-order guidance in AGENTS.md.
- Make Flat Screen and Stereo Screen checkboxes mutually exclusive in VRPane.
- Fix Vulkan Android stereo-screen submission to end the render pass and
submit the command buffer once before releasing both eye images, instead of
once per eye.
- Add IOpenXRSwapchain::GetEyeSwapchainHandle() and SubmitStereoFlatFrame(),
with per-backend overrides for Vulkan, D3D11, D3D12, and OGL.
- Add OpenXRManager::SubmitStereoQuadFrame() which builds two world-locked
XrCompositionLayerQuad layers with XR_EYE_VISIBILITY_LEFT/RIGHT and publishes
them through the pacing thread (or EndFrame on the legacy path).
- Extend PublishedXRFrame and FrameThreadLoop to submit the stereo quad pair.
- Add UI checkbox for Stereo Screen in VRPane and VRConfigWidget.
The game renders with classic SBS stereoscopy; left/right XFB layers are blit
into the corresponding OpenXR eye swapchains and shown as a 3D floating screen.
- Skip Vulkan surface creation when VR session is active; output goes to
OpenXR swapchains, not a desktop window. This avoids WSI crashes under
compositors such as Gamescope.
- Guard VKGfx backbuffer/present/fullscreen paths against a null swapchain
in headless mode.
- Allow Present() and Presenter::Initialize() to run on any VR-active
backend, not only Android GLES, so the XR frame lifecycle works with
headless Vulkan.
Cleaned up diagnostic init logging added during earlier debugging.
Opening a game, closing it, and opening another crashed or device-lost on
the Vulkan backend with VR enabled. D3D11 and D3D12 both restart cleanly,
so this was Vulkan-specific rather than an OpenXR runtime limitation.
Root cause, proven with VK_EXT_device_fault plus
VK_EXT_device_address_binding_report: the fault is a write-invalid at a
page-aligned address that the binding tracer reports as not owned by the
second game device. The OpenXR compositor still references resources that
lived on the first game VkDevice; destroying that device frees the memory
and the compositor later writes into the freed page. The D3D backends are
immune because their shared compositor resources are COM ref-counted,
while vkDestroyDevice frees unconditionally.
Fix: persist and reuse the VulkanContext (VkInstance and VkDevice) across
VR games, so vkCreateDevice/vkDestroyDevice run once per process and those
pages are never unmapped. Per-game objects are still fully recreated,
including the surface, swapchain, object cache, command buffers, and the
OpenXR session and XrInstance. A device-lost context is never persisted. A
non-VR launch, or a runtime requiring a device extension the persisted
device lacks, discards it and initializes fresh.
Two problems that persisting exposed:
- InitBackendInfo() creates and destroys a temporary VkInstance on every
game boot and then calls UnloadVulkanLibrary(), which nulls every global
Vulkan function pointer. Reuse now re-resolves the instance and device
dispatch for the persisted handles.
- That same UnloadVulkanLibrary() unmaps vulkan-1.dll on runtimes that do
not otherwise pin it, invalidating the persisted handles. This is why
VDXR still failed after SteamVR worked. Added KeepVulkanLibraryLoaded()
so unloading is suppressed while a device is persisted.
Also removes the XR_KHR_vulkan_enable2 path. Creating a second device
against a retained XrInstance crashes inside the runtime in a way SEH
cannot catch. The backend now always uses legacy XR_KHR_vulkan_enable,
where Dolphin owns instance and device creation and the runtime binds at
xrCreateSession.
Supporting changes: ordered OpenXR session teardown (ShutdownSession and
DestroySession), a device-lost guard in CommandBufferManager that stops
emulation instead of resubmitting to a dead device, synchronous release of
Dolphin views and framebuffers before xrDestroySwapchain, and the two GPU
fault diagnostics. Note that vkGetDeviceFaultInfoEXT must be called with
vendorBinarySize zeroed on the second call, or the driver writes the
vendor blob to a null pointer.
Verified on SteamVR/PSVR2, VDXR/Quest 3, and Meta Link over repeated
open/close cycles.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replaces the boolean "Force VBI to 90 Hz" override with an explicit
choice across Off / Auto / 72 / 90 / 120 Hz. Auto samples the headset
refresh rate at OpenXR session startup (via XR_FB_display_refresh_rate)
and rounds to the closest supported value. Migrates the legacy
AutoVBIFromHMD setting on first load. Per-game profile editor and the
HK_VR_TOGGLE_FORCE_VBI hotkey are updated to cycle Off <-> Auto.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Meta's Oculus runtime reports max Vulkan 1.0.0 via XR_KHR_vulkan_enable
v1, which would clamp Dolphin's instance to 1.0 and crash vkCreateDevice
(multiview is a 1.1 feature). Treat 1.0.0 as "unknown" and keep the
negotiated 1.1/1.2 instance.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Yellow squiggly lines begone!
Done automatically on .cpp files through `run-clang-tidy`, with manual corrections to the mistakes.
If an import is directly used, but is technically unnecessary since it's recursively imported by something else, it is *not* removed.
The tool doesn't touch .h files, so I did some of them by hand while fixing errors due to old recursive imports.
Not everything is removed, but the cleanup should be substantial enough.
Because this done on Linux, code that isn't used on it is mostly untouched.
(Hopefully no open PR is depending on these imports...)