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.
The previous fix to fit them in their dialog box often unseated
the arrow from the widget it is trying to point at. With the user
so actively interacting with the widgets to get the help,
including having to hold the pointer steady to keep it where they
can read it (arguably a problem of its own), I don't think we need
to quite that aggressively point at the target widget.
Steam OS intepreted that as a resize of the window and recentered it.
This caused a lot of displeasing UI churn.
This still leaves the little chevrons pointed at the wrong places
sometimes but it is still a much better user experience.
In flat Vulkan mode, Steam Frame controller buttons can appear not to
reach the game while the system's laser-pointer mode still owns focus.
This is an easy mistake to make when starting a game from the headset.
After a successful boot, show an informational dialog for explicit Flat
Display (Vulkan) mode and for legacy configurations with OpenXR disabled.
The check happens after boot metadata and per-game VR settings are loaded,
so immersive VR and Stereo 3D Screen launches are not interrupted.
Add the requested reminder text and a Never See This Dialog Again
checkbox. Persist the opt-out in Main.Interface, and leave it unchecked by
default so the reminder appears for new users.
Immersive VR previously used the full runtime-reported left/right view
positions, with Units per Meter scaling both world geometry and the
per-eye offsets. That left no direct way to soften strong near-field
pop-out without also changing the apparent world scale.
Add Graphics.VR.StereoSeparation as a 0-200 percent setting, defaulting
to 100 percent. Scale each eye's positional offset around the average
eye center independently of shared head translation. At 100 percent the
projection is unchanged, 0 percent brings the virtual view positions
together toward mono, and values above 100 percent amplify stereo depth.
Units per Meter continues to set world scale and head-motion mapping.
Expose the setting in the global VR pane and per-game VR configuration,
include it in game VR override application/reset, and refresh cached
projection rows when it changes. Help text explains the scale/depth
tradeoff, possible near-field comfort benefit, and that Stereo 3D Screen
continues to use the existing Stereoscopic Depth control.
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.
Diagnosis confirmed: the runtime rebases the reference space on system
recenter (logged head_yaw matched the user's per-run offset exactly), and
controllers share the HMD's tracking frame. Keep only the low-volume
reference-space-change and recenter log lines.
The runtime rebases the reference space on system recenter, universe
switches, and relocalization, and notifies apps via
XrEventDataReferenceSpaceChangePending. Dolphin only logged the event, so
the world-locked flat-screen quad kept its stale coordinates and appeared
to jump by the rebase yaw every time the user used the system recenter
(the HMD's yaw in the runtime's arbitrary local frame — random per run).
On the event, invalidate the cached flat-screen pose so the panel is
re-placed in front of the user's current gaze, which is also the expected
behavior after a system recenter. Log the event's poseInPreviousSpace yaw
delta, and add a throttled view-yaw vs aim-yaw diagnostic to check whether
the runtime reports controllers in a different tracking frame.
Remove the reference-space recreation recenter, ScreenYawOffset config, and
debug logging. Restore the original home-position recenter and world-locked
flat screen placement so we can diagnose the underlying issue from a clean
baseline.
Log head yaw, reference-space offset yaw, and post-recenter screen pose for
5 frames after each recenter. This is diagnostic to find why recenter
produces a fixed per-run error.
The previous recenter code computed the head pose in the current (already
recentered) reference space and passed it directly to xrCreateReferenceSpace,
which interprets the pose relative to the runtime's natural LOCAL frame. This
made repeated recenters compound incorrectly and produced a random-looking
offset per run.
Accumulate the head-in-current-space offset onto a persistent
m_recenter_offset pose relative to the natural LOCAL frame, and create each new
reference space from that accumulated offset. Also add diagnostic logging for
recenter.
Adds a VR/ScreenYawOffset setting (degrees, default 0). Positive values
rotate the virtual screen counter-clockwise from above (to the left);
negative values rotate clockwise (to the right). This is intended as a
diagnostic/tuning knob for the stereoscopic virtual screen placement.
The recenter offset was computing the inverse of the desired transform.
The new reference space should be placed at the current head pose (yaw-only)
so that the current gaze becomes forward, not rotated the opposite way.
Recenter now recreates the OpenXR reference space with a yaw-only offset
that maps the user's current head pose to the origin. This makes the flat
virtual screen appear directly in front of the user after recentering, with
upright orientation, instead of being left at an arbitrary session-start
forward direction.
Reverts the temporary head-locked stereo-screen behavior; the screen is
world-locked again, so the Wiimote pointer orientation stays consistent.
In stereo-screen mode, recompute the virtual screen pose every frame from the
user's current head yaw so the screen always stays in front of their gaze.
This avoids the confusing behavior where the initial world-locked placement
captured a random head pose during startup and recenter could leave the screen
off to one side.
Flat mono panel mode keeps the existing world-locked behavior.
SteamVR on the Steam Frame returns XR_ERROR_FEATURE_UNSUPPORTED for both
60 Hz and 120 Hz requests, so the request function works but the runtime
has no such modes. Remove the temporary diagnostic log and keep the
feature available for runtimes that do support app-controlled refresh rates.
Add GFX_VR_REQUESTED_REFRESH_RATE setting (Auto/60/72/90/120 Hz). When a
non-zero rate is configured, call xrRequestDisplayRefreshRateFB after the
session begins so the runtime can switch the headset panel to a rate that
matches the game's frame rate (e.g. 60 Hz or 120 Hz for 60 fps Wii games,
instead of the default 72 Hz).
This should reduce judder caused by the compositor showing 60 fps content on
a 72 Hz display. The runtime may clamp the request to the nearest supported
rate; the result is logged.
UI exposed in VRPane and VRConfigWidget.
- 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.
Add IsOpenXRStereoScreen() and route classic SBS frames through a new
SubmitStereoFlatFrame() path. The left XFB layer is blit into the left
OpenXR eye swapchain and the right layer into the right eye swapchain,
then the backend submits two quad layers at the virtual-screen pose.
Stereo-screen mode is treated like flat mode for direct-to-HMD and
pacing-thread deferral: the eye blits happen at submit time, not during
the desktop mirror path.
Add GFX_VR_STEREO_SCREEN setting. When OpenXR is enabled and this mode is
active, force stereo_mode to SBS so the game renders with Dolphin's classic
side-by-side stereoscopy. Extend VRSessionActive() to keep the OpenXR session
alive for this path. This is the first step toward submitting a stereoscopic
virtual screen via OpenXR quad layers.
- 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.