diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..17aa5f2a
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Shell scripts must stay LF even on Windows checkouts (they run on Linux).
+*.sh text eol=lf
diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml
index bc035af8..b6a09a34 100644
--- a/.github/workflows/Build.yml
+++ b/.github/workflows/Build.yml
@@ -14,8 +14,14 @@ env:
STEAM_PATH: temp
jobs:
+ Linux64:
+ uses: ./.github/workflows/BuildLinux.yml
+ secrets: inherit
+
Win64:
runs-on: windows-2022
+ # The release step below attaches the Linux tarball, so wait for it.
+ needs: Linux64
steps:
- name: Checkout repo and submodules
@@ -23,6 +29,7 @@ jobs:
with:
submodules: recursive
fetch-depth: 0
+ token: ${{ secrets.SHARED_PAT }}
- name: Get current date, commit hash and count
run: |
@@ -86,12 +93,31 @@ jobs:
- name: Zip vrto3d_profiles folder
run: Compress-Archive -Path vrto3d_profiles/* -DestinationPath vrto3d_profiles.zip
+ - name: Install Inno Setup
+ run: choco install innosetup --yes --no-progress
+ shell: pwsh
+
+ - name: Compile installer
+ run: iscc installer\VRto3D-Installer.iss
+ shell: pwsh
+
- name: Upload zip artifact
uses: actions/upload-artifact@v4
with:
name: "VRto3D-${{env.CommitHashShort}}"
path: output/${{github.event.repository.name}}.zip
+ - name: Upload installer artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: "VRto3D-Installer-${{env.CommitHashShort}}"
+ path: installer/Output/VRto3D-Installer.exe
+
+ - name: Download Linux tarball
+ uses: actions/download-artifact@v4
+ with:
+ name: vrto3d-linux64
+
- name: GitHub pre-release
uses: "marvinpinto/action-automatic-releases@latest"
with:
@@ -101,7 +127,9 @@ jobs:
title: "[${{env.CurrentDate}}] ${{github.event.repository.name}} r${{env.CommitCount}}@${{env.CommitHashShort}}"
files: |
output/${{github.event.repository.name}}.zip
+ vrto3d-linux64.tar.gz
vrto3d_profiles.zip
+ installer/Output/VRto3D-Installer.exe
README_combined.jpg
changelog_combined.jpg
\ No newline at end of file
diff --git a/.github/workflows/BuildLinux.yml b/.github/workflows/BuildLinux.yml
new file mode 100644
index 00000000..b1612879
--- /dev/null
+++ b/.github/workflows/BuildLinux.yml
@@ -0,0 +1,53 @@
+name: Build Linux
+
+on:
+ push:
+ branches:
+ - linux_port
+ pull_request:
+ branches:
+ - main
+ workflow_dispatch:
+ # Called by Build.yml on main so the release ships the Linux tarball.
+ workflow_call:
+
+jobs:
+ Linux64:
+ # Oldest supported LTS: binaries link the runner's glibc, so building on
+ # 22.04 (glibc 2.35) keeps the tarball running on older distros and SteamOS.
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Checkout repo and submodules
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ fetch-depth: 0
+ token: ${{ secrets.SHARED_PAT }}
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y ninja-build libvulkan-dev libx11-dev \
+ libxrandr-dev libwayland-dev wayland-protocols libxkbcommon-dev \
+ libdrm-dev
+
+ - name: Configure
+ run: cmake -S vrto3d -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo
+
+ - name: Build
+ run: cmake --build build
+
+ - name: Package driver + installer
+ run: |
+ mkdir -p package/vrto3d-linux64
+ cp -a build/output/drivers package/vrto3d-linux64/
+ cp installer/install.sh package/vrto3d-linux64/
+ cp LICENSE README.md package/vrto3d-linux64/
+ chmod +x package/vrto3d-linux64/install.sh
+ tar czf vrto3d-linux64.tar.gz -C package vrto3d-linux64
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: vrto3d-linux64
+ path: vrto3d-linux64.tar.gz
diff --git a/.gitignore b/.gitignore
index c572fa6e..c7597eed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,6 +32,7 @@ bld/
[Ll]og/
[Ll]ogs/
output
+vrto3d/build
# Visual Studio 2015/2017 cache/options directory
.vs/
@@ -240,6 +241,7 @@ ClientBin/
*.pfx
*.publishsettings
orleans.codegen.cs
+.claude
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
diff --git a/.gitmodules b/.gitmodules
index 970039ee..26ff1b33 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -10,3 +10,20 @@
[submodule "external/VRto3DLib"]
path = external/VRto3DLib
url = https://github.com/oneup03/VRto3DLib.git
+[submodule "external/nvapi"]
+ path = external/nvapi
+ url = https://github.com/NVIDIA/nvapi.git
+[submodule "external/imgui"]
+ path = external/imgui
+ url = https://github.com/ocornut/imgui.git
+ branch = master
+[submodule "external/adl_sdk"]
+ path = external/adl_sdk
+ url = https://github.com/GPUOpen-LibrariesAndSDKs/display-library
+[submodule "external/SR-lib"]
+ path = external/SR-lib
+ url = https://github.com/bo3b/SR-lib.git
+[submodule "external/NV3D-Lib"]
+ path = external/NV3D-Lib
+ url = https://github.com/oneup03/NV3D-Lib.git
+ branch = nv3d-glass
diff --git a/.vscode/build-driver.ps1 b/.vscode/build-driver.ps1
index 5dfe93de..91f4cfff 100644
--- a/.vscode/build-driver.ps1
+++ b/.vscode/build-driver.ps1
@@ -14,7 +14,13 @@ $ErrorActionPreference = 'Stop'
$repoRoot = Split-Path -Parent $PSScriptRoot
Set-Location $repoRoot
-$steamPath = if ($env:STEAM_PATH) { $env:STEAM_PATH } else { 'C:\Program Files (x86)\Steam' }
+$steamPath = if ($env:STEAM_PATH) {
+ $env:STEAM_PATH
+} else {
+ # Fall back to the registry (Steam isn't always in Program Files).
+ $reg = (Get-ItemProperty 'HKCU:\Software\Valve\Steam' -ErrorAction SilentlyContinue).SteamPath
+ if ($reg) { $reg -replace '/', '\' } else { 'C:\Program Files (x86)\Steam' }
+}
$msbuildCmd = Get-Command msbuild -ErrorAction SilentlyContinue | Select-Object -First 1
$msbuildExe = if ($msbuildCmd) { $msbuildCmd.Source } else { $null }
@@ -38,12 +44,14 @@ if ($LASTEXITCODE -ne 0) {
}
if ($Target -in @('Build', 'Rebuild')) {
- cmd /c "xcopy \"vrto3d\\vrto3d\" \"output\\drivers\\vrto3d\" /S /Y /I"
+ # Call xcopy directly (no cmd /c) — nested cmd quoting mangles paths
+ # ("Invalid path"), and trailing backslashes must be avoided.
+ & xcopy 'vrto3d\vrto3d' 'output\drivers\vrto3d' /S /Y /I
if ($LASTEXITCODE -gt 1) {
exit $LASTEXITCODE
}
- cmd /c "xcopy \"output\\drivers\\\" \"$steamPath\\steamapps\\common\\SteamVR\\drivers\\\" /S /Y /I"
+ & xcopy 'output\drivers' (Join-Path $steamPath 'steamapps\common\SteamVR\drivers') /S /Y /I
if ($LASTEXITCODE -gt 1) {
exit $LASTEXITCODE
}
diff --git a/README.md b/README.md
index 9747a29c..d58da98a 100644
--- a/README.md
+++ b/README.md
@@ -1,334 +1,452 @@
-# VRto3D
-
-- OpenVR Driver that can render in SbS or TaB 3D with other formats converted to through ReShade
-- Compatible games play great with a XInput controller. No motion controls required!
-- VRto3D itself does not "fix" games for 3D, but it allows you to run VR modded (fixed) games on a 3D Display
-- Supports User Profiles for individual games
-- Provides HMD Pitch and Yaw emulation for games that require it
-- Currently targeting OpenVR 2.5.1.
-- Windows-only solution currently, but there are other solutions on Linux like Monado XR.
-
-
-## Compatible 3D Displays
-- 3D TVs & Projectors - work great, use [Base Installation](#base-installation) in SbS/TaB mode or potentially [Frame Packing](#framepacking-hdmi-3d-only-if-you-need-this-output-format) instructions
-- Passive/Interlaced 3D displays - work great, use [Interlaced](#interlaced-checkerboard-and-anaglyph-installation-only-if-you-need-this-output-format) instructions
-- AR Glasses (Rokid, Xreal, Viture, RayNeo) - work great, use [AR Glasses](#ar-glasses-only-if-you-need-this-output-format) instructions. If you don't have a USBC port with DP-Alt mode on your PC, they require a compatible adapter - choose one with SBS and audio support. A USBC extension is also recommended. VertoXR can be used for 6DoF or 3DoF Head Tracking
-- Lume Pad - works great, use [Base Installation](#base-installation) instructions, requires Sunshine/Gamestream + Moonlight
-- SR Displays (Acer Spatial Labs / Asus Spatial Vision / Samsung Odyssey 3D) - work great, use [SR Displays](#sr-simulated-reality-displays-only-if-you-need-this-output-format) instructions. SR-OpenTrack-Bridge can be used for 6DoF or 3DoF Head Tracking
-- 3D Vision/Frame Sequential - use [WibbleWobbleVR](https://oneup03.github.io/VRto3D/wiki/WibbleWobbleVR3.0) instead
-- Virtual Desktop with a VR headset - works with [additional setup](https://oneup03.github.io/VRto3D/wiki/VirtualDesktop)
-
-
-## Compatible VR Games & Mods
-Checkout the [Compatibility List](https://oneup03.github.io/VRto3D/wiki/Compatibility-List) to see if a game has been tested
-
-
-## Hotkeys
-- Adjust Depth (Separation) with `Ctrl + F3` and `Ctrl + F4`
- - Synchronize the convergence setting by also holding `Shift` - this often has issues in VR mods
-- Adjust Convergence with `Ctrl + F5` and `Ctrl + F6` - this often has issues in VR mods
-- Save all current settings (ones indicated with a `"+"` under [Configuration](#configuration)) as a profile for the currently running game with `Ctrl + F7` A beep will indicate success
-- Reload the profile settings (ones with a `"+"`) from the current game's `game.exe_config.json` with `Ctrl + F10` A beep will indicate success
-- Reload the profile settings (ones with a `"+"`) from `default_config.json` with `Ctrl + Shift + F10` A beep will indicate success
-- Toggle locking the SteamVR Headset Window to the foreground and focusing the game window with `Ctrl + F8`
-- Adjust HMD position and yaw origin with `Ctrl + Home/End` for Y, `Ctrl + Delete/PageDown` for X, `Ctrl + Insert/PageUp` for Yaw, and `Ctrl + Shift + PageUp/PageDown` for Height
- - This is useful if you want to align the HMD to a lighthouse tracked position
-- Check the [Controls](#controls) section and the Configuration table below to setup HMD camera controls for VR games (check the compatibility list to see if they are needed)
-- Check the [User Presets](#user-presets) section for instructions on setting up your own Depth/Separation and Convergence presets and also reference the Configuration table below
-- When Pitch/Yaw emulation is enabled (and use_track_filter is disabled), you can adjust the ctrl_sensitivity with `Ctrl -` and `Ctrl +` and the pitch_radius with `Ctrl [` and `Ctrl ]`
-- When `use_track_filter` is `true`:
- - `Ctrl -` / `Ctrl +` and `Ctrl [` / `Ctrl ]` adjust track filter rotation/translation sensitivity, and also hold `Shift` to adjust track filter rotation/translation deadzones
- - `Ctrl + ;` / `Ctrl + '` adjust track filter zoom smoothing, and also hold `Shift` to adjust track filter max zoom range
-- Save `hmd_height, hmd_x, hmd_y, hmd_yaw`, and all 6 Track Filter parameters using `Ctrl + F9`
-- Attempt to take a SbS Screenshot with `Ctrl + F12` (doesn't always work)
-
-
-## Configuration
-
-- VRto3D has to be installed and SteamVR launched once for this config file to show up
-- Modify the `Steam\config\vrto3d\default_config.json` for your setup
-- Some changes made to this configuration require a restart of SteamVR to take effect
-- Fields with a `"+"` next to them will be saved to a game's profile when you press `Ctrl + F7` and can be reloaded from either the game's profile using `Ctrl + F10` or the `default_config.json` using `Ctrl + Shift + F10`
-- Reference Virtual-Key Code strings for user hotkeys
-- Reference [Profile Creation Steps](#profile-creation-steps) for creating a game-specific profile
-
-| Field Name | Type | Description | Default Value |
-|---------------------|---------|---------------------------------------------------------------------------------------------|----------------|
-| `display_index` | `int` | 3D display selection by display order (`0` = auto primary, `1` = first display, `2` = second, etc.) | `0` |
-| `multi_display` | `bool` | Span 3D window across two displays, starting from display_index and moving to the right | `false` |
-| `render_width` | `int` | The width to render per eye | `1920` |
-| `render_height` | `int` | The height to render per eye | `1080` |
-| `hmd_height` + | `float` | The height/Z position origin of the simulated HMD | `1.0` |
-| `hmd_x` | `float` | The X position origin of the simulated HMD | `0.0` |
-| `hmd_y` | `float` | The y position origin of the simulated HMD | `0.0` |
-| `hmd_yaw` | `float` | The yaw attitude of the simulated HMD | `0.0` |
-| `aspect_ratio` | `float` | The aspect ratio used to calculate vertical FoV | `1.77778` |
-| `fov` + | `float` | The horizontal field of view (FoV) for the VR rendering | `90.0` |
-| `depth` + | `float` | The max separation. Overrides VR's IPD field | `0.1` |
-| `convergence` + | `float` | Where the left and right images converge. Adjusts frustum | `1.0` |
-| `async_enable` + | `bool` | Whether or not to use Asynchronous Reprojection. May improve or worsen smoothness | `false` |
-| `disable_hotkeys` | `bool` | Disable Depth & Convergence adjustment hotkeys to avoid conflict with other 3D mods | `false` |
-| `tab_enable` | `bool` | Enable or disable top-and-bottom (TaB/OU) 3D output (Side by Side is default) | `false` |
-| `framepack_offset` | `int` | Pixel gap between left and right views in TaB mode. Use for framepacking/HDMI 3D | `0` |
-| `reverse_enable` | `bool` | Enable or disable reversed 3D output | `false` |
-| `vd_fsbs_hack` | `bool` | Enable or disable half height Full-SbS for Virtual Desktop | `false` |
-| `dash_enable` | `bool` | Enable or disable SteamVR Dashboard and Home | `false` |
-| `auto_focus` | `bool` | Enable or disable automatic focusing/bringing VRto3D to foreground | `true` |
-| `display_latency` | `float` | The display latency in seconds | `0.011` |
-| `display_frequency` | `float` | The display refresh rate per-eye, in Hz | `60.0` |
-| `pitch_enable` + | `bool` | Enables or disables Controller right stick y-axis mapped to HMD Pitch | `false` |
-| `yaw_enable` + | `bool` | Enables or disables Controller right stick x-axis mapped to HMD Yaw | `false` |
-| `use_open_track` | `bool` | Enables or disables OpenTrack 6DoF HMD Control | `false` |
-| `open_track_port` | `int` | UDP Port for OpenTrack | `4242` |
-| `use_track_filter` | `bool` | Enables or disables Accela-Hamilton style pose filtering for tracking rotation and position | `false` |
-| `trk_flt_rot_sens` | `float` | Rotation smoothing threshold for track filter (lower = more smoothing) | `0.5` |
-| `trk_flt_pos_sens` | `float` | Position smoothing threshold for track filter (lower = more smoothing) | `0.25` |
-| `trk_flt_rot_dz` | `float` | Rotation deadzone used by track filter | `0.03` |
-| `trk_flt_pos_dz` | `float` | Position deadzone used by track filter | `0.02` |
-| `trk_flt_zoom_smooth`| `float` | Additional rotation smoothing when moving toward the display | `0.0` |
-| `trk_flt_max_zoom` | `float` | Max Z distance used for scaling zoom smoothing | `10.0` |
-| `launch_script` | `string`| Command executed once when VRto3D driver activates (`"start vertoxr://steamvr"`) | `""` |
-| `pose_reset_key` + | `string`| The Virtual-Key Code to reset the HMD position and orientation | `"VK_NUMPAD7"` |
-| `ctrl_toggle_key` + | `string`| The Virtual-Key Code to toggle Pitch and Yaw emulation on/off when they are enabled | `"XINPUT_GAMEPAD_RIGHT_THUMB"` |
-| `ctrl_toggle_type` +| `string`| The ctrl_toggle_key's behavior ("toggle" "hold") | `"toggle"` |
-| `pitch_radius` + | `float` | Radius of curvature for the HMD to pitch along. Useful in 3rd person VR games | `0.0` |
-| `ctrl_deadzone` + | `float` | Controller Deadzone when using pitch or yaw emulation | `0.05` |
-| `ctrl_sensitivity` +| `float` | Controller Sensitivity when using pitch or yaw emulation | `1.0` |
-| `user_load_key` + | `string`| The Virtual-Key Code to load user preset | `"VK_NUMPAD1"` |
-| `user_store_key` + | `string`| The Virtual-Key Code to store user preset temporarily | `"VK_NUMPAD4"` |
-| `user_key_type` + | `string`| The store key's behavior ("switch" "toggle" "hold") | `"switch"` |
-| `user_depth` + | `float` | The separation value for a user preset | `0.1` |
-| `user_convergence` +| `float` | The convergence value for a user preset | `1.0` |
-| `user_fov` + | `float` | The fov value for a user preset (optional, will default to global fov) | `90.0` |
-
-
-## Base Installation
-
-- Install SteamVR
-- Download the [latest VRto3D release](https://github.com/oneup03/VRto3D/releases/latest/download/vrto3d.zip) and copy the `vrto3d` folder from inside the VRto3D.zip to your `Steam\steamapps\common\SteamVR\drivers` folder
-- Launch SteamVR once to generate the `default_config.json` and you should see a 1080p SbS `Headset Window` upscaled to fullscreen
-- Close SteamVR
-- Edit the `Steam\config\vrto3d\default_config.json` as needed - [see what each setting does](#configuration)
- - Set `display_index` to your 3D display using display enumeration order (`1` = first enumerated display, `2` = second, etc). Leave it as `0` to auto-use the current primary display. Sometimes this glitches back to the wrong screen and you may have to restart SteamVR
- - Set your render resolution per eye to what you want - can save some performance by reducing this. If your display is half-SbS or half-TaB, then you can try setting this to that half-resolution
- - Configure any `Virtual-Key Code` settings to use keys that you want (especially `user_load_keys` settings as these load a defined depth+convergence preset)
-- Download the latest [VRto3D profiles](https://github.com/oneup03/VRto3D/releases/download/latest/vrto3d_profiles.zip) for games and extract them to your `Steam\config\vrto3d\` folder
-- Run SteamVR to verify that you see the Headset window covering your entire display. This is usually not needed before running games.
- - The Headset window should appear on the configured `display_index` display
- - Dismiss Headset Notice about `Enable Direct Display Mode` as this does nothing
-- Try launching a VR game
-- Keyboard and Mouse are usable, but you may run into issues with accidentally clicking the wrong window or the cursor escaping the game window if the game's mouse control is coded poorly
- - Can try using AutoCursorLock if the mouse keeps escaping
-- Make the game run in windowed mode either in-game settings or with `Alt + Enter` This will alleviate controller input and fullscreen issues. (Borderless fullscreen/windowed sometimes also work)
-- If needed, press `Ctrl + F8` to lock the 3D window to the foreground and focus the game window
- - This is automated by default with the `auto_focus` setting when a VRto3D profile exists for the game
-- If game controls & audio aren't working, use `Alt + Tab` to switch to the game window
-- To quit, exit the game and try to `Alt + Tab` out
- - If the 3D window remains in the foreground, press `Ctrl + F8` to toggle the foregrounding off, and then `Alt + Tab` out
-
-
-## Interlaced, Checkerboard, and Anaglyph Installation (only if you need this output format)
-
-- Complete the [Base Installation](#base-installation) section
-- Optionally set `tab_enable` to true in `Steam\config\vrto3d\default_config.json` if you prefer to lose half vertical resolution instead of half horizontal resolution
- - If using interlaced mode, you want SbS for Column Interlaced and TaB for Row/Line Interlaced. Most interlaced displays should use TaB
-- Download the latest ReShade with full add-on support
-- Run the ReShade installer
- - Browse to to your `Steam\steamapps\common\SteamVR\bin\win64` folder
- - Select `vrserver.exe` and click Next
- - Select `DirectX 11` and click Next
- - Click `Uncheck All` and click Next, Next, Finish
-- Download 3DToElse.fx and save it to `Steam\steamapps\common\SteamVR\bin\win64\reshade-shaders\Shaders`
-- Run SteamVR
-- Press `Home` to open ReShade and click `Skip Tutorial`
-- Select `To_Else` in the menu to enable 3DToElse
-- Disable ReShade's `Performance Mode` checkbox
-- Change 3DToElse settings:
- - Set `Stereoscopic Mode Input` to `Side by Side` (or `Top and Bottom` if you set `tab_enable` above)
- - Set `3D Display Mode` to the type needed for your display (even anaglyph)
- - `Eye Swap` can be toggled if needed
- - Don't touch `Perspective Slider`
-- Enable ReShade's `Performance Mode` checkbox
-- Once configuration is complete, you can run everything the same way as the Base Installation
-- If ReShade settings don't save and you keep getting prompted for the tutorial, you may have to manually edit `Steam\steamapps\common\SteamVR\bin\win64\ReShade.ini` and disable Tutorial with `TutorialProgress=4`
-
-
-## FramePacking, HDMI 3D (only if you need this output format)
-
-- Complete the [Base Installation](#base-installation) section
-- In `Steam\config\vrto3d\default_config.json` set these settings:
- - Set `display_index` to the 3D display where frame-packed output should appear
- - `tab_enable` to true
- - `framepack_offset` to `45` for 1920x2205 or `30` for 1280x1470 (this may vary by display)
-- More instructions and discussion are in this forum
-- Create one of these Custom Resolutions in Nvidia Control Panel or CRU:
-- frame_packed_720p : resolution 1280x1470, 60Hz
- - horizontal: 1280 active; 110 front, 40 sync, 220 back (1650 total)
- - vertical: 1470 active; 5 front, 5 sync, 20 back (1500 total)
-- frame_packed_1080p : resolution 1920x2205, 24Hz/60Hz
- - horizontal: 1920 active; 638 front, 44 sync, 148 back (2750 total)
- - vertical: 2205 active; 4 front, 5 sync, 36 back (2250 total)
-- May need to use CVT reduced blank specs for success with 1080 60Hz, but 60Hz is not standard and may not work at all
- - horizontal: 1920 active; 48 front, 32 sync, 80 back (2080 total)
- - vertical: 2205 active; 4 front, 5 sync, 36 back (2250 total)
-- It may be necessary to remove other resolutions with CRU to avoid games changing the resolution. Hopefully running them in windowed mode (required for VRto3D) will prevent issues though
-
-
-## SR (Simulated Reality) Displays (only if you need this output format)
-
-- Complete the [Base Installation](#base-installation) section
-- If your display supports higher refresh rates than 60hz, you can optionally set `display_frequency` to match in `Steam\config\vrto3d\default_config.json`
-- Install the software package provided with your SR display (Samsung Odyssey 3D Hub or Acer TrueGame)
-- Download the latest ReShade with full add-on support
-- Run the ReShade installer
- - Browse to to your `Steam\steamapps\common\SteamVR\bin\win64` folder
- - Select `vrserver.exe` and click Next
- - Select `DirectX 11` and click Next
- - Click `Uncheck All` and click Next
- - Select `3DGameBridge by Janthony & DinnerBram` and click Next
- - Click Finish
-- Run SteamVR
-- Press `Home` to open ReShade and click `Skip Tutorial`
-- Click on the `Add-Ons` tab
-- Select `srReshade` in the menu to enable it
- - Expand the srReshade dropdown and if you get a `Status: Inactive - Unable to load all SR DLLs` then you may need to do these additional steps:
- - Open Windows Run with `Win + R`
- - Paste this command: `cmd /k setx PATH "C:\Program Files\LeiaSR\Platform\bin;%PATH%"`
- - Exit the terminal and reboot
- - 3D can be toggled on and off by using srReshade's `Ctrl + 2` hotkey
-- Click on the `Home` tab
- - Enable ReShade's `Performance Mode` checkbox
-- Once configuration is complete, you can run everything the same way as the Base Installation
-- If ReShade settings don't save and you keep getting prompted for the tutorial, you may have to manually edit `Steam\steamapps\common\SteamVR\bin\win64\ReShade.ini` and disable Tutorial with `TutorialProgress=4`
-- If you experience a super dark screen, try enabling a random ReShade shader
-- Optional: Setup 6DoF or 3DoF Head Tracking
- - Download SR-OpenTrack-Bridge for 6DoF or 3DoF Head Tracking
- - Save it to somewhere with a short path without spaces like `C:/Apps/SRTrack/`
- - In `Steam\config\vrto3d\default_config.json` set these settings
- - Set `use_open_track` to `true` and ensure `open_track_port` is set to `4242`
- - Set `use_track_filter` to `true`
- - Set `launch_script` to `"start C:/Apps/SRTrack/Simulated_Reality_OpenTrack_Bridge.exe"` and tracking will be auto started every time you start SteamVR
- - Start SteamVR and SR-OpenTrack-Bridge should start also
- - Configure the settings in the SR-OpenTrack-Bridge window to tune the tracker alongside adjusting the VRto3D track filter using the [Hotkeys](#hotkeys)
- - Either the `Yaw/Pitch only` or `XYZ + Yaw/Pitch` modes are recommended
- - Lock the SR-OpenTrack-Bridge settings with `Ctrl + L` to avoid unwanted changes later
- - Alt + Tab to the SR-OpenTrack-Bridge window and press `Ctrl + X` to recenter tracking
-
-
-## AR Glasses (only if you need this output format)
-- Connect AR glasses, switch to Full-SbS mode
-- Complete the [Base Installation](#base-installation) section
-- Optional: install VertoXR to provide 6DoF or 3DoF Head Tracking for popular AR glasses
- - Open VertoXR, connect to the AR glasses
- - Select `Game Mode`, place glasses on a flat surface looking straight ahead, and click `Start Calibrate`. Calibration may need to be redone if misalignment occurs
- - Edit `OpenTrack Configuration` and disable `Enable Roll` if needed
- - `Start` the OpenTrack VertoXR plugin
- - In `Steam\config\vrto3d\default_config.json` set these settings:
- - Set `use_open_track` to `true` and ensure `open_track_port` matches the VertoXR OpenTrack port
- - Set `launch_script` to `"start vertoxr://steamvr"` and VertoXR will be auto started with Open Track active every time you start SteamVR
- - Use the `Recenter` button in VertoXR as needed
-
-
-## Frame Sequential (3DVision)
-- Use [WibbleWobbleVR](https://oneup03.github.io/VRto3D/wiki/WibbleWobbleVR3.0) instead - it has support for VRto3D profiles
-
-
-## Notes
-- The game's main window has to be in focus for control input from your mouse/keyboard/controller to work
-- SteamVR may still complain about Direct Display mode, but this can be safely dismissed
-- Exiting SteamVR may "restart" Steam - this is normal
-- Overlays generally won't work on this virtual HMD
-- XInput controller is recommended
-- SteamVR doesn't support HDR currently
- - AutoHDR may work, but some games will be too dark or too bright
-- DLSS, TAA, and other temporal based settings can create a halo around objects. Most VR mods have fixes for this, but some may not
-
-#### Controls
-- If you want to use Steam Input
- - Open Steam->Settings->Controller
- - Toggle on `Enable Steam Input for Xbox Controllers`
- - Click `Edit` on the `Desktop Layout` and then select `Disable Steam Input`
- - On SteamVR's library page, click the `Controller Icon` and select `Disable Steam Input`
- - Generally you need to start SteamVR first and separately from the game for Steam Input to work
-- This project is primarily targeted for VR mods of flatscreen games, not full VR games. As such, there is only HMD pitch & yaw emulation and no VR controller emulation
-- You can setup a Lighthouse + Vive Tracker + tracked controllers with VRto3D for a seated play area. See [this guide](https://oneup03.github.io/VRto3D/wiki/Motion-Controls-&-Tracking) for details
-- Several VR controller only games can be made to work by using Driver4VR, a paid SteamVR Vive controller emulator. Games with mainly pointer controls work ok. Games with a lot of interaction/movement don't work well.
-- Optional HMD `pitch_enable` and `yaw_enable` emulation can be turned on to help with games or mods that need it (maps to XInput right stick)
- - Reference Virtual-Key Code to find the strings to use for these hotkeys
- - The `ctrl_toggle_key` can be set and used to toggle these settings on/off in-game (only functions if `pitch_enable` and/or `yaw_enable` is set to true)
- - The `ctrl_toggle_type` can be set to either `"toggle"` pitch/yaw on/off or `"hold"` that disables them while the button is held
- - The `pose_reset_key` can be set to allow resetting the view to the original position and orientation
- - Both of these keys can be set to XInput buttons & combinations or single keyboard/mouse keys as outlined in User Presets - Load Keys below
- - The `pitch_radius` can be set to make the pitch emulation move along a semicircle instead of just tilting up/down in place. Use the [Hotkeys](#hotkeys) to adjust this in-game
-- OpenTrack 6DoF or 3DoF support is available over UDP loopback at the configured `open_track_port` when `use_open_track` is true
- - It can be used in combination with Pitch/Yaw emulation and HMD offsets
- - Both AR glasses and SR displays are compatible via 3rd party apps mentioned above
- - You can also use the OpenTrack app to do tracking with cameras, IMUs, phone apps, etc
-- Track filtering of 6DoF/3DoF input can be enabled with `use_track_filter`
- - This filter is useful for reducing jitter while preserving responsiveness through sensitivity/deadzone tuning
- - See the [Hotkeys](#hotkeys) section for how to adjust the filter
-
-#### User Presets
-- If you swap between different convergence settings in-game, sometimes you will end up with black bars on the sides of the screen or you may not see a change immediately. If you reload/restart/reinitialize the VR mod, you should see the change
-- It is recommended to use a single convergence setting for all your presets given the above issue with some VR mods
-- Create any number of user depth/separation & convergence hotkeys in the `user_settings` area of the `default_config.json`
- - A user preset looks like this:
- - ```
- {
- "user_load_key": "VK_NUMPAD1",
- "user_store_key": "VK_NUMPAD4",
- "user_key_type": "switch",
- "user_depth": 0.1,
- "user_convergence": 1.0,
- "user_fov": 70.0
- },
- ```
-- A Load key and a Store key can be configured to load and save Depth/Separation and Convergence settings for a preset
- - Load keys can use XInput buttons & combinations as well as single keyboard/mouse keys
- - The Guide button can be used, but not in combinations
- - XInput Combinations can be set like this `"XINPUT_GAMEPAD_A+XINPUT_GAMEPAD_B"`
- - Store keys can only use single keyboard/mouse keys
- - Reference Virtual-Key Code to find the strings to use for these hotkeys
-- The Load key can be configured to `"switch"` to the user depth/separation & convergence setting, `"toggle"` between the preset and the previous setting every 1.5s, or `"hold"` the user setting until the key is released
-- The Store key will update your user Depth/Separation and Convergence setting to the current value (this only saves while the game is running - you need to create a game profile as detailed below to store it permanently)
-- It is recommended to have a single user preset of `"switch"` type that matches the default depth/separation & convergence so you can easily get back to the default
-
-#### Profile Creation Steps:
-1. Modify or copy and create user preset(s) in `default_config.json` (or preferably in the `Game.exe_config.json` if one already exists) for the game you want to play
-2. If applicable, modify `hmd_height, fov, pitch_enable, yaw_enable, pose_reset_key, ctrl_toggle_key, ctrl_toggle_type, pitch_radius, ctrl_deadzone, ctrl_sensitivity` for the game profile
-3. If the game is already running, use `Ctrl + Shift + F10` to reload the `default_config.json` (or `Ctrl + F10` to reload the `Game.exe_config.json`) with your new settings and presets
-4. Adjust depth/separation (`Ctrl + F3` and `Ctrl + F4` with `+ shift` if possible) & convergence (`Ctrl + F5` and `Ctrl + F6`) for a preset
-5. Use the configured `user_store_key` to temporarily save the current depth/separation & convergence values to the preset
-6. Repeat 4 & 5 for each preset you need
-7. Adjust depth/separation & convergence back to what you want the default to be (if you have a default `"switch"` preset, you can use its configured `user_load_key`)
-8. If applicable, adjust the `ctrl_sensitivity` with `Ctrl -` and `Ctrl +` and the `pitch_radius` with `Ctrl [` and `Ctrl ]`
-9. Save the profile with `Ctrl + F7`
-10. Open your new profile from `Steam\config\vrto3d\` in a text editor and make final adjustments like: making all the convergence values match to avoid rendering or performance issues, changing virtual-key mappings, or tweaking other values/settings
-11. Close out of SteamVR and the game and restart the game. You should hear a loud beep to indicate the profile loaded. Test the profile and you can still make any adjustments per above instructions
-12. Share your `Steam\config\vrto3d\Game.exe_config.json` with others
-
-#### Troubleshooting
-- If SteamVR appears on the wrong display, set `display_index` to the correct display enumeration order and restart SteamVR
-- If SteamVR crashes and disables add-ons, you will need to re-enable VRto3D in the SteamVR Status window
-- The first thing to try is deleting your `Steam\config\steamvr.vrsettings` and `Steam\config\vrto3d\default_config.json`
-- If you have used other SteamVR drivers that also create a virtual HMD, you will need to disable and/or uninstall them
- - Run SteamVR
- - On the SteamVR Status window, go to `Menu -> Settings`
- - Change to the `Startup / Shutdown` tab
- - Click `Manage Add-Ons`
- - Turn `Off` any virtual HMD drivers (ALVR, VRidge, OpenTrack, VCR, iVRy, etc)
- - if issues still arise, try a Clean SteamVR Install and delete your `Steam\steamapps\common\SteamVR` folder
-- Conversely, to uninstall or disable VRto3D, you can disable it under the same `Manage Add-Ons` menu or delete the `vrto3d` folder from your `Steam\steamapps\common\SteamVR\drivers` folder
-- If you have a VR headset and run into issues with this driver, here's some things to try:
- - Disconnect VR headset from computer
- - Clean SteamVR Install
- - Set SteamVR as OpenXR Runtime
-
-
-## Building
-
-- Clone the code and initialize submodules
-- Define `STEAM_PATH` environment variable with the path to your main Steam folder
-- Open Solution in Visual Studio 2022 or VSCode
-- Use the solution to build this driver
-- Build output is automatically copied to your `SteamVR\drivers` folder
+# VRto3D
+
+- OpenVR Driver (OpenXR also supported) that can render in any stereo format natively
+- Compatible games play great with a XInput controller. No motion controls required!
+- VRto3D itself does not "fix" games for 3D, but it allows you to run VR modded (fixed) games on a 3D Display
+- Supports User Profiles for individual games
+- Provides HMD Pitch and Yaw emulation for games that require it
+- Currently targeting OpenVR 2.15.6.
+- Runs on Windows and natively on Linux — see the [Linux](#linux) section for platform-specific setup
+
+
+## Compatible 3D Displays & Output Modes
+
+VRto3D renders one canonical 2W x H side-by-side frame internally and the active Output Mode repacks it for your display. Pick the matching `output_mode` either in `default_config.json` or from the OSD `System` tab (`Ctrl + Home`). Most modes only need the [Regular Installation](#installation); the few that need extra software or display setup are called out below.
+
+Find your display type and use the listed Output Mode(s):
+
+
+ 3D TVs & Projectors (HDMI SbS / TaB)
+
+- **Output Modes:** `SbS`, `TaB`, or one of the `FramePacked*` modes
+- Pick `SbS` or `TaB` based on what your TV / projector accepts as a stereo input
+- `FramePacked*` modes target HDMI 1.4 frame-packed 3D - VRto3D will attempt to engage the custom resolution, but it still relies on your display accepting it and allowing you to engage 3D:
+ - `FramePacked720p60` - 1280x1470 @60Hz, 30px gap
+ - `FramePacked1080p24` - 1920x2205 @24Hz, 45px gap (most universal)
+ - `FramePacked1080p60` - 1920x2205 @60Hz, 45px gap, 371.25 MHz pixel clock (HDMI 2.0+)
+ - `FramePacked1080p60CVT` - 1920x2205 @60Hz with CVT reduced blanking, 45px gap, 280.8 MHz pixel clock (HDMI 2.0)
+ - For any of these modes, it is recommended to start SteamVR before starting the game, as they change monitor modes, which might break games
+- For FramePacked, you can also try manually creating one of these Custom Resolutions in Nvidia Control Panel or CRU first:
+ - **frame_packed_720p** - 1280x1470, 60Hz
+ - horizontal: 1280 active; 110 front, 40 sync, 220 back (1650 total)
+ - vertical: 1470 active; 5 front, 5 sync, 20 back (1500 total)
+ - **frame_packed_1080p** - 1920x2205, 24Hz / 60Hz
+ - horizontal: 1920 active; 638 front, 44 sync, 148 back (2750 total)
+ - vertical: 2205 active; 4 front, 5 sync, 36 back (2250 total)
+ - May need to use CVT reduced blank specs for success with 1080 60Hz, but 60Hz is not standard and may not work at all
+ - horizontal: 1920 active; 48 front, 32 sync, 80 back (2080 total)
+ - vertical: 2205 active; 4 front, 5 sync, 36 back (2250 total)
+ - It may be necessary to remove other resolutions with CRU to avoid games changing the resolution. Hopefully running them in windowed mode (required for VRto3D) will prevent issues though
+ - More instructions and discussion are in this forum thread
+- **Linux:** `SbS`, `TaB`, and the `FramePacked*` modes all work. Frame-packed timing is applied differently than on Windows:
+ - **X11 session**: VRto3D applies the 1280x1470/1920x2205 custom modeline at runtime via XRandR — no EDID hacking (AMD/Intel; NVIDIA proprietary needs `ModeValidation` overrides in xorg.conf)
+ - **Wayland session**: runtime custom modelines aren't possible. Generate an EDID override with `tools/make_fp_edid.py` and load it via `drm.edid_firmware=:edid/.bin` on the kernel cmdline
+ - Either way the HDMI 3D InfoFrame is not emitted (kernel limitation) — most 3D TVs sync to the raw timing or let you force 3D mode manually, exactly like CRU-based setups on Windows
+
+
+
+
+ Passive / Interlaced 3D Displays
+
+- **Output Modes:** `RowInterlaced`, `ColInterlaced`, or `Checkerboard`
+- `RowInterlaced` covers the vast majority of passive 3D TVs and monitors
+- `ColInterlaced` is for column-interlaced passive panels
+- `Checkerboard` is for DLP-link 3D projectors and the older Mitsubishi / Samsung DLP 3D TVs (`(x+y)%2` eye selection)
+- **Linux:** supported
+
+
+
+
+ AR Glasses (Rokid, Xreal, Viture, RayNeo)
+
+- **Output Mode:** `SbS` (switch the glasses to Full-SbS mode first)
+- If you don't have a USBC port with DP-Alt mode on your PC, you'll need a compatible adapter - choose one with SBS and audio support
+- A USBC extension is also recommended
+- Optional: install VertoXR for 6DoF or 3DoF Head Tracking
+ - Open VertoXR and connect to the AR glasses
+ - Select `Game Mode`, place the glasses on a flat surface looking straight ahead, and click `Start Calibrate`. Calibration may need to be redone if misalignment occurs
+ - Edit `OpenTrack Configuration` and disable `Enable Roll` if needed
+ - `Start` the OpenTrack VertoXR plugin
+ - Open the VRto3D OSD (`Ctrl + Home`) and on the `Tracking` tab tick `Enable OpenTrack` under `OpenTrack`, then set `UDP Port` to match VertoXR's OpenTrack port (restart SteamVR after a port change)
+ - On the OSD `System` tab, set `Launch Script` to `start vertoxr://steamvr` so VertoXR auto-starts with OpenTrack active every time you start SteamVR
+ - Click `Save Default Cfg` in the OSD footer to persist the changes
+ - Use the `Recenter` button in VertoXR as needed
+- **Linux:** `SbS` output is supported; the optional VertoXR head-tracking app is Windows-only, so use another OpenTrack source for head tracking on Linux
+
+
+
+
+ Lume Pad, 3DS, Other Wireless 3D Displays
+
+- **Output Mode:** `SbS`
+- Lume Pad Requires Sunshine/Gamestream + Moonlight to stream the SbS output to the device
+- 3DS Requires Sunshine/Gamestream + Moonlight with a custom resolution
+- **Linux:** supported
+
+
+
+
+ SR Displays (Acer Spatial Labs / Asus Spatial Vision / Samsung Odyssey 3D)
+
+- **Output Mode:** `LeiaSR` (SR Display Weaver)
+- Install Samsung Odyssey 3D Hub or Acer TrueGame first
+- Check the `Fix LeiaSR library loading (requires restart)` option in the VRto3D installer
+ - Or Manually: Open Windows Run with `Win + R`, Paste this command: `cmd /k setx PATH "C:\Program Files\LeiaSR\Platform\bin;%PATH%"` Exit the terminal and reboot
+- 6DoF head tracking is built in - just enable Open Track in the OSD `Tracking` tab. Tune the LeiaSR head-tracking and track filter sliders on the same tab
+- **Linux:** not available (compiled out) - there is no Linux SR SDK in-tree
+
+
+
+
+ 3D Vision / Frame Sequential (Shutter Glasses)
+
+- **Output Modes:** `WibbleWobble` (preferred) or `NvidiaDX9` (legacy / unstable)
+- `WibbleWobble` requires the WibbleWobbleClient - select `Install WibbleWobble for Frame Sequential 3D` in the VRto3D Installer. Follow the [WibbleWobble SteamVR Setup Instructions](https://oneup03.github.io/VRto3D/wiki/WibbleWobbleVR3.0#steamvr-setup) (skip the `VR Config` step, but do the rest). When framerate is low, you will have eye flickering
+- `NvidiaDX9` requires the [3DVision driver installed](https://oneup03.github.io/3DVision4All/docs/Native) and 3D Enabled. May freeze or crash, requiring a hard reset. Should be more stable on single-display setups. If it crashes, just try again
+- For both of these modes, it is recommended to start SteamVR before starting the game, as they change monitor modes, which might break games
+- Either one may require `Swap Eyes` to be set depending on how your display initializes
+- **Linux:** not available (compiled out) - `NvidiaDX9` has no Linux driver stack, and `WibbleWobble` is a Windows-only client (a port may come later)
+
+
+
+
+ VR Headset (via Virtual Desktop)
+
+- **Output Mode:** `VirtualDesktop`
+- Half-height Full-SbS in a 2W x 2H window with black bars. See the [Virtual Desktop setup wiki](https://oneup03.github.io/VRto3D/wiki/VirtualDesktop) for required additional configuration
+- **Linux:** the `VirtualDesktop` output mode works
+
+
+
+
+ Dual Display Setups (Dual Projector / Dual Monitor)
+
+- **Output Modes:** `DualDisplay` or `DualDisplayFlip`
+- This assumes you have two displays with the same resolution that are aligned vertically. You can also use Nvidia Surround
+- `DualDisplay` puts the left eye on the `display_index` and the right eye on the display to the right of it
+- `DualDisplayFlip` is the same as `DualDisplay` but the left eye is flipped vertically - useful for mirror-based dual-monitor 3D rigs
+- **Linux:** supported
+
+
+
+
+ Anaglyph 3D (Color-Filter Glasses)
+
+- **Output Modes:** any of the `Anaglyph*` modes - works on any 2D display
+- Red / Cyan glasses: `AnaglyphRedCyan` (simple R \| GB split), `AnaglyphRedCyanDubois` (Dubois - best general-purpose), `AnaglyphRedCyanDeghosted`, `AnaglyphRedCyanCompromise`
+- Green / Magenta glasses: `AnaglyphGreenMagenta` (simple G \| RB split), `AnaglyphGreenMagentaDubois`, `AnaglyphGreenMagentaDeghosted`
+- Blue / Amber (ColorCode 3D) glasses: `AnaglyphBlueAmber`
+- **Linux:** supported
+
+
+
+
+ 2D / Mono Display
+
+- **Output Mode:** `Mono`
+- Single-eye view on any normal 2D display. Toggle `eye_swap` to render the right eye instead of the left
+- **Linux:** supported
+
+
+
+
+## Compatible VR Games & Mods
+Checkout the [Compatibility List](https://oneup03.github.io/VRto3D/wiki/Compatibility-List) to see if a game has been tested
+
+
+## Hotkeys
+Most adjustments now happen in the [On-Screen Menu](#user-presets-via-osd) — open it with `Ctrl + Home` (or `Start + D-Pad Down` on a connected XInput controller). The remaining global hotkeys are intentionally minimal:
+
+- Open / close the OSD menu with `Ctrl + Home` or the `Start + D-Pad Down` gamepad chord
+ - Once open, the menu accepts mouse, keyboard, and gamepad navigation (D-pad / left stick to move, `A` to activate, `B` to cancel the current widget, `LB`/`RB` to page through tabs, right stick to scroll)
+ - Tap `X` (Xbox) / `Square` (PS) to jump nav up to the title bar (where the `X` close button lives — press `A` to dismiss); hold the same button to enter ImGui's *windowing mode* (left stick moves the window, D-pad resizes it, `LB`/`RB` cycle windows)
+- Adjust Depth (Separation) with `Ctrl + F3` and `Ctrl + F4` - re-syncs the projection so the change is visible immediately (some VR mods otherwise need a reload to pick it up)
+ - Hold `Shift` to skip the projection re-sync (bare depth nudge)
+- Adjust Convergence with `Ctrl + F5` and `Ctrl + F6` - this often has issues in VR mods
+- Save current Depth / Convergence / FoV (and other profile fields marked with `"+"` under [Configuration](#configuration)) to the running game's `Game.exe_config.json` with `Ctrl + F7` - a beep indicates success
+- Reload the running game's `Game.exe_config.json` with `Ctrl + F10`, or reload `default_config.json` with `Ctrl + Shift + F10` - a beep indicates success
+- Toggle Auto-Depth on / off with `Ctrl + F11`
+- Toggle locking the SteamVR Headset Window to the foreground (and focusing the game window) with `Ctrl + F8`
+- Take a SbS (and Crossview) Screenshot with `Ctrl + F12`
+- All of the above (except `Ctrl + Home`) can be disabled with `disable_hotkeys` if they conflict with another 3D mod - the OSD menu remains accessible
+
+User-defined preset hotkeys (configured under [User Presets](#user-presets-via-osd)) work alongside the built-in keys above.
+
+
+## Installation
+
+- Install SteamVR
+- **Recommended**: Download [`VRto3D-Installer.exe`](https://github.com/oneup03/VRto3D/releases/download/latest/VRto3D-Installer.exe) from the latest release and run it. The installer auto-detects your Steam library, installs/updates the VRto3D driver, and offers optional cleanup of legacy ReShade and third-party SteamVR drivers. It can also install WibbleWobble and launch SteamVR for you.
+ - **Manual alternative**: Download the [latest VRto3D release](https://github.com/oneup03/VRto3D/releases/latest/download/vrto3d.zip) and copy the `vrto3d` folder from inside the VRto3D.zip to your `Steam\steamapps\common\SteamVR\drivers` folder
+- Launch SteamVR once to generate `default_config.json` and you should see a 1080p SbS `VRto3D` window upscaled to fullscreen
+- Open the OSD menu with `Ctrl + Home` and use the `System` tab to pick `Display Index`, `Output Mode`, and render resolution for your display - see the [Output Modes](#compatible-3d-displays--output-modes) table for which mode to choose
+ - Or edit `Steam\config\vrto3d\default_config.json` manually - [see what each setting does](#configuration)
+ - Sometimes the display selection glitches back to the wrong screen and you may have to restart SteamVR
+ - If your display is half-SbS or half-TaB, you can usually save some performance by reducing the per-eye render to half-resolution. (may break aspect ratio in some games)
+- Download the latest [VRto3D profiles](https://github.com/oneup03/VRto3D/releases/download/latest/vrto3d_profiles.zip) for games and extract them to your `Steam\config\vrto3d\` folder (or click `Download Latest Profiles` on the OSD's `System` tab)
+- Restart SteamVR to verify that you see the Headset window covering your entire display. This is usually not needed before running games.
+ - The Headset window should appear on the configured `display_index` display
+- Try launching a VR game
+- Keyboard and Mouse are usable, but you may run into issues with accidentally clicking the wrong window or the cursor escaping the game window if the game's mouse control is coded poorly
+ - Enable `Lock Cursor to Game` (`lock_cursor`) in the OSD's `System` tab to confine the mouse to the game window, and `Hide Cursor` (`hide_cursor`) to fully hide the OS cursor while the game is focused
+ - Enable `Stereo Cursor` (`stereo_cursor`) to replace the flat OS cursor with a per-eye 3D cursor drawn at an adjustable depth — recommended for SbS/TaB displays where the OS cursor only lines up with one eye
+- Make the game run in windowed mode either in-game settings or with `Alt + Enter` This will alleviate controller input and fullscreen issues. (Borderless fullscreen/windowed sometimes also work)
+- If needed, press `Ctrl + F8` to lock the 3D window to the foreground and focus the game window
+ - This is automated by default with the `auto_focus` setting
+- If game controls & audio aren't working, use `Alt + Tab` to switch to the game window
+- To quit, exit the game and try to `Alt + Tab` out
+ - If the 3D window remains in the foreground, press `Ctrl + F8` to toggle the foregrounding off, and then `Alt + Tab` out
+
+
+## Linux
+
+Native Linux SteamVR driver — same direct-mode architecture as Windows, no virtual display or capture chain. The SteamVR compositor renders games into driver-allocated Vulkan images (shared via dmabuf), VRto3D repacks them into your chosen 3D format and presents fullscreen on the selected display. Configuration, profiles, the OSD, and hotkeys all work the same as on Windows; everything below is Linux-specific.
+
+#### Requirements
+
+- SteamVR for Linux (Steam → SteamVR → install; ~2.16+)
+- A Vulkan-capable GPU (tested: AMD/RADV on Steam Deck)
+- Desktop session (X11 or Wayland). KDE, GNOME, Hyprland/Sway all work; always-on-top is guaranteed on KDE/wlroots (layer-shell) and X11.
+- For hotkeys/gamepad: your user in the `input` group:
+ ```
+ sudo usermod -aG input $USER # then log out/in
+ ```
+
+#### Install
+
+- Download [`vrto3d-linux64.tar.gz`](https://github.com/oneup03/VRto3D/releases/download/latest/vrto3d-linux64.tar.gz) from the latest release, extract it, and run the installer:
+ ```
+ tar xzf vrto3d-linux64.tar.gz
+ cd vrto3d-linux64
+ ./install.sh
+ ```
+ The script finds your Steam installation (pass `--steam ` if it's somewhere unusual), copies the driver into `SteamVR/drivers/`, registers it in `steamvr.vrsettings`, and warns if your user is missing the `input` group. Run `./install.sh --uninstall` to remove it again.
+- **Manual alternative**: copy the `drivers/vrto3d` folder from the tarball into `/steamapps/common/SteamVR/drivers/`, then in `~/.local/share/Steam/config/steamvr.vrsettings` set:
+ ```json
+ "steamvr": { "requireHmd": true, "forcedDriver": "vrto3d", "activateMultipleDrivers": true }
+ ```
+
+Config lives in `/config/vrto3d/default_config.json`, same schema as Windows. Keybind names use the portable vocabulary (`Key_A`, `Numpad7`, `Pad_A`, `Pad_Start+Pad_DPadDown`); legacy `VK_*`/`XINPUT_*` names still load and are rewritten on the next profile save.
+
+#### Display selection & presenters
+
+`display_index` picks the output (0 = primary, 1..N = connected order). The presenter is chosen by session: Wayland (layer-shell overlay surface — always on top on KDE/Hyprland/Sway, plain fullscreen on GNOME) or X11 (borderless `_NET_WM_STATE_ABOVE` window). Override with env `VRTO3D_PRESENTER=x11|wayland` (e.g. force X11/XWayland when you need runtime frame-packed modelines).
+
+Per-output-mode Linux compatibility (including the LeiaSR / 3D Vision / WibbleWobble modes that are compiled out, and the runtime vs. EDID handling for frame-packed HDMI) is noted inline in the [Output Modes](#compatible-3d-displays--output-modes) table.
+
+#### Limitations
+
+- Cursor lock/hide/stereo (`hide_cursor`/`lock_cursor`/`stereo_cursor`) and focus stealing — no cross-client mechanism on Wayland; X11 cursor grab may come later
+
+
+## Configuration
+
+- VRto3D has to be installed and SteamVR launched once for `default_config.json` to be created
+- The easiest way to edit settings is the in-game OSD menu (`Ctrl + Home`); changes take effect immediately and the footer's `Save Default Cfg` / `Save Game Cfg` buttons persist them
+- For manual edits, modify `Steam\config\vrto3d\default_config.json` - some changes require a SteamVR restart (display index, output mode, render size, OpenTrack port)
+- Fields with a `"+"` next to them are saved to a game's profile when you press `Ctrl + F7` (or use the OSD `Save Game Cfg` footer button), and can be reloaded from the game's profile (`Ctrl + F10`) or `default_config.json` (`Ctrl + Shift + F10`)
+- Reference Virtual-Key Code strings for hotkey fields
+- Reference [Profile Creation Steps](#profile-creation-via-osd) for creating a game-specific profile
+
+| Field Name | Type | Description | Default Value |
+|-------------------------------|---------|---------------------------------------------------------------------------------------------------|----------------|
+| `display_index` | `int` | 3D display selection by display order (`0` = auto primary, `1` = first display, `2` = second, etc.) | `0` |
+| `output_mode` | `string`| Stereo output format. See the [Output Modes](#compatible-3d-displays--output-modes) table for valid values | `"SbS"` |
+| `eye_swap` | `bool` | Swap left and right eyes | `false` |
+| `render_width` | `int` | The width to render per eye | `1920` |
+| `render_height` | `int` | The height to render per eye | `1080` |
+| `display_frequency` | `float` | Per-eye refresh rate in Hz. `0.0` auto-detects from the target monitor at activation | `0.0` |
+| `hmd_height` + | `float` | The height/Z position origin of the simulated HMD | `1.0` |
+| `hmd_x` | `float` | The X position origin of the simulated HMD | `0.0` |
+| `hmd_y` | `float` | The y position origin of the simulated HMD | `0.0` |
+| `hmd_yaw` | `float` | The yaw attitude of the simulated HMD | `0.0` |
+| `aspect_ratio` | `float` | The aspect ratio used to calculate vertical FoV | `1.77778` |
+| `fov` + | `float` | The horizontal field of view (FoV) for the VR rendering | `90.0` |
+| `depth` + | `float` | The max separation. Overrides VR's IPD field | `0.1` |
+| `convergence` + | `float` | Where the left and right images converge. Adjusts frustum | `1.0` |
+| `async_enable` + | `bool` | Whether or not to use Asynchronous Reprojection. May improve or worsen smoothness | `false` |
+| `auto_depth_enabled` + | `bool` | Enable Auto-Depth (GPU disparity analysis caps depth so the closest object stays comfortable) | `false` |
+| `auto_depth_target_disparity` +| `float`| Target max on-screen disparity, as a fraction of one eye's width | `0.005` |
+| `auto_depth_smoothing` + | `float` | Auto-Depth smoothing (higher = snappier; lower = smoother) | `0.08` |
+| `shader_enabled` + | `bool` | Enable the display-correction shader pass (Lift/Gamma/Gain + S-Curve) - see [Display Correction](#display-correction-crosstalk-reduction) | `false` |
+| `shader_lift` + | `[float]` | RGB Lift (shadows), 3-element array, each `0..2` | `[1.0, 1.0, 1.0]` |
+| `shader_gamma` + | `[float]` | RGB Gamma (midtones), 3-element array, each `0..2` | `[1.0, 1.0, 1.0]` |
+| `shader_gain` + | `[float]` | RGB Gain (highlights), 3-element array, each `0..2` | `[1.0, 1.0, 1.0]` |
+| `shader_curve` + | `float` | Extended S-Curve strength (`0.33..3.0`; `1.0` = pass-through; `<1` reduces midtone contrast) | `1.0` |
+| `shader_curve_offset_low` + | `float` | S-Curve low-color offset (`-1..1`) | `0.0` |
+| `shader_curve_offset_high` + | `float` | S-Curve high-color offset (`-1..1`) | `0.0` |
+| `shader_curve_offset_both` + | `float` | S-Curve both-color blend-point offset (`-1..1`) | `0.0` |
+| `disable_hotkeys` | `bool` | Disable the global hotkeys (Depth/Convergence/profile/save) to avoid conflict with other 3D mods. `Ctrl + Home` and `Start + D-Pad Down` (OSD toggles) are unaffected | `false` |
+| `dash_enable` | `bool` | Enable or disable SteamVR Dashboard and Home | `false` |
+| `auto_focus` | `bool` | Enable or disable automatic focusing/bringing VRto3D to foreground | `true` |
+| `hide_cursor` + | `bool` | Fully hide the OS cursor while the game is focused (blanks the system cursor set; restored on focus loss) | `false` |
+| `lock_cursor` + | `bool` | Confine the OS cursor to the focused game window's client area (re-asserted continuously) | `false` |
+| `stereo_cursor` + | `bool` | Hide the OS cursor and draw a per-eye 3D cursor in both eye views instead (also used for the OSD menu) | `false` |
+| `cursor_depth` + | `float` | Stereo cursor per-eye shift in pixels: `0` = screen plane, positive = into the screen, negative = pop-out | `0.0` |
+| `cursor_size` + | `int` | Stereo cursor arrow height in per-eye pixels | `32` |
+| `pitch_enable` + | `bool` | Enables or disables Controller right stick y-axis mapped to HMD Pitch | `false` |
+| `yaw_enable` + | `bool` | Enables or disables Controller right stick x-axis mapped to HMD Yaw | `false` |
+| `use_open_track` | `bool` | Enables or disables OpenTrack 6DoF HMD Control | `false` |
+| `open_track_port` | `int` | UDP Port for OpenTrack | `4242` |
+| `use_track_filter` | `bool` | Enables or disables Accela-Hamilton style pose filtering for tracking rotation and position | `false` |
+| `trk_flt_rot_sens` | `float` | Rotation smoothing threshold for track filter (lower = more smoothing) | `0.5` |
+| `trk_flt_pos_sens` | `float` | Position smoothing threshold for track filter (lower = more smoothing) | `0.25` |
+| `trk_flt_rot_dz` | `float` | Rotation deadzone used by track filter | `0.03` |
+| `trk_flt_pos_dz` | `float` | Position deadzone used by track filter | `0.02` |
+| `trk_flt_zoom_smooth` | `float` | Additional rotation smoothing when moving toward the display | `0.0` |
+| `trk_flt_max_zoom` | `float` | Max Z distance used for scaling zoom smoothing | `10.0` |
+| `sr_tracking_enabled` | `bool` | Enable the LeiaSR built-in head-tracking sender. Disable to feed OpenTrack from another source (e.g. the OpenTrack app) while in LeiaSR mode | `true` |
+| `sr_filter_pos_mincutoff` | `float` | LeiaSR built-in head tracking: One-Euro position min cutoff | `0.08` |
+| `sr_filter_pos_beta` | `float` | LeiaSR built-in head tracking: One-Euro position beta | `0.08` |
+| `sr_filter_rot_mincutoff` | `float` | LeiaSR built-in head tracking: One-Euro rotation min cutoff | `0.12` |
+| `sr_filter_rot_beta` | `float` | LeiaSR built-in head tracking: One-Euro rotation beta | `0.01` |
+| `sr_angle_deadzone_deg` | `float` | LeiaSR built-in head tracking: angular deadzone in degrees | `0.2` |
+| `sr_sens_yaw` | `float` | LeiaSR built-in head tracking: yaw sensitivity | `1.0` |
+| `sr_sens_pitch` | `float` | LeiaSR built-in head tracking: pitch sensitivity | `1.0` |
+| `sr_sens_roll` | `float` | LeiaSR built-in head tracking: roll sensitivity | `1.0` |
+| `sr_max_yaw` | `float` | LeiaSR built-in head tracking: max yaw clamp (deg) | `70.0` |
+| `sr_max_pitch` | `float` | LeiaSR built-in head tracking: max pitch clamp (deg) | `70.0` |
+| `sr_max_roll` | `float` | LeiaSR built-in head tracking: max roll clamp (deg) | `70.0` |
+| `sr_track_mode` | `string`| LeiaSR built-in head tracking mode (`"XYZ_YawPitch"`, `"XYZ"`, `"YawPitch"`, `"Full6DOF"`, `"YawPitchRoll"`) | `"XYZ_YawPitch"` |
+| `launch_script` | `string`| Command executed once when VRto3D driver activates (e.g. `"start vertoxr://steamvr"`) | `""` |
+| `pose_reset_key` + | `string`| The Virtual-Key Code to reset the HMD position and orientation | `"VK_NUMPAD7"` |
+| `ctrl_toggle_key` + | `string`| The Virtual-Key Code to toggle Pitch and Yaw emulation on/off when they are enabled | `"VK_NUMPAD8"` |
+| `ctrl_toggle_type` + | `string`| The ctrl_toggle_key's behavior (`"toggle"`, `"hold"`) | `"toggle"` |
+| `pitch_radius` + | `float` | Radius of curvature for the HMD to pitch along. Useful in 3rd person VR games | `0.0` |
+| `ctrl_deadzone` + | `float` | Controller Deadzone when using pitch or yaw emulation | `0.05` |
+| `ctrl_sensitivity` + | `float` | Controller Sensitivity when using pitch or yaw emulation | `1.0` |
+| `user_settings[].user_load_key` + | `string`| The Virtual-Key Code (or XInput chord) to load a user preset | `"VK_NUMPAD1"` |
+| `user_settings[].user_key_type` + | `string`| The load key's behavior. Only `"toggle"` supports multi-preset cycles; `"switch"` and `"hold"` keep just the first value | `"switch"` |
+| `user_settings[].user_depth` + | `float` or `[float]` | The separation value(s) for a user preset (array = cycle on each press, `toggle` mode only) | `0.1` |
+| `user_settings[].user_convergence` + | `float` or `[float]` | The convergence value(s) for a user preset (array = cycle on each press, `toggle` mode only) | `1.0` |
+| `user_settings[].user_fov` + | `float` or `[float]` | The fov value(s) for a user preset; `0` falls back to the global FoV (array = cycle on each press, `toggle` mode only) | `90.0` |
+
+
+## Additional Settings
+- The game's main window has to be in focus for control input from your mouse/keyboard/controller to work. Auto Focus should do this for you, but sometimes you may need to `Alt + Tab` to the game
+- Overlays generally won't work on this virtual HMD
+- XInput controller is recommended
+- SteamVR doesn't support HDR currently
+ - AutoHDR may work, but some games will be too dark or too bright
+- DLSS, TAA, and other temporal based settings can create a halo around objects. Most VR mods have fixes for this, but some may not
+
+#### Display Correction (Crosstalk Reduction)
+- Some 3D displays show visible left/right crosstalk in high-contrast scenes, which can make some games hard to watch. The OSD `Shader` tab runs a configurable post-process pass on the final stereo image that trades some brightness/contrast for sharply less visible crosstalk - toggle it on/off instantly when needed
+- Works with every output mode that produces a side-by-side internal frame (SbS, TaB, Interlaced, Checkerboard, Anaglyph, Mono, LeiaSR, NvidiaDX9, WibbleWobble, etc.)
+- **Controls** on the `Shader` tab:
+ - `Enable Display Correction Shader` - master on/off (defaults off, no perf cost when off)
+ - **Lift / Gamma / Gain** - per-channel RGB controls (`0..2`, default `1.0`): `RGB Lift` raises/lowers shadows, `RGB Gamma` adjusts midtone gamma, `RGB Gain` scales highlights
+ - **S-Curve** - extended contrast curve. `Curve` (range `0.33..3.0`, default `1.0` = pass-through); below `1.0` *reduces* midtone contrast (the crosstalk-fix direction), above `1.0` *increases* it. `Curve Offset (Low / High / Both)` fine-tune the curve's low-end, high-end, and overall blend point (`-1..1`, default `0`)
+ - `Reset to Defaults` - snap everything back to pass-through
+- **Example starting point** for a LeiaSR 3D monitor with heavy crosstalk (Acer/Samsung 27"):
+
+ | Control | Value |
+ |---|---|
+ | `Enable Display Correction Shader` | on |
+ | `RGB Gamma` | `0.6, 0.6, 0.6` |
+ | `Curve` | `0.33` |
+ | `Curve Offset (Both)` | `-1.0` |
+
+- This combination strongly reduces midtone contrast (where most crosstalk is visible) while compressing the overall range. From there, tune `Curve` upward toward `1.0` until you find the smallest correction that hides the crosstalk in your worst-case scene - every step toward `1.0` recovers contrast
+- Save with `Save Game Cfg` (per-game) or `Save Default Cfg` (global) in the OSD footer to persist the settings
+
+#### Controls
+- If you want to use Steam Input
+ - Open Steam->Settings->Controller
+ - Toggle on `Enable Steam Input for Xbox Controllers`
+ - Click `Edit` on the `Desktop Layout` and then select `Disable Steam Input`
+ - On SteamVR's library page, click the `Controller Icon` and select `Disable Steam Input`
+ - Generally you need to start SteamVR first and separately from the game for Steam Input to work
+- This project is primarily targeted for VR mods of flatscreen games, not full VR games. As such, there is currently no VR controller emulation
+- You can setup a Lighthouse + Vive Tracker + tracked controllers with VRto3D for a seated play area. See [this guide](https://oneup03.github.io/VRto3D/wiki/Motion-Controls-&-Tracking) for details
+- Several VR controller only games can be made to work by using Driver4VR, a paid SteamVR Vive controller emulator. Games with mainly pointer controls work ok. Games with a lot of interaction/movement don't work well.
+- Optional XInput right-stick HMD Pitch / Yaw emulation can be turned on for games or mods that need it - configure it from the OSD `Tracking` tab (`Ctrl + Home` -> `XInput (Xbox) Controller`)
+ - Tick `Pitch (right stick)` and / or `Yaw (right stick)` to map them
+ - `Sensitivity`, `Stick Deadzone`, and `Pitch Radius` (curves the view along a semicircle - useful in 3rd-person games) all live in the same panel
+ - `Toggle Key` (default `VK_NUMPAD8`) toggles pitch/yaw on/off in-game; the `Mode` combo next to it switches between `toggle` and `hold` behavior. `Reset Key` (default `VK_NUMPAD7`) recenters. Both bindings accept any keyboard/mouse key or XInput button/chord - click `Set` to capture a single key/button or `Combo` to capture an XInput chord, or type a string from key_names.h directly
+- OpenTrack 6DoF / 3DoF support is available over UDP loopback - tick `Enable OpenTrack` on the OSD `Tracking` tab (`OpenTrack` panel) and set the UDP port to match your sender (default `4242`; port changes need a SteamVR restart)
+ - Works alongside XInput Pitch/Yaw emulation and the HMD offsets
+ - SR Displays are supported natively (no bridge required) - enable OpenTrack with `output_mode: "LeiaSR"` and tune the One-Euro filter / sensitivities / clamps in the OSD `Tracking` tab's `LeiaSR Head Tracking` panel
+ - AR glasses are compatible via 3rd party apps like VertoXR - set the `Launch Script` field on the OSD `System` tab to `start vertoxr://steamvr` to auto-start it with SteamVR
+ - You can also use the OpenTrack app to do tracking with cameras, IMUs, phone apps, etc
+- Track filtering of 6DoF/3DoF input can be enabled with `use_track_filter`
+ - This filter is useful for reducing jitter while preserving responsiveness through sensitivity/deadzone tuning
+ - Adjust filter sensitivities and deadzones from the OSD `Tracking` tab
+
+#### User Presets (via OSD)
+- Press `Ctrl + Home` (or `Start + D-Pad Down` on a gamepad) to open the OSD menu and select the `User Hotkeys` tab
+- Each row maps a Load key to a `(Depth, Convergence, FoV)` preset. Only `toggle` mode supports multi-preset cycles - `switch` and `hold` rows are trimmed to the first value on edit and on profile load
+ - Click `Set` to capture a single key, mouse button, or XInput button
+ - Click `Combo` to capture an XInput chord (e.g. `XINPUT_GAMEPAD_LEFT_SHOULDER+XINPUT_GAMEPAD_RIGHT_SHOULDER`); the chord commits when you release everything
+ - You can also type any Virtual-Key Code string into the Load field directly
+- Choose the load behavior under `Mode`:
+ - `switch` - jump to the preset and stay there
+ - `toggle` - bounce between the preset and the previous setting every 1.5s
+ - `hold` - apply the preset only while the key is held
+- Edit `Depth`, `Conv`, and `FoV` directly, or click `Copy Current` to fill them from the current Stereo tab values
+ - Comma-separated values create a cycle - each press of the Load key advances to the next entry (`toggle` mode only; `switch` and `hold` keep only the first value)
+ - `FoV = 0` falls back to the active profile's global FoV
+- `+ Add Preset Row` adds a new empty row; the `X` button removes a row
+- It is recommended to keep one `switch` preset that matches your default depth/convergence so you can easily get back to baseline
+- If you swap between different convergence settings in-game, sometimes you will end up with black bars on the sides of the screen or you may not see a change immediately - reload/restart/reinitialize the VR mod to pick it up. Using a single convergence value across all presets in a game avoids the issue entirely
+- Changes are live as soon as you edit them but only persist when you click `Save Game Cfg` or `Save Default Cfg` in the footer
+
+#### Profile Creation (via OSD)
+1. Launch the game and let VRto3D load - the OSD title bar will show `Profile: (default)` if no per-game profile exists yet
+2. Press `Ctrl + Home` (or `Start + D-Pad Down` on a gamepad) to open the OSD menu
+3. **Stereo tab**: dial in `Depth`, `Convergence`, `FoV`, and toggle `Swap Eyes` / `Auto-Depth` as needed
+4. **User Hotkeys tab**: add preset rows for the depth/convergence/fov values you want, bind their Load keys, and use `Copy Current` to capture the current values into a row
+5. **Tracking tab** (only if the game needs it): set `Height` under `HMD Pose`, and enable `Pitch (right stick)` / `Yaw (right stick)` under `XInput (Xbox) Controller` for games that need camera control
+6. **System tab**: toggle `Async Reprojection` under `Misc` if it helps smoothness for this game
+7. In the footer click `Save Game Cfg` to write `Steam\config\vrto3d\Game.exe_config.json` (the button label includes the game name and is disabled until VRto3D detects a running game). `Save Default Cfg` writes the equivalent values to `default_config.json` for use as a global baseline
+8. (Optional) Open the file in a text editor for final tweaks - making all convergence values match avoids rendering / performance issues, and you can hand-edit virtual-key strings or other fields the OSD doesn't expose
+9. Restart the game; you should hear a beep when the profile loads. Continue tuning in-OSD and re-save with `Ctrl + F7` (or the footer button) at any time
+10. Share your `Steam\config\vrto3d\Game.exe_config.json` with others
+
+
+## Troubleshooting
+- If SteamVR appears on the wrong display, set `display_index` to the correct display enumeration order (or pick it from the OSD `System` tab) and restart SteamVR
+- If SteamVR crashes and disables add-ons, you will need to re-enable VRto3D in the SteamVR Status window
+- The first thing to try is deleting your `Steam\config\steamvr.vrsettings` and `Steam\config\vrto3d\default_config.json`
+- If you have used other SteamVR drivers that also create a virtual HMD, you will need to disable and/or uninstall them
+ - Run SteamVR
+ - On the SteamVR Status window, go to `Menu -> Settings`
+ - Change to the `Startup / Shutdown` tab
+ - Click `Manage Add-Ons`
+ - Turn `Off` any virtual HMD drivers (ALVR, VRidge, OpenTrack, VCR, iVRy, etc)
+ - if issues still arise, try a Clean SteamVR Install and delete your `Steam\steamapps\common\SteamVR` folder
+- Conversely, to uninstall or disable VRto3D, you can disable it under the same `Manage Add-Ons` menu or delete the `vrto3d` folder from your `Steam\steamapps\common\SteamVR\drivers` folder
+- If you have a VR headset and run into issues with this driver, here's some things to try:
+ - Disconnect VR headset from computer
+ - Clean SteamVR Install
+ - Set SteamVR as OpenXR Runtime
+
+#### Linux
+- `vrto3d.txt` log: `/logs/vrto3d.txt` (plus stderr in vrserver.txt)
+- No hotkeys → check `groups` includes `input`; the OSD shows a warning toast
+- No output window → check `WAYLAND_DISPLAY`/`DISPLAY` reach vrserver (launch SteamVR from a desktop session, not a raw console)
+- Screenshots land in `/steamapps/common/SteamVR/screenshots`
+
+
+## Building
+
+#### Windows
+- Clone the code and initialize submodules
+- Define `STEAM_PATH` environment variable with the path to your main Steam folder
+- Open Solution in Visual Studio 2022 or VSCode
+- Use the solution to build this driver
+- Build output is automatically copied to your `SteamVR\drivers` folder
+
+#### Linux
+```
+cd vrto3d
+cmake -B build -G Ninja
+cmake --build build
+```
+Dependencies: gcc/clang C++17, cmake, Vulkan headers, libX11 + libXrandr, wayland-client, libxkbcommon, libdrm headers. (Shaders ship pre-compiled as SPIR-V headers; regenerate with `shaders/compile_shaders.sh` if you edit them — needs glslc.)
+
+For quick iteration, register the build output in place instead of copying it:
+```
+~/.local/share/Steam/steamapps/common/SteamVR/bin/vrpathreg.sh adddriver \
+ /vrto3d/build/output/drivers/vrto3d
+```
diff --git a/external/NV3D-Lib b/external/NV3D-Lib
new file mode 160000
index 00000000..20db53d2
--- /dev/null
+++ b/external/NV3D-Lib
@@ -0,0 +1 @@
+Subproject commit 20db53d215052b4beb32e0684ced119d2e6510bf
diff --git a/external/SR-lib b/external/SR-lib
new file mode 160000
index 00000000..ba7f5c76
--- /dev/null
+++ b/external/SR-lib
@@ -0,0 +1 @@
+Subproject commit ba7f5c76049ef451c1ad266b4ecd99d14aba6bfc
diff --git a/external/VRto3DLib b/external/VRto3DLib
index 8d93d630..eccc3894 160000
--- a/external/VRto3DLib
+++ b/external/VRto3DLib
@@ -1 +1 @@
-Subproject commit 8d93d63057e856109e03c8b0df668c0d4f3a5202
+Subproject commit eccc38941d95b42e942325063d33d70170a41f3f
diff --git a/external/adl_sdk b/external/adl_sdk
new file mode 160000
index 00000000..84c881cb
--- /dev/null
+++ b/external/adl_sdk
@@ -0,0 +1 @@
+Subproject commit 84c881cb2136f04287eb3af12de00a9e2867aa9e
diff --git a/external/imgui b/external/imgui
new file mode 160000
index 00000000..f5befd2d
--- /dev/null
+++ b/external/imgui
@@ -0,0 +1 @@
+Subproject commit f5befd2d29e66809cd1110a152e375a7f1981f06
diff --git a/external/nvapi b/external/nvapi
new file mode 160000
index 00000000..9b181ea5
--- /dev/null
+++ b/external/nvapi
@@ -0,0 +1 @@
+Subproject commit 9b181ea572f680327fe01a14a0f1f41c78034104
diff --git a/external/openvr b/external/openvr
index ae46a8dd..09240643 160000
--- a/external/openvr
+++ b/external/openvr
@@ -1 +1 @@
-Subproject commit ae46a8dd0172580648c8922658a100439115d3eb
+Subproject commit 0924064316de3effbcd1acf1e309182a2deb1c05
diff --git a/external/stb/stb_image_write.h b/external/stb/stb_image_write.h
new file mode 100644
index 00000000..e4b32ed1
--- /dev/null
+++ b/external/stb/stb_image_write.h
@@ -0,0 +1,1724 @@
+/* stb_image_write - v1.16 - public domain - http://nothings.org/stb
+ writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015
+ no warranty implied; use at your own risk
+
+ Before #including,
+
+ #define STB_IMAGE_WRITE_IMPLEMENTATION
+
+ in the file that you want to have the implementation.
+
+ Will probably not work correctly with strict-aliasing optimizations.
+
+ABOUT:
+
+ This header file is a library for writing images to C stdio or a callback.
+
+ The PNG output is not optimal; it is 20-50% larger than the file
+ written by a decent optimizing implementation; though providing a custom
+ zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that.
+ This library is designed for source code compactness and simplicity,
+ not optimal image file size or run-time performance.
+
+BUILDING:
+
+ You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h.
+ You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace
+ malloc,realloc,free.
+ You can #define STBIW_MEMMOVE() to replace memmove()
+ You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function
+ for PNG compression (instead of the builtin one), it must have the following signature:
+ unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality);
+ The returned data will be freed with STBIW_FREE() (free() by default),
+ so it must be heap allocated with STBIW_MALLOC() (malloc() by default),
+
+UNICODE:
+
+ If compiling for Windows and you wish to use Unicode filenames, compile
+ with
+ #define STBIW_WINDOWS_UTF8
+ and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert
+ Windows wchar_t filenames to utf8.
+
+USAGE:
+
+ There are five functions, one for each image file format:
+
+ int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
+ int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data);
+ int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data);
+ int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality);
+ int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data);
+
+ void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically
+
+ There are also five equivalent functions that use an arbitrary write function. You are
+ expected to open/close your file-equivalent before and after calling these:
+
+ int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes);
+ int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data);
+ int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data);
+ int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data);
+ int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality);
+
+ where the callback is:
+ void stbi_write_func(void *context, void *data, int size);
+
+ You can configure it with these global variables:
+ int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE
+ int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression
+ int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode
+
+
+ You can define STBI_WRITE_NO_STDIO to disable the file variant of these
+ functions, so the library will not use stdio.h at all. However, this will
+ also disable HDR writing, because it requires stdio for formatted output.
+
+ Each function returns 0 on failure and non-0 on success.
+
+ The functions create an image file defined by the parameters. The image
+ is a rectangle of pixels stored from left-to-right, top-to-bottom.
+ Each pixel contains 'comp' channels of data stored interleaved with 8-bits
+ per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is
+ monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall.
+ The *data pointer points to the first byte of the top-left-most pixel.
+ For PNG, "stride_in_bytes" is the distance in bytes from the first byte of
+ a row of pixels to the first byte of the next row of pixels.
+
+ PNG creates output files with the same number of components as the input.
+ The BMP format expands Y to RGB in the file format and does not
+ output alpha.
+
+ PNG supports writing rectangles of data even when the bytes storing rows of
+ data are not consecutive in memory (e.g. sub-rectangles of a larger image),
+ by supplying the stride between the beginning of adjacent rows. The other
+ formats do not. (Thus you cannot write a native-format BMP through the BMP
+ writer, both because it is in BGR order and because it may have padding
+ at the end of the line.)
+
+ PNG allows you to set the deflate compression level by setting the global
+ variable 'stbi_write_png_compression_level' (it defaults to 8).
+
+ HDR expects linear float data. Since the format is always 32-bit rgb(e)
+ data, alpha (if provided) is discarded, and for monochrome data it is
+ replicated across all three channels.
+
+ TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed
+ data, set the global variable 'stbi_write_tga_with_rle' to 0.
+
+ JPEG does ignore alpha channels in input data; quality is between 1 and 100.
+ Higher quality looks better but results in a bigger image.
+ JPEG baseline (no JPEG progressive).
+
+CREDITS:
+
+
+ Sean Barrett - PNG/BMP/TGA
+ Baldur Karlsson - HDR
+ Jean-Sebastien Guay - TGA monochrome
+ Tim Kelsey - misc enhancements
+ Alan Hickman - TGA RLE
+ Emmanuel Julien - initial file IO callback implementation
+ Jon Olick - original jo_jpeg.cpp code
+ Daniel Gibson - integrate JPEG, allow external zlib
+ Aarni Koskela - allow choosing PNG filter
+
+ bugfixes:
+ github:Chribba
+ Guillaume Chereau
+ github:jry2
+ github:romigrou
+ Sergio Gonzalez
+ Jonas Karlsson
+ Filip Wasil
+ Thatcher Ulrich
+ github:poppolopoppo
+ Patrick Boettcher
+ github:xeekworx
+ Cap Petschulat
+ Simon Rodriguez
+ Ivan Tikhonov
+ github:ignotion
+ Adam Schackart
+ Andrew Kensler
+
+LICENSE
+
+ See end of file for license information.
+
+*/
+
+#ifndef INCLUDE_STB_IMAGE_WRITE_H
+#define INCLUDE_STB_IMAGE_WRITE_H
+
+#include
+
+// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline'
+#ifndef STBIWDEF
+#ifdef STB_IMAGE_WRITE_STATIC
+#define STBIWDEF static
+#else
+#ifdef __cplusplus
+#define STBIWDEF extern "C"
+#else
+#define STBIWDEF extern
+#endif
+#endif
+#endif
+
+#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations
+STBIWDEF int stbi_write_tga_with_rle;
+STBIWDEF int stbi_write_png_compression_level;
+STBIWDEF int stbi_write_force_png_filter;
+#endif
+
+#ifndef STBI_WRITE_NO_STDIO
+STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
+STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data);
+STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data);
+STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data);
+STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality);
+
+#ifdef STBIW_WINDOWS_UTF8
+STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input);
+#endif
+#endif
+
+typedef void stbi_write_func(void *context, void *data, int size);
+
+STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes);
+STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data);
+STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data);
+STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data);
+STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality);
+
+STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean);
+
+#endif//INCLUDE_STB_IMAGE_WRITE_H
+
+#ifdef STB_IMAGE_WRITE_IMPLEMENTATION
+
+#ifdef _WIN32
+ #ifndef _CRT_SECURE_NO_WARNINGS
+ #define _CRT_SECURE_NO_WARNINGS
+ #endif
+ #ifndef _CRT_NONSTDC_NO_DEPRECATE
+ #define _CRT_NONSTDC_NO_DEPRECATE
+ #endif
+#endif
+
+#ifndef STBI_WRITE_NO_STDIO
+#include
+#endif // STBI_WRITE_NO_STDIO
+
+#include
+#include
+#include
+#include
+
+#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED))
+// ok
+#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED)
+// ok
+#else
+#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)."
+#endif
+
+#ifndef STBIW_MALLOC
+#define STBIW_MALLOC(sz) malloc(sz)
+#define STBIW_REALLOC(p,newsz) realloc(p,newsz)
+#define STBIW_FREE(p) free(p)
+#endif
+
+#ifndef STBIW_REALLOC_SIZED
+#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz)
+#endif
+
+
+#ifndef STBIW_MEMMOVE
+#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz)
+#endif
+
+
+#ifndef STBIW_ASSERT
+#include
+#define STBIW_ASSERT(x) assert(x)
+#endif
+
+#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff)
+
+#ifdef STB_IMAGE_WRITE_STATIC
+static int stbi_write_png_compression_level = 8;
+static int stbi_write_tga_with_rle = 1;
+static int stbi_write_force_png_filter = -1;
+#else
+int stbi_write_png_compression_level = 8;
+int stbi_write_tga_with_rle = 1;
+int stbi_write_force_png_filter = -1;
+#endif
+
+static int stbi__flip_vertically_on_write = 0;
+
+STBIWDEF void stbi_flip_vertically_on_write(int flag)
+{
+ stbi__flip_vertically_on_write = flag;
+}
+
+typedef struct
+{
+ stbi_write_func *func;
+ void *context;
+ unsigned char buffer[64];
+ int buf_used;
+} stbi__write_context;
+
+// initialize a callback-based context
+static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context)
+{
+ s->func = c;
+ s->context = context;
+}
+
+#ifndef STBI_WRITE_NO_STDIO
+
+static void stbi__stdio_write(void *context, void *data, int size)
+{
+ fwrite(data,1,size,(FILE*) context);
+}
+
+#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8)
+#ifdef __cplusplus
+#define STBIW_EXTERN extern "C"
+#else
+#define STBIW_EXTERN extern
+#endif
+STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide);
+STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default);
+
+STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input)
+{
+ return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL);
+}
+#endif
+
+static FILE *stbiw__fopen(char const *filename, char const *mode)
+{
+ FILE *f;
+#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8)
+ wchar_t wMode[64];
+ wchar_t wFilename[1024];
+ if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename)))
+ return 0;
+
+ if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode)))
+ return 0;
+
+#if defined(_MSC_VER) && _MSC_VER >= 1400
+ if (0 != _wfopen_s(&f, wFilename, wMode))
+ f = 0;
+#else
+ f = _wfopen(wFilename, wMode);
+#endif
+
+#elif defined(_MSC_VER) && _MSC_VER >= 1400
+ if (0 != fopen_s(&f, filename, mode))
+ f=0;
+#else
+ f = fopen(filename, mode);
+#endif
+ return f;
+}
+
+static int stbi__start_write_file(stbi__write_context *s, const char *filename)
+{
+ FILE *f = stbiw__fopen(filename, "wb");
+ stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f);
+ return f != NULL;
+}
+
+static void stbi__end_write_file(stbi__write_context *s)
+{
+ fclose((FILE *)s->context);
+}
+
+#endif // !STBI_WRITE_NO_STDIO
+
+typedef unsigned int stbiw_uint32;
+typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1];
+
+static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v)
+{
+ while (*fmt) {
+ switch (*fmt++) {
+ case ' ': break;
+ case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int));
+ s->func(s->context,&x,1);
+ break; }
+ case '2': { int x = va_arg(v,int);
+ unsigned char b[2];
+ b[0] = STBIW_UCHAR(x);
+ b[1] = STBIW_UCHAR(x>>8);
+ s->func(s->context,b,2);
+ break; }
+ case '4': { stbiw_uint32 x = va_arg(v,int);
+ unsigned char b[4];
+ b[0]=STBIW_UCHAR(x);
+ b[1]=STBIW_UCHAR(x>>8);
+ b[2]=STBIW_UCHAR(x>>16);
+ b[3]=STBIW_UCHAR(x>>24);
+ s->func(s->context,b,4);
+ break; }
+ default:
+ STBIW_ASSERT(0);
+ return;
+ }
+ }
+}
+
+static void stbiw__writef(stbi__write_context *s, const char *fmt, ...)
+{
+ va_list v;
+ va_start(v, fmt);
+ stbiw__writefv(s, fmt, v);
+ va_end(v);
+}
+
+static void stbiw__write_flush(stbi__write_context *s)
+{
+ if (s->buf_used) {
+ s->func(s->context, &s->buffer, s->buf_used);
+ s->buf_used = 0;
+ }
+}
+
+static void stbiw__putc(stbi__write_context *s, unsigned char c)
+{
+ s->func(s->context, &c, 1);
+}
+
+static void stbiw__write1(stbi__write_context *s, unsigned char a)
+{
+ if ((size_t)s->buf_used + 1 > sizeof(s->buffer))
+ stbiw__write_flush(s);
+ s->buffer[s->buf_used++] = a;
+}
+
+static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c)
+{
+ int n;
+ if ((size_t)s->buf_used + 3 > sizeof(s->buffer))
+ stbiw__write_flush(s);
+ n = s->buf_used;
+ s->buf_used = n+3;
+ s->buffer[n+0] = a;
+ s->buffer[n+1] = b;
+ s->buffer[n+2] = c;
+}
+
+static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d)
+{
+ unsigned char bg[3] = { 255, 0, 255}, px[3];
+ int k;
+
+ if (write_alpha < 0)
+ stbiw__write1(s, d[comp - 1]);
+
+ switch (comp) {
+ case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case
+ case 1:
+ if (expand_mono)
+ stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp
+ else
+ stbiw__write1(s, d[0]); // monochrome TGA
+ break;
+ case 4:
+ if (!write_alpha) {
+ // composite against pink background
+ for (k = 0; k < 3; ++k)
+ px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255;
+ stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]);
+ break;
+ }
+ /* FALLTHROUGH */
+ case 3:
+ stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]);
+ break;
+ }
+ if (write_alpha > 0)
+ stbiw__write1(s, d[comp - 1]);
+}
+
+static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono)
+{
+ stbiw_uint32 zero = 0;
+ int i,j, j_end;
+
+ if (y <= 0)
+ return;
+
+ if (stbi__flip_vertically_on_write)
+ vdir *= -1;
+
+ if (vdir < 0) {
+ j_end = -1; j = y-1;
+ } else {
+ j_end = y; j = 0;
+ }
+
+ for (; j != j_end; j += vdir) {
+ for (i=0; i < x; ++i) {
+ unsigned char *d = (unsigned char *) data + (j*x+i)*comp;
+ stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d);
+ }
+ stbiw__write_flush(s);
+ s->func(s->context, &zero, scanline_pad);
+ }
+}
+
+static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...)
+{
+ if (y < 0 || x < 0) {
+ return 0;
+ } else {
+ va_list v;
+ va_start(v, fmt);
+ stbiw__writefv(s, fmt, v);
+ va_end(v);
+ stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono);
+ return 1;
+ }
+}
+
+static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data)
+{
+ if (comp != 4) {
+ // write RGB bitmap
+ int pad = (-x*3) & 3;
+ return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad,
+ "11 4 22 4" "4 44 22 444444",
+ 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header
+ 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header
+ } else {
+ // RGBA bitmaps need a v4 header
+ // use BI_BITFIELDS mode with 32bpp and alpha mask
+ // (straight BI_RGB with alpha mask doesn't work in most readers)
+ return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *)data,1,0,
+ "11 4 22 4" "4 44 22 444444 4444 4 444 444 444 444",
+ 'B', 'M', 14+108+x*y*4, 0, 0, 14+108, // file header
+ 108, x,y, 1,32, 3,0,0,0,0,0, 0xff0000,0xff00,0xff,0xff000000u, 0, 0,0,0, 0,0,0, 0,0,0, 0,0,0); // bitmap V4 header
+ }
+}
+
+STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data)
+{
+ stbi__write_context s = { 0 };
+ stbi__start_write_callbacks(&s, func, context);
+ return stbi_write_bmp_core(&s, x, y, comp, data);
+}
+
+#ifndef STBI_WRITE_NO_STDIO
+STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data)
+{
+ stbi__write_context s = { 0 };
+ if (stbi__start_write_file(&s,filename)) {
+ int r = stbi_write_bmp_core(&s, x, y, comp, data);
+ stbi__end_write_file(&s);
+ return r;
+ } else
+ return 0;
+}
+#endif //!STBI_WRITE_NO_STDIO
+
+static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data)
+{
+ int has_alpha = (comp == 2 || comp == 4);
+ int colorbytes = has_alpha ? comp-1 : comp;
+ int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3
+
+ if (y < 0 || x < 0)
+ return 0;
+
+ if (!stbi_write_tga_with_rle) {
+ return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0,
+ "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8);
+ } else {
+ int i,j,k;
+ int jend, jdir;
+
+ stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8);
+
+ if (stbi__flip_vertically_on_write) {
+ j = 0;
+ jend = y;
+ jdir = 1;
+ } else {
+ j = y-1;
+ jend = -1;
+ jdir = -1;
+ }
+ for (; j != jend; j += jdir) {
+ unsigned char *row = (unsigned char *) data + j * x * comp;
+ int len;
+
+ for (i = 0; i < x; i += len) {
+ unsigned char *begin = row + i * comp;
+ int diff = 1;
+ len = 1;
+
+ if (i < x - 1) {
+ ++len;
+ diff = memcmp(begin, row + (i + 1) * comp, comp);
+ if (diff) {
+ const unsigned char *prev = begin;
+ for (k = i + 2; k < x && len < 128; ++k) {
+ if (memcmp(prev, row + k * comp, comp)) {
+ prev += comp;
+ ++len;
+ } else {
+ --len;
+ break;
+ }
+ }
+ } else {
+ for (k = i + 2; k < x && len < 128; ++k) {
+ if (!memcmp(begin, row + k * comp, comp)) {
+ ++len;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ if (diff) {
+ unsigned char header = STBIW_UCHAR(len - 1);
+ stbiw__write1(s, header);
+ for (k = 0; k < len; ++k) {
+ stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp);
+ }
+ } else {
+ unsigned char header = STBIW_UCHAR(len - 129);
+ stbiw__write1(s, header);
+ stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin);
+ }
+ }
+ }
+ stbiw__write_flush(s);
+ }
+ return 1;
+}
+
+STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data)
+{
+ stbi__write_context s = { 0 };
+ stbi__start_write_callbacks(&s, func, context);
+ return stbi_write_tga_core(&s, x, y, comp, (void *) data);
+}
+
+#ifndef STBI_WRITE_NO_STDIO
+STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data)
+{
+ stbi__write_context s = { 0 };
+ if (stbi__start_write_file(&s,filename)) {
+ int r = stbi_write_tga_core(&s, x, y, comp, (void *) data);
+ stbi__end_write_file(&s);
+ return r;
+ } else
+ return 0;
+}
+#endif
+
+// *************************************************************************************************
+// Radiance RGBE HDR writer
+// by Baldur Karlsson
+
+#define stbiw__max(a, b) ((a) > (b) ? (a) : (b))
+
+#ifndef STBI_WRITE_NO_STDIO
+
+static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear)
+{
+ int exponent;
+ float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2]));
+
+ if (maxcomp < 1e-32f) {
+ rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
+ } else {
+ float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp;
+
+ rgbe[0] = (unsigned char)(linear[0] * normalize);
+ rgbe[1] = (unsigned char)(linear[1] * normalize);
+ rgbe[2] = (unsigned char)(linear[2] * normalize);
+ rgbe[3] = (unsigned char)(exponent + 128);
+ }
+}
+
+static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte)
+{
+ unsigned char lengthbyte = STBIW_UCHAR(length+128);
+ STBIW_ASSERT(length+128 <= 255);
+ s->func(s->context, &lengthbyte, 1);
+ s->func(s->context, &databyte, 1);
+}
+
+static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data)
+{
+ unsigned char lengthbyte = STBIW_UCHAR(length);
+ STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code
+ s->func(s->context, &lengthbyte, 1);
+ s->func(s->context, data, length);
+}
+
+static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline)
+{
+ unsigned char scanlineheader[4] = { 2, 2, 0, 0 };
+ unsigned char rgbe[4];
+ float linear[3];
+ int x;
+
+ scanlineheader[2] = (width&0xff00)>>8;
+ scanlineheader[3] = (width&0x00ff);
+
+ /* skip RLE for images too small or large */
+ if (width < 8 || width >= 32768) {
+ for (x=0; x < width; x++) {
+ switch (ncomp) {
+ case 4: /* fallthrough */
+ case 3: linear[2] = scanline[x*ncomp + 2];
+ linear[1] = scanline[x*ncomp + 1];
+ linear[0] = scanline[x*ncomp + 0];
+ break;
+ default:
+ linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0];
+ break;
+ }
+ stbiw__linear_to_rgbe(rgbe, linear);
+ s->func(s->context, rgbe, 4);
+ }
+ } else {
+ int c,r;
+ /* encode into scratch buffer */
+ for (x=0; x < width; x++) {
+ switch(ncomp) {
+ case 4: /* fallthrough */
+ case 3: linear[2] = scanline[x*ncomp + 2];
+ linear[1] = scanline[x*ncomp + 1];
+ linear[0] = scanline[x*ncomp + 0];
+ break;
+ default:
+ linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0];
+ break;
+ }
+ stbiw__linear_to_rgbe(rgbe, linear);
+ scratch[x + width*0] = rgbe[0];
+ scratch[x + width*1] = rgbe[1];
+ scratch[x + width*2] = rgbe[2];
+ scratch[x + width*3] = rgbe[3];
+ }
+
+ s->func(s->context, scanlineheader, 4);
+
+ /* RLE each component separately */
+ for (c=0; c < 4; c++) {
+ unsigned char *comp = &scratch[width*c];
+
+ x = 0;
+ while (x < width) {
+ // find first run
+ r = x;
+ while (r+2 < width) {
+ if (comp[r] == comp[r+1] && comp[r] == comp[r+2])
+ break;
+ ++r;
+ }
+ if (r+2 >= width)
+ r = width;
+ // dump up to first run
+ while (x < r) {
+ int len = r-x;
+ if (len > 128) len = 128;
+ stbiw__write_dump_data(s, len, &comp[x]);
+ x += len;
+ }
+ // if there's a run, output it
+ if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd
+ // find next byte after run
+ while (r < width && comp[r] == comp[x])
+ ++r;
+ // output run up to r
+ while (x < r) {
+ int len = r-x;
+ if (len > 127) len = 127;
+ stbiw__write_run_data(s, len, comp[x]);
+ x += len;
+ }
+ }
+ }
+ }
+ }
+}
+
+static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data)
+{
+ if (y <= 0 || x <= 0 || data == NULL)
+ return 0;
+ else {
+ // Each component is stored separately. Allocate scratch space for full output scanline.
+ unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4);
+ int i, len;
+ char buffer[128];
+ char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n";
+ s->func(s->context, header, sizeof(header)-1);
+
+#ifdef __STDC_LIB_EXT1__
+ len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
+#else
+ len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
+#endif
+ s->func(s->context, buffer, len);
+
+ for(i=0; i < y; i++)
+ stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i));
+ STBIW_FREE(scratch);
+ return 1;
+ }
+}
+
+STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data)
+{
+ stbi__write_context s = { 0 };
+ stbi__start_write_callbacks(&s, func, context);
+ return stbi_write_hdr_core(&s, x, y, comp, (float *) data);
+}
+
+STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data)
+{
+ stbi__write_context s = { 0 };
+ if (stbi__start_write_file(&s,filename)) {
+ int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data);
+ stbi__end_write_file(&s);
+ return r;
+ } else
+ return 0;
+}
+#endif // STBI_WRITE_NO_STDIO
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// PNG writer
+//
+
+#ifndef STBIW_ZLIB_COMPRESS
+// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size()
+#define stbiw__sbraw(a) ((int *) (void *) (a) - 2)
+#define stbiw__sbm(a) stbiw__sbraw(a)[0]
+#define stbiw__sbn(a) stbiw__sbraw(a)[1]
+
+#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a))
+#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0)
+#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a)))
+
+#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v))
+#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0)
+#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0)
+
+static void *stbiw__sbgrowf(void **arr, int increment, int itemsize)
+{
+ int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1;
+ void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2);
+ STBIW_ASSERT(p);
+ if (p) {
+ if (!*arr) ((int *) p)[1] = 0;
+ *arr = (void *) ((int *) p + 2);
+ stbiw__sbm(*arr) = m;
+ }
+ return *arr;
+}
+
+static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount)
+{
+ while (*bitcount >= 8) {
+ stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer));
+ *bitbuffer >>= 8;
+ *bitcount -= 8;
+ }
+ return data;
+}
+
+static int stbiw__zlib_bitrev(int code, int codebits)
+{
+ int res=0;
+ while (codebits--) {
+ res = (res << 1) | (code & 1);
+ code >>= 1;
+ }
+ return res;
+}
+
+static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit)
+{
+ int i;
+ for (i=0; i < limit && i < 258; ++i)
+ if (a[i] != b[i]) break;
+ return i;
+}
+
+static unsigned int stbiw__zhash(unsigned char *data)
+{
+ stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16);
+ hash ^= hash << 3;
+ hash += hash >> 5;
+ hash ^= hash << 4;
+ hash += hash >> 17;
+ hash ^= hash << 25;
+ hash += hash >> 6;
+ return hash;
+}
+
+#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount))
+#define stbiw__zlib_add(code,codebits) \
+ (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush())
+#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c)
+// default huffman tables
+#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8)
+#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9)
+#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7)
+#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8)
+#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n))
+#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n))
+
+#define stbiw__ZHASH 16384
+
+#endif // STBIW_ZLIB_COMPRESS
+
+STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality)
+{
+#ifdef STBIW_ZLIB_COMPRESS
+ // user provided a zlib compress implementation, use that
+ return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality);
+#else // use builtin
+ static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 };
+ static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 };
+ static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 };
+ static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 };
+ unsigned int bitbuf=0;
+ int i,j, bitcount=0;
+ unsigned char *out = NULL;
+ unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**));
+ if (hash_table == NULL)
+ return NULL;
+ if (quality < 5) quality = 5;
+
+ stbiw__sbpush(out, 0x78); // DEFLATE 32K window
+ stbiw__sbpush(out, 0x5e); // FLEVEL = 1
+ stbiw__zlib_add(1,1); // BFINAL = 1
+ stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman
+
+ for (i=0; i < stbiw__ZHASH; ++i)
+ hash_table[i] = NULL;
+
+ i=0;
+ while (i < data_len-3) {
+ // hash next 3 bytes of data to be compressed
+ int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3;
+ unsigned char *bestloc = 0;
+ unsigned char **hlist = hash_table[h];
+ int n = stbiw__sbcount(hlist);
+ for (j=0; j < n; ++j) {
+ if (hlist[j]-data > i-32768) { // if entry lies within window
+ int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i);
+ if (d >= best) { best=d; bestloc=hlist[j]; }
+ }
+ }
+ // when hash table entry is too long, delete half the entries
+ if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) {
+ STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality);
+ stbiw__sbn(hash_table[h]) = quality;
+ }
+ stbiw__sbpush(hash_table[h],data+i);
+
+ if (bestloc) {
+ // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal
+ h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1);
+ hlist = hash_table[h];
+ n = stbiw__sbcount(hlist);
+ for (j=0; j < n; ++j) {
+ if (hlist[j]-data > i-32767) {
+ int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1);
+ if (e > best) { // if next match is better, bail on current match
+ bestloc = NULL;
+ break;
+ }
+ }
+ }
+ }
+
+ if (bestloc) {
+ int d = (int) (data+i - bestloc); // distance back
+ STBIW_ASSERT(d <= 32767 && best <= 258);
+ for (j=0; best > lengthc[j+1]-1; ++j);
+ stbiw__zlib_huff(j+257);
+ if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]);
+ for (j=0; d > distc[j+1]-1; ++j);
+ stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5);
+ if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]);
+ i += best;
+ } else {
+ stbiw__zlib_huffb(data[i]);
+ ++i;
+ }
+ }
+ // write out final bytes
+ for (;i < data_len; ++i)
+ stbiw__zlib_huffb(data[i]);
+ stbiw__zlib_huff(256); // end of block
+ // pad with 0 bits to byte boundary
+ while (bitcount)
+ stbiw__zlib_add(0,1);
+
+ for (i=0; i < stbiw__ZHASH; ++i)
+ (void) stbiw__sbfree(hash_table[i]);
+ STBIW_FREE(hash_table);
+
+ // store uncompressed instead if compression was worse
+ if (stbiw__sbn(out) > data_len + 2 + ((data_len+32766)/32767)*5) {
+ stbiw__sbn(out) = 2; // truncate to DEFLATE 32K window and FLEVEL = 1
+ for (j = 0; j < data_len;) {
+ int blocklen = data_len - j;
+ if (blocklen > 32767) blocklen = 32767;
+ stbiw__sbpush(out, data_len - j == blocklen); // BFINAL = ?, BTYPE = 0 -- no compression
+ stbiw__sbpush(out, STBIW_UCHAR(blocklen)); // LEN
+ stbiw__sbpush(out, STBIW_UCHAR(blocklen >> 8));
+ stbiw__sbpush(out, STBIW_UCHAR(~blocklen)); // NLEN
+ stbiw__sbpush(out, STBIW_UCHAR(~blocklen >> 8));
+ memcpy(out+stbiw__sbn(out), data+j, blocklen);
+ stbiw__sbn(out) += blocklen;
+ j += blocklen;
+ }
+ }
+
+ {
+ // compute adler32 on input
+ unsigned int s1=1, s2=0;
+ int blocklen = (int) (data_len % 5552);
+ j=0;
+ while (j < data_len) {
+ for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; }
+ s1 %= 65521; s2 %= 65521;
+ j += blocklen;
+ blocklen = 5552;
+ }
+ stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8));
+ stbiw__sbpush(out, STBIW_UCHAR(s2));
+ stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8));
+ stbiw__sbpush(out, STBIW_UCHAR(s1));
+ }
+ *out_len = stbiw__sbn(out);
+ // make returned pointer freeable
+ STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len);
+ return (unsigned char *) stbiw__sbraw(out);
+#endif // STBIW_ZLIB_COMPRESS
+}
+
+static unsigned int stbiw__crc32(unsigned char *buffer, int len)
+{
+#ifdef STBIW_CRC32
+ return STBIW_CRC32(buffer, len);
+#else
+ static unsigned int crc_table[256] =
+ {
+ 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
+ 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
+ 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
+ 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
+ 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
+ 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
+ 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
+ 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
+ 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
+ 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
+ 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
+ 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
+ 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
+ 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
+ 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
+ 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
+ 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
+ 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
+ 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
+ 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
+ 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
+ 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
+ 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
+ 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
+ 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
+ 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
+ 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
+ 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
+ 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
+ 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
+ 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
+ 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
+ };
+
+ unsigned int crc = ~0u;
+ int i;
+ for (i=0; i < len; ++i)
+ crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)];
+ return ~crc;
+#endif
+}
+
+#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4)
+#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v));
+#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3])
+
+static void stbiw__wpcrc(unsigned char **data, int len)
+{
+ unsigned int crc = stbiw__crc32(*data - len - 4, len+4);
+ stbiw__wp32(*data, crc);
+}
+
+static unsigned char stbiw__paeth(int a, int b, int c)
+{
+ int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c);
+ if (pa <= pb && pa <= pc) return STBIW_UCHAR(a);
+ if (pb <= pc) return STBIW_UCHAR(b);
+ return STBIW_UCHAR(c);
+}
+
+// @OPTIMIZE: provide an option that always forces left-predict or paeth predict
+static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer)
+{
+ static int mapping[] = { 0,1,2,3,4 };
+ static int firstmap[] = { 0,1,0,5,6 };
+ int *mymap = (y != 0) ? mapping : firstmap;
+ int i;
+ int type = mymap[filter_type];
+ unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y);
+ int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes;
+
+ if (type==0) {
+ memcpy(line_buffer, z, width*n);
+ return;
+ }
+
+ // first loop isn't optimized since it's just one pixel
+ for (i = 0; i < n; ++i) {
+ switch (type) {
+ case 1: line_buffer[i] = z[i]; break;
+ case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break;
+ case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break;
+ case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break;
+ case 5: line_buffer[i] = z[i]; break;
+ case 6: line_buffer[i] = z[i]; break;
+ }
+ }
+ switch (type) {
+ case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break;
+ case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break;
+ case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break;
+ case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break;
+ case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break;
+ case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break;
+ }
+}
+
+STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len)
+{
+ int force_filter = stbi_write_force_png_filter;
+ int ctype[5] = { -1, 0, 4, 2, 6 };
+ unsigned char sig[8] = { 137,80,78,71,13,10,26,10 };
+ unsigned char *out,*o, *filt, *zlib;
+ signed char *line_buffer;
+ int j,zlen;
+
+ if (stride_bytes == 0)
+ stride_bytes = x * n;
+
+ if (force_filter >= 5) {
+ force_filter = -1;
+ }
+
+ filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0;
+ line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; }
+ for (j=0; j < y; ++j) {
+ int filter_type;
+ if (force_filter > -1) {
+ filter_type = force_filter;
+ stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer);
+ } else { // Estimate the best filter by running through all of them:
+ int best_filter = 0, best_filter_val = 0x7fffffff, est, i;
+ for (filter_type = 0; filter_type < 5; filter_type++) {
+ stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer);
+
+ // Estimate the entropy of the line using this filter; the less, the better.
+ est = 0;
+ for (i = 0; i < x*n; ++i) {
+ est += abs((signed char) line_buffer[i]);
+ }
+ if (est < best_filter_val) {
+ best_filter_val = est;
+ best_filter = filter_type;
+ }
+ }
+ if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it
+ stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer);
+ filter_type = best_filter;
+ }
+ }
+ // when we get here, filter_type contains the filter type, and line_buffer contains the data
+ filt[j*(x*n+1)] = (unsigned char) filter_type;
+ STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n);
+ }
+ STBIW_FREE(line_buffer);
+ zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level);
+ STBIW_FREE(filt);
+ if (!zlib) return 0;
+
+ // each tag requires 12 bytes of overhead
+ out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12);
+ if (!out) return 0;
+ *out_len = 8 + 12+13 + 12+zlen + 12;
+
+ o=out;
+ STBIW_MEMMOVE(o,sig,8); o+= 8;
+ stbiw__wp32(o, 13); // header length
+ stbiw__wptag(o, "IHDR");
+ stbiw__wp32(o, x);
+ stbiw__wp32(o, y);
+ *o++ = 8;
+ *o++ = STBIW_UCHAR(ctype[n]);
+ *o++ = 0;
+ *o++ = 0;
+ *o++ = 0;
+ stbiw__wpcrc(&o,13);
+
+ stbiw__wp32(o, zlen);
+ stbiw__wptag(o, "IDAT");
+ STBIW_MEMMOVE(o, zlib, zlen);
+ o += zlen;
+ STBIW_FREE(zlib);
+ stbiw__wpcrc(&o, zlen);
+
+ stbiw__wp32(o,0);
+ stbiw__wptag(o, "IEND");
+ stbiw__wpcrc(&o,0);
+
+ STBIW_ASSERT(o == out + *out_len);
+
+ return out;
+}
+
+#ifndef STBI_WRITE_NO_STDIO
+STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes)
+{
+ FILE *f;
+ int len;
+ unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len);
+ if (png == NULL) return 0;
+
+ f = stbiw__fopen(filename, "wb");
+ if (!f) { STBIW_FREE(png); return 0; }
+ fwrite(png, 1, len, f);
+ fclose(f);
+ STBIW_FREE(png);
+ return 1;
+}
+#endif
+
+STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes)
+{
+ int len;
+ unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len);
+ if (png == NULL) return 0;
+ func(context, png, len);
+ STBIW_FREE(png);
+ return 1;
+}
+
+
+/* ***************************************************************************
+ *
+ * JPEG writer
+ *
+ * This is based on Jon Olick's jo_jpeg.cpp:
+ * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html
+ */
+
+static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,
+ 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 };
+
+static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) {
+ int bitBuf = *bitBufP, bitCnt = *bitCntP;
+ bitCnt += bs[1];
+ bitBuf |= bs[0] << (24 - bitCnt);
+ while(bitCnt >= 8) {
+ unsigned char c = (bitBuf >> 16) & 255;
+ stbiw__putc(s, c);
+ if(c == 255) {
+ stbiw__putc(s, 0);
+ }
+ bitBuf <<= 8;
+ bitCnt -= 8;
+ }
+ *bitBufP = bitBuf;
+ *bitCntP = bitCnt;
+}
+
+static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) {
+ float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p;
+ float z1, z2, z3, z4, z5, z11, z13;
+
+ float tmp0 = d0 + d7;
+ float tmp7 = d0 - d7;
+ float tmp1 = d1 + d6;
+ float tmp6 = d1 - d6;
+ float tmp2 = d2 + d5;
+ float tmp5 = d2 - d5;
+ float tmp3 = d3 + d4;
+ float tmp4 = d3 - d4;
+
+ // Even part
+ float tmp10 = tmp0 + tmp3; // phase 2
+ float tmp13 = tmp0 - tmp3;
+ float tmp11 = tmp1 + tmp2;
+ float tmp12 = tmp1 - tmp2;
+
+ d0 = tmp10 + tmp11; // phase 3
+ d4 = tmp10 - tmp11;
+
+ z1 = (tmp12 + tmp13) * 0.707106781f; // c4
+ d2 = tmp13 + z1; // phase 5
+ d6 = tmp13 - z1;
+
+ // Odd part
+ tmp10 = tmp4 + tmp5; // phase 2
+ tmp11 = tmp5 + tmp6;
+ tmp12 = tmp6 + tmp7;
+
+ // The rotator is modified from fig 4-8 to avoid extra negations.
+ z5 = (tmp10 - tmp12) * 0.382683433f; // c6
+ z2 = tmp10 * 0.541196100f + z5; // c2-c6
+ z4 = tmp12 * 1.306562965f + z5; // c2+c6
+ z3 = tmp11 * 0.707106781f; // c4
+
+ z11 = tmp7 + z3; // phase 5
+ z13 = tmp7 - z3;
+
+ *d5p = z13 + z2; // phase 6
+ *d3p = z13 - z2;
+ *d1p = z11 + z4;
+ *d7p = z11 - z4;
+
+ *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6;
+}
+
+static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) {
+ int tmp1 = val < 0 ? -val : val;
+ val = val < 0 ? val-1 : val;
+ bits[1] = 1;
+ while(tmp1 >>= 1) {
+ ++bits[1];
+ }
+ bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) {
+ }
+ // end0pos = first element in reverse order !=0
+ if(end0pos == 0) {
+ stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB);
+ return DU[0];
+ }
+ for(i = 1; i <= end0pos; ++i) {
+ int startpos = i;
+ int nrzeroes;
+ unsigned short bits[2];
+ for (; DU[i]==0 && i<=end0pos; ++i) {
+ }
+ nrzeroes = i-startpos;
+ if ( nrzeroes >= 16 ) {
+ int lng = nrzeroes>>4;
+ int nrmarker;
+ for (nrmarker=1; nrmarker <= lng; ++nrmarker)
+ stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes);
+ nrzeroes &= 15;
+ }
+ stbiw__jpg_calcBits(DU[i], bits);
+ stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]);
+ stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits);
+ }
+ if(end0pos != 63) {
+ stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB);
+ }
+ return DU[0];
+}
+
+static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) {
+ // Constants that don't pollute global namespace
+ static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0};
+ static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11};
+ static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d};
+ static const unsigned char std_ac_luminance_values[] = {
+ 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,
+ 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,
+ 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,
+ 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
+ 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,
+ 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,
+ 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa
+ };
+ static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0};
+ static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11};
+ static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77};
+ static const unsigned char std_ac_chrominance_values[] = {
+ 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,
+ 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,
+ 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,
+ 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
+ 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,
+ 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,
+ 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa
+ };
+ // Huffman tables
+ static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}};
+ static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}};
+ static const unsigned short YAC_HT[256][2] = {
+ {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0}
+ };
+ static const unsigned short UVAC_HT[256][2] = {
+ {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0},
+ {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0}
+ };
+ static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,
+ 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99};
+ static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,
+ 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99};
+ static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f,
+ 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f };
+
+ int row, col, i, k, subsample;
+ float fdtbl_Y[64], fdtbl_UV[64];
+ unsigned char YTable[64], UVTable[64];
+
+ if(!data || !width || !height || comp > 4 || comp < 1) {
+ return 0;
+ }
+
+ quality = quality ? quality : 90;
+ subsample = quality <= 90 ? 1 : 0;
+ quality = quality < 1 ? 1 : quality > 100 ? 100 : quality;
+ quality = quality < 50 ? 5000 / quality : 200 - quality * 2;
+
+ for(i = 0; i < 64; ++i) {
+ int uvti, yti = (YQT[i]*quality+50)/100;
+ YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti);
+ uvti = (UVQT[i]*quality+50)/100;
+ UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti);
+ }
+
+ for(row = 0, k = 0; row < 8; ++row) {
+ for(col = 0; col < 8; ++col, ++k) {
+ fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]);
+ fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]);
+ }
+ }
+
+ // Write Headers
+ {
+ static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 };
+ static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 };
+ const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width),
+ 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 };
+ s->func(s->context, (void*)head0, sizeof(head0));
+ s->func(s->context, (void*)YTable, sizeof(YTable));
+ stbiw__putc(s, 1);
+ s->func(s->context, UVTable, sizeof(UVTable));
+ s->func(s->context, (void*)head1, sizeof(head1));
+ s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1);
+ s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values));
+ stbiw__putc(s, 0x10); // HTYACinfo
+ s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1);
+ s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values));
+ stbiw__putc(s, 1); // HTUDCinfo
+ s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1);
+ s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values));
+ stbiw__putc(s, 0x11); // HTUACinfo
+ s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1);
+ s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values));
+ s->func(s->context, (void*)head2, sizeof(head2));
+ }
+
+ // Encode 8x8 macroblocks
+ {
+ static const unsigned short fillBits[] = {0x7F, 7};
+ int DCY=0, DCU=0, DCV=0;
+ int bitBuf=0, bitCnt=0;
+ // comp == 2 is grey+alpha (alpha is ignored)
+ int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0;
+ const unsigned char *dataR = (const unsigned char *)data;
+ const unsigned char *dataG = dataR + ofsG;
+ const unsigned char *dataB = dataR + ofsB;
+ int x, y, pos;
+ if(subsample) {
+ for(y = 0; y < height; y += 16) {
+ for(x = 0; x < width; x += 16) {
+ float Y[256], U[256], V[256];
+ for(row = y, pos = 0; row < y+16; ++row) {
+ // row >= height => use last input row
+ int clamped_row = (row < height) ? row : height - 1;
+ int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp;
+ for(col = x; col < x+16; ++col, ++pos) {
+ // if col >= width => use pixel from last input column
+ int p = base_p + ((col < width) ? col : (width-1))*comp;
+ float r = dataR[p], g = dataG[p], b = dataB[p];
+ Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128;
+ U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b;
+ V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b;
+ }
+ }
+ DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
+ DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
+ DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
+ DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
+
+ // subsample U,V
+ {
+ float subU[64], subV[64];
+ int yy, xx;
+ for(yy = 0, pos = 0; yy < 8; ++yy) {
+ for(xx = 0; xx < 8; ++xx, ++pos) {
+ int j = yy*32+xx*2;
+ subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f;
+ subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f;
+ }
+ }
+ DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
+ DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
+ }
+ }
+ }
+ } else {
+ for(y = 0; y < height; y += 8) {
+ for(x = 0; x < width; x += 8) {
+ float Y[64], U[64], V[64];
+ for(row = y, pos = 0; row < y+8; ++row) {
+ // row >= height => use last input row
+ int clamped_row = (row < height) ? row : height - 1;
+ int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp;
+ for(col = x; col < x+8; ++col, ++pos) {
+ // if col >= width => use pixel from last input column
+ int p = base_p + ((col < width) ? col : (width-1))*comp;
+ float r = dataR[p], g = dataG[p], b = dataB[p];
+ Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128;
+ U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b;
+ V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b;
+ }
+ }
+
+ DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT);
+ DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
+ DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
+ }
+ }
+ }
+
+ // Do the bit alignment of the EOI marker
+ stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits);
+ }
+
+ // EOI
+ stbiw__putc(s, 0xFF);
+ stbiw__putc(s, 0xD9);
+
+ return 1;
+}
+
+STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality)
+{
+ stbi__write_context s = { 0 };
+ stbi__start_write_callbacks(&s, func, context);
+ return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality);
+}
+
+
+#ifndef STBI_WRITE_NO_STDIO
+STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality)
+{
+ stbi__write_context s = { 0 };
+ if (stbi__start_write_file(&s,filename)) {
+ int r = stbi_write_jpg_core(&s, x, y, comp, data, quality);
+ stbi__end_write_file(&s);
+ return r;
+ } else
+ return 0;
+}
+#endif
+
+#endif // STB_IMAGE_WRITE_IMPLEMENTATION
+
+/* Revision history
+ 1.16 (2021-07-11)
+ make Deflate code emit uncompressed blocks when it would otherwise expand
+ support writing BMPs with alpha channel
+ 1.15 (2020-07-13) unknown
+ 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels
+ 1.13
+ 1.12
+ 1.11 (2019-08-11)
+
+ 1.10 (2019-02-07)
+ support utf8 filenames in Windows; fix warnings and platform ifdefs
+ 1.09 (2018-02-11)
+ fix typo in zlib quality API, improve STB_I_W_STATIC in C++
+ 1.08 (2018-01-29)
+ add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter
+ 1.07 (2017-07-24)
+ doc fix
+ 1.06 (2017-07-23)
+ writing JPEG (using Jon Olick's code)
+ 1.05 ???
+ 1.04 (2017-03-03)
+ monochrome BMP expansion
+ 1.03 ???
+ 1.02 (2016-04-02)
+ avoid allocating large structures on the stack
+ 1.01 (2016-01-16)
+ STBIW_REALLOC_SIZED: support allocators with no realloc support
+ avoid race-condition in crc initialization
+ minor compile issues
+ 1.00 (2015-09-14)
+ installable file IO function
+ 0.99 (2015-09-13)
+ warning fixes; TGA rle support
+ 0.98 (2015-04-08)
+ added STBIW_MALLOC, STBIW_ASSERT etc
+ 0.97 (2015-01-18)
+ fixed HDR asserts, rewrote HDR rle logic
+ 0.96 (2015-01-17)
+ add HDR output
+ fix monochrome BMP
+ 0.95 (2014-08-17)
+ add monochrome TGA output
+ 0.94 (2014-05-31)
+ rename private functions to avoid conflicts with stb_image.h
+ 0.93 (2014-05-27)
+ warning fixes
+ 0.92 (2010-08-01)
+ casts to unsigned char to fix warnings
+ 0.91 (2010-07-17)
+ first public release
+ 0.90 first internal release
+*/
+
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------------------------------
+*/
diff --git a/game_configs/GoWRagnarokIni.zip b/game_configs/GoWRagnarokIni.zip
new file mode 100644
index 00000000..6ca56942
Binary files /dev/null and b/game_configs/GoWRagnarokIni.zip differ
diff --git a/game_configs/uevr/b1-Win64-Shipping.zip b/game_configs/uevr/b1-Win64-Shipping.zip
index 2a6ec27a..09dc0664 100644
Binary files a/game_configs/uevr/b1-Win64-Shipping.zip and b/game_configs/uevr/b1-Win64-Shipping.zip differ
diff --git a/installer/README.md b/installer/README.md
new file mode 100644
index 00000000..37eccf1c
--- /dev/null
+++ b/installer/README.md
@@ -0,0 +1,43 @@
+# VRto3D Installer
+
+A Windows installer (Inno Setup) that automates VRto3D installation, optional WibbleWobble install, and a few common cleanup tasks.
+
+## What it does
+
+The installer wizard offers five tasks:
+
+- **Install / update VRto3D driver** (default on) — downloads `vrto3d.zip` from the latest GitHub release and copies the `vrto3d` folder into SteamVR's `drivers/` directory. Falls back to a `vrto3d.zip` placed next to the installer .exe if the download fails.
+- **Install WibbleWobble for Frame Sequential 3D** (default off) — downloads `WibbleWobbleBeta7.zip`, lets the user pick a parent folder, extracts the `WibbleWobble` folder there, and runs `WibbleWobble\WibbleWobbleClient\Register.bat` elevated. Also removes the legacy `\drivers\WibbleWobbleVR` driver, which conflicts with VRto3D's built-in WibbleWobble presenter.
+- **Remove legacy ReShade from `SteamVR\bin\win64`** (default on) — deletes only files identified as ReShade artifacts (DLL hooks containing the `ReShade` signature, plus `ReShade.ini`/`.log`/`reshade-shaders\`/`reshade-presets\`/`reshade-cache\` and `3DToElse.fx`). SteamVR's own files are not touched.
+- **Remove third-party SteamVR drivers and reset `steamvr.vrsettings`** (default on) — deletes any folder under `\drivers\` that isn't on the Valve allowlist (`gamepad`, `htc`, `indexcontroller`, `indexhmd`, `lighthouse`, `null`, `oculus`, `oculus_legacy`, `prism`, `tundra_labs`, `vrlink`, `vrto3d`) and deletes `\config\steamvr.vrsettings`.
+- **Launch SteamVR when finished** (default on) — starts SteamVR via `steam://run/250820` from the Finish page.
+
+## How SteamVR is located
+
+1. Reads `HKCU\Software\Valve\Steam\SteamPath` from the registry.
+2. Parses `\steamapps\libraryfolders.vdf` and looks for app `250820` (SteamVR) — handles users with SteamVR installed on a different drive.
+3. Falls back to `\steamapps\common\SteamVR` if the VDF parse misses.
+
+If neither Steam nor SteamVR is detected, the installer offers to open Steam's install page (`steam://install/250820`) or lets the user point to a SteamVR folder manually.
+
+## Building
+
+Requires Inno Setup 6.4 or later (for the bundled 7-Zip extractor).
+
+```powershell
+choco install innosetup -y
+iscc installer\VRto3D-Installer.iss
+```
+
+Output: `installer\Output\VRto3D-Installer.exe`.
+
+## Distribution
+
+Built in CI by `.github/workflows/Build.yml` and uploaded as a separate release asset alongside `vrto3d.zip`. Users can either:
+
+- Download just `VRto3D-Installer.exe` and let it pull `vrto3d.zip` over the network at install time.
+- Download both `VRto3D-Installer.exe` and `vrto3d.zip` into the same folder for offline installs — the installer auto-detects the local zip when the download fails.
+
+## Logs
+
+Every action is appended to `\logs\VRto3D-Installer.log` (e.g. `C:\Program Files (x86)\Steam\logs\VRto3D-Installer.log`). If Steam isn't detected, the log falls back to `%TEMP%\VRto3D-Installer.log`. Useful for diagnosing skipped tasks (e.g. "SteamVR path not set, skipping").
diff --git a/installer/VRto3D-Installer.iss b/installer/VRto3D-Installer.iss
new file mode 100644
index 00000000..a1dad488
--- /dev/null
+++ b/installer/VRto3D-Installer.iss
@@ -0,0 +1,1101 @@
+; VRto3D Installer
+; Inno Setup 6.4+ script. Build with: iscc installer\VRto3D-Installer.iss
+
+#define AppName "VRto3D"
+#define AppPublisher "oneup03"
+#define AppURL "https://github.com/oneup03/VRto3D"
+#define VRto3DReleaseUrl "https://github.com/oneup03/VRto3D/releases/latest/download/vrto3d.zip"
+#define VRto3DPreReleaseUrl "https://github.com/oneup03/VRto3D/releases/download/latest/VRto3D.zip"
+#define WWZipUrl "https://github.com/user-attachments/files/29946580/WibbleWobbleBeta11.zip"
+#define SteamVRAppId "250820"
+
+[Setup]
+AppId={{A6F4D9E2-7C3B-4A2E-9F1B-1B3E2D7C8F40}
+AppName={#AppName}
+AppVersion=1.0
+AppVerName={#AppName}
+AppPublisher={#AppPublisher}
+AppPublisherURL={#AppURL}
+AppSupportURL={#AppURL}
+DefaultDirName={autopf}\VRto3D
+DisableWelcomePage=no
+DisableDirPage=yes
+DisableProgramGroupPage=yes
+DisableReadyPage=no
+Uninstallable=no
+PrivilegesRequired=admin
+OutputDir=Output
+OutputBaseFilename=VRto3D-Installer
+WizardStyle=modern
+ArchitecturesInstallIn64BitMode=x64compatible
+SolidCompression=yes
+Compression=lzma2
+ArchiveExtraction=full
+ShowLanguageDialog=no
+CloseApplications=no
+
+[Languages]
+Name: "english"; MessagesFile: "compiler:Default.isl"
+
+[Tasks]
+Name: "install"; Description: "Install / update VRto3D driver"; GroupDescription: "Actions:"; Flags: checkedonce
+Name: "install\release"; Description: "Latest official release"; Flags: exclusive
+Name: "install\prerelease"; Description: "Latest pre-release"; Flags: exclusive unchecked
+Name: "install\local"; Description: "Local VRto3D.zip (next to installer)"; Flags: exclusive unchecked
+Name: "cleanreshade"; Description: "Remove legacy ReShade from SteamVR\bin\win64"; GroupDescription: "Cleanup (recommended):"
+Name: "cleandrivers"; Description: "Remove third-party SteamVR drivers and reset steamvr.vrsettings"; GroupDescription: "Cleanup (recommended):"
+Name: "wibblewobble"; Description: "Install WibbleWobble for Frame Sequential 3D"; GroupDescription: "Optional:"; Flags: unchecked
+Name: "addleiasrpath"; Description: "Fix LeiaSR library loading (requires restart)"; GroupDescription: "Optional:"; Flags: unchecked
+Name: "launchsteamvr"; Description: "Launch SteamVR when finished"; GroupDescription: "Finish:"
+
+[Code]
+var
+ SteamPath: String;
+ SteamVRPath: String;
+ ReleaseTag: String;
+ ReleaseTitle: String;
+ PreReleaseTag: String;
+ PreReleaseTitle: String;
+ LocalZipNote: String;
+ LogPath: String;
+
+ SteamPage: TInputDirWizardPage;
+ WWPage: TInputDirWizardPage;
+
+{ ============================================================ }
+{ Logging }
+{ ============================================================ }
+
+procedure LogLine(const S: String);
+var
+ Line: AnsiString;
+begin
+ if LogPath = '' then Exit;
+ Line := AnsiString(GetDateTimeString('yyyy-mm-dd hh:nn:ss', '-', ':') + ' ' + S + #13#10);
+ SaveStringToFile(LogPath, Line, FileExists(LogPath));
+end;
+
+{ ============================================================ }
+{ Path / string helpers }
+{ ============================================================ }
+
+function NormalizeSlashes(const S: String): String;
+var
+ I: Integer;
+ R: String;
+begin
+ R := S;
+ for I := 1 to Length(R) do
+ if R[I] = '/' then
+ R[I] := '\';
+ Result := R;
+end;
+
+function PathCombine(const A, B: String): String;
+begin
+ if A = '' then
+ Result := B
+ else if A[Length(A)] = '\' then
+ Result := A + B
+ else
+ Result := A + '\' + B;
+end;
+
+{ Pull the second quoted string out of a VDF "key" "value" line.
+ Returns empty string if no such pair exists. }
+function VdfLineValue(const Line: String): String;
+var
+ Q1, Q2, Q3, Q4: Integer;
+ S: String;
+begin
+ Result := '';
+ S := Line;
+ Q1 := Pos('"', S);
+ if Q1 = 0 then Exit;
+ Q2 := Pos('"', Copy(S, Q1 + 1, MaxInt));
+ if Q2 = 0 then Exit;
+ Q2 := Q2 + Q1;
+ Q3 := Pos('"', Copy(S, Q2 + 1, MaxInt));
+ if Q3 = 0 then Exit;
+ Q3 := Q3 + Q2;
+ Q4 := Pos('"', Copy(S, Q3 + 1, MaxInt));
+ if Q4 = 0 then Exit;
+ Q4 := Q4 + Q3;
+ Result := Copy(S, Q3 + 1, Q4 - Q3 - 1);
+end;
+
+function VdfLineKey(const Line: String): String;
+var
+ Q1, Q2: Integer;
+ S: String;
+begin
+ Result := '';
+ S := Line;
+ Q1 := Pos('"', S);
+ if Q1 = 0 then Exit;
+ Q2 := Pos('"', Copy(S, Q1 + 1, MaxInt));
+ if Q2 = 0 then Exit;
+ Q2 := Q2 + Q1;
+ Result := LowerCase(Copy(S, Q1 + 1, Q2 - Q1 - 1));
+end;
+
+function SplitLines(const Text: String): TArrayOfString;
+var
+ I, J, N: Integer;
+begin
+ N := 0;
+ SetArrayLength(Result, 0);
+ J := 1;
+ for I := 1 to Length(Text) do
+ begin
+ if Text[I] = #10 then
+ begin
+ SetArrayLength(Result, N + 1);
+ Result[N] := Copy(Text, J, I - J);
+ if (Length(Result[N]) > 0) and (Result[N][Length(Result[N])] = #13) then
+ Result[N] := Copy(Result[N], 1, Length(Result[N]) - 1);
+ N := N + 1;
+ J := I + 1;
+ end;
+ end;
+ if J <= Length(Text) then
+ begin
+ SetArrayLength(Result, N + 1);
+ Result[N] := Copy(Text, J, Length(Text) - J + 1);
+ end;
+end;
+
+{ Parse libraryfolders.vdf and return the library root that contains app SteamVRAppId.
+ Returns empty string if the app isn't listed in any library. }
+function FindSteamVRLibrary(const VdfPath: String): String;
+var
+ Raw: AnsiString;
+ Lines: TArrayOfString;
+ I: Integer;
+ Trimmed: String;
+ CurPath: String;
+ Key: String;
+ InApps: Boolean;
+ AppsDepth: Integer;
+begin
+ Result := '';
+ if not FileExists(VdfPath) then Exit;
+ if not LoadStringFromFile(VdfPath, Raw) then Exit;
+ Lines := SplitLines(String(Raw));
+
+ CurPath := '';
+ InApps := False;
+ AppsDepth := 0;
+
+ for I := 0 to GetArrayLength(Lines) - 1 do
+ begin
+ Trimmed := Trim(Lines[I]);
+ if Trimmed = '' then Continue;
+
+ Key := VdfLineKey(Trimmed);
+
+ if (not InApps) and (Key = 'path') then
+ begin
+ CurPath := VdfLineValue(Trimmed);
+ StringChangeEx(CurPath, '\\', '\', True);
+ CurPath := NormalizeSlashes(CurPath);
+ Continue;
+ end;
+
+ if (not InApps) and (Key = 'apps') then
+ begin
+ InApps := True;
+ AppsDepth := 0;
+ Continue;
+ end;
+
+ if InApps then
+ begin
+ if Trimmed = '{' then
+ begin
+ AppsDepth := AppsDepth + 1;
+ Continue;
+ end;
+ if Trimmed = '}' then
+ begin
+ AppsDepth := AppsDepth - 1;
+ if AppsDepth <= 0 then
+ InApps := False;
+ Continue;
+ end;
+ if Key = '{#SteamVRAppId}' then
+ begin
+ if CurPath <> '' then
+ begin
+ Result := CurPath;
+ Exit;
+ end;
+ end;
+ end;
+ end;
+end;
+
+{ ============================================================ }
+{ Steam path detection }
+{ ============================================================ }
+
+function DetectSteamPath: String;
+var
+ S: String;
+begin
+ Result := '';
+ if RegQueryStringValue(HKCU, 'Software\Valve\Steam', 'SteamPath', S) then
+ Result := NormalizeSlashes(S);
+end;
+
+function DetectSteamVRPath: String;
+var
+ Lib, Candidate: String;
+begin
+ Result := '';
+ if SteamPath = '' then Exit;
+
+ Lib := FindSteamVRLibrary(PathCombine(SteamPath, 'steamapps\libraryfolders.vdf'));
+ if Lib <> '' then
+ begin
+ Candidate := PathCombine(Lib, 'steamapps\common\SteamVR');
+ if DirExists(Candidate) then
+ begin
+ Result := Candidate;
+ Exit;
+ end;
+ end;
+
+ Candidate := PathCombine(SteamPath, 'steamapps\common\SteamVR');
+ if DirExists(Candidate) then
+ Result := Candidate;
+end;
+
+{ ============================================================ }
+{ GitHub release lookup }
+{ ============================================================ }
+
+function ExtractJsonString(const Body, Field: String): String;
+var
+ Key: String;
+ P, Q1, Q2: Integer;
+ Out_: String;
+begin
+ Result := '';
+ Key := '"' + Field + '"';
+ P := Pos(Key, Body);
+ if P = 0 then Exit;
+ P := P + Length(Key);
+ while (P <= Length(Body)) and ((Body[P] = ' ') or (Body[P] = ':') or (Body[P] = #9)) do
+ P := P + 1;
+ if (P > Length(Body)) or (Body[P] <> '"') then Exit;
+ Q1 := P + 1;
+ Q2 := Q1;
+ while Q2 <= Length(Body) do
+ begin
+ if Body[Q2] = '\' then
+ begin
+ Q2 := Q2 + 2;
+ Continue;
+ end;
+ if Body[Q2] = '"' then Break;
+ Q2 := Q2 + 1;
+ end;
+ Out_ := Copy(Body, Q1, Q2 - Q1);
+ StringChangeEx(Out_, '\"', '"', True);
+ StringChangeEx(Out_, '\\', '\', True);
+ StringChangeEx(Out_, '\n', #10, True);
+ Result := Out_;
+end;
+
+procedure FetchReleaseInfo(const Endpoint: String; out Tag, Title: String);
+var
+ WinHttp: Variant;
+ Body: String;
+ Status: Integer;
+begin
+ Tag := '';
+ Title := '';
+ LogLine('FetchReleaseInfo: GET ' + Endpoint);
+ try
+ WinHttp := CreateOleObject('WinHttp.WinHttpRequest.5.1');
+ WinHttp.SetTimeouts(10000, 10000, 10000, 10000);
+ WinHttp.Open('GET', 'https://api.github.com/repos/oneup03/VRto3D' + Endpoint, False);
+ WinHttp.SetRequestHeader('User-Agent', 'VRto3D-Installer');
+ WinHttp.SetRequestHeader('Accept', 'application/vnd.github+json');
+ WinHttp.Send('');
+ Status := WinHttp.Status;
+ LogLine('FetchReleaseInfo: HTTP ' + IntToStr(Status));
+ if Status = 200 then
+ begin
+ Body := WinHttp.ResponseText;
+ Tag := ExtractJsonString(Body, 'tag_name');
+ Title := ExtractJsonString(Body, 'name');
+ LogLine('FetchReleaseInfo: tag="' + Tag + '" title="' + Title + '"');
+ end;
+ except
+ LogLine('FetchReleaseInfo error: ' + GetExceptionMessage);
+ end;
+end;
+
+procedure FetchLatestRelease;
+begin
+ FetchReleaseInfo('/releases/latest', ReleaseTag, ReleaseTitle);
+ FetchReleaseInfo('/releases/tags/latest', PreReleaseTag, PreReleaseTitle);
+end;
+
+{ ============================================================ }
+{ File / folder helpers }
+{ ============================================================ }
+
+function DeleteFolderRecursive(const Path: String): Boolean;
+begin
+ Result := True;
+ if not DirExists(Path) then Exit;
+ Result := DelTree(Path, True, True, True);
+ if Result then
+ LogLine('Deleted folder: ' + Path)
+ else
+ LogLine('Failed to delete folder: ' + Path);
+end;
+
+function DeleteFileLogged(const Path: String): Boolean;
+begin
+ Result := True;
+ if not FileExists(Path) then Exit;
+ Result := DeleteFile(Path);
+ if Result then
+ LogLine('Deleted file: ' + Path)
+ else
+ LogLine('Failed to delete file: ' + Path);
+end;
+
+{ Copy Src directory tree to Dest (Dest's parent must exist). }
+function CopyFolderRecursive(const Src, Dest: String): Boolean;
+var
+ ResultCode: Integer;
+ Cmd: String;
+begin
+ ForceDirectories(ExtractFilePath(RemoveBackslashUnlessRoot(Dest)));
+ Cmd := '/C xcopy "' + Src + '" "' + Dest + '" /E /I /Y /Q';
+ if not Exec(ExpandConstant('{cmd}'), Cmd, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
+ begin
+ LogLine('Exec(xcopy) failed for ' + Src + ' -> ' + Dest);
+ Result := False;
+ Exit;
+ end;
+ Result := DirExists(Dest);
+ if Result then
+ LogLine('Copied: ' + Src + ' -> ' + Dest)
+ else
+ LogLine('xcopy returned ' + IntToStr(ResultCode) + ' but dest missing: ' + Dest);
+end;
+
+{ Conservative ReShade detection - look for the literal "ReShade" byte string anywhere
+ in the file content. Reliable for ReShade DLLs (which embed it in their .rsrc data)
+ and won't match Valve's own dxgi/d3d* DLLs. }
+function IsReShadeDll(const Path: String): Boolean;
+var
+ Raw: AnsiString;
+ I, N: Integer;
+begin
+ Result := False;
+ if not FileExists(Path) then Exit;
+ if not LoadStringFromFile(Path, Raw) then Exit;
+ N := Length(Raw);
+ if N < 7 then Exit;
+ for I := 1 to N - 6 do
+ begin
+ if (Raw[I] = 'R') and
+ (Raw[I + 1] = 'e') and
+ (Raw[I + 2] = 'S') and
+ (Raw[I + 3] = 'h') and
+ (Raw[I + 4] = 'a') and
+ (Raw[I + 5] = 'd') and
+ (Raw[I + 6] = 'e') then
+ begin
+ Result := True;
+ Exit;
+ end;
+ end;
+end;
+
+function InstallerDir: String;
+begin
+ Result := ExtractFilePath(ExpandConstant('{srcexe}'));
+end;
+
+{ Look for a local VRto3D zip next to the installer .exe (either case variant). }
+function FindLocalVRto3DZip: String;
+var
+ Candidate: String;
+begin
+ Result := '';
+ Candidate := PathCombine(InstallerDir, 'vrto3d.zip');
+ if FileExists(Candidate) then
+ begin
+ Result := Candidate;
+ Exit;
+ end;
+ Candidate := PathCombine(InstallerDir, 'VRto3D.zip');
+ if FileExists(Candidate) then
+ Result := Candidate;
+end;
+
+{ In-place text patch on a small ASCII file. No-op (file left untouched) when the
+ pattern isn't present, so these are safe to keep even after WibbleWobble drops
+ the interactive prompts on its side. }
+procedure PatchTextFile(const Path, Find, Replace: String);
+var
+ Raw: AnsiString;
+ S: String;
+ Count: Integer;
+begin
+ if not FileExists(Path) then
+ begin
+ LogLine('PatchTextFile: file not present, skipping: ' + Path);
+ Exit;
+ end;
+ if not LoadStringFromFile(Path, Raw) then
+ begin
+ LogLine('PatchTextFile: load failed for ' + Path);
+ Exit;
+ end;
+ S := String(Raw);
+ Count := StringChangeEx(S, Find, Replace, True);
+ if Count = 0 then
+ begin
+ LogLine('PatchTextFile: pattern not found, left as-is: ' + Path);
+ Exit;
+ end;
+ if SaveStringToFile(Path, AnsiString(S), False) then
+ LogLine('PatchTextFile: applied ' + IntToStr(Count) + ' change(s) in ' + Path)
+ else
+ LogLine('PatchTextFile: save failed for ' + Path);
+end;
+
+{ ============================================================ }
+{ Download / extract }
+{ ============================================================ }
+
+function ResolveZip(const Url, FileName: String; out ZipPath: String): Boolean;
+var
+ TmpZip, LocalZip: String;
+begin
+ Result := False;
+ TmpZip := ExpandConstant('{tmp}\') + FileName;
+
+ try
+ DownloadTemporaryFile(Url, FileName, '', nil);
+ if FileExists(TmpZip) then
+ begin
+ ZipPath := TmpZip;
+ LogLine('Downloaded ' + FileName);
+ Result := True;
+ Exit;
+ end;
+ except
+ LogLine('Download failed for ' + FileName + ': ' + GetExceptionMessage);
+ end;
+
+ if LowerCase(FileName) = 'vrto3d.zip' then
+ LocalZip := FindLocalVRto3DZip
+ else
+ begin
+ LocalZip := PathCombine(InstallerDir, FileName);
+ if not FileExists(LocalZip) then
+ LocalZip := '';
+ end;
+
+ if LocalZip <> '' then
+ begin
+ ZipPath := LocalZip;
+ LogLine('Using local zip: ' + LocalZip);
+ Result := True;
+ Exit;
+ end;
+
+ LogLine('No source available for ' + FileName + ' (online + offline both failed).');
+end;
+
+{ ============================================================ }
+{ Tasks }
+{ ============================================================ }
+
+procedure TaskInstallVRto3D;
+var
+ ZipPath, ExtractDir, SrcDir, DestDir, ChannelUrl, Channel, LocalZip: String;
+begin
+ if SteamVRPath = '' then
+ begin
+ LogLine('TaskInstallVRto3D: SteamVR path not set, skipping.');
+ Exit;
+ end;
+
+ if WizardIsTaskSelected('install\local') then
+ begin
+ Channel := 'local';
+ LocalZip := FindLocalVRto3DZip;
+ if LocalZip = '' then
+ begin
+ LogLine('TaskInstallVRto3D: local zip not found next to installer.');
+ MsgBox(
+ 'No vrto3d.zip / VRto3D.zip found next to this installer.' #13#10 #13#10 +
+ 'Place the zip next to VRto3D-Installer.exe and re-run, or pick a different channel.',
+ mbError, MB_OK);
+ Exit;
+ end;
+ ZipPath := LocalZip;
+ LogLine('TaskInstallVRto3D: channel=local using ' + ZipPath);
+ end
+ else
+ begin
+ if WizardIsTaskSelected('install\prerelease') then
+ begin
+ ChannelUrl := '{#VRto3DPreReleaseUrl}';
+ Channel := 'pre-release';
+ end
+ else
+ begin
+ ChannelUrl := '{#VRto3DReleaseUrl}';
+ Channel := 'release';
+ end;
+ LogLine('TaskInstallVRto3D: channel=' + Channel + ' url=' + ChannelUrl);
+
+ if not ResolveZip(ChannelUrl, 'vrto3d.zip', ZipPath) then
+ begin
+ MsgBox(
+ 'Could not download vrto3d.zip and no local copy was found next to the installer.' #13#10 #13#10 +
+ 'Reconnect to the internet, or download vrto3d.zip from:' #13#10 +
+ ' https://github.com/oneup03/VRto3D/releases/latest' #13#10 +
+ 'and place it next to this installer, then re-run.',
+ mbError, MB_OK);
+ Exit;
+ end;
+ end;
+
+ ExtractDir := ExpandConstant('{tmp}\vrto3d_extract');
+ ForceDirectories(ExtractDir);
+ ExtractArchive(ZipPath, ExtractDir, '', True, nil);
+
+ SrcDir := PathCombine(ExtractDir, 'drivers\vrto3d');
+ if not DirExists(SrcDir) then
+ SrcDir := PathCombine(ExtractDir, 'vrto3d');
+ if not DirExists(SrcDir) then
+ begin
+ LogLine('TaskInstallVRto3D: vrto3d folder not found in zip extracted at ' + ExtractDir);
+ MsgBox('The vrto3d.zip archive does not contain a vrto3d folder. Aborting install.', mbError, MB_OK);
+ Exit;
+ end;
+
+ DestDir := PathCombine(SteamVRPath, 'drivers\vrto3d');
+ if DirExists(DestDir) then
+ DeleteFolderRecursive(DestDir);
+ ForceDirectories(PathCombine(SteamVRPath, 'drivers'));
+ CopyFolderRecursive(SrcDir, DestDir);
+ LogLine('Installed VRto3D driver to ' + DestDir);
+end;
+
+procedure DeleteReShadePresetGlob(const Bin64: String);
+var
+ Rec: TFindRec;
+ Found: Boolean;
+begin
+ Found := FindFirst(PathCombine(Bin64, 'ReShadePreset*.ini'), Rec);
+ if not Found then Exit;
+ try
+ repeat
+ DeleteFileLogged(PathCombine(Bin64, Rec.Name));
+ until not FindNext(Rec);
+ finally
+ FindClose(Rec);
+ end;
+end;
+
+procedure TaskCleanReshade;
+var
+ Bin64, P: String;
+ I: Integer;
+ Dlls: array[0..5] of String;
+ Files: array[0..2] of String;
+ Folders: array[0..2] of String;
+begin
+ if SteamVRPath = '' then
+ begin
+ LogLine('TaskCleanReshade: SteamVR path not set, skipping.');
+ Exit;
+ end;
+ Bin64 := PathCombine(SteamVRPath, 'bin\win64');
+ if not DirExists(Bin64) then
+ begin
+ LogLine('TaskCleanReshade: ' + Bin64 + ' does not exist, skipping.');
+ Exit;
+ end;
+
+ Dlls[0] := 'dxgi.dll';
+ Dlls[1] := 'd3d9.dll';
+ Dlls[2] := 'd3d10.dll';
+ Dlls[3] := 'd3d11.dll';
+ Dlls[4] := 'd3d12.dll';
+ Dlls[5] := 'opengl32.dll';
+ for I := 0 to 5 do
+ begin
+ P := PathCombine(Bin64, Dlls[I]);
+ if FileExists(P) and IsReShadeDll(P) then
+ DeleteFileLogged(P);
+ end;
+
+ Files[0] := 'ReShade.ini';
+ Files[1] := 'ReShade.log';
+ Files[2] := '3DToElse.fx';
+ for I := 0 to 2 do
+ DeleteFileLogged(PathCombine(Bin64, Files[I]));
+
+ DeleteReShadePresetGlob(Bin64);
+
+ Folders[0] := 'reshade-shaders';
+ Folders[1] := 'reshade-presets';
+ Folders[2] := 'reshade-cache';
+ for I := 0 to 2 do
+ DeleteFolderRecursive(PathCombine(Bin64, Folders[I]));
+end;
+
+procedure TaskCleanDrivers;
+var
+ DriversRoot, VrSettings, ItemPath, NameLower: String;
+ Rec: TFindRec;
+ Allow: array[0..11] of String;
+ Keep, Found: Boolean;
+ I: Integer;
+begin
+ if SteamVRPath = '' then
+ begin
+ LogLine('TaskCleanDrivers: SteamVR path not set, skipping.');
+ Exit;
+ end;
+ DriversRoot := PathCombine(SteamVRPath, 'drivers');
+ if not DirExists(DriversRoot) then
+ begin
+ LogLine('TaskCleanDrivers: ' + DriversRoot + ' does not exist, skipping.');
+ Exit;
+ end;
+
+ Allow[0] := 'gamepad';
+ Allow[1] := 'htc';
+ Allow[2] := 'indexcontroller';
+ Allow[3] := 'indexhmd';
+ Allow[4] := 'lighthouse';
+ Allow[5] := 'null';
+ Allow[6] := 'oculus';
+ Allow[7] := 'oculus_legacy';
+ Allow[8] := 'prism';
+ Allow[9] := 'tundra_labs';
+ Allow[10] := 'vrlink';
+ Allow[11] := 'vrto3d';
+
+ Found := FindFirst(PathCombine(DriversRoot, '*'), Rec);
+ if Found then
+ try
+ repeat
+ if (Rec.Name = '.') or (Rec.Name = '..') then Continue;
+ if (Rec.Attributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then Continue;
+ NameLower := LowerCase(Rec.Name);
+ Keep := False;
+ for I := 0 to 11 do
+ if NameLower = Allow[I] then
+ begin
+ Keep := True;
+ Break;
+ end;
+ if not Keep then
+ begin
+ ItemPath := PathCombine(DriversRoot, Rec.Name);
+ DeleteFolderRecursive(ItemPath);
+ end;
+ until not FindNext(Rec);
+ finally
+ FindClose(Rec);
+ end;
+
+ if SteamPath <> '' then
+ begin
+ VrSettings := PathCombine(SteamPath, 'config\steamvr.vrsettings');
+ DeleteFileLogged(VrSettings);
+ end;
+end;
+
+procedure TaskInstallWibbleWobble;
+var
+ ZipPath, ExtractDir, SrcDir, DestDir, RegBat, LegacyDriver: String;
+ ResultCode: Integer;
+begin
+ if SteamVRPath <> '' then
+ begin
+ LegacyDriver := PathCombine(SteamVRPath, 'drivers\WibbleWobbleVR');
+ if DirExists(LegacyDriver) then
+ begin
+ LogLine('Removing legacy WibbleWobbleVR SteamVR driver: ' + LegacyDriver);
+ DeleteFolderRecursive(LegacyDriver);
+ end;
+ end;
+
+ if not ResolveZip('{#WWZipUrl}', 'WibbleWobbleBeta7.2.zip', ZipPath) then
+ begin
+ MsgBox(
+ 'Could not download WibbleWobbleBeta7.2.zip and no local copy was found next to the installer.' #13#10 #13#10 +
+ 'Skipping WibbleWobble install. The legacy WibbleWobbleVR driver (if present) was still removed.',
+ mbInformation, MB_OK);
+ Exit;
+ end;
+
+ ExtractDir := ExpandConstant('{tmp}\ww_extract');
+ ForceDirectories(ExtractDir);
+ ExtractArchive(ZipPath, ExtractDir, '', True, nil);
+
+ SrcDir := PathCombine(ExtractDir, 'WibbleWobble');
+ if not DirExists(SrcDir) then
+ begin
+ LogLine('TaskInstallWibbleWobble: WibbleWobble folder not found inside zip at ' + ExtractDir);
+ MsgBox('The WibbleWobble zip did not contain a WibbleWobble folder.', mbError, MB_OK);
+ Exit;
+ end;
+
+ if WWPage = nil then
+ DestDir := 'C:\Apps\WibbleWobble'
+ else
+ DestDir := RemoveBackslashUnlessRoot(Trim(WWPage.Values[0]));
+
+ if DirExists(DestDir) then
+ begin
+ if MsgBox(
+ 'A WibbleWobble folder already exists at:' #13#10 + DestDir + #13#10 #13#10 +
+ 'Overwrite it?',
+ mbConfirmation, MB_YESNO) <> IDYES then
+ begin
+ LogLine('User declined WibbleWobble overwrite at ' + DestDir);
+ Exit;
+ end;
+ DeleteFolderRecursive(DestDir);
+ end;
+
+ ForceDirectories(DestDir);
+ CopyFolderRecursive(SrcDir, DestDir);
+
+ RegBat := PathCombine(DestDir, 'WibbleWobbleClient\Register.bat');
+ if not FileExists(RegBat) then
+ begin
+ LogLine('TaskInstallWibbleWobble: Register.bat not found at ' + RegBat);
+ MsgBox(
+ 'Register.bat not found at:' #13#10 + RegBat + #13#10 #13#10 +
+ 'WibbleWobble files were copied but registration was skipped.',
+ mbError, MB_OK);
+ Exit;
+ end;
+
+ { Strip the interactive prompts so Register.bat and its child PowerShell run
+ unattended. Each call is a no-op if WibbleWobble already ships without the
+ prompt, so keeping them in cannot break a fixed bat/ps1. }
+ PatchTextFile(RegBat, #13#10 + 'pause', '');
+ PatchTextFile(RegBat, '"%install_path%\"' + #13#10, '"%install_path%\" /f' + #13#10);
+ PatchTextFile(PathCombine(DestDir, 'WibbleWobbleClient\SetRealtimePrivilege.ps1'),
+ 'Read-Host "Press Enter to exit"', '');
+
+ LogLine('Running Register.bat (installer is already elevated): ' + RegBat);
+ if not Exec(ExpandConstant('{cmd}'),
+ '/C ""' + RegBat + '""',
+ ExtractFilePath(RegBat),
+ SW_SHOWNORMAL, ewWaitUntilTerminated, ResultCode) then
+ begin
+ LogLine('Exec failed for Register.bat');
+ MsgBox(
+ 'Failed to launch Register.bat. You can run it manually as administrator from:' #13#10 +
+ RegBat,
+ mbError, MB_OK);
+ end
+ else
+ LogLine('Register.bat exited with code ' + IntToStr(ResultCode));
+end;
+
+procedure TaskAddLeiaSRPath;
+var
+ LeiaPath, CurrentPath: String;
+ ResultCode: Integer;
+begin
+ LeiaPath := 'C:\Program Files\LeiaSR\Platform\bin';
+ CurrentPath := GetEnv('PATH');
+ if Pos(LowerCase(LeiaPath), LowerCase(CurrentPath)) > 0 then
+ begin
+ LogLine('TaskAddLeiaSRPath: ' + LeiaPath + ' already on PATH, skipping.');
+ Exit;
+ end;
+ LogLine('TaskAddLeiaSRPath: prepending ' + LeiaPath + ' to user PATH (reboot required)');
+ if not Exec(ExpandConstant('{cmd}'),
+ '/C setx PATH "' + LeiaPath + ';%PATH%"',
+ '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
+ LogLine('setx exec failed')
+ else
+ LogLine('setx exited with code ' + IntToStr(ResultCode));
+end;
+
+procedure TaskLaunchSteamVR;
+var
+ ResultCode: Integer;
+begin
+ LogLine('Launching SteamVR via steam://run/{#SteamVRAppId}');
+ ShellExec('open', 'steam://run/{#SteamVRAppId}', '', '', SW_SHOWNORMAL, ewNoWait, ResultCode);
+end;
+
+{ ============================================================ }
+{ Wizard hooks }
+{ ============================================================ }
+
+function InitializeSetup: Boolean;
+var
+ Resp: Integer;
+ ResultCode: Integer;
+ LogDir: String;
+begin
+ Result := True;
+ SteamPath := DetectSteamPath;
+
+ if SteamPath <> '' then
+ begin
+ LogDir := PathCombine(SteamPath, 'logs');
+ ForceDirectories(LogDir);
+ LogPath := PathCombine(LogDir, 'VRto3D-Installer.log');
+ end
+ else
+ LogPath := ExpandConstant('{tmp}\VRto3D-Installer.log');
+
+ LogLine('--- VRto3D Installer started ---');
+ LogLine('Steam path: ' + SteamPath);
+ if SteamPath = '' then
+ begin
+ Resp := MsgBox(
+ 'Steam was not found on this machine.' #13#10 #13#10 +
+ 'Install Steam from https://store.steampowered.com/about/ , then re-run this installer.' #13#10 #13#10 +
+ 'Continue anyway and pick a SteamVR folder manually?',
+ mbConfirmation, MB_YESNO);
+ if Resp <> IDYES then
+ begin
+ Result := False;
+ Exit;
+ end;
+ end;
+
+ SteamVRPath := DetectSteamVRPath;
+ if SteamVRPath = '' then
+ begin
+ Resp := MsgBox(
+ 'SteamVR was not detected in any Steam library folder.' #13#10 #13#10 +
+ 'Yes - open Steam''s install page for SteamVR (re-run this installer afterwards).' #13#10 +
+ 'No - continue and pick the SteamVR folder manually.' #13#10 +
+ 'Cancel - abort the installer.',
+ mbConfirmation, MB_YESNOCANCEL);
+ if Resp = IDYES then
+ begin
+ ShellExec('open', 'steam://install/{#SteamVRAppId}', '', '', SW_SHOWNORMAL, ewNoWait, ResultCode);
+ Result := False;
+ Exit;
+ end
+ else if Resp = IDCANCEL then
+ begin
+ Result := False;
+ Exit;
+ end;
+ end;
+
+ FetchLatestRelease;
+
+ if FindLocalVRto3DZip <> '' then
+ LocalZipNote := #13#10 + 'Local zip detected: ' + FindLocalVRto3DZip
+ else
+ LocalZipNote := '';
+end;
+
+procedure InitializeWizard;
+var
+ WelcomeText: String;
+begin
+ if ReleaseTag <> '' then
+ WelcomeText := 'Latest release: ' + ReleaseTag
+ else
+ WelcomeText := 'Latest release: unknown';
+ if PreReleaseTitle <> '' then
+ WelcomeText := WelcomeText + #13#10 + 'Latest pre-release: ' + PreReleaseTitle
+ else if PreReleaseTag <> '' then
+ WelcomeText := WelcomeText + #13#10 + 'Latest pre-release: ' + PreReleaseTag
+ else
+ WelcomeText := WelcomeText + #13#10 + 'Latest pre-release: unknown';
+ WelcomeText := WelcomeText + LocalZipNote;
+
+ WizardForm.WelcomeLabel2.Caption :=
+ WizardForm.WelcomeLabel2.Caption + #13#10 + #13#10 + WelcomeText;
+
+ SteamPage := CreateInputDirPage(wpWelcome,
+ 'SteamVR Location',
+ 'Confirm where SteamVR is installed.',
+ 'VRto3D will be installed into the drivers subfolder of this path.',
+ False, '');
+ SteamPage.Add('SteamVR root folder:');
+ if SteamVRPath <> '' then
+ SteamPage.Values[0] := SteamVRPath
+ else if SteamPath <> '' then
+ SteamPage.Values[0] := PathCombine(SteamPath, 'steamapps\common\SteamVR')
+ else
+ SteamPage.Values[0] := 'C:\Program Files (x86)\Steam\steamapps\common\SteamVR';
+
+ WWPage := CreateInputDirPage(wpSelectTasks,
+ 'WibbleWobble Location',
+ 'Choose where WibbleWobble will be installed.',
+ 'WibbleWobble files will be extracted directly into this folder.',
+ False, '');
+ WWPage.Add('WibbleWobble install folder:');
+ WWPage.Values[0] := 'C:\Apps\WibbleWobble';
+end;
+
+function ShouldSkipPage(PageID: Integer): Boolean;
+begin
+ Result := False;
+ if (WWPage <> nil) and (PageID = WWPage.ID) then
+ Result := not WizardIsTaskSelected('wibblewobble');
+end;
+
+function NextButtonClick(CurPageID: Integer): Boolean;
+begin
+ Result := True;
+ if (SteamPage <> nil) and (CurPageID = SteamPage.ID) then
+ begin
+ SteamVRPath := RemoveBackslashUnlessRoot(Trim(SteamPage.Values[0]));
+ if SteamVRPath = '' then
+ begin
+ MsgBox('Please specify the SteamVR folder.', mbError, MB_OK);
+ Result := False;
+ Exit;
+ end;
+ if not DirExists(SteamVRPath) then
+ begin
+ if MsgBox('The folder' #13#10 + SteamVRPath + #13#10 + 'does not exist. Continue anyway?',
+ mbConfirmation, MB_YESNO) <> IDYES then
+ begin
+ Result := False;
+ Exit;
+ end;
+ end;
+ LogLine('SteamVR root selected: ' + SteamVRPath);
+ end;
+end;
+
+function UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo,
+ MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String;
+var
+ S: String;
+begin
+ S := 'SteamVR root:' + NewLine + Space + SteamVRPath + NewLine + NewLine;
+ if WizardIsTaskSelected('install') then
+ begin
+ S := S + 'Install / update VRto3D driver:' + NewLine +
+ Space + SteamVRPath + '\drivers\vrto3d' + NewLine;
+ if WizardIsTaskSelected('install\local') then
+ S := S + Space + 'Channel: local zip next to installer' + NewLine
+ else if WizardIsTaskSelected('install\prerelease') then
+ S := S + Space + 'Channel: pre-release (' + PreReleaseTag + ')' + NewLine
+ else
+ S := S + Space + 'Channel: official release (' + ReleaseTag + ')' + NewLine;
+ S := S + NewLine;
+ end;
+ if WizardIsTaskSelected('wibblewobble') and (WWPage <> nil) then
+ S := S + 'Install WibbleWobble:' + NewLine +
+ Space + WWPage.Values[0] + NewLine +
+ Space + '(legacy ' + SteamVRPath + '\drivers\WibbleWobbleVR will be removed if present)' + NewLine + NewLine;
+ if WizardIsTaskSelected('cleanreshade') then
+ S := S + 'Remove ReShade from:' + NewLine +
+ Space + SteamVRPath + '\bin\win64' + NewLine +
+ Space + '(only files identified as ReShade will be deleted)' + NewLine + NewLine;
+ if WizardIsTaskSelected('cleandrivers') then
+ begin
+ S := S + 'Remove third-party SteamVR drivers under:' + NewLine +
+ Space + SteamVRPath + '\drivers' + NewLine +
+ Space + '(default Valve drivers and vrto3d are kept)' + NewLine;
+ if SteamPath <> '' then
+ S := S + Space + 'Also delete: ' + SteamPath + '\config\steamvr.vrsettings' + NewLine;
+ S := S + NewLine;
+ end;
+ if WizardIsTaskSelected('addleiasrpath') then
+ S := S + 'Fix LeiaSR library loading (requires restart):' + NewLine +
+ Space + 'Add C:\Program Files\LeiaSR\Platform\bin to user PATH' + NewLine + NewLine;
+ if WizardIsTaskSelected('launchsteamvr') then
+ S := S + 'Launch SteamVR after install completes' + NewLine;
+ Result := S;
+end;
+
+procedure CurStepChanged(CurStep: TSetupStep);
+begin
+ if CurStep <> ssInstall then Exit;
+
+ WizardForm.StatusLabel.Caption := 'Running selected tasks...';
+
+ if WizardIsTaskSelected('install') then
+ begin
+ WizardForm.StatusLabel.Caption := 'Installing VRto3D driver...';
+ try
+ TaskInstallVRto3D;
+ except
+ LogLine('install task error: ' + GetExceptionMessage);
+ end;
+ end;
+
+ if WizardIsTaskSelected('wibblewobble') then
+ begin
+ WizardForm.StatusLabel.Caption := 'Installing WibbleWobble...';
+ try
+ TaskInstallWibbleWobble;
+ except
+ LogLine('wibblewobble task error: ' + GetExceptionMessage);
+ end;
+ end;
+
+ if WizardIsTaskSelected('cleanreshade') then
+ begin
+ WizardForm.StatusLabel.Caption := 'Cleaning up legacy ReShade...';
+ try
+ TaskCleanReshade;
+ except
+ LogLine('cleanreshade task error: ' + GetExceptionMessage);
+ end;
+ end;
+
+ if WizardIsTaskSelected('cleandrivers') then
+ begin
+ WizardForm.StatusLabel.Caption := 'Removing third-party SteamVR drivers...';
+ try
+ TaskCleanDrivers;
+ except
+ LogLine('cleandrivers task error: ' + GetExceptionMessage);
+ end;
+ end;
+
+ if WizardIsTaskSelected('addleiasrpath') then
+ begin
+ WizardForm.StatusLabel.Caption := 'Adding LeiaSR Platform\bin to PATH...';
+ try
+ TaskAddLeiaSRPath;
+ except
+ LogLine('addleiasrpath task error: ' + GetExceptionMessage);
+ end;
+ end;
+end;
+
+procedure CurPageChanged(CurPageID: Integer);
+begin
+ if CurPageID = wpFinished then
+ begin
+ if WizardIsTaskSelected('launchsteamvr') then
+ TaskLaunchSteamVR;
+ end;
+end;
+
+procedure DeinitializeSetup;
+begin
+ LogLine('--- VRto3D Installer finished ---');
+end;
diff --git a/installer/install.sh b/installer/install.sh
new file mode 100644
index 00000000..84868abe
--- /dev/null
+++ b/installer/install.sh
@@ -0,0 +1,200 @@
+#!/usr/bin/env bash
+# VRto3D Linux installer: copies the driver into SteamVR's drivers/ folder
+# (auto-discovered, same layout as the Windows install) and registers it in
+# steamvr.vrsettings. Ships at the root of vrto3d-linux64.tar.gz next to the
+# drivers/ payload. No dependencies beyond coreutils + awk.
+#
+# Usage: ./install.sh [--uninstall] [--steam ]
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+UNINSTALL=0
+STEAM_ROOT=""
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ --uninstall) UNINSTALL=1 ;;
+ --steam) STEAM_ROOT="${2:?--steam needs a path}"; shift ;;
+ -h|--help)
+ echo "Usage: $0 [--uninstall] [--steam ]"
+ exit 0 ;;
+ *) echo "Unknown option: $1" >&2; exit 1 ;;
+ esac
+ shift
+done
+
+die() { echo "error: $*" >&2; exit 1; }
+
+# --- locate the Steam root ---------------------------------------------------
+if [ -z "$STEAM_ROOT" ]; then
+ for cand in \
+ "$HOME/.local/share/Steam" \
+ "$HOME/.steam/steam" \
+ "$HOME/.steam/root" \
+ "$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam"; do
+ if [ -d "$cand/steamapps" ]; then
+ STEAM_ROOT="$(readlink -f "$cand")"
+ break
+ fi
+ done
+fi
+[ -n "$STEAM_ROOT" ] && [ -d "$STEAM_ROOT/steamapps" ] \
+ || die "Steam not found - pass its root with --steam "
+
+# --- locate SteamVR (may live in any Steam library folder) -------------------
+STEAMVR=""
+LIBS="$STEAM_ROOT"
+VDF="$STEAM_ROOT/steamapps/libraryfolders.vdf"
+if [ -f "$VDF" ]; then
+ LIBS="$LIBS
+$(sed -n 's/^[[:space:]]*"path"[[:space:]]*"\(.*\)"[[:space:]]*$/\1/p' "$VDF")"
+fi
+while IFS= read -r lib; do
+ if [ -n "$lib" ] && [ -d "$lib/steamapps/common/SteamVR" ]; then
+ STEAMVR="$lib/steamapps/common/SteamVR"
+ break
+ fi
+done < "$VRSETTINGS" <<'EOF'
+{
+ "steamvr" : {
+ "requireHmd" : true,
+ "forcedDriver" : "vrto3d",
+ "activateMultipleDrivers" : true
+ }
+}
+EOF
+}
+
+# Replace/insert the three managed keys, preserving everything else.
+# Exits non-zero when the file shape is unexpected; caller falls back.
+patch_vrsettings() {
+ awk '
+ BEGIN { injected = 0; bad = 0; n = 0 }
+ # drop existing occurrences of the keys we manage
+ /^[[:space:]]*"(requireHmd|forcedDriver|activateMultipleDrivers)"[[:space:]]*:/ { next }
+ {
+ if (!injected && index($0, "\"steamvr\"") > 0) {
+ if ($0 ~ /"steamvr"[[:space:]]*:[[:space:]]*[{][[:space:]]*[}],?[[:space:]]*$/) {
+ # empty inline section: expand it
+ match($0, /^[[:space:]]*/); ind = substr($0, 1, RLENGTH)
+ tail = ""; if ($0 ~ /,[[:space:]]*$/) tail = ","
+ out[n++] = ind "\"steamvr\" : {"
+ out[n++] = ind " \"requireHmd\" : true,"
+ out[n++] = ind " \"forcedDriver\" : \"vrto3d\","
+ out[n++] = ind " \"activateMultipleDrivers\" : true"
+ out[n++] = ind "}" tail
+ injected = 1
+ next
+ }
+ if ($0 ~ /"steamvr"[[:space:]]*:[[:space:]]*[{][[:space:]]*$/) {
+ # multiline section: inject right after the opening brace
+ match($0, /^[[:space:]]*/); ind = substr($0, 1, RLENGTH)
+ out[n++] = $0
+ out[n++] = ind " \"requireHmd\" : true,"
+ out[n++] = ind " \"forcedDriver\" : \"vrto3d\","
+ out[n++] = ind " \"activateMultipleDrivers\" : true,"
+ injected = 1
+ next
+ }
+ bad = 1; exit # "steamvr" in a shape we do not understand
+ }
+ out[n++] = $0
+ }
+ END {
+ if (bad) exit 2
+ if (!injected) {
+ # no steamvr section: add one right after the top-level brace
+ if (n == 0 || out[0] !~ /^[[:space:]]*[{][[:space:]]*$/) exit 2
+ m = 0; new[m++] = out[0]
+ new[m++] = " \"steamvr\" : {"
+ new[m++] = " \"requireHmd\" : true,"
+ new[m++] = " \"forcedDriver\" : \"vrto3d\","
+ new[m++] = " \"activateMultipleDrivers\" : true"
+ new[m++] = " },"
+ for (i = 1; i < n; i++) new[m++] = out[i]
+ n = m
+ for (i = 0; i < n; i++) out[i] = new[i]
+ }
+ # drop commas left dangling before a closing brace/bracket
+ for (i = 0; i < n; i++) {
+ if (i + 1 < n && out[i] ~ /,[[:space:]]*$/ && out[i+1] ~ /^[[:space:]]*[]}]/)
+ sub(/,[[:space:]]*$/, "", out[i])
+ print out[i]
+ }
+ }' "$VRSETTINGS" > "$VRSETTINGS.tmp" || { rm -f "$VRSETTINGS.tmp"; return 1; }
+ mv "$VRSETTINGS.tmp" "$VRSETTINGS"
+}
+
+# Remove the keys this installer set (only when VRto3D is the forced driver).
+strip_vrsettings() {
+ grep -q '"forcedDriver"[[:space:]]*:[[:space:]]*"vrto3d"' "$VRSETTINGS" || return 0
+ awk '
+ /^[[:space:]]*"(requireHmd|forcedDriver)"[[:space:]]*:/ { next }
+ { out[n++] = $0 }
+ END {
+ for (i = 0; i < n; i++) {
+ if (i + 1 < n && out[i] ~ /,[[:space:]]*$/ && out[i+1] ~ /^[[:space:]]*[]}]/)
+ sub(/,[[:space:]]*$/, "", out[i])
+ print out[i]
+ }
+ }' "$VRSETTINGS" > "$VRSETTINGS.tmp" || { rm -f "$VRSETTINGS.tmp"; return 1; }
+ mv "$VRSETTINGS.tmp" "$VRSETTINGS"
+}
+
+# --- uninstall ----------------------------------------------------------------
+if [ "$UNINSTALL" = 1 ]; then
+ rm -rf "$DRIVER_DST"
+ [ -f "$VRSETTINGS" ] && strip_vrsettings
+ echo "VRto3D removed from $STEAMVR"
+ echo "(config/profiles left in $STEAM_ROOT/config/vrto3d - delete manually if unwanted)"
+ exit 0
+fi
+
+# --- install --------------------------------------------------------------------
+[ -d "$SCRIPT_DIR/drivers/vrto3d" ] || die "drivers/vrto3d not found next to install.sh"
+
+rm -rf "$DRIVER_DST"
+mkdir -p "$STEAMVR/drivers"
+cp -a "$SCRIPT_DIR/drivers/vrto3d" "$DRIVER_DST"
+
+# Register as the forced HMD driver (the settings the README documents).
+if [ -f "$VRSETTINGS" ]; then
+ cp "$VRSETTINGS" "$VRSETTINGS.bak"
+ if ! patch_vrsettings; then
+ echo "note: could not merge into steamvr.vrsettings - writing a fresh one" >&2
+ echo " (previous file kept at $VRSETTINGS.bak)" >&2
+ write_fresh_vrsettings
+ fi
+else
+ write_fresh_vrsettings
+fi
+
+echo "VRto3D installed to $DRIVER_DST"
+
+# Hotkeys/gamepad read /dev/input directly, which needs the input group.
+if ! id -nG | grep -qw input; then
+ echo
+ echo "NOTE: your user is not in the 'input' group - hotkeys and gamepad"
+ echo "input will not work until you run:"
+ echo " sudo usermod -aG input ${USER:-$(id -un)}"
+ echo "and log out and back in."
+fi
+
+echo
+echo "Done. Start SteamVR - the 3D output window should appear on your display."
+echo "Open the OSD with Ctrl + Home to pick display, output mode, and resolution."
diff --git a/tools/make_fp_edid.py b/tools/make_fp_edid.py
new file mode 100644
index 00000000..d3bf4441
--- /dev/null
+++ b/tools/make_fp_edid.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python3
+"""Generate EDID firmware binaries for HDMI 1.4 frame-packed timings.
+
+For Wayland sessions (no runtime modelines) or displays whose EDID doesn't
+advertise the frame-packed timing, load one of these via the kernel:
+
+ sudo mkdir -p /usr/lib/firmware/edid # SteamOS: steamos-readonly disable first
+ sudo cp vrto3d_fp1080p60cvt.bin /usr/lib/firmware/edid/
+ # then add to kernel cmdline (connector name from `ls /sys/class/drm`):
+ drm.edid_firmware=HDMI-A-1:edid/vrto3d_fp1080p60cvt.bin
+
+On X11 sessions this is unnecessary — VRto3D applies the modeline at runtime
+via XRandR (see presenter/x11_modeline.cpp).
+
+Timings mirror s_frame_pack_timings in external/VRto3DLib/src/json_manager.cpp.
+"""
+import struct
+import sys
+
+# name, active_w, active_h, refresh, h_total, h_front, h_sync, h_back, v_total, v_front, v_sync, v_back
+TIMINGS = [
+ ("vrto3d_fp720p60", 1280, 1470, 60, 1650, 110, 40, 220, 1500, 5, 5, 20),
+ ("vrto3d_fp1080p24", 1920, 2205, 24, 2750, 638, 44, 148, 2250, 4, 5, 36),
+ ("vrto3d_fp1080p60", 1920, 2205, 60, 2750, 638, 44, 148, 2250, 4, 5, 36),
+ ("vrto3d_fp1080p60cvt", 1920, 2205, 60, 2080, 48, 32, 80, 2250, 4, 5, 36),
+]
+
+
+def dtd(active_w, active_h, refresh, h_total, h_front, h_sync, h_back, v_total, v_front, v_sync, v_back):
+ """18-byte EDID Detailed Timing Descriptor."""
+ pclk_10khz = round(h_total * v_total * refresh / 10000)
+ h_blank = h_total - active_w
+ v_blank = v_total - active_h
+ d = bytearray(18)
+ struct.pack_into("> 8) << 4) | (h_blank >> 8)
+ d[5] = active_h & 0xFF
+ d[6] = v_blank & 0xFF
+ d[7] = ((active_h >> 8) << 4) | (v_blank >> 8)
+ d[8] = h_front & 0xFF
+ d[9] = h_sync & 0xFF
+ d[10] = ((v_front & 0xF) << 4) | (v_sync & 0xF)
+ d[11] = (((h_front >> 8) & 3) << 6) | (((h_sync >> 8) & 3) << 4) \
+ | (((v_front >> 4) & 3) << 2) | ((v_sync >> 4) & 3)
+ # Image size unknown (0), no borders.
+ d[17] = 0x1E # digital separate sync, +hsync +vsync
+ return bytes(d)
+
+
+def display_name_descriptor(name):
+ payload = (name[:12] + "\n").ljust(13, " ").encode("ascii")
+ return b"\x00\x00\x00\xfc\x00" + payload
+
+
+def dummy_descriptor():
+ return b"\x00\x00\x00\x10\x00" + b"\x00" * 13
+
+
+def make_edid(name, timing):
+ e = bytearray(128)
+ e[0:8] = b"\x00\xff\xff\xff\xff\xff\xff\x00"
+ # Manufacturer "VTD", product 0x3D01
+ mfg = ((ord("V") - 64) << 10) | ((ord("T") - 64) << 5) | (ord("D") - 64)
+ struct.pack_into(">H", e, 8, mfg)
+ struct.pack_into("
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+class LowPassFilter {
+ float y_ = 0.0f, s_ = 0.0f, a_ = 0.5f;
+ bool initialized_ = false;
+
+public:
+ LowPassFilter() = default;
+ explicit LowPassFilter(float alpha, float initval = 0.0f)
+ : y_(initval), s_(initval)
+ , a_(alpha < 0.0001f ? 0.0001f : (alpha > 1.0f ? 1.0f : alpha)) {}
+
+ float filter(float value) {
+ if (initialized_) {
+ s_ = a_ * value + (1.0f - a_) * s_;
+ } else {
+ s_ = value;
+ initialized_ = true;
+ }
+ y_ = value;
+ return s_;
+ }
+
+ float filterWithAlpha(float value, float alpha) {
+ a_ = (alpha < 0.0001f) ? 0.0001f : (alpha > 1.0f ? 1.0f : alpha);
+ return filter(value);
+ }
+
+ bool hasLastRawValue() const { return initialized_; }
+ float lastRawValue() const { return y_; }
+ float lastFilteredValue() const { return s_; }
+ void reset() { initialized_ = false; y_ = 0.0f; s_ = 0.0f; }
+};
+
+class OneEuroFilter {
+ float freq_, mincutoff_, beta_, dcutoff_;
+ LowPassFilter x_, dx_;
+ float lasttime_ = -1.0f;
+
+ float alpha(float cutoff) const {
+ float te = 1.0f / freq_;
+ float tau = 1.0f / (2.0f * static_cast(M_PI) * cutoff);
+ return 1.0f / (1.0f + tau / te);
+ }
+
+public:
+ OneEuroFilter() : freq_(60.0f), mincutoff_(1.0f), beta_(0.0f), dcutoff_(1.0f) {
+ x_ = LowPassFilter(alpha(mincutoff_));
+ dx_ = LowPassFilter(alpha(dcutoff_));
+ }
+
+ OneEuroFilter(float freq, float mincutoff = 1.0f, float beta = 0.0f, float dcutoff = 1.0f)
+ : freq_(freq > 0.0f ? freq : 60.0f)
+ , mincutoff_(mincutoff > 0.0f ? mincutoff : 1.0f)
+ , beta_(beta)
+ , dcutoff_(dcutoff > 0.0f ? dcutoff : 1.0f)
+ {
+ x_ = LowPassFilter(alpha(mincutoff_));
+ dx_ = LowPassFilter(alpha(dcutoff_));
+ }
+
+ float filter(float value, float timestamp = -1.0f) {
+ if (lasttime_ >= 0.0f && timestamp >= 0.0f && timestamp > lasttime_) {
+ float dt = timestamp - lasttime_;
+ if (dt > 0.0f) freq_ = 1.0f / dt;
+ }
+ lasttime_ = timestamp;
+
+ float dvalue = x_.hasLastRawValue()
+ ? (value - x_.lastFilteredValue()) * freq_
+ : 0.0f;
+ float edvalue = dx_.filterWithAlpha(dvalue, alpha(dcutoff_));
+
+ float cutoff = mincutoff_ + beta_ * std::fabs(edvalue);
+
+ return x_.filterWithAlpha(value, alpha(cutoff));
+ }
+
+ void setFrequency(float f) { if (f > 0.0f) freq_ = f; }
+ void setMinCutoff(float mc) { if (mc > 0.0f) mincutoff_ = mc; }
+ void setBeta(float b) { beta_ = b; }
+ float getMinCutoff() const { return mincutoff_; }
+ float getBeta() const { return beta_; }
+ float getFrequency() const { return freq_; }
+ void reset() { x_.reset(); dx_.reset(); lasttime_ = -1.0f; }
+};
diff --git a/utils/vrmath/util_vrmath.vcxproj b/utils/vrmath/util_vrmath.vcxproj
index da1e5b6e..fadb22cb 100644
--- a/utils/vrmath/util_vrmath.vcxproj
+++ b/utils/vrmath/util_vrmath.vcxproj
@@ -146,6 +146,7 @@
+
diff --git a/vrto3d/CMakeLists.txt b/vrto3d/CMakeLists.txt
new file mode 100644
index 00000000..05605604
--- /dev/null
+++ b/vrto3d/CMakeLists.txt
@@ -0,0 +1,286 @@
+cmake_minimum_required(VERSION 3.21)
+project(vrto3d_driver LANGUAGES C CXX)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(ROOT "${CMAKE_CURRENT_SOURCE_DIR}/..")
+
+# Sources shared by both platforms.
+set(DRIVER_SOURCES
+ src/hmd_device_driver.cpp
+ src/hmd_driver_factory.cpp
+ src/device_provider.cpp
+ src/vr_recenter.cpp
+ src/osd/osd_menu.cpp
+)
+
+set(IMGUI_DIR "${ROOT}/external/imgui")
+
+if(WIN32)
+ list(APPEND DRIVER_SOURCES
+ src/dx11_renderer.cpp
+ src/screenshot.cpp
+ src/auto_depth.cpp
+ src/platform_win32.cpp
+ src/presenter/presenter_factory.cpp
+ src/presenter/window_presenter.cpp
+ src/presenter/leiasr_presenter.cpp
+ src/presenter/nvstereo_dx9_presenter.cpp
+ src/presenter/wibblewobble_presenter.cpp
+ src/presenter/display_timing_helper.cpp
+ src/osd/osd_renderer.cpp
+ src/osd/osd_input.cpp
+ )
+else()
+ list(APPEND DRIVER_SOURCES
+ src/platform_linux.cpp
+ src/vk/vk_context.cpp
+ src/vk/vk_renderer.cpp
+ src/vk/direct_mode_component_vk.cpp
+ src/presenter/vk_swapchain_util.cpp
+ src/presenter/vk_presenter_factory.cpp
+ src/presenter/wayland_presenter.cpp
+ src/presenter/x11_presenter.cpp
+ src/presenter/x11_modeline.cpp
+ src/presenter/wibblewobble_presenter_linux.cpp
+ src/presenter/wayland_protocols/xdg-shell-client-protocol.c
+ src/presenter/wayland_protocols/wlr-layer-shell-client-protocol.c
+ src/osd/osd_renderer_vk.cpp
+ src/osd/osd_input_linux.cpp
+ )
+endif()
+
+# Dear ImGui (vendored as submodule at external/imgui).
+# Compiled directly into the driver — no separate target.
+set(IMGUI_SOURCES
+ ${IMGUI_DIR}/imgui.cpp
+ ${IMGUI_DIR}/imgui_draw.cpp
+ ${IMGUI_DIR}/imgui_tables.cpp
+ ${IMGUI_DIR}/imgui_widgets.cpp
+)
+if(WIN32)
+ list(APPEND IMGUI_SOURCES ${IMGUI_DIR}/backends/imgui_impl_dx11.cpp)
+else()
+ list(APPEND IMGUI_SOURCES ${IMGUI_DIR}/backends/imgui_impl_vulkan.cpp)
+endif()
+list(APPEND DRIVER_SOURCES ${IMGUI_SOURCES})
+
+add_library(driver_vrto3d SHARED ${DRIVER_SOURCES})
+
+target_include_directories(driver_vrto3d PRIVATE
+ src
+ ${ROOT}/external/VRto3DLib/include
+ ${ROOT}/external/json/include
+ ${ROOT}/external/openvr/headers
+ ${ROOT}/utils/vrmath
+ ${IMGUI_DIR}
+ ${IMGUI_DIR}/backends
+)
+
+# VRto3DLib submodule — compile its sources directly. No transitive target yet.
+target_sources(driver_vrto3d PRIVATE
+ ${ROOT}/external/VRto3DLib/src/json_manager.cpp
+ ${ROOT}/external/VRto3DLib/src/key_names.cpp
+ ${ROOT}/external/VRto3DLib/src/app_id_mgr.cpp
+)
+
+if(WIN32)
+ target_include_directories(driver_vrto3d PRIVATE
+ ${ROOT}/external/SR-lib/SR-SDK-1.34.10/x64/include
+ ${ROOT}/external/SR-lib/SR-SDK-1.34.10/x64/third_party/OpenCV/include
+ ${ROOT}/external/nvapi
+ ${ROOT}/external/adl_sdk/include
+ )
+
+ # Pre-built openvr_api import lib ships with the submodule.
+ if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+ set(OPENVR_LIB_DIR "${ROOT}/external/openvr/lib/win64")
+ else()
+ set(OPENVR_LIB_DIR "${ROOT}/external/openvr/lib/win32")
+ endif()
+ target_link_libraries(driver_vrto3d PRIVATE
+ "${OPENVR_LIB_DIR}/openvr_api.lib"
+ "${ROOT}/external/SR-lib/SR-SDK-1.34.10/x64/lib/simulatedreality.lib"
+ "${ROOT}/external/SR-lib/SR-SDK-1.34.10/x64/lib/SimulatedRealityCore.lib"
+ "${ROOT}/external/SR-lib/SR-SDK-1.34.10/x64/lib/SimulatedRealityDirectX.lib"
+ "${ROOT}/external/SR-lib/SR-SDK-1.34.10/x64/lib/DimencoWeaving.lib"
+ "${ROOT}/external/SR-lib/SR-SDK-1.34.10/x64/third_party/OpenCV/lib/x64/opencv_core343.lib"
+ "${ROOT}/external/nvapi/amd64/nvapi64.lib"
+ delayimp
+ d3d11
+ dxgi
+ dxguid
+ d3dcompiler
+ d3d9
+ xinput9_1_0
+ ws2_32
+ user32
+ gdi32
+ urlmon
+ )
+ # Delay-load LeiaSR + OpenCV + NVAPI DLLs so the driver still loads when
+ # those runtimes aren't installed. The DLLs are only resolved when their
+ # first symbol is actually called (i.e., when LeiaSrPresenter::Init runs,
+ # or when NV3D-Lib calls NvAPI_Initialize inside CreateInterfaceDX11 —
+ # the lib SEH-guards that call so a missing nvapi64.dll fails cleanly).
+ target_link_options(driver_vrto3d PRIVATE
+ "/DELAYLOAD:simulatedreality.dll"
+ "/DELAYLOAD:SimulatedRealityCore.dll"
+ "/DELAYLOAD:SimulatedRealityDirectX.dll"
+ "/DELAYLOAD:DimencoWeaving.dll"
+ "/DELAYLOAD:opencv_core343.dll"
+ "/DELAYLOAD:nvapi64.dll"
+ "/DELAYLOAD:openvr_api.dll"
+ )
+ target_compile_definitions(driver_vrto3d PRIVATE
+ WIN32_LEAN_AND_MEAN
+ NOMINMAX
+ UNICODE
+ _UNICODE
+ )
+
+ # --- NV3D-Lib (external/NV3D-Lib, MSBuild-only upstream) -----------------
+ # Compiled here as a separate static target so its own vendored nvapi /
+ # json / minhook headers can't shadow VRto3D's copies (the driver uses
+ # external/nvapi and external/json/include directly). Only the DX11
+ # backend TU is built: the DX12/OGL/Vulkan backends are separate TUs whose
+ # CreateInterface* factories nothing references, and skipping them avoids
+ # a Vulkan-Headers dependency (only vulkan_backend.cpp includes it).
+ # MinHook is compiled INTO this target (from NV3D-Lib's own nested
+ # submodule) — it powers the lib's 3D Vision suppressor; VRto3D no longer
+ # carries a minhook copy of its own.
+ set(NV3DLIB_DIR "${ROOT}/external/NV3D-Lib/NV3DLib")
+ set(NV3DLIB_EXT "${ROOT}/external/NV3D-Lib/external")
+
+ # nvtimings_embedded.h is gitignored upstream and regenerated at build
+ # time. Generate into the submodule's src/ (the same location NV3D-Lib's
+ # own MSBuild pre-build writes): lightboost.cpp includes it with quotes,
+ # so the sibling-directory lookup always wins over include paths — a
+ # binary-dir copy could be shadowed by a stale sibling.
+ set(NV3D_EMBED_HDR "${NV3DLIB_DIR}/src/nvtimings_embedded.h")
+ add_custom_command(
+ OUTPUT "${NV3D_EMBED_HDR}"
+ COMMAND powershell.exe -NoProfile -ExecutionPolicy Bypass
+ -File "${ROOT}/external/NV3D-Lib/tools/embed_nvtimings.ps1"
+ -InputJson "${ROOT}/external/NV3D-Lib/nvtimings.json"
+ -OutputHeader "${NV3D_EMBED_HDR}"
+ DEPENDS "${ROOT}/external/NV3D-Lib/nvtimings.json"
+ "${ROOT}/external/NV3D-Lib/tools/embed_nvtimings.ps1"
+ COMMENT "Generating NV3D-Lib nvtimings_embedded.h"
+ )
+
+ set(NV3DLIB_SOURCES
+ ${NV3DLIB_DIR}/src/NV3D.cpp
+ ${NV3DLIB_DIR}/src/d3d9_presenter.cpp
+ ${NV3DLIB_DIR}/src/lightboost.cpp
+ ${NV3DLIB_DIR}/src/log.cpp
+ ${NV3DLIB_DIR}/src/nv_3dvision_suppressor.cpp
+ ${NV3DLIB_DIR}/src/present_window.cpp
+ ${NV3DLIB_DIR}/src/backends/dx11_backend.cpp
+ ${NV3D_EMBED_HDR}
+ ${NV3DLIB_EXT}/minhook/src/buffer.c
+ ${NV3DLIB_EXT}/minhook/src/hook.c
+ ${NV3DLIB_EXT}/minhook/src/trampoline.c
+ )
+ if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+ list(APPEND NV3DLIB_SOURCES ${NV3DLIB_EXT}/minhook/src/hde/hde64.c)
+ else()
+ list(APPEND NV3DLIB_SOURCES ${NV3DLIB_EXT}/minhook/src/hde/hde32.c)
+ endif()
+
+ add_library(nv3dlib STATIC ${NV3DLIB_SOURCES})
+ target_include_directories(nv3dlib
+ PUBLIC ${NV3DLIB_DIR}/include
+ PRIVATE ${NV3DLIB_DIR}/src
+ ${NV3DLIB_EXT}/nvapi
+ ${NV3DLIB_EXT}/minhook/include
+ ${NV3DLIB_EXT}/json/single_include
+ )
+ # Parity with NV3DLib.vcxproj (note: no UNICODE here — the lib uses
+ # explicit W-suffixed Win32 APIs).
+ target_compile_definitions(nv3dlib PRIVATE
+ WIN32_LEAN_AND_MEAN
+ NOMINMAX
+ _CRT_SECURE_NO_WARNINGS
+ )
+ set_property(TARGET nv3dlib PROPERTY CXX_STANDARD 17)
+ set_property(TARGET nv3dlib PROPERTY CXX_STANDARD_REQUIRED ON)
+ # nvapi64.lib / d3d9 / d3d11 / dxgi already on the driver's link line
+ # satisfy the archive's externals.
+ target_link_libraries(driver_vrto3d PRIVATE nv3dlib)
+else()
+ # Linux: the driver links no openvr import lib (the driver context comes
+ # in through HmdDriverFactory); everything else is system libraries.
+ target_sources(driver_vrto3d PRIVATE
+ ${ROOT}/external/VRto3DLib/src/linux_input.cpp
+ )
+ target_include_directories(driver_vrto3d PRIVATE
+ ${ROOT}/external/stb
+ src/presenter/wayland_protocols
+ /usr/include/libdrm
+ )
+ find_package(Threads REQUIRED)
+ # libopenvr_api.so is needed for the OpenVR *client* API used by
+ # vr_recenter.cpp (VR_Init/VR_GetGenericInterface); the driver-side
+ # interfaces come in through HmdDriverFactory and need no library.
+ target_link_libraries(driver_vrto3d PRIVATE
+ "${ROOT}/external/openvr/lib/linux64/libopenvr_api.so"
+ vulkan
+ X11
+ Xext
+ Xrandr
+ wayland-client
+ xkbcommon
+ dl
+ Threads::Threads
+ )
+ target_compile_options(driver_vrto3d PRIVATE -fvisibility=hidden -fPIC)
+ set_target_properties(driver_vrto3d PROPERTIES
+ PREFIX ""
+ VISIBILITY_INLINES_HIDDEN ON
+ BUILD_RPATH "$ORIGIN"
+ INSTALL_RPATH "$ORIGIN"
+ )
+endif()
+
+# Post-build: copy manifest + settings next to the binary and drop the driver
+# into SteamVR's drivers/ dir for quick iteration.
+set(OUT_DIR "${CMAKE_BINARY_DIR}/output/drivers/vrto3d")
+if(WIN32)
+ if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+ set(BIN_SUBDIR "bin/win64")
+ set(OPENVR_DLL "${ROOT}/external/openvr/bin/win64/openvr_api.dll")
+ else()
+ set(BIN_SUBDIR "bin/win32")
+ set(OPENVR_DLL "${ROOT}/external/openvr/bin/win32/openvr_api.dll")
+ endif()
+else()
+ set(BIN_SUBDIR "bin/linux64")
+endif()
+
+add_custom_command(TARGET driver_vrto3d POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E make_directory "${OUT_DIR}/${BIN_SUBDIR}"
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different $
+ "${OUT_DIR}/${BIN_SUBDIR}/$"
+ COMMAND ${CMAKE_COMMAND} -E copy_directory
+ "${CMAKE_CURRENT_SOURCE_DIR}/vrto3d"
+ "${OUT_DIR}"
+ COMMENT "Staging driver_vrto3d into ${OUT_DIR}"
+)
+
+if(WIN32)
+ add_custom_command(TARGET driver_vrto3d POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different "${OPENVR_DLL}"
+ "${OUT_DIR}/${BIN_SUBDIR}/openvr_api.dll"
+ COMMENT "Staging openvr_api.dll next to driver_vrto3d"
+ )
+else()
+ # RPATH $ORIGIN resolves this staged copy at dlopen time.
+ add_custom_command(TARGET driver_vrto3d POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different
+ "${ROOT}/external/openvr/bin/linux64/libopenvr_api.so"
+ "${OUT_DIR}/${BIN_SUBDIR}/libopenvr_api.so"
+ COMMENT "Staging libopenvr_api.so next to driver_vrto3d"
+ )
+endif()
diff --git a/vrto3d/protocols/wlr-layer-shell-unstable-v1.xml b/vrto3d/protocols/wlr-layer-shell-unstable-v1.xml
new file mode 100644
index 00000000..f458f65b
--- /dev/null
+++ b/vrto3d/protocols/wlr-layer-shell-unstable-v1.xml
@@ -0,0 +1,393 @@
+
+
+
+ Copyright © 2017 Drew DeVault
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+
+
+
+
+ Clients can use this interface to assign the surface_layer role to
+ wl_surfaces. Such surfaces are assigned to a "layer" of the output and
+ rendered with a defined z-depth respective to each other. They may also be
+ anchored to the edges and corners of a screen and have margins, or be
+ positioned in a specific part of the screen. They are intended for
+ surfaces that are drawn as part of the desktop, like panels and docks.
+
+
+
+
+ Create a layer surface for an existing surface. This assigns the role of
+ layer_surface, or raises a protocol error if another role is already
+ assigned.
+
+ Creating a layer surface from a wl_surface which has a buffer attached
+ or committed is a client error, and any attempts by a client to attach
+ or manipulate a buffer prior to the first layer_surface.configure call
+ must also be treated as errors.
+
+ After creating a layer_surface object and setting it up, the client
+ must perform an initial commit without any buffer attached.
+ The compositor will reply with a layer_surface.configure event.
+ The client must acknowledge it and is then allowed to attach a buffer
+ to map the surface.
+
+ You may pass NULL for output to allow the compositor to decide which
+ output to use. Generally this will be the one that the user most
+ recently interacted with.
+
+ Clients can specify a namespace that defines the purpose of the layer
+ surface.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ These values indicate which layers a surface can be rendered in. They
+ are ordered by z depth, bottom-most first. Traditional shell surfaces
+ will typically be rendered between the bottom and top layers.
+ Fullscreen shell surfaces are typically rendered at the top layer.
+ Multiple surfaces can share a single layer, and ordering within a
+ single layer is undefined.
+
+
+
+
+
+
+
+
+
+
+
+
+ This request indicates that the client will not use the layer_shell
+ object any more. Objects that have been created through this instance
+ are not affected.
+
+
+
+
+
+
+ An interface that may be implemented by a wl_surface, for surfaces that
+ are designed to be rendered as a layer of a stacked desktop-like
+ environment.
+
+ Layer surface state (layer, size, anchor, exclusive zone,
+ margin, interactivity) is double-buffered, and will be applied at the
+ time wl_surface.commit of the corresponding wl_surface is called.
+
+ Attaching a null buffer to a layer surface unmaps it.
+
+ Unmapping a layer_surface means that the surface cannot be shown by the
+ compositor until it is explicitly mapped again. The layer_surface
+ returns to the state it had right after layer_shell.get_layer_surface.
+ The client can re-map the surface by performing a commit without any
+ buffer attached, waiting for a configure event and handling it as usual.
+
+
+
+
+ Sets the size of the surface in surface-local coordinates. The
+ compositor will display the surface centered with respect to its
+ anchors.
+
+ If you pass 0 for either value, the compositor will assign it and
+ inform you of the assignment in the configure event. You must set your
+ anchor to opposite edges in the dimensions you omit; not doing so is a
+ protocol error. Both values are 0 by default.
+
+ Size is double-buffered, see wl_surface.commit.
+
+
+
+
+
+
+
+ Requests that the compositor anchor the surface to the specified edges
+ and corners. If two orthogonal edges are specified (e.g. 'top' and
+ 'left'), then the anchor point will be the intersection of the edges
+ (e.g. the top left corner of the output); otherwise the anchor point
+ will be centered on that edge, or in the center if none is specified.
+
+ Anchor is double-buffered, see wl_surface.commit.
+
+
+
+
+
+
+ Requests that the compositor avoids occluding an area with other
+ surfaces. The compositor's use of this information is
+ implementation-dependent - do not assume that this region will not
+ actually be occluded.
+
+ A positive value is only meaningful if the surface is anchored to one
+ edge or an edge and both perpendicular edges. If the surface is not
+ anchored, anchored to only two perpendicular edges (a corner), anchored
+ to only two parallel edges or anchored to all edges, a positive value
+ will be treated the same as zero.
+
+ A positive zone is the distance from the edge in surface-local
+ coordinates to consider exclusive.
+
+ Surfaces that do not wish to have an exclusive zone may instead specify
+ how they should interact with surfaces that do. If set to zero, the
+ surface indicates that it would like to be moved to avoid occluding
+ surfaces with a positive exclusive zone. If set to -1, the surface
+ indicates that it would not like to be moved to accommodate for other
+ surfaces, and the compositor should extend it all the way to the edges
+ it is anchored to.
+
+ For example, a panel might set its exclusive zone to 10, so that
+ maximized shell surfaces are not shown on top of it. A notification
+ might set its exclusive zone to 0, so that it is moved to avoid
+ occluding the panel, but shell surfaces are shown underneath it. A
+ wallpaper or lock screen might set their exclusive zone to -1, so that
+ they stretch below or over the panel.
+
+ The default value is 0.
+
+ Exclusive zone is double-buffered, see wl_surface.commit.
+
+
+
+
+
+
+ Requests that the surface be placed some distance away from the anchor
+ point on the output, in surface-local coordinates. Setting this value
+ for edges you are not anchored to has no effect.
+
+ The exclusive zone includes the margin.
+
+ Margin is double-buffered, see wl_surface.commit.
+
+
+
+
+
+
+
+
+
+ Set how keyboard events are delivered to this surface. By default,
+ layer shell surfaces do not receive keyboard events; this request can
+ be used to change this.
+
+ This setting is inherited by child surfaces set by the get_popup
+ request.
+
+ Layer surfaces receive pointer, touch, and tablet events normally. If
+ you do not want to receive them, set the input region on your surface
+ to an empty region.
+
+ Keyboard interactivity is double-buffered, see wl_surface.commit.
+
+
+
+
+
+
+ This assigns an xdg_popup's parent to this layer_surface. This popup
+ should have been created via xdg_surface::get_popup with the parent set
+ to NULL, and this request must be invoked before committing the popup's
+ initial state.
+
+ See the documentation of xdg_popup for more details about what an
+ xdg_popup is and how it is used.
+
+
+
+
+
+
+ When a configure event is received, if a client commits the
+ surface in response to the configure event, then the client
+ must make an ack_configure request sometime before the commit
+ request, passing along the serial of the configure event.
+
+ If the client receives multiple configure events before it
+ can respond to one, it only has to ack the last configure event.
+
+ A client is not required to commit immediately after sending
+ an ack_configure request - it may even ack_configure several times
+ before its next surface commit.
+
+ A client may send multiple ack_configure requests before committing, but
+ only the last request sent before a commit indicates which configure
+ event the client really is responding to.
+
+
+
+
+
+
+ This request destroys the layer surface.
+
+
+
+
+
+ The configure event asks the client to resize its surface.
+
+ Clients should arrange their surface for the new states, and then send
+ an ack_configure request with the serial sent in this configure event at
+ some point before committing the new surface.
+
+ The client is free to dismiss all but the last configure event it
+ received.
+
+ The width and height arguments specify the size of the window in
+ surface-local coordinates.
+
+ The size is a hint, in the sense that the client is free to ignore it if
+ it doesn't resize, pick a smaller size (to satisfy aspect ratio or
+ resize in steps of NxM pixels). If the client picks a smaller size and
+ is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
+ surface will be centered on this axis.
+
+ If the width or height arguments are zero, it means the client should
+ decide its own window dimension.
+
+
+
+
+
+
+
+
+ The closed event is sent by the compositor when the surface will no
+ longer be shown. The output may have been destroyed or the user may
+ have asked for it to be removed. Further changes to the surface will be
+ ignored. The client should destroy the resource after receiving this
+ event, and create a new surface if they so choose.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Change the layer that the surface is rendered on.
+
+ Layer is double-buffered, see wl_surface.commit.
+
+
+
+
+
+
+
+
+ Types of keyboard interaction possible for layer shell surfaces. The
+ rationale for this is twofold: (1) some applications are not interested
+ in keyboard events and not allowing them to be focused can improve the
+ desktop experience; (2) some applications will want to take exclusive
+ keyboard focus.
+
+
+
+
+ This value indicates that this surface is not interested in keyboard
+ events and the compositor should never assign it the keyboard focus.
+
+ This is the default value, set for newly created layer shell surfaces.
+
+ This is useful for e.g. desktop widgets that display information or
+ only have interaction with non-keyboard input devices.
+
+
+
+
+
+ Request exclusive keyboard focus if this surface is above the shell surface layer.
+
+ For the top and overlay layers, the seat will always give
+ exclusive keyboard focus to the top-most layer which has keyboard
+ interactivity set to exclusive. If this layer contains multiple
+ surfaces with keyboard interactivity set to exclusive, the compositor
+ determines the one receiving keyboard events in an implementation-
+ defined manner. In this case, no guarantee is made when this surface
+ will receive keyboard focus (if ever).
+
+ For the bottom and background layers, the compositor is allowed to use
+ normal focus semantics.
+
+ This setting is mainly intended for applications that need to ensure
+ they receive all keyboard events, such as as lock screens or on-screen
+ keyboards.
+
+
+
+
+
+ This requests the compositor to allow this surface to be focused and
+ unfocused by the user in an implementation-defined manner. The user
+ should be able to unfocus this surface even regardless of the layer
+ it is on.
+
+ Typically, the compositor will want to use its normal mechanism to
+ manage keyboard focus between layer shell surfaces with this setting
+ and regular toplevels on the desktop layer (e.g. click to focus).
+ Nevertheless, it is possible for a compositor to require a special
+ interaction to focus or unfocus layer shell surfaces (e.g. requiring
+ a click even if focus follows the mouse normally, or providing a
+ keybinding to switch focus between layers).
+
+ This setting is mainly intended for desktop shell components (e.g.
+ panels) that allow keyboard interaction. Using this option can allow
+ implementing a desktop shell that can be fully usable without the
+ mouse.
+
+
+
+
+
diff --git a/vrto3d/protocols/xdg-shell.xml b/vrto3d/protocols/xdg-shell.xml
new file mode 100644
index 00000000..39ecf8ac
--- /dev/null
+++ b/vrto3d/protocols/xdg-shell.xml
@@ -0,0 +1,1418 @@
+
+
+
+
+ Copyright © 2008-2013 Kristian Høgsberg
+ Copyright © 2013 Rafael Antognolli
+ Copyright © 2013 Jasper St. Pierre
+ Copyright © 2010-2013 Intel Corporation
+ Copyright © 2015-2017 Samsung Electronics Co., Ltd
+ Copyright © 2015-2017 Red Hat Inc.
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+
+
+
+
+ The xdg_wm_base interface is exposed as a global object enabling clients
+ to turn their wl_surfaces into windows in a desktop environment. It
+ defines the basic functionality needed for clients and the compositor to
+ create windows that can be dragged, resized, maximized, etc, as well as
+ creating transient windows such as popup menus.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Destroy this xdg_wm_base object.
+
+ Destroying a bound xdg_wm_base object while there are surfaces
+ still alive created by this xdg_wm_base object instance is illegal
+ and will result in a defunct_surfaces error.
+
+
+
+
+
+ Create a positioner object. A positioner object is used to position
+ surfaces relative to some parent surface. See the interface description
+ and xdg_surface.get_popup for details.
+
+
+
+
+
+
+ This creates an xdg_surface for the given surface. While xdg_surface
+ itself is not a role, the corresponding surface may only be assigned
+ a role extending xdg_surface, such as xdg_toplevel or xdg_popup. It is
+ illegal to create an xdg_surface for a wl_surface which already has an
+ assigned role and this will result in a role error.
+
+ This creates an xdg_surface for the given surface. An xdg_surface is
+ used as basis to define a role to a given surface, such as xdg_toplevel
+ or xdg_popup. It also manages functionality shared between xdg_surface
+ based surface roles.
+
+ See the documentation of xdg_surface for more details about what an
+ xdg_surface is and how it is used.
+
+
+
+
+
+
+
+ A client must respond to a ping event with a pong request or
+ the client may be deemed unresponsive. See xdg_wm_base.ping
+ and xdg_wm_base.error.unresponsive.
+
+
+
+
+
+
+ The ping event asks the client if it's still alive. Pass the
+ serial specified in the event back to the compositor by sending
+ a "pong" request back with the specified serial. See xdg_wm_base.pong.
+
+ Compositors can use this to determine if the client is still
+ alive. It's unspecified what will happen if the client doesn't
+ respond to the ping request, or in what timeframe. Clients should
+ try to respond in a reasonable amount of time. The “unresponsive”
+ error is provided for compositors that wish to disconnect unresponsive
+ clients.
+
+ A compositor is free to ping in any way it wants, but a client must
+ always respond to any xdg_wm_base object it created.
+
+
+
+
+
+
+
+ The xdg_positioner provides a collection of rules for the placement of a
+ child surface relative to a parent surface. Rules can be defined to ensure
+ the child surface remains within the visible area's borders, and to
+ specify how the child surface changes its position, such as sliding along
+ an axis, or flipping around a rectangle. These positioner-created rules are
+ constrained by the requirement that a child surface must intersect with or
+ be at least partially adjacent to its parent surface.
+
+ See the various requests for details about possible rules.
+
+ At the time of the request, the compositor makes a copy of the rules
+ specified by the xdg_positioner. Thus, after the request is complete the
+ xdg_positioner object can be destroyed or reused; further changes to the
+ object will have no effect on previous usages.
+
+ For an xdg_positioner object to be considered complete, it must have a
+ non-zero size set by set_size, and a non-zero anchor rectangle set by
+ set_anchor_rect. Passing an incomplete xdg_positioner object when
+ positioning a surface raises an invalid_positioner error.
+
+
+
+
+
+
+
+
+ Notify the compositor that the xdg_positioner will no longer be used.
+
+
+
+
+
+ Set the size of the surface that is to be positioned with the positioner
+ object. The size is in surface-local coordinates and corresponds to the
+ window geometry. See xdg_surface.set_window_geometry.
+
+ If a zero or negative size is set the invalid_input error is raised.
+
+
+
+
+
+
+
+ Specify the anchor rectangle within the parent surface that the child
+ surface will be placed relative to. The rectangle is relative to the
+ window geometry as defined by xdg_surface.set_window_geometry of the
+ parent surface.
+
+ When the xdg_positioner object is used to position a child surface, the
+ anchor rectangle may not extend outside the window geometry of the
+ positioned child's parent surface.
+
+ If a negative size is set the invalid_input error is raised.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Defines the anchor point for the anchor rectangle. The specified anchor
+ is used derive an anchor point that the child surface will be
+ positioned relative to. If a corner anchor is set (e.g. 'top_left' or
+ 'bottom_right'), the anchor point will be at the specified corner;
+ otherwise, the derived anchor point will be centered on the specified
+ edge, or in the center of the anchor rectangle if no edge is specified.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Defines in what direction a surface should be positioned, relative to
+ the anchor point of the parent surface. If a corner gravity is
+ specified (e.g. 'bottom_right' or 'top_left'), then the child surface
+ will be placed towards the specified gravity; otherwise, the child
+ surface will be centered over the anchor point on any axis that had no
+ gravity specified. If the gravity is not in the ‘gravity’ enum, an
+ invalid_input error is raised.
+
+
+
+
+
+
+ The constraint adjustment value define ways the compositor will adjust
+ the position of the surface, if the unadjusted position would result
+ in the surface being partly constrained.
+
+ Whether a surface is considered 'constrained' is left to the compositor
+ to determine. For example, the surface may be partly outside the
+ compositor's defined 'work area', thus necessitating the child surface's
+ position be adjusted until it is entirely inside the work area.
+
+ The adjustments can be combined, according to a defined precedence: 1)
+ Flip, 2) Slide, 3) Resize.
+
+
+
+ Don't alter the surface position even if it is constrained on some
+ axis, for example partially outside the edge of an output.
+
+
+
+
+ Slide the surface along the x axis until it is no longer constrained.
+
+ First try to slide towards the direction of the gravity on the x axis
+ until either the edge in the opposite direction of the gravity is
+ unconstrained or the edge in the direction of the gravity is
+ constrained.
+
+ Then try to slide towards the opposite direction of the gravity on the
+ x axis until either the edge in the direction of the gravity is
+ unconstrained or the edge in the opposite direction of the gravity is
+ constrained.
+
+
+
+
+ Slide the surface along the y axis until it is no longer constrained.
+
+ First try to slide towards the direction of the gravity on the y axis
+ until either the edge in the opposite direction of the gravity is
+ unconstrained or the edge in the direction of the gravity is
+ constrained.
+
+ Then try to slide towards the opposite direction of the gravity on the
+ y axis until either the edge in the direction of the gravity is
+ unconstrained or the edge in the opposite direction of the gravity is
+ constrained.
+
+
+
+
+ Invert the anchor and gravity on the x axis if the surface is
+ constrained on the x axis. For example, if the left edge of the
+ surface is constrained, the gravity is 'left' and the anchor is
+ 'left', change the gravity to 'right' and the anchor to 'right'.
+
+ If the adjusted position also ends up being constrained, the resulting
+ position of the flip_x adjustment will be the one before the
+ adjustment.
+
+
+
+
+ Invert the anchor and gravity on the y axis if the surface is
+ constrained on the y axis. For example, if the bottom edge of the
+ surface is constrained, the gravity is 'bottom' and the anchor is
+ 'bottom', change the gravity to 'top' and the anchor to 'top'.
+
+ The adjusted position is calculated given the original anchor
+ rectangle and offset, but with the new flipped anchor and gravity
+ values.
+
+ If the adjusted position also ends up being constrained, the resulting
+ position of the flip_y adjustment will be the one before the
+ adjustment.
+
+
+
+
+ Resize the surface horizontally so that it is completely
+ unconstrained.
+
+
+
+
+ Resize the surface vertically so that it is completely unconstrained.
+
+
+
+
+
+
+ Specify how the window should be positioned if the originally intended
+ position caused the surface to be constrained, meaning at least
+ partially outside positioning boundaries set by the compositor. The
+ adjustment is set by constructing a bitmask describing the adjustment to
+ be made when the surface is constrained on that axis.
+
+ If no bit for one axis is set, the compositor will assume that the child
+ surface should not change its position on that axis when constrained.
+
+ If more than one bit for one axis is set, the order of how adjustments
+ are applied is specified in the corresponding adjustment descriptions.
+
+ The default adjustment is none.
+
+
+
+
+
+
+ Specify the surface position offset relative to the position of the
+ anchor on the anchor rectangle and the anchor on the surface. For
+ example if the anchor of the anchor rectangle is at (x, y), the surface
+ has the gravity bottom|right, and the offset is (ox, oy), the calculated
+ surface position will be (x + ox, y + oy). The offset position of the
+ surface is the one used for constraint testing. See
+ set_constraint_adjustment.
+
+ An example use case is placing a popup menu on top of a user interface
+ element, while aligning the user interface element of the parent surface
+ with some user interface element placed somewhere in the popup surface.
+
+
+
+
+
+
+
+
+
+ When set reactive, the surface is reconstrained if the conditions used
+ for constraining changed, e.g. the parent window moved.
+
+ If the conditions changed and the popup was reconstrained, an
+ xdg_popup.configure event is sent with updated geometry, followed by an
+ xdg_surface.configure event.
+
+
+
+
+
+ Set the parent window geometry the compositor should use when
+ positioning the popup. The compositor may use this information to
+ determine the future state the popup should be constrained using. If
+ this doesn't match the dimension of the parent the popup is eventually
+ positioned against, the behavior is undefined.
+
+ The arguments are given in the surface-local coordinate space.
+
+
+
+
+
+
+
+ Set the serial of an xdg_surface.configure event this positioner will be
+ used in response to. The compositor may use this information together
+ with set_parent_size to determine what future state the popup should be
+ constrained using.
+
+
+
+
+
+
+
+ An interface that may be implemented by a wl_surface, for
+ implementations that provide a desktop-style user interface.
+
+ It provides a base set of functionality required to construct user
+ interface elements requiring management by the compositor, such as
+ toplevel windows, menus, etc. The types of functionality are split into
+ xdg_surface roles.
+
+ Creating an xdg_surface does not set the role for a wl_surface. In order
+ to map an xdg_surface, the client must create a role-specific object
+ using, e.g., get_toplevel, get_popup. The wl_surface for any given
+ xdg_surface can have at most one role, and may not be assigned any role
+ not based on xdg_surface.
+
+ A role must be assigned before any other requests are made to the
+ xdg_surface object.
+
+ The client must call wl_surface.commit on the corresponding wl_surface
+ for the xdg_surface state to take effect.
+
+ Creating an xdg_surface from a wl_surface which has a buffer attached or
+ committed is a client error, and any attempts by a client to attach or
+ manipulate a buffer prior to the first xdg_surface.configure call must
+ also be treated as errors.
+
+ After creating a role-specific object and setting it up (e.g. by sending
+ the title, app ID, size constraints, parent, etc), the client must
+ perform an initial commit without any buffer attached. The compositor
+ will reply with initial wl_surface state such as
+ wl_surface.preferred_buffer_scale followed by an xdg_surface.configure
+ event. The client must acknowledge it and is then allowed to attach a
+ buffer to map the surface.
+
+ Mapping an xdg_surface-based role surface is defined as making it
+ possible for the surface to be shown by the compositor. Note that
+ a mapped surface is not guaranteed to be visible once it is mapped.
+
+ For an xdg_surface to be mapped by the compositor, the following
+ conditions must be met:
+ (1) the client has assigned an xdg_surface-based role to the surface
+ (2) the client has set and committed the xdg_surface state and the
+ role-dependent state to the surface
+ (3) the client has committed a buffer to the surface
+
+ A newly-unmapped surface is considered to have met condition (1) out
+ of the 3 required conditions for mapping a surface if its role surface
+ has not been destroyed, i.e. the client must perform the initial commit
+ again before attaching a buffer.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Destroy the xdg_surface object. An xdg_surface must only be destroyed
+ after its role object has been destroyed, otherwise
+ a defunct_role_object error is raised.
+
+
+
+
+
+ This creates an xdg_toplevel object for the given xdg_surface and gives
+ the associated wl_surface the xdg_toplevel role.
+
+ See the documentation of xdg_toplevel for more details about what an
+ xdg_toplevel is and how it is used.
+
+
+
+
+
+
+ This creates an xdg_popup object for the given xdg_surface and gives
+ the associated wl_surface the xdg_popup role.
+
+ If null is passed as a parent, a parent surface must be specified using
+ some other protocol, before committing the initial state.
+
+ See the documentation of xdg_popup for more details about what an
+ xdg_popup is and how it is used.
+
+
+
+
+
+
+
+
+ The window geometry of a surface is its "visible bounds" from the
+ user's perspective. Client-side decorations often have invisible
+ portions like drop-shadows which should be ignored for the
+ purposes of aligning, placing and constraining windows. Note that
+ in some situations, compositors may clip rendering to the window
+ geometry, so the client should avoid putting functional elements
+ outside of it.
+
+ The window geometry is double-buffered state, see wl_surface.commit.
+
+ When maintaining a position, the compositor should treat the (x, y)
+ coordinate of the window geometry as the top left corner of the window.
+ A client changing the (x, y) window geometry coordinate should in
+ general not alter the position of the window.
+
+ Once the window geometry of the surface is set, it is not possible to
+ unset it, and it will remain the same until set_window_geometry is
+ called again, even if a new subsurface or buffer is attached.
+
+ If never set, the value is the full bounds of the surface,
+ including any subsurfaces. This updates dynamically on every
+ commit. This unset is meant for extremely simple clients.
+
+ The arguments are given in the surface-local coordinate space of
+ the wl_surface associated with this xdg_surface, and may extend outside
+ of the wl_surface itself to mark parts of the subsurface tree as part of
+ the window geometry.
+
+ When applied, the effective window geometry will be the set window
+ geometry clamped to the bounding rectangle of the combined
+ geometry of the surface of the xdg_surface and the associated
+ subsurfaces.
+
+ The effective geometry will not be recalculated unless a new call to
+ set_window_geometry is done and the new pending surface state is
+ subsequently applied.
+
+ The width and height of the effective window geometry must be
+ greater than zero. Setting an invalid size will raise an
+ invalid_size error.
+
+
+
+
+
+
+
+
+
+ When a configure event is received, if a client commits the
+ surface in response to the configure event, then the client
+ must make an ack_configure request sometime before the commit
+ request, passing along the serial of the configure event.
+
+ For instance, for toplevel surfaces the compositor might use this
+ information to move a surface to the top left only when the client has
+ drawn itself for the maximized or fullscreen state.
+
+ If the client receives multiple configure events before it
+ can respond to one, it only has to ack the last configure event.
+ Acking a configure event that was never sent raises an invalid_serial
+ error.
+
+ A client is not required to commit immediately after sending
+ an ack_configure request - it may even ack_configure several times
+ before its next surface commit.
+
+ A client may send multiple ack_configure requests before committing, but
+ only the last request sent before a commit indicates which configure
+ event the client really is responding to.
+
+ Sending an ack_configure request consumes the serial number sent with
+ the request, as well as serial numbers sent by all configure events
+ sent on this xdg_surface prior to the configure event referenced by
+ the committed serial.
+
+ It is an error to issue multiple ack_configure requests referencing a
+ serial from the same configure event, or to issue an ack_configure
+ request referencing a serial from a configure event issued before the
+ event identified by the last ack_configure request for the same
+ xdg_surface. Doing so will raise an invalid_serial error.
+
+
+
+
+
+
+ The configure event marks the end of a configure sequence. A configure
+ sequence is a set of one or more events configuring the state of the
+ xdg_surface, including the final xdg_surface.configure event.
+
+ Where applicable, xdg_surface surface roles will during a configure
+ sequence extend this event as a latched state sent as events before the
+ xdg_surface.configure event. Such events should be considered to make up
+ a set of atomically applied configuration states, where the
+ xdg_surface.configure commits the accumulated state.
+
+ Clients should arrange their surface for the new states, and then send
+ an ack_configure request with the serial sent in this configure event at
+ some point before committing the new surface.
+
+ If the client receives multiple configure events before it can respond
+ to one, it is free to discard all but the last event it received.
+
+
+
+
+
+
+
+
+ This interface defines an xdg_surface role which allows a surface to,
+ among other things, set window-like properties such as maximize,
+ fullscreen, and minimize, set application-specific metadata like title and
+ id, and well as trigger user interactive operations such as interactive
+ resize and move.
+
+ A xdg_toplevel by default is responsible for providing the full intended
+ visual representation of the toplevel, which depending on the window
+ state, may mean things like a title bar, window controls and drop shadow.
+
+ Unmapping an xdg_toplevel means that the surface cannot be shown
+ by the compositor until it is explicitly mapped again.
+ All active operations (e.g., move, resize) are canceled and all
+ attributes (e.g. title, state, stacking, ...) are discarded for
+ an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to
+ the state it had right after xdg_surface.get_toplevel. The client
+ can re-map the toplevel by performing a commit without any buffer
+ attached, waiting for a configure event and handling it as usual (see
+ xdg_surface description).
+
+ Attaching a null buffer to a toplevel unmaps the surface.
+
+
+
+
+ This request destroys the role surface and unmaps the surface;
+ see "Unmapping" behavior in interface section for details.
+
+
+
+
+
+
+
+
+
+
+
+ Set the "parent" of this surface. This surface should be stacked
+ above the parent surface and all other ancestor surfaces.
+
+ Parent surfaces should be set on dialogs, toolboxes, or other
+ "auxiliary" surfaces, so that the parent is raised when the dialog
+ is raised.
+
+ Setting a null parent for a child surface unsets its parent. Setting
+ a null parent for a surface which currently has no parent is a no-op.
+
+ Only mapped surfaces can have child surfaces. Setting a parent which
+ is not mapped is equivalent to setting a null parent. If a surface
+ becomes unmapped, its children's parent is set to the parent of
+ the now-unmapped surface. If the now-unmapped surface has no parent,
+ its children's parent is unset. If the now-unmapped surface becomes
+ mapped again, its parent-child relationship is not restored.
+
+ The parent toplevel must not be one of the child toplevel's
+ descendants, and the parent must be different from the child toplevel,
+ otherwise the invalid_parent protocol error is raised.
+
+
+
+
+
+
+ Set a short title for the surface.
+
+ This string may be used to identify the surface in a task bar,
+ window list, or other user interface elements provided by the
+ compositor.
+
+ The string must be encoded in UTF-8.
+
+
+
+
+
+
+ Set an application identifier for the surface.
+
+ The app ID identifies the general class of applications to which
+ the surface belongs. The compositor can use this to group multiple
+ surfaces together, or to determine how to launch a new application.
+
+ For D-Bus activatable applications, the app ID is used as the D-Bus
+ service name.
+
+ The compositor shell will try to group application surfaces together
+ by their app ID. As a best practice, it is suggested to select app
+ ID's that match the basename of the application's .desktop file.
+ For example, "org.freedesktop.FooViewer" where the .desktop file is
+ "org.freedesktop.FooViewer.desktop".
+
+ Like other properties, a set_app_id request can be sent after the
+ xdg_toplevel has been mapped to update the property.
+
+ See the desktop-entry specification [0] for more details on
+ application identifiers and how they relate to well-known D-Bus
+ names and .desktop files.
+
+ [0] https://standards.freedesktop.org/desktop-entry-spec/
+
+
+
+
+
+
+ Clients implementing client-side decorations might want to show
+ a context menu when right-clicking on the decorations, giving the
+ user a menu that they can use to maximize or minimize the window.
+
+ This request asks the compositor to pop up such a window menu at
+ the given position, relative to the local surface coordinates of
+ the parent surface. There are no guarantees as to what menu items
+ the window menu contains, or even if a window menu will be drawn
+ at all.
+
+ This request must be used in response to some sort of user action
+ like a button press, key press, or touch down event.
+
+
+
+
+
+
+
+
+
+ Start an interactive, user-driven move of the surface.
+
+ This request must be used in response to some sort of user action
+ like a button press, key press, or touch down event. The passed
+ serial is used to determine the type of interactive move (touch,
+ pointer, etc).
+
+ The server may ignore move requests depending on the state of
+ the surface (e.g. fullscreen or maximized), or if the passed serial
+ is no longer valid.
+
+ If triggered, the surface will lose the focus of the device
+ (wl_pointer, wl_touch, etc) used for the move. It is up to the
+ compositor to visually indicate that the move is taking place, such as
+ updating a pointer cursor, during the move. There is no guarantee
+ that the device focus will return when the move is completed.
+
+
+
+
+
+
+
+ These values are used to indicate which edge of a surface
+ is being dragged in a resize operation.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Start a user-driven, interactive resize of the surface.
+
+ This request must be used in response to some sort of user action
+ like a button press, key press, or touch down event. The passed
+ serial is used to determine the type of interactive resize (touch,
+ pointer, etc).
+
+ The server may ignore resize requests depending on the state of
+ the surface (e.g. fullscreen or maximized).
+
+ If triggered, the client will receive configure events with the
+ "resize" state enum value and the expected sizes. See the "resize"
+ enum value for more details about what is required. The client
+ must also acknowledge configure events using "ack_configure". After
+ the resize is completed, the client will receive another "configure"
+ event without the resize state.
+
+ If triggered, the surface also will lose the focus of the device
+ (wl_pointer, wl_touch, etc) used for the resize. It is up to the
+ compositor to visually indicate that the resize is taking place,
+ such as updating a pointer cursor, during the resize. There is no
+ guarantee that the device focus will return when the resize is
+ completed.
+
+ The edges parameter specifies how the surface should be resized, and
+ is one of the values of the resize_edge enum. Values not matching
+ a variant of the enum will cause the invalid_resize_edge protocol error.
+ The compositor may use this information to update the surface position
+ for example when dragging the top left corner. The compositor may also
+ use this information to adapt its behavior, e.g. choose an appropriate
+ cursor image.
+
+
+
+
+
+
+
+
+ The different state values used on the surface. This is designed for
+ state values like maximized, fullscreen. It is paired with the
+ configure event to ensure that both the client and the compositor
+ setting the state can be synchronized.
+
+ States set in this way are double-buffered, see wl_surface.commit.
+
+
+
+ The surface is maximized. The window geometry specified in the configure
+ event must be obeyed by the client, or the xdg_wm_base.invalid_surface_state
+ error is raised.
+
+ The client should draw without shadow or other
+ decoration outside of the window geometry.
+
+
+
+
+ The surface is fullscreen. The window geometry specified in the
+ configure event is a maximum; the client cannot resize beyond it. For
+ a surface to cover the whole fullscreened area, the geometry
+ dimensions must be obeyed by the client. For more details, see
+ xdg_toplevel.set_fullscreen.
+
+
+
+
+ The surface is being resized. The window geometry specified in the
+ configure event is a maximum; the client cannot resize beyond it.
+ Clients that have aspect ratio or cell sizing configuration can use
+ a smaller size, however.
+
+
+
+
+ Client window decorations should be painted as if the window is
+ active. Do not assume this means that the window actually has
+ keyboard or pointer focus.
+
+
+
+
+ The window is currently in a tiled layout and the left edge is
+ considered to be adjacent to another part of the tiling grid.
+
+ The client should draw without shadow or other decoration outside of
+ the window geometry on the left edge.
+
+
+
+
+ The window is currently in a tiled layout and the right edge is
+ considered to be adjacent to another part of the tiling grid.
+
+ The client should draw without shadow or other decoration outside of
+ the window geometry on the right edge.
+
+
+
+
+ The window is currently in a tiled layout and the top edge is
+ considered to be adjacent to another part of the tiling grid.
+
+ The client should draw without shadow or other decoration outside of
+ the window geometry on the top edge.
+
+
+
+
+ The window is currently in a tiled layout and the bottom edge is
+ considered to be adjacent to another part of the tiling grid.
+
+ The client should draw without shadow or other decoration outside of
+ the window geometry on the bottom edge.
+
+
+
+
+ The surface is currently not ordinarily being repainted; for
+ example because its content is occluded by another window, or its
+ outputs are switched off due to screen locking.
+
+
+
+
+ The left edge of the window is currently constrained, meaning it
+ shouldn't attempt to resize from that edge. It can for example mean
+ it's tiled next to a monitor edge on the constrained side of the
+ window.
+
+
+
+
+ The right edge of the window is currently constrained, meaning it
+ shouldn't attempt to resize from that edge. It can for example mean
+ it's tiled next to a monitor edge on the constrained side of the
+ window.
+
+
+
+
+ The top edge of the window is currently constrained, meaning it
+ shouldn't attempt to resize from that edge. It can for example mean
+ it's tiled next to a monitor edge on the constrained side of the
+ window.
+
+
+
+
+ The bottom edge of the window is currently constrained, meaning it
+ shouldn't attempt to resize from that edge. It can for example mean
+ it's tiled next to a monitor edge on the constrained side of the
+ window.
+
+
+
+
+
+
+ Set a maximum size for the window.
+
+ The client can specify a maximum size so that the compositor does
+ not try to configure the window beyond this size.
+
+ The width and height arguments are in window geometry coordinates.
+ See xdg_surface.set_window_geometry.
+
+ Values set in this way are double-buffered, see wl_surface.commit.
+
+ The compositor can use this information to allow or disallow
+ different states like maximize or fullscreen and draw accurate
+ animations.
+
+ Similarly, a tiling window manager may use this information to
+ place and resize client windows in a more effective way.
+
+ The client should not rely on the compositor to obey the maximum
+ size. The compositor may decide to ignore the values set by the
+ client and request a larger size.
+
+ If never set, or a value of zero in the request, means that the
+ client has no expected maximum size in the given dimension.
+ As a result, a client wishing to reset the maximum size
+ to an unspecified state can use zero for width and height in the
+ request.
+
+ Requesting a maximum size to be smaller than the minimum size of
+ a surface is illegal and will result in an invalid_size error.
+
+ The width and height must be greater than or equal to zero. Using
+ strictly negative values for width or height will result in a
+ invalid_size error.
+
+
+
+
+
+
+
+ Set a minimum size for the window.
+
+ The client can specify a minimum size so that the compositor does
+ not try to configure the window below this size.
+
+ The width and height arguments are in window geometry coordinates.
+ See xdg_surface.set_window_geometry.
+
+ Values set in this way are double-buffered, see wl_surface.commit.
+
+ The compositor can use this information to allow or disallow
+ different states like maximize or fullscreen and draw accurate
+ animations.
+
+ Similarly, a tiling window manager may use this information to
+ place and resize client windows in a more effective way.
+
+ The client should not rely on the compositor to obey the minimum
+ size. The compositor may decide to ignore the values set by the
+ client and request a smaller size.
+
+ If never set, or a value of zero in the request, means that the
+ client has no expected minimum size in the given dimension.
+ As a result, a client wishing to reset the minimum size
+ to an unspecified state can use zero for width and height in the
+ request.
+
+ Requesting a minimum size to be larger than the maximum size of
+ a surface is illegal and will result in an invalid_size error.
+
+ The width and height must be greater than or equal to zero. Using
+ strictly negative values for width and height will result in a
+ invalid_size error.
+
+
+
+
+
+
+
+ Maximize the surface.
+
+ After requesting that the surface should be maximized, the compositor
+ will respond by emitting a configure event. Whether this configure
+ actually sets the window maximized is subject to compositor policies.
+ The client must then update its content, drawing in the configured
+ state. The client must also acknowledge the configure when committing
+ the new content (see ack_configure).
+
+ It is up to the compositor to decide how and where to maximize the
+ surface, for example which output and what region of the screen should
+ be used.
+
+ If the surface was already maximized, the compositor will still emit
+ a configure event with the "maximized" state.
+
+ If the surface is in a fullscreen state, this request has no direct
+ effect. It may alter the state the surface is returned to when
+ unmaximized unless overridden by the compositor.
+
+
+
+
+
+ Unmaximize the surface.
+
+ After requesting that the surface should be unmaximized, the compositor
+ will respond by emitting a configure event. Whether this actually
+ un-maximizes the window is subject to compositor policies.
+ If available and applicable, the compositor will include the window
+ geometry dimensions the window had prior to being maximized in the
+ configure event. The client must then update its content, drawing it in
+ the configured state. The client must also acknowledge the configure
+ when committing the new content (see ack_configure).
+
+ It is up to the compositor to position the surface after it was
+ unmaximized; usually the position the surface had before maximizing, if
+ applicable.
+
+ If the surface was already not maximized, the compositor will still
+ emit a configure event without the "maximized" state.
+
+ If the surface is in a fullscreen state, this request has no direct
+ effect. It may alter the state the surface is returned to when
+ unmaximized unless overridden by the compositor.
+
+
+
+
+
+ Make the surface fullscreen.
+
+ After requesting that the surface should be fullscreened, the
+ compositor will respond by emitting a configure event. Whether the
+ client is actually put into a fullscreen state is subject to compositor
+ policies. The client must also acknowledge the configure when
+ committing the new content (see ack_configure).
+
+ The output passed by the request indicates the client's preference as
+ to which display it should be set fullscreen on. If this value is NULL,
+ it's up to the compositor to choose which display will be used to map
+ this surface.
+
+ If the surface doesn't cover the whole output, the compositor will
+ position the surface in the center of the output and compensate with
+ with border fill covering the rest of the output. The content of the
+ border fill is undefined, but should be assumed to be in some way that
+ attempts to blend into the surrounding area (e.g. solid black).
+
+ If the fullscreened surface is not opaque, the compositor must make
+ sure that other screen content not part of the same surface tree (made
+ up of subsurfaces, popups or similarly coupled surfaces) are not
+ visible below the fullscreened surface.
+
+
+
+
+
+
+ Make the surface no longer fullscreen.
+
+ After requesting that the surface should be unfullscreened, the
+ compositor will respond by emitting a configure event.
+ Whether this actually removes the fullscreen state of the client is
+ subject to compositor policies.
+
+ Making a surface unfullscreen sets states for the surface based on the following:
+ * the state(s) it may have had before becoming fullscreen
+ * any state(s) decided by the compositor
+ * any state(s) requested by the client while the surface was fullscreen
+
+ The compositor may include the previous window geometry dimensions in
+ the configure event, if applicable.
+
+ The client must also acknowledge the configure when committing the new
+ content (see ack_configure).
+
+
+
+
+
+ Request that the compositor minimize your surface. There is no
+ way to know if the surface is currently minimized, nor is there
+ any way to unset minimization on this surface.
+
+ If you are looking to throttle redrawing when minimized, please
+ instead use the wl_surface.frame event for this, as this will
+ also work with live previews on windows in Alt-Tab, Expose or
+ similar compositor features.
+
+
+
+
+
+ This configure event asks the client to resize its toplevel surface or
+ to change its state. The configured state should not be applied
+ immediately. See xdg_surface.configure for details.
+
+ The width and height arguments specify a hint to the window
+ about how its surface should be resized in window geometry
+ coordinates. See set_window_geometry.
+
+ If the width or height arguments are zero, it means the client
+ should decide its own window dimension. This may happen when the
+ compositor needs to configure the state of the surface but doesn't
+ have any information about any previous or expected dimension.
+
+ The states listed in the event specify how the width/height
+ arguments should be interpreted, and possibly how it should be
+ drawn.
+
+ Clients must send an ack_configure in response to this event. See
+ xdg_surface.configure and xdg_surface.ack_configure for details.
+
+
+
+
+
+
+
+
+ The close event is sent by the compositor when the user
+ wants the surface to be closed. This should be equivalent to
+ the user clicking the close button in client-side decorations,
+ if your application has any.
+
+ This is only a request that the user intends to close the
+ window. The client may choose to ignore this request, or show
+ a dialog to ask the user to save their data, etc.
+
+
+
+
+
+
+
+ The configure_bounds event may be sent prior to a xdg_toplevel.configure
+ event to communicate the bounds a window geometry size is recommended
+ to constrain to.
+
+ The passed width and height are in surface coordinate space. If width
+ and height are 0, it means bounds is unknown and equivalent to as if no
+ configure_bounds event was ever sent for this surface.
+
+ The bounds can for example correspond to the size of a monitor excluding
+ any panels or other shell components, so that a surface isn't created in
+ a way that it cannot fit.
+
+ The bounds may change at any point, and in such a case, a new
+ xdg_toplevel.configure_bounds will be sent, followed by
+ xdg_toplevel.configure and xdg_surface.configure.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This event advertises the capabilities supported by the compositor. If
+ a capability isn't supported, clients should hide or disable the UI
+ elements that expose this functionality. For instance, if the
+ compositor doesn't advertise support for minimized toplevels, a button
+ triggering the set_minimized request should not be displayed.
+
+ The compositor will ignore requests it doesn't support. For instance,
+ a compositor which doesn't advertise support for minimized will ignore
+ set_minimized requests.
+
+ Compositors must send this event once before the first
+ xdg_surface.configure event. When the capabilities change, compositors
+ must send this event again and then send an xdg_surface.configure
+ event.
+
+ The configured state should not be applied immediately. See
+ xdg_surface.configure for details.
+
+ The capabilities are sent as an array of 32-bit unsigned integers in
+ native endianness.
+
+
+
+
+
+
+
+ A popup surface is a short-lived, temporary surface. It can be used to
+ implement for example menus, popovers, tooltips and other similar user
+ interface concepts.
+
+ A popup can be made to take an explicit grab. See xdg_popup.grab for
+ details.
+
+ When the popup is dismissed, a popup_done event will be sent out, and at
+ the same time the surface will be unmapped. See the xdg_popup.popup_done
+ event for details.
+
+ Explicitly destroying the xdg_popup object will also dismiss the popup and
+ unmap the surface. Clients that want to dismiss the popup when another
+ surface of their own is clicked should dismiss the popup using the destroy
+ request.
+
+ A newly created xdg_popup will be stacked on top of all previously created
+ xdg_popup surfaces associated with the same xdg_toplevel.
+
+ The parent of an xdg_popup must be mapped (see the xdg_surface
+ description) before the xdg_popup itself.
+
+ The client must call wl_surface.commit on the corresponding wl_surface
+ for the xdg_popup state to take effect.
+
+
+
+
+
+
+
+
+ This destroys the popup. Explicitly destroying the xdg_popup
+ object will also dismiss the popup, and unmap the surface.
+
+ If this xdg_popup is not the "topmost" popup, the
+ xdg_wm_base.not_the_topmost_popup protocol error will be sent.
+
+
+
+
+
+ This request makes the created popup take an explicit grab. An explicit
+ grab will be dismissed when the user dismisses the popup, or when the
+ client destroys the xdg_popup. This can be done by the user clicking
+ outside the surface, using the keyboard, or even locking the screen
+ through closing the lid or a timeout.
+
+ If the compositor denies the grab, the popup will be immediately
+ dismissed.
+
+ This request must be used in response to some sort of user action like a
+ button press, key press, or touch down event. The serial number of the
+ event should be passed as 'serial'.
+
+ The parent of a grabbing popup must either be an xdg_toplevel surface or
+ another xdg_popup with an explicit grab. If the parent is another
+ xdg_popup it means that the popups are nested, with this popup now being
+ the topmost popup.
+
+ Nested popups must be destroyed in the reverse order they were created
+ in, e.g. the only popup you are allowed to destroy at all times is the
+ topmost one.
+
+ When compositors choose to dismiss a popup, they may dismiss every
+ nested grabbing popup as well. When a compositor dismisses popups, it
+ will follow the same dismissing order as required from the client.
+
+ If the topmost grabbing popup is destroyed, the grab will be returned to
+ the parent of the popup, if that parent previously had an explicit grab.
+
+ If the parent is a grabbing popup which has already been dismissed, this
+ popup will be immediately dismissed. If the parent is a popup that did
+ not take an explicit grab, an error will be raised.
+
+ During a popup grab, the client owning the grab will receive pointer
+ and touch events for all their surfaces as normal (similar to an
+ "owner-events" grab in X11 parlance), while the top most grabbing popup
+ will always have keyboard focus.
+
+
+
+
+
+
+
+ This event asks the popup surface to configure itself given the
+ configuration. The configured state should not be applied immediately.
+ See xdg_surface.configure for details.
+
+ The x and y arguments represent the position the popup was placed at
+ given the xdg_positioner rule, relative to the upper left corner of the
+ window geometry of the parent surface.
+
+ For version 2 or older, the configure event for an xdg_popup is only
+ ever sent once for the initial configuration. Starting with version 3,
+ it may be sent again if the popup is setup with an xdg_positioner with
+ set_reactive requested, or in response to xdg_popup.reposition requests.
+
+
+
+
+
+
+
+
+
+ The popup_done event is sent out when a popup is dismissed by the
+ compositor. The client should destroy the xdg_popup object at this
+ point.
+
+
+
+
+
+
+
+ Reposition an already-mapped popup. The popup will be placed given the
+ details in the passed xdg_positioner object, and a
+ xdg_popup.repositioned followed by xdg_popup.configure and
+ xdg_surface.configure will be emitted in response. Any parameters set
+ by the previous positioner will be discarded.
+
+ The passed token will be sent in the corresponding
+ xdg_popup.repositioned event. The new popup position will not take
+ effect until the corresponding configure event is acknowledged by the
+ client. See xdg_popup.repositioned for details. The token itself is
+ opaque, and has no other special meaning.
+
+ If multiple reposition requests are sent, the compositor may skip all
+ but the last one.
+
+ If the popup is repositioned in response to a configure event for its
+ parent, the client should send an xdg_positioner.set_parent_configure
+ and possibly an xdg_positioner.set_parent_size request to allow the
+ compositor to properly constrain the popup.
+
+ If the popup is repositioned together with a parent that is being
+ resized, but not in response to a configure event, the client should
+ send an xdg_positioner.set_parent_size request.
+
+
+
+
+
+
+
+ The repositioned event is sent as part of a popup configuration
+ sequence, together with xdg_popup.configure and lastly
+ xdg_surface.configure to notify the completion of a reposition request.
+
+ The repositioned event is to notify about the completion of a
+ xdg_popup.reposition request. The token argument is the token passed
+ in the xdg_popup.reposition request.
+
+ Immediately after this event is emitted, xdg_popup.configure and
+ xdg_surface.configure will be sent with the updated size and position,
+ as well as a new configure serial.
+
+ The client should optionally update the content of the popup, but must
+ acknowledge the new popup configuration for the new position to take
+ effect. See xdg_surface.ack_configure for details.
+
+
+
+
+
+
diff --git a/vrto3d/shaders/compile_shaders.sh b/vrto3d/shaders/compile_shaders.sh
new file mode 100755
index 00000000..711cc68c
--- /dev/null
+++ b/vrto3d/shaders/compile_shaders.sh
@@ -0,0 +1,73 @@
+#!/bin/sh
+#
+# This file is part of VRto3D.
+#
+# VRto3D is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# VRto3D is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with VRto3D. If not, see .
+#
+# Compiles the repack shaders to SPIR-V and wraps them into C headers under
+# generated/. The headers are checked in as a toolchain fallback so the build
+# only needs a C++ compiler; re-run this script after editing any shader.
+#
+# Requires: glslc (shaderc), typically from the Vulkan SDK or distro package.
+
+set -eu
+cd "$(dirname "$0")"
+
+mkdir -p generated
+
+# emit
+# Produces generated/_spv.h defining:
+# static const uint32_t _spv[] = { ... };
+# glslc -mfmt=c emits just the {...} initializer list; we wrap it here.
+emit() {
+ in=$1
+ name=$2
+ out="generated/${name}_spv.h"
+ tmp=$(mktemp)
+
+ glslc -O -mfmt=c "$in" -o "$tmp"
+
+ {
+ printf '/*\n'
+ printf ' * This file is part of VRto3D.\n'
+ printf ' *\n'
+ printf ' * VRto3D is free software: you can redistribute it and/or modify\n'
+ printf ' * it under the terms of the GNU Lesser General Public License as published by\n'
+ printf ' * the Free Software Foundation, either version 3 of the License, or\n'
+ printf ' * (at your option) any later version.\n'
+ printf ' *\n'
+ printf ' * VRto3D is distributed in the hope that it will be useful,\n'
+ printf ' * but WITHOUT ANY WARRANTY; without even the implied warranty of\n'
+ printf ' * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n'
+ printf ' * GNU Lesser General Public License for more details.\n'
+ printf ' *\n'
+ printf ' * You should have received a copy of the GNU Lesser General Public License\n'
+ printf ' * along with VRto3D. If not, see .\n'
+ printf ' */\n'
+ printf '/* Generated by compile_shaders.sh from %s — do not edit. */\n' "$in"
+ printf '#pragma once\n'
+ printf '\n'
+ printf '#include \n'
+ printf '\n'
+ printf 'static const uint32_t %s_spv[] =\n' "$name"
+ cat "$tmp"
+ printf ';\n'
+ } > "$out"
+
+ rm -f "$tmp"
+ echo "wrote $out"
+}
+
+emit fullscreen.vert fullscreen_vert
+emit repack.frag repack_frag
diff --git a/vrto3d/shaders/fullscreen.vert b/vrto3d/shaders/fullscreen.vert
new file mode 100644
index 00000000..2bbb7d50
--- /dev/null
+++ b/vrto3d/shaders/fullscreen.vert
@@ -0,0 +1,39 @@
+/*
+ * This file is part of VRto3D.
+ *
+ * VRto3D is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VRto3D is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with VRto3D. If not, see .
+ */
+#version 450
+
+// Fullscreen triangle from gl_VertexIndex — no vertex buffer.
+//
+// UV convention: Vulkan clip space is Y-down (NDC y = -1 is the TOP of the
+// viewport), so vertex 0 at (-1,-1) is the top-left corner of the output
+// window and uv = (pos + 1) / 2 yields uv == (0,0) there. uv.y therefore
+// increases downward — uv (0,0) = top-left of the OUTPUT window, matching
+// both the D3D11 presenter's texcoord convention and the sbs texture's
+// row order (v = 0 samples the top row). Do NOT flip uv.y.
+//
+// gl_VertexIndex 0 -> pos (-1,-1) uv (0,0)
+// gl_VertexIndex 1 -> pos ( 3,-1) uv (2,0)
+// gl_VertexIndex 2 -> pos (-1, 3) uv (0,2)
+
+layout(location = 0) out vec2 uv;
+
+void main() {
+ vec2 pos = vec2((gl_VertexIndex == 1) ? 3.0 : -1.0,
+ (gl_VertexIndex == 2) ? 3.0 : -1.0);
+ gl_Position = vec4(pos, 0.0, 1.0);
+ uv = (pos + 1.0) * 0.5;
+}
diff --git a/vrto3d/shaders/generated/fullscreen_vert_spv.h b/vrto3d/shaders/generated/fullscreen_vert_spv.h
new file mode 100644
index 00000000..28e54791
--- /dev/null
+++ b/vrto3d/shaders/generated/fullscreen_vert_spv.h
@@ -0,0 +1,80 @@
+/*
+ * This file is part of VRto3D.
+ *
+ * VRto3D is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VRto3D is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with VRto3D. If not, see .
+ */
+/* Generated by compile_shaders.sh from fullscreen.vert — do not edit. */
+#pragma once
+
+#include
+
+static const uint32_t fullscreen_vert_spv[] =
+{0x07230203,0x00010000,0x000d000b,0x00000031,
+0x00000000,0x00020011,0x00000001,0x0006000b,
+0x00000001,0x4c534c47,0x6474732e,0x3035342e,
+0x00000000,0x0003000e,0x00000000,0x00000001,
+0x0008000f,0x00000000,0x00000004,0x6e69616d,
+0x00000000,0x0000000c,0x0000001f,0x0000002a,
+0x00040047,0x0000000c,0x0000000b,0x0000002a,
+0x00030047,0x0000001d,0x00000002,0x00050048,
+0x0000001d,0x00000000,0x0000000b,0x00000000,
+0x00050048,0x0000001d,0x00000001,0x0000000b,
+0x00000001,0x00050048,0x0000001d,0x00000002,
+0x0000000b,0x00000003,0x00050048,0x0000001d,
+0x00000003,0x0000000b,0x00000004,0x00040047,
+0x0000002a,0x0000001e,0x00000000,0x00020013,
+0x00000002,0x00030021,0x00000003,0x00000002,
+0x00030016,0x00000006,0x00000020,0x00040017,
+0x00000007,0x00000006,0x00000002,0x00040015,
+0x0000000a,0x00000020,0x00000001,0x00040020,
+0x0000000b,0x00000001,0x0000000a,0x0004003b,
+0x0000000b,0x0000000c,0x00000001,0x0004002b,
+0x0000000a,0x0000000e,0x00000001,0x00020014,
+0x0000000f,0x0004002b,0x00000006,0x00000011,
+0x40400000,0x0004002b,0x00000006,0x00000012,
+0xbf800000,0x0004002b,0x0000000a,0x00000015,
+0x00000002,0x00040017,0x00000019,0x00000006,
+0x00000004,0x00040015,0x0000001a,0x00000020,
+0x00000000,0x0004002b,0x0000001a,0x0000001b,
+0x00000001,0x0004001c,0x0000001c,0x00000006,
+0x0000001b,0x0006001e,0x0000001d,0x00000019,
+0x00000006,0x0000001c,0x0000001c,0x00040020,
+0x0000001e,0x00000003,0x0000001d,0x0004003b,
+0x0000001e,0x0000001f,0x00000003,0x0004002b,
+0x0000000a,0x00000020,0x00000000,0x0004002b,
+0x00000006,0x00000022,0x00000000,0x0004002b,
+0x00000006,0x00000023,0x3f800000,0x00040020,
+0x00000027,0x00000003,0x00000019,0x00040020,
+0x00000029,0x00000003,0x00000007,0x0004003b,
+0x00000029,0x0000002a,0x00000003,0x0004002b,
+0x00000006,0x0000002e,0x3f000000,0x0005002c,
+0x00000007,0x00000030,0x00000023,0x00000023,
+0x00050036,0x00000002,0x00000004,0x00000000,
+0x00000003,0x000200f8,0x00000005,0x0004003d,
+0x0000000a,0x0000000d,0x0000000c,0x000500aa,
+0x0000000f,0x00000010,0x0000000d,0x0000000e,
+0x000600a9,0x00000006,0x00000013,0x00000010,
+0x00000011,0x00000012,0x000500aa,0x0000000f,
+0x00000016,0x0000000d,0x00000015,0x000600a9,
+0x00000006,0x00000017,0x00000016,0x00000011,
+0x00000012,0x00050050,0x00000007,0x00000018,
+0x00000013,0x00000017,0x00070050,0x00000019,
+0x00000026,0x00000013,0x00000017,0x00000022,
+0x00000023,0x00050041,0x00000027,0x00000028,
+0x0000001f,0x00000020,0x0003003e,0x00000028,
+0x00000026,0x00050081,0x00000007,0x0000002d,
+0x00000018,0x00000030,0x0005008e,0x00000007,
+0x0000002f,0x0000002d,0x0000002e,0x0003003e,
+0x0000002a,0x0000002f,0x000100fd,0x00010038}
+;
diff --git a/vrto3d/shaders/generated/repack_frag_spv.h b/vrto3d/shaders/generated/repack_frag_spv.h
new file mode 100644
index 00000000..dc5ca718
--- /dev/null
+++ b/vrto3d/shaders/generated/repack_frag_spv.h
@@ -0,0 +1,1051 @@
+/*
+ * This file is part of VRto3D.
+ *
+ * VRto3D is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VRto3D is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with VRto3D. If not, see .
+ */
+/* Generated by compile_shaders.sh from repack.frag — do not edit. */
+#pragma once
+
+#include
+
+static const uint32_t repack_frag_spv[] =
+{0x07230203,0x00010000,0x000d000b,0x00000b45,
+0x00000000,0x00020011,0x00000001,0x0006000b,
+0x00000001,0x4c534c47,0x6474732e,0x3035342e,
+0x00000000,0x0003000e,0x00000000,0x00000001,
+0x0008000f,0x00000004,0x00000004,0x6e69616d,
+0x00000000,0x000000b8,0x0000013c,0x00000502,
+0x00030010,0x00000004,0x00000007,0x00030047,
+0x0000001c,0x00000002,0x00050048,0x0000001c,
+0x00000000,0x00000023,0x00000000,0x00050048,
+0x0000001c,0x00000001,0x00000023,0x00000008,
+0x00050048,0x0000001c,0x00000002,0x00000023,
+0x00000010,0x00050048,0x0000001c,0x00000003,
+0x00000023,0x00000014,0x00050048,0x0000001c,
+0x00000004,0x00000023,0x00000018,0x00050048,
+0x0000001c,0x00000005,0x00000023,0x0000001c,
+0x00050048,0x0000001c,0x00000006,0x00000023,
+0x00000020,0x00050048,0x0000001c,0x00000007,
+0x00000023,0x00000030,0x00050048,0x0000001c,
+0x00000008,0x00000023,0x00000040,0x00050048,
+0x0000001c,0x00000009,0x00000023,0x00000050,
+0x00040047,0x0000003a,0x00000021,0x00000000,
+0x00040047,0x0000003a,0x00000022,0x00000000,
+0x00040047,0x000000b8,0x0000001e,0x00000000,
+0x00040047,0x0000013c,0x0000000b,0x0000000f,
+0x00040047,0x00000502,0x0000001e,0x00000000,
+0x00020013,0x00000002,0x00030021,0x00000003,
+0x00000002,0x00040015,0x00000006,0x00000020,
+0x00000001,0x00030016,0x00000008,0x00000020,
+0x00040017,0x0000000a,0x00000008,0x00000004,
+0x00040017,0x00000011,0x00000008,0x00000003,
+0x00040017,0x0000001b,0x00000006,0x00000002,
+0x000c001e,0x0000001c,0x0000001b,0x0000001b,
+0x00000006,0x00000006,0x00000006,0x00000008,
+0x0000000a,0x0000000a,0x0000000a,0x0000000a,
+0x00040020,0x0000001d,0x00000009,0x0000001c,
+0x0004003b,0x0000001d,0x0000001e,0x00000009,
+0x0004002b,0x00000006,0x0000001f,0x00000003,
+0x00040020,0x00000020,0x00000009,0x00000006,
+0x0004002b,0x00000006,0x00000023,0x00000000,
+0x00020014,0x00000024,0x0004002b,0x00000006,
+0x00000029,0x00000001,0x0004002b,0x00000008,
+0x00000030,0x3f000000,0x00090019,0x00000037,
+0x00000008,0x00000001,0x00000000,0x00000000,
+0x00000000,0x00000001,0x00000000,0x0003001b,
+0x00000038,0x00000037,0x00040020,0x00000039,
+0x00000000,0x00000038,0x0004003b,0x00000039,
+0x0000003a,0x00000000,0x00040017,0x0000003e,
+0x00000008,0x00000002,0x0004002b,0x00000006,
+0x00000044,0x00000009,0x00040015,0x00000045,
+0x00000020,0x00000000,0x0004002b,0x00000045,
+0x00000046,0x00000000,0x00040020,0x00000047,
+0x00000009,0x00000008,0x0004002b,0x00000045,
+0x0000004b,0x00000001,0x0004002b,0x00000045,
+0x0000004f,0x00000002,0x0004002b,0x00000006,
+0x00000055,0x00000005,0x0004002b,0x00000008,
+0x00000060,0x3f800000,0x0004002b,0x00000008,
+0x0000006e,0x00000000,0x0004002b,0x00000008,
+0x00000077,0x3fc00000,0x0004002b,0x00000006,
+0x00000078,0x00000006,0x00040020,0x00000079,
+0x00000009,0x0000000a,0x0004002b,0x00000006,
+0x0000008c,0x00000008,0x0004002b,0x00000006,
+0x00000094,0x00000007,0x0004002b,0x00000006,
+0x000000a2,0x00000002,0x0004002b,0x00000006,
+0x000000a8,0x0000000d,0x00040020,0x000000b7,
+0x00000001,0x0000003e,0x0004003b,0x000000b7,
+0x000000b8,0x00000001,0x00040020,0x000000b9,
+0x00000001,0x00000008,0x0004002b,0x00000008,
+0x000000c7,0x40000000,0x0004002b,0x00000006,
+0x000000de,0x0000000a,0x0004002b,0x00000006,
+0x000000e2,0x0000000b,0x0004002b,0x00000006,
+0x000000e6,0x0000000c,0x0004002b,0x00000008,
+0x000000f0,0x41f00000,0x0004002b,0x00000008,
+0x000000f6,0x42340000,0x0007002c,0x0000000a,
+0x00000134,0x0000006e,0x0000006e,0x0000006e,
+0x00000060,0x00040020,0x0000013b,0x00000001,
+0x0000000a,0x0004003b,0x0000013b,0x0000013c,
+0x00000001,0x0004002b,0x00000006,0x00000163,
+0x00000004,0x0004002b,0x00000008,0x00000184,
+0x3e800000,0x0004002b,0x00000008,0x0000018b,
+0x3f400000,0x0004002b,0x00000006,0x000001b4,
+0x0000000e,0x0004002b,0x00000006,0x000001e3,
+0x00000017,0x0004002b,0x00000006,0x00000204,
+0x0000000f,0x0004002b,0x00000006,0x00000211,
+0x00000010,0x0004002b,0x00000008,0x00000216,
+0x3edfbe77,0x0004002b,0x00000008,0x0000021a,
+0x3ee5e354,0x0004002b,0x00000008,0x0000021f,
+0x3e27ef9e,0x0004002b,0x00000008,0x00000224,
+0x3c343958,0x0004002b,0x00000008,0x00000229,
+0x3d03126f,0x0004002b,0x00000008,0x0000022e,
+0x3be56042,0x0004002b,0x00000008,0x00000235,
+0xbd7df3b6,0x0004002b,0x00000008,0x00000239,
+0x3d7df3b6,0x0004002b,0x00000008,0x0000023e,
+0x3cc49ba6,0x0004002b,0x00000008,0x00000243,
+0x3ec10625,0x0004002b,0x00000008,0x00000248,
+0x3f42d0e5,0x0004002b,0x00000008,0x0000024d,
+0x3c1374bc,0x0004002b,0x00000008,0x00000254,
+0xbd449ba6,0x0004002b,0x00000008,0x00000258,
+0x3d4ccccd,0x0004002b,0x00000008,0x0000025d,
+0x3c8b4396,0x0004002b,0x00000008,0x00000262,
+0x3cd4fdf4,0x0004002b,0x00000008,0x00000267,
+0x3dbe76c9,0x0004002b,0x00000008,0x0000026c,
+0x3f9df3b6,0x0004002b,0x00000006,0x00000278,
+0x00000011,0x0004002b,0x00000008,0x0000027e,
+0x3bc49ba6,0x0004002b,0x00000008,0x00000285,
+0x3ee66666,0x0004002b,0x00000008,0x000002b3,
+0x3f933333,0x0004002b,0x00000006,0x0000030f,
+0x00000012,0x0004002b,0x00000008,0x00000314,
+0x3ee0c49c,0x0004002b,0x00000008,0x00000315,
+0x3ee4dd2f,0x0004002b,0x00000008,0x00000316,
+0x3e178d50,0x0006002c,0x00000011,0x00000317,
+0x00000314,0x00000315,0x00000316,0x0004002b,
+0x00000008,0x0000031c,0x3dc28f5c,0x0004002b,
+0x00000008,0x0000031d,0x3f6f1aa0,0x0004002b,
+0x00000008,0x0000031e,0xbba3d70a,0x0006002c,
+0x00000011,0x0000031f,0x0000031c,0x0000031d,
+0x0000031e,0x0004002b,0x00000008,0x00000324,
+0xbc9374bc,0x0004002b,0x00000008,0x00000325,
+0xbce56042,0x0004002b,0x00000008,0x00000326,
+0x3f874bc7,0x0006002c,0x00000011,0x00000327,
+0x00000324,0x00000325,0x00000326,0x0004002b,
+0x00000006,0x00000338,0x00000013,0x0004002b,
+0x00000006,0x00000345,0x00000014,0x0004002b,
+0x00000008,0x0000034d,0x3e21cac1,0x0004002b,
+0x00000008,0x00000352,0x3d1fbe77,0x0004002b,
+0x00000008,0x00000357,0x3f076c8b,0x0004002b,
+0x00000008,0x0000035c,0x3f347ae1,0x0004002b,
+0x00000008,0x00000367,0x3e916873,0x0004002b,
+0x00000008,0x0000036b,0x3f2b020c,0x0004002b,
+0x00000008,0x00000370,0x3e126e98,0x0004002b,
+0x00000008,0x00000375,0x3c83126f,0x0004002b,
+0x00000008,0x0000037a,0x3c75c28f,0x0004002b,
+0x00000008,0x0000037f,0x3d851eb8,0x0004002b,
+0x00000008,0x00000386,0xbc75c28f,0x0004002b,
+0x00000008,0x0000038a,0x3cdd2f1b,0x0004002b,
+0x00000008,0x0000038f,0x3cac0831,0x0004002b,
+0x00000008,0x00000398,0x3d99999a,0x0004002b,
+0x00000008,0x0000039d,0x3f6fdf3b,0x0004002b,
+0x00000006,0x000003a9,0x00000015,0x0004002b,
+0x00000008,0x000003b9,0x3f4ccccd,0x0004002b,
+0x00000008,0x000003e5,0x3f866666,0x0004002b,
+0x00000006,0x0000043f,0x00000016,0x0004002b,
+0x00000008,0x00000474,0x3f8ccccd,0x00040020,
+0x00000501,0x00000003,0x0000000a,0x0004003b,
+0x00000501,0x00000502,0x00000003,0x0006002c,
+0x00000011,0x00000b16,0x0000006e,0x0000006e,
+0x0000006e,0x0006002c,0x00000011,0x00000b17,
+0x00000060,0x00000060,0x00000060,0x0006002c,
+0x00000011,0x00000b18,0x00000077,0x00000077,
+0x00000077,0x0006002c,0x00000011,0x00000b19,
+0x00000030,0x00000030,0x00000030,0x0004002b,
+0x00000008,0x00000b1b,0x3e8ccccd,0x0007002c,
+0x0000000a,0x00000b1c,0x00000285,0x00000b1b,
+0x00000b1b,0x00000060,0x0007002c,0x0000000a,
+0x00000b1d,0x0000006e,0x0000006e,0x0000006e,
+0x0000006e,0x0007002c,0x0000000a,0x00000b1e,
+0x00000060,0x00000060,0x00000060,0x00000060,
+0x0007002c,0x0000000a,0x00000b1f,0x0000006e,
+0x00000060,0x0000006e,0x00000060,0x0007002c,
+0x0000000a,0x00000b20,0x0000006e,0x0000006e,
+0x00000060,0x00000060,0x0004002b,0x00000008,
+0x00000b21,0xbb449ba6,0x0004002b,0x00000008,
+0x00000b22,0xbac49ba6,0x0004002b,0x00000008,
+0x00000b23,0x3b449ba6,0x0004002b,0x00000008,
+0x00000b24,0x3e4ccccc,0x0007002c,0x0000000a,
+0x00000b25,0x000003b9,0x00000b24,0x0000006e,
+0x00000060,0x0007002c,0x0000000a,0x00000b26,
+0x00000b1b,0x00000285,0x00000b1b,0x00000060,
+0x0007002c,0x0000000a,0x00000b27,0x0000006e,
+0x00000b24,0x000003b9,0x00000060,0x0004002b,
+0x00000008,0x00000b28,0x3c072b02,0x0004002b,
+0x00000008,0x00000b29,0xbb872b02,0x0004002b,
+0x00000008,0x00000b2a,0xbc072b02,0x0004002b,
+0x00000008,0x00000b2b,0x3b872b02,0x0007002c,
+0x0000000a,0x00000b2c,0x00000060,0x0000006e,
+0x0000006e,0x00000060,0x0007002c,0x0000000a,
+0x00000b2d,0x00000b1b,0x00000b1b,0x00000285,
+0x00000060,0x0004002b,0x00000008,0x00000b2f,
+0x3f80511a,0x0004002b,0x00000008,0x00000b30,
+0x3ccac083,0x0004002b,0x00000008,0x00000b31,
+0xbc4ac083,0x0004002b,0x00000008,0x00000b32,
+0xbccac083,0x0004002b,0x00000008,0x00000b33,
+0x3d4ac083,0x00050036,0x00000002,0x00000004,
+0x00000000,0x00000003,0x000200f8,0x00000005,
+0x000300f7,0x000008f4,0x00000000,0x000300fb,
+0x00000046,0x0000056a,0x000200f8,0x0000056a,
+0x00050041,0x00000020,0x0000056b,0x0000001e,
+0x000000a2,0x0004003d,0x00000006,0x0000056c,
+0x0000056b,0x000500aa,0x00000024,0x0000056e,
+0x0000056c,0x00000023,0x000500aa,0x00000024,
+0x00000570,0x0000056c,0x000000a8,0x000500a6,
+0x00000024,0x00000571,0x0000056e,0x00000570,
+0x000500aa,0x00000024,0x00000573,0x0000056c,
+0x00000055,0x000500a6,0x00000024,0x00000574,
+0x00000571,0x00000573,0x000500aa,0x00000024,
+0x00000576,0x0000056c,0x00000078,0x000500a6,
+0x00000024,0x00000577,0x00000574,0x00000576,
+0x000500aa,0x00000024,0x00000579,0x0000056c,
+0x00000094,0x000500a6,0x00000024,0x0000057a,
+0x00000577,0x00000579,0x000300f7,0x00000593,
+0x00000000,0x000400fa,0x0000057a,0x0000057b,
+0x00000593,0x000200f8,0x0000057b,0x00050041,
+0x000000b9,0x0000057c,0x000000b8,0x00000046,
+0x0004003d,0x00000008,0x0000057d,0x0000057c,
+0x000500b8,0x00000024,0x0000057e,0x0000057d,
+0x00000030,0x000600a9,0x00000006,0x0000057f,
+0x0000057e,0x00000023,0x00000029,0x000300f7,
+0x0000058c,0x00000000,0x000400fa,0x0000057e,
+0x00000583,0x00000587,0x000200f8,0x00000587,
+0x00050083,0x00000008,0x0000058a,0x0000057d,
+0x00000030,0x00050085,0x00000008,0x0000058b,
+0x0000058a,0x000000c7,0x000200f9,0x0000058c,
+0x000200f8,0x00000583,0x00050085,0x00000008,
+0x00000586,0x0000057d,0x000000c7,0x000200f9,
+0x0000058c,0x000200f8,0x0000058c,0x000700f5,
+0x00000008,0x00000b10,0x00000586,0x00000583,
+0x0000058b,0x00000587,0x00050041,0x000000b9,
+0x00000590,0x000000b8,0x0000004b,0x0004003d,
+0x00000008,0x00000591,0x00000590,0x00050041,
+0x00000020,0x000008fb,0x0000001e,0x0000001f,
+0x0004003d,0x00000006,0x000008fc,0x000008fb,
+0x000500ab,0x00000024,0x000008fd,0x000008fc,
+0x00000023,0x000300f7,0x00000903,0x00000000,
+0x000400fa,0x000008fd,0x000008fe,0x00000901,
+0x000200f8,0x00000901,0x000200f9,0x00000903,
+0x000200f8,0x000008fe,0x00050082,0x00000006,
+0x00000900,0x00000029,0x0000057f,0x000200f9,
+0x00000903,0x000200f8,0x00000903,0x000700f5,
+0x00000006,0x00000b11,0x00000900,0x000008fe,
+0x0000057f,0x00000901,0x0004006f,0x00000008,
+0x00000908,0x00000b11,0x00050081,0x00000008,
+0x00000b3e,0x00000b10,0x00000908,0x00050085,
+0x00000008,0x0000090a,0x00000030,0x00000b3e,
+0x0004003d,0x00000038,0x0000090b,0x0000003a,
+0x00050050,0x0000003e,0x0000090e,0x0000090a,
+0x00000591,0x00050057,0x0000000a,0x0000090f,
+0x0000090b,0x0000090e,0x000200f9,0x000008f4,
+0x000200f8,0x00000593,0x000500aa,0x00000024,
+0x00000595,0x0000056c,0x00000029,0x000500aa,
+0x00000024,0x00000597,0x0000056c,0x00000044,
+0x000500a6,0x00000024,0x00000598,0x00000595,
+0x00000597,0x000500aa,0x00000024,0x0000059a,
+0x0000056c,0x000000de,0x000500a6,0x00000024,
+0x0000059b,0x00000598,0x0000059a,0x000500aa,
+0x00000024,0x0000059d,0x0000056c,0x000000e2,
+0x000500a6,0x00000024,0x0000059e,0x0000059b,
+0x0000059d,0x000500aa,0x00000024,0x000005a0,
+0x0000056c,0x000000e6,0x000500a6,0x00000024,
+0x000005a1,0x0000059e,0x000005a0,0x000300f7,
+0x000005dd,0x00000000,0x000400fa,0x000005a1,
+0x000005a2,0x000005dd,0x000200f8,0x000005a2,
+0x000300f7,0x000005ab,0x00000000,0x000400fa,
+0x00000597,0x000005a5,0x000005a6,0x000200f8,
+0x000005a6,0x000500ab,0x00000024,0x000005a8,
+0x0000056c,0x00000029,0x000600a9,0x00000008,
+0x00000b3f,0x000005a8,0x000000f6,0x0000006e,
+0x000200f9,0x000005ab,0x000200f8,0x000005a5,
+0x000200f9,0x000005ab,0x000200f8,0x000005ab,
+0x000700f5,0x00000008,0x00000b0b,0x000000f0,
+0x000005a5,0x00000b3f,0x000005a6,0x000500ba,
+0x00000024,0x000005ad,0x00000b0b,0x0000006e,
+0x000300f7,0x000005b5,0x00000000,0x000400fa,
+0x000005ad,0x000005ae,0x000005b4,0x000200f8,
+0x000005b4,0x000200f9,0x000005b5,0x000200f8,
+0x000005ae,0x00060041,0x00000020,0x000005b0,
+0x0000001e,0x00000023,0x0000004b,0x0004003d,
+0x00000006,0x000005b1,0x000005b0,0x0004006f,
+0x00000008,0x000005b2,0x000005b1,0x00050088,
+0x00000008,0x000005b3,0x00000b0b,0x000005b2,
+0x000200f9,0x000005b5,0x000200f8,0x000005b5,
+0x000700f5,0x00000008,0x00000b0d,0x000005b3,
+0x000005ae,0x0000006e,0x000005b4,0x00050083,
+0x00000008,0x000005b8,0x00000060,0x00000b0d,
+0x00050085,0x00000008,0x000005b9,0x000005b8,
+0x00000030,0x00050041,0x000000b9,0x000005ba,
+0x000000b8,0x0000004b,0x0004003d,0x00000008,
+0x000005bb,0x000005ba,0x000500b8,0x00000024,
+0x000005bd,0x000005bb,0x000005b9,0x000300f7,
+0x000005dc,0x00000000,0x000400fa,0x000005bd,
+0x000005be,0x000005c6,0x000200f8,0x000005c6,
+0x00050081,0x00000008,0x000005cb,0x000005b9,
+0x00000b0d,0x000500be,0x00000024,0x000005cc,
+0x000005bb,0x000005cb,0x000300f7,0x000005db,
+0x00000000,0x000400fa,0x000005cc,0x000005cd,
+0x000005da,0x000200f8,0x000005da,0x000200f9,
+0x000008f4,0x000200f8,0x000005cd,0x00050083,
+0x00000008,0x000005d3,0x000005bb,0x000005cb,
+0x00050088,0x00000008,0x000005d5,0x000005d3,
+0x000005b9,0x00050041,0x000000b9,0x000005d6,
+0x000000b8,0x00000046,0x0004003d,0x00000008,
+0x000005d7,0x000005d6,0x00050041,0x00000020,
+0x0000092f,0x0000001e,0x0000001f,0x0004003d,
+0x00000006,0x00000930,0x0000092f,0x000500ab,
+0x00000024,0x00000931,0x00000930,0x00000023,
+0x000600a9,0x00000006,0x00000b40,0x00000931,
+0x00000023,0x00000029,0x0004006f,0x00000008,
+0x0000093c,0x00000b40,0x00050081,0x00000008,
+0x00000b3c,0x000005d7,0x0000093c,0x00050085,
+0x00000008,0x0000093e,0x00000030,0x00000b3c,
+0x0004003d,0x00000038,0x0000093f,0x0000003a,
+0x00050050,0x0000003e,0x00000942,0x0000093e,
+0x000005d5,0x00050057,0x0000000a,0x00000943,
+0x0000093f,0x00000942,0x000200f9,0x000008f4,
+0x000200f8,0x000005db,0x000100ff,0x000200f8,
+0x000005be,0x00050088,0x00000008,0x000005c2,
+0x000005bb,0x000005b9,0x00050041,0x000000b9,
+0x000005c3,0x000000b8,0x00000046,0x0004003d,
+0x00000008,0x000005c4,0x000005c3,0x00050041,
+0x00000020,0x00000915,0x0000001e,0x0000001f,
+0x0004003d,0x00000006,0x00000916,0x00000915,
+0x000500ab,0x00000024,0x00000917,0x00000916,
+0x00000023,0x000600a9,0x00000006,0x00000b41,
+0x00000917,0x00000029,0x00000023,0x0004006f,
+0x00000008,0x00000922,0x00000b41,0x00050081,
+0x00000008,0x00000b3d,0x000005c4,0x00000922,
+0x00050085,0x00000008,0x00000924,0x00000030,
+0x00000b3d,0x0004003d,0x00000038,0x00000925,
+0x0000003a,0x00050050,0x0000003e,0x00000928,
+0x00000924,0x000005c2,0x00050057,0x0000000a,
+0x00000929,0x00000925,0x00000928,0x000200f9,
+0x000008f4,0x000200f8,0x000005dc,0x000100ff,
+0x000200f8,0x000005dd,0x000500aa,0x00000024,
+0x000005df,0x0000056c,0x000000a2,0x000300f7,
+0x000005ec,0x00000000,0x000400fa,0x000005df,
+0x000005e0,0x000005ec,0x000200f8,0x000005e0,
+0x00050041,0x000000b9,0x000005e1,0x0000013c,
+0x0000004b,0x0004003d,0x00000008,0x000005e2,
+0x000005e1,0x0004006e,0x00000006,0x000005e3,
+0x000005e2,0x000500c7,0x00000006,0x000005e5,
+0x000005e3,0x00000029,0x00050041,0x000000b9,
+0x000005e7,0x000000b8,0x00000046,0x0004003d,
+0x00000008,0x000005e8,0x000005e7,0x00050041,
+0x000000b9,0x000005e9,0x000000b8,0x0000004b,
+0x0004003d,0x00000008,0x000005ea,0x000005e9,
+0x00050041,0x00000020,0x00000949,0x0000001e,
+0x0000001f,0x0004003d,0x00000006,0x0000094a,
+0x00000949,0x000500ab,0x00000024,0x0000094b,
+0x0000094a,0x00000023,0x000300f7,0x00000951,
+0x00000000,0x000400fa,0x0000094b,0x0000094c,
+0x0000094f,0x000200f8,0x0000094f,0x000200f9,
+0x00000951,0x000200f8,0x0000094c,0x00050082,
+0x00000006,0x0000094e,0x00000029,0x000005e5,
+0x000200f9,0x00000951,0x000200f8,0x00000951,
+0x000700f5,0x00000006,0x00000b0a,0x0000094e,
+0x0000094c,0x000005e5,0x0000094f,0x0004006f,
+0x00000008,0x00000956,0x00000b0a,0x00050081,
+0x00000008,0x00000b3b,0x000005e8,0x00000956,
+0x00050085,0x00000008,0x00000958,0x00000030,
+0x00000b3b,0x0004003d,0x00000038,0x00000959,
+0x0000003a,0x00050050,0x0000003e,0x0000095c,
+0x00000958,0x000005ea,0x00050057,0x0000000a,
+0x0000095d,0x00000959,0x0000095c,0x000200f9,
+0x000008f4,0x000200f8,0x000005ec,0x000500aa,
+0x00000024,0x000005ee,0x0000056c,0x0000001f,
+0x000300f7,0x000005fb,0x00000000,0x000400fa,
+0x000005ee,0x000005ef,0x000005fb,0x000200f8,
+0x000005ef,0x00050041,0x000000b9,0x000005f0,
+0x0000013c,0x00000046,0x0004003d,0x00000008,
+0x000005f1,0x000005f0,0x0004006e,0x00000006,
+0x000005f2,0x000005f1,0x000500c7,0x00000006,
+0x000005f4,0x000005f2,0x00000029,0x00050041,
+0x000000b9,0x000005f6,0x000000b8,0x00000046,
+0x0004003d,0x00000008,0x000005f7,0x000005f6,
+0x00050041,0x000000b9,0x000005f8,0x000000b8,
+0x0000004b,0x0004003d,0x00000008,0x000005f9,
+0x000005f8,0x00050041,0x00000020,0x00000963,
+0x0000001e,0x0000001f,0x0004003d,0x00000006,
+0x00000964,0x00000963,0x000500ab,0x00000024,
+0x00000965,0x00000964,0x00000023,0x000300f7,
+0x0000096b,0x00000000,0x000400fa,0x00000965,
+0x00000966,0x00000969,0x000200f8,0x00000969,
+0x000200f9,0x0000096b,0x000200f8,0x00000966,
+0x00050082,0x00000006,0x00000968,0x00000029,
+0x000005f4,0x000200f9,0x0000096b,0x000200f8,
+0x0000096b,0x000700f5,0x00000006,0x00000b09,
+0x00000968,0x00000966,0x000005f4,0x00000969,
+0x0004006f,0x00000008,0x00000970,0x00000b09,
+0x00050081,0x00000008,0x00000b3a,0x000005f7,
+0x00000970,0x00050085,0x00000008,0x00000972,
+0x00000030,0x00000b3a,0x0004003d,0x00000038,
+0x00000973,0x0000003a,0x00050050,0x0000003e,
+0x00000976,0x00000972,0x000005f9,0x00050057,
+0x0000000a,0x00000977,0x00000973,0x00000976,
+0x000200f9,0x000008f4,0x000200f8,0x000005fb,
+0x000500aa,0x00000024,0x000005fd,0x0000056c,
+0x00000163,0x000300f7,0x0000060f,0x00000000,
+0x000400fa,0x000005fd,0x000005fe,0x0000060f,
+0x000200f8,0x000005fe,0x00050041,0x000000b9,
+0x000005ff,0x0000013c,0x0000004b,0x0004003d,
+0x00000008,0x00000600,0x000005ff,0x0004006e,
+0x00000006,0x00000601,0x00000600,0x00050041,
+0x000000b9,0x00000602,0x0000013c,0x00000046,
+0x0004003d,0x00000008,0x00000603,0x00000602,
+0x0004006e,0x00000006,0x00000604,0x00000603,
+0x00050080,0x00000006,0x00000607,0x00000601,
+0x00000604,0x000500c7,0x00000006,0x00000608,
+0x00000607,0x00000029,0x00050041,0x000000b9,
+0x0000060a,0x000000b8,0x00000046,0x0004003d,
+0x00000008,0x0000060b,0x0000060a,0x00050041,
+0x000000b9,0x0000060c,0x000000b8,0x0000004b,
+0x0004003d,0x00000008,0x0000060d,0x0000060c,
+0x00050041,0x00000020,0x0000097d,0x0000001e,
+0x0000001f,0x0004003d,0x00000006,0x0000097e,
+0x0000097d,0x000500ab,0x00000024,0x0000097f,
+0x0000097e,0x00000023,0x000300f7,0x00000985,
+0x00000000,0x000400fa,0x0000097f,0x00000980,
+0x00000983,0x000200f8,0x00000983,0x000200f9,
+0x00000985,0x000200f8,0x00000980,0x00050082,
+0x00000006,0x00000982,0x00000029,0x00000608,
+0x000200f9,0x00000985,0x000200f8,0x00000985,
+0x000700f5,0x00000006,0x00000b08,0x00000982,
+0x00000980,0x00000608,0x00000983,0x0004006f,
+0x00000008,0x0000098a,0x00000b08,0x00050081,
+0x00000008,0x00000b39,0x0000060b,0x0000098a,
+0x00050085,0x00000008,0x0000098c,0x00000030,
+0x00000b39,0x0004003d,0x00000038,0x0000098d,
+0x0000003a,0x00050050,0x0000003e,0x00000990,
+0x0000098c,0x0000060d,0x00050057,0x0000000a,
+0x00000991,0x0000098d,0x00000990,0x000200f9,
+0x000008f4,0x000200f8,0x0000060f,0x000500aa,
+0x00000024,0x00000611,0x0000056c,0x0000008c,
+0x000300f7,0x00000639,0x00000000,0x000400fa,
+0x00000611,0x00000612,0x00000639,0x000200f8,
+0x00000612,0x00050041,0x000000b9,0x00000613,
+0x000000b8,0x0000004b,0x0004003d,0x00000008,
+0x00000614,0x00000613,0x000500b8,0x00000024,
+0x00000615,0x00000614,0x00000184,0x000400a8,
+0x00000024,0x00000616,0x00000615,0x000300f7,
+0x0000061b,0x00000000,0x000400fa,0x00000616,
+0x00000617,0x0000061b,0x000200f8,0x00000617,
+0x000500be,0x00000024,0x0000061a,0x00000614,
+0x0000018b,0x000200f9,0x0000061b,0x000200f8,
+0x0000061b,0x000700f5,0x00000024,0x0000061c,
+0x00000615,0x00000612,0x0000061a,0x00000617,
+0x000300f7,0x0000061e,0x00000000,0x000400fa,
+0x0000061c,0x0000061d,0x0000061e,0x000200f8,
+0x0000061d,0x000200f9,0x000008f4,0x000200f8,
+0x0000061e,0x00050083,0x00000008,0x00000621,
+0x00000614,0x00000184,0x00050085,0x00000008,
+0x00000622,0x00000621,0x000000c7,0x00050041,
+0x000000b9,0x00000623,0x000000b8,0x00000046,
+0x0004003d,0x00000008,0x00000624,0x00000623,
+0x000500b8,0x00000024,0x00000625,0x00000624,
+0x00000030,0x000600a9,0x00000006,0x00000626,
+0x00000625,0x00000023,0x00000029,0x000300f7,
+0x00000633,0x00000000,0x000400fa,0x00000625,
+0x0000062a,0x0000062e,0x000200f8,0x0000062e,
+0x00050083,0x00000008,0x00000631,0x00000624,
+0x00000030,0x00050085,0x00000008,0x00000632,
+0x00000631,0x000000c7,0x000200f9,0x00000633,
+0x000200f8,0x0000062a,0x00050085,0x00000008,
+0x0000062d,0x00000624,0x000000c7,0x000200f9,
+0x00000633,0x000200f8,0x00000633,0x000700f5,
+0x00000008,0x00000b06,0x0000062d,0x0000062a,
+0x00000632,0x0000062e,0x00050041,0x00000020,
+0x00000997,0x0000001e,0x0000001f,0x0004003d,
+0x00000006,0x00000998,0x00000997,0x000500ab,
+0x00000024,0x00000999,0x00000998,0x00000023,
+0x000300f7,0x0000099f,0x00000000,0x000400fa,
+0x00000999,0x0000099a,0x0000099d,0x000200f8,
+0x0000099d,0x000200f9,0x0000099f,0x000200f8,
+0x0000099a,0x00050082,0x00000006,0x0000099c,
+0x00000029,0x00000626,0x000200f9,0x0000099f,
+0x000200f8,0x0000099f,0x000700f5,0x00000006,
+0x00000b07,0x0000099c,0x0000099a,0x00000626,
+0x0000099d,0x0004006f,0x00000008,0x000009a4,
+0x00000b07,0x00050081,0x00000008,0x00000b38,
+0x00000b06,0x000009a4,0x00050085,0x00000008,
+0x000009a6,0x00000030,0x00000b38,0x0004003d,
+0x00000038,0x000009a7,0x0000003a,0x00050050,
+0x0000003e,0x000009aa,0x000009a6,0x00000622,
+0x00050057,0x0000000a,0x000009ab,0x000009a7,
+0x000009aa,0x000200f9,0x000008f4,0x000200f8,
+0x00000639,0x000500aa,0x00000024,0x0000063b,
+0x0000056c,0x000001b4,0x000300f7,0x0000065e,
+0x00000000,0x000400fa,0x0000063b,0x0000063c,
+0x0000065e,0x000200f8,0x0000063c,0x00050041,
+0x000000b9,0x0000063d,0x000000b8,0x00000046,
+0x0004003d,0x00000008,0x0000063e,0x0000063d,
+0x000500b8,0x00000024,0x0000063f,0x0000063e,
+0x00000030,0x000600a9,0x00000006,0x00000640,
+0x0000063f,0x00000023,0x00000029,0x000300f7,
+0x0000064d,0x00000000,0x000400fa,0x0000063f,
+0x00000644,0x00000648,0x000200f8,0x00000648,
+0x00050083,0x00000008,0x0000064b,0x0000063e,
+0x00000030,0x00050085,0x00000008,0x0000064c,
+0x0000064b,0x000000c7,0x000200f9,0x0000064d,
+0x000200f8,0x00000644,0x00050085,0x00000008,
+0x00000647,0x0000063e,0x000000c7,0x000200f9,
+0x0000064d,0x000200f8,0x0000064d,0x000700f5,
+0x00000008,0x00000b03,0x00000647,0x00000644,
+0x0000064c,0x00000648,0x000500aa,0x00000024,
+0x00000650,0x00000640,0x00000023,0x000300f7,
+0x00000658,0x00000000,0x000400fa,0x00000650,
+0x00000651,0x00000655,0x000200f8,0x00000655,
+0x00050041,0x000000b9,0x00000656,0x000000b8,
+0x0000004b,0x0004003d,0x00000008,0x00000657,
+0x00000656,0x000200f9,0x00000658,0x000200f8,
+0x00000651,0x00050041,0x000000b9,0x00000652,
+0x000000b8,0x0000004b,0x0004003d,0x00000008,
+0x00000653,0x00000652,0x00050083,0x00000008,
+0x00000654,0x00000060,0x00000653,0x000200f9,
+0x00000658,0x000200f8,0x00000658,0x000700f5,
+0x00000008,0x00000b04,0x00000654,0x00000651,
+0x00000657,0x00000655,0x00050041,0x00000020,
+0x000009b1,0x0000001e,0x0000001f,0x0004003d,
+0x00000006,0x000009b2,0x000009b1,0x000500ab,
+0x00000024,0x000009b3,0x000009b2,0x00000023,
+0x000300f7,0x000009b9,0x00000000,0x000400fa,
+0x000009b3,0x000009b4,0x000009b7,0x000200f8,
+0x000009b7,0x000200f9,0x000009b9,0x000200f8,
+0x000009b4,0x00050082,0x00000006,0x000009b6,
+0x00000029,0x00000640,0x000200f9,0x000009b9,
+0x000200f8,0x000009b9,0x000700f5,0x00000006,
+0x00000b05,0x000009b6,0x000009b4,0x00000640,
+0x000009b7,0x0004006f,0x00000008,0x000009be,
+0x00000b05,0x00050081,0x00000008,0x00000b37,
+0x00000b03,0x000009be,0x00050085,0x00000008,
+0x000009c0,0x00000030,0x00000b37,0x0004003d,
+0x00000038,0x000009c1,0x0000003a,0x00050050,
+0x0000003e,0x000009c4,0x000009c0,0x00000b04,
+0x00050057,0x0000000a,0x000009c5,0x000009c1,
+0x000009c4,0x000200f9,0x000008f4,0x000200f8,
+0x0000065e,0x000500aa,0x00000024,0x00000660,
+0x0000056c,0x000001e3,0x000300f7,0x00000667,
+0x00000000,0x000400fa,0x00000660,0x00000661,
+0x00000667,0x000200f8,0x00000661,0x00050041,
+0x000000b9,0x00000662,0x000000b8,0x00000046,
+0x0004003d,0x00000008,0x00000663,0x00000662,
+0x00050041,0x000000b9,0x00000664,0x000000b8,
+0x0000004b,0x0004003d,0x00000008,0x00000665,
+0x00000664,0x00050041,0x00000020,0x000009cb,
+0x0000001e,0x0000001f,0x0004003d,0x00000006,
+0x000009cc,0x000009cb,0x000500ab,0x00000024,
+0x000009cd,0x000009cc,0x00000023,0x000600a9,
+0x00000006,0x00000b42,0x000009cd,0x00000029,
+0x00000023,0x0004006f,0x00000008,0x000009d8,
+0x00000b42,0x00050081,0x00000008,0x00000b36,
+0x00000663,0x000009d8,0x00050085,0x00000008,
+0x000009da,0x00000030,0x00000b36,0x0004003d,
+0x00000038,0x000009db,0x0000003a,0x00050050,
+0x0000003e,0x000009de,0x000009da,0x00000665,
+0x00050057,0x0000000a,0x000009df,0x000009db,
+0x000009de,0x000200f9,0x000008f4,0x000200f8,
+0x00000667,0x00050041,0x000000b9,0x00000668,
+0x000000b8,0x00000046,0x0004003d,0x00000008,
+0x00000669,0x00000668,0x00050041,0x000000b9,
+0x0000066a,0x000000b8,0x0000004b,0x0004003d,
+0x00000008,0x0000066b,0x0000066a,0x00050041,
+0x00000020,0x000009e5,0x0000001e,0x0000001f,
+0x0004003d,0x00000006,0x000009e6,0x000009e5,
+0x000500ab,0x00000024,0x000009e7,0x000009e6,
+0x00000023,0x000600a9,0x00000006,0x00000b43,
+0x000009e7,0x00000029,0x00000023,0x00050085,
+0x00000008,0x000009f0,0x00000030,0x00000669,
+0x0004006f,0x00000008,0x000009f2,0x00000b43,
+0x00050085,0x00000008,0x000009f3,0x00000030,
+0x000009f2,0x00050081,0x00000008,0x000009f4,
+0x000009f0,0x000009f3,0x0004003d,0x00000038,
+0x000009f5,0x0000003a,0x00050050,0x0000003e,
+0x000009f8,0x000009f4,0x0000066b,0x00050057,
+0x0000000a,0x000009f9,0x000009f5,0x000009f8,
+0x000600a9,0x00000006,0x00000b44,0x000009e7,
+0x00000023,0x00000029,0x0004006f,0x00000008,
+0x00000a0c,0x00000b44,0x00050085,0x00000008,
+0x00000a0d,0x00000030,0x00000a0c,0x00050081,
+0x00000008,0x00000a0e,0x000009f0,0x00000a0d,
+0x0004003d,0x00000038,0x00000a0f,0x0000003a,
+0x00050050,0x0000003e,0x00000a12,0x00000a0e,
+0x0000066b,0x00050057,0x0000000a,0x00000a13,
+0x00000a0f,0x00000a12,0x000500aa,0x00000024,
+0x00000673,0x0000056c,0x00000204,0x000300f7,
+0x0000067c,0x00000000,0x000400fa,0x00000673,
+0x00000674,0x0000067c,0x000200f8,0x00000674,
+0x00050051,0x00000008,0x00000676,0x000009f9,
+0x00000000,0x00050051,0x00000008,0x00000678,
+0x00000a13,0x00000001,0x00050051,0x00000008,
+0x0000067a,0x00000a13,0x00000002,0x00070050,
+0x0000000a,0x0000067b,0x00000676,0x00000678,
+0x0000067a,0x00000060,0x000200f9,0x000008f4,
+0x000200f8,0x0000067c,0x000500aa,0x00000024,
+0x0000067e,0x0000056c,0x00000211,0x000300f7,
+0x000006cc,0x00000000,0x000400fa,0x0000067e,
+0x0000067f,0x000006cc,0x000200f8,0x0000067f,
+0x00050051,0x00000008,0x00000681,0x000009f9,
+0x00000000,0x00050085,0x00000008,0x00000682,
+0x00000216,0x00000681,0x00050051,0x00000008,
+0x00000684,0x000009f9,0x00000001,0x00050085,
+0x00000008,0x00000685,0x0000021a,0x00000684,
+0x00050081,0x00000008,0x00000686,0x00000682,
+0x00000685,0x00050051,0x00000008,0x00000688,
+0x000009f9,0x00000002,0x00050085,0x00000008,
+0x00000689,0x0000021f,0x00000688,0x00050081,
+0x00000008,0x0000068a,0x00000686,0x00000689,
+0x00050051,0x00000008,0x0000068c,0x00000a13,
+0x00000000,0x00050085,0x00000008,0x0000068d,
+0x00000224,0x0000068c,0x00050083,0x00000008,
+0x0000068e,0x0000068a,0x0000068d,0x00050051,
+0x00000008,0x00000690,0x00000a13,0x00000001,
+0x00050085,0x00000008,0x00000691,0x00000229,
+0x00000690,0x00050083,0x00000008,0x00000692,
+0x0000068e,0x00000691,0x00050051,0x00000008,
+0x00000694,0x00000a13,0x00000002,0x00050085,
+0x00000008,0x00000695,0x0000022e,0x00000694,
+0x00050083,0x00000008,0x00000696,0x00000692,
+0x00000695,0x0008000c,0x00000008,0x00000697,
+0x00000001,0x0000002b,0x00000696,0x0000006e,
+0x00000060,0x00050085,0x00000008,0x0000069a,
+0x00000235,0x00000681,0x00050085,0x00000008,
+0x0000069d,0x00000239,0x00000684,0x00050083,
+0x00000008,0x0000069e,0x0000069a,0x0000069d,
+0x00050085,0x00000008,0x000006a1,0x0000023e,
+0x00000688,0x00050083,0x00000008,0x000006a2,
+0x0000069e,0x000006a1,0x00050085,0x00000008,
+0x000006a5,0x00000243,0x0000068c,0x00050081,
+0x00000008,0x000006a6,0x000006a2,0x000006a5,
+0x00050085,0x00000008,0x000006a9,0x00000248,
+0x00000690,0x00050081,0x00000008,0x000006aa,
+0x000006a6,0x000006a9,0x00050085,0x00000008,
+0x000006ad,0x0000024d,0x00000694,0x00050081,
+0x00000008,0x000006ae,0x000006aa,0x000006ad,
+0x0008000c,0x00000008,0x000006af,0x00000001,
+0x0000002b,0x000006ae,0x0000006e,0x00000060,
+0x00050085,0x00000008,0x000006b2,0x00000254,
+0x00000681,0x00050085,0x00000008,0x000006b5,
+0x00000258,0x00000684,0x00050083,0x00000008,
+0x000006b6,0x000006b2,0x000006b5,0x00050085,
+0x00000008,0x000006b9,0x0000025d,0x00000688,
+0x00050083,0x00000008,0x000006ba,0x000006b6,
+0x000006b9,0x00050085,0x00000008,0x000006bd,
+0x00000262,0x0000068c,0x00050083,0x00000008,
+0x000006be,0x000006ba,0x000006bd,0x00050085,
+0x00000008,0x000006c1,0x00000267,0x00000690,
+0x00050083,0x00000008,0x000006c2,0x000006be,
+0x000006c1,0x00050085,0x00000008,0x000006c5,
+0x0000026c,0x00000694,0x00050081,0x00000008,
+0x000006c6,0x000006c2,0x000006c5,0x0008000c,
+0x00000008,0x000006c7,0x00000001,0x0000002b,
+0x000006c6,0x0000006e,0x00000060,0x00070050,
+0x0000000a,0x000006cb,0x00000697,0x000006af,
+0x000006c7,0x00000060,0x000200f9,0x000008f4,
+0x000200f8,0x000006cc,0x000500aa,0x00000024,
+0x000006ce,0x0000056c,0x00000278,0x000300f7,
+0x00000754,0x00000000,0x000400fa,0x000006ce,
+0x000006cf,0x00000754,0x000200f8,0x000006cf,
+0x00050085,0x0000000a,0x000006df,0x000009f9,
+0x00000b1c,0x0008000c,0x0000000a,0x000006e2,
+0x00000001,0x0000002b,0x000006df,0x00000b1d,
+0x00000b1e,0x00050051,0x00000008,0x000006e4,
+0x000006e2,0x00000000,0x00050051,0x00000008,
+0x000006e6,0x000006e2,0x00000001,0x00050081,
+0x00000008,0x000006e7,0x000006e4,0x000006e6,
+0x00050051,0x00000008,0x000006e9,0x000006e2,
+0x00000002,0x00050081,0x00000008,0x000006ea,
+0x000006e7,0x000006e9,0x0007000c,0x00000008,
+0x000006eb,0x00000001,0x0000001a,0x000006ea,
+0x00000060,0x00050085,0x0000000a,0x000006f2,
+0x00000a13,0x00000b1f,0x0008000c,0x0000000a,
+0x000006f5,0x00000001,0x0000002b,0x000006f2,
+0x00000b1d,0x00000b1e,0x00050051,0x00000008,
+0x000006f7,0x000006f5,0x00000000,0x00050051,
+0x00000008,0x000006f9,0x000006f5,0x00000001,
+0x00050081,0x00000008,0x000006fa,0x000006f7,
+0x000006f9,0x00050051,0x00000008,0x000006fc,
+0x000006f5,0x00000002,0x00050081,0x00000008,
+0x000006fd,0x000006fa,0x000006fc,0x0007000c,
+0x00000008,0x000006fe,0x00000001,0x0000001a,
+0x000006fd,0x000002b3,0x00050085,0x0000000a,
+0x00000705,0x00000a13,0x00000b20,0x0008000c,
+0x0000000a,0x00000708,0x00000001,0x0000002b,
+0x00000705,0x00000b1d,0x00000b1e,0x00050051,
+0x00000008,0x0000070a,0x00000708,0x00000000,
+0x00050051,0x00000008,0x0000070c,0x00000708,
+0x00000001,0x00050081,0x00000008,0x0000070d,
+0x0000070a,0x0000070c,0x00050051,0x00000008,
+0x0000070f,0x00000708,0x00000002,0x00050081,
+0x00000008,0x00000710,0x0000070d,0x0000070f,
+0x0007000c,0x00000008,0x00000711,0x00000001,
+0x0000001a,0x00000710,0x000002b3,0x00050085,
+0x00000008,0x00000719,0x000006eb,0x0000027e,
+0x00050081,0x00000008,0x0000071a,0x000006eb,
+0x00000719,0x00050085,0x00000008,0x0000071f,
+0x000006fe,0x00000b21,0x00050081,0x00000008,
+0x00000720,0x0000071a,0x0000071f,0x00050085,
+0x00000008,0x00000725,0x00000711,0x00000b21,
+0x00050081,0x00000008,0x00000726,0x00000720,
+0x00000725,0x00050085,0x00000008,0x0000072e,
+0x000006eb,0x00000b22,0x00050081,0x00000008,
+0x0000072f,0x000006fe,0x0000072e,0x00050085,
+0x00000008,0x00000734,0x000006fe,0x00000b23,
+0x00050081,0x00000008,0x00000735,0x0000072f,
+0x00000734,0x00050085,0x00000008,0x0000073a,
+0x00000711,0x00000b22,0x00050081,0x00000008,
+0x0000073b,0x00000735,0x0000073a,0x00050081,
+0x00000008,0x00000744,0x00000711,0x0000072e,
+0x00050085,0x00000008,0x00000749,0x000006fe,
+0x00000b22,0x00050081,0x00000008,0x0000074a,
+0x00000744,0x00000749,0x00050085,0x00000008,
+0x0000074f,0x00000711,0x00000b23,0x00050081,
+0x00000008,0x00000750,0x0000074a,0x0000074f,
+0x00070050,0x0000000a,0x00000b35,0x00000726,
+0x0000073b,0x00000750,0x00000060,0x000200f9,
+0x000008f4,0x000200f8,0x00000754,0x000500aa,
+0x00000024,0x00000756,0x0000056c,0x0000030f,
+0x000300f7,0x0000076c,0x00000000,0x000400fa,
+0x00000756,0x00000757,0x0000076c,0x000200f8,
+0x00000757,0x0008004f,0x00000011,0x00000759,
+0x000009f9,0x000009f9,0x00000000,0x00000001,
+0x00000002,0x00050094,0x00000008,0x0000075a,
+0x00000317,0x00000759,0x0008004f,0x00000011,
+0x0000075c,0x00000a13,0x00000a13,0x00000000,
+0x00000001,0x00000002,0x00050094,0x00000008,
+0x0000075d,0x0000031f,0x0000075c,0x00050094,
+0x00000008,0x00000760,0x00000327,0x0000075c,
+0x00060050,0x00000011,0x00000764,0x0000075a,
+0x0000075d,0x00000760,0x0008000c,0x00000011,
+0x00000767,0x00000001,0x0000002b,0x00000764,
+0x00000b16,0x00000b17,0x00050051,0x00000008,
+0x00000768,0x00000767,0x00000000,0x00050051,
+0x00000008,0x00000769,0x00000767,0x00000001,
+0x00050051,0x00000008,0x0000076a,0x00000767,
+0x00000002,0x00070050,0x0000000a,0x0000076b,
+0x00000768,0x00000769,0x0000076a,0x00000060,
+0x000200f9,0x000008f4,0x000200f8,0x0000076c,
+0x000500aa,0x00000024,0x0000076e,0x0000056c,
+0x00000338,0x000300f7,0x00000777,0x00000000,
+0x000400fa,0x0000076e,0x0000076f,0x00000777,
+0x000200f8,0x0000076f,0x00050051,0x00000008,
+0x00000771,0x00000a13,0x00000000,0x00050051,
+0x00000008,0x00000773,0x000009f9,0x00000001,
+0x00050051,0x00000008,0x00000775,0x00000a13,
+0x00000002,0x00070050,0x0000000a,0x00000776,
+0x00000771,0x00000773,0x00000775,0x00000060,
+0x000200f9,0x000008f4,0x000200f8,0x00000777,
+0x000500aa,0x00000024,0x00000779,0x0000056c,
+0x00000345,0x000300f7,0x000007c7,0x00000000,
+0x000400fa,0x00000779,0x0000077a,0x000007c7,
+0x000200f8,0x0000077a,0x00050051,0x00000008,
+0x0000077c,0x000009f9,0x00000000,0x00050085,
+0x00000008,0x0000077d,0x00000235,0x0000077c,
+0x00050051,0x00000008,0x0000077f,0x000009f9,
+0x00000001,0x00050085,0x00000008,0x00000780,
+0x0000034d,0x0000077f,0x00050083,0x00000008,
+0x00000781,0x0000077d,0x00000780,0x00050051,
+0x00000008,0x00000783,0x000009f9,0x00000002,
+0x00050085,0x00000008,0x00000784,0x00000352,
+0x00000783,0x00050083,0x00000008,0x00000785,
+0x00000781,0x00000784,0x00050051,0x00000008,
+0x00000787,0x00000a13,0x00000000,0x00050085,
+0x00000008,0x00000788,0x00000357,0x00000787,
+0x00050081,0x00000008,0x00000789,0x00000785,
+0x00000788,0x00050051,0x00000008,0x0000078b,
+0x00000a13,0x00000001,0x00050085,0x00000008,
+0x0000078c,0x0000035c,0x0000078b,0x00050081,
+0x00000008,0x0000078d,0x00000789,0x0000078c,
+0x00050051,0x00000008,0x0000078f,0x00000a13,
+0x00000002,0x00050085,0x00000008,0x00000790,
+0x0000023e,0x0000078f,0x00050081,0x00000008,
+0x00000791,0x0000078d,0x00000790,0x0008000c,
+0x00000008,0x00000792,0x00000001,0x0000002b,
+0x00000791,0x0000006e,0x00000060,0x00050085,
+0x00000008,0x00000795,0x00000367,0x0000077c,
+0x00050085,0x00000008,0x00000798,0x0000036b,
+0x0000077f,0x00050081,0x00000008,0x00000799,
+0x00000795,0x00000798,0x00050085,0x00000008,
+0x0000079c,0x00000370,0x00000783,0x00050081,
+0x00000008,0x0000079d,0x00000799,0x0000079c,
+0x00050085,0x00000008,0x000007a0,0x00000375,
+0x00000787,0x00050083,0x00000008,0x000007a1,
+0x0000079d,0x000007a0,0x00050085,0x00000008,
+0x000007a4,0x0000037a,0x0000078b,0x00050083,
+0x00000008,0x000007a5,0x000007a1,0x000007a4,
+0x00050085,0x00000008,0x000007a8,0x0000037f,
+0x0000078f,0x00050081,0x00000008,0x000007a9,
+0x000007a5,0x000007a8,0x0008000c,0x00000008,
+0x000007aa,0x00000001,0x0000002b,0x000007a9,
+0x0000006e,0x00000060,0x00050085,0x00000008,
+0x000007ad,0x00000386,0x0000077c,0x00050085,
+0x00000008,0x000007b0,0x0000038a,0x0000077f,
+0x00050083,0x00000008,0x000007b1,0x000007ad,
+0x000007b0,0x00050085,0x00000008,0x000007b4,
+0x0000038f,0x00000783,0x00050081,0x00000008,
+0x000007b5,0x000007b1,0x000007b4,0x00050085,
+0x00000008,0x000007b8,0x0000024d,0x00000787,
+0x00050081,0x00000008,0x000007b9,0x000007b5,
+0x000007b8,0x00050085,0x00000008,0x000007bc,
+0x00000398,0x0000078b,0x00050081,0x00000008,
+0x000007bd,0x000007b9,0x000007bc,0x00050085,
+0x00000008,0x000007c0,0x0000039d,0x0000078f,
+0x00050081,0x00000008,0x000007c1,0x000007bd,
+0x000007c0,0x0008000c,0x00000008,0x000007c2,
+0x00000001,0x0000002b,0x000007c1,0x0000006e,
+0x00000060,0x00070050,0x0000000a,0x000007c6,
+0x00000792,0x000007aa,0x000007c2,0x00000060,
+0x000200f9,0x000008f4,0x000200f8,0x000007c7,
+0x000500aa,0x00000024,0x000007c9,0x0000056c,
+0x000003a9,0x000300f7,0x00000851,0x00000000,
+0x000400fa,0x000007c9,0x000007ca,0x00000851,
+0x000200f8,0x000007ca,0x00050085,0x0000000a,
+0x000007d7,0x00000a13,0x00000b25,0x0008000c,
+0x0000000a,0x000007da,0x00000001,0x0000002b,
+0x000007d7,0x00000b1d,0x00000b1e,0x00050051,
+0x00000008,0x000007dc,0x000007da,0x00000000,
+0x00050051,0x00000008,0x000007de,0x000007da,
+0x00000001,0x00050081,0x00000008,0x000007df,
+0x000007dc,0x000007de,0x00050051,0x00000008,
+0x000007e1,0x000007da,0x00000002,0x00050081,
+0x00000008,0x000007e2,0x000007df,0x000007e1,
+0x0007000c,0x00000008,0x000007e3,0x00000001,
+0x0000001a,0x000007e2,0x000002b3,0x00050085,
+0x0000000a,0x000007ee,0x000009f9,0x00000b26,
+0x0008000c,0x0000000a,0x000007f1,0x00000001,
+0x0000002b,0x000007ee,0x00000b1d,0x00000b1e,
+0x00050051,0x00000008,0x000007f3,0x000007f1,
+0x00000000,0x00050051,0x00000008,0x000007f5,
+0x000007f1,0x00000001,0x00050081,0x00000008,
+0x000007f6,0x000007f3,0x000007f5,0x00050051,
+0x00000008,0x000007f8,0x000007f1,0x00000002,
+0x00050081,0x00000008,0x000007f9,0x000007f6,
+0x000007f8,0x0007000c,0x00000008,0x000007fa,
+0x00000001,0x0000001a,0x000007f9,0x000003e5,
+0x00050085,0x0000000a,0x00000801,0x00000a13,
+0x00000b27,0x0008000c,0x0000000a,0x00000804,
+0x00000001,0x0000002b,0x00000801,0x00000b1d,
+0x00000b1e,0x00050051,0x00000008,0x00000806,
+0x00000804,0x00000000,0x00050051,0x00000008,
+0x00000808,0x00000804,0x00000001,0x00050081,
+0x00000008,0x00000809,0x00000806,0x00000808,
+0x00050051,0x00000008,0x0000080b,0x00000804,
+0x00000002,0x00050081,0x00000008,0x0000080c,
+0x00000809,0x0000080b,0x0007000c,0x00000008,
+0x0000080d,0x00000001,0x0000001a,0x0000080c,
+0x000002b3,0x00050085,0x00000008,0x00000816,
+0x000007e3,0x00000b28,0x00050081,0x00000008,
+0x00000817,0x000007e3,0x00000816,0x00050085,
+0x00000008,0x0000081c,0x000007fa,0x00000b29,
+0x00050081,0x00000008,0x0000081d,0x00000817,
+0x0000081c,0x00050085,0x00000008,0x00000822,
+0x0000080d,0x00000b29,0x00050081,0x00000008,
+0x00000823,0x0000081d,0x00000822,0x00050085,
+0x00000008,0x0000082b,0x000007e3,0x00000b2a,
+0x00050081,0x00000008,0x0000082c,0x000007fa,
+0x0000082b,0x00050085,0x00000008,0x00000831,
+0x000007fa,0x00000b2b,0x00050081,0x00000008,
+0x00000832,0x0000082c,0x00000831,0x00050085,
+0x00000008,0x00000837,0x0000080d,0x00000b2a,
+0x00050081,0x00000008,0x00000838,0x00000832,
+0x00000837,0x00050085,0x00000008,0x00000840,
+0x000007e3,0x00000b29,0x00050081,0x00000008,
+0x00000841,0x0000080d,0x00000840,0x00050081,
+0x00000008,0x00000847,0x00000841,0x0000081c,
+0x00050085,0x00000008,0x0000084c,0x0000080d,
+0x00000b28,0x00050081,0x00000008,0x0000084d,
+0x00000847,0x0000084c,0x00070050,0x0000000a,
+0x00000b34,0x00000823,0x00000838,0x0000084d,
+0x00000060,0x000200f9,0x000008f4,0x000200f8,
+0x00000851,0x000500aa,0x00000024,0x00000853,
+0x0000056c,0x0000043f,0x000300f7,0x000008ef,
+0x00000000,0x000400fa,0x00000853,0x00000854,
+0x000008ef,0x000200f8,0x00000854,0x00050085,
+0x0000000a,0x00000860,0x000009f9,0x00000b2c,
+0x0008000c,0x0000000a,0x00000863,0x00000001,
+0x0000002b,0x00000860,0x00000b1d,0x00000b1e,
+0x00050051,0x00000008,0x00000865,0x00000863,
+0x00000000,0x00050051,0x00000008,0x00000867,
+0x00000863,0x00000001,0x00050081,0x00000008,
+0x00000868,0x00000865,0x00000867,0x00050051,
+0x00000008,0x0000086a,0x00000863,0x00000002,
+0x00050081,0x00000008,0x0000086b,0x00000868,
+0x0000086a,0x0007000c,0x00000008,0x0000086c,
+0x00000001,0x0000001a,0x0000086b,0x000003e5,
+0x00050085,0x0000000a,0x00000873,0x000009f9,
+0x00000b1f,0x0008000c,0x0000000a,0x00000876,
+0x00000001,0x0000002b,0x00000873,0x00000b1d,
+0x00000b1e,0x00050051,0x00000008,0x00000878,
+0x00000876,0x00000000,0x00050051,0x00000008,
+0x0000087a,0x00000876,0x00000001,0x00050081,
+0x00000008,0x0000087b,0x00000878,0x0000087a,
+0x00050051,0x00000008,0x0000087d,0x00000876,
+0x00000002,0x00050081,0x00000008,0x0000087e,
+0x0000087b,0x0000087d,0x0007000c,0x00000008,
+0x0000087f,0x00000001,0x0000001a,0x0000087e,
+0x00000474,0x00050085,0x0000000a,0x0000088a,
+0x00000a13,0x00000b2d,0x0008000c,0x0000000a,
+0x0000088d,0x00000001,0x0000002b,0x0000088a,
+0x00000b1d,0x00000b1e,0x00050051,0x00000008,
+0x0000088f,0x0000088d,0x00000000,0x00050051,
+0x00000008,0x00000891,0x0000088d,0x00000001,
+0x00050081,0x00000008,0x00000892,0x0000088f,
+0x00000891,0x00050051,0x00000008,0x00000894,
+0x0000088d,0x00000002,0x00050081,0x00000008,
+0x00000895,0x00000892,0x00000894,0x0007000c,
+0x00000008,0x00000896,0x00000001,0x0000001a,
+0x00000895,0x00000060,0x0007000c,0x00000008,
+0x0000089d,0x00000001,0x0000001a,0x00000896,
+0x00000b2f,0x00050083,0x00000008,0x000008a0,
+0x00000060,0x00000896,0x0006000c,0x00000008,
+0x000008a1,0x00000001,0x00000004,0x000008a0,
+0x0007000c,0x00000008,0x000008a5,0x00000001,
+0x0000001a,0x000008a1,0x00000b2f,0x00050083,
+0x00000008,0x000008a6,0x00000060,0x000008a5,
+0x0008000c,0x00000008,0x000008a9,0x00000001,
+0x0000002e,0x0000089d,0x000008a6,0x00000896,
+0x00050085,0x00000008,0x000008b2,0x0000086c,
+0x00000b30,0x00050081,0x00000008,0x000008b3,
+0x0000086c,0x000008b2,0x00050085,0x00000008,
+0x000008b8,0x0000087f,0x00000b31,0x00050081,
+0x00000008,0x000008b9,0x000008b3,0x000008b8,
+0x00050085,0x00000008,0x000008be,0x000008a9,
+0x00000b31,0x00050081,0x00000008,0x000008bf,
+0x000008b9,0x000008be,0x00060052,0x0000000a,
+0x00000aed,0x000008bf,0x00000134,0x00000000,
+0x00050085,0x00000008,0x000008c7,0x0000086c,
+0x00000b31,0x00050081,0x00000008,0x000008c8,
+0x0000087f,0x000008c7,0x00050085,0x00000008,
+0x000008cd,0x0000087f,0x00000b30,0x00050081,
+0x00000008,0x000008ce,0x000008c8,0x000008cd,
+0x00050081,0x00000008,0x000008d4,0x000008ce,
+0x000008be,0x00060052,0x0000000a,0x00000af3,
+0x000008d4,0x00000aed,0x00000001,0x00050085,
+0x00000008,0x000008dc,0x0000086c,0x00000b32,
+0x00050081,0x00000008,0x000008dd,0x000008a9,
+0x000008dc,0x00050085,0x00000008,0x000008e2,
+0x0000087f,0x00000b32,0x00050081,0x00000008,
+0x000008e3,0x000008dd,0x000008e2,0x00050085,
+0x00000008,0x000008e8,0x000008a9,0x00000b33,
+0x00050081,0x00000008,0x000008e9,0x000008e3,
+0x000008e8,0x00060052,0x0000000a,0x00000af9,
+0x000008e9,0x00000af3,0x00000002,0x0008000c,
+0x0000000a,0x000008ee,0x00000001,0x0000002b,
+0x00000af9,0x00000b1d,0x00000b1e,0x000200f9,
+0x000008f4,0x000200f8,0x000008ef,0x0004003d,
+0x0000003e,0x000008f0,0x000000b8,0x00050051,
+0x00000008,0x000008f1,0x000008f0,0x00000000,
+0x00050051,0x00000008,0x000008f2,0x000008f0,
+0x00000001,0x00070050,0x0000000a,0x000008f3,
+0x000008f1,0x000008f2,0x0000006e,0x00000060,
+0x000200f9,0x000008f4,0x000200f8,0x000008f4,
+0x002b00f5,0x0000000a,0x00000b12,0x0000090f,
+0x00000903,0x00000929,0x000005be,0x00000943,
+0x000005cd,0x00000134,0x000005da,0x0000095d,
+0x00000951,0x00000977,0x0000096b,0x00000991,
+0x00000985,0x00000134,0x0000061d,0x000009ab,
+0x0000099f,0x000009c5,0x000009b9,0x000009df,
+0x00000661,0x0000067b,0x00000674,0x000006cb,
+0x0000067f,0x00000b35,0x000006cf,0x0000076b,
+0x00000757,0x00000776,0x0000076f,0x000007c6,
+0x0000077a,0x00000b34,0x000007ca,0x000008ee,
+0x00000854,0x000008f3,0x000008ef,0x00050041,
+0x00000020,0x000004f2,0x0000001e,0x00000163,
+0x0004003d,0x00000006,0x000004f3,0x000004f2,
+0x000500ab,0x00000024,0x000004f4,0x000004f3,
+0x00000023,0x000300f7,0x000004f6,0x00000000,
+0x000400fa,0x000004f4,0x000004f5,0x000004f6,
+0x000200f8,0x000004f5,0x0008004f,0x00000011,
+0x000004f9,0x00000b12,0x00000b12,0x00000000,
+0x00000001,0x00000002,0x00060041,0x00000047,
+0x00000a1c,0x0000001e,0x00000044,0x00000046,
+0x0004003d,0x00000008,0x00000a1d,0x00000a1c,
+0x00060041,0x00000047,0x00000a1e,0x0000001e,
+0x00000044,0x0000004b,0x0004003d,0x00000008,
+0x00000a1f,0x00000a1e,0x00060041,0x00000047,
+0x00000a20,0x0000001e,0x00000044,0x0000004f,
+0x0004003d,0x00000008,0x00000a21,0x00000a20,
+0x0006000c,0x00000011,0x00000a23,0x00000001,
+0x00000004,0x000004f9,0x00050041,0x00000047,
+0x00000a24,0x0000001e,0x00000055,0x0004003d,
+0x00000008,0x00000a25,0x00000a24,0x00060050,
+0x00000011,0x00000a26,0x00000a25,0x00000a25,
+0x00000a25,0x0007000c,0x00000011,0x00000a27,
+0x00000001,0x0000001a,0x00000a23,0x00000a26,
+0x00060050,0x00000011,0x00000a29,0x00000a1d,
+0x00000a1d,0x00000a1d,0x00050081,0x00000011,
+0x00000a2a,0x00000a27,0x00000a29,0x00050088,
+0x00000008,0x00000a2f,0x00000060,0x00000a25,
+0x00060050,0x00000011,0x00000a30,0x00000a2f,
+0x00000a2f,0x00000a2f,0x0007000c,0x00000011,
+0x00000a31,0x00000001,0x0000001a,0x00000a23,
+0x00000a30,0x00060050,0x00000011,0x00000a33,
+0x00000a1f,0x00000a1f,0x00000a1f,0x00050081,
+0x00000011,0x00000a34,0x00000a31,0x00000a33,
+0x00060050,0x00000011,0x00000a37,0x00000a21,
+0x00000a21,0x00000a21,0x00050081,0x00000011,
+0x00000a38,0x000004f9,0x00000a37,0x0008000c,
+0x00000011,0x00000a3b,0x00000001,0x0000002b,
+0x00000a38,0x00000b16,0x00000b17,0x0008000c,
+0x00000011,0x00000a3f,0x00000001,0x0000002e,
+0x00000a2a,0x00000a34,0x00000a3b,0x00050041,
+0x00000079,0x00000a41,0x0000001e,0x00000078,
+0x0004003d,0x0000000a,0x00000a42,0x00000a41,
+0x0008004f,0x00000011,0x00000a43,0x00000a42,
+0x00000a42,0x00000000,0x00000001,0x00000002,
+0x0005008e,0x00000011,0x00000a44,0x00000a43,
+0x00000030,0x00050083,0x00000011,0x00000a46,
+0x00000b18,0x00000a44,0x00050085,0x00000011,
+0x00000a47,0x00000a3f,0x00000a46,0x00050081,
+0x00000011,0x00000a4c,0x00000a47,0x00000a44,
+0x00050083,0x00000011,0x00000a4e,0x00000a4c,
+0x00000b19,0x0008000c,0x00000011,0x00000a52,
+0x00000001,0x0000002b,0x00000a4e,0x00000b16,
+0x00000b17,0x00050041,0x00000079,0x00000a53,
+0x0000001e,0x0000008c,0x0004003d,0x0000000a,
+0x00000a54,0x00000a53,0x0008004f,0x00000011,
+0x00000a55,0x00000a54,0x00000a54,0x00000000,
+0x00000001,0x00000002,0x00050085,0x00000011,
+0x00000a57,0x00000a52,0x00000a55,0x0006000c,
+0x00000011,0x00000a59,0x00000001,0x00000004,
+0x00000a57,0x00050041,0x00000079,0x00000a5a,
+0x0000001e,0x00000094,0x0004003d,0x0000000a,
+0x00000a5b,0x00000a5a,0x0008004f,0x00000011,
+0x00000a5c,0x00000a5b,0x00000a5b,0x00000000,
+0x00000001,0x00000002,0x00050088,0x00000011,
+0x00000a5e,0x00000b17,0x00000a5c,0x0007000c,
+0x00000011,0x00000a5f,0x00000001,0x0000001a,
+0x00000a59,0x00000a5e,0x0008000c,0x00000011,
+0x00000a63,0x00000001,0x0000002b,0x00000a5f,
+0x00000b16,0x00000b17,0x00050051,0x00000008,
+0x000004fc,0x00000a63,0x00000000,0x00060052,
+0x0000000a,0x00000afb,0x000004fc,0x00000b12,
+0x00000000,0x00050051,0x00000008,0x000004fe,
+0x00000a63,0x00000001,0x00060052,0x0000000a,
+0x00000afd,0x000004fe,0x00000afb,0x00000001,
+0x00050051,0x00000008,0x00000500,0x00000a63,
+0x00000002,0x00060052,0x0000000a,0x00000aff,
+0x00000500,0x00000afd,0x00000002,0x000200f9,
+0x000004f6,0x000200f8,0x000004f6,0x000700f5,
+0x0000000a,0x00000b13,0x00000b12,0x000008f4,
+0x00000aff,0x000004f5,0x0003003e,0x00000502,
+0x00000b13,0x000100fd,0x00010038}
+;
diff --git a/vrto3d/shaders/repack.frag b/vrto3d/shaders/repack.frag
new file mode 100644
index 00000000..9c9aed96
--- /dev/null
+++ b/vrto3d/shaders/repack.frag
@@ -0,0 +1,342 @@
+/*
+ * This file is part of VRto3D.
+ *
+ * VRto3D is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VRto3D is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with VRto3D. If not, see .
+ */
+#version 450
+
+// GLSL port of the Windows presenter's repack pixel shader (kPsSource in
+// vrto3d/src/presenter/window_presenter.cpp) plus the display-correction
+// pass (kAdjustPsHlsl in vrto3d/src/dx11_renderer.cpp) folded in as an
+// optional post-step gated by pc.correction_enabled.
+//
+// The input `sbs` is the canonical 2W x H side-by-side texture — left eye in
+// the left half (before eye_swap). uv (0,0) is the top-left of the OUTPUT
+// window (see fullscreen.vert), and v = 0 samples the top row of `sbs`.
+//
+// `pc.mode` carries the raw OutputMode enum value (vrto3dlib/stereo_config.h),
+// NOT the Windows presenter's remapped shader enum. The FramePacked* variants
+// derive their blank-gap row count from the mode itself (30 px for 720p60,
+// 45 px for the 1080p variants — mirrors FramePackTimingSpec::gap_pixels,
+// which is what the Windows presenter feeds as `framepack_offset`).
+//
+// C++ mirror of the push-constant block (std430 layout, 96 bytes total):
+//
+// struct RepackPushConstants { // offset
+// int32_t out_size[2]; // 0 swapchain extent (w, h)
+// int32_t eye_size[2]; // 8 per-eye pixel size of sbs
+// int32_t mode; // 16 OutputMode int value
+// int32_t eye_swap; // 20 0/1
+// int32_t correction_enabled; // 24 0/1 display-correction post-step
+// float curve; // 28 shader_curve
+// float lift[4]; // 32 rgb + pad
+// float gamma[4]; // 48 rgb + pad
+// float gain[4]; // 64 rgb + pad
+// float curve_offsets[4]; // 80 x=off_low y=off_high z=off_both w=unused
+// }; // 96 bytes (<= 128 push-constant min-spec)
+// static_assert(sizeof(RepackPushConstants) == 96);
+
+layout(set = 0, binding = 0) uniform sampler2D sbs;
+
+layout(location = 0) in vec2 uv;
+layout(location = 0) out vec4 out_color;
+
+layout(push_constant) uniform Pc {
+ ivec2 out_size; // swapchain extent
+ ivec2 eye_size; // per-eye pixel size of sbs (sbs is 2*eye_size.x wide); reserved
+ int mode; // OutputMode int value
+ int eye_swap; // 0/1
+ int correction_enabled; // display-correction post-step
+ float curve; // shader_curve
+ vec4 lift; // rgb + pad
+ vec4 gamma_; // rgb + pad
+ vec4 gain; // rgb + pad
+ vec4 curve_offsets; // x=off_low y=off_high z=off_both w=unused
+} pc;
+
+// OutputMode enum values — keep in sync with vrto3dlib/stereo_config.h.
+const int kSbS = 0;
+const int kTaB = 1;
+const int kRowInterlaced = 2;
+const int kColInterlaced = 3;
+const int kCheckerboard = 4;
+const int kLeiaSR = 5; // alternate presenter — SbS fallback
+const int kNvidiaDX9 = 6; // alternate presenter — SbS fallback
+const int kWibbleWobble = 7; // alternate presenter — SbS fallback
+const int kVirtualDesktop = 8;
+const int kFramePacked720p60 = 9;
+const int kFramePacked1080p24 = 10;
+const int kFramePacked1080p60 = 11;
+const int kFramePacked1080p60CVT = 12;
+const int kDualDisplay = 13;
+const int kDualDisplayFlip = 14;
+const int kAnaglyphRedCyan = 15;
+const int kAnaglyphRedCyanDubois = 16;
+const int kAnaglyphRedCyanDeghosted = 17;
+const int kAnaglyphRedCyanCompromise = 18;
+const int kAnaglyphGreenMagenta = 19;
+const int kAnaglyphGreenMagentaDubois = 20;
+const int kAnaglyphGreenMagentaDeghosted = 21;
+const int kAnaglyphBlueAmber = 22;
+const int kMono = 23;
+
+// Given a half (0=left, 1=right), sample the input SbS at (u_half, v).
+// u_half in [0,1] = position within the eye's half of the input.
+// eye_swap flips which half-source we read (mirrors HLSL SampleEye).
+vec4 SampleEye(int half_idx, float u_half, float v) {
+ int eye = (pc.eye_swap != 0) ? (1 - half_idx) : half_idx;
+ float u_src = 0.5 * u_half + 0.5 * float(eye);
+ return texture(sbs, vec2(u_src, v));
+}
+
+// Display-correction post-process — exact port of kAdjustPsHlsl
+// (dx11_renderer.cpp): extended SCurve.fx followed by LiftGammaGain.fx.
+// Operates in the same byte space the swapchain stores (defaults are all
+// pass-through, so black stays black in framepack gap rows).
+vec3 ApplyCorrection(vec3 col) {
+ float off_low = pc.curve_offsets.x;
+ float off_high = pc.curve_offsets.y;
+ float off_both = pc.curve_offsets.z;
+
+ // Extended SCurve.fx — at curve=1.0 the mix degenerates to pass-through.
+ vec3 low = pow(abs(col), vec3(pc.curve)) + off_low;
+ vec3 high = pow(abs(col), vec3(1.0 / pc.curve)) + off_high;
+ vec3 t = clamp(col + off_both, 0.0, 1.0);
+ col = mix(low, high, t);
+
+ // LiftGammaGain.fx (operates in sRGB byte space — matches ReShade default).
+ col = col * (1.5 - 0.5 * pc.lift.rgb) + 0.5 * pc.lift.rgb - 0.5;
+ col = clamp(col, 0.0, 1.0);
+ col *= pc.gain.rgb;
+ col = pow(abs(col), 1.0 / pc.gamma_.rgb);
+
+ return clamp(col, 0.0, 1.0);
+}
+
+vec4 Repack() {
+ int m = pc.mode;
+
+ // SbS / DualDisplay (+ alternate-presenter fallbacks): straight
+ // horizontal split. For DualDisplay the output window spans two
+ // monitors, so the left half (= left monitor) shows the left eye
+ // fullscreen and the right half the right eye.
+ if (m == kSbS || m == kDualDisplay
+ || m == kLeiaSR || m == kNvidiaDX9 || m == kWibbleWobble) {
+ int half_idx = (uv.x < 0.5) ? 0 : 1;
+ float u_half = (uv.x < 0.5) ? (uv.x * 2.0) : ((uv.x - 0.5) * 2.0);
+ return SampleEye(half_idx, u_half, uv.y);
+ }
+
+ // TaB / FramePacked*: left eye on the top rows, blank gap rows black,
+ // right eye below. Plain TaB has no gap; the FramePacked variants derive
+ // the gap row count from the HDMI 1.4 timing spec.
+ if (m == kTaB || m == kFramePacked720p60 || m == kFramePacked1080p24
+ || m == kFramePacked1080p60 || m == kFramePacked1080p60CVT) {
+ float gap_px = 0.0;
+ if (m == kFramePacked720p60) gap_px = 30.0;
+ else if (m != kTaB) gap_px = 45.0; // 1080p24/60/60CVT
+ float gap = (gap_px > 0.0) ? (gap_px / float(pc.out_size.y)) : 0.0;
+ float half_height = (1.0 - gap) * 0.5;
+ if (uv.y < half_height) {
+ // Top half -> left eye
+ return SampleEye(0, uv.x, uv.y / half_height);
+ } else if (uv.y >= half_height + gap) {
+ // Bottom half -> right eye
+ float v_rel = (uv.y - (half_height + gap)) / half_height;
+ return SampleEye(1, uv.x, v_rel);
+ } else {
+ return vec4(0.0, 0.0, 0.0, 1.0); // framepack gap
+ }
+ }
+
+ // Row-interlaced: alternating rows; odd output rows = right eye.
+ if (m == kRowInterlaced) {
+ int row = int(gl_FragCoord.y);
+ int half_idx = row & 1;
+ return SampleEye(half_idx, uv.x, uv.y);
+ }
+
+ // Col-interlaced
+ if (m == kColInterlaced) {
+ int col = int(gl_FragCoord.x);
+ int half_idx = col & 1;
+ return SampleEye(half_idx, uv.x, uv.y);
+ }
+
+ // Checkerboard
+ if (m == kCheckerboard) {
+ int row = int(gl_FragCoord.y);
+ int col = int(gl_FragCoord.x);
+ int half_idx = (row + col) & 1;
+ return SampleEye(half_idx, uv.x, uv.y);
+ }
+
+ // VirtualDesktop: SbS in center band of a 2W x 2H window (rows [0.25, 0.75)).
+ if (m == kVirtualDesktop) {
+ if (uv.y < 0.25 || uv.y >= 0.75) return vec4(0.0, 0.0, 0.0, 1.0);
+ float v = (uv.y - 0.25) * 2.0;
+ int half_idx = (uv.x < 0.5) ? 0 : 1;
+ float u_half = (uv.x < 0.5) ? (uv.x * 2.0) : ((uv.x - 0.5) * 2.0);
+ return SampleEye(half_idx, u_half, v);
+ }
+
+ // DualDisplayFlip: same horizontal split as SbS/DualDisplay, but the LEFT
+ // half is sampled with v inverted (vertical flip).
+ if (m == kDualDisplayFlip) {
+ int half_idx = (uv.x < 0.5) ? 0 : 1;
+ float u_half = (uv.x < 0.5) ? (uv.x * 2.0) : ((uv.x - 0.5) * 2.0);
+ float v_src = (half_idx == 0) ? (1.0 - uv.y) : uv.y;
+ return SampleEye(half_idx, u_half, v_src);
+ }
+
+ // Mono — single-eye view. eye_swap selects right (1) instead of left (0).
+ if (m == kMono) {
+ return SampleEye(0, uv.x, uv.y);
+ }
+
+ // ----- Anaglyph variants (3DToElse / iaian7+vectorform formulas) -----
+ // Sample full image of each eye; cA = left, cB = right.
+ vec4 cA = SampleEye(0, uv.x, uv.y);
+ vec4 cB = SampleEye(1, uv.x, uv.y);
+
+ // Anaglyph red-cyan (simple)
+ if (m == kAnaglyphRedCyan) {
+ return vec4(cA.r, cB.g, cB.b, 1.0);
+ }
+
+ // Anaglyph red-cyan Dubois
+ if (m == kAnaglyphRedCyanDubois) {
+ float r = clamp( 0.437 * cA.r + 0.449 * cA.g + 0.164 * cA.b
+ - 0.011 * cB.r - 0.032 * cB.g - 0.007 * cB.b, 0.0, 1.0);
+ float g = clamp(-0.062 * cA.r - 0.062 * cA.g - 0.024 * cA.b
+ + 0.377 * cB.r + 0.761 * cB.g + 0.009 * cB.b, 0.0, 1.0);
+ float b = clamp(-0.048 * cA.r - 0.050 * cA.g - 0.017 * cA.b
+ - 0.026 * cB.r - 0.093 * cB.g + 1.234 * cB.b, 0.0, 1.0);
+ return vec4(r, g, b, 1.0);
+ }
+
+ // Anaglyph red-cyan deghosted (iaian7 / vectorform)
+ if (m == kAnaglyphRedCyanDeghosted) {
+ float Contrast = 1.0;
+ float DeGhost = 0.06 * 0.1;
+ float contrast = (Contrast * 0.5) + 0.5;
+ float LOne = contrast * 0.45;
+ float ROne = contrast;
+
+ vec4 image = vec4(0.0, 0.0, 0.0, 1.0);
+ vec4 accum;
+ accum = clamp(cA * vec4(LOne, (1.0 - LOne) * 0.5, (1.0 - LOne) * 0.5, 1.0), 0.0, 1.0);
+ image.r = pow(accum.r + accum.g + accum.b, 1.00);
+ accum = clamp(cB * vec4(1.0 - ROne, ROne, 0.0, 1.0), 0.0, 1.0);
+ image.g = pow(accum.r + accum.g + accum.b, 1.15);
+ accum = clamp(cB * vec4(1.0 - ROne, 0.0, ROne, 1.0), 0.0, 1.0);
+ image.b = pow(accum.r + accum.g + accum.b, 1.15);
+
+ accum = image;
+ image.r = accum.r + (accum.r * DeGhost) + (accum.g * (DeGhost * -0.5)) + (accum.b * (DeGhost * -0.5));
+ image.g = accum.g + (accum.r * (DeGhost * -0.25)) + (accum.g * (DeGhost * 0.5)) + (accum.b * (DeGhost * -0.25));
+ image.b = accum.b + (accum.r * (DeGhost * -0.25)) + (accum.g * (DeGhost * -0.25)) + (accum.b * (DeGhost * 0.5));
+ image.a = 1.0;
+ return image;
+ }
+
+ // Anaglyph red-cyan compromise. The HLSL uses mul(row_matrix, rgb) —
+ // written out here as explicit row dot products to avoid GLSL's
+ // column-major mat3 constructor changing the meaning.
+ if (m == kAnaglyphRedCyanCompromise) {
+ float r = dot(vec3( 0.439, 0.447, 0.148), cA.rgb);
+ float g = dot(vec3( 0.095, 0.934, -0.005), cB.rgb);
+ float b = dot(vec3(-0.018, -0.028, 1.057), cB.rgb);
+ return vec4(clamp(vec3(r, g, b), 0.0, 1.0), 1.0);
+ }
+
+ // Anaglyph green-magenta (simple)
+ if (m == kAnaglyphGreenMagenta) {
+ return vec4(cB.r, cA.g, cB.b, 1.0);
+ }
+
+ // Anaglyph green-magenta Dubois
+ if (m == kAnaglyphGreenMagentaDubois) {
+ float r = clamp(-0.062 * cA.r - 0.158 * cA.g - 0.039 * cA.b
+ + 0.529 * cB.r + 0.705 * cB.g + 0.024 * cB.b, 0.0, 1.0);
+ float g = clamp( 0.284 * cA.r + 0.668 * cA.g + 0.143 * cA.b
+ - 0.016 * cB.r - 0.015 * cB.g + 0.065 * cB.b, 0.0, 1.0);
+ float b = clamp(-0.015 * cA.r - 0.027 * cA.g + 0.021 * cA.b
+ + 0.009 * cB.r + 0.075 * cB.g + 0.937 * cB.b, 0.0, 1.0);
+ return vec4(r, g, b, 1.0);
+ }
+
+ // Anaglyph green-magenta deghosted
+ if (m == kAnaglyphGreenMagentaDeghosted) {
+ float Contrast = 1.0;
+ float DeGhost = 0.06 * 0.275;
+ float contrast = (Contrast * 0.5) + 0.5;
+ float LOne = contrast * 0.45;
+ float ROne = contrast * 0.8;
+
+ vec4 image = vec4(0.0, 0.0, 0.0, 1.0);
+ vec4 accum;
+ accum = clamp(cB * vec4(ROne, 1.0 - ROne, 0.0, 1.0), 0.0, 1.0);
+ image.r = pow(accum.r + accum.g + accum.b, 1.15);
+ accum = clamp(cA * vec4((1.0 - LOne) * 0.5, LOne, (1.0 - LOne) * 0.5, 1.0), 0.0, 1.0);
+ image.g = pow(accum.r + accum.g + accum.b, 1.05);
+ accum = clamp(cB * vec4(0.0, 1.0 - ROne, ROne, 1.0), 0.0, 1.0);
+ image.b = pow(accum.r + accum.g + accum.b, 1.15);
+
+ accum = image;
+ image.r = accum.r + (accum.r * (DeGhost * 0.5)) + (accum.g * (DeGhost * -0.25)) + (accum.b * (DeGhost * -0.25));
+ image.g = accum.g + (accum.r * (DeGhost * -0.5)) + (accum.g * (DeGhost * 0.25)) + (accum.b * (DeGhost * -0.5));
+ image.b = accum.b + (accum.r * (DeGhost * -0.25)) + (accum.g * (DeGhost * -0.25)) + (accum.b * (DeGhost * 0.5));
+ image.a = 1.0;
+ return image;
+ }
+
+ // Anaglyph blue-amber (ColorCode style)
+ if (m == kAnaglyphBlueAmber) {
+ float Contrast = 1.0;
+ float DeGhost = 0.06 * 0.275;
+ float contrast = (Contrast * 0.5) + 0.5;
+ float LOne = contrast * 0.45;
+ float ROne = contrast;
+
+ vec4 image = vec4(0.0, 0.0, 0.0, 1.0);
+ vec4 accum;
+ accum = clamp(cA * vec4(ROne, 0.0, 1.0 - ROne, 1.0), 0.0, 1.0);
+ image.r = pow(accum.r + accum.g + accum.b, 1.05);
+ accum = clamp(cA * vec4(0.0, ROne, 1.0 - ROne, 1.0), 0.0, 1.0);
+ image.g = pow(accum.r + accum.g + accum.b, 1.10);
+ accum = clamp(cB * vec4((1.0 - LOne) * 0.5, (1.0 - LOne) * 0.5, LOne, 1.0), 0.0, 1.0);
+ image.b = pow(accum.r + accum.g + accum.b, 1.0);
+ image.b = mix(pow(image.b, (DeGhost * 0.15) + 1.0),
+ 1.0 - pow(abs(1.0 - image.b), (DeGhost * 0.15) + 1.0),
+ image.b);
+
+ accum = image;
+ image.r = accum.r + (accum.r * (DeGhost * 1.5)) + (accum.g * (DeGhost * -0.75)) + (accum.b * (DeGhost * -0.75));
+ image.g = accum.g + (accum.r * (DeGhost * -0.75)) + (accum.g * (DeGhost * 1.5)) + (accum.b * (DeGhost * -0.75));
+ image.b = accum.b + (accum.r * (DeGhost * -1.5)) + (accum.g * (DeGhost * -1.5)) + (accum.b * (DeGhost * 3.0));
+ return clamp(image, 0.0, 1.0);
+ }
+
+ // Fallback (mirrors the HLSL debug output)
+ return vec4(uv, 0.0, 1.0);
+}
+
+void main() {
+ vec4 c = Repack();
+ if (pc.correction_enabled != 0) {
+ c.rgb = ApplyCorrection(c.rgb);
+ }
+ out_color = c;
+}
diff --git a/vrto3d/src/auto_depth.cpp b/vrto3d/src/auto_depth.cpp
new file mode 100644
index 00000000..eb4a99c9
--- /dev/null
+++ b/vrto3d/src/auto_depth.cpp
@@ -0,0 +1,455 @@
+/*
+ * This file is part of VRto3D.
+ *
+ * VRto3D is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VRto3D is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with VRto3D. If not, see .
+ */
+#include "auto_depth.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include
+
+#include
+#include
+#include
+
+#include
+
+#include "vrto3dlib/debug_log.hpp"
+
+#include "hmd_device_driver.h" // StereoDisplayComponent::FeedAutoDepthSample
+
+
+// Disparity histogram — 128 buckets, each spanning `stride` source pixels.
+// Stride is 4 up to ~2K-wide eyes and grows on wider targets so the buckets
+// always cover the full search radius (see Run()).
+// Layout: [0..127] = bucket counts, [128] = total committed matches.
+#define NUM_BUCKETS 128
+
+namespace {
+
+// Quarter-res block-matched left/right disparity search. Each thread picks a
+// left-eye sample, sweeps `search_radius` candidate offsets in the right eye,
+// records the best (lowest SAD) as that thread's disparity, and increments a
+// bucket in the global disparity histogram. The CPU then walks the histogram
+// for a percentile/peak-shape result.
+constexpr const char* kAutoDepthCS = R"HLSL(
+Texture2D g_sbs : register(t0);
+RWByteAddressBuffer g_result : register(u0);
+
+cbuffer Params : register(b0)
+{
+ uint sbs_w;
+ uint sbs_h;
+ uint search_radius; // in source pixels
+ uint stride; // 4 = quarter-res
+};
+
+#define NUM_BUCKETS 128
+
+// Block size for SAD matching. A 5x1 horizontal block is enough to make random
+// single-pixel color collisions vanishingly rare, while staying cheap inside
+// the inner search loop.
+#define BLOCK_HALF 2
+
+float3 LoadL(uint x, uint y) { return g_sbs.Load(int3(x, y, 0)).rgb; }
+float3 LoadR(uint rx, uint y) { return g_sbs.Load(int3(rx, y, 0)).rgb; }
+
+[numthreads(8, 8, 1)]
+void CSMain(uint3 DTid : SV_DispatchThreadID, uint GI : SV_GroupIndex)
+{
+ uint eye_w = sbs_w / 2;
+ uint x = DTid.x * stride;
+ uint y = DTid.y * stride;
+
+ // Inner ROI: skip the periphery where SteamVR's hidden-area mask blacks
+ // out the lens corners. Black-vs-black matches at the eye edges
+ // otherwise dominate the disparity histogram. Keep the central ~84% wide
+ // by ~90% tall window (per eye).
+ uint x_lo = eye_w * 8u / 100u;
+ uint x_hi = eye_w * 92u / 100u;
+ uint y_lo = sbs_h * 5u / 100u;
+ uint y_hi = sbs_h * 95u / 100u;
+
+ if (x >= x_lo && x < x_hi && y >= y_lo && y < y_hi)
+ {
+ // Local gradient — skip textureless regions.
+ float3 L = LoadL(x, y);
+ float3 Lr = LoadL(min(x + stride, eye_w - 1), y);
+ float grad = abs(L.r - Lr.r) + abs(L.g - Lr.g) + abs(L.b - Lr.b);
+ // Brightness floor — black hidden-area-mask pixels that survive the
+ // ROI gate (anti-aliased mask edges) are still ambiguous matches.
+ float Llum = L.r + L.g + L.b;
+ if (grad > 0.04 && Llum > 0.05)
+ {
+ // Cache the left block once.
+ float3 Lb[2 * BLOCK_HALF + 1];
+ [unroll] for (int i = -BLOCK_HALF; i <= BLOCK_HALF; ++i)
+ Lb[i + BLOCK_HALF] = LoadL(x + i, y);
+
+ // Search crossed (pop-out) disparity only: right-eye match is to
+ // the LEFT of the left-eye column. Under the converged off-axis
+ // projection d=0 == the screen plane (z = convergence), growing d
+ // == closer. Uncrossed (behind-screen) disparity is not searched
+ // — background divergence is not this loop's problem.
+ uint max_d = min(search_radius, x - BLOCK_HALF);
+ float bestCost = 1e9;
+ float secondBest = 1e9;
+ uint bestD = 0;
+ for (uint d = 0; d < max_d; d += stride)
+ {
+ uint rx = eye_w + x - d;
+ float cost = 0.0;
+ [unroll] for (int j = -BLOCK_HALF; j <= BLOCK_HALF; ++j)
+ {
+ float3 R = LoadR(rx + j, y);
+ float3 dC = abs(Lb[j + BLOCK_HALF] - R);
+ cost += dC.r + dC.g + dC.b;
+ }
+ // Runner-up tracking excludes candidates adjacent to the
+ // current best: on smooth textures the cost curve's own
+ // shoulder is nearly as cheap as the minimum, and counting it
+ // as "second best" made the uniqueness test below reject
+ // valid matches on gently-textured near surfaces.
+ if (cost < bestCost)
+ {
+ if (d - bestD > stride) { secondBest = bestCost; }
+ bestCost = cost;
+ bestD = d;
+ }
+ else if (cost < secondBest && d - bestD > stride)
+ {
+ secondBest = cost;
+ }
+ }
+
+ // Lowe-style uniqueness test: only trust the match if it's
+ // meaningfully better than the runner-up (rejects ambiguous /
+ // repeating-pattern regions where a "best" disparity is random).
+ // Also require an absolute quality floor.
+ const float maxAbsCost = 0.30 * float(2 * BLOCK_HALF + 1); // ~0.3 per pixel * block size
+ if (bestCost < maxAbsCost && bestCost * 1.4 < secondBest)
+ {
+ // Bucket index: each bucket spans `stride` source pixels.
+ uint bucket = bestD / stride;
+ if (bucket >= NUM_BUCKETS) bucket = NUM_BUCKETS - 1;
+ uint orig0;
+ g_result.InterlockedAdd(bucket * 4, 1, orig0);
+ g_result.InterlockedAdd(NUM_BUCKETS * 4, 1, orig0);
+ }
+ }
+ }
+}
+)HLSL";
+
+struct AutoDepthParams {
+ uint32_t sbs_w;
+ uint32_t sbs_h;
+ uint32_t search_radius;
+ uint32_t stride;
+};
+
+} // namespace
+
+
+namespace vrto3d {
+
+void AutoDepthAnalyzer::Init(ID3D11Device* device)
+{
+ device_ = device;
+}
+
+
+bool AutoDepthAnalyzer::EnsureResources(ID3D11Texture2D* sbs)
+{
+ if (init_failed_) return false;
+ if (!device_ || !sbs) return false;
+
+ // Rebuild the SRV whenever the source texture changes (e.g. window
+ // resize re-creates `out_sbs_` upstream).
+ if (sbs != srv_source_) {
+ sbs_srv_.Reset();
+ srv_source_ = sbs;
+ }
+
+ if (!sbs_srv_) {
+ D3D11_TEXTURE2D_DESC td{};
+ sbs->GetDesc(&td);
+ D3D11_SHADER_RESOURCE_VIEW_DESC sd{};
+ sd.Format = td.Format;
+ sd.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
+ sd.Texture2D.MipLevels = 1;
+ sd.Texture2D.MostDetailedMip = 0;
+ HRESULT hr = device_->CreateShaderResourceView(sbs, &sd, &sbs_srv_);
+ if (FAILED(hr)) {
+ LOG() << "AutoDepth: CreateShaderResourceView hr=0x" << std::hex << hr;
+ init_failed_ = true;
+ return false;
+ }
+ }
+
+ if (cs_ && result_buf_ && result_uav_ && staging_[0] && staging_[1] && params_cb_) {
+ return sbs_srv_ != nullptr;
+ }
+
+ // Compile the compute shader.
+ if (!cs_) {
+ Microsoft::WRL::ComPtr blob, err;
+ HRESULT hr = D3DCompile(kAutoDepthCS, std::strlen(kAutoDepthCS),
+ nullptr, nullptr, nullptr,
+ "CSMain", "cs_5_0", 0, 0, &blob, &err);
+ if (FAILED(hr)) {
+ if (err) LOG() << "AutoDepth: D3DCompile error: " << reinterpret_cast(err->GetBufferPointer());
+ else LOG() << "AutoDepth: D3DCompile failed hr=0x" << std::hex << hr;
+ init_failed_ = true;
+ return false;
+ }
+ hr = device_->CreateComputeShader(blob->GetBufferPointer(), blob->GetBufferSize(),
+ nullptr, &cs_);
+ if (FAILED(hr)) {
+ LOG() << "AutoDepth: CreateComputeShader hr=0x" << std::hex << hr;
+ init_failed_ = true;
+ return false;
+ }
+ }
+
+ constexpr UINT kResultUints = NUM_BUCKETS + 1;
+ constexpr UINT kResultBytes = kResultUints * 4;
+ if (!result_buf_) {
+ D3D11_BUFFER_DESC bd{};
+ bd.ByteWidth = kResultBytes;
+ bd.Usage = D3D11_USAGE_DEFAULT;
+ bd.BindFlags = D3D11_BIND_UNORDERED_ACCESS;
+ bd.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS;
+ bd.StructureByteStride = 0;
+ HRESULT hr = device_->CreateBuffer(&bd, nullptr, &result_buf_);
+ if (FAILED(hr)) {
+ LOG() << "AutoDepth: CreateBuffer(result) hr=0x" << std::hex << hr;
+ init_failed_ = true;
+ return false;
+ }
+
+ D3D11_UNORDERED_ACCESS_VIEW_DESC uavd{};
+ uavd.Format = DXGI_FORMAT_R32_TYPELESS;
+ uavd.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
+ uavd.Buffer.FirstElement = 0;
+ uavd.Buffer.NumElements = kResultUints;
+ uavd.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW;
+ hr = device_->CreateUnorderedAccessView(result_buf_.Get(), &uavd, &result_uav_);
+ if (FAILED(hr)) {
+ LOG() << "AutoDepth: CreateUAV hr=0x" << std::hex << hr;
+ init_failed_ = true;
+ return false;
+ }
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ if (staging_[i]) continue;
+ D3D11_BUFFER_DESC bd{};
+ bd.ByteWidth = kResultBytes;
+ bd.Usage = D3D11_USAGE_STAGING;
+ bd.BindFlags = 0;
+ bd.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+ bd.MiscFlags = 0;
+ HRESULT hr = device_->CreateBuffer(&bd, nullptr, &staging_[i]);
+ if (FAILED(hr)) {
+ LOG() << "AutoDepth: CreateBuffer(staging[" << i << "]) hr=0x" << std::hex << hr;
+ init_failed_ = true;
+ return false;
+ }
+ }
+
+ if (!params_cb_) {
+ D3D11_BUFFER_DESC bd{};
+ bd.ByteWidth = sizeof(AutoDepthParams);
+ bd.Usage = D3D11_USAGE_DYNAMIC;
+ bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
+ bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
+ HRESULT hr = device_->CreateBuffer(&bd, nullptr, ¶ms_cb_);
+ if (FAILED(hr)) {
+ LOG() << "AutoDepth: CreateBuffer(params) hr=0x" << std::hex << hr;
+ init_failed_ = true;
+ return false;
+ }
+ }
+
+ return sbs_srv_ != nullptr;
+}
+
+
+void AutoDepthAnalyzer::Run(ID3D11DeviceContext* ctx,
+ ID3D11Texture2D* sbs,
+ uint32_t sbs_w,
+ uint32_t sbs_h,
+ uint64_t frame_counter,
+ StereoDisplayComponent* component)
+{
+ if (!ctx || !sbs || !component) return;
+ if (sbs_w == 0 || sbs_h == 0) return;
+ if (!EnsureResources(sbs)) return;
+
+ const uint32_t eye_w = sbs_w / 2;
+ const uint32_t search_radius = (std::max)(eye_w / 4u, 16u);
+ // Disparity step / decimation stride: 4px (quarter-res) up to ~2K-wide
+ // eyes, growing with the search radius beyond that so the 128-bucket
+ // histogram always spans the full radius — previously anything past
+ // 508px of disparity silently piled into the top bucket and the loop
+ // under-corrected exactly when objects were closest.
+ const uint32_t kStride = (std::max)(
+ 4u, (search_radius + NUM_BUCKETS - 1u) / NUM_BUCKETS);
+
+ // Update params CB.
+ {
+ D3D11_MAPPED_SUBRESOURCE m{};
+ if (FAILED(ctx->Map(params_cb_.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &m))) return;
+ AutoDepthParams* p = reinterpret_cast(m.pData);
+ p->sbs_w = sbs_w;
+ p->sbs_h = sbs_h;
+ p->search_radius = search_radius;
+ p->stride = kStride;
+ ctx->Unmap(params_cb_.Get(), 0);
+ }
+
+ // Bind + clear + dispatch.
+ const UINT clear_val[4] = {0, 0, 0, 0};
+ ctx->ClearUnorderedAccessViewUint(result_uav_.Get(), clear_val);
+
+ ID3D11ShaderResourceView* srvs[1] = { sbs_srv_.Get() };
+ ID3D11UnorderedAccessView* uavs[1] = { result_uav_.Get() };
+ ID3D11Buffer* cbs[1] = { params_cb_.Get() };
+
+ ctx->CSSetShader(cs_.Get(), nullptr, 0);
+ ctx->CSSetShaderResources(0, 1, srvs);
+ ctx->CSSetUnorderedAccessViews(0, 1, uavs, nullptr);
+ ctx->CSSetConstantBuffers(0, 1, cbs);
+
+ const uint32_t tx = (eye_w / kStride + 7) / 8;
+ const uint32_t ty = (sbs_h / kStride + 7) / 8;
+ ctx->Dispatch((std::max)(tx, 1u), (std::max)(ty, 1u), 1);
+
+ // Unbind to avoid hazards with the OSD composite / presenter that follow.
+ ID3D11ShaderResourceView* null_srv[1] = { nullptr };
+ ID3D11UnorderedAccessView* null_uav[1] = { nullptr };
+ ctx->CSSetShaderResources(0, 1, null_srv);
+ ctx->CSSetUnorderedAccessViews(0, 1, null_uav, nullptr);
+
+ // Copy result to this frame's staging slot.
+ const uint32_t cur = ring_idx_;
+ const uint32_t other = ring_idx_ ^ 1u;
+ ctx->CopyResource(staging_[cur].Get(), result_buf_.Get());
+ inflight_frame_[cur] = frame_counter;
+
+ // Try to read the previous frame's result without stalling.
+ if (inflight_frame_[other] != UINT64_MAX) {
+ D3D11_MAPPED_SUBRESOURCE m{};
+ HRESULT hr = ctx->Map(staging_[other].Get(), 0,
+ D3D11_MAP_READ, D3D11_MAP_FLAG_DO_NOT_WAIT, &m);
+ if (SUCCEEDED(hr)) {
+ uint32_t hist[NUM_BUCKETS + 1] = {};
+ if (m.pData) {
+ std::memcpy(hist, m.pData, sizeof(hist));
+ }
+ ctx->Unmap(staging_[other].Get(), 0);
+ inflight_frame_[other] = UINT64_MAX;
+
+ const uint32_t match_count = hist[NUM_BUCKETS];
+ uint32_t max_disp = 0;
+ uint32_t top_bucket = 0;
+ // Compute the structural-residue "tail floor": average count
+ // across the top 12 buckets. Loop-boundary residue piles ~30-50
+ // hits/bucket here every frame regardless of scene; a real near-
+ // object peak will tower well above this floor.
+ uint32_t tail_sum = 0;
+ for (int b = NUM_BUCKETS - 12; b < NUM_BUCKETS; ++b) tail_sum += hist[b];
+ const uint32_t tail_floor = tail_sum / 12u;
+
+ // 5-bucket window must clear several thresholds simultaneously
+ // (per-bucket densities identical to the original 3-bucket tune,
+ // rescaled by 5/3 — the wider window lets a genuine near-object
+ // cluster spread over 5-6 buckets qualify where 3 adjacent
+ // buckets alone couldn't):
+ // (a) absolute floor (250 hits)
+ // (b) ~5.6% of total
+ // (c) 5x mean per bucket
+ // (d) 4x the tail-floor window (rejects flat boundary residue
+ // while still allowing genuine high-disparity clusters
+ // that produce a clear peak above the residue baseline)
+ const uint32_t mean_per_bucket = match_count / NUM_BUCKETS;
+ const uint32_t min_window = (std::max)({
+ 250u,
+ match_count / 18u,
+ 5u * 5u * mean_per_bucket,
+ 4u * 5u * tail_floor});
+ for (int b = NUM_BUCKETS - 3; b >= 2; --b) {
+ const uint32_t window = hist[b - 2] + hist[b - 1] + hist[b]
+ + hist[b + 1] + hist[b + 2];
+ if (window >= min_window) {
+ top_bucket = static_cast(b);
+ max_disp = top_bucket * kStride;
+ break;
+ }
+ }
+
+ // Peak at the top of the histogram means matches at/beyond the
+ // search radius piled into the last bucket — the true disparity
+ // may be larger than reported.
+ const bool saturated = top_bucket >= NUM_BUCKETS - 3;
+
+ component->FeedAutoDepthSample(max_disp, eye_w, kStride);
+
+ if (component->IsAutoDepthLoggingEnabled() &&
+ (frame_counter <= 5 || (frame_counter % 120 == 0))) {
+ LOG() << "AutoDepth: max_disp=" << max_disp
+ << " px (eye_w=" << eye_w
+ << ", search=" << search_radius
+ << ", stride=" << kStride
+ << ", bucket=" << top_bucket
+ << ", frac=" << (eye_w ? float(max_disp) / float(eye_w) : 0.0f)
+ << ", matches=" << match_count
+ << (saturated ? ", SATURATED" : "")
+ << ")";
+ std::ostringstream ss;
+ ss << "AutoDepth hist:";
+ for (int b = 0; b < NUM_BUCKETS - 8; b += 4) {
+ ss << " [" << b << "]=" << hist[b];
+ }
+ for (int b = NUM_BUCKETS - 8; b < NUM_BUCKETS; ++b) {
+ ss << " [" << b << "]=" << hist[b];
+ }
+ LOG() << ss.str();
+ }
+ }
+ // DXGI_ERROR_WAS_STILL_DRAWING -> just try again next frame.
+ }
+
+ ring_idx_ ^= 1u;
+}
+
+
+void AutoDepthAnalyzer::Shutdown()
+{
+ cs_.Reset();
+ sbs_srv_.Reset();
+ result_uav_.Reset();
+ result_buf_.Reset();
+ staging_[0].Reset();
+ staging_[1].Reset();
+ params_cb_.Reset();
+ device_.Reset();
+ srv_source_ = nullptr;
+ inflight_frame_[0] = inflight_frame_[1] = UINT64_MAX;
+ ring_idx_ = 0;
+ init_failed_ = false;
+}
+
+} // namespace vrto3d
diff --git a/vrto3d/src/auto_depth.h b/vrto3d/src/auto_depth.h
new file mode 100644
index 00000000..c2095d33
--- /dev/null
+++ b/vrto3d/src/auto_depth.h
@@ -0,0 +1,79 @@
+/*
+ * This file is part of VRto3D.
+ *
+ * VRto3D is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VRto3D is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with VRto3D. If not, see .
+ */
+#pragma once
+
+#include
+
+#include
+#include
+
+class StereoDisplayComponent;
+
+namespace vrto3d {
+
+// Disparity-search compute pipeline driving auto-depth.
+//
+// Lifetime: created once, lazy-initialized on first Run() call (so the D3D
+// device is already alive). Resources keyed to a specific source SbS texture
+// are rebuilt automatically whenever Run() sees a new pointer.
+//
+// Thread-affinity: all methods must be called on the renderer's window
+// thread (the same one that owns the immediate context).
+class AutoDepthAnalyzer {
+public:
+ AutoDepthAnalyzer() = default;
+ ~AutoDepthAnalyzer() = default;
+
+ // Stash the device. Resources are not built until the first Run().
+ void Init(ID3D11Device* device);
+
+ // Dispatch the disparity search on `sbs` (2W x H side-by-side, full
+ // SbS dimensions in `sbs_w`/`sbs_h`), copy the histogram to a staging
+ // ring slot, and read back the previous frame's slot without stalling.
+ // Feeds the result into component->FeedAutoDepthSample. `frame_counter`
+ // is used solely for diagnostic logging cadence.
+ void Run(ID3D11DeviceContext* ctx,
+ ID3D11Texture2D* sbs,
+ uint32_t sbs_w,
+ uint32_t sbs_h,
+ uint64_t frame_counter,
+ StereoDisplayComponent* component);
+
+ // Release every D3D resource. Safe to call multiple times.
+ void Shutdown();
+
+private:
+ bool EnsureResources(ID3D11Texture2D* sbs);
+
+ Microsoft::WRL::ComPtr device_;
+ Microsoft::WRL::ComPtr cs_;
+ Microsoft::WRL::ComPtr sbs_srv_;
+ Microsoft::WRL::ComPtr result_buf_;
+ Microsoft::WRL::ComPtr result_uav_;
+ Microsoft::WRL::ComPtr staging_[2];
+ Microsoft::WRL::ComPtr params_cb_;
+
+ // Source texture the SRV is bound to. When this changes (e.g. resize),
+ // the SRV is rebuilt.
+ ID3D11Texture2D* srv_source_ = nullptr;
+
+ uint64_t inflight_frame_[2] = { UINT64_MAX, UINT64_MAX };
+ uint32_t ring_idx_ = 0;
+ bool init_failed_ = false;
+};
+
+} // namespace vrto3d
diff --git a/vrto3d/src/device_provider.cpp b/vrto3d/src/device_provider.cpp
index 5a7e606e..1ecd0b7f 100644
--- a/vrto3d/src/device_provider.cpp
+++ b/vrto3d/src/device_provider.cpp
@@ -16,13 +16,53 @@
*/
#define WIN32_LEAN_AND_MEAN
-#include
+#include
+#include
+#ifdef _WIN32
#include
#include
#include
#include "vrto3dlib/win32_helper.hpp"
#include "device_provider.h"
+#include "dx11_renderer.h"
+#include "direct_mode_component.h"
+#include "presenter/nvstereo_dx9_presenter.h"
+#else
+#include
+#include
+#include
+#include
+
+#include "vrto3dlib/linux_helper.hpp"
+#include "device_provider.h"
+#include "vk/vk_renderer.h"
+#include "vk/direct_mode_component_vk.h"
+#endif
+#include "vr_recenter.h"
+
+namespace {
+
+// Sample the PID a few seconds after disconnect — most games take a moment
+// to fully exit after their SteamVR connection drops. If the PID is gone,
+// shut SteamVR down; otherwise the user still has the app running and we
+// leave SteamVR alone.
+void ScheduleAutoExitCheck(uint32_t pid)
+{
+ std::thread([pid]() {
+ std::this_thread::sleep_for(std::chrono::seconds(5));
+ if (IsProcessRunning(pid)) {
+ LOG() << "auto_exit: pid " << pid
+ << " still running after 5s, leaving SteamVR alone";
+ return;
+ }
+ LOG() << "auto_exit: pid " << pid
+ << " is gone, shutting down SteamVR";
+ RequestSteamVRShutdown();
+ }).detach();
+}
+
+} // namespace
//-----------------------------------------------------------------------------
// Purpose: This is called by vrserver after it receives a pointer back from HmdDriverFactory.
@@ -30,8 +70,33 @@
//-----------------------------------------------------------------------------
vr::EVRInitError MyDeviceProvider::Init( vr::IVRDriverContext *pDriverContext )
{
+#ifdef _WIN32
global_mtx_ = CreateMutex(NULL, TRUE, kVRto3DMutexName);
+ // Best-effort process-level DPI elevation. Usually fails because vrserver
+ // already locked its awareness, in which case the per-thread elevation in
+ // LeiaSrPresenter::WindowThreadLoop and the WM_DPICHANGED handler in
+ // PresentWndProc cover us.
+ using SetProcessDpiAwarenessContextFn = BOOL (WINAPI*)(DPI_AWARENESS_CONTEXT);
+ if (HMODULE user32 = GetModuleHandleW(L"user32.dll")) {
+ if (auto set_proc_ctx = reinterpret_cast(
+ GetProcAddress(user32, "SetProcessDpiAwarenessContext"))) {
+ set_proc_ctx(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
+ }
+ }
+#else
+ // Single-instance guard: flock'd file in XDG_RUNTIME_DIR (mirrors the
+ // Global\VRto3DDriver named mutex on Windows).
+ {
+ const char* run = getenv("XDG_RUNTIME_DIR");
+ std::string lock_path = std::string(run ? run : "/tmp") + "/" + kVRto3DLockName;
+ int fd = open(lock_path.c_str(), O_CREAT | O_RDWR | O_CLOEXEC, 0600);
+ if (fd >= 0)
+ flock(fd, LOCK_EX | LOCK_NB); // advisory; keep fd for process lifetime
+ global_mtx_ = (void*)(intptr_t)fd;
+ }
+#endif
+
// We need to initialise our driver context to make calls to the server.
// OpenVR provides a macro to do this for us.
VR_INIT_SERVER_DRIVER_CONTEXT( pDriverContext );
@@ -39,13 +104,19 @@ vr::EVRInitError MyDeviceProvider::Init( vr::IVRDriverContext *pDriverContext )
// First, initialize our hmd, which we'll later pass OpenVR a pointer to.
my_hmd_device_ = std::make_unique< MockControllerDeviceDriver >();
+ static const char kSerialNumber[] = "VRto3D-1234";
+
// TrackedDeviceAdded returning true means we have had our device added to SteamVR.
- if (!vr::VRServerDriverHost()->TrackedDeviceAdded("VRto3D-1234", vr::TrackedDeviceClass_HMD, my_hmd_device_.get()))
+ if (!vr::VRServerDriverHost()->TrackedDeviceAdded(kSerialNumber, vr::TrackedDeviceClass_HMD, my_hmd_device_.get()))
{
LOG() << "Failed to create hmd device!";
return vr::VRInitError_Driver_Unknown;
}
+ // No DisplayRedirect sibling needed in direct mode — game eye textures
+ // arrive via IVRDriverDirectModeComponent::SubmitLayer / Present rather
+ // than IVRVirtualDisplay::Present.
+
return vr::VRInitError_None;
}
@@ -81,27 +152,54 @@ void MyDeviceProvider::RunFrame()
vr::VREvent_t vrEvent;
while (vr::VRServerDriverHost()->PollNextEvent(&vrEvent, sizeof(vrEvent)))
{
- if ((vrEvent.eventType == vr::VREvent_ProcessConnected ||
+ // Connect-side events are NOT gated by wait_count_. When a mod (e.g.
+ // RealVR) re-inits VR after an FoV/resolution change, the reconnect
+ // event lands inside the post-disconnect cool-off window; dropping it
+ // would leave the renderer stuck in paused_for_disconnect_ with no
+ // further chance to resume.
+ if (vrEvent.eventType == vr::VREvent_ProcessConnected ||
vrEvent.eventType == vr::VREvent_ActionBindingReloaded ||
vrEvent.eventType == vr::VREvent_SceneApplicationChanged ||
- vrEvent.eventType == vr::VREvent_Input_BindingLoadFailed ||
+ vrEvent.eventType == vr::VREvent_Input_BindingLoadFailed ||
vrEvent.eventType == vr::VREvent_Input_BindingLoadSuccessful ||
- vrEvent.eventType == vr::VREvent_Input_ActionManifestReloaded) &&
- wait_count_ == 0)
+ vrEvent.eventType == vr::VREvent_Input_ActionManifestReloaded)
{
auto appName = GetProcessName(vrEvent.data.process.pid);
auto lowerAppName = appName;
std::transform(lowerAppName.begin(), lowerAppName.end(), lowerAppName.begin(), ::tolower);
- if (Skip_Processes.find(appName) == Skip_Processes.end() &&
- lowerAppName.find("exe") != std::string::npos)
+#ifdef _WIN32
+ const bool name_is_game = lowerAppName.find("exe") != std::string::npos;
+#else
+ // Native Linux games have no .exe suffix; Proton games keep it.
+ const bool name_is_game = !lowerAppName.empty();
+#endif
+ const bool skipped = Skip_Processes.find(lowerAppName) != Skip_Processes.end();
+ if (!skipped && name_is_game)
{
app_name_ = appName;
app_pid_ = vrEvent.data.process.pid;
- LOG() << "AppName = " << app_name_.c_str();
+ g_current_app_pid.store(app_pid_);
+ LOG() << "AppName = " << app_name_.c_str()
+ << " (evt=" << (int)vrEvent.eventType
+ << " pid=" << vrEvent.data.process.pid << ")";
my_hmd_device_->LoadSettings(app_name_, app_pid_, vr::VREvent_ProcessConnected);
+ // Resume the renderer (cleared by the prior ProcessDisconnected).
+ if (auto* r = my_hmd_device_->GetRenderer()) r->OnAppConnect();
wait_count_ = 500;
}
+ else if (vrEvent.eventType == vr::VREvent_ProcessConnected ||
+ vrEvent.eventType == vr::VREvent_SceneApplicationChanged)
+ {
+ // Not treated as a game connect. Logged so we can see what
+ // becomes the scene app after a real game exits (e.g. SteamVR
+ // Home / environments) — a spurious auto-raise there is the
+ // suspected "stayed on top after quitting the game" cause.
+ LOG() << "connect ignored: name='" << appName.c_str()
+ << "' pid=" << vrEvent.data.process.pid
+ << " evt=" << (int)vrEvent.eventType
+ << " skip=" << skipped << " is_game=" << name_is_game;
+ }
}
else if ((vrEvent.eventType == vr::VREvent_ProcessDisconnected ||
vrEvent.eventType == vr::VREvent_Compositor_ApplicationNotResponding ||
@@ -109,12 +207,45 @@ void MyDeviceProvider::RunFrame()
!app_name_.empty() && vrEvent.data.process.pid == app_pid_ && wait_count_ == 0)
{
LOG() << "Unload = " << app_name_.c_str();
+ // Sample auto_exit + pid before LoadSettings / app_pid_ reset.
+ // The actual liveness check + shutdown happens on a delayed
+ // thread (ScheduleAutoExitCheck) so the game has time to fully
+ // terminate after the SteamVR disconnect fires.
+ const bool want_auto_exit =
+ my_hmd_device_->GetStereoComponent() &&
+ my_hmd_device_->GetStereoComponent()->GetConfig().auto_exit;
+ const uint32_t pid_snapshot = app_pid_;
my_hmd_device_->LoadSettings(app_name_, app_pid_, vr::VREvent_ProcessDisconnected);
+
+ // Drop the departed game's swap-texture handles BEFORE we
+ // continue submitting frames. The compositor doesn't always call
+ // DestroySwapTextureSet on app exit, leaving stale entries in
+ // handle_map_ that the renderer would try to import — and that
+ // import was the trigger for the DEVICE_REMOVED cascade observed
+ // in crash logs. Pause the renderer so it stops trying until the
+ // next ProcessConnected.
+ if (auto* dmc = my_hmd_device_->GetDirectModeComponent()) {
+ dmc->DestroyAllSwapTextureSets(pid_snapshot);
+ }
+ if (auto* r = my_hmd_device_->GetRenderer()) r->OnAppDisconnect();
+
app_name_ = "";
app_pid_ = 0;
+ g_current_app_pid.store(0);
wait_count_ = 500;
+ if (want_auto_exit) {
+ LOG() << "auto_exit: scheduling check for pid " << pid_snapshot;
+ ScheduleAutoExitCheck(pid_snapshot);
+ }
}
}
+
+ // VREvent_Quit isn't delivered via the driver-host event stream — it
+ // goes to *client* apps over their IPC pipe. Our TriggerOpenVRRecenter
+ // VR_Init registers us as a Background client, so we have to drain
+ // that client-side queue and ack quit, or vrserver waits 5s for us
+ // and force-kills the process.
+ vrto3d::PumpOpenVRClientEvents();
}
//-----------------------------------------------------------------------------
@@ -140,12 +271,41 @@ void MyDeviceProvider::LeaveStandby()
//-----------------------------------------------------------------------------
void MyDeviceProvider::Cleanup()
{
+#ifdef _WIN32
if (global_mtx_)
{
CloseHandle((HANDLE)global_mtx_);
}
+#else
+ if ((intptr_t)global_mtx_ >= 0)
+ {
+ close((int)(intptr_t)global_mtx_);
+ }
+#endif
// Our controller devices will have already deactivated. Let's now destroy them.
my_hmd_device_ = nullptr;
+
+ // Tear down the background OpenVR client session we used for chaperone
+ // recenters. vrserver flags an unclean exit if a VR_Init'd client never
+ // VR_Shutdown's. This is the last hook called before our DLL unloads.
+ vrto3d::ShutdownOpenVRClient();
+
+#ifdef _WIN32
+ // NV3D-Glass exit pattern: when the NVIDIA 3D Vision presenter ran this
+ // session, hard-kill the process now that our own teardown is complete.
+ // A hard display freeze (reboot required) has been observed AFTER a clean
+ // explicit teardown — inside process exit, where nvd3dumx.dll /
+ // nvapi64.dll / d3d9.dll run their DLL_PROCESS_DETACH against the driver
+ // stereo state the FSE teardown just strained. TerminateProcess skips CRT
+ // static destructors and all DLL_PROCESS_DETACH (ExitProcess would not)
+ // and never returns on success. Every other output mode keeps the normal
+ // exit path.
+ if (vrto3d::NvStereoWasActiveThisSession()) {
+ LOG() << "MyDeviceProvider::Cleanup: NV3D session — TerminateProcess "
+ "to skip NVIDIA DLL_PROCESS_DETACH";
+ TerminateProcess(GetCurrentProcess(), 0);
+ }
+#endif
}
diff --git a/vrto3d/src/direct_mode_component.cpp b/vrto3d/src/direct_mode_component.cpp
new file mode 100644
index 00000000..a150aa8e
--- /dev/null
+++ b/vrto3d/src/direct_mode_component.cpp
@@ -0,0 +1,417 @@
+/*
+ * This file is part of VRto3D.
+ *
+ * VRto3D is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VRto3D is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with VRto3D. If not, see .
+ */
+
+#include "direct_mode_component.h"
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include "dx11_renderer.h"
+#include "vrto3dlib/debug_log.hpp"
+#include "vrto3dlib/win32_helper.hpp"
+
+
+DirectModeComponent::DirectModeComponent(Dx11Renderer* renderer)
+ : renderer_(renderer)
+{}
+
+
+DirectModeComponent::~DirectModeComponent()
+{
+ std::lock_guard lk(sets_mutex_);
+ // Each TextureSet shows up three times in handle_map_; delete each unique
+ // pointer once.
+ std::set deleted;
+ for (auto& kv : handle_map_) {
+ if (deleted.insert(kv.second.first).second) {
+ delete kv.second.first;
+ }
+ }
+ handle_map_.clear();
+}
+
+
+void DirectModeComponent::CreateSwapTextureSet(uint32_t unPid,
+ const SwapTextureSetDesc_t* pDesc,
+ SwapTextureSet_t* pOut)
+{
+ if (!pDesc || !pOut || !renderer_ || !renderer_->Device()) return;
+
+ // Once the DX11 device is terminally dead, every CreateTexture2D below
+ // fails with DXGI_ERROR_DEVICE_REMOVED (0x887a0005). SteamVR retries
+ // CreateSwapTextureSet on each failure, which becomes a hot spam loop
+ // that has been observed to wedge vrcompositor and bring SteamVR down.
+ // Refuse early so the swap-texture set comes back empty and the caller
+ // eventually surfaces the failure instead of pounding the dead device.
+ if (renderer_->IsDeviceDead()) {
+ static std::atomic logged{false};
+ bool e = false;
+ if (logged.compare_exchange_strong(e, true)) {
+ LOG() << "DirectModeComponent::CreateSwapTextureSet pid=" << unPid
+ << " refused — DX11 device is dead, SteamVR restart required";
+ }
+ return;
+ }
+
+ LOG() << "DirectModeComponent::CreateSwapTextureSet pid=" << unPid
+ << " " << pDesc->nWidth << "x" << pDesc->nHeight
+ << " fmt=" << pDesc->nFormat
+ << " samples=" << pDesc->nSampleCount;
+
+ D3D11_TEXTURE2D_DESC td{};
+ td.Width = pDesc->nWidth;
+ td.Height = pDesc->nHeight;
+ td.MipLevels = 1;
+ td.ArraySize = 1;
+ td.Format = static_cast(pDesc->nFormat);
+ td.SampleDesc.Count = pDesc->nSampleCount == 0 ? 1 : pDesc->nSampleCount;
+ td.Usage = D3D11_USAGE_DEFAULT;
+ td.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
+ // Legacy shared handle (matches ALVR's working path; the NTHANDLE +
+ // keyed-mutex path is documented but not exercised here).
+ td.MiscFlags = D3D11_RESOURCE_MISC_SHARED;
+
+ auto* set = new TextureSet();
+ set->pid = unPid;
+
+ bool ok = true;
+ for (int i = 0; i < 3; ++i) {
+ HRESULT hr = renderer_->Device()->CreateTexture2D(&td, nullptr, &set->textures[i]);
+ if (FAILED(hr) || !set->textures[i]) {
+ LOG() << "DirectModeComponent: CreateTexture2D[" << i << "] failed hr=0x"
+ << std::hex << hr;
+ // If the failure is a terminal DXGI device state, latch the
+ // renderer so subsequent CreateSwapTextureSet calls bail at the
+ // top instead of spam-failing.
+ (void)renderer_->MarkDeviceDeadIfTerminal(hr, "DirectModeComponent::CreateTexture2D");
+ ok = false;
+ break;
+ }
+
+ Microsoft::WRL::ComPtr dxgi_res;
+ hr = set->textures[i].As(&dxgi_res);
+ if (FAILED(hr) || !dxgi_res) {
+ LOG() << "DirectModeComponent: QueryInterface(IDXGIResource)[" << i
+ << "] failed hr=0x" << std::hex << hr;
+ ok = false;
+ break;
+ }
+
+ hr = dxgi_res->GetSharedHandle(&set->handles[i]);
+ if (FAILED(hr) || !set->handles[i]) {
+ LOG() << "DirectModeComponent: GetSharedHandle[" << i << "] failed hr=0x"
+ << std::hex << hr;
+ ok = false;
+ break;
+ }
+
+ pOut->rSharedTextureHandles[i] =
+ reinterpret_cast(set->handles[i]);
+ }
+
+ if (!ok) {
+ delete set;
+ for (int i = 0; i < 3; ++i) pOut->rSharedTextureHandles[i] = 0;
+ pOut->unTextureFlags = 0;
+ return;
+ }
+
+ // unTextureFlags = 0 — we use legacy shared handles, not NT handles.
+ pOut->unTextureFlags = 0;
+
+ {
+ std::lock_guard lk(sets_mutex_);
+ for (int i = 0; i < 3; ++i) {
+ handle_map_.emplace(set->handles[i], std::make_pair(set, i));
+ }
+ }
+}
+
+
+void DirectModeComponent::DestroySwapTextureSet(vr::SharedTextureHandle_t sharedTextureHandle)
+{
+ auto raw = reinterpret_cast(static_cast(sharedTextureHandle));
+ std::lock_guard lk(sets_mutex_);
+ auto it = handle_map_.find(raw);
+ if (it == handle_map_.end()) return;
+ TextureSet* set = it->second.first;
+ for (HANDLE h : set->handles) handle_map_.erase(h);
+ delete set;
+}
+
+
+void DirectModeComponent::DestroyAllSwapTextureSets(uint32_t unPid)
+{
+ std::lock_guard lk(sets_mutex_);
+ // Collect unique sets owned by this pid, delete them, then prune the map.
+ std::set to_delete;
+ for (auto& kv : handle_map_) {
+ if (kv.second.first->pid == unPid) to_delete.insert(kv.second.first);
+ }
+ for (TextureSet* s : to_delete) {
+ for (HANDLE h : s->handles) handle_map_.erase(h);
+ delete s;
+ }
+}
+
+
+void DirectModeComponent::GetNextSwapTextureSetIndex(vr::SharedTextureHandle_t /*handles*/[2],
+ uint32_t (*pIndices)[2])
+{
+ (*pIndices)[0] = ((*pIndices)[0] + 1) % 3;
+ (*pIndices)[1] = ((*pIndices)[1] + 1) % 3;
+}
+
+
+void DirectModeComponent::SubmitLayer(const SubmitLayerPerEye_t (&perEye)[2])
+{
+ std::lock_guard lk(submit_mutex_);
+ if (submit_layer_count_ < kMaxLayers) {
+ submit_layers_[submit_layer_count_][0] = perEye[0];
+ submit_layers_[submit_layer_count_][1] = perEye[1];
+ }
+ // Always increment so the logged count reflects what the compositor
+ // submits even when it exceeds kMaxLayers.
+ ++submit_layer_count_;
+}
+
+
+void DirectModeComponent::Present(vr::SharedTextureHandle_t syncTexture)
+{
+ if (!renderer_) return;
+
+ SubmitLayerPerEye_t local[kMaxLayers][2]{};
+ int submitted_count = 0;
+ int kept_count = 0;
+ {
+ std::lock_guard lk(submit_mutex_);
+ submitted_count = submit_layer_count_;
+ submit_layer_count_ = 0;
+ if (submitted_count == 0) {
+ if (last_logged_submit_count_ != 0) {
+ LOG() << "DirectModeComponent: Present with no SubmitLayer "
+ "(was " << last_logged_submit_count_ << ")";
+ last_logged_submit_count_ = 0;
+ }
+ return;
+ }
+ kept_count = submitted_count > kMaxLayers ? kMaxLayers : submitted_count;
+ for (int i = 0; i < kept_count; ++i) {
+ local[i][0] = submit_layers_[i][0];
+ local[i][1] = submit_layers_[i][1];
+ }
+ }
+
+ if (submitted_count != last_logged_submit_count_) {
+ LOG() << "DirectModeComponent: SubmitLayer calls per Present = "
+ << submitted_count
+ << " (was " << last_logged_submit_count_ << ")";
+ last_logged_submit_count_ = submitted_count;
+ }
+
+ // Resolve each layer's eye handles back to the textures we allocated in
+ // CreateSwapTextureSet, dropping placeholder/marker quads as we go. The
+ // resolve loop also compacts local[] in lockstep with pairs[] so the
+ // sort below can permute them together by a single index.
+ //
+ // Placeholder filter: UEVR mods can submit a 2x4 or 16x16 marker on the
+ // game's pid alongside the real scene that flows through vrcompositor's
+ // full-eye target. If we kept the marker it would either (a) sort to
+ // index 0 under the pid-first rule and shrink out_sbs_ to ~4x4, breaking
+ // the picture, or (b) end up as a stretched overlay smearing whatever
+ // garbage the placeholder holds across the full eye. Anything below
+ // kMinSceneAreaPx effective pixels can't carry visual content; drop it.
+ constexpr uint64_t kMinSceneAreaPx = 64ull * 64ull;
+ Dx11Renderer::DirectModeLayerPair pairs[kMaxLayers]{};
+ int resolved_count = 0;
+ int filtered_tiny = 0;
+ {
+ std::lock_guard lk(sets_mutex_);
+ auto find_one = [&](vr::SharedTextureHandle_t h, uint32_t* out_pid)
+ -> Microsoft::WRL::ComPtr
+ {
+ auto raw = reinterpret_cast(static_cast(h));
+ auto it = handle_map_.find(raw);
+ if (it == handle_map_.end()) return {};
+ if (out_pid) *out_pid = it->second.first->pid;
+ return it->second.first->textures[it->second.second];
+ };
+ for (int i = 0; i < kept_count; ++i) {
+ uint32_t pid_l = 0, pid_r = 0;
+ Microsoft::WRL::ComPtr left = find_one(local[i][0].hTexture, &pid_l);
+ Microsoft::WRL::ComPtr right = find_one(local[i][1].hTexture, &pid_r);
+ if (!left || !right) {
+ ++handle_miss_count_;
+ continue;
+ }
+ D3D11_TEXTURE2D_DESC ld{};
+ left->GetDesc(&ld);
+ const float du = std::abs(local[i][0].bounds.uMax - local[i][0].bounds.uMin);
+ const float dv = std::abs(local[i][0].bounds.vMax - local[i][0].bounds.vMin);
+ const uint64_t eff_px =
+ static_cast(static_cast(ld.Width) * du + 0.5) *
+ static_cast(static_cast(ld.Height) * dv + 0.5);
+ if (eff_px < kMinSceneAreaPx) {
+ ++filtered_tiny;
+ continue;
+ }
+ pairs[resolved_count].left = std::move(left);
+ pairs[resolved_count].right = std::move(right);
+ pairs[resolved_count].bounds_left = local[i][0].bounds;
+ pairs[resolved_count].bounds_right = local[i][1].bounds;
+ pairs[resolved_count].pid = pid_l;
+ if (i != resolved_count) {
+ local[resolved_count][0] = local[i][0];
+ local[resolved_count][1] = local[i][1];
+ }
+ ++resolved_count;
+ }
+ }
+ if (filtered_tiny > 0 && filtered_tiny != last_logged_filtered_tiny_) {
+ LOG() << "DirectModeComponent: filtered " << filtered_tiny
+ << " sub-threshold layer(s) (<" << kMinSceneAreaPx << "px)";
+ last_logged_filtered_tiny_ = filtered_tiny;
+ } else if (filtered_tiny == 0 && last_logged_filtered_tiny_ != 0) {
+ last_logged_filtered_tiny_ = 0;
+ }
+
+ // Reorder layers so OnDirectModeFrame's "layers[0] is the opaque base,
+ // layers[1+] composite on top" assumption holds across mods.
+ //
+ // The resolve loop above already dropped sub-threshold placeholder
+ // quads, so every remaining layer is real, scene-or-overlay-sized
+ // content. Two-tier sort over what's left:
+ // 1. Layers owned by the game process come before layers owned by
+ // vrcompositor.exe. This catches Luke Ross R.E.A.L., which submits
+ // the actual 3D scene from the game's pid (a smaller texture) but
+ // routes its in-game UI / mod menu through a larger vrcompositor-
+ // owned overlay layer. Without this, the area heuristic alone
+ // picked the UI layer as base and the game scene (alpha=1 across
+ // the surface) drew over it, hiding the UI completely.
+ // 2. Within the same group, larger effective area (texture dims
+ // scaled by submitted bounds rect) wins. This handles UEVR mods
+ // that submit both scene and a same-pid HUD quad: the full scene
+ // beats the smaller HUD. It also handles bounds-shrunk
+ // allocations (e.g. a 2592x1458 texture with bounds=(0,0,0.741,
+ // 0.741) rendered into the top-left 1920x1080) — raw
+ // width*height would mis-prefer a smaller full-bounds layer
+ // like SteamVR Home.
+ if (resolved_count > 1) {
+ auto effective_area = [](const Dx11Renderer::DirectModeLayerPair& p) -> uint64_t {
+ D3D11_TEXTURE2D_DESC d{};
+ p.left->GetDesc(&d);
+ const float du = std::abs(p.bounds_left.uMax - p.bounds_left.uMin);
+ const float dv = std::abs(p.bounds_left.vMax - p.bounds_left.vMin);
+ const double eff_w = static_cast(d.Width) * du;
+ const double eff_h = static_cast(d.Height) * dv;
+ if (eff_w <= 0.0 || eff_h <= 0.0) return 0;
+ return static_cast(eff_w * eff_h);
+ };
+
+ struct LayerKey {
+ int original_index;
+ bool is_compositor;
+ uint64_t eff_area;
+ };
+ LayerKey keys[kMaxLayers];
+ for (int i = 0; i < resolved_count; ++i) {
+ auto it = pid_name_cache_.find(pairs[i].pid);
+ if (it == pid_name_cache_.end()) {
+ it = pid_name_cache_.emplace(pairs[i].pid, GetProcessName(pairs[i].pid)).first;
+ }
+ keys[i].original_index = i;
+ keys[i].is_compositor = _stricmp(it->second.c_str(), "vrcompositor.exe") == 0;
+ keys[i].eff_area = effective_area(pairs[i]);
+ }
+
+ int order[kMaxLayers];
+ for (int i = 0; i < resolved_count; ++i) order[i] = i;
+ std::stable_sort(order, order + resolved_count,
+ [&](int a, int b) {
+ if (keys[a].is_compositor != keys[b].is_compositor) {
+ return !keys[a].is_compositor;
+ }
+ return keys[a].eff_area > keys[b].eff_area;
+ });
+
+ bool needs_reorder = false;
+ for (int i = 0; i < resolved_count; ++i) {
+ if (order[i] != i) { needs_reorder = true; break; }
+ }
+ if (needs_reorder) {
+ Dx11Renderer::DirectModeLayerPair sorted_pairs[kMaxLayers]{};
+ SubmitLayerPerEye_t sorted_local[kMaxLayers][2]{};
+ for (int i = 0; i < resolved_count; ++i) {
+ sorted_pairs[i] = std::move(pairs[order[i]]);
+ sorted_local[i][0] = local[order[i]][0];
+ sorted_local[i][1] = local[order[i]][1];
+ }
+ for (int i = 0; i < resolved_count; ++i) {
+ pairs[i] = std::move(sorted_pairs[i]);
+ local[i][0] = sorted_local[i][0];
+ local[i][1] = sorted_local[i][1];
+ }
+ }
+ }
+
+ // Per-layer log: emitted once per change in resolved layer count.
+ // Useful when adding a new mod/game combo — pid + process name lets
+ // us sanity-check the post-reorder order (game-pid layer should be
+ // at index 0, vrcompositor-pid layers should follow).
+ if (resolved_count != last_logged_layer_dim_count_) {
+ last_logged_layer_dim_count_ = resolved_count;
+ for (int i = 0; i < resolved_count; ++i) {
+ D3D11_TEXTURE2D_DESC ld{}, rd{};
+ pairs[i].left->GetDesc(&ld);
+ pairs[i].right->GetDesc(&rd);
+
+ const uint32_t pid = pairs[i].pid;
+ auto it = pid_name_cache_.find(pid);
+ if (it == pid_name_cache_.end()) {
+ it = pid_name_cache_.emplace(pid, GetProcessName(pid)).first;
+ }
+ const std::string& pname = it->second;
+
+ LOG() << "DirectModeComponent: layer " << i
+ << " pid=" << pid << "(" << pname.c_str() << ")"
+ << " dims L=" << ld.Width << "x" << ld.Height
+ << " R=" << rd.Width << "x" << rd.Height
+ << " fmt=" << ld.Format
+ << " bounds L=(" << local[i][0].bounds.uMin << "," << local[i][0].bounds.vMin
+ << "," << local[i][0].bounds.uMax << "," << local[i][0].bounds.vMax << ")";
+ }
+ }
+
+ if (resolved_count == 0) {
+ if (handle_miss_count_ == 1 || (handle_miss_count_ % 60) == 0) {
+ LOG() << "DirectModeComponent::Present: no submitted layers resolved "
+ "to handle_map_ entries (misses=" << handle_miss_count_ << ")";
+ }
+ return;
+ }
+ if (handle_miss_count_ > 0) {
+ LOG() << "DirectModeComponent::Present: handle lookups recovered after "
+ << handle_miss_count_ << " misses";
+ handle_miss_count_ = 0;
+ }
+
+ renderer_->OnDirectModeFrame(pairs, resolved_count, syncTexture);
+}
diff --git a/vrto3d/src/direct_mode_component.h b/vrto3d/src/direct_mode_component.h
new file mode 100644
index 00000000..a020c2d9
--- /dev/null
+++ b/vrto3d/src/direct_mode_component.h
@@ -0,0 +1,108 @@
+/*
+ * This file is part of VRto3D.
+ *
+ * VRto3D is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * VRto3D is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with VRto3D. If not, see .
+ */
+#pragma once
+
+#include