To bridge the popular Steam Lossless Scaling (LS) application—known for its LSFG frame generation and dual-GPU offloading capabilities—into a true OpenXR VR environment, you have to bypass the limitation where standard desktop hooks only capture the flat mirror window.
Achieving a true VR-native equivalent of Lossless Scaling that can leverage a secondary GPU for stereo frame generation requires intercepting the OpenXR swapchain directly.
Phase 1: Architecture of VR-Native Lossless Scaling
Unlike flat screen games where Lossless Scaling grabs a Win32/DXGI window swapchain, OpenXR renders two distinct eye projections submitted directly to a headset runtime. To construct a localized pipeline:
* OpenXR API Layer Hook: You intercept the xrEndFrame function via an OpenXR active layer (similar to how OpenXR Toolkit or PrimaShock operate).
* Asymmetric Dual-GPU Offload:
* GPU 0 (Primary): Renders the base simulation/game at a reduced native resolution (e.g., 70% scale) to maintain a high base framerate (e.g., solid 45 FPS to target a locked 90Hz/120Hz final output).
* GPU 1 (Secondary / iGPU): Pulls the rendered left and right eye textures over the PCIe bus via an asynchronous Vulkan/DX12 P2P memory copy.
* Stereo LSFG (Lossless Scaling Frame Generation): The secondary GPU runs a modified optical flow and machine-learning interpolation pass independently on both eye buffers, synthesizing intermediate frames (45 \text{ FPS} \rightarrow 90 \text{ FPS}) without stealing core frame-time from the primary rendering card.
Phase 2: Building the OpenXR Interception Layer (C++)
To implement a custom layer that acts like Lossless Scaling inside the VR compositor loop, structure your OpenXR runtime hook framework as follows:
1. Initialize the Layer and Intercept xrNegotiateLoaderInterface
Your DLL must expose the standard OpenXR loader negotiation entry point so the runtime routes API calls through your scaling wrapper.
// Basic structure for OpenXR layer negotiation
extern “C” {
XrResult XRAPI_CALL my_xrNegotiateLoaderInterface(
const XrNegotiateLoaderInfo\* loaderInfo,
const char\* layerName,
XrNegotiateLayerRequest\* layerRequest) {
// Setup function pointers for interception
layerRequest->getInstanceProcAddr = my_xrGetInstanceProcAddr;
layerRequest->xrCreateInstance = my_xrCreateInstance;
return XR_SUCCESS;
}
}
2. Hook xrEndFrame for Dual-GPU Processing
Inside xrEndFrame, grab the eye textures before they are submitted to the compositor, push them to your secondary GPU queue, execute the scaling/frame-gen algorithm, and pass the synthesized frames down the chain.
XrResult XRAPI_CALL my_xrEndFrame(
XrSession session,
const XrFrameEndInfo\* frameEndInfo) {
// 1. Inspect layer projections for Left and Right eyes
const XrCompositionLayerProjection\* projectionLayer = nullptr;
for (uint32_t i = 0; i < frameEndInfo->layerCount; ++i) {
if (frameEndInfo->layers\[i\]->type == XR_TYPE_COMPOSITION_LAYER_PROJECTION) {
projectionLayer = reinterpret_cast<const XrCompositionLayerProjection\*>(frameEndInfo->layers\[i\]);
break;
}
}
if (projectionLayer) {
// 2. Offload textures to Secondary GPU (GPU 1) via P2P Vulkan/DX12 queue
// Execute LSFG frame generation algorithm for alternate frame injection
TriggerSecondaryGPUFrameGen(projectionLayer->views\[0\].subImage,
projectionLayer->views\[1\].subImage);
}
// 3. Pass modified frame info to the native runtime (SteamVR / Meta / Virtual Desktop)
return g_next_xrEndFrame(session, frameEndInfo);
}
Phase 3: Practical Workaround (Using the Steam App Today)
If you want to use the current Steam Lossless Scaling application alongside a secondary GPU setup right now without writing a custom OpenXR driver, you must use the Desktop/Windowed Mirror Method:
* Configure Dual-GPU Windows Mapping:
* Set your primary discrete GPU to handle high-performance rendering.
* Bind the Steam Lossless Scaling application explicitly to your secondary GPU or integrated graphics (iGPU) via Windows Graphics Settings.
* Launch the VR Simulator in Windowed Mirror Mode:
* Open your VR title (e.g., Assetto Corsa Competizione or flight sims) with its desktop mirror view active and set to a borderless window matching your target downscaled resolution.
* Hook via Lossless Scaling:
* Target the game’s desktop mirror window inside the Lossless Scaling app.
* Enable LSFG, set your preferred performance scaling algorithm (like LS1 or bilinear upscaling), and trigger the scaling hotkey.
* The Catch: Because this captures the compressed desktop preview window rather than the direct OpenXR compositor stream, head-rotation latency on the generated frames will feel decoupled (resembling asynchronous reprojection on a flat monitor). For true head-locked zero-latency performance, you must rely on native runtime features like Virtual Desktop’s SpaceWarp
or build out the explicit OpenXR P2P layer outlined in Phase 2.