From a3c6e5c984354e9d13ddfeef157d38cb053781be Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 13:33:46 +1000 Subject: [PATCH 01/36] Mac in the headset: stream Mac windows and screens into the Frame as panels Frame Control can now show any Mac window, or a whole screen, as its own SteamVR panel on the Steam Frame (Tools -> Mac in the headset, macOS only). Place it with the SteamVR dashboard; the laser clicks and scrolls, and the Mac's own keyboard types. - mac/frame-mac-view (Swift, no dependencies): ScreenCaptureKit capture per window or display, VideoToolbox H.264 with low-latency rate control (JPEG fallback), a loopback HTTP/WebSocket server, CGEvent/AX input playback, and a display-awake assertion while anyone watches. - ui/frame_macview.py: starts the agent, runs an ssh -R tunnel with a supervisor that reopens it on the same port, and launches a Chromium app window per stream on gamescope's :0, tagged with STEAM_GAME for its own panel. - Frame Control's key never leaves the Mac: viewers get single-use, per-source tickets and reconnect keys that Stop revokes. - ui/mac-view.html: WebCodecs decode, keyframe recovery, pointer/wheel/keys back. - Bundled in the Mac app build; tests/test_macview.py builds and drives the agent on macOS. Verified on the Frame (build 20260925.6191901) with the test pattern: panel in about 1.5 s, about 60 fps, Mac-to-window about 11-17 ms, tunnel recovery in 4 s. Laser input and real window capture still need a person in the headset. Also commits the other thread's first-party rule (steam-frame skill) and the first-party options table in docs/streaming.md. Reviewed by GPT-6 Astra (xhigh, read-only) over six rounds; all findings fixed. Co-Authored-By: Claude Opus 5.5 (1M context) --- .claude/skills/steam-frame/SKILL.md | 6 + .gitignore | 1 + README.md | 1 + app/package.json | 16 +- docs/frame-control.md | 4 + docs/how-the-frame-works.md | 1 + docs/mac-in-headset.md | 204 ++++++++++ docs/open-questions.md | 13 + docs/streaming.md | 18 +- mac/frame-mac-view/Sources/Capture.swift | 339 ++++++++++++++++ mac/frame-mac-view/Sources/Encoder.swift | 172 ++++++++ mac/frame-mac-view/Sources/Input.swift | 168 ++++++++ mac/frame-mac-view/Sources/Server.swift | 267 ++++++++++++ mac/frame-mac-view/Sources/main.swift | 490 +++++++++++++++++++++++ mac/frame-mac-view/build.sh | 10 + tests/test_macview.py | 274 +++++++++++++ ui/frame_macview.py | 361 +++++++++++++++++ ui/index.html | 81 +++- ui/mac-view.html | 242 +++++++++++ ui/server.py | 46 ++- 20 files changed, 2708 insertions(+), 6 deletions(-) create mode 100644 docs/mac-in-headset.md create mode 100644 mac/frame-mac-view/Sources/Capture.swift create mode 100644 mac/frame-mac-view/Sources/Encoder.swift create mode 100644 mac/frame-mac-view/Sources/Input.swift create mode 100644 mac/frame-mac-view/Sources/Server.swift create mode 100644 mac/frame-mac-view/Sources/main.swift create mode 100755 mac/frame-mac-view/build.sh create mode 100644 tests/test_macview.py create mode 100644 ui/frame_macview.py create mode 100644 ui/mac-view.html diff --git a/.claude/skills/steam-frame/SKILL.md b/.claude/skills/steam-frame/SKILL.md index 0437825..48c9f1c 100644 --- a/.claude/skills/steam-frame/SKILL.md +++ b/.claude/skills/steam-frame/SKILL.md @@ -48,3 +48,9 @@ running `--help`: `paste-to-frame.sh`, `serve-bootstrap.sh` and - `sudo` on the Frame asks for the user's Developer Mode password. Hand those steps to the user (open Terminal) and keep automation to non-sudo commands. - The Mac uses BSD userland and zsh (no `timeout`, use `head -n`). +- **First-party first.** For any new capability, investigate the first-party + way before anything else: Valve (SteamOS, Steam, Steam Link), Apple (the Mac + and iPhone), and KDE (the Frame's desktop is Plasma). It's usually the best + answer. If it isn't, write down why not. If it is, find what Frame Control + can do to make it easier to set up (install over SSH, pre-seed settings, + pair automatically, tell the user the one setting to turn on). diff --git a/.gitignore b/.gitignore index d89ecc2..0c187b8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ apk-catalog/data/index-v2.json* compat-db/.env.lakebed.server compat-db/.lakebed/ tests/smoke/results/ +mac/bin/ diff --git a/README.md b/README.md index 5f25235..36bffb4 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,7 @@ Frame's software fits together, all checked against a real headset and labelled | [Sideloading Linux and Windows games](docs/sideloading.md) | A .zip, folder or .exe as a Steam Devkit Game, runtime detection | | [Install links for websites](docs/web-install.md) | `frame-control://install` links and manifests, the rules, a button to paste | | [Steam games](docs/steam-games.md) · [VR video](docs/vr-video.md) · [WebXR in Chromium](docs/webxr-chromium.md) | Installing and buying, watching VR180/360, the Chromium build | +| [Mac in the headset](docs/mac-in-headset.md) | Mac windows and screens as panels in the Frame, with laser and keyboard input | | [SSH](docs/ssh.md) · [Streaming](docs/streaming.md) · [Files](docs/file-transfer.md) · [Panels](docs/panels.md) · [Tailscale](docs/tailscale.md) | Topic notes | | [Frame Control for iPhone](docs/iphone.md) | The iPhone and iPad app, how it runs the server on the Frame, pairing | | [Recovery and OS images](docs/recovery-and-images.md) | Where to download the Frame's OS, what's inside, testing without the headset | diff --git a/app/package.json b/app/package.json index bd5701a..a06fee8 100644 --- a/app/package.json +++ b/app/package.json @@ -9,8 +9,8 @@ "scripts": { "start": "env -u ELECTRON_RUN_AS_NODE electron .", "icon": "env -u ELECTRON_RUN_AS_NODE electron build/make-icon.js", - "dist": "node build/fetch-deps.js mac arm64 && electron-builder --mac --arm64 --publish never", - "dist:dir": "node build/fetch-deps.js mac arm64 && electron-builder --mac --arm64 --dir", + "dist": "sh ../mac/frame-mac-view/build.sh && node build/fetch-deps.js mac arm64 && electron-builder --mac --arm64 --publish never", + "dist:dir": "sh ../mac/frame-mac-view/build.sh && node build/fetch-deps.js mac arm64 && electron-builder --mac --arm64 --dir", "dist:linux": "node build/fetch-deps.js linux x64 arm64 && electron-builder --linux --x64 --arm64 --publish never", "dist:win": "node build/fetch-deps.js win x64 && electron-builder --win --x64 --publish never" }, @@ -105,9 +105,19 @@ "dmg", "zip" ], + "extraResources": [ + { + "from": "../mac/bin", + "to": "mac/bin", + "filter": [ + "frame-mac-view" + ] + } + ], "extendInfo": { "NSAppleEventsUsageDescription": "Frame Control opens Terminal for SSH sessions and for power actions that need the Developer Mode password.", - "NSLocalNetworkUsageDescription": "Frame Control connects to your Steam Frame over SSH on the local network." + "NSLocalNetworkUsageDescription": "Frame Control connects to your Steam Frame over SSH on the local network.", + "NSScreenCaptureUsageDescription": "Frame Control shows your Mac's windows and screens inside the Steam Frame when you ask it to." }, "artifactName": "Frame-Control-mac-${arch}.${ext}" }, diff --git a/docs/frame-control.md b/docs/frame-control.md index 299c57c..5befb8c 100644 --- a/docs/frame-control.md +++ b/docs/frame-control.md @@ -70,6 +70,10 @@ counts them while they run. Runtime picked from the program's header), listed under **Sideloaded titles** with Launch and Remove; see [sideloading.md](sideloading.md). Send typed text, or your computer's clipboard, to the Frame clipboard. +- **Mac in the headset** (macOS): show any Mac window, or a whole screen, as + its own panel in the headset. Place it with the SteamVR dashboard, click and + scroll with the laser, and type on the Mac. Streams hardware H.264 through + an SSH tunnel; see [mac-in-headset.md](mac-in-headset.md). - **Flatpaks**: install and remove them (quick picks: Moonlight, Firefox, VLC, Remmina). - **One-click tools**: SSH or SFTP in a terminal window, Steam Link, and remote diff --git a/docs/how-the-frame-works.md b/docs/how-the-frame-works.md index 74f81f1..9681da8 100644 --- a/docs/how-the-frame-works.md +++ b/docs/how-the-frame-works.md @@ -51,6 +51,7 @@ Lepton (Android 11, podman container "lepton-dev") ← its own panel, app 305600 | **T3 Code desktop runs natively.** The stock release `T3-Code-0.0.42-arm64.AppImage` in `~/Applications/T3CodeDesktop/` starts with no extra setup: glibc 2.39, `libfuse.so.2`, GTK 3, NSS and libsecret are on the image. `panel-on-frame.sh --name t3code-desktop -- '~/Applications/T3CodeDesktop/T3-Code.AppImage'` gives it its own panel (`valve.steam.desktopgame.2000281357`, `--ozone-platform=x11`). Its bundled server listens on `127.0.0.1:3773` and shows up in onboarding as the `frame` computer, with `passwordStore: gnome-libsecret`. The image has no agent CLI and no `node`. Agents run through the LAN CLIProxyAPI (`llm-proxy.lan:8317`, which resolves on the Frame). Claude Code 2.1.283 comes from `claude.ai/install.sh`, and Codex 0.157.1 from the `codex-aarch64-unknown-linux-musl` release tarball, both into `~/.local/bin`. `with-cliproxy` and a mode-600 `~/.config/cliproxyapi/secrets.env` are copied from the Mac. The wrappers `claude-cliproxy` and `codex-cliproxy` (a `-c model_provider=cliproxy`, `wire_api="responses"`, `env_key="CLIPROXY_API_KEY"`) are set as `providers.claudeAgent.binaryPath` and `providers.codex.binaryPath` in `~/.t3/userdata/settings.json`, and T3 picked that up without a restart. Through the wrappers, `claude auth status` reports `loggedIn: true` (`oauth_token`), and both CLIs answered a prompt with `kimi-k3`. `gamescopectl screenshot` captured another layer (the Lepton T3 app) rather than this panel. `DISPLAY=:0 xwd -id ` piped to `ffmpeg` captures the window itself (1920×1080). **Verified 2026-09-26**, BUILD_ID 20260922.6101926. | Running T3 Code as a host on the Frame | | Power actions need `sudo`, which asks for the Developer Mode password over SSH. | Frame Control's power buttons | | **SSH server:** OpenSSH 9.7p1. It offers `publickey,password` (keyboard-interactive is off, PAM on) and also asks `userdbctl ssh-authorized-keys` for keys. OpenSSH ≥ 8.8 rejects SHA-1 `ssh-rsa` signatures by default, so a client whose RSA support is SHA-1 only (the Swift library Citadel, for one) can't log in with the RSA key that devkit pairing installs; use ed25519 (**inferred** from OpenSSH defaults). **Verified 2026-09-27**, BUILD_ID 20260922.6101926. | [iphone.md](iphone.md), `ui/frame_connect.py` | +| **A Chromium app window on gamescope's `:0` with its own `STEAM_GAME` becomes a panel, even when started over SSH.** `chromium-xr/chrome --ozone-platform=x11 --app=URL --window-size=1280,720` got a 1920×1080 window, and tagging it produced `[Overlays] Created: valve.steam.desktopgame.` in `~/.local/share/Steam/logs/vrwebhelper_systemui.txt`. Chromium XR decoded a 1080p H.264 WebCodecs stream from the Mac at about 60 fps. XTest events sent to `:0` (libXtst through Python ctypes) did not reach the page. The Frame has `libXtst`, `xprop`, `xwininfo`, `curl` and the `C.utf8` locale. **Verified 2026-09-28**, BUILD_ID 20260925.6191901. | [mac-in-headset.md](mac-in-headset.md) | | **Tools on the image:** Python 3.12.3, `ffmpeg`, `openssl`, `curl`, `rsync`, `zip`/`unzip`, `flatpak`, `wpctl`, `podman`. **No `adb`.** `steamos` is uid 1000, in `wheel`, and sudoers has `%wheel ALL=(ALL) ALL`, so `sudo -S` takes the Developer Mode password on stdin. **Verified 2026-09-27.** | Running Frame Control's server on the Frame (`FRAME_LOCAL=1`, [iphone.md](iphone.md)) | | **Each Lepton instance is a podman container** named `lepton-steamlaunch-`, labelled with its ADB port (`podman ps --format '{{.Names}} {{.Labels.adb_port}}'`). `podman exec /system/bin/sh -c '…'` runs Android's shell inside it with no adb at all (used for `pidof` and `logcat` by the app tester). Running `wm size`/`wm density` that way is untested. **Verified 2026-09-27.** | `ui/frame_android.py`, the iPhone app's display settings | | **Asleep means off the network.** In standby the Frame stops answering on its LAN address, `frame.local` and Tailscale alike (`Host is down`, `No route to host`, timeouts), and ping fails. It was unreachable for about 2.5 hours until woken. Nothing over SSH can wake it. **Verified 2026-09-27.** | Frame Control's offline banner and retries | diff --git a/docs/mac-in-headset.md b/docs/mac-in-headset.md new file mode 100644 index 0000000..55728bc --- /dev/null +++ b/docs/mac-in-headset.md @@ -0,0 +1,204 @@ +# Mac in the headset + +Frame Control can show any Mac window, or a whole Mac screen, as its own panel +in the Steam Frame. You place each panel anywhere in the room with the SteamVR +dashboard. The laser clicks and drags, the thumbstick scrolls, and you type on +the Mac's own keyboard. Find it under **Tools → Mac in the headset** (macOS +only). + +The confidence labels are the same as in [ssh.md](ssh.md). + +## Why this design + +First-party options come first, as the repo's rule asks, with the reason +each one was or wasn't chosen. The full list for every device is in +[streaming.md](streaming.md#first-party-options-and-why-they-do-or-dont-fit). +Checked 2026-09-28. + +| Goal | First-party option | Chosen? | Why | +|---|---|---|---| +| One Mac screen in the headset | **Apple Screen Sharing** (VNC) → Remmina (Remmina 1.4.43 is already installed on this Frame) | Kept as the fallback (`panel-on-frame.sh mac-screen`) | It's the closest to first-party and needs nothing new. But VNC sends compressed tiles rather than video, so moving content is slow. It shows only whole screens | +| One Mac screen | **Steam Remote Play**, Mac as host (Valve) | No | macOS isn't a SteamVR host, and Mac-hosted Remote Play is reported broken ([Steam forum](https://steamcommunity.com/groups/homestream/discussions/1/574921459914429988/)). It streams games, not the desktop. **Not tested here**; one real try is still worth doing | +| One Mac screen | **AirPlay** (Apple) | No | Apple licenses AirPlay receivers only to TV and speaker makers, and nothing official runs on Linux. UxPlay is an unofficial receiver, and it mirrors a whole screen, not single windows | +| One Mac screen | **Sidecar / Mac Virtual Display** (Apple) | No | These work only with an iPad or Apple Vision Pro | +| **Each Mac window as its own panel** | None | – | No first-party way does this: Apple's per-app streaming is only for Vision Pro, and Valve's desktop streaming needs a Windows SteamVR host. So Frame Control does it itself | +| Mac keyboard and trackpad driving the headset | **Bluetooth HID** | No | macOS can't act as a Bluetooth keyboard or mouse. A real Bluetooth keyboard paired with the Frame still works | +| Mac keyboard and trackpad | **KDE Connect** (KDE) | No | The Frame has no `kdeconnectd` and it isn't on Flathub. Its Mac app has no keyboard or mouse sharing (**inferred**), and on Wayland it can only reach the desktop panel | +| Mac keyboard and trackpad | **xrdp** (Valve, Developer Mode) | No | It runs a separate Linux session that you view on the Mac. It isn't the headset's view, and it doesn't carry input the other way | + +What that leaves is our own stream: nothing to install on the Mac or the +Frame, and whole screens or single windows. Here the Mac's own keyboard and +trackpad need no forwarding, because the windows are still on the Mac. The +laser is the only input that has to be sent back. + +Other routes that were compared: + +| Option | One screen | Each window | Speed | Verdict | +|---|---|---|---|---| +| Sunshine → Moonlight | ✓ | – | Good | Sunshine's macOS support is still experimental ([discussion #777](https://github.com/orgs/LizardByte/discussions/777)), and it captures whole screens only | +| Virtual Desktop, Immersed | – | – | – | No Frame client as of September 2026 | +| **Frame Control's own stream** | ✓ | ✓ | Hardware H.264, sending only changed frames | **Built** | + +To type into VR surfaces other than these panels (SteamVR's dashboard, +games), the Frame supports a uinput keyboard and mouse without sudo +(verified 2026-09-27: `steamos` is in `input`, and `/dev/uinput` is +`root:input 660`). That's a separate feature, not part of this one. + +## How it works + +``` +Mac Frame +ScreenCaptureKit (one window or display) + → VideoToolbox H.264 (hardware, low-latency, + no B-frames) + → frame-mac-view, 127.0.0.1 ──ssh -R──→ 127.0.0.1:479xx + → Chromium app window per stream + (WebCodecs decode), on gamescope's + X display, tagged STEAM_GAME + → its own SteamVR panel + ← CGEvent (clicks, drags, wheel, keys) ←──── pointer, wheel and key events +``` + +- **The agent** is `mac/bin/frame-mac-view`, built from `mac/frame-mac-view` + (Swift, no dependencies; `build.sh`). Frame Control's server starts it on + first use and stops it on quit. + - It captures with ScreenCaptureKit, which sends frames only when something + changes, so idle windows cost nothing. + - It encodes in hardware with VideoToolbox's low-latency rate control (plain + real-time mode where that's unavailable). + - While anyone is watching, it keeps the Mac's display awake. A sleeping + display isn't drawn, so there would be nothing to capture. +- **The link** is an `ssh -R` tunnel on its own connection. It's encrypted and + works anywhere `ssh frame` works, Tailscale included, with no firewall + changes on the Mac. If the headset sleeps or the network drops, Frame + Control reopens the tunnel on the same port, and open viewers reconnect by + themselves. +- **Access.** + - Frame Control's own key never leaves the Mac. + - Each viewer is opened with a **single-use ticket**. It's tied to one + window or display and expires after a minute. It's spent as soon as the + viewer confirms it has received its reconnect key. Until then, a retry + gets the same key, so a connection lost at that moment doesn't strand + the viewer. Stop revokes tickets that haven't been used yet. + - After that, the viewer holds a reconnect key for that one source, in + memory only. **Stop** revokes it. + - Remaining risk: a program running as `steamos` on the Frame could read a + ticket from Chromium's command line in the first second or so and use it + first. That gets it the one source being opened, not the Mac, and the + real viewer would then fail to connect. Android apps in Lepton run in + their own podman container, so they shouldn't see the Frame's process + list (inferred, not checked). +- **The viewer** is `ui/mac-view.html`, served by the agent. It opens on the + Frame as a Chromium app window, preferring Chromium XR (`~/chromium-xr`, + built with H.264) over Flathub Chromium. + - The page puts `[fcNNNNN]` in its title. The launcher finds the window by + that tag and sets `STEAM_GAME` to a stable id per source, which gives it + its own panel (see [panels.md](panels.md)). The same Mac window gets the + same panel id each time. + - It decodes with WebCodecs. If it falls behind, it skips to the next + keyframe instead of showing old frames late. + - It falls back to JPEG stills (**Compatible** quality) where H.264 isn't + available. +- **Flow control.** When the link backs up, the agent skips capture frames + *before* encoding, so no reference frame goes missing. Once the link drains, + it sends the newest picture. +- **Input.** + - A click on a window's panel brings that Mac window to the front + (Accessibility API), then clicks at the same point. Double clicks, right + clicks, drags and the wheel work too. + - Keys typed into the panel are sent as Mac key codes. Any keys or buttons + still held down are released if the viewer loses focus or disconnects, or + when the stream stops. Characters the key + table doesn't know, such as those from other keyboard layouts, are typed + as text. + - The Mac's own keyboard and trackpad keep working as normal. Click a panel + with the laser, then type on the Mac. + +## Permissions (Mac) + +- **Screen Recording**, to see windows. Without it, the card asks for it. +- **Accessibility**, so input from the headset reaches the Mac. Without it the + stream still works, and the viewer says clicks won't go through. + +Both are granted to Frame Control. After granting, press **Refresh**, which +restarts the helper so it picks them up. The app is ad-hoc signed, so macOS +may ask again after an update. + +## Quality settings + +| Setting | Long side | fps | Codec | Use | +|---|---|---|---|---| +| Sharp | 2560 | 60 | H.264, ~0.14 bits/pixel | Text-heavy windows on a strong link | +| Balanced (default) | 1920 | 60 | H.264, ~0.1 bits/pixel | Most things | +| Light | 1280 | 30 | H.264 | Weak Wi-Fi or Tailscale off the LAN | +| Compatible | 1280 | 20 | JPEG | A Frame browser without H.264 | + +## Checked so far (2026-09-28) + +- **Mac (checked by hand, macOS 26.5.2).** + - The agent builds, and lists windows and displays. + - In a browser, the test pattern decoded at about 60 fps (H.264) and 30 fps + (JPEG, about 22 Mbps). + - A click in the viewer arrived at the same point in the source. + - `pmset -g assertions` showed the display-awake assertion only while a + stream was being watched. +- **Automated** (`tests/test_macview.py`, on CI's macOS runner): the agent + builds (a build failure fails the job). + - `/ping` and the page are open; everything else needs the key. + - Tickets work once and only for their own source, and Stop revokes + reconnect keys. + - A WebSocket frame claiming 2^63 bytes closes that socket, and the agent + keeps running. + - A stream sends an SPS-led H.264 keyframe, and sends another when asked. + - Stop ends the stream on the Mac even if the viewer ignores it. + - The test doesn't decode video, time it, or check where clicks land. +- **Linux aarch64 (verified in a stand-in, not on the Frame).** In an Arch + Linux ARM container with sshd, Xvfb as `:0` and Chromium 153: + - The real `show` path worked in 1–2.3 s: tunnel, ticket, launcher, + window found and tagged `STEAM_GAME`. + - Frame Control's key didn't appear anywhere in the stand-in's process + list. + - After the tunnel was killed, it came back on the same port within 4 s, + and the viewer reconnected by itself. + - Chromium decoded the stream in software with the GPU off. + - Stop closed the window, and Chromium exited. + - This test found and fixed a bug: in a C locale, `xwininfo` can't print a + title with non-ASCII characters, so the launcher reads `_NET_WM_NAME` + with `xprop`. +- **On the Frame (verified 2026-09-28, SteamOS build 20260925.6191901, + from the Mac, nobody wearing the headset).** Frame Control's **Show** with + the test pattern: + - The panel was ready in 1.45 s. SteamVR logged `[Overlays] Created: + valve.steam.desktopgame.2001639889` (in `vrwebhelper_systemui.txt`). + - The window was tagged `STEAM_GAME`, and gamescope sized it to 1920×1080 + although 1280×720 was asked for. + - Chromium XR decoded it live at about 60 fps: the frame counter advanced + 62 in 1.04 s. + - Over home Wi-Fi, the frames in the viewer window had been drawn on the + Mac about 11–17 ms earlier, plus `xwd`'s own time. That's measured + against the two clocks, which were 37–39 ms apart (±4 ms, measured over + one SSH session). SteamVR's compositor and the display come on top. + - Clicks and keys injected with XTest into gamescope's Xwayland didn't + reach the page. That's inconclusive, not a failure: XTest on gamescope + isn't how real input arrives. The laser should arrive as a left mouse + button and the thumbstick as a wheel, because the window has an app id + (from gamescope's source; see [panels.md](panels.md)). +- **Not yet checked:** + - Clicking, dragging and scrolling with the laser while wearing the + headset. + - Real window capture and input on a Mac with both permissions granted. + - Keys from SteamVR's on-screen keyboard. + - Whether Flathub Chromium has H.264. Chromium XR is used when it's + installed, as it is on this Frame. + - Latency with real, busy windows at Sharp. + +## Limits + +- Only windows on the Mac's current desktop (Space) are listed, and + minimised windows can't be captured. +- A window's panel shows only that window. Its menus and sheets are separate + windows on the Mac, so open them from the Mac or use **Whole screen**. +- Keys go to whichever Mac window is in front. Clicking a panel brings its + window to the front first. +- Ctrl stays Ctrl. On the Mac, copy is ⌘C, so use Meta+C on a keyboard paired + with the Frame. diff --git a/docs/open-questions.md b/docs/open-questions.md index 44583b2..8592280 100644 --- a/docs/open-questions.md +++ b/docs/open-questions.md @@ -125,6 +125,19 @@ Still open: 4, 6, 7, 11 (in-headset connect), 12–15, 16 (off-LAN and after a r - The Mac EDL flashing script in `~/Downloads/steam-frame-recovery/` hasn't been run against a Frame. +## Mac in the headset (2026-09-28) + +The test pattern streams to the Frame as its own panel at about 60 fps +(verified, build 20260925.6191901; see +[mac-in-headset.md](mac-in-headset.md#checked-so-far-2026-09-28)). Still to +check in the headset: + +- Laser clicks, drags and thumbstick scrolling in a viewer panel. +- Real window capture and input once Screen Recording and Accessibility are + granted to Frame Control. +- Keys from SteamVR's on-screen keyboard. +- Whether Flathub Chromium decodes H.264 (otherwise use Compatible). + ## Unconfirmed claims made in these docs - `/home` and `/etc` persist across Frame OS updates. This is inferred from diff --git a/docs/streaming.md b/docs/streaming.md index 8a1b448..95313bb 100644 --- a/docs/streaming.md +++ b/docs/streaming.md @@ -34,7 +34,8 @@ flat 2D desktop streaming into a window on the Frame's Linux desktop. | Option | Setup | Confidence | Verdict | |---|---|---|---| -| **macOS Screen Sharing (VNC) → Remmina on the Frame** | **Mac:** System Settings → General → Sharing → Screen Sharing on → (i) → enable "VNC viewers may control screen with password". **Frame:** `./scripts/install-apps.sh remmina` from the Mac, then open Remmina in the headset and connect to `vnc://.local` | **Inferred.** Remmina is on Flathub for **aarch64** with VNC and RDP ([Flathub](https://flathub.org/apps/org.remmina.Remmina)). The Frame desktop runs Flatpaks ([UploadVR](https://www.uploadvr.com/flatpaks-open-source-steam-frame/)). macOS VNC is built in. | **Recommended.** Nothing to install on the Mac, and it's easy to set up. Latency is fine for productivity but not for games. You'll type the Mac's hostname once in Remmina on the headset, then save the profile. To avoid even that, the script can pre-seed a Remmina profile over SSH (see below). | +| **Frame Control → Tools → Mac in the headset** | Nothing to install. Allow Screen Recording and Accessibility for Frame Control, then press **Show** next to any window or screen | **Verified** on the Mac and in an aarch64 Linux stand-in; **not yet tried in the headset** (2026-09-28) | **Recommended.** Hardware H.264 over an SSH tunnel. Each window becomes its own panel you can place anywhere. Laser clicks and scrolls, and the Mac's keyboard types. See [mac-in-headset.md](mac-in-headset.md) | +| **macOS Screen Sharing (VNC) → Remmina on the Frame** | **Mac:** System Settings → General → Sharing → Screen Sharing on → (i) → enable "VNC viewers may control screen with password". **Frame:** `./scripts/install-apps.sh remmina` from the Mac, then open Remmina in the headset and connect to `vnc://.local` | **Inferred.** Remmina is on Flathub for **aarch64** with VNC and RDP ([Flathub](https://flathub.org/apps/org.remmina.Remmina)). The Frame desktop runs Flatpaks ([UploadVR](https://www.uploadvr.com/flatpaks-open-source-steam-frame/)). macOS VNC is built in. | **Fallback.** Nothing to install on the Mac, and it's easy to set up. Latency is fine for productivity but not for games. You'll type the Mac's hostname once in Remmina on the headset, then save the profile. To avoid even that, the script can pre-seed a Remmina profile over SSH (see below). | | Sunshine (Mac) → Moonlight (Frame Flatpak) | `brew install` Sunshine on the Mac, then `./scripts/install-apps.sh moonlight` | Moonlight Flatpak supports **aarch64** ([Flathub](https://flathub.org/apps/com.moonlight_stream.Moonlight)). **Sunshine on macOS is poorly supported**: install problems on Apple Silicon/Sequoia, and no virtual gamepads ([LizardByte discussion #777](https://github.com/orgs/LizardByte/discussions/777)). | Try it if VNC is too laggy. Expect some friction. | | Steam Remote Play with the Mac as host | Steam on the Mac, Steam Link/Remote Play on the Frame | macOS-hosted Remote Play is reported broken or flaky in 2024–2026 ([Steam discussion](https://steamcommunity.com/groups/homestream/discussions/1/574921459914429988/)) | Not recommended. It's only for games, if it works at all. | | Immersed / Virtual Desktop | Vendor apps | Immersed has a Mac agent but no known Frame client. Virtual Desktop's developer said he'd "try" to port it ([NewsBreak](https://www.newsbreak.com/news/4892834783961-virtual-desktop-dev-says-he-ll-try-to-bring-the-app-to-steam-frame)). | Not available as of 2026-09-25. Check again later. | @@ -53,6 +54,21 @@ choose to save it. Remmina stores passwords encrypted with a per-install key, so the script doesn't try to write the password. (The Remmina file format is standard; the Flatpak data path is inferred.) +## First-party options, and why they do or don't fit + +Checked 2026-09-28. The first-party way is usually the best one, so these are +listed first; the sections above and below explain the alternatives. + +| Goal | First-party option | Fits? | Why, and what would make it easier | +|---|---|---|---| +| Type and point from the **iPhone** | **KDE Connect** (KDE; official [iOS app](https://apps.apple.com/app/kde-connect/id1580245991)) remote touchpad and keyboard, plus clipboard and files | **Best candidate, untested on the Frame** | The Frame doesn't have it (verified: no `kdeconnectd`), it isn't on Flathub, and the root is read-only, so it would have to run from `~` or a container. On Wayland it types through KWin, so it can only reach the desktop panel, not SteamVR or games (**inferred**). Steam Deck users report its remote input breaking after SteamOS updates ([SteamOS #1939](https://github.com/ValveSoftware/SteamOS/issues/1939)). If it works, Frame Control could install it and pair it for you. | +| Type and point from the **Mac** | KDE Connect for macOS (KDE builds) | Same as above | Its Mac app sends clipboard and files but has no keyboard/mouse sharing (**inferred**). | +| Either | **Bluetooth keyboard and mouse** paired in SteamOS (Valve) | Yes, with real hardware | Neither device can pretend to be one: iOS refuses the HID service ([Apple forums](https://developer.apple.com/forums/thread/733916)), and macOS has no built-in way. | +| Either | **xrdp** in Developer Mode (Valve) | No | Input goes into a *separate* desktop shown on the Mac, not into what you see in the headset. | +| **Mac screen** in the Frame | **Screen Sharing** (Apple's VNC server) + Remmina (already installed on this Frame, profile pre-seeded by `install-apps.sh`) | **Yes, closest to first-party** | Only the Mac side is first-party; Remmina is the client. Turn on System Settings → General → Sharing → Screen Sharing → (i) → "VNC viewers may control screen with password". Still to test in the headset (open question 11). | +| Mac screen | **Steam Remote Play** with the Mac as host (Valve) | Probably not | macOS isn't a SteamVR host, and Mac-hosted Remote Play is reported broken ([Steam forum](https://steamcommunity.com/groups/homestream/discussions/1/574921459914429988/)). One quick test is worth doing: Steam on the Mac, then Remote Play from the Frame's Steam. | +| Mac or **iPhone screen** | **AirPlay** (Apple) | Not officially | It's Apple's own mirroring for both, but Apple only licenses receivers to TV and speaker makers; nothing official runs on Linux. UxPlay (below) is the unofficial receiver. | + ## C. Show the iPhone's screen inside the Frame iOS only shares its screen two ways: **AirPlay** (Screen Mirroring in Control diff --git a/mac/frame-mac-view/Sources/Capture.swift b/mac/frame-mac-view/Sources/Capture.swift new file mode 100644 index 0000000..4967045 --- /dev/null +++ b/mac/frame-mac-view/Sources/Capture.swift @@ -0,0 +1,339 @@ +// Where pictures come from: one Mac window or one display (ScreenCaptureKit), +// or a generated test pattern that needs no permissions and shows the input +// the viewer sends, for checking the whole path without touching the Mac. +import AppKit +import CoreMedia +import CoreVideo +import Foundation +import ScreenCaptureKit + +enum Source: Equatable { + case window(CGWindowID) + case display(CGDirectDisplayID) + case test + + init?(_ s: String) { + let parts = s.split(separator: ":", maxSplits: 1).map(String.init) + switch (parts.first, parts.count > 1 ? UInt32(parts[1]) : nil) { + case ("window", let id?): self = .window(id) + case ("display", let id?): self = .display(id) + case ("test", _): self = .test + default: return nil + } + } + + var key: String { + switch self { + case .window(let id): return "window:\(id)" + case .display(let id): return "display:\(id)" + case .test: return "test" + } + } +} + +/// Size to capture at: the source's pixel size, shrunk to fit a box whose +/// long side is `maxLong` (default 1920), even numbers for the encoder. +func fitSize(width: Double, height: Double, maxLong: Int) -> (Int, Int) { + let scale = min(1, Double(maxLong) / max(width, height, 1)) + let w = max(16, Int((width * scale / 2).rounded()) * 2), h = max(16, Int((height * scale / 2).rounded()) * 2) + return (w, h) +} + +/// What CGWindowList says about a window right now (no permission needed for +/// bounds; titles need Screen Recording). +struct WindowInfo { + let id: CGWindowID + let pid: pid_t + let app: String + let title: String + let bounds: CGRect + let layer: Int + let onScreen: Bool + + static func all(onScreenOnly: Bool = true) -> [WindowInfo] { + let opts: CGWindowListOption = onScreenOnly ? [.optionOnScreenOnly, .excludeDesktopElements] : [.excludeDesktopElements] + let list = CGWindowListCopyWindowInfo(opts, kCGNullWindowID) as? [[CFString: Any]] ?? [] + return list.compactMap(WindowInfo.init) + } + + static func find(_ id: CGWindowID) -> WindowInfo? { + let list = CGWindowListCopyWindowInfo(.optionIncludingWindow, id) as? [[CFString: Any]] ?? [] + return list.compactMap(WindowInfo.init).first { $0.id == id } + } + + init?(_ d: [CFString: Any]) { + guard let id = d[kCGWindowNumber] as? CGWindowID, let pid = d[kCGWindowOwnerPID] as? pid_t, + let b = d[kCGWindowBounds] as? [String: Any], let rect = CGRect(dictionaryRepresentation: b as CFDictionary) + else { return nil } + self.id = id + self.pid = pid + app = d[kCGWindowOwnerName] as? String ?? "" + title = d[kCGWindowName] as? String ?? "" + bounds = rect + layer = d[kCGWindowLayer] as? Int ?? 0 + onScreen = d[kCGWindowIsOnscreen] as? Bool ?? false + } +} + +protocol CaptureSource: AnyObject { + var onFrame: ((CVPixelBuffer, CMTime) -> Void)? { get set } + var onEnded: ((String) -> Void)? { get set } + /// Points on the Mac's global display space that the picture covers. + var frameRect: CGRect { get } + var title: String { get } + var app: String { get } + var pid: pid_t? { get } + func start(maxLong: Int, fps: Int, completion: @escaping (String?) -> Void) + func stop() + func pointer(x: Double, y: Double, text: String?) // for the test pattern +} + +extension CaptureSource { + func pointer(x: Double, y: Double, text: String?) {} +} + +/// ScreenCaptureKit, for a window or a display. +final class SCKSource: NSObject, CaptureSource, SCStreamOutput, SCStreamDelegate { + let source: Source + var onFrame: ((CVPixelBuffer, CMTime) -> Void)? + var onEnded: ((String) -> Void)? + private(set) var frameRect = CGRect.zero + private(set) var title = "" + private(set) var app = "" + private(set) var pid: pid_t? + private var stream: SCStream? + private var config = SCStreamConfiguration() + private var maxLong = 1920 + private var scale = 2.0 + private var poll: DispatchSourceTimer? + private let queue = DispatchQueue(label: "frame-mac-view.capture", qos: .userInteractive) + /// Set by stop(), on `queue`; a start still enumerating windows checks it + /// before it starts capturing, so a viewer that left early leaves nothing on. + private var cancelled = false + /// The window or display no longer exists, so retrying can't help. + private(set) var gone = false + var onChange: (() -> Void)? // title or size changed + + init(_ source: Source) { self.source = source } + + func start(maxLong: Int, fps: Int, completion: @escaping (String?) -> Void) { + self.maxLong = maxLong + SCShareableContent.getExcludingDesktopWindows(true, onScreenWindowsOnly: false) { [self] content, error in + queue.async { self.begin(content, error, fps: fps, completion: completion) } + } + } + + private func begin(_ content: SCShareableContent?, _ error: Error?, fps: Int, completion: @escaping (String?) -> Void) { + if cancelled { return } + guard let content else { + return completion("Screen Recording isn't allowed for Frame Control (\(error?.localizedDescription ?? "no content"))") + } + let filter: SCContentFilter + switch source { + case .window(let id): + guard let w = content.windows.first(where: { $0.windowID == id }) else { + gone = true + return completion("that window has closed") + } + filter = SCContentFilter(desktopIndependentWindow: w) + title = w.title ?? "" + app = w.owningApplication?.applicationName ?? "" + pid = w.owningApplication?.processID + frameRect = w.frame + case .display(let id): + guard let d = content.displays.first(where: { $0.displayID == id }) else { + gone = true + return completion("that display isn't connected") + } + filter = SCContentFilter(display: d, excludingWindows: []) + title = displayName(id) + app = "Mac" + frameRect = CGDisplayBounds(id) + case .test: + return completion("not a ScreenCaptureKit source") + } + scale = Double(filter.pointPixelScale) + let (w, h) = fitSize(width: frameRect.width * scale, height: frameRect.height * scale, maxLong: maxLong) + config.width = w + config.height = h + config.minimumFrameInterval = CMTime(value: 1, timescale: CMTimeScale(fps)) + config.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + config.colorMatrix = CGDisplayStream.yCbCrMatrix_ITU_R_709_2 + config.colorSpaceName = CGColorSpace.sRGB + config.showsCursor = true + config.queueDepth = 5 + config.scalesToFit = true + config.preservesAspectRatio = true + config.capturesAudio = false + let stream = SCStream(filter: filter, configuration: config, delegate: self) + do { + try stream.addStreamOutput(self, type: .screen, sampleHandlerQueue: queue) + } catch { + return completion("couldn't capture: \(error.localizedDescription)") + } + self.stream = stream + stream.startCapture { error in + self.queue.async { + if self.cancelled { + stream.stopCapture { _ in } + return + } + if let error { return completion("couldn't capture: \(error.localizedDescription)") } + self.startPolling() + completion(nil) + } + } + } + + func stop() { + queue.async { + self.cancelled = true + self.poll?.cancel() + self.poll = nil + self.stream?.stopCapture { _ in } + self.stream = nil + } + } + + /// Windows move, resize, retitle and close; ScreenCaptureKit doesn't say. + private func startPolling() { + guard case .window(let id) = source else { return } + let t = DispatchSource.makeTimerSource(queue: queue) + t.schedule(deadline: .now() + 1, repeating: 1) + t.setEventHandler { [weak self] in + guard let self else { return } + guard let info = WindowInfo.find(id) else { + self.stop() + self.onEnded?("the window closed") + return + } + var changed = false + if !info.title.isEmpty, info.title != self.title { self.title = info.title; changed = true } + let old = self.frameRect + self.frameRect = info.bounds + if abs(old.width - info.bounds.width) > 1 || abs(old.height - info.bounds.height) > 1 { + let (w, h) = fitSize(width: info.bounds.width * self.scale, height: info.bounds.height * self.scale, maxLong: self.maxLong) + self.config.width = w + self.config.height = h + self.stream?.updateConfiguration(self.config) { _ in } + changed = true + } + if changed { self.onChange?() } + } + t.resume() + poll = t + } + + func stream(_ stream: SCStream, didOutputSampleBuffer sample: CMSampleBuffer, of type: SCStreamOutputType) { + guard type == .screen, sample.isValid, let pb = CMSampleBufferGetImageBuffer(sample) else { return } + // Only complete frames carry new pixels; idle and blank ones don't. + if let atts = CMSampleBufferGetSampleAttachmentsArray(sample, createIfNecessary: false) as? [[SCStreamFrameInfo: Any]], + let raw = atts.first?[.status] as? Int, let status = SCFrameStatus(rawValue: raw), status != .complete { + return + } + onFrame?(pb, CMSampleBufferGetPresentationTimeStamp(sample)) + } + + func stream(_ stream: SCStream, didStopWithError error: Error) { + onEnded?("capture stopped: \(error.localizedDescription)") + } +} + +func displayName(_ id: CGDirectDisplayID) -> String { + for screen in NSScreen.screens { + if (screen.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? NSNumber)?.uint32Value == id { + return screen.localizedName + } + } + return "Display \(id)" +} + +/// A moving test card: bars, a clock and a frame counter, plus a dot where the +/// viewer's pointer is and the last key it sent, so input can be checked too. +final class TestSource: CaptureSource { + var onFrame: ((CVPixelBuffer, CMTime) -> Void)? + var onEnded: ((String) -> Void)? + let frameRect = CGRect(x: 0, y: 0, width: 1280, height: 720) + let title = "Test pattern" + let app = "Frame Control" + let pid: pid_t? = nil + private var timer: DispatchSourceTimer? + private var pool: CVPixelBufferPool? + private var n = 0 + private var dot: (Double, Double)? + private var lastText = "Point or type in the headset" + private let queue = DispatchQueue(label: "frame-mac-view.test") + private var size = (1280, 720) + + func start(maxLong: Int, fps: Int, completion: @escaping (String?) -> Void) { + size = fitSize(width: 1280, height: 720, maxLong: maxLong) + let attrs: [CFString: Any] = [kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_32BGRA, + kCVPixelBufferWidthKey: size.0, kCVPixelBufferHeightKey: size.1, + kCVPixelBufferIOSurfacePropertiesKey: [:] as CFDictionary] + CVPixelBufferPoolCreate(nil, nil, attrs as CFDictionary, &pool) + let t = DispatchSource.makeTimerSource(queue: queue) + t.schedule(deadline: .now(), repeating: 1.0 / Double(fps)) + t.setEventHandler { [weak self] in self?.draw() } + t.resume() + timer = t + completion(nil) + } + + func stop() { + timer?.cancel() + timer = nil + } + + func pointer(x: Double, y: Double, text: String?) { + queue.async { + if x >= 0 { self.dot = (x, y) } + if let text { self.lastText = text } + } + } + + private func draw() { + guard let pool else { return } + var out: CVPixelBuffer? + CVPixelBufferPoolCreatePixelBuffer(nil, pool, &out) + guard let pb = out else { return } + CVPixelBufferLockBaseAddress(pb, []) + defer { CVPixelBufferUnlockBaseAddress(pb, []) } + let (w, h) = size + guard let ctx = CGContext(data: CVPixelBufferGetBaseAddress(pb), width: w, height: h, bitsPerComponent: 8, + bytesPerRow: CVPixelBufferGetBytesPerRow(pb), space: CGColorSpace(name: CGColorSpace.sRGB)!, + bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue) + else { return } + let colors: [(CGFloat, CGFloat, CGFloat)] = [(0.75, 0.75, 0.75), (0.75, 0.75, 0), (0, 0.75, 0.75), (0, 0.75, 0), + (0.75, 0, 0.75), (0.75, 0, 0), (0, 0, 0.75)] + let bw = CGFloat(w) / CGFloat(colors.count) + for (i, c) in colors.enumerated() { + ctx.setFillColor(red: c.0, green: c.1, blue: c.2, alpha: 1) + ctx.fill(CGRect(x: CGFloat(i) * bw, y: CGFloat(h) * 0.35, width: bw + 1, height: CGFloat(h) * 0.65)) + } + ctx.setFillColor(red: 0.08, green: 0.09, blue: 0.11, alpha: 1) + ctx.fill(CGRect(x: 0, y: 0, width: w, height: Int(Double(h) * 0.35))) + // A bar sweeping once a second shows motion and dropped frames. + let x = CGFloat(n % 60) / 60 * CGFloat(w) + ctx.setFillColor(red: 1, green: 1, blue: 1, alpha: 1) + ctx.fill(CGRect(x: x, y: CGFloat(h) * 0.35, width: max(4, CGFloat(w) / 120), height: CGFloat(h) * 0.65)) + let f = DateFormatter() + f.dateFormat = "HH:mm:ss.SSS" + let label = "Frame Control test pattern \(f.string(from: Date())) frame \(n)\n\(lastText)" + let text = NSAttributedString(string: label, attributes: [ + .font: NSFont.monospacedSystemFont(ofSize: CGFloat(h) / 24, weight: .medium), + .foregroundColor: NSColor.white, + ]) + let ns = NSGraphicsContext(cgContext: ctx, flipped: false) + NSGraphicsContext.saveGraphicsState() + NSGraphicsContext.current = ns + text.draw(at: CGPoint(x: CGFloat(w) * 0.03, y: CGFloat(h) * 0.08)) + NSGraphicsContext.restoreGraphicsState() + if let (px, py) = dot { + let r = CGFloat(h) / 40 + ctx.setFillColor(red: 1, green: 0.2, blue: 0.2, alpha: 1) + ctx.fillEllipse(in: CGRect(x: CGFloat(px) * CGFloat(w) - r, y: (1 - CGFloat(py)) * CGFloat(h) - r, width: 2 * r, height: 2 * r)) + } + n += 1 + onFrame?(pb, CMClockGetTime(CMClockGetHostTimeClock())) + } +} diff --git a/mac/frame-mac-view/Sources/Encoder.swift b/mac/frame-mac-view/Sources/Encoder.swift new file mode 100644 index 0000000..114f403 --- /dev/null +++ b/mac/frame-mac-view/Sources/Encoder.swift @@ -0,0 +1,172 @@ +// VideoToolbox encoding: H.264 for the normal path (hardware, low-latency rate +// control, no B-frames, Annex B output that WebCodecs takes without a +// description), or JPEG stills for viewers that can't decode H.264. +import CoreMedia +import Foundation +import VideoToolbox + +enum Codec: String { + case h264, jpeg +} + +final class Encoder { + let codec: Codec + let fps: Int + let bitsPerPixel: Double + private(set) var width = 0 + private(set) var height = 0 + private var session: VTCompressionSession? + private var forceKey = true + private var lastPts = CMTime.invalid + /// Called on VideoToolbox's thread with one access unit (or JPEG) per frame. + var onFrame: ((Data, Bool, CMTime) -> Void)? + var onError: ((String) -> Void)? + /// Called once per frame handed to VideoToolbox, however it went. + var onDone: (() -> Void)? + + init(codec: Codec, fps: Int, bitsPerPixel: Double) { + self.codec = codec + self.fps = fps + self.bitsPerPixel = bitsPerPixel + } + + deinit { invalidate() } + + func requestKeyFrame() { forceKey = true } + + func invalidate() { + if let s = session { + VTCompressionSessionCompleteFrames(s, untilPresentationTimeStamp: .invalid) + VTCompressionSessionInvalidate(s) + } + session = nil + } + + private func makeSession(width w: Int, height h: Int) -> Bool { + invalidate() + var spec: [CFString: Any] = [:] + if codec == .h264 { spec[kVTVideoEncoderSpecification_EnableLowLatencyRateControl] = true } + var s: VTCompressionSession? + let type = codec == .h264 ? kCMVideoCodecType_H264 : kCMVideoCodecType_JPEG + func create(_ spec: [CFString: Any]) -> OSStatus { + VTCompressionSessionCreate(allocator: nil, width: Int32(w), height: Int32(h), codecType: type, + encoderSpecification: spec as CFDictionary, imageBufferAttributes: nil, + compressedDataAllocator: nil, outputCallback: nil, refcon: nil, + compressionSessionOut: &s) + } + var err = create(spec) + // Low-latency rate control needs Apple's hardware encoder; without it + // (some VMs), plain real-time encoding still works. + if err != noErr, !spec.isEmpty { err = create([:]) } + guard err == noErr, let s else { + onError?("couldn't start the \(codec.rawValue) encoder (VideoToolbox \(err))") + return false + } + func set(_ key: CFString, _ value: Any) { VTSessionSetProperty(s, key: key, value: value as CFTypeRef) } + set(kVTCompressionPropertyKey_RealTime, true) + set(kVTCompressionPropertyKey_ColorPrimaries, kCVImageBufferColorPrimaries_ITU_R_709_2) + set(kVTCompressionPropertyKey_TransferFunction, kCVImageBufferTransferFunction_ITU_R_709_2) + set(kVTCompressionPropertyKey_YCbCrMatrix, kCVImageBufferYCbCrMatrix_ITU_R_709_2) + if codec == .h264 { + let bps = min(max(Double(w * h * fps) * bitsPerPixel, 2_000_000), 60_000_000) + set(kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_ConstrainedHigh_AutoLevel) + set(kVTCompressionPropertyKey_AllowFrameReordering, false) + set(kVTCompressionPropertyKey_AverageBitRate, Int(bps)) + set(kVTCompressionPropertyKey_ExpectedFrameRate, fps) + // A keyframe every 10 s at most, so a viewer that lost one recovers + // even if it never asks. Viewers ask for one when they start. + set(kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, 10) + } else { + set(kVTCompressionPropertyKey_Quality, 0.8) + } + VTCompressionSessionPrepareToEncodeFrames(s) + session = s + lastPts = .invalid + width = w + height = h + forceKey = true + return true + } + + /// False if the frame never reached VideoToolbox (then onDone won't come). + @discardableResult + func encode(_ pb: CVPixelBuffer, pts given: CMTime) -> Bool { + // VideoToolbox needs strictly increasing timestamps; a resent picture + // stamped "now" can be followed by a capture stamped a moment earlier. + var pts = given + if lastPts.isValid, CMTimeCompare(pts, lastPts) <= 0 { pts = CMTimeAdd(lastPts, CMTime(value: 1, timescale: 1_000_000)) } + let w = CVPixelBufferGetWidth(pb), h = CVPixelBufferGetHeight(pb) + if session == nil || w != width || h != height { + guard makeSession(width: w, height: h) else { return false } + } + guard let s = session else { return false } + var props: CFDictionary? + if forceKey { + props = [kVTEncodeFrameOptionKey_ForceKeyFrame: true] as CFDictionary + forceKey = false + } + let codec = self.codec + lastPts = pts + let status = VTCompressionSessionEncodeFrame(s, imageBuffer: pb, presentationTimeStamp: pts, duration: .invalid, + frameProperties: props, infoFlagsOut: nil) { [weak self] status, _, sample in + guard let self else { return } + defer { self.onDone?() } + guard status == noErr, let sample else { return } + if codec == .jpeg { + if let data = Self.bytes(sample) { self.onFrame?(data, true, pts) } + } else if let (data, key) = Self.annexB(sample) { + self.onFrame?(data, key, pts) + } + } + if status != noErr { forceKey = true } + return status == noErr + } + + private static func bytes(_ sample: CMSampleBuffer) -> Data? { + guard let block = CMSampleBufferGetDataBuffer(sample) else { return nil } + var length = 0 + var ptr: UnsafeMutablePointer? + guard CMBlockBufferGetDataPointer(block, atOffset: 0, lengthAtOffsetOut: nil, totalLengthOut: &length, + dataPointerOut: &ptr) == noErr, let ptr else { return nil } + return Data(bytes: ptr, count: length) + } + + /// AVCC sample -> Annex B access unit, with SPS and PPS before keyframes. + static func annexB(_ sample: CMSampleBuffer) -> (Data, Bool)? { + guard let avcc = bytes(sample) else { return nil } + var key = true + if let atts = CMSampleBufferGetSampleAttachmentsArray(sample, createIfNecessary: false) as? [[CFString: Any]], + let first = atts.first, first[kCMSampleAttachmentKey_NotSync] as? Bool == true { + key = false + } + let start: [UInt8] = [0, 0, 0, 1] + var out = Data() + if key, let fmt = CMSampleBufferGetFormatDescription(sample) { + var count = 0 + CMVideoFormatDescriptionGetH264ParameterSetAtIndex(fmt, parameterSetIndex: 0, parameterSetPointerOut: nil, + parameterSetSizeOut: nil, parameterSetCountOut: &count, + nalUnitHeaderLengthOut: nil) + for i in 0..? + var n = 0 + if CMVideoFormatDescriptionGetH264ParameterSetAtIndex(fmt, parameterSetIndex: i, parameterSetPointerOut: &p, + parameterSetSizeOut: &n, parameterSetCountOut: nil, + nalUnitHeaderLengthOut: nil) == noErr, let p { + out.append(contentsOf: start) + out.append(p, count: n) + } + } + } + let bytes = [UInt8](avcc) + var i = 0 + while i + 4 <= bytes.count { + let n = Int(bytes[i]) << 24 | Int(bytes[i + 1]) << 16 | Int(bytes[i + 2]) << 8 | Int(bytes[i + 3]) + i += 4 + guard n > 0, i + n <= bytes.count else { break } + out.append(contentsOf: start) + out.append(contentsOf: bytes[i..<(i + n)]) + i += n + } + return (out, key) + } +} diff --git a/mac/frame-mac-view/Sources/Input.swift b/mac/frame-mac-view/Sources/Input.swift new file mode 100644 index 0000000..0f51efc --- /dev/null +++ b/mac/frame-mac-view/Sources/Input.swift @@ -0,0 +1,168 @@ +// Turns the viewer's pointer and key events into real Mac input. Needs the +// Accessibility permission ("control your computer"); without it macOS drops +// the events silently, so the agent reports the permission to the viewer. +import AppKit +import ApplicationServices +import Foundation + +@_silgen_name("_AXUIElementGetWindow") +private func _AXUIElementGetWindow(_ element: AXUIElement, _ id: UnsafeMutablePointer) -> AXError + +enum Input { + static let source = CGEventSource(stateID: .hidSystemState) + /// What each viewer (session id) is holding down, so one panel closing + /// lets go of its own keys and buttons and nobody else's. Main thread. + private static var buttonsHeld: [Int: Set] = [:] + private static var keysHeld: [Int: Set] = [:] + + private static var lastDown: (time: TimeInterval, point: CGPoint, button: Int, count: Int)? + + static var allowed: Bool { AXIsProcessTrusted() } + + /// Brings a window to the front so a click lands on it, not on whatever + /// covers it, and typing goes to it. + static func focus(window id: CGWindowID, pid: pid_t) { + if frontWindow() == id { return } + let app = AXUIElementCreateApplication(pid) + var value: CFTypeRef? + if AXUIElementCopyAttributeValue(app, kAXWindowsAttribute as CFString, &value) == .success, + let windows = value as? [AXUIElement] { + for w in windows { + var wid: CGWindowID = 0 + if _AXUIElementGetWindow(w, &wid) == .success, wid == id { + AXUIElementPerformAction(w, kAXRaiseAction as CFString) + AXUIElementSetAttributeValue(w, kAXMainAttribute as CFString, kCFBooleanTrue) + break + } + } + } + AXUIElementSetAttributeValue(app, kAXFrontmostAttribute as CFString, kCFBooleanTrue) + NSRunningApplication(processIdentifier: pid)?.activate() + } + + static func frontWindow() -> CGWindowID? { + WindowInfo.all().first { $0.layer == 0 }?.id + } + + /// `button` uses the browser's numbering: 0 left, 1 middle, 2 right. + static func mouse(_ kind: String, button: Int, at p: CGPoint, owner: Int) { + let b: CGMouseButton = button == 2 ? .right : button == 1 ? .center : .left + let type: CGEventType + switch kind { + case "down": + type = b == .left ? .leftMouseDown : b == .right ? .rightMouseDown : .otherMouseDown + buttonsHeld[owner, default: []].insert(button) + case "up": + type = b == .left ? .leftMouseUp : b == .right ? .rightMouseUp : .otherMouseUp + buttonsHeld[owner]?.remove(button) + // Another viewer still holding it keeps it down. + if buttonsHeld.values.contains(where: { $0.contains(button) }) { return } + default: // a drag is whatever this viewer is holding + let mine = buttonsHeld[owner] ?? [] + if mine.contains(0) { type = .leftMouseDragged } + else if mine.contains(2) { type = .rightMouseDragged } + else if mine.contains(1) { type = .otherMouseDragged } + else { type = .mouseMoved } + } + let mine = buttonsHeld[owner] ?? [] + let held: CGMouseButton = mine.contains(0) ? .left : mine.contains(2) ? .right : mine.contains(1) ? .center : .left + guard let e = CGEvent(mouseEventSource: source, mouseType: type, mouseCursorPosition: p, + mouseButton: kind == "move" ? held : b) else { return } + if kind == "down" || kind == "up" { + let now = ProcessInfo.processInfo.systemUptime + var count = 1 + if kind == "down" { + if let l = lastDown, l.button == button, now - l.time < NSEvent.doubleClickInterval, + abs(l.point.x - p.x) < 5, abs(l.point.y - p.y) < 5 { count = l.count + 1 } + lastDown = (now, p, button, count) + } else if let l = lastDown, l.button == button { + count = l.count + } + e.setIntegerValueField(.mouseEventClickState, value: Int64(count)) + } + e.post(tap: .cghidEventTap) + } + + static func scroll(dx: Double, dy: Double, at p: CGPoint, owner: Int) { + mouse("move", button: 0, at: p, owner: owner) + // Browsers report pixels with +y meaning "scroll down"; macOS's +y is up. + guard let e = CGEvent(scrollWheelEvent2Source: source, units: .pixel, wheelCount: 2, + wheel1: Int32(-dy.rounded()), wheel2: Int32(-dx.rounded()), wheel3: 0) else { return } + e.location = p + e.post(tap: .cghidEventTap) + } + + /// Lets go of every button and key this viewer is holding down, so a + /// dropped connection can't leave, say, Shift or ⌘ stuck on. + static func releaseAll(owner: Int) { + let p = CGEvent(source: nil)?.location ?? .zero + let buttons = buttonsHeld.removeValue(forKey: owner) ?? [] + let keys = keysHeld.removeValue(forKey: owner) ?? [] + // Only what no other viewer is still holding. + for b in buttons where !buttonsHeld.values.contains(where: { $0.contains(b) }) { + let type: CGEventType = b == 2 ? .rightMouseUp : b == 1 ? .otherMouseUp : .leftMouseUp + CGEvent(mouseEventSource: source, mouseType: type, mouseCursorPosition: p, + mouseButton: b == 2 ? .right : b == 1 ? .center : .left)?.post(tap: .cghidEventTap) + } + for k in keys where !keysHeld.values.contains(where: { $0.contains(k) }) { + CGEvent(keyboardEventSource: source, virtualKey: k, keyDown: false)?.post(tap: .cghidEventTap) + } + } + + static func key(code: String, key: String, down: Bool, mods: [String], owner: Int) { + var flags = CGEventFlags() + if mods.contains("shift") { flags.insert(.maskShift) } + if mods.contains("ctrl") { flags.insert(.maskControl) } + if mods.contains("alt") { flags.insert(.maskAlternate) } + if mods.contains("meta") { flags.insert(.maskCommand) } + if let vk = keyCodes[code] { + guard let e = CGEvent(keyboardEventSource: source, virtualKey: vk, keyDown: down) else { return } + if down { + keysHeld[owner, default: []].insert(vk) + } else { + keysHeld[owner]?.remove(vk) + if keysHeld.values.contains(where: { $0.contains(vk) }) { return } // still held elsewhere + } + e.flags = flags + e.post(tap: .cghidEventTap) + } else if down, !key.isEmpty, key.count <= 4, key.unicodeScalars.allSatisfy({ $0.value >= 0x20 }) { + // A key the table doesn't know (a non-US layout, the headset's + // on-screen keyboard): type its character instead. + text(key) + } + } + + static func text(_ s: String) { + let units = Array(s.utf16) + for chunk in stride(from: 0, to: units.count, by: 16) { + let part = Array(units[chunk.. macOS virtual key code (ANSI positions). + static let keyCodes: [String: CGKeyCode] = [ + "KeyA": 0x00, "KeyS": 0x01, "KeyD": 0x02, "KeyF": 0x03, "KeyH": 0x04, "KeyG": 0x05, "KeyZ": 0x06, "KeyX": 0x07, + "KeyC": 0x08, "KeyV": 0x09, "IntlBackslash": 0x0A, "KeyB": 0x0B, "KeyQ": 0x0C, "KeyW": 0x0D, "KeyE": 0x0E, + "KeyR": 0x0F, "KeyY": 0x10, "KeyT": 0x11, "Digit1": 0x12, "Digit2": 0x13, "Digit3": 0x14, "Digit4": 0x15, + "Digit6": 0x16, "Digit5": 0x17, "Equal": 0x18, "Digit9": 0x19, "Digit7": 0x1A, "Minus": 0x1B, "Digit8": 0x1C, + "Digit0": 0x1D, "BracketRight": 0x1E, "KeyO": 0x1F, "KeyU": 0x20, "BracketLeft": 0x21, "KeyI": 0x22, + "KeyP": 0x23, "Enter": 0x24, "KeyL": 0x25, "KeyJ": 0x26, "Quote": 0x27, "KeyK": 0x28, "Semicolon": 0x29, + "Backslash": 0x2A, "Comma": 0x2B, "Slash": 0x2C, "KeyN": 0x2D, "KeyM": 0x2E, "Period": 0x2F, "Tab": 0x30, + "Space": 0x31, "Backquote": 0x32, "Backspace": 0x33, "Escape": 0x35, "MetaRight": 0x36, "MetaLeft": 0x37, + "ShiftLeft": 0x38, "CapsLock": 0x39, "AltLeft": 0x3A, "ControlLeft": 0x3B, "ShiftRight": 0x3C, + "AltRight": 0x3D, "ControlRight": 0x3E, "F17": 0x40, "NumpadDecimal": 0x41, "NumpadMultiply": 0x43, + "NumpadAdd": 0x45, "NumLock": 0x47, "NumpadDivide": 0x4B, "NumpadEnter": 0x4C, "NumpadSubtract": 0x4E, + "F18": 0x4F, "F19": 0x50, "NumpadEqual": 0x51, "Numpad0": 0x52, "Numpad1": 0x53, "Numpad2": 0x54, + "Numpad3": 0x55, "Numpad4": 0x56, "Numpad5": 0x57, "Numpad6": 0x58, "Numpad7": 0x59, "F20": 0x5A, + "Numpad8": 0x5B, "Numpad9": 0x5C, "F5": 0x60, "F6": 0x61, "F7": 0x62, "F3": 0x63, "F8": 0x64, "F9": 0x65, + "F11": 0x67, "F13": 0x69, "F16": 0x6A, "F14": 0x6B, "F10": 0x6D, "ContextMenu": 0x6E, "F12": 0x6F, + "F15": 0x71, "Insert": 0x72, "Help": 0x72, "Home": 0x73, "PageUp": 0x74, "Delete": 0x75, "F4": 0x76, + "End": 0x77, "F2": 0x78, "PageDown": 0x79, "F1": 0x7A, "ArrowLeft": 0x7B, "ArrowRight": 0x7C, + "ArrowDown": 0x7D, "ArrowUp": 0x7E, "OSLeft": 0x37, "OSRight": 0x36, + ] +} diff --git a/mac/frame-mac-view/Sources/Server.swift b/mac/frame-mac-view/Sources/Server.swift new file mode 100644 index 0000000..83db9e3 --- /dev/null +++ b/mac/frame-mac-view/Sources/Server.swift @@ -0,0 +1,267 @@ +// A small HTTP/1.1 + WebSocket server on Network.framework, loopback only. +// Enough for the agent's JSON endpoints, the viewer page and one WebSocket per +// stream; not a general web server. +import CryptoKit +import Foundation +import Network + +struct Request { + let method: String + let path: String + let query: [String: String] + let headers: [String: String] // lower-cased names +} + +final class Server { + let queue = DispatchQueue(label: "frame-mac-view.server") + private let listener: NWListener + private let handle: (Request, HTTPConnection) -> Void + + init(port: UInt16, handle: @escaping (Request, HTTPConnection) -> Void) throws { + let tcp = NWProtocolTCP.Options() + tcp.noDelay = true + let params = NWParameters(tls: nil, tcp: tcp) + // Port 0 lets the system pick; `port` then says which. + params.requiredLocalEndpoint = .hostPort(host: "127.0.0.1", port: NWEndpoint.Port(rawValue: port) ?? .any) + params.allowLocalEndpointReuse = true + listener = try NWListener(using: params) + self.handle = handle + } + + var port: UInt16? { listener.port?.rawValue } + + func start(ready: @escaping (Error?) -> Void) { + listener.stateUpdateHandler = { state in + switch state { + case .ready: ready(nil) + case .failed(let e): ready(e) + default: break + } + } + listener.newConnectionHandler = { [weak self] conn in + guard let self else { return } + HTTPConnection(conn, queue: self.queue, handle: self.handle).start() + } + listener.start(queue: queue) + } +} + +final class HTTPConnection { + let conn: NWConnection + let queue: DispatchQueue + private let handle: (Request, HTTPConnection) -> Void + private var buffer = Data() + private var retained: HTTPConnection? // alive until the response is sent + + init(_ conn: NWConnection, queue: DispatchQueue, handle: @escaping (Request, HTTPConnection) -> Void) { + self.conn = conn + self.queue = queue + self.handle = handle + } + + func start() { + retained = self + conn.start(queue: queue) + readHead() + } + + private func readHead() { + conn.receive(minimumIncompleteLength: 1, maximumLength: 16384) { [self] data, _, done, error in + if let data { buffer.append(data) } + if let end = buffer.range(of: Data("\r\n\r\n".utf8)) { + guard let req = Self.parse(buffer[.. 16384 { + close() + } else { + readHead() + } + } + } + + static func parse(_ head: Data) -> Request? { + guard let text = String(data: head, encoding: .utf8) else { return nil } + let lines = text.components(separatedBy: "\r\n") + let parts = lines[0].split(separator: " ") + guard parts.count >= 2, let url = URLComponents(string: String(parts[1])) else { return nil } + var headers: [String: String] = [:] + for line in lines.dropFirst() { + guard let colon = line.firstIndex(of: ":") else { continue } + headers[line[.. WebSocket? { + guard req.headers["upgrade"]?.lowercased() == "websocket", let key = req.headers["sec-websocket-key"] else { + respond(400, text: "expected a WebSocket upgrade") + return nil + } + let accept = Data(Insecure.SHA1.hash(data: Data((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").utf8))).base64EncodedString() + let head = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: \(accept)\r\n\r\n" + conn.send(content: Data(head.utf8), completion: .contentProcessed { _ in }) + let ws = WebSocket(conn, queue: queue) + retained = nil + return ws + } +} + +/// Server side of RFC 6455. Sends are counted until the network stack has +/// taken them, so the stream can skip frames instead of queueing seconds of +/// video on a slow link. +final class WebSocket { + static let maxMessage: UInt64 = 1 << 20 + let conn: NWConnection + let queue: DispatchQueue + var onText: ((String) -> Void)? + var onClose: (() -> Void)? + private var buffer = Data() + private var fragments = Data() + private var fragmentOpcode: UInt8 = 0 + private var closed = false + private var retained: WebSocket? + private let lock = NSLock() + private var _pending = 0 + var onDrain: (() -> Void)? + + /// Bytes handed to the connection that it hasn't sent yet. Any thread. + var pendingBytes: Int { lock.lock(); defer { lock.unlock() }; return _pending } + + init(_ conn: NWConnection, queue: DispatchQueue) { + self.conn = conn + self.queue = queue + } + + func start() { + retained = self + read() + } + + func sendText(_ s: String) { send(opcode: 1, Data(s.utf8)) } + func sendJSON(_ obj: Any) { + if let d = try? JSONSerialization.data(withJSONObject: obj), let s = String(data: d, encoding: .utf8) { sendText(s) } + } + func sendBinary(_ d: Data) { send(opcode: 2, d) } + + func send(opcode: UInt8, _ payload: Data) { + var frame = Data([0x80 | opcode]) + let n = payload.count + if n < 126 { + frame.append(UInt8(n)) + } else if n < 65536 { + frame.append(126) + frame.append(contentsOf: [UInt8(n >> 8), UInt8(n & 0xff)]) + } else { + frame.append(127) + for shift in stride(from: 56, through: 0, by: -8) { frame.append(UInt8((UInt64(n) >> UInt64(shift)) & 0xff)) } + } + frame.append(payload) + let size = frame.count + lock.lock(); _pending += size; lock.unlock() + conn.send(content: frame, completion: .contentProcessed { [weak self] _ in + guard let self else { return } + self.lock.lock(); self._pending -= size; self.lock.unlock() + self.onDrain?() + }) + } + + func close() { + guard !closed else { return } + closed = true + send(opcode: 8, Data([0x03, 0xe8])) // 1000, normal closure + conn.send(content: nil, isComplete: true, completion: .contentProcessed { [weak self] _ in self?.conn.cancel() }) + finish() + } + + private func finish() { + let cb = onClose + onClose = nil + onText = nil + onDrain = nil + cb?() + retained = nil + } + + private func read() { + conn.receive(minimumIncompleteLength: 1, maximumLength: 65536) { [weak self] data, _, done, error in + guard let self, !self.closed else { return } + if let data { self.buffer.append(data) } + self.parse() + if error != nil || done { + self.closed = true + self.conn.cancel() + self.finish() + } else if !self.closed { + self.read() + } + } + } + + private func parse() { + while buffer.count >= 2 { + let b = [UInt8](buffer.prefix(14)) + let fin = b[0] & 0x80 != 0, opcode = b[0] & 0x0f, masked = b[1] & 0x80 != 0 + // Lengths are unsigned and clients only send small control + // messages, so anything big (or a 64-bit length with the top bit + // set) is refused before it's turned into an Int. + var len64 = UInt64(b[1] & 0x7f), off = 2 + if len64 == 126 { + guard b.count >= 4 else { return } + len64 = UInt64(b[2]) << 8 | UInt64(b[3]); off = 4 + } else if len64 == 127 { + guard b.count >= 10 else { return } + len64 = 0 + for i in 2..<10 { len64 = len64 << 8 | UInt64(b[i]) } + off = 10 + } + guard len64 <= WebSocket.maxMessage else { return close() } + let len = Int(len64) + let maskOff = off + if masked { off += 4 } + guard buffer.count >= off + len else { return } + let start = buffer.startIndex + var payload = Data(buffer[(start + off)..<(start + off + len)]) + if masked { + let mask = [UInt8](buffer[(start + maskOff)..<(start + maskOff + 4)]) + payload.withUnsafeMutableBytes { p in + for i in 0.., display: or test. +import AppKit +import ApplicationServices +import Foundation +import IOKit.pwr_mgt +import ScreenCaptureKit +import Security + +let version = "1" + +func windowsJSON() -> [[String: Any]] { + let me = ProcessInfo.processInfo.processIdentifier + let skip: Set = ["Window Server", "Dock", "Control Centre", "Control Center", "Notification Centre", + "Notification Center", "Spotlight", "SystemUIServer", "Wallpaper", "WindowManager"] + return WindowInfo.all().filter { + $0.layer == 0 && $0.pid != me && !skip.contains($0.app) && $0.bounds.width >= 120 && $0.bounds.height >= 80 + }.map { + ["id": $0.id, "src": "window:\($0.id)", "pid": $0.pid, "app": $0.app, "title": $0.title, + "w": Int($0.bounds.width), "h": Int($0.bounds.height)] + } +} + +func displaysJSON() -> [[String: Any]] { + var ids = [CGDirectDisplayID](repeating: 0, count: 16) + var n: UInt32 = 0 + // Online, not active: a display that's asleep is still one you can stream. + CGGetOnlineDisplayList(16, &ids, &n) + return ids.prefix(Int(n)).filter { CGDisplayMirrorsDisplay($0) == kCGNullDirectDisplay }.map { id in + let b = CGDisplayBounds(id) + return ["id": id, "src": "display:\(id)", "name": displayName(id), "w": Int(b.width), "h": Int(b.height), + "main": CGDisplayIsMain(id) != 0] + } +} + +func permissionsJSON() -> [String: Any] { + ["screen": CGPreflightScreenCaptureAccess(), "accessibility": AXIsProcessTrusted()] +} + +func printJSON(_ obj: Any) { + let d = (try? JSONSerialization.data(withJSONObject: obj, options: [.sortedKeys, .prettyPrinted])) ?? Data() + FileHandle.standardOutput.write(d + Data("\n".utf8)) +} + +/// One viewer watching one source. +final class Session { + let id: Int + let source: Source + let capture: CaptureSource + let encoder: Encoder + let ws: WebSocket + let codec: Codec + let reconnectKey: String + private let lock = NSLock() + private var last: (CVPixelBuffer, CMTime)? + private var skipped = false + private var stopped = false + private var inFlight = 0 + /// Frames already queued for the network; beyond this, or with two frames + /// already in the encoder, new frames are skipped (before encoding, so no + /// reference frame goes missing) and the newest picture is sent once + /// things catch up. Only that newest picture is kept meanwhile. + let maxPending: Int + static let maxInFlight = 2 + var onEnd: ((Session) -> Void)? + var onAck: (() -> Void)? + var onFinished: ((String) -> Void)? + private let encodeQueue = DispatchQueue(label: "frame-mac-view.encode", qos: .userInteractive) + + init(id: Int, source: Source, capture: CaptureSource, ws: WebSocket, codec: Codec, fps: Int, bitsPerPixel: Double, + reconnectKey: String) { + self.id = id + self.source = source + self.capture = capture + self.ws = ws + self.codec = codec + self.reconnectKey = reconnectKey + encoder = Encoder(codec: codec, fps: fps, bitsPerPixel: bitsPerPixel) + maxPending = codec == .jpeg ? 3 << 20 : 1 << 20 + } + + /// Encodes `pb` unless the link or the encoder is busy, in which case it + /// becomes the picture to send next. + private func offer(_ pb: CVPixelBuffer, pts: CMTime) { + lock.lock() + last = (pb, pts) + let busy = stopped || ws.pendingBytes > maxPending || inFlight >= Session.maxInFlight + if busy { skipped = true } else { inFlight += 1 } + lock.unlock() + if !busy { submit(pb, pts: pts) } + } + + private func submit(_ pb: CVPixelBuffer, pts: CMTime) { + encodeQueue.async { + if !self.encoder.encode(pb, pts: pts) { self.finished() } + } + } + + /// An encode finished (or failed): send the newest skipped picture if + /// there's room now. + private func finished() { + lock.lock() + inFlight = max(0, inFlight - 1) + lock.unlock() + resendIfRoom() + } + + private func resendIfRoom() { + lock.lock() + var next: (CVPixelBuffer, CMTime)? + if !stopped, skipped, ws.pendingBytes <= maxPending / 2, inFlight < Session.maxInFlight, let l = last { + next = l + skipped = false + inFlight += 1 + } + lock.unlock() + if let (pb, _) = next { submit(pb, pts: CMClockGetTime(CMClockGetHostTimeClock())) } + } + + func start(maxLong: Int, fps: Int) { + encoder.onFrame = { [weak self] data, key, pts in + guard let self else { return } + var msg = Data([key ? 1 : 0]) + var us = UInt64(max(0, CMTimeGetSeconds(pts)) * 1_000_000).bigEndian + msg.append(Data(bytes: &us, count: 8)) + msg.append(data) + self.ws.sendBinary(msg) + } + encoder.onDone = { [weak self] in self?.finished() } + encoder.onError = { [weak self] message in self?.ws.sendJSON(["t": "error", "message": message]) } + capture.onFrame = { [weak self] pb, pts in self?.offer(pb, pts: pts) } + capture.onEnded = { [weak self] reason in + guard let self else { return } + self.ws.sendJSON(["t": "closed", "reason": reason]) + self.onFinished?(self.source.key) + self.end() + } + ws.onDrain = { [weak self] in self?.resendIfRoom() } + ws.onText = { [weak self] text in self?.handle(text) } + ws.onClose = { [weak self] in self?.end() } + ws.start() + // Reconnect with this (kept in the page's memory, never on a command line). + ws.sendJSON(["t": "hello", "r": reconnectKey]) + if let sck = capture as? SCKSource { + sck.onChange = { [weak self] in self?.sendInfo() } + } + capture.start(maxLong: maxLong, fps: fps) { [weak self] error in + guard let self else { return } + if let error { + if (self.capture as? SCKSource)?.gone == true { + // Final, like a window closing mid-stream: the viewer closes + // and its key is revoked, rather than retrying for ever. + self.ws.sendJSON(["t": "closed", "reason": error]) + self.onFinished?(self.source.key) + } else { + self.ws.sendJSON(["t": "error", "message": error]) + } + self.end() + return + } + self.sendInfo() + } + } + + func sendInfo() { + ws.sendJSON(["t": "info", "src": source.key, "title": capture.title, "app": capture.app, "codec": codec.rawValue, + "input": source == .test || Input.allowed, + "aspect": Double(capture.frameRect.width / max(capture.frameRect.height, 1))]) + } + + var isStopped: Bool { lock.lock(); defer { lock.unlock() }; return stopped } + + func end() { + lock.lock() + let was = stopped + stopped = true + last = nil + lock.unlock() + guard !was else { return } + capture.stop() + encodeQueue.async { self.encoder.invalidate() } + let owner = id + DispatchQueue.main.async { Input.releaseAll(owner: owner) } + ws.close() + onEnd?(self) + } + + /// A point in the picture (0...1 each way) -> Mac global coordinates. + private func point(_ x: Double, _ y: Double) -> CGPoint { + let r = capture.frameRect + return CGPoint(x: r.minX + min(max(x, 0), 1) * r.width, y: r.minY + min(max(y, 0), 1) * r.height) + } + + private func handle(_ text: String) { + guard !isStopped, let d = text.data(using: .utf8), + let m = try? JSONSerialization.jsonObject(with: d) as? [String: Any], let t = m["t"] as? String else { return } + let x = m["x"] as? Double ?? -1, y = m["y"] as? Double ?? -1 + if t == "ack" { + onAck?() + onAck = nil + return + } + if t == "key-frame" { + encodeQueue.async { self.encoder.requestKeyFrame() } // before the resend, same queue + lock.lock(); skipped = last != nil; lock.unlock() + resendIfRoom() + return + } + if source == .test { + let desc: String? + switch t { + case "m": desc = (m["e"] as? String) == "move" ? nil : "mouse \(m["e"] ?? "") button \(m["b"] ?? 0)" + case "wheel": desc = "wheel \(m["dx"] ?? 0), \(m["dy"] ?? 0)" + case "k": desc = (m["e"] as? String) == "down" ? "key \(m["code"] ?? "") \"\(m["key"] ?? "")\"" : nil + case "text": desc = "text \"\(m["s"] ?? "")\"" + default: desc = nil + } + capture.pointer(x: x, y: y, text: desc) + return + } + DispatchQueue.main.async { [self] in + guard !isStopped else { return } // Stop revokes input at once + switch t { + case "m": + let kind = m["e"] as? String ?? "move", b = m["b"] as? Int ?? 0 + let p = point(x, y) + if kind == "down", case .window(let wid) = source, let pid = capture.pid { + Input.focus(window: wid, pid: pid) + } + Input.mouse(kind, button: b, at: p, owner: id) + case "wheel": + Input.scroll(dx: m["dx"] as? Double ?? 0, dy: m["dy"] as? Double ?? 0, at: point(x, y), owner: id) + case "k": + Input.key(code: m["code"] as? String ?? "", key: m["key"] as? String ?? "", + down: (m["e"] as? String) == "down", mods: m["mods"] as? [String] ?? [], owner: id) + case "text": + if let s = m["s"] as? String, s.count <= 4096 { Input.text(s) } + case "release": + Input.releaseAll(owner: id) + case "focus": + if case .window(let wid) = source, let pid = capture.pid { Input.focus(window: wid, pid: pid) } + default: break + } + } + } +} + +func randomKey() -> String { + var bytes = [UInt8](repeating: 0, count: 24) + _ = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes) + return Data(bytes).base64EncodedString().replacingOccurrences(of: "+", with: "-") + .replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "=", with: "") +} + +func sameSecret(_ a: String, _ b: String) -> Bool { + guard !a.isEmpty, a.utf8.count == b.utf8.count else { return false } + return zip(a.utf8, b.utf8).reduce(0, { $0 | ($1.0 ^ $1.1) }) == 0 +} + +final class Agent { + /// Frame Control's own key. It stays on the Mac: the Frame only ever sees + /// single-use tickets and per-stream reconnect keys, both tied to one source. + let token: String + let page: URL? + var sessions: [Int: Session] = [:] { didSet { keepDisplayAwake(!sessions.isEmpty) } } + var nextId = 1 + let lock = NSLock() + private var assertion: IOPMAssertionID = 0 + /// ticket -> (source, expiry, reconnect key once redeemed). A ticket opens + /// one viewer within a minute; it may be redeemed again, for the same key, + /// only until that viewer confirms it has the key ("ack"). + private var tickets: [String: (src: String, expiry: Date, key: String?)] = [:] + /// reconnect key -> source, until Stop for that source. + private var reconnectKeys: [String: String] = [:] + /// Sources that ended for good (the window closed), for Frame Control. + private var finished = Set() + + /// While anyone watches, keep the Mac's display on: a sleeping display + /// stops being drawn, so there'd be nothing to capture (and it would lock). + private func keepDisplayAwake(_ on: Bool) { + if on, assertion == 0 { + IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleDisplaySleep as CFString, + IOPMAssertionLevel(kIOPMAssertionLevelOn), + "Frame Control is showing this Mac in a Steam Frame" as CFString, &assertion) + } else if !on, assertion != 0 { + IOPMAssertionRelease(assertion) + assertion = 0 + } + } + + init(token: String, page: URL?) { + self.token = token + self.page = page + } + + /// Whether this request may open a stream of `src`: Frame Control's key, + /// an unused ticket for that source, or a live reconnect key for it. + private func mayStream(_ req: Request, src: String) -> String? { + lock.lock() + defer { lock.unlock() } + let now = Date() + tickets = tickets.filter { $0.value.expiry > now } + if sameSecret(req.query["k"] ?? "", token) { return randomKey() } + if let t = req.query["t"], let entry = tickets[t], entry.src == src { + // A retry before the viewer got its key gets the same key back. + let key = entry.key ?? randomKey() + tickets[t] = (src, entry.expiry, key) + reconnectKeys[key] = src + return key + } + if let r = req.query["r"], reconnectKeys[r] == src { return r } + return nil + } + + /// The viewer has its reconnect key, so the ticket that led to it can't + /// be used again (also when the ack comes over a reconnection). + func acknowledged(key: String) { + lock.lock(); tickets = tickets.filter { $0.value.key != key }; lock.unlock() + } + + func handle(_ req: Request, _ c: HTTPConnection) { + // Open to anything that reaches the port: a liveness check for the + // tunnel, and the viewer page, which holds no secrets. + switch (req.method, req.path) { + case ("GET", "/ping"): return c.respond(200, text: "frame-mac-view") + case ("GET", "/view"): + guard let page, let body = try? Data(contentsOf: page) else { return c.respond(404, text: "no viewer page") } + return c.respond(200, body: body, type: "text/html; charset=utf-8") + case ("GET", "/stream"): + guard let src = Source(req.query["src"] ?? "") else { return c.respond(400, text: "bad src") } + guard let key = mayStream(req, src: src.key) else { return c.respond(403, text: "forbidden") } + return stream(req, c, src: src, key: key) + default: break + } + guard sameSecret(req.query["k"] ?? req.headers["x-token"] ?? "", token) else { + return c.respond(403, text: "forbidden") + } + switch (req.method, req.path) { + case ("GET", "/status"): + lock.lock() + let list = sessions.values.map { ["id": $0.id, "src": $0.source.key, "title": $0.capture.title, "app": $0.capture.app] } + lock.unlock() + var s = permissionsJSON() + s["version"] = version + s["streams"] = list + lock.lock(); s["finished"] = Array(finished); lock.unlock() + c.respond(json: s) + case ("GET", "/windows"): + c.respond(json: ["windows": windowsJSON(), "screen": CGPreflightScreenCaptureAccess()]) + case ("GET", "/displays"): + c.respond(json: ["displays": displaysJSON()]) + case ("POST", "/ticket"): + guard let src = Source(req.query["src"] ?? "") else { return c.respond(400, text: "bad src") } + let t = randomKey() + lock.lock() + tickets[t] = (src.key, Date().addingTimeInterval(60), nil) + finished.remove(src.key) + lock.unlock() + c.respond(json: ["ticket": t]) + case ("POST", "/close"): + // Tell viewers to close their windows, but don't rely on them: + // the sessions end here and can't reconnect. + let src = req.query["src"] + lock.lock() + let matching = sessions.values.filter { src == nil || $0.source.key == src } + reconnectKeys = reconnectKeys.filter { src != nil && $0.value != src } + tickets = tickets.filter { src != nil && $0.value.src != src } // not yet used ones too + lock.unlock() + for s in matching { s.ws.sendJSON(["t": "close"]) } + DispatchQueue.global().asyncAfter(deadline: .now() + 0.3) { for s in matching { s.end() } } + c.respond(json: ["closed": matching.count]) + case ("POST", "/permissions"): + DispatchQueue.main.async { requestPermissions() } + c.respond(json: permissionsJSON()) + default: + c.respond(404, text: "not found") + } + } + + private func stream(_ req: Request, _ c: HTTPConnection, src: Source, key: String) { + let codec = Codec(rawValue: req.query["codec"] ?? "h264") ?? .h264 + let maxLong = min(max(Int(req.query["max"] ?? "") ?? 1920, 320), 3840) + let fps = min(max(Int(req.query["fps"] ?? "") ?? 60, 5), 120) + let bpp = min(max(Double(req.query["bpp"] ?? "") ?? 0.1, 0.02), 0.5) + guard let ws = c.upgrade(req) else { return } + let capture: CaptureSource = src == .test ? TestSource() : SCKSource(src) + lock.lock() + // One live viewer per key: a reconnection (or a second use of an + // unacknowledged ticket) replaces the one before. + let replaced = sessions.values.filter { $0.reconnectKey == key } + let session = Session(id: nextId, source: src, capture: capture, ws: ws, codec: codec, fps: fps, + bitsPerPixel: bpp, reconnectKey: key) + nextId += 1 + sessions[session.id] = session + lock.unlock() + for old in replaced { old.end() } + session.onEnd = { [weak self] s in + guard let self else { return } + self.lock.lock(); self.sessions[s.id] = nil; self.lock.unlock() + } + // The source is gone (its window closed): its viewers can't come back. + session.onFinished = { [weak self] src in + guard let self else { return } + self.lock.lock() + self.finished.insert(src) + self.reconnectKeys = self.reconnectKeys.filter { $0.value != src } + self.lock.unlock() + } + session.onAck = { [weak self] in self?.acknowledged(key: key) } + session.start(maxLong: maxLong, fps: fps) + } +} + +func requestPermissions() { + if !CGPreflightScreenCaptureAccess() { CGRequestScreenCaptureAccess() } + if !AXIsProcessTrusted() { + AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true] as CFDictionary) + } +} + +func argument(_ name: String, in args: [String]) -> String? { + guard let i = args.firstIndex(of: name), i + 1 < args.count else { return nil } + return args[i + 1] +} + +let args = Array(CommandLine.arguments.dropFirst()) +switch args.first { +case "windows": + printJSON(["windows": windowsJSON(), "screen": CGPreflightScreenCaptureAccess()]) +case "displays": + printJSON(["displays": displaysJSON()]) +case "permissions": + printJSON(permissionsJSON()) +case "request-permissions": + requestPermissions() + printJSON(permissionsJSON()) +case "serve": + let port = UInt16(argument("--port", in: args) ?? "") ?? 0 + let token = ProcessInfo.processInfo.environment["FRAME_MAC_VIEW_TOKEN"] ?? "" + guard !token.isEmpty else { + FileHandle.standardError.write(Data("frame-mac-view: set FRAME_MAC_VIEW_TOKEN\n".utf8)) + exit(2) + } + let page = argument("--page", in: args).map { URL(fileURLWithPath: $0) } + let agent = Agent(token: token, page: page) + // Quit when the parent goes away (it holds our stdin open). + if args.contains("--exit-on-eof") { + DispatchQueue.global().async { + while FileHandle.standardInput.availableData.count > 0 {} + exit(0) + } + } + let server: Server + do { + server = try Server(port: port) { req, c in agent.handle(req, c) } + } catch { + FileHandle.standardError.write(Data("frame-mac-view: \(error)\n".utf8)) + exit(1) + } + server.start { [server] error in + if let error { + FileHandle.standardError.write(Data("frame-mac-view: can't listen on 127.0.0.1:\(port): \(error)\n".utf8)) + exit(1) + } + print("frame-mac-view listening on 127.0.0.1:\(server.port ?? port)") + fflush(stdout) + } + _ = NSApplication.shared // AppKit for NSScreen names and app activation + withExtendedLifetime(server) { RunLoop.main.run() } +default: + FileHandle.standardError.write(Data("usage: frame-mac-view serve|windows|displays|permissions|request-permissions\n".utf8)) + exit(2) +} diff --git a/mac/frame-mac-view/build.sh b/mac/frame-mac-view/build.sh new file mode 100755 index 0000000..4fafda2 --- /dev/null +++ b/mac/frame-mac-view/build.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# Builds the Mac streaming agent into mac/bin/frame-mac-view (arm64, macOS 14+). +# Frame Control runs it for "Mac in the headset"; the Electron app bundles it. +set -eu +here=$(cd "$(dirname "$0")" && pwd) +out=${1:-$here/../bin/frame-mac-view} +mkdir -p "$(dirname "$out")" +xcrun swiftc -O -swift-version 5 -target arm64-apple-macos14.0 \ + -o "$out" "$here"/Sources/*.swift +echo "built $out" diff --git a/tests/test_macview.py b/tests/test_macview.py new file mode 100644 index 0000000..1a31ee1 --- /dev/null +++ b/tests/test_macview.py @@ -0,0 +1,274 @@ +"""Mac in the headset (ui/frame_macview.py and the frame-mac-view agent). + +The Python checks run anywhere. On macOS the agent is built and driven over +HTTP and WebSocket with its test pattern, which needs no Screen Recording +permission: status, the token, the viewer page, H.264 keyframes, pointer +input reaching the source, and closing. + +Run: python3 -m unittest discover -s tests +""" +import base64 +import http.client +import json +import os +import shutil +import socket +import struct +import subprocess +import sys +import tempfile +import time +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(ROOT / "ui")) + +import frame_macview # noqa: E402 + + +class Helpers(unittest.TestCase): + def test_panel_id_is_stable_and_in_range(self): + a = frame_macview.panel_id("window:123") + self.assertEqual(a, frame_macview.panel_id("window:123")) + self.assertNotEqual(a, frame_macview.panel_id("window:124")) + self.assertTrue(2_001_000_000 <= a < 2_002_000_000) + + def test_fit_keeps_aspect_inside_the_panel(self): + self.assertEqual(frame_macview.fit(2560, 1440), (1920, 1080)) + w, h = frame_macview.fit(800, 1600) + self.assertEqual(h, 1080) + self.assertAlmostEqual(w / h, 0.5, places=2) + + @unittest.skipUnless(shutil.which("bash"), "needs bash") + def test_launch_script_parses(self): + r = subprocess.run(["bash", "-n"], input=frame_macview.LAUNCH, text=True, capture_output=True) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_show_checks_the_source_before_anything_else(self): + mv = frame_macview.MacView(["ssh"], lambda *a, **k: self.fail("no ssh"), "frame") + with self.assertRaises(frame_macview.MacViewError): + mv.show("rm -rf /") + + def test_missing_browser_is_explained(self): + calls = [] + + def run(remote, stdin=None, timeout=30): + calls.append(remote) + e = RuntimeError("exit 3") + e.stdout = "NO_BROWSER\n" + raise e + + mv = frame_macview.MacView(["ssh"], run, "frame") + mv.call = lambda path, **kw: {"screen": True, "ticket": "tk"} + mv.ensure_tunnel = lambda **kw: None + mv.remote_port = 47900 + with self.assertRaises(frame_macview.MacViewError) as cm: + mv.show("window:5") + self.assertIn("Chromium", str(cm.exception)) + self.assertTrue(calls[0].startswith("bash -s -- ")) + + def test_screen_permission_is_checked_before_the_headset(self): + mv = frame_macview.MacView(["ssh"], lambda *a, **k: self.fail("no ssh"), "frame") + mv.call = lambda path, **kw: {"screen": False} + with self.assertRaises(frame_macview.MacViewError) as cm: + mv.show("display:1") + self.assertIn("Screen Recording", str(cm.exception)) + + +class WS: + """A minimal WebSocket client (masked frames out, plain frames in).""" + + def __init__(self, port, path): + self.s = socket.create_connection(("127.0.0.1", port), timeout=10) + key = base64.b64encode(os.urandom(16)).decode() + self.s.sendall(f"GET {path} HTTP/1.1\r\nHost: x\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n" + f"Sec-WebSocket-Key: {key}\r\nSec-WebSocket-Version: 13\r\n\r\n".encode()) + head = b"" + while b"\r\n\r\n" not in head: + head += self.s.recv(1) + self.status = int(head.split()[1]) + self.buf = b"" + + def _read(self, n): + while len(self.buf) < n: + chunk = self.s.recv(65536) + if not chunk: + raise EOFError + self.buf += chunk + out, self.buf = self.buf[:n], self.buf[n:] + return out + + def recv(self): + b0, b1 = self._read(2) + n = b1 & 0x7F + if n == 126: + n = struct.unpack(">H", self._read(2))[0] + elif n == 127: + n = struct.unpack(">Q", self._read(8))[0] + return b0 & 0x0F, self._read(n) + + def send_text(self, text): + data, mask = text.encode(), os.urandom(4) + head = bytes([0x81, 0x80 | len(data)]) if len(data) < 126 else bytes([0x81, 0xFE]) + struct.pack(">H", len(data)) + self.s.sendall(head + mask + bytes(c ^ mask[i % 4] for i, c in enumerate(data))) + + def close(self): + self.s.close() + + +@unittest.skipUnless(sys.platform == "darwin" and shutil.which("xcrun"), "the agent is macOS-only") +class Agent(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.tmp = tempfile.mkdtemp() + cls.bin = Path(cls.tmp) / "frame-mac-view" + r = subprocess.run(["/bin/sh", str(ROOT / "mac" / "frame-mac-view" / "build.sh"), str(cls.bin)], + capture_output=True, text=True, timeout=600) + if r.returncode: # a real failure on a Mac with Xcode: don't hide it as a skip + raise AssertionError("agent didn't build:\n" + (r.stderr or r.stdout)[-2000:]) + cls.token = "t0ken-" + os.urandom(6).hex() + cls.proc = subprocess.Popen([str(cls.bin), "serve", "--port", "0", "--page", str(ROOT / "ui" / "mac-view.html"), + "--exit-on-eof"], env={**os.environ, "FRAME_MAC_VIEW_TOKEN": cls.token}, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True) + line = cls.proc.stdout.readline() + cls.port = int(line.rsplit(":", 1)[1]) + + @classmethod + def tearDownClass(cls): + cls.proc.stdin.close() # --exit-on-eof + cls.proc.stdout.close() + try: + cls.proc.wait(5) + except subprocess.TimeoutExpired: + cls.proc.kill() + shutil.rmtree(cls.tmp, ignore_errors=True) + + def get(self, path, method="GET"): + c = http.client.HTTPConnection("127.0.0.1", self.port, timeout=10) + c.request(method, path) + r = c.getresponse() + return r.status, r.read() + + def test_token_is_required(self): + self.assertEqual(self.get("/status")[0], 403) + self.assertEqual(self.get("/status?k=wrong")[0], 403) + status, body = self.get(f"/status?k={self.token}") + self.assertEqual(status, 200) + self.assertIn("screen", json.loads(body)) + + def test_serves_the_viewer_page(self): + status, body = self.get(f"/view?k={self.token}&src=test") + self.assertEqual(status, 200) + self.assertIn(b"VideoDecoder", body) + + def test_lists_displays(self): + status, body = self.get(f"/displays?k={self.token}") + self.assertEqual(status, 200) + self.assertIsInstance(json.loads(body)["displays"], list) + + def ticket(self, src): + status, body = self.get(f"/ticket?k={self.token}&src={src}", method="POST") + self.assertEqual(status, 200) + return json.loads(body)["ticket"] + + def test_ping_and_page_are_open_but_streams_are_not(self): + self.assertEqual(self.get("/ping"), (200, b"frame-mac-view")) + self.assertEqual(WS(self.port, "/stream?src=test").status, 403) + self.assertEqual(self.get("/ticket?src=test", method="POST")[0], 403) + + def test_tickets_are_single_use_and_tied_to_one_source(self): + t = self.ticket("test") + self.assertEqual(WS(self.port, f"/stream?src=display:1&t={t}").status, 403) # wrong source + ws = WS(self.port, f"/stream?src=test&t={t}") + self.assertEqual(ws.status, 101) + hello = json.loads(ws.recv()[1]) + self.assertEqual(hello["t"], "hello") + # Until the viewer acknowledges, a retry (the hello got lost) gets the + # same key, and replaces the first viewer rather than adding one. + retry = WS(self.port, f"/stream?src=test&t={t}") + self.assertEqual(retry.status, 101) + self.assertEqual(json.loads(retry.recv()[1])["r"], hello["r"]) + time.sleep(0.3) + _, body = self.get(f"/status?k={self.token}") + self.assertEqual(len(json.loads(body)["streams"]), 1) + ws.close() + ws = retry + ws.send_text(json.dumps({"t": "ack"})) + time.sleep(0.3) + self.assertEqual(WS(self.port, f"/stream?src=test&t={t}").status, 403) # spent + again = WS(self.port, f"/stream?src=test&r={hello['r']}") # the viewer reconnecting + self.assertEqual(again.status, 101) + again.close() + ws.close() + # Stop revokes the reconnect key. + self.get(f"/close?k={self.token}&src=test", method="POST") + self.assertEqual(WS(self.port, f"/stream?src=test&r={hello['r']}").status, 403) + + def test_stop_revokes_tickets_not_yet_used(self): + t = self.ticket("test") + self.get(f"/close?k={self.token}&src=test", method="POST") + self.assertEqual(WS(self.port, f"/stream?src=test&t={t}").status, 403) + + def test_bad_frame_lengths_close_the_socket_not_the_agent(self): + ws = WS(self.port, f"/stream?src=test&t={self.ticket('test')}") + self.assertEqual(ws.status, 101) + # A masked frame claiming 2^63 bytes. + ws.s.sendall(bytes([0x81, 0xFF]) + struct.pack(">Q", 1 << 63) + os.urandom(4)) + with self.assertRaises((EOFError, OSError)): + for _ in range(1000): + ws.recv() + ws.close() + self.assertEqual(self.get(f"/status?k={self.token}")[0], 200) + + def test_stream_input_and_close(self): + ws = WS(self.port, f"/stream?k={self.token}&src=test&codec=h264&fps=30&max=640") + self.assertEqual(ws.status, 101) + info = None + key = None + for _ in range(200): + op, data = ws.recv() + if op == 1: + msg = json.loads(data) + if msg["t"] == "hello": + continue + if msg["t"] == "error": + self.skipTest("no H.264 encoder here: " + msg["message"]) + if msg["t"] == "info": + info = msg + elif op == 2 and data[0] == 1: + key = data + if info and key: + break + self.assertEqual(info["src"], "test") + self.assertTrue(info["input"]) + # A keyframe: flags, 8-byte timestamp, then Annex B with the SPS first. + self.assertEqual(key[9:13], b"\x00\x00\x00\x01") + self.assertEqual(key[13] & 0x1F, 7) # NAL type 7, SPS + ws.send_text(json.dumps({"t": "m", "e": "down", "b": 0, "x": 0.5, "y": 0.5})) + ws.send_text(json.dumps({"t": "key-frame"})) + got_key = False + for _ in range(200): + op, data = ws.recv() + if op == 2 and data[0] == 1: + got_key = True + break + self.assertTrue(got_key, "no keyframe after asking for one") + _, body = self.get(f"/status?k={self.token}") + self.assertEqual([s["src"] for s in json.loads(body)["streams"]], ["test"]) + status, body = self.get(f"/close?k={self.token}", method="POST") + self.assertEqual(json.loads(body)["closed"], 1) + for _ in range(400): + op, data = ws.recv() + if op == 1: + break + self.assertEqual(json.loads(data)["t"], "close") + # Without the viewer doing anything, the agent ends the stream itself. + time.sleep(1) + _, body = self.get(f"/status?k={self.token}") + self.assertEqual(json.loads(body)["streams"], []) + ws.close() + + +if __name__ == "__main__": + unittest.main() diff --git a/ui/frame_macview.py b/ui/frame_macview.py new file mode 100644 index 0000000..cf31580 --- /dev/null +++ b/ui/frame_macview.py @@ -0,0 +1,361 @@ +"""Mac in the headset: stream Mac windows or displays into the Steam Frame as +panels you can place anywhere, with the laser, wheel and keys driving the Mac. + +Runs on the Mac, inside Frame Control's server. The pieces: + +- mac/bin/frame-mac-view (Swift, built from mac/frame-mac-view): captures with + ScreenCaptureKit, encodes with VideoToolbox, serves ui/mac-view.html and one + WebSocket per stream on 127.0.0.1, and plays input back with CGEvent. +- An `ssh -R` tunnel, so the Frame reaches the agent on its own 127.0.0.1. + Every request carries a random token, so other programs on the Frame + (Android apps included) can't watch or drive the Mac. +- A Chromium app window on the Frame per stream, on gamescope's X display + with its own STEAM_GAME id, which makes it its own SteamVR panel (see + docs/panels.md). Chromium XR (~/chromium-xr) is preferred because it's + built with H.264; Flathub Chromium is the fallback. + +Stdlib only. MacView gets a plain ssh argv for the tunnel (its own +connection) and the server's `run(remote, stdin=, timeout=)` for commands. +""" +import json +import os +import re +import secrets +import shutil +import subprocess +import sys +import threading +import time +import urllib.error +import urllib.request +import zlib +from pathlib import Path +from urllib.parse import urlencode + +HERE = Path(__file__).resolve().parent +ROOT = HERE.parent +PAGE = HERE / "mac-view.html" +SOURCES = ROOT / "mac" / "frame-mac-view" +AGENT = Path(os.environ.get("FRAME_MAC_VIEW") or ROOT / "mac" / "bin" / "frame-mac-view") +REMOTE_PORTS = range(47900, 47920) +SUPPORTED = sys.platform == "darwin" + +# Stream settings by name: long side in pixels, frames per second, H.264 bits +# per pixel per frame, codec. JPEG is for a Frame browser without H.264. +QUALITY = { + "sharp": {"max": 2560, "fps": 60, "bpp": 0.14, "codec": "h264"}, + "balanced": {"max": 1920, "fps": 60, "bpp": 0.1, "codec": "h264"}, + "light": {"max": 1280, "fps": 30, "bpp": 0.08, "codec": "h264"}, + "compatible": {"max": 1280, "fps": 20, "bpp": 0.1, "codec": "jpeg"}, +} +# Viewer windows are fitted inside a panel's size. gamescope made a 1280x720 +# request 1920x1080 anyway (verified 2026-09-28, build 20260925.6191901). +PANEL_BOX = (1920, 1080) + +# Opens one viewer on gamescope's X display and gives its window its own panel +# id. Args: appid url width height tag. The page puts "[tag]" in its title at +# once, which is how its X window is found (Chromium may hand the URL to an +# instance that's already running, so there's no process to follow). +LAUNCH = r"""set -u +appid=$1 url=$2 w=$3 h=$4 tag=$5 +export DISPLAY=:0 LC_ALL=C.UTF-8 +unset WAYLAND_DISPLAY +if ! xprop -root GAMESCOPE_FOCUSABLE_WINDOWS >/dev/null 2>&1; then + echo "The headset isn't showing anything (gamescope's display :0 isn't up). Wake it and try again." >&2 + exit 2 +fi +common=(--ozone-platform=x11 --force-device-scale-factor=1 --no-first-run --no-default-browser-check + --password-store=basic --disable-session-crashed-bubble --noerrdialogs --disable-infobars + --disable-features=Translate,MediaRouter --autoplay-policy=no-user-gesture-required + "--window-size=$w,$h" "--app=$url") +if [ -x "$HOME/chromium-xr/chrome" ]; then + cmd=("$HOME/chromium-xr/chrome" "--user-data-dir=$HOME/.local/share/frame-control/mac-view" "${common[@]}") +elif flatpak info org.chromium.Chromium >/dev/null 2>&1; then + cmd=(flatpak run org.chromium.Chromium + "--user-data-dir=$HOME/.var/app/org.chromium.Chromium/data/frame-mac-view" "${common[@]}") +else + echo "NO_BROWSER" + exit 3 +fi +log=/tmp/frame-mac-view.log +setsid nohup "${cmd[@]}" >>"$log" 2>&1 /dev/null | awk '/^ +0x/ {print $1}'); do + xprop -id "$win" _NET_WM_NAME WM_NAME 2>/dev/null | grep -qF "[$tag]" || continue + if xprop -id "$win" -f STEAM_GAME 32c -set STEAM_GAME "$appid" 2>/dev/null; then + echo "panel valve.steam.desktopgame.$appid window $win" + exit 0 + fi + done +done +echo "The viewer started, but its window didn't appear within 30 s. Chromium's log:" >&2 +tail -n 15 "$log" >&2 +exit 1 +""" + + +class MacViewError(Exception): + pass + + +def panel_id(src): + """A stable panel id per source, in the range panel-on-frame.sh uses.""" + return 2_001_000_000 + zlib.crc32(f"mac:{src}".encode()) % 1_000_000 + + +def fit(w, h, box=PANEL_BOX): + s = min(box[0] / max(w, 1), box[1] / max(h, 1)) + return max(320, round(w * s)), max(200, round(h * s)) + + +class MacView: + def __init__(self, tunnel_ssh, run, frame, track=None): + self.tunnel_ssh = list(tunnel_ssh) + self.run = run + self.frame = frame + self.track = track or (lambda proc: None) # the server ends these on exit + self.lock = threading.Lock() + self.token = secrets.token_urlsafe(24) + self.agent = None + self.port = None + self.tunnel = None + self.remote_port = None + self.supervisor = None + self.closing = False + self.shown = set() # sources with a viewer out there, connected or retrying + + # ---- the agent on this Mac ---- + + def unavailable(self): + """Why this can't work here, or None.""" + if not SUPPORTED: + return "Streaming your computer into the headset needs macOS." + if not AGENT.exists() and not (SOURCES.exists() and shutil.which("xcrun")): + return "The Mac streaming helper is missing from this copy of Frame Control." + return None + + def build(self): + if AGENT.exists() and not self._stale(): + return + if not (SOURCES / "build.sh").exists(): + if AGENT.exists(): + return + raise MacViewError("The Mac streaming helper is missing.") + r = subprocess.run(["/bin/sh", str(SOURCES / "build.sh"), str(AGENT)], capture_output=True, text=True, + stdin=subprocess.DEVNULL, timeout=600) + if r.returncode != 0: + raise MacViewError("Couldn't build the Mac streaming helper: " + (r.stderr or r.stdout).strip()[-400:]) + + def _stale(self): + try: + built = AGENT.stat().st_mtime + return any(p.stat().st_mtime > built for p in (SOURCES / "Sources").glob("*.swift")) + except OSError: + return False + + def ensure_agent(self): + with self.lock: + if self.agent and self.agent.poll() is None: + return + reason = self.unavailable() + if reason: + raise MacViewError(reason) + self.build() + env = {**os.environ, "FRAME_MAC_VIEW_TOKEN": self.token} + # The same port as before when restarting, so a running tunnel still + # fits; otherwise (or if it's gone) whatever the system gives. + for port in dict.fromkeys([self.port or 0, 0]): + self.agent = subprocess.Popen([str(AGENT), "serve", "--port", str(port), "--page", str(PAGE), + "--exit-on-eof"], env=env, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + line = self.agent.stdout.readline() + m = re.search(r"listening on 127\.0\.0\.1:(\d+)", line) + if m: + break + self.agent.kill() + else: + raise MacViewError(f"The Mac streaming helper didn't start: {line.strip() or 'no output'}") + if self.port != int(m.group(1)): + self._drop_tunnel() + self.port = int(m.group(1)) + self.track(self.agent) + threading.Thread(target=self.agent.stdout.read, daemon=True).start() # drain + + def call(self, path, method="GET", **query): + """A request to the agent; starts it if needed.""" + self.ensure_agent() + url = f"http://127.0.0.1:{self.port}{path}?{urlencode({**query, 'k': self.token})}" + req = urllib.request.Request(url, method=method, data=b"" if method == "POST" else None) + try: + with urllib.request.urlopen(req, timeout=10) as r: + return json.load(r) + except (urllib.error.URLError, OSError, ValueError) as e: + raise MacViewError(f"The Mac streaming helper didn't answer: {e}") + + # ---- the tunnel from the Frame ---- + + def _drop_tunnel(self): + if self.tunnel and self.tunnel.poll() is None: + self.tunnel.terminate() + self.tunnel = None + + def tunnel_up(self): + return self.tunnel is not None and self.tunnel.poll() is None + + def ensure_tunnel(self, allow_new_port=False): + """Open the tunnel, on the port viewers already use if there is one. + A new port only when nobody is watching, since viewers can't move.""" + with self.lock: + if self.tunnel_up(): + return + last = "" + ports = [self.remote_port] if self.remote_port else list(REMOTE_PORTS) + if self.remote_port and allow_new_port: + ports += [p for p in REMOTE_PORTS if p != self.remote_port] + for port in ports: + proc = subprocess.Popen([*self.tunnel_ssh, "-o", "ExitOnForwardFailure=yes", + "-o", "ServerAliveInterval=5", "-o", "ServerAliveCountMax=3", "-N", + "-R", f"127.0.0.1:{port}:127.0.0.1:{self.port}", self.frame], + stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, text=True) + # A taken port makes ssh exit once it's connected; a working + # tunnel answers the agent's status from the Frame's side. + ok = False + for _ in range(15): + time.sleep(0.4) + if proc.poll() is not None: + break + if self._probe(port): + ok = True + break + if ok: + self.tunnel, self.remote_port = proc, port + self.track(proc) + self._supervise() + return + if proc.poll() is None: + proc.terminate() + proc.wait() + last = (proc.stderr.read() or "").strip() + if "forward" not in last.lower(): + break # not a port clash: the Frame is unreachable + raise MacViewError(f"Couldn't open a tunnel from {self.frame} to this Mac: {last or 'no answer through it'}") + + def _supervise(self): + """Reopen the tunnel on the same port after the headset sleeps or the + network drops, so viewers that are retrying find the Mac again.""" + if self.supervisor and self.supervisor.is_alive(): + return + + def loop(): + while not self.closing: + time.sleep(5) + if self.closing or self.tunnel_up() or not (self.agent and self.agent.poll() is None): + continue + try: + self.ensure_tunnel() + except MacViewError: + pass # still unreachable; try again shortly + + self.supervisor = threading.Thread(target=loop, daemon=True) + self.supervisor.start() + + def _probe(self, port): + """Whether the Frame reaches the agent through the tunnel on `port`.""" + # /ping needs no key, so none appears on the Frame's command lines. + cmd = f"curl -s -m 2 'http://127.0.0.1:{port}/ping'" + try: + return self.run(cmd, timeout=8).strip() == "frame-mac-view" + except Exception: # noqa: BLE001 - the server's Failure, timeouts: all mean "not yet" + return False + + # ---- viewers on the Frame ---- + + def show(self, src, quality="balanced", width=None, height=None): + if src != "test" and not (src.startswith("window:") or src.startswith("display:")): + raise MacViewError("Pick a window or display to show.") + q = QUALITY.get(quality) or QUALITY["balanced"] + state = self.call("/status") + if src != "test" and not state.get("screen"): + raise MacViewError("Frame Control needs Screen Recording permission first (Allow… under Mac in the headset).") + self.shown -= set(state.get("finished", [])) # their Mac windows closed + if src in self.shown or any(st.get("src") == src for st in state.get("streams", [])): + # Showing it again replaces the old viewer, connected or not: this + # revokes its keys so it can't come back alongside the new one. + self.stop(src) + # Viewers that lost the tunnel keep retrying its old port, even though + # the agent no longer counts them, so only move when none are out there. + others = self.shown - {src} + self.ensure_tunnel(allow_new_port=not others) + appid = panel_id(src) + # Unique per launch, so a new window is never confused with an old one. + tag = "fc" + secrets.token_hex(4) + # A single-use ticket for this source, not Frame Control's key: the URL + # is visible in the Frame's process list. + ticket = self.call("/ticket", method="POST", src=src)["ticket"] + params = {"src": src, "t": ticket, "tag": tag, "codec": q["codec"], "max": q["max"], "fps": q["fps"], + "bpp": q["bpp"]} + url = f"http://127.0.0.1:{self.remote_port}/view?{urlencode(params)}" + w, h = fit(width or 1280, height or 720) + args = " ".join(_quote(str(a)) for a in (appid, url, w, h, tag)) + try: + out = self.run("bash -s -- " + args, stdin=LAUNCH, timeout=60) + except Exception as e: # noqa: BLE001 - the server's Failure carries the Frame's words + if "NO_BROWSER" in (getattr(e, "stdout", "") or ""): + raise MacViewError("The Frame needs a browser for this: install Chromium (Tools → Linux apps, " + "org.chromium.Chromium) or Chromium XR.") + raise MacViewError(str(e)) + self.shown.add(src) + return {"panel": f"valve.steam.desktopgame.{appid}", "src": src, "detail": out.strip()} + + def stop(self, src=None): + if src: + self.shown.discard(src) + else: + self.shown.clear() + if not (self.agent and self.agent.poll() is None): + return {"closed": 0} + return self.call("/close", method="POST", **({"src": src} if src else {})) + + def state(self): + reason = self.unavailable() + if reason: + return {"available": False, "reason": reason} + status = self.call("/status") + windows = self.call("/windows").get("windows", []) if status.get("screen") else [] + displays = self.call("/displays").get("displays", []) + return {"available": True, "screen": status.get("screen", False), + "accessibility": status.get("accessibility", False), "streams": status.get("streams", []), + "windows": windows, "displays": displays, "tunnel": self.tunnel_up()} + + def restart_agent(self): + """Only if it's missing a permission: a running stream would stop.""" + with self.lock: + if not (self.agent and self.agent.poll() is None): + return + status = self.call("/status") + if status.get("screen") and status.get("accessibility"): + return + with self.lock: + self.agent.terminate() + self.agent.wait(5) + + def request_permissions(self): + return self.call("/permissions", method="POST") + + def shutdown(self): + self.closing = True + try: + self.stop() + except MacViewError: + pass + for proc in (self.tunnel, self.agent): + if proc and proc.poll() is None: + proc.terminate() + + +def _quote(s): + return "'" + s.replace("'", "'\\''") + "'" diff --git a/ui/index.html b/ui/index.html index 3845ef7..73948d6 100644 --- a/ui/index.html +++ b/ui/index.html @@ -309,6 +309,11 @@ .game-card .bar > i { display: block; height: 100%; background: var(--action); } .f-0 { color: var(--muted); } /* ---- Android display ---- */ + #mvQuality { background: rgba(0,0,0,.28); color: var(--text); border: 1px solid transparent; border-radius: 3px; padding: 4px 6px; font: inherit; font-size: 12px; } + .mv-windows { max-height: 420px; overflow-y: auto; } + .mv-perm { background: rgba(255,190,60,.08); border: 1px solid rgba(255,190,60,.25); border-radius: 4px; padding: 10px 12px; margin-bottom: 12px; font-size: 13px; } + .mv-perm .row { margin-top: 8px; } + .mv-live { color: var(--green, #7fd67f); font-size: 12px; } .disp select { width: 100%; background: rgba(0,0,0,.28); color: var(--text); border: 1px solid transparent; border-radius: 3px; padding: 8px 10px; font: inherit; font-size: 13px; } .disp select:focus { outline: none; border-color: var(--blue); } @@ -642,6 +647,25 @@
FrameDrop: sideloader (Windows only for now)
+ + @@ -2107,10 +2131,64 @@ $("shotsRefresh").onclick = loadShots; $("shotsSaveNew").onclick = e => saveShots(shots.list.filter(s => !s.saved), e.currentTarget); $("shotsFolder").onclick = e => act($("shotsFolder").textContent, () => api("/api/open", { what: "shots" }), e.currentTarget); +// ---- Mac in the headset: windows and displays as panels (ui/frame_macview.py) ---- +let mvSeq = 0; +function mvRow(src, title, sub, w, h, streaming) { + return `
${esc(title)}
+
${esc(sub)}${streaming ? ' · in the headset' : ""}
+ ${streaming ? `` + : ``} +
`; +} +async function loadMacView(restart) { + const seq = ++mvSeq; + let s, err; + try { s = await api("/api/macview" + (restart ? "?restart=1" : "")); } catch (e) { err = e; } + if (seq !== mvSeq) return; + const box = $("macview"); + if (err) { box.hidden = false; return failed($("mvWindows"), err); } + if (!s.available) { box.hidden = true; return; } + box.hidden = false; + const live = new Set((s.streams || []).map(x => x.src)); + $("mvCount").textContent = live.size ? `${live.size} showing` : ""; + const perm = []; + if (!s.screen) perm.push(`Frame Control needs Screen Recording permission to show your windows.`); + if (!s.accessibility) perm.push(`Allow Accessibility too, so clicks and keys from the headset reach the Mac.`); + $("mvPerm").hidden = !perm.length; + $("mvPerm").innerHTML = perm.join(" ") + `
+ Then press Refresh. macOS may ask you to reopen Frame Control.
`; + $("mvDisplays").innerHTML = (s.displays || []).map(d => + mvRow(d.src, `Whole screen: ${d.name}`, `${d.w}×${d.h}${d.main ? " · main display" : ""}`, d.w, d.h, live.has(d.src))).join(""); + const wins = s.windows || []; + $("mvWinCount").textContent = wins.length ? `${wins.length}` : ""; + $("mvWindows").innerHTML = !s.screen ? `
Windows appear here once Screen Recording is allowed.
` + : wins.length ? wins.map(w => mvRow(w.src, w.title || w.app, `${w.title ? w.app + " · " : ""}${w.w}×${w.h}`, w.w, w.h, live.has(w.src))).join("") + : `
No windows open on this Mac's current desktop.
`; +} +$("mvRefresh").onclick = () => loadMacView(true); +$("macview").onclick = async e => { + const b = e.target.closest("[data-mv]"); if (!b) return; + const src = b.dataset.src; + if (b.dataset.mv === "show") { + await act(`Show ${b.dataset.title} in the headset`, async () => { + const r = await api("/api/macview", { action: "show", src, quality: $("mvQuality").value, + w: +b.dataset.w || undefined, h: +b.dataset.h || undefined }); + return { message: `${b.dataset.title} is a panel in the headset now. Float it with the SteamVR dashboard.`, ...r }; + }, b); + } else if (b.dataset.mv === "stop") { + await act("Stop showing it", () => api("/api/macview", { action: "stop", src }), b); + } else if (b.dataset.mv === "stopall") { + await act("Stop everything in the headset", () => api("/api/macview", { action: "stop" }), b); + } else if (b.dataset.mv === "perm") { + await act("Ask macOS for permission", () => api("/api/macview", { action: "permissions" }), b); + } + setTimeout(loadMacView, 800); +}; + // ---- pages: #home, #games, #android, #tools (older section links still work) ---- const PAGES = ["home", "games", "android", "tools"]; const SECTION_PAGE = { view: "home", device: "home", shots: "home", library: "games", sideloaded: "games", getgames: "games", - display: "android", transfer: "tools", apps: "tools", power: "tools" }; + display: "android", transfer: "tools", apps: "tools", power: "tools", macview: "tools" }; let page = "home"; function showPage() { const id = location.hash.slice(1); @@ -2122,6 +2200,7 @@ function showPage() { document.title = page === "home" ? "Frame Control" : `${page[0].toUpperCase() + page.slice(1)} · Frame Control`; const section = !PAGES.includes(id) && id && $(id); if (section) section.scrollIntoView(); else window.scrollTo(0, 0); + if (page === "tools") loadMacView(); } window.addEventListener("hashchange", showPage); // While a text field has focus, phones hide the bottom tab bar (see body.typing in the CSS). diff --git a/ui/mac-view.html b/ui/mac-view.html new file mode 100644 index 0000000..d56ecf9 --- /dev/null +++ b/ui/mac-view.html @@ -0,0 +1,242 @@ + + + + + + +Mac + + + + +
Connecting to the Mac…
+ + + diff --git a/ui/server.py b/ui/server.py index ef22af6..bd8a3db 100755 --- a/ui/server.py +++ b/ui/server.py @@ -39,6 +39,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parent)) import frame_android # noqa: E402 import frame_catalog # noqa: E402 import frame_host # noqa: E402 +import frame_macview # noqa: E402 import frame_store # noqa: E402 import frame_titles # noqa: E402 import frame_webinstall # noqa: E402 @@ -1213,10 +1214,50 @@ def _sweep_one(prefix, d): pass +# ---- Mac in the headset (frame_macview.py) ---------------------------------- + +# The tunnel gets its own connection: the shared master's options would win +# over anything added after them. +macview = frame_macview.MacView(["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=8"], + lambda remote, stdin=None, timeout=30: ssh(remote, stdin=stdin, timeout=timeout), + FRAME, track=_live_tunnels.add) + + +def macview_state(query=None): + if LOCAL: + return {"available": False, "reason": "Runs on your computer, not the headset."} + try: + # Refresh restarts the helper, which picks up a permission just granted. + if (query or {}).get("restart") == ["1"]: + macview.restart_agent() + return macview.state() + except frame_macview.MacViewError as e: + raise Failure(str(e), 500) + + +def macview_action(body): + """{action: show|stop|permissions, src, quality, w, h}.""" + if LOCAL: + raise Failure("Runs on your computer, not the headset.", 400) + action = body.get("action") + try: + if action == "show": + w, h = body.get("w"), body.get("h") + return macview.show(str(body.get("src") or ""), str(body.get("quality") or "balanced"), + int(w) if w else None, int(h) if h else None) + if action == "stop": + return macview.stop(body.get("src") or None) + if action == "permissions": + return macview.request_permissions() + except frame_macview.MacViewError as e: + raise Failure(str(e), 502) + raise Failure("unknown action", 400) + + POST = {"/api/android/display": android_display, "/api/android": android, "/api/titles": titles, "/api/launch": launch, "/api/steam": steam, "/api/volume": set_volume, "/api/clipboard": clipboard, "/api/flatpak": flatpak, "/api/open": open_thing, "/api/shots/save": save_shots, "/api/webinstall/check": webinstall_check, "/api/webinstall/start": webinstall_start, - "/api/webinstall/cancel": webinstall_cancel} + "/api/webinstall/cancel": webinstall_cancel, "/api/macview": macview_action} # ---- HTTP ------------------------------------------------------------------ @@ -1336,6 +1377,8 @@ class Handler(BaseHTTPRequestHandler): "shared": frame_catalog.compat_db.shared()}) elif path == "/api/android/catalog": self.send_json({"apps": frame_catalog.catalog()}) + elif path == "/api/macview": + self.send_json(macview_state(parse_qs(url.query))) elif path == "/api/status": self.send_json(status({})) elif path == "/api/steam/owned": @@ -1529,6 +1572,7 @@ def main(): if not frame_host.WINDOWS: signal.signal(signal.SIGTERM, signal.SIG_IGN) webinstall_shutdown() + macview.shutdown() # close the headset's viewers before the agent goes # The master was started with -N, so it stays up until told to exit. if CONTROL: subprocess.run([*MUX, "-O", "exit", FRAME], capture_output=True, stdin=subprocess.DEVNULL) From 724b020e93e569ed3c3172ecbf9970d0a990edda Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 15:56:53 +1000 Subject: [PATCH 02/36] Document what it takes to run VR and Quest APKs in Lepton Verified on the headset: hello_xr and Open Brush's Quest build run immersively unmodified; Wolvic's Quest build needs a LAUNCHER category and fails on SteamVR's Android runtime being OpenXR 1.0 only. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/vr-apks.md | 89 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 docs/vr-apks.md diff --git a/docs/vr-apks.md b/docs/vr-apks.md new file mode 100644 index 0000000..7b913ff --- /dev/null +++ b/docs/vr-apks.md @@ -0,0 +1,89 @@ +# VR APKs and Quest games in Lepton + +What it takes to run an immersive (OpenXR) Android app, including Meta Quest +builds, on the Frame. Checked on SteamOS BUILD_ID 20260925.6191901, Lepton +v2.8.14 (rootfs v2.8.11), SteamVR 2.18.1, on 2026-09-28, unless marked +**inferred**. + +## How a VR APK reaches SteamVR (verified) + +- Lepton ships a standard Khronos system runtime manifest, + `/vendor/etc/openxr/1/active_runtime.json`, pointing at SteamVR's Android + client, `/data/steamvr/runtime/bin/androidarm64/vrclient.so`. That is the + host's `/opt/steamvr/bin/androidarm64/`, bind-mounted in. +- An APK's own Khronos-style `libopenxr_loader.so` tries the runtime brokers + (`org.khronos.openxr.runtime_broker`, `…system_runtime_broker`), finds + neither, then falls back to that manifest. Nothing in the APK has to change + for discovery. +- Install the APK without the flatscreen marker + (`python3 ui/frame_android.py install app.apk --vr`). The marker only + controls Lepton's 2D Android surface; the app itself has to start an + OpenXR session. +- Lepton also loads Valve's `XR_APILAYER_VALVE_fdm_injection` layer from + `/vendor/etc/openxr/1/api_layers/implicit.d/`. Only layers in Valve's own + directories are picked up (`liblepton/vulkan_layers.sh`), so a third-party + layer has to ship inside the APK. + +**Open Brush 2.32.29, the Quest APK from its GitHub release (Unity OpenXR, +Vulkan), works unmodified.** Its manifest already has `LAUNCHER` next to +`com.oculus.intent.category.VR`. Unity asked for OpenXR 1.1, got +`XR_ERROR_API_VERSION_UNSUPPORTED`, retried with 1.0 and succeeded. SteamVR +took it as the scene app, created Touch, simple-controller and Frame-controller +bindings, and the session reached `XR_SESSION_STATE_FOCUSED`. A headset capture +(`ui/frame_vrshot.py`, after waking the compositor and closing the dashboard) +showed a dark sky over a mountain horizon; nobody wore the headset to confirm +it was Open Brush's scene or to try drawing. + +**Khronos `hello_xr` (Vulkan, 1.1.63 release APK) works unmodified:** +`Instance RuntimeName=SteamVR/OpenXR RuntimeVersion=2.18.1`, 1728×1728 +swapchains per eye, session `IDLE → READY → SYNCHRONIZED` (the headset was +not being worn, so it did not reach `FOCUSED`). + +## What SteamVR's Android runtime supports (verified, from `vrclient.so`) + +- **OpenXR 1.0 only.** An app requesting `XR_API_VERSION_1_0` works; one + requesting 1.1 (`XR_CURRENT_API_VERSION` in a 1.1 SDK) gets + `XR_ERROR_API_VERSION_UNSUPPORTED` from the runtime. +- Extensions include `XR_KHR_opengl_es_enable`, `XR_KHR_vulkan_enable{,2}`, + `XR_KHR_composition_layer_depth`, `XR_KHR_locate_spaces`, + `XR_EXT_local_floor`, `XR_EXT_uuid`, `XR_EXT_palm_pose`, + `XR_EXT_hand_tracking`, `XR_EXT_eye_gaze_interaction`, and these Meta ones: + `XR_FB_display_refresh_rate`, `XR_FB_foveation{,_configuration,_vulkan}`, + `XR_FB_space_warp`, `XR_FB_swapchain_update_state`, + `XR_META_foveation_eye_tracked`, `XR_META_recommended_layer_resolution`, + `XR_META_vulkan_swapchain_create_info`, `XR_META_performance_metrics`. +- Not present: `XR_FB_passthrough`, `XR_FB_hand_tracking_*`, + `XR_FB_spatial_entity*`, `XR_FB_color_space`, + `XR_KHR_android_thread_settings`, `XR_OCULUS_*`. +- Interaction profiles include `oculus/touch_controller`, `khr/simple_controller`, + `valve/frame_controller` and the usual PC controllers. Valve documents Touch + bindings as a working fallback on the Frame controllers. + +## What stops a Quest APK (verified with Wolvic 1.9, `oculusvr` build) + +1. **Lepton won't start it.** Lepton's `apk-info-extractor` only accepts an + activity whose intent filter has `android.intent.action.MAIN` and + `android.intent.category.LAUNCHER`. Quest apps use + `com.oculus.intent.category.VR` instead, so Lepton logs `APP_ACTIVITY is + empty` and exits. There is no override. **Fix:** add the `LAUNCHER` + category to that intent filter and re-sign. After that, Wolvic started. +2. **OpenXR 1.1.** Wolvic's Quest build then requested OpenXR 1.1 and aborted + on `XR_ERROR_API_VERSION_UNSUPPORTED`. Unity's OpenXR plugin retries with + 1.0 (Open Brush, above), so this mostly bites native and non-Unity apps. **Fix (inferred):** an API layer + inside the APK that asks the runtime for 1.0 and maps the 1.1 core + functions to the extensions the runtime does have (`XR_KHR_locate_spaces`, + `XR_EXT_local_floor`, `XR_EXT_uuid`, `XR_EXT_palm_pose`). +3. **Not yet reached:** required Meta-only extensions (each app differs), + swapchain formats (the Lynx Wolvic build needed `GL_SRGB8_ALPHA8`), and + Meta platform services. + +The loader was never the problem: Wolvic's Quest `libopenxr_loader.so` is a +Khronos-style loader and found SteamVR through `/vendor`. + +## Out of scope + +- **Meta entitlement.** Apps that call the Oculus Platform SDK + (`libovrplatformloader.so`) to check the Quest store licence need Meta's + services. Frame Control won't work around that. +- **VrApi-era apps** (`libvrapi.so`, before OpenXR) need an API translator, + not a patch. From a1bc6522c3e77dc41c8900f826ce8a0a2a5239f7 Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 16:16:56 +1000 Subject: [PATCH 03/36] Detect VR APKs and repair launchers with pure Python v2 signing --- docs/vr-apks.md | 28 +++ tests/test_frame_apk_vr.py | 187 +++++++++++++++++++ ui/frame_android.py | 60 ++++++- ui/frame_apk.py | 8 +- ui/frame_apk_sign.py | 356 +++++++++++++++++++++++++++++++++++++ ui/frame_apk_vr.py | 113 ++++++++++++ ui/index.html | 7 + ui/server.py | 10 +- 8 files changed, 761 insertions(+), 8 deletions(-) create mode 100644 tests/test_frame_apk_vr.py create mode 100644 ui/frame_apk_sign.py create mode 100644 ui/frame_apk_vr.py diff --git a/docs/vr-apks.md b/docs/vr-apks.md index 7b913ff..45ecd56 100644 --- a/docs/vr-apks.md +++ b/docs/vr-apks.md @@ -87,3 +87,31 @@ Khronos-style loader and found SteamVR through `/vendor`. services. Frame Control won't work around that. - **VrApi-era apps** (`libvrapi.so`, before OpenXR) need an API translator, not a patch. + +## Frame Control does this for you + +APK uploads and `python3 ui/frame_android.py install app.apk` detect VR +manifest categories, Samsung's `vr_only` flag and the arm64 OpenXR loader. +VR apps default to immersive mode without the flatscreen marker. The upload +selector or CLI `--flat` / `--vr` overrides that choice. Compatibility notes +identify legacy VrApi, Meta platform SDK and OpenXR libraries. + +If a VR MAIN intent filter lacks LAUNCHER, Frame Control inserts it, repacks +and v2-signs the APK locally before copying it; `meta.json` records +`"patched": ["launcher"]`. Unchanged ZIP members retain their compressed +bytes; stored libraries are aligned to 16 KiB. The RSA signing identity lives +in Frame Control's per-user app-data directory as `apk-signing-key.json` +(mode 0600). Keep this key to preserve the signer on subsequent patched +updates. A re-signed APK cannot update an installation signed by its original +publisher; Android also treats it as a different signer for signature checks. + +Inspect or prepare an APK without contacting the headset: + +```sh +python3 ui/frame_android.py info app.apk +python3 ui/frame_android.py patch app.apk patched.apk +python3 ui/frame_android.py patch app.apk patched.apk --add assets/openxr/1/api_layers/implicit.d/X.json=X.json --add lib/arm64-v8a/libX.so=libX.so +``` + +The patch fixes Lepton's launch-category requirement. It does not supply an +OpenXR 1.1 translation layer, Meta services or a VrApi implementation. diff --git a/tests/test_frame_apk_vr.py b/tests/test_frame_apk_vr.py new file mode 100644 index 0000000..c13e856 --- /dev/null +++ b/tests/test_frame_apk_vr.py @@ -0,0 +1,187 @@ +"""Local-only VR manifest, raw ZIP and independent signature checks.""" +import io +import os +from pathlib import Path +import struct +import subprocess +import sys +import tempfile +import unittest +from unittest.mock import patch +import zipfile + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'ui')) +import frame_apk +import frame_apk_vr as vr +import frame_apk_sign as signing +import frame_android +from test_frame_apk import pool + + +def manifest(utf8=False, launcher=False, category=None, samsung=False, split=False): + strings = ['manifest', 'package', 'org.test.vr', 'application', 'activity', 'intent-filter', + 'action', 'category', 'name', vr.MAIN, category or next(iter(sorted(vr.VR))), + vr.LAUNCHER, 'http://schemas.android.com/apk/res/android', 'meta-data', 'value', + 'com.samsung.android.vr.application.mode', 'vr_only'] + def start(tag, attrs=()): + body = struct.pack(' {d}/meta.json.tmp && mv {d}/meta.json.tmp {d}/meta.json', input=json.dumps(meta, indent=1)) -def install(apk_path, flatscreen=True, name=None, source=None, icon_png=None): +def install(apk_path, flatscreen=None, name=None, source=None, icon_png=None): info = apk_info(apk_path) if icon_png: info['icon_png'] = icon_png @@ -114,7 +120,16 @@ def install(apk_path, flatscreen=True, name=None, source=None, icon_png=None): pkg = info['package'] if not PKG_RE.match(pkg): raise FrameError(f'unexpected package name {pkg!r}') + if flatscreen is None: + flatscreen = not info['vr'] with _install_lock: + if not info['launchable'] and info['vr_activity']: + with tempfile.TemporaryDirectory(prefix='frame-vr-') as tmp: + patched = os.path.join(tmp, 'app.apk') + patch(apk_path, patched) + info['patched'] = ['launcher'] + info['launchable'] = True + return _install(patched, info, pkg, flatscreen, name, source or os.path.basename(apk_path)) return _install(apk_path, info, pkg, flatscreen, name, source) @@ -143,6 +158,8 @@ def _install(apk_path, info, pkg, flatscreen, name, source): raise FrameError(f'Steam did not return a shortcut id (got {reply[:80]!r})') meta = {'package': pkg, 'label': name or info['label'], 'version': info['version'], 'instance': iid, 'shortcut': shortcut, 'game_id': game_id(shortcut), + 'vr': info.get('vr', False), 'vr_issues': info.get('vr_issues', []), + 'launchable': info.get('launchable', False), 'patched': info.get('patched', []), 'flatscreen': flatscreen, 'installed': time.strftime('%Y-%m-%dT%H:%M:%S'), 'source': source or os.path.basename(apk_path)} _write_meta(d, meta) @@ -273,11 +290,46 @@ def probe(pkg, wait=20): 'container_up': ctr in running_instances()} +def patch(src, dst, add=None): + try: + info = apk_info(src) + with zipfile.ZipFile(src) as z: + original = frame_apk._read(z, 'AndroidManifest.xml', frame_apk.MAX_MANIFEST) + manifest = add_launcher_category(original) if info['vr_activity'] else original + if not info['launchable'] and not info['vr_activity']: + raise FrameError('APK has no MAIN/LAUNCHER or patchable VR activity') + repack(src, dst, replace={'AndroidManifest.xml': manifest}, add=add) + result = apk_info(dst) + result.pop('icon_png', None) + result['patched'] = ['launcher'] if manifest != original else [] + return result + except (OSError, ValueError, zipfile.BadZipFile, frame_apk.ApkError) as e: + raise FrameError(str(e)) from e + + def main(): cmd, *args = sys.argv[1:] or ['help'] try: if cmd == 'install': - r = install(args[0], flatscreen='--vr' not in args) + r = install(args[0], flatscreen=False if '--vr' in args else True if '--flat' in args else None) + elif cmd in ('info', 'describe'): + r = apk_info(args[0]) + r.pop('icon_png', None) + elif cmd == 'patch': + import argparse + parser = argparse.ArgumentParser(description='Patch and v2-sign an APK locally') + parser.add_argument('src') + parser.add_argument('dst') + parser.add_argument('--add', action='append', default=[], metavar='NAME=PATH') + opts = parser.parse_args(args) + additions = {} + for item in opts.add: + if '=' not in item: + raise FrameError('--add requires NAME=PATH') + entry, path = item.split('=', 1) + with open(path, 'rb') as f: + additions[entry] = f.read() + r = patch(opts.src, opts.dst, additions) elif cmd == 'list': r = list_apps() elif cmd in ('launch', 'stop', 'probe'): @@ -286,7 +338,7 @@ def main(): r = remove(args[0], keep_data='--keep-data' in args) else: sys.exit(__doc__) - except FrameError as e: + except (FrameError, OSError) as e: sys.exit(f'error: {e}') print(json.dumps(r, indent=1)) diff --git a/ui/frame_apk.py b/ui/frame_apk.py index 943af2a..541b321 100644 --- a/ui/frame_apk.py +++ b/ui/frame_apk.py @@ -10,7 +10,7 @@ import zipfile # android: attribute resource ids; names can be stripped by shrinkers, ids can't. ATTR = {0x01010001: 'label', 0x01010002: 'icon', 0x01010003: 'name', - 0x0101021b: 'versionCode', 0x0101021c: 'versionName', 0x0101020c: 'minSdkVersion'} + 0x01010024: 'value', 0x0101021b: 'versionCode', 0x0101021c: 'versionName', 0x0101020c: 'minSdkVersion'} T_REF, T_STRING, T_INT_DEC, T_INT_HEX = 0x01, 0x03, 0x10, 0x11 # APKs can come from websites (install links), so nothing read from one may be # unbounded. zipfile stops at a member's declared size, so checking it is enough. @@ -215,8 +215,11 @@ def apk_info(path): if 'AndroidManifest.xml' not in names: raise ApkError('not an APK: no AndroidManifest.xml') try: - elements = manifest_elements(_read(z, 'AndroidManifest.xml', MAX_MANIFEST)) + manifest_data = _read(z, 'AndroidManifest.xml', MAX_MANIFEST) + elements = manifest_elements(manifest_data) res = Resources(_read(z, 'resources.arsc', MAX_ARSC) if 'resources.arsc' in names else b'') + from frame_apk_vr import detection + vr_info = detection(manifest_data, names) except (struct.error, IndexError, zipfile.BadZipFile) as e: raise ApkError(f'could not read the APK manifest: {e}') tags = {} @@ -235,6 +238,7 @@ def apk_info(path): 'min_sdk': min_sdk[1] if min_sdk and min_sdk[0] in (T_INT_DEC, T_INT_HEX) else None, 'icon_png': None, } + info.update(vr_info) try: info['icon_png'] = _icon_png(z, names, _icons(app.get('icon'), res)) except Exception: # noqa: BLE001 - any unreadable icon just means no icon diff --git a/ui/frame_apk_sign.py b/ui/frame_apk_sign.py new file mode 100644 index 0000000..091820d --- /dev/null +++ b/ui/frame_apk_sign.py @@ -0,0 +1,356 @@ +"""Lossless ZIP repacking and APK v2 RSA/SHA-256 signing, Python 3.9 stdlib. + +Spec: https://source.android.com/docs/security/features/apksigning/v2 +Sections: APK Signing Block; APK Signature Scheme v2 Block; Integrity-protected +contents; Verification. No verity algorithm is used, so no verity padding. +""" +import hashlib +import io +import json +import math +import os +from pathlib import Path +import re +import secrets +import struct +import tempfile +import zipfile +import zlib + +import frame_host + +MAGIC = b'APK Sig Block 42' +V2 = 0x7109871a +ALG = 0x0103 +SHA256_DER = bytes.fromhex('3031300d060960864801650304020105000420') + + +def u32(n): + return struct.pack('= n: + raise ValueError('invalid RSA signature size/value') + actual = pow(int.from_bytes(sig, 'big'), e, n).to_bytes(size, 'big') + if actual != encoded_hash(data, size): + raise ValueError('RSA signature mismatch') + + +def certificate(key): + name = sequence(der(0x31, sequence(bytes.fromhex('0603550403'), der(12, b'Frame Control APK signer')))) + validity = sequence(der(0x17, b'200101000000Z'), der(0x18, b'21200101000000Z')) + tbs = sequence(der(0xa0, integer(2)), integer(1), CERT_ALG, name, validity, name, public_key(key)) + return sequence(tbs, CERT_ALG, der(3, b'\0' + rsa_sign(tbs, key))) + + +def _prime(bits): + small = (3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47) + while True: + n = secrets.randbits(bits) | (3 << (bits - 2)) | 1 + if any(n % p == 0 for p in small) or (n - 1) % 65537 == 0: + continue + d, s = n - 1, 0 + while d % 2 == 0: + d //= 2 + s += 1 + for _ in range(40): # Miller-Rabin error bound <= 2^-80 + x = pow(secrets.randbelow(n - 3) + 2, d, n) + if x in (1, n - 1): + continue + for _ in range(s - 1): + x = pow(x, 2, n) + if x == n - 1: + break + else: + break + else: + return n + + +def signing_key(path=None): + """Persistent identity in app data, never an evictable cache. Atomic publication. + + Hard-linking a fully written private temp file prevents concurrent first-use + callers from selecting different identities or reading a partial key. + """ + path = Path(path) if path is not None else frame_host.data_dir('apk-signing-key.json') + if not path.exists(): + p, q = _prime(1024), _prime(1024) + while q == p: + q = _prime(1024) + key = {'n': p * q, 'e': 65537, 'd': pow(65537, -1, math.lcm(p - 1, q - 1))} + path.parent.mkdir(parents=True, exist_ok=True) + fd, tmp = tempfile.mkstemp(prefix='.apk-key-', dir=str(path.parent)) + try: + with os.fdopen(fd, 'w') as f: + json.dump(key, f) + f.flush() + os.fsync(f.fileno()) + try: + os.link(tmp, path) + except FileExistsError: + pass + finally: + os.unlink(tmp) + os.chmod(path, 0o600) + key = json.loads(path.read_text()) + if key['n'].bit_length() != 2048 or key['e'] != 65537: + raise ValueError('invalid cached APK signing key') + rsa_verify(b'key check', rsa_sign(b'key check', key), key['n'], key['e']) + return key + + +def _eocd(data): + # ZIP comments can contain the EOCD signature; accept only an exact EOF fit. + for at in range(len(data) - 22, max(-1, len(data) - 65558), -1): + if data[at:at + 4] == b'PK\5\6' and at + 22 + struct.unpack_from(' len(data): + raise ValueError('truncated length prefix') + size = struct.unpack_from(' len(data): + raise ValueError('length prefix outside block') + result.append(data[off:off + size]) + off += size + return result + + +def _der_parts(data): + result, off = [], 0 + while off < len(data): + start = off + tag, size = data[off:off + 2] + off += 2 + if size & 128: + count = size & 127 + if not count or count > 4: + raise ValueError('invalid DER length') + size = int.from_bytes(data[off:off + count], 'big') + off += count + if off + size > len(data): + raise ValueError('truncated DER') + result.append((tag, data[off:off + size], data[start:off + size])) + off += size + return result + + +def _cert_key(cert): + outer = _der_parts(cert) + if len(outer) != 1 or outer[0][0] != 0x30: + raise ValueError('invalid certificate') + fields = _der_parts(outer[0][1]) + tbs = _der_parts(fields[0][1]) + spki = tbs[6 if tbs[0][0] == 0xa0 else 5][2] + pub = _der_parts(_der_parts(spki)[0][1]) + if pub[0][2] != RSA_ALG or pub[1][1][:1] != b'\0': + raise ValueError('certificate is not RSA') + numbers = _der_parts(_der_parts(pub[1][1][1:])[0][1]) + n, e = [int.from_bytes(item[1], 'big') for item in numbers] + return n, e, spki + + +def verify(path): + """Verify this v2-only format; return True or raise ValueError on corruption. + + A valid signature establishes integrity, not trust in the APK publisher. + """ + data = Path(path).read_bytes() + try: + eo, cd = _eocd(data) + if data[cd - 16:cd] != MAGIC: + raise ValueError('no APK signing block') + size = struct.unpack_from(' cd - 24: + raise ValueError('invalid signing pair') + ident = struct.unpack_from('= 0xffffffff: + raise ValueError('ZIP64 APKs are unsupported') + output.write(struct.pack('= 65535 or cd + len(directory) >= 0xffffffff: + raise ValueError('ZIP64 APKs are unsupported') + output.write(directory) + output.write(struct.pack('.zip, folder or .exe becomes a title in the Steam library. + +

Sideloaded titles

Loading…
@@ -1138,6 +1144,7 @@ function upload(file, mode) { xhr.setRequestHeader("X-Frame-UI", "1"); xhr.setRequestHeader("X-Filename", encodeURIComponent(file.name)); xhr.setRequestHeader("X-Mode", mode); + if (mode === "apk") xhr.setRequestHeader("X-APK-Display", $("apkDisplay").value); const bar = $("prog").firstElementChild; $("prog").style.display = "block"; bar.style.width = "0"; xhr.upload.onprogress = e => { if (e.lengthComputable) bar.style.width = (100 * e.loaded / e.total) + "%"; }; diff --git a/ui/server.py b/ui/server.py index 05661e3..cff2ed9 100755 --- a/ui/server.py +++ b/ui/server.py @@ -1337,10 +1337,16 @@ class Handler(BaseHTTPRequestHandler): if mode == "apk": ensure_master() try: - m = frame_android.install(str(dest), source=name) + display = self.headers.get("X-APK-Display", "auto") + if display not in ("auto", "flat", "vr"): + raise frame_android.FrameError("invalid APK display mode") + m = frame_android.install(str(dest), source=name, + flatscreen=None if display == "auto" else display == "flat") except frame_android.FrameError as e: raise Failure(str(e), 400) - return {"message": f"Installed {m['label']} as its own app in the Steam library", "app": m} + kind = "VR app" if not m['flatscreen'] else "app" + notes = " ".join(m.get("vr_issues", [])) + return {"message": f"Installed {m['label']} as its own {kind} in the Steam library. {notes}".strip(), "app": m} return {"message": push_file(dest)} finally: if not keep: From 0dbe18d824aad8f451748886c18c2f68946f9dfc Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 16:24:27 +1000 Subject: [PATCH 04/36] Add APK-local OpenXR 1.1 compatibility layer for Frame --- frame/openxr-compat/.gitattributes | 2 + frame/openxr-compat/.gitignore | 1 + frame/openxr-compat/CMakeLists.txt | 25 + frame/openxr-compat/README.md | 254 + .../XrApiLayer_FRAME_compat.json | 21 + frame/openxr-compat/build.sh | 12 + frame/openxr-compat/compat_logic.h | 87 + .../evidence/apk-verification.txt | 29 + frame/openxr-compat/evidence/ctest.txt | 9 + frame/openxr-compat/evidence/readelf.txt | 110 + frame/openxr-compat/layer.cpp | 272 + .../arm64-v8a/libXrApiLayer_FRAME_compat.so | Bin 0 -> 98248 bytes frame/openxr-compat/tests/compat_tests.cpp | 70 + frame/openxr-compat/tests/dispatch_tests.cpp | 134 + frame/openxr-compat/tests/shims/android/log.h | 9 + frame/openxr-compat/tests/shims/jni.h | 3 + frame/openxr-compat/vendor/LICENSE | 208 + .../openxr-compat/vendor/loader_interfaces.h | 114 + frame/openxr-compat/vendor/openxr/openxr.h | 14911 ++++++++++++++++ .../vendor/openxr/openxr_platform.h | 819 + .../vendor/openxr/openxr_platform_defines.h | 114 + 21 files changed, 17204 insertions(+) create mode 100644 frame/openxr-compat/.gitattributes create mode 100644 frame/openxr-compat/.gitignore create mode 100644 frame/openxr-compat/CMakeLists.txt create mode 100644 frame/openxr-compat/README.md create mode 100644 frame/openxr-compat/XrApiLayer_FRAME_compat.json create mode 100755 frame/openxr-compat/build.sh create mode 100644 frame/openxr-compat/compat_logic.h create mode 100644 frame/openxr-compat/evidence/apk-verification.txt create mode 100644 frame/openxr-compat/evidence/ctest.txt create mode 100644 frame/openxr-compat/evidence/readelf.txt create mode 100644 frame/openxr-compat/layer.cpp create mode 100755 frame/openxr-compat/prebuilt/arm64-v8a/libXrApiLayer_FRAME_compat.so create mode 100644 frame/openxr-compat/tests/compat_tests.cpp create mode 100644 frame/openxr-compat/tests/dispatch_tests.cpp create mode 100644 frame/openxr-compat/tests/shims/android/log.h create mode 100644 frame/openxr-compat/tests/shims/jni.h create mode 100644 frame/openxr-compat/vendor/LICENSE create mode 100644 frame/openxr-compat/vendor/loader_interfaces.h create mode 100644 frame/openxr-compat/vendor/openxr/openxr.h create mode 100644 frame/openxr-compat/vendor/openxr/openxr_platform.h create mode 100644 frame/openxr-compat/vendor/openxr/openxr_platform_defines.h diff --git a/frame/openxr-compat/.gitattributes b/frame/openxr-compat/.gitattributes new file mode 100644 index 0000000..15fc64b --- /dev/null +++ b/frame/openxr-compat/.gitattributes @@ -0,0 +1,2 @@ +# Preserve upstream headers byte-for-byte, including their existing whitespace. +vendor/** -whitespace diff --git a/frame/openxr-compat/.gitignore b/frame/openxr-compat/.gitignore new file mode 100644 index 0000000..cebbcdf --- /dev/null +++ b/frame/openxr-compat/.gitignore @@ -0,0 +1 @@ +/build-*/ diff --git a/frame/openxr-compat/CMakeLists.txt b/frame/openxr-compat/CMakeLists.txt new file mode 100644 index 0000000..6f75b6b --- /dev/null +++ b/frame/openxr-compat/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.22) +project(FrameXrCompat LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +add_library(compat_logic INTERFACE) +target_include_directories(compat_logic INTERFACE . vendor) +if(ANDROID) + add_library(XrApiLayer_FRAME_compat SHARED layer.cpp) + target_link_libraries(XrApiLayer_FRAME_compat PRIVATE compat_logic log) + target_compile_definitions(XrApiLayer_FRAME_compat PRIVATE XR_USE_PLATFORM_ANDROID XR_NO_PROTOTYPES) + target_compile_options(XrApiLayer_FRAME_compat PRIVATE -O2 -fvisibility=hidden -Wall -Wextra -Werror) + target_link_options(XrApiLayer_FRAME_compat PRIVATE -Wl,-z,max-page-size=16384 -Wl,--no-undefined -Wl,--exclude-libs,ALL -Wl,-s) +else() + enable_testing() + add_executable(compat_tests tests/compat_tests.cpp) + target_link_libraries(compat_tests PRIVATE compat_logic) + target_compile_options(compat_tests PRIVATE -Wall -Wextra -Werror) + add_test(NAME compat_logic COMMAND compat_tests) + add_executable(dispatch_tests tests/dispatch_tests.cpp) + target_link_libraries(dispatch_tests PRIVATE compat_logic) + target_include_directories(dispatch_tests PRIVATE tests/shims) + target_compile_definitions(dispatch_tests PRIVATE XR_USE_PLATFORM_ANDROID XR_NO_PROTOTYPES) + target_compile_options(dispatch_tests PRIVATE -Wall -Wextra -Werror) + add_test(NAME layer_dispatch COMMAND dispatch_tests) +endif() diff --git a/frame/openxr-compat/README.md b/frame/openxr-compat/README.md new file mode 100644 index 0000000..6c366c9 --- /dev/null +++ b/frame/openxr-compat/README.md @@ -0,0 +1,254 @@ +# Frame OpenXR compatibility API layer + +`XR_APILAYER_FRAME_compat` is an APK-local implicit API layer for Steam Frame's +OpenXR 1.0 Android runtime. It translates selected 1.1 functionality; it is not +a conformant replacement for a complete 1.1 runtime. Device facts are in +[../../docs/vr-apks.md](../../docs/vr-apks.md). No headset was contacted during +implementation. Loading, Valve-layer coexistence, controller usability, and +Wolvic startup/rendering on Lepton remain **unverified on device**. + +## Behavior + +- The negotiated `createApiLayerInstance` hook handles `xrCreateInstance`. + API requests >= 1.1 become `XR_API_VERSION_1_0` (1.0.63 with these headers), + rather than carrying an arbitrary application's patch into the 1.0 request. + Requests below 1.1 are unchanged. The loader may reject future API versions + before any layer runs. +- Enumerate downstream extensions through the **next layer's** GIPA before + creation. Only advertised promoted extensions are added, with duplicates + removed. All other application extensions remain, except the two stubs below. + Log added, stubbed and missing extensions with tag `FrameXrCompat`. +- Missing `XR_KHR_android_thread_settings` is advertised and accepted; + `xrSetAndroidApplicationThreadKHR` logs and returns success without changing + thread scheduling. Missing `XR_OCULUS_android_session_state_enable` is + advertised and accepted, with no commands or additional session behavior. + If downstream supports either extension, preserve it and its real commands. + Stubbing these capabilities is intentionally limited to accepting the app's + request; it does not provide Meta services. +- The manifest's `instance_extensions` makes both stubs visible during the + loader's pre-instance enumeration and validation. The layer also exposes + an enumeration implementation with count/capacity handling. Before a next + dispatch is available it can enumerate only its own extensions; normal app + global enumeration is performed by the loader, merging runtime and manifest + entries. See [loader_core.cpp L106–145](https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/release-1.1.63/src/loader/loader_core.cpp#L106) + and [manifest parsing L549–558](https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/release-1.1.63/src/loader/manifest_file.cpp#L549). +- Unknown missing extensions are never silently removed. Khronos may reject + them **before** entering the layer and name them in `OpenXR-Loader` logs + ([loader_instance.cpp L149–176](https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/release-1.1.63/src/loader/loader_instance.cpp#L149)). + If they reach this layer, it logs their names and preserves them for the + downstream failure. +- `xrLocateSpaces` dispatches to `xrLocateSpacesKHR` per session/instance. + This is the only command added in 1.1. The three relevant structure types + (`SPACES_LOCATE_INFO`, `SPACE_LOCATIONS`, `SPACE_VELOCITIES`) and their KHR + versions are **numeric aliases**, and the structs are typedef aliases in + these headers. Explicit type conversion of local input/output copies is + therefore an identity conversion; the velocity chain passes through without + mutating its types or linkage. Output arrays remain shared and receive the + runtime's results, including on error; caller struct headers remain intact. +- With palm pose enabled, rewrite both hand paths ending in + `/input/grip_surface/pose` to `/input/palm_ext/pose` in `xrStringToPath` and + in binding suggestions (including paths obtained before this layer rewrote + them). Unknown paths are left alone. +- `xrGetInstanceProperties` and unrelated commands pass through. The real + runtime's identity is not spoofed. Dispatch tables and session ownership are + locked, and removed after successful destruction. Downstream calls occur + outside the lock. + +## Spec basis and interaction profiles + +The pinned [1.1 promotions appendix, versions.adoc L16–156](https://github.com/KhronosGroup/OpenXR-Docs/blob/release-1.1.63/specification/sources/chapters/versions.adoc#L16) +includes a generated promotion list. Its source is the +[1.1.63 XML registry](https://github.com/KhronosGroup/OpenXR-Docs/blob/release-1.1.63/specification/registry/xr.xml), +selecting `extension[@promotedto='XR_VERSION_1_1']`. The full list implemented: + +| Promoted extension | Handling | +| --- | --- | +| `XR_KHR_locate_spaces` | Enable if advertised; core command alias | +| `XR_EXT_local_floor` | Enable if advertised; reference-space enum is an alias | +| `XR_EXT_uuid` | Enable if advertised; type alias, no commands | +| `XR_EXT_palm_pose` | Enable if advertised; rewrite grip-surface paths | +| `XR_VARJO_quad_views` | Enable if advertised; view-configuration enum alias, support remains optional | +| `XR_EXT_samsung_odyssey_controller` | Enable if advertised | +| `XR_EXT_hp_mixed_reality_controller` | Enable if advertised | +| `XR_HTC_vive_cosmos_controller_interaction` | Enable if advertised | +| `XR_HTC_vive_focus3_controller_interaction` | Enable if advertised | +| `XR_ML_ml2_controller_interaction` | Enable if advertised | +| `XR_FB_touch_controller_pro` | Enable if advertised; old profile still usable | +| `XR_META_touch_controller_plus` | Enable if advertised; old profile still usable | +| `XR_BD_controller_interaction` | Enable if advertised | +| `XR_KHR_maintenance1` | Enable if advertised (included in the pinned registry's promotion list) | + +`XR_EXT_hand_interaction` is **not** promoted to 1.1. Preserve it if requested; +it is not auto-enabled merely because the runtime advertises it. The parent +provided its availability; the device notes list extensions non-exhaustively. + +The registry's `XR_VERSION_1_1` feature introduces 13 profile paths. For +Samsung Odyssey, HP mixed reality, HTC Cosmos/Focus3, ML2 and the three +ByteDance Pico profiles, drop a whole suggestion call with success when the +corresponding extension is absent. Keep it when the extension is enabled. +Drop the five new Meta paths: `touch_pro_controller`, `touch_plus_controller`, +`touch_controller_rift_cv1`, `touch_controller_quest_1_rift_s`, and +`touch_controller_quest_2`. The verified runtime notes do not list these; +Pro/Plus promotion also renames several components, so the old extension's +presence alone does not prove support for the new profile. This layer does +not implement those component conversions. Existing old Pro/Plus profile +paths are passed through if the app uses them. + +Preserve the documented `oculus/touch_controller`, `khr/simple_controller`, +`valve/frame_controller` and original PC profile paths. The notes' "usual PC +controllers" is not a complete enumerated list: availability of promoted PC +profiles is inferred from the runtime's extension advertisement, not invented +from that phrase. OpenXR has no general profile enumeration API. Dropping +suggestions prevents an unsupported 1.1 profile from aborting initialization; +it does not create bindings, so the app must also suggest a supported fallback. + +## Headers and licensing + +The required public headers (`openxr.h`, `openxr_platform.h`, +`openxr_platform_defines.h`) are unmodified from +[OpenXR-SDK release-1.1.63](https://github.com/KhronosGroup/OpenXR-SDK/tree/release-1.1.63/include/openxr), +commit [f2448a8](https://github.com/KhronosGroup/OpenXR-SDK/commit/f2448a8797c85814aa892efc1ab8707900fbcc78). +The requested filename `loader_interfaces.h` no longer exists in 1.1 SDK +releases: negotiation was ratified and moved to `openxr_loader_negotiation.h` +in 1.0.33 ([spec history L166–186](https://github.com/KhronosGroup/OpenXR-Docs/blob/release-1.1.63/specification/sources/chapters/versions.adoc#L166)). +To retain that requested interface filename, the layer uses the unmodified, +ABI-compatible [last legacy header from SDK-Source release-1.0.32](https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/release-1.0.32/src/common/loader_interfaces.h). +Only these four headers are vendored. They offer Apache-2.0 OR MIT; this +vendoring uses Apache-2.0, reproduced in [vendor/LICENSE](vendor/LICENSE). + +## Android discovery and coexistence + +**Minimum asset-discovery loader release: 1.0.25 (2022-09-02).** Its +[release commit, 15c3d8e](https://github.com/KhronosGroup/OpenXR-SDK-Source/commit/15c3d8eb99994e5365d6b6f96eefaf4c51a65a9d) +explicitly announces APK-packaged API layers; +[manifest_file.cpp L665–723](https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/release-1.0.25/src/loader/manifest_file.cpp#L665) +implements discovery, and [L934–940](https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/release-1.0.25/src/loader/manifest_file.cpp#L934) +adds asset manifests after filesystem manifests. This is not a 1.1-only feature. + +**For an app requesting API 1.1, use a 1.1 loader (first release 1.1.36) or +newer.** A 1.0 loader rejects the request before the layer can downgrade it; +see [loader_core.cpp L217–230](https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/release-1.1.63/src/loader/loader_core.cpp#L217). +The runtime can remain 1.0. + +Source inspection at release-1.1.63 confirms: + +- [manifest_file.cpp L720–783](https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/release-1.1.63/src/loader/manifest_file.cpp#L720) + uses the initialized Android asset manager and scans + `openxr/1/api_layers/implicit.d/` for JSON. The application must initialize + the loader with its Android context (`xrInitializeLoaderKHR`). In the APK, + the entry is `assets/openxr/1/api_layers/implicit.d/XrApiLayer_FRAME_compat.json`. +- A bare `library_path`, as used here, is **not explicitly concatenated** with + `nativeLibraryDir`. [L878–900](https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/release-1.1.63/src/loader/manifest_file.cpp#L878) + leaves a bare name for normal dynamic-linker search in the application's + namespace (including its native library directory). Relative paths with a + slash use [LocateLibraryInAssets L943–952](https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/release-1.1.63/src/loader/manifest_file.cpp#L943), + which resolves against `GetAndroidNativeLibraryDir()`. + [loader_init_data.cpp L87–96](https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/release-1.1.63/src/loader/loader_init_data.cpp#L87) + obtains the asset manager and `ApplicationInfo.nativeLibraryDir` via JNI. + Wolvic's supplied manifest has `android:extractNativeLibs="true"`. +- [android_utilities.cpp L267–322](https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/release-1.1.63/src/loader/android_utilities.cpp#L267) + handles runtime broker discovery, not APK layer discovery. Its + `native_lib_dir + so_filename` and `dlopen` refer to the runtime package. + They must not be confused with the app's layer library resolution. +- [manifest_file.cpp L1008–1023](https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/release-1.1.63/src/loader/manifest_file.cpp#L1008) + retains filesystem manifests and then appends asset manifests. This leaves + `/vendor`'s `XR_APILAYER_VALVE_fdm_injection` discoverable. This layer advances + `nextInfo` exactly once, uses next-layer GIPA, preserves the rest of the + create-info chain and never opens `vrclient.so` directly. No environment + layer-list override or assumed ordering is needed. + +`DISABLE_FRAME_XR_COMPAT` is the manifest's disable environment variable. +Leave it unset to activate the implicit layer. No `enable_environment` is +required. + +## Rebuild and verify + +NDK **r30 / 30.0.16248370**, installed at +`~/Library/Android/ndk/30.0.16248370`, was the latest stable/LTS package shown +by the [Android download page](https://developer.android.com/ndk/downloads) +on 2026-09-28. Downloaded +[android-ndk-r30-darwin.dmg](https://dl.google.com/android/repository/android-ndk-r30-darwin.dmg) +(1,072,970,961 bytes). The page publishes SHA-1, not SHA-256: + +``` +expected: 48591224b6657f46eebbc9d95d4af09bbed8d107 +actual: 48591224b6657f46eebbc9d95d4af09bbed8d107 (PASS) +``` + +Mounted read-only with `hdiutil`; copied +`AndroidNDK16248370.app/Contents/NDK` into that version directory. Despite the +`darwin-x86_64` toolchain directory name, clang is universal arm64/x86_64 and +ran on this Apple Silicon Mac. + +```sh +frame/openxr-compat/build.sh +# Or set ANDROID_NDK_HOME to an installed NDK. +cmake -S frame/openxr-compat -B frame/openxr-compat/build-host -G Ninja +cmake --build frame/openxr-compat/build-host +ctest --test-dir frame/openxr-compat/build-host --output-on-failure +``` + +The script produces arm64-v8a / android-24, C++17, `-O2`, static libc++, hidden +internal symbols, stripped output, and `-Wl,-z,max-page-size=16384`. +The committed prebuilt is `prebuilt/arm64-v8a/libXrApiLayer_FRAME_compat.so`. +Its SHA-256 is recorded below with the APK checks. + +[Host test output](evidence/ctest.txt): two tests passed, covering pure logic +and the actual layer with a fake next layer and host-only log/JNI shims. +[Full llvm-readelf -d -s -l output](evidence/readelf.txt) records three LOAD +segments aligned `0x4000`, only `xrNegotiateLoaderApiLayerInterface` exported, +and only `libc.so`, `libm.so`, `libdl.so`, `liblog.so` as NEEDED libraries. +All 46 undefined imports are versioned `@LIBC` except `__android_log_print`. +There is no `libc++_shared.so` dependency and no unstripped `.symtab`. + +No independent model review was run: the task explicitly prohibits delegation. +No `CODING_STANDARDS.md` exists in this worktree or the primary worktree. + +## Wolvic injection artifact (Mac only) + +Copied `/tmp/vrapk/wq-dec` to `/tmp/vrapk/wq-layer-dec`; retained its existing +LAUNCHER fix and native-library extraction setting. Added only: + +``` +assets/openxr/1/api_layers/implicit.d/XrApiLayer_FRAME_compat.json +lib/arm64-v8a/libXrApiLayer_FRAME_compat.so +``` + +Built using `/tmp/vrapk/jdk/Contents/Home/bin/java` and +`/tmp/vrapk/apktool_3.0.3.jar`, then signed in place using +`/tmp/vrapk/uber-apk-signer-1.3.0.jar --overwrite` (embedded debug certificate): + +```sh +/tmp/vrapk/jdk/Contents/Home/bin/java -jar /tmp/vrapk/apktool_3.0.3.jar \ + b /tmp/vrapk/wq-layer-dec -o /tmp/vrapk/wolvic-quest-compat.apk +/tmp/vrapk/jdk/Contents/Home/bin/java -jar /tmp/vrapk/uber-apk-signer-1.3.0.jar \ + -a /tmp/vrapk/wolvic-quest-compat.apk --overwrite +/tmp/vrapk/jdk/Contents/Home/bin/java -jar /tmp/vrapk/uber-apk-signer-1.3.0.jar \ + -a /tmp/vrapk/wolvic-quest-compat.apk --onlyVerify +``` + +Final APK: `/tmp/vrapk/wolvic-quest-compat.apk` (not committed). +The bundled `lib/arm64-v8a/libopenxr_loader.so` is byte-for-byte unchanged. +Its strings include both `openxr/1/api_layers/implicit.d/` and +`openxr/1/api_layers/explicit.d/`, `AddManifestFilesAndroid` error messages, +and `xrLocateSpaces`. Thus it contains APK asset discovery and evidence of +1.1 command support. Its exact upstream release number was not established +from the binary; runtime execution of those paths remains unverified. + +Prebuilt library SHA-256: + +``` +57abbcec6fbc6df7c79e2b9a46db1a5d8613b4db3c0d7e49bf87cb3172280d10 +``` + +APK SHA-256: + +``` +355a681625e38b71563dcffecb031799eff27894d6ab910a659bace057e82c1f +``` + +Final `--onlyVerify` exited 0: zip alignment verified, v2/v3 signatures +verified, one APK processed and zero errors. ZIP readback confirmed that the +injected library and manifest match the committed inputs, the loader is +unchanged, and the binary Android manifest retains the LAUNCHER category. +See [APK verification evidence](evidence/apk-verification.txt). diff --git a/frame/openxr-compat/XrApiLayer_FRAME_compat.json b/frame/openxr-compat/XrApiLayer_FRAME_compat.json new file mode 100644 index 0000000..143d8e3 --- /dev/null +++ b/frame/openxr-compat/XrApiLayer_FRAME_compat.json @@ -0,0 +1,21 @@ +{ + "file_format_version": "1.0.0", + "api_layer": { + "name": "XR_APILAYER_FRAME_compat", + "library_path": "libXrApiLayer_FRAME_compat.so", + "api_version": "1.1", + "implementation_version": "1", + "description": "Steam Frame OpenXR 1.1 to 1.0 compatibility", + "disable_environment": "DISABLE_FRAME_XR_COMPAT", + "instance_extensions": [ + { + "name": "XR_KHR_android_thread_settings", + "extension_version": "6" + }, + { + "name": "XR_OCULUS_android_session_state_enable", + "extension_version": "1" + } + ] + } +} diff --git a/frame/openxr-compat/build.sh b/frame/openxr-compat/build.sh new file mode 100755 index 0000000..3b08089 --- /dev/null +++ b/frame/openxr-compat/build.sh @@ -0,0 +1,12 @@ +#!/bin/sh +set -eu +here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +ndk=${ANDROID_NDK_HOME:-"$HOME/Library/Android/ndk/30.0.16248370"} +cmake -S "$here" -B "$here/build-android" -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE="$ndk/build/cmake/android.toolchain.cmake" \ + -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-24 \ + -DANDROID_STL=c++_static -DCMAKE_BUILD_TYPE=Release +cmake --build "$here/build-android" +mkdir -p "$here/prebuilt/arm64-v8a" +cp "$here/build-android/libXrApiLayer_FRAME_compat.so" "$here/prebuilt/arm64-v8a/" +shasum -a 256 "$here/prebuilt/arm64-v8a/libXrApiLayer_FRAME_compat.so" diff --git a/frame/openxr-compat/compat_logic.h b/frame/openxr-compat/compat_logic.h new file mode 100644 index 0000000..a5c6e57 --- /dev/null +++ b/frame/openxr-compat/compat_logic.h @@ -0,0 +1,87 @@ +#pragma once +#include +#include +#include +#include + +namespace frame { +using Names = std::vector; +inline constexpr const char* layerName = "XR_APILAYER_FRAME_compat"; +inline constexpr const char* stubs[] = {"XR_KHR_android_thread_settings", "XR_OCULUS_android_session_state_enable"}; +// OpenXR-Docs release-1.1.63 registry: promotedto="XR_VERSION_1_1". +inline constexpr const char* promotions[] = { + "XR_KHR_locate_spaces", "XR_EXT_local_floor", "XR_EXT_uuid", "XR_EXT_palm_pose", + "XR_VARJO_quad_views", "XR_EXT_samsung_odyssey_controller", "XR_EXT_hp_mixed_reality_controller", + "XR_HTC_vive_cosmos_controller_interaction", "XR_HTC_vive_focus3_controller_interaction", + "XR_ML_ml2_controller_interaction", "XR_FB_touch_controller_pro", "XR_META_touch_controller_plus", + "XR_BD_controller_interaction", "XR_KHR_maintenance1"}; +inline bool has(const Names& names, const std::string& n) { + return std::find(names.begin(), names.end(), n) != names.end(); +} +inline bool downgrade(XrVersion v) { return v >= XR_MAKE_VERSION(1, 1, 0); } +inline XrVersion runtimeVersion(XrVersion v) { return downgrade(v) ? XR_API_VERSION_1_0 : v; } +struct ExtensionPlan { Names downstream, stubbed, missing, added; }; +inline ExtensionPlan extensions(const Names& requested, const Names& available, bool promote) { + ExtensionPlan p; + for (const auto& n : requested) { + if (!has(available, n) && (n == stubs[0] || n == stubs[1])) p.stubbed.push_back(n); + else { + if (!has(p.downstream, n)) p.downstream.push_back(n); + if (!has(available, n)) p.missing.push_back(n); + } + } + if (promote) for (auto n : promotions) if (has(available, n) && !has(p.downstream, n)) { + p.downstream.push_back(n); p.added.push_back(n); + } + return p; +} +inline std::string rewritePath(std::string p, bool palm) { + const std::string suffix = "/input/grip_surface/pose"; + if (palm && (p == "/user/hand/left" + suffix || p == "/user/hand/right" + suffix)) + p.replace(p.size() - suffix.size(), suffix.size(), "/input/palm_ext/pose"); + return p; +} +// Profiles added to 1.1. The three legacy Meta splits have no 1.0 extension. +// The renamed Pro/Plus profiles have different component semantics; do not +// claim the old extension implements the new profile merely because it exists. +inline bool dropProfile(const std::string& p, const Names& enabled) { + for (auto name : {"touch_pro_controller", "touch_plus_controller", "touch_controller_rift_cv1", + "touch_controller_quest_1_rift_s", "touch_controller_quest_2"}) + if (p == std::string("/interaction_profiles/meta/") + name) return true; + const char* profiles[][2] = { + {"samsung/odyssey_controller", "XR_EXT_samsung_odyssey_controller"}, + {"hp/mixed_reality_controller", "XR_EXT_hp_mixed_reality_controller"}, + {"htc/vive_cosmos_controller", "XR_HTC_vive_cosmos_controller_interaction"}, + {"htc/vive_focus3_controller", "XR_HTC_vive_focus3_controller_interaction"}, + {"ml/ml2_controller", "XR_ML_ml2_controller_interaction"}, + {"bytedance/pico_neo3_controller", "XR_BD_controller_interaction"}, + {"bytedance/pico4_controller", "XR_BD_controller_interaction"}, + {"bytedance/pico_g3_controller", "XR_BD_controller_interaction"}}; + for (auto& entry : profiles) if (p == std::string("/interaction_profiles/") + entry[0]) + return !has(enabled, entry[1]); + return false; +} +inline XrStructureType extensionType(XrStructureType t) { + switch (t) { + case XR_TYPE_SPACES_LOCATE_INFO: return XR_TYPE_SPACES_LOCATE_INFO_KHR; + case XR_TYPE_SPACE_LOCATIONS: return XR_TYPE_SPACE_LOCATIONS_KHR; + case XR_TYPE_SPACE_VELOCITIES: return XR_TYPE_SPACE_VELOCITIES_KHR; + default: return t; + } +} +inline XrResult locate(PFN_xrLocateSpacesKHR next, XrSession session, + const XrSpacesLocateInfo* info, XrSpaceLocations* locations) { + if (!info || !locations) return XR_ERROR_VALIDATION_FAILURE; + static_assert(XR_TYPE_SPACE_VELOCITIES == XR_TYPE_SPACE_VELOCITIES_KHR); + static_assert(XR_TYPE_SPACE_LOCATIONS == XR_TYPE_SPACE_LOCATIONS_KHR); + static_assert(XR_TYPE_SPACES_LOCATE_INFO == XR_TYPE_SPACES_LOCATE_INFO_KHR); + auto in = *info; + auto out = *locations; + in.type = extensionType(in.type); out.type = extensionType(out.type); + // The enums and typedefs are exact aliases. All chained velocities, + // including unknown next structures, can be forwarded without mutation. + const auto result = next(session, &in, &out); + locations->locationCount = out.locationCount; + return result; +} +} diff --git a/frame/openxr-compat/evidence/apk-verification.txt b/frame/openxr-compat/evidence/apk-verification.txt new file mode 100644 index 0000000..bfed1f5 --- /dev/null +++ b/frame/openxr-compat/evidence/apk-verification.txt @@ -0,0 +1,29 @@ +2026-09-28, Mac only; no device connection. +APK: /tmp/vrapk/wolvic-quest-compat.apk +SHA-256: 355a681625e38b71563dcffecb031799eff27894d6ab910a659bace057e82c1f + +Command: +/tmp/vrapk/jdk/Contents/Home/bin/java -jar /tmp/vrapk/uber-apk-signer-1.3.0.jar -a /tmp/vrapk/wolvic-quest-compat.apk --onlyVerify +Exit status: 0 +Relevant output: +- zipalign verified +- signature verified [v2, v3] +Successfully processed 1 APKs and 0 errors in 0.63 seconds. + +ZIP readback checks: PASS +- Injected JSON identical to frame/openxr-compat/XrApiLayer_FRAME_compat.json. +- Injected SO identical to prebuilt/arm64-v8a/libXrApiLayer_FRAME_compat.so. +- Existing libopenxr_loader.so identical to /tmp/vrapk/wq-dec version. +- Binary AndroidManifest.xml contains android.intent.category.LAUNCHER. + +Loader strings present: +openxr/1/api_layers/implicit.d/ +openxr/1/api_layers/explicit.d/ +ApiLayerManifestFile::AddManifestFilesAndroid unable to open asset +xrLocateSpaces + +NDK: 30.0.16248370 (r30) +DMG size: 1072970961 bytes +Download-page SHA-1: 48591224b6657f46eebbc9d95d4af09bbed8d107 +Measured SHA-1: 48591224b6657f46eebbc9d95d4af09bbed8d107 +SHA check: PASS diff --git a/frame/openxr-compat/evidence/ctest.txt b/frame/openxr-compat/evidence/ctest.txt new file mode 100644 index 0000000..3e3686d --- /dev/null +++ b/frame/openxr-compat/evidence/ctest.txt @@ -0,0 +1,9 @@ +Test project /Users/saphid/projects/steam-frame-xrlayer/frame/openxr-compat/build-host + Start 1: compat_logic +1/2 Test #1: compat_logic ..................... Passed 0.01 sec + Start 2: layer_dispatch +2/2 Test #2: layer_dispatch ................... Passed 0.42 sec + +100% tests passed out of 2 + +Total Test time (real) = 0.43 sec diff --git a/frame/openxr-compat/evidence/readelf.txt b/frame/openxr-compat/evidence/readelf.txt new file mode 100644 index 0000000..67a0dbe --- /dev/null +++ b/frame/openxr-compat/evidence/readelf.txt @@ -0,0 +1,110 @@ + +Elf file type is DYN (Shared object file) +Entry point 0x0 +There are 9 program headers, starting at offset 64 + +Program Headers: + Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align + PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x0001f8 0x0001f8 R 0x8 + LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x0169e0 0x0169e0 R E 0x4000 + LOAD 0x0169e0 0x000000000001a9e0 0x000000000001a9e0 0x000a90 0x001620 RW 0x4000 + LOAD 0x017470 0x000000000001f470 0x000000000001f470 0x000068 0x000bc0 RW 0x4000 + DYNAMIC 0x017048 0x000000000001b048 0x000000000001b048 0x0001c0 0x0001c0 RW 0x8 + GNU_RELRO 0x0169e0 0x000000000001a9e0 0x000000000001a9e0 0x000a90 0x001620 R 0x1 + GNU_EH_FRAME 0x004fb4 0x0000000000004fb4 0x0000000000004fb4 0x00070c 0x00070c R 0x4 + GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x0 + NOTE 0x000238 0x0000000000000238 0x0000000000000238 0x0000bc 0x0000bc R 0x4 + + Section to Segment mapping: + Segment Sections... + 00 + 01 .note.android.ident .note.gnu.build-id .dynsym .gnu.version .gnu.version_r .gnu.hash .dynstr .rela.dyn .rela.plt .gcc_except_table .rodata .eh_frame_hdr .eh_frame .text __lcxx_override .plt + 02 .data.rel.ro .fini_array .init_array .dynamic .got .got.plt .relro_padding + 03 .data .bss + 04 .dynamic + 05 .data.rel.ro .fini_array .init_array .dynamic .got .got.plt .relro_padding + 06 .eh_frame_hdr + 07 + 08 .note.android.ident .note.gnu.build-id + None .comment .shstrtab +Dynamic section at offset 0x17048 contains 28 entries: + Tag Type Name/Value + 0x0000000000000001 (NEEDED) Shared library: [liblog.so] + 0x0000000000000001 (NEEDED) Shared library: [libm.so] + 0x0000000000000001 (NEEDED) Shared library: [libdl.so] + 0x0000000000000001 (NEEDED) Shared library: [libc.so] + 0x000000000000000e (SONAME) Library soname: [libXrApiLayer_FRAME_compat.so] + 0x000000000000001e (FLAGS) BIND_NOW + 0x000000006ffffffb (FLAGS_1) NOW + 0x0000000000000007 (RELA) 0xae8 + 0x0000000000000008 (RELASZ) 5424 (bytes) + 0x0000000000000009 (RELAENT) 24 (bytes) + 0x000000006ffffff9 (RELACOUNT) 225 + 0x0000000000000017 (JMPREL) 0x2018 + 0x0000000000000002 (PLTRELSZ) 1080 (bytes) + 0x0000000000000003 (PLTGOT) 0x1b2f0 + 0x0000000000000014 (PLTREL) RELA + 0x0000000000000006 (SYMTAB) 0x2f8 + 0x000000000000000b (SYMENT) 24 (bytes) + 0x0000000000000005 (STRTAB) 0x838 + 0x000000000000000a (STRSZ) 681 (bytes) + 0x000000006ffffef5 (GNU_HASH) 0x818 + 0x0000000000000019 (INIT_ARRAY) 0x1b030 + 0x000000000000001b (INIT_ARRAYSZ) 24 (bytes) + 0x000000000000001a (FINI_ARRAY) 0x1b020 + 0x000000000000001c (FINI_ARRAYSZ) 16 (bytes) + 0x000000006ffffff0 (VERSYM) 0x778 + 0x000000006ffffffe (VERNEED) 0x7d8 + 0x000000006fffffff (VERNEEDNUM) 2 + 0x0000000000000000 (NULL) 0x0 + +Symbol table '.dynsym' contains 48 entries: + Num: Value Size Type Bind Vis Ndx Name + 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND + 1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __cxa_finalize@LIBC + 2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __cxa_atexit@LIBC + 3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strcmp@LIBC + 4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __android_log_print + 5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@LIBC + 6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memmove@LIBC + 7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strlen@LIBC + 8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memcpy@LIBC + 9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __strcpy_chk@LIBC + 10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memset@LIBC + 11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memcmp@LIBC + 12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_mutex_lock@LIBC + 13: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_mutex_unlock@LIBC + 14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __errno@LIBC + 15: 0000000000000000 0 FUNC GLOBAL DEFAULT UND snprintf@LIBC + 16: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_mutex_destroy@LIBC + 17: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strerror_r@LIBC + 18: 0000000000000000 0 FUNC GLOBAL DEFAULT UND abort@LIBC + 19: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_getspecific@LIBC + 20: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_setspecific@LIBC + 21: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_key_create@LIBC + 22: 0000000000000000 0 FUNC GLOBAL DEFAULT UND syscall@LIBC + 23: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND __sF@LIBC + 24: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fwrite@LIBC + 25: 0000000000000000 0 FUNC GLOBAL DEFAULT UND vfprintf@LIBC + 26: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fputc@LIBC + 27: 0000000000000000 0 FUNC GLOBAL DEFAULT UND vasprintf@LIBC + 28: 0000000000000000 0 FUNC GLOBAL DEFAULT UND android_set_abort_message@LIBC + 29: 0000000000000000 0 FUNC GLOBAL DEFAULT UND openlog@LIBC + 30: 0000000000000000 0 FUNC GLOBAL DEFAULT UND syslog@LIBC + 31: 0000000000000000 0 FUNC GLOBAL DEFAULT UND closelog@LIBC + 32: 0000000000000000 0 FUNC GLOBAL DEFAULT UND malloc@LIBC + 33: 0000000000000000 0 FUNC GLOBAL DEFAULT UND free@LIBC + 34: 0000000000000000 0 FUNC GLOBAL DEFAULT UND posix_memalign@LIBC + 35: 0000000000000000 0 FUNC GLOBAL DEFAULT UND realloc@LIBC + 36: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_once@LIBC + 37: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_key_delete@LIBC + 38: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getauxval@LIBC + 39: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __system_property_get@LIBC + 40: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strncmp@LIBC + 41: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fprintf@LIBC + 42: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fflush@LIBC + 43: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_rwlock_wrlock@LIBC + 44: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_rwlock_unlock@LIBC + 45: 0000000000000000 0 FUNC GLOBAL DEFAULT UND dl_iterate_phdr@LIBC + 46: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_rwlock_rdlock@LIBC + 47: 00000000000081a0 284 FUNC GLOBAL DEFAULT 14 xrNegotiateLoaderApiLayerInterface diff --git a/frame/openxr-compat/layer.cpp b/frame/openxr-compat/layer.cpp new file mode 100644 index 0000000..3d6f9df --- /dev/null +++ b/frame/openxr-compat/layer.cpp @@ -0,0 +1,272 @@ +#include "compat_logic.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG(...) __android_log_print(ANDROID_LOG_INFO, "FrameXrCompat", __VA_ARGS__) +#define GUARD_END catch (const std::bad_alloc&) { return XR_ERROR_OUT_OF_MEMORY; } catch (...) { return XR_ERROR_RUNTIME_FAILURE; } +namespace { +struct Dispatch { + PFN_xrGetInstanceProcAddr gipa{}; + PFN_xrDestroyInstance destroy{}; + PFN_xrCreateSession createSession{}; + PFN_xrDestroySession destroySession{}; + PFN_xrLocateSpacesKHR locate{}; + PFN_xrStringToPath stringToPath{}; + PFN_xrPathToString pathToString{}; + PFN_xrSuggestInteractionProfileBindings suggest{}; + PFN_xrSetAndroidApplicationThreadKHR thread{}; + frame::Names enabled, stubbed; + XrInstance instance{}; + bool promote{}, palm{}; +}; +std::mutex mutex; +std::unordered_map> instances; +std::unordered_map> sessions; +// No runtime library is opened here: every call goes through the next layer. +PFN_xrEnumerateInstanceExtensionProperties enumerateNext{}; +template T proc(PFN_xrGetInstanceProcAddr gipa, XrInstance i, const char* name) { + PFN_xrVoidFunction f{}; + if (XR_FAILED(gipa(i, name, &f))) return nullptr; + return reinterpret_cast(f); +} +std::shared_ptr get(XrInstance i) { + std::lock_guard lock(mutex); + auto it = instances.find(i); return it == instances.end() ? nullptr : it->second; +} +std::shared_ptr get(XrSession s) { + std::lock_guard lock(mutex); + auto it = sessions.find(s); return it == sessions.end() ? nullptr : it->second; +} +XrResult properties(PFN_xrEnumerateInstanceExtensionProperties fn, + std::vector& out) { + if (!fn) return XR_ERROR_FUNCTION_UNSUPPORTED; + for (int attempt = 0; attempt < 4; ++attempt) { + uint32_t n{}; + auto r = fn(nullptr, 0, &n, nullptr); + if (XR_FAILED(r)) return r; + out.assign(n, {XR_TYPE_EXTENSION_PROPERTIES, nullptr, {}, 0}); + if (!n) return XR_SUCCESS; + r = fn(nullptr, n, &n, out.data()); + if (r == XR_ERROR_SIZE_INSUFFICIENT) continue; + if (XR_FAILED(r)) return r; + out.resize(n); return XR_SUCCESS; + } + return XR_ERROR_RUNTIME_FAILURE; +} +XrResult XRAPI_CALL enumerate(const char* layer, uint32_t capacity, uint32_t* count, + XrExtensionProperties* output) try { + if (!count || (capacity && !output)) return XR_ERROR_VALIDATION_FAILURE; + const bool own = layer && std::strcmp(layer, frame::layerName) == 0; + PFN_xrEnumerateInstanceExtensionProperties next; + { std::lock_guard lock(mutex); next = enumerateNext; } + if (layer && *layer && !own) return next ? next(layer, capacity, count, output) : XR_ERROR_API_LAYER_NOT_PRESENT; + std::vector all; + if (!own && next) { + auto r = properties(next, all); if (XR_FAILED(r)) return r; + } + for (unsigned i = 0; i < 2; ++i) { + bool found = false; + for (auto& e : all) if (!std::strcmp(e.extensionName, frame::stubs[i])) found = true; + if (!found) { + XrExtensionProperties p{XR_TYPE_EXTENSION_PROPERTIES, nullptr, {}, i == 0 ? XR_KHR_android_thread_settings_SPEC_VERSION : 1u}; + std::strcpy(p.extensionName, frame::stubs[i]); all.push_back(p); + } + } + *count = static_cast(all.size()); + LOG("enumerate extensions: %u (includes stubs)", *count); + if (!capacity) return XR_SUCCESS; + if (capacity < all.size()) return XR_ERROR_SIZE_INSUFFICIENT; + for (size_t i = 0; i < all.size(); ++i) { + if (output[i].type != XR_TYPE_EXTENSION_PROPERTIES) return XR_ERROR_VALIDATION_FAILURE; + std::strcpy(output[i].extensionName, all[i].extensionName); + output[i].extensionVersion = all[i].extensionVersion; + } + return XR_SUCCESS; +} GUARD_END +XrResult XRAPI_CALL destroyInstance(XrInstance i) try { + auto d = get(i); if (!d) return XR_ERROR_HANDLE_INVALID; + auto r = d->destroy(i); + if (XR_SUCCEEDED(r)) { + std::lock_guard lock(mutex); + for (auto it = sessions.begin(); it != sessions.end();) { + if (it->second == d) it = sessions.erase(it); else ++it; + } + instances.erase(i); + if (instances.empty()) enumerateNext = nullptr; + } + return r; +} GUARD_END +XrResult XRAPI_CALL createSession(XrInstance i, const XrSessionCreateInfo* info, XrSession* s) try { + auto d = get(i); if (!d) return XR_ERROR_HANDLE_INVALID; + auto r = d->createSession(i, info, s); + if (XR_SUCCEEDED(r)) { + try { std::lock_guard lock(mutex); sessions.emplace(*s, d); } + catch (...) { d->destroySession(*s); *s = XR_NULL_HANDLE; throw; } + } + return r; +} GUARD_END +XrResult XRAPI_CALL destroySession(XrSession s) try { + auto d = get(s); if (!d) return XR_ERROR_HANDLE_INVALID; + auto r = d->destroySession(s); + if (XR_SUCCEEDED(r)) { std::lock_guard lock(mutex); sessions.erase(s); } + return r; +} GUARD_END +XrResult XRAPI_CALL locateSpaces(XrSession s, const XrSpacesLocateInfo* in, XrSpaceLocations* out) try { + auto d = get(s); if (!d) return XR_ERROR_HANDLE_INVALID; + if (!d->locate) return XR_ERROR_FUNCTION_UNSUPPORTED; + LOG("xrLocateSpaces -> xrLocateSpacesKHR (core/KHR type aliases)"); + return frame::locate(d->locate, s, in, out); +} GUARD_END +XrResult XRAPI_CALL threadSettings(XrSession s, XrAndroidThreadTypeKHR type, uint32_t tid) try { + auto d = get(s); if (!d) return XR_ERROR_HANDLE_INVALID; + if (frame::has(d->stubbed, frame::stubs[0])) { + LOG("stub xrSetAndroidApplicationThreadKHR type=%d tid=%u -> XR_SUCCESS (no scheduling change)", type, tid); + return XR_SUCCESS; + } + return d->thread ? d->thread(s, type, tid) : XR_ERROR_FUNCTION_UNSUPPORTED; +} GUARD_END +XrResult XRAPI_CALL stringToPath(XrInstance i, const char* path, XrPath* out) try { + auto d = get(i); if (!d) return XR_ERROR_HANDLE_INVALID; + if (!path) return XR_ERROR_VALIDATION_FAILURE; + auto rewritten = frame::rewritePath(path, d->palm); + if (rewritten != path) LOG("path %s -> %s", path, rewritten.c_str()); + return d->stringToPath(i, rewritten.c_str(), out); +} GUARD_END +XrResult pathString(const Dispatch& d, XrPath path, std::string& out) { + uint32_t n{}; + auto r = d.pathToString(d.instance, path, 0, &n, nullptr); + if (XR_FAILED(r)) return r; + std::vector text(n); + r = d.pathToString(d.instance, path, n, &n, text.data()); + if (XR_SUCCEEDED(r)) out.assign(text.data()); + return r; +} +XrResult XRAPI_CALL suggest(XrInstance i, const XrInteractionProfileSuggestedBinding* info) try { + auto d = get(i); if (!d) return XR_ERROR_HANDLE_INVALID; + if (!info || (info->countSuggestedBindings && !info->suggestedBindings)) return XR_ERROR_VALIDATION_FAILURE; + std::string profile; + auto r = pathString(*d, info->interactionProfile, profile); + if (XR_FAILED(r)) return r; + if (d->promote && frame::dropProfile(profile, d->enabled)) { + LOG("drop unsupported 1.1 interaction profile %s (%u suggestions)", profile.c_str(), info->countSuggestedBindings); + return XR_SUCCESS; + } + auto copy = *info; + std::vector bindings; + for (uint32_t index = 0; index < info->countSuggestedBindings; ++index) { + auto b = info->suggestedBindings[index]; + std::string path; + r = pathString(*d, b.binding, path); if (XR_FAILED(r)) return r; + auto rewritten = frame::rewritePath(path, d->palm); + if (rewritten != path) { + LOG("binding %s -> %s", path.c_str(), rewritten.c_str()); + r = d->stringToPath(i, rewritten.c_str(), &b.binding); if (XR_FAILED(r)) return r; + } + bindings.push_back(b); + } + copy.suggestedBindings = bindings.data(); + return d->suggest(i, ©); +} GUARD_END +XrResult XRAPI_CALL gipa(XrInstance, const char*, PFN_xrVoidFunction*); +XrResult XRAPI_CALL createLayer(const XrInstanceCreateInfo* info, const XrApiLayerCreateInfo* layer, + XrInstance* instance) try { + if (!info || !instance || !layer || layer->structType != XR_LOADER_INTERFACE_STRUCT_API_LAYER_CREATE_INFO || + layer->structVersion != XR_API_LAYER_CREATE_INFO_STRUCT_VERSION || layer->structSize != sizeof(*layer) || + !layer->nextInfo || layer->nextInfo->structType != XR_LOADER_INTERFACE_STRUCT_API_LAYER_NEXT_INFO || + layer->nextInfo->structVersion != XR_API_LAYER_NEXT_INFO_STRUCT_VERSION || + layer->nextInfo->structSize != sizeof(XrApiLayerNextInfo) || + !layer->nextInfo->nextGetInstanceProcAddr || !layer->nextInfo->nextCreateApiLayerInstance || + (info->enabledExtensionCount && !info->enabledExtensionNames)) return XR_ERROR_INITIALIZATION_FAILED; + *instance = XR_NULL_HANDLE; + auto next = layer->nextInfo->nextGetInstanceProcAddr; + auto enumFn = proc(next, XR_NULL_HANDLE, "xrEnumerateInstanceExtensionProperties"); + std::vector available; + auto r = properties(enumFn, available); if (XR_FAILED(r)) { LOG("runtime extension enumeration failed: %d", r); return r; } + frame::Names requested, supported; + for (auto& p : available) supported.emplace_back(p.extensionName); + for (uint32_t n = 0; n < info->enabledExtensionCount; ++n) { + if (!info->enabledExtensionNames[n]) return XR_ERROR_VALIDATION_FAILURE; + requested.emplace_back(info->enabledExtensionNames[n]); + } + auto d = std::make_shared(); + d->promote = frame::downgrade(info->applicationInfo.apiVersion); + auto plan = frame::extensions(requested, supported, d->promote); + for (auto& n : plan.added) LOG("enable promoted extension %s", n.c_str()); + for (auto& n : plan.stubbed) LOG("emulate missing extension %s; remove downstream", n.c_str()); + for (auto& n : plan.missing) LOG("unsupported extension %s retained; runtime must reject", n.c_str()); + auto copy = *info; + copy.applicationInfo.apiVersion = frame::runtimeVersion(info->applicationInfo.apiVersion); + if (d->promote) LOG("API %u.%u.%u -> 1.0.%u", unsigned(XR_VERSION_MAJOR(info->applicationInfo.apiVersion)), + unsigned(XR_VERSION_MINOR(info->applicationInfo.apiVersion)), unsigned(XR_VERSION_PATCH(info->applicationInfo.apiVersion)), + unsigned(XR_VERSION_PATCH(copy.applicationInfo.apiVersion))); + std::vector names; + for (auto& n : plan.downstream) names.push_back(n.c_str()); + copy.enabledExtensionCount = static_cast(names.size()); copy.enabledExtensionNames = names.data(); + auto chain = *layer; chain.nextInfo = layer->nextInfo->next; + r = layer->nextInfo->nextCreateApiLayerInstance(©, &chain, instance); + if (XR_FAILED(r)) { LOG("downstream xrCreateInstance -> %d", r); return r; } + d->gipa = next; d->instance = *instance; + d->destroy = proc(next, *instance, "xrDestroyInstance"); + // A successful runtime instance must expose xrDestroyInstance. + if (!d->destroy) { LOG("invalid downstream: missing xrDestroyInstance"); return XR_ERROR_INITIALIZATION_FAILED; } + try { + d->enabled = std::move(plan.downstream); d->stubbed = std::move(plan.stubbed); + d->palm = frame::has(d->enabled, "XR_EXT_palm_pose"); +#define LOAD(field, name) d->field = proc(next, *instance, #name) + LOAD(createSession, xrCreateSession); LOAD(destroySession, xrDestroySession); + LOAD(stringToPath, xrStringToPath); LOAD(pathToString, xrPathToString); + LOAD(suggest, xrSuggestInteractionProfileBindings); + if (frame::has(d->enabled, "XR_KHR_locate_spaces")) LOAD(locate, xrLocateSpacesKHR); + if (frame::has(d->enabled, frame::stubs[0])) LOAD(thread, xrSetAndroidApplicationThreadKHR); +#undef LOAD + if (!d->createSession || !d->destroySession || !d->stringToPath || !d->pathToString || !d->suggest) { + d->destroy(*instance); *instance = XR_NULL_HANDLE; return XR_ERROR_INITIALIZATION_FAILED; + } + std::lock_guard lock(mutex); + instances.emplace(*instance, d); enumerateNext = enumFn; + } catch (...) { d->destroy(*instance); *instance = XR_NULL_HANDLE; throw; } + LOG("created instance; next-layer dispatch retained (Valve layer coexists)"); + return r; +} GUARD_END +XrResult XRAPI_CALL gipa(XrInstance i, const char* name, PFN_xrVoidFunction* out) try { + if (!name || !out) return XR_ERROR_VALIDATION_FAILURE; + *out = nullptr; +#define RETURN_PROC(n, fn) if (!std::strcmp(name, n)) { *out = reinterpret_cast(fn); return XR_SUCCESS; } + RETURN_PROC("xrGetInstanceProcAddr", gipa) + RETURN_PROC("xrCreateApiLayerInstance", createLayer) + RETURN_PROC("xrEnumerateInstanceExtensionProperties", enumerate) + auto d = get(i); if (!d) return XR_ERROR_HANDLE_INVALID; + RETURN_PROC("xrDestroyInstance", destroyInstance) + RETURN_PROC("xrCreateSession", createSession) + RETURN_PROC("xrDestroySession", destroySession) + RETURN_PROC("xrStringToPath", stringToPath) + RETURN_PROC("xrSuggestInteractionProfileBindings", suggest) + if (d->locate && d->promote) { RETURN_PROC("xrLocateSpaces", locateSpaces) } + if (frame::has(d->stubbed, frame::stubs[0])) { RETURN_PROC("xrSetAndroidApplicationThreadKHR", threadSettings) } +#undef RETURN_PROC + // Includes xrGetInstanceProperties: report the actual runtime's identity. + return d->gipa(i, name, out); +} GUARD_END +} // namespace +extern "C" __attribute__((visibility("default"))) XrResult XRAPI_CALL xrNegotiateLoaderApiLayerInterface( + const XrNegotiateLoaderInfo* loader, const char* name, XrNegotiateApiLayerRequest* request) { + if (!loader || !name || !request || std::strcmp(name, frame::layerName) || + loader->structType != XR_LOADER_INTERFACE_STRUCT_LOADER_INFO || + loader->structVersion != XR_LOADER_INFO_STRUCT_VERSION || loader->structSize != sizeof(*loader) || + request->structType != XR_LOADER_INTERFACE_STRUCT_API_LAYER_REQUEST || + request->structVersion != XR_API_LAYER_INFO_STRUCT_VERSION || request->structSize != sizeof(*request) || + loader->minInterfaceVersion > 1 || loader->maxInterfaceVersion < 1 || + loader->minApiVersion > XR_CURRENT_API_VERSION || loader->maxApiVersion < XR_MAKE_VERSION(1, 1, 0)) + return XR_ERROR_INITIALIZATION_FAILED; + request->layerInterfaceVersion = 1; + request->layerApiVersion = std::min(XR_CURRENT_API_VERSION, loader->maxApiVersion); + request->getInstanceProcAddr = gipa; request->createApiLayerInstance = createLayer; + LOG("negotiated interface 1, API 1.1"); + return XR_SUCCESS; +} diff --git a/frame/openxr-compat/prebuilt/arm64-v8a/libXrApiLayer_FRAME_compat.so b/frame/openxr-compat/prebuilt/arm64-v8a/libXrApiLayer_FRAME_compat.so new file mode 100755 index 0000000000000000000000000000000000000000..e58d223fe68c2b5c12c44da3652ca3ab908612a9 GIT binary patch literal 98248 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 V=C~;XbFTy6zx2p$fN-W^`2*{Un3Dhi literal 0 HcmV?d00001 diff --git a/frame/openxr-compat/tests/compat_tests.cpp b/frame/openxr-compat/tests/compat_tests.cpp new file mode 100644 index 0000000..addf345 --- /dev/null +++ b/frame/openxr-compat/tests/compat_tests.cpp @@ -0,0 +1,70 @@ +#include "compat_logic.h" +#include +#include +#include +#define CHECK(expr) do { if (!(expr)) { std::cerr << __LINE__ << ": " #expr "\n"; std::exit(1); } } while (false) +namespace { +void* sentinel = reinterpret_cast(0x1234); +XrResult XRAPI_CALL fakeLocate(XrSession, const XrSpacesLocateInfo* in, XrSpaceLocations* out) { + CHECK(in->type == XR_TYPE_SPACES_LOCATE_INFO_KHR); + CHECK(out->type == XR_TYPE_SPACE_LOCATIONS_KHR); + CHECK(in->next == sentinel); + auto* velocities = static_cast(out->next); + CHECK(velocities->type == XR_TYPE_SPACE_VELOCITIES_KHR); + CHECK(velocities->next == sentinel); + CHECK(in->spaceCount == 1 && in->time == 123); + out->locations[0].pose.position.x = 42; + velocities->velocities[0].linearVelocity.y = 7; + return XR_SUCCESS; +} +} +int main() { + using namespace frame; + CHECK(!downgrade(XR_MAKE_VERSION(1, 0, 99))); + CHECK(runtimeVersion(XR_MAKE_VERSION(1, 0, 42)) == XR_MAKE_VERSION(1, 0, 42)); + CHECK(runtimeVersion(XR_MAKE_VERSION(1, 1, 0)) == XR_API_VERSION_1_0); + CHECK(runtimeVersion(XR_MAKE_VERSION(1, 1, 999)) == XR_API_VERSION_1_0); + CHECK(runtimeVersion(XR_MAKE_VERSION(2, 0, 0)) == XR_API_VERSION_1_0); + Names available{"XR_KHR_locate_spaces", "XR_EXT_palm_pose", "XR_EXT_hand_interaction", "XR_VARJO_quad_views"}; + Names requested{stubs[0], stubs[1], "XR_MISSING_test", "XR_EXT_palm_pose"}; + auto p = extensions(requested, available, true); + CHECK(p.stubbed.size() == 2 && p.missing == Names{"XR_MISSING_test"}); + CHECK(has(p.downstream, "XR_MISSING_test")); + CHECK(has(p.downstream, "XR_KHR_locate_spaces") && has(p.downstream, "XR_VARJO_quad_views")); + CHECK(!has(p.downstream, "XR_EXT_hand_interaction")); + CHECK(!has(p.downstream, "XR_EXT_local_floor")); + CHECK(std::count(p.downstream.begin(), p.downstream.end(), "XR_EXT_palm_pose") == 1); + available.push_back(stubs[0]); + CHECK(extensions(requested, available, true).stubbed == Names{stubs[1]}); + CHECK(extensions({}, available, false).downstream.empty()); + CHECK(rewritePath("/user/hand/left/input/grip_surface/pose", true) == "/user/hand/left/input/palm_ext/pose"); + CHECK(rewritePath("/user/hand/right/input/grip_surface/pose", true) == "/user/hand/right/input/palm_ext/pose"); + CHECK(rewritePath("/user/hand/left/input/grip_surface/pose", false) == "/user/hand/left/input/grip_surface/pose"); + CHECK(rewritePath("/user/hand/left/input/grip_surface/pose/extra", true) == "/user/hand/left/input/grip_surface/pose/extra"); + CHECK(rewritePath("/user/hand/left/input/grip/pose", true) == "/user/hand/left/input/grip/pose"); + CHECK(dropProfile("/interaction_profiles/meta/touch_pro_controller", {})); + CHECK(dropProfile("/interaction_profiles/meta/touch_controller_quest_2", {})); + CHECK(dropProfile("/interaction_profiles/hp/mixed_reality_controller", {})); + CHECK(!dropProfile("/interaction_profiles/hp/mixed_reality_controller", {"XR_EXT_hp_mixed_reality_controller"})); + CHECK(!dropProfile("/interaction_profiles/oculus/touch_controller", {})); + CHECK(!dropProfile("/interaction_profiles/khr/simple_controller", {})); + CHECK(!dropProfile("/interaction_profiles/valve/frame_controller", {})); + CHECK(!dropProfile("/interaction_profiles/ext/hand_interaction_ext", {})); + CHECK(extensionType(XR_TYPE_SPACES_LOCATE_INFO) == XR_TYPE_SPACES_LOCATE_INFO_KHR); + CHECK(extensionType(XR_TYPE_SPACE_LOCATIONS) == XR_TYPE_SPACE_LOCATIONS_KHR); + CHECK(extensionType(XR_TYPE_SPACE_VELOCITIES) == XR_TYPE_SPACE_VELOCITIES_KHR); + CHECK(extensionType(XR_TYPE_INSTANCE_CREATE_INFO) == XR_TYPE_INSTANCE_CREATE_INFO); + static_assert(std::is_same_v); + XrSpace space{}; + XrSpaceLocationData data{}; + XrSpaceVelocityData velocity{}; + XrSpaceVelocities velocities{XR_TYPE_SPACE_VELOCITIES, sentinel, 1, &velocity}; + XrSpaceLocations locations{XR_TYPE_SPACE_LOCATIONS, &velocities, 1, &data}; + XrSpacesLocateInfo info{XR_TYPE_SPACES_LOCATE_INFO, sentinel, XR_NULL_HANDLE, 123, 1, &space}; + CHECK(locate(fakeLocate, XR_NULL_HANDLE, &info, &locations) == XR_SUCCESS); + CHECK(data.pose.position.x == 42 && velocity.linearVelocity.y == 7); + CHECK(info.next == sentinel && locations.next == &velocities && velocities.next == sentinel); + CHECK(info.type == XR_TYPE_SPACES_LOCATE_INFO && locations.type == XR_TYPE_SPACE_LOCATIONS && velocities.type == XR_TYPE_SPACE_VELOCITIES); + CHECK(locate(fakeLocate, XR_NULL_HANDLE, nullptr, &locations) == XR_ERROR_VALIDATION_FAILURE); + std::cout << "All version, extension, path/profile and locate-chain checks passed\n"; +} diff --git a/frame/openxr-compat/tests/dispatch_tests.cpp b/frame/openxr-compat/tests/dispatch_tests.cpp new file mode 100644 index 0000000..97aa48b --- /dev/null +++ b/frame/openxr-compat/tests/dispatch_tests.cpp @@ -0,0 +1,134 @@ +// Compile the actual layer with host-only JNI/logging shims, and a fake next +// layer. This exercises ABI routing without a runtime or a headset. +#include "../layer.cpp" +#include +#include +#define CHECK(expr) do { if (!(expr)) { std::cerr << __LINE__ << ": " #expr "\n"; std::exit(1); } } while (false) +namespace { +uintptr_t serial = 100; +frame::Names seenExtensions; +XrVersion seenVersion{}; +XrApiLayerNextInfo* seenNext{}; +int suggestionCalls{}, locateCalls{}, realThreadCalls{}; +bool nativeThread{}; +std::unordered_map paths; +XrResult XRAPI_CALL fakeEnumerate(const char*, uint32_t capacity, uint32_t* count, XrExtensionProperties* out) { + const char* extensions[] = {"XR_KHR_locate_spaces", "XR_EXT_palm_pose", frame::stubs[0]}; + *count = nativeThread ? 3 : 2; + if (!capacity) return XR_SUCCESS; + if (capacity < *count) return XR_ERROR_SIZE_INSUFFICIENT; + for (uint32_t j = 0; j < *count; ++j) { std::strcpy(out[j].extensionName, extensions[j]); out[j].extensionVersion = 1; } + return XR_SUCCESS; +} +XrResult XRAPI_CALL fakeCreate(const XrInstanceCreateInfo* in, const XrApiLayerCreateInfo* layer, XrInstance* i) { + seenVersion = in->applicationInfo.apiVersion; seenNext = layer->nextInfo; + seenExtensions.clear(); + for (uint32_t j = 0; j < in->enabledExtensionCount; ++j) seenExtensions.emplace_back(in->enabledExtensionNames[j]); + if (frame::has(seenExtensions, "XR_MISSING_test")) return XR_ERROR_EXTENSION_NOT_PRESENT; + *i = reinterpret_cast(++serial); return XR_SUCCESS; +} +XrResult XRAPI_CALL fakeDestroy(XrInstance) { return XR_SUCCESS; } +XrResult XRAPI_CALL fakeCreateSession(XrInstance, const XrSessionCreateInfo*, XrSession* s) { + *s = reinterpret_cast(++serial); return XR_SUCCESS; +} +XrResult XRAPI_CALL fakeDestroySession(XrSession) { return XR_SUCCESS; } +XrResult XRAPI_CALL fakeString(XrInstance, const char* text, XrPath* p) { + *p = ++serial; paths[*p] = text; return XR_SUCCESS; +} +XrResult XRAPI_CALL fakePath(XrInstance, XrPath p, uint32_t cap, uint32_t* count, char* out) { + if (!paths.count(p)) return XR_ERROR_PATH_INVALID; + *count = static_cast(paths[p].size() + 1); + if (!cap) return XR_SUCCESS; + if (cap < *count) return XR_ERROR_SIZE_INSUFFICIENT; + std::strcpy(out, paths[p].c_str()); return XR_SUCCESS; +} +XrResult XRAPI_CALL fakeSuggest(XrInstance, const XrInteractionProfileSuggestedBinding* info) { + ++suggestionCalls; + if (info->countSuggestedBindings) CHECK(paths[info->suggestedBindings[0].binding] == "/user/hand/left/input/palm_ext/pose"); + return XR_SUCCESS; +} +XrResult XRAPI_CALL fakeLocate(XrSession, const XrSpacesLocateInfo*, XrSpaceLocations*) { ++locateCalls; return XR_SUCCESS; } +XrResult XRAPI_CALL fakeThread(XrSession, XrAndroidThreadTypeKHR, uint32_t) { ++realThreadCalls; return XR_TIMEOUT_EXPIRED; } +XrResult XRAPI_CALL fakeProperties(XrInstance, XrInstanceProperties*) { return XR_SUCCESS; } +XrResult XRAPI_CALL fakeGipa(XrInstance, const char* n, PFN_xrVoidFunction* out) { +#define MAP(name, f) if (!std::strcmp(n, name)) { *out = reinterpret_cast(f); return XR_SUCCESS; } + MAP("xrEnumerateInstanceExtensionProperties", fakeEnumerate) + MAP("xrDestroyInstance", fakeDestroy) + MAP("xrCreateSession", fakeCreateSession) MAP("xrDestroySession", fakeDestroySession) + MAP("xrStringToPath", fakeString) MAP("xrPathToString", fakePath) + MAP("xrSuggestInteractionProfileBindings", fakeSuggest) MAP("xrLocateSpacesKHR", fakeLocate) + MAP("xrSetAndroidApplicationThreadKHR", fakeThread) MAP("xrGetInstanceProperties", fakeProperties) +#undef MAP + *out = nullptr; return XR_ERROR_FUNCTION_UNSUPPORTED; +} +} +int main() { + XrNegotiateLoaderInfo loader{XR_LOADER_INTERFACE_STRUCT_LOADER_INFO, 1, sizeof(XrNegotiateLoaderInfo), 1, 1, + XR_MAKE_VERSION(1,0,0), XR_MAKE_VERSION(1,1023,4095)}; + XrNegotiateApiLayerRequest request{}; + request.structType = XR_LOADER_INTERFACE_STRUCT_API_LAYER_REQUEST; request.structVersion = 1; request.structSize = sizeof(request); + CHECK(xrNegotiateLoaderApiLayerInterface(&loader, frame::layerName, &request) == XR_SUCCESS); + CHECK(request.getInstanceProcAddr && request.createApiLayerInstance); + loader.maxApiVersion = XR_MAKE_VERSION(1,1,0); + CHECK(xrNegotiateLoaderApiLayerInterface(&loader, frame::layerName, &request) == XR_SUCCESS); + CHECK(request.layerApiVersion == loader.maxApiVersion); + loader.maxApiVersion = XR_MAKE_VERSION(1,0,99); + CHECK(xrNegotiateLoaderApiLayerInterface(&loader, frame::layerName, &request) == XR_ERROR_INITIALIZATION_FAILED); + loader.maxApiVersion = XR_MAKE_VERSION(1,1023,4095); + CHECK(xrNegotiateLoaderApiLayerInterface(&loader, "wrong", &request) == XR_ERROR_INITIALIZATION_FAILED); + XrApiLayerNextInfo tail{}; + XrApiLayerNextInfo next{XR_LOADER_INTERFACE_STRUCT_API_LAYER_NEXT_INFO, 1, sizeof(XrApiLayerNextInfo), {}, fakeGipa, fakeCreate, &tail}; + XrApiLayerCreateInfo layer{XR_LOADER_INTERFACE_STRUCT_API_LAYER_CREATE_INFO, 1, sizeof(XrApiLayerCreateInfo), nullptr, {}, &next}; + XrInstanceCreateInfo info{XR_TYPE_INSTANCE_CREATE_INFO, nullptr, 0, {}, 0, nullptr, 2, frame::stubs}; + info.applicationInfo.apiVersion = XR_MAKE_VERSION(1,1,999); + XrInstance a{}, b{}; + CHECK(createLayer(&info, &layer, &a) == XR_SUCCESS); + CHECK(seenVersion == XR_API_VERSION_1_0 && info.applicationInfo.apiVersion == XR_MAKE_VERSION(1,1,999)); + CHECK(seenNext == &tail && layer.nextInfo == &next); + CHECK(!frame::has(seenExtensions, frame::stubs[0]) && frame::has(seenExtensions, "XR_KHR_locate_spaces")); + XrSession sa{}, sb{}; + CHECK(createSession(a, nullptr, &sa) == XR_SUCCESS); + CHECK(threadSettings(sa, XR_ANDROID_THREAD_TYPE_APPLICATION_MAIN_KHR, 1) == XR_SUCCESS); + nativeThread = true; + CHECK(createLayer(&info, &layer, &b) == XR_SUCCESS); + CHECK(frame::has(seenExtensions, frame::stubs[0])); + CHECK(createSession(b, nullptr, &sb) == XR_SUCCESS); + PFN_xrVoidFunction fn{}; + CHECK(gipa(b, "xrSetAndroidApplicationThreadKHR", &fn) == XR_SUCCESS); + CHECK(reinterpret_cast(fn)(sb, XR_ANDROID_THREAD_TYPE_APPLICATION_MAIN_KHR, 1) == XR_TIMEOUT_EXPIRED); + CHECK(realThreadCalls == 1); + CHECK(threadSettings(sa, XR_ANDROID_THREAD_TYPE_APPLICATION_MAIN_KHR, 1) == XR_SUCCESS); + CHECK(gipa(a, "xrGetInstanceProperties", &fn) == XR_SUCCESS && fn == reinterpret_cast(fakeProperties)); + CHECK(gipa(a, "xrLocateSpaces", &fn) == XR_SUCCESS); + XrSpacesLocateInfo locateInfo{XR_TYPE_SPACES_LOCATE_INFO, nullptr, XR_NULL_HANDLE, 1, 0, nullptr}; + XrSpaceLocations locations{XR_TYPE_SPACE_LOCATIONS, nullptr, 0, nullptr}; + CHECK(reinterpret_cast(fn)(sa, &locateInfo, &locations) == XR_SUCCESS && locateCalls == 1); + XrPath binding{}, profile{}; + CHECK(stringToPath(a, "/user/hand/left/input/grip_surface/pose", &binding) == XR_SUCCESS); + CHECK(paths[binding] == "/user/hand/left/input/palm_ext/pose"); + fakeString(a, "/user/hand/left/input/grip_surface/pose", &binding); // preexisting path: rewrite in suggest too + fakeString(a, "/interaction_profiles/oculus/touch_controller", &profile); + XrActionSuggestedBinding action{XR_NULL_HANDLE, binding}; + XrInteractionProfileSuggestedBinding suggested{XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING, nullptr, profile, 1, &action}; + CHECK(suggest(a, &suggested) == XR_SUCCESS && suggestionCalls == 1); + CHECK(paths[action.binding] == "/user/hand/left/input/grip_surface/pose"); + fakeString(a, "/interaction_profiles/meta/touch_pro_controller", &suggested.interactionProfile); + CHECK(suggest(a, &suggested) == XR_SUCCESS && suggestionCalls == 1); + uint32_t count{}; + CHECK(enumerate(frame::layerName, 0, &count, nullptr) == XR_SUCCESS && count == 2); + XrExtensionProperties props[2]{{XR_TYPE_EXTENSION_PROPERTIES, nullptr, {}, 0}, {XR_TYPE_EXTENSION_PROPERTIES, nullptr, {}, 0}}; + CHECK(enumerate(frame::layerName, 1, &count, props) == XR_ERROR_SIZE_INSUFFICIENT); + CHECK(enumerate(frame::layerName, 2, &count, props) == XR_SUCCESS && count == 2); + CHECK(enumerate(nullptr, 0, &count, nullptr) == XR_SUCCESS && count == 4); + const char* missing = "XR_MISSING_test"; + info.enabledExtensionCount = 1; info.enabledExtensionNames = &missing; + XrInstance failed{}; + CHECK(createLayer(&info, &layer, &failed) == XR_ERROR_EXTENSION_NOT_PRESENT && failed == XR_NULL_HANDLE); + CHECK(destroySession(sa) == XR_SUCCESS); + CHECK(threadSettings(sa, XR_ANDROID_THREAD_TYPE_APPLICATION_MAIN_KHR, 1) == XR_ERROR_HANDLE_INVALID); + CHECK(destroyInstance(a) == XR_SUCCESS); + CHECK(get(sb) && !get(a)); + CHECK(destroyInstance(b) == XR_SUCCESS && !get(sb)); + CHECK(instances.empty() && sessions.empty() && enumerateNext == nullptr); + std::cout << "Negotiation, next-layer chaining, multi-instance dispatch, stubs, forwarding and cleanup passed\n"; +} diff --git a/frame/openxr-compat/tests/shims/android/log.h b/frame/openxr-compat/tests/shims/android/log.h new file mode 100644 index 0000000..dcaf894 --- /dev/null +++ b/frame/openxr-compat/tests/shims/android/log.h @@ -0,0 +1,9 @@ +#pragma once +// Host-only logcat replacement. Android builds use the NDK header and liblog. +#include +#include +#define ANDROID_LOG_INFO 4 +inline int __android_log_print(int, const char*, const char* fmt, ...) { + va_list args; va_start(args, fmt); auto r = std::vfprintf(stderr, fmt, args); + va_end(args); std::fputc('\n', stderr); return r; +} diff --git a/frame/openxr-compat/tests/shims/jni.h b/frame/openxr-compat/tests/shims/jni.h new file mode 100644 index 0000000..6223bc2 --- /dev/null +++ b/frame/openxr-compat/tests/shims/jni.h @@ -0,0 +1,3 @@ +#pragma once +// Only the opaque jobject declaration is needed by openxr_platform.h. +typedef void* jobject; diff --git a/frame/openxr-compat/vendor/LICENSE b/frame/openxr-compat/vendor/LICENSE new file mode 100644 index 0000000..527a83a --- /dev/null +++ b/frame/openxr-compat/vendor/LICENSE @@ -0,0 +1,208 @@ +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, +AND DISTRIBUTION + + 1. Definitions. + + + +"License" shall mean the terms and conditions for use, reproduction, and distribution +as defined by Sections 1 through 9 of this document. + + + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + + + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct +or indirect, to cause the direction or management of such entity, whether +by contract or otherwise, or (ii) ownership of fifty percent (50%) or more +of the outstanding shares, or (iii) beneficial ownership of such entity. + + + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions +granted by this License. + + + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + + + +"Object" form shall mean any form resulting from mechanical transformation +or translation of a Source form, including but not limited to compiled object +code, generated documentation, and conversions to other media types. + + + +"Work" shall mean the work of authorship, whether in Source or Object form, +made available under the License, as indicated by a copyright notice that +is included in or attached to the work (an example is provided in the Appendix +below). + + + +"Derivative Works" shall mean any work, whether in Source or Object form, +that is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative +Works shall not include works that remain separable from, or merely link (or +bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative +Works thereof, that is intentionally submitted to Licensor for inclusion in +the Work by the copyright owner or by an individual or Legal Entity authorized +to submit on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication +sent to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor +for the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + + + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently incorporated +within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this +License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable copyright license to reproduce, prepare +Derivative Works of, publicly display, publicly perform, sublicense, and distribute +the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, +each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) patent +license to make, have made, use, offer to sell, sell, import, and otherwise +transfer the Work, where such license applies only to those patent claims +licensable by such Contributor that are necessarily infringed by their Contribution(s) +alone or by combination of their Contribution(s) with the Work to which such +Contribution(s) was submitted. If You institute patent litigation against +any entity (including a cross-claim or counterclaim in a lawsuit) alleging +that the Work or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses granted to You +under this License for that Work shall terminate as of the date such litigation +is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or +Derivative Works thereof in any medium, with or without modifications, and +in Source or Object form, provided that You meet the following conditions: + +(a) You must give any other recipients of the Work or Derivative Works a copy +of this License; and + +(b) You must cause any modified files to carry prominent notices stating that +You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source +form of the Work, excluding those notices that do not pertain to any part +of the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its distribution, +then any Derivative Works that You distribute must include a readable copy +of the attribution notices contained within such NOTICE file, excluding those +notices that do not pertain to any part of the Derivative Works, in at least +one of the following places: within a NOTICE text file distributed as part +of the Derivative Works; within the Source form or documentation, if provided +along with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works +that You distribute, alongside or as an addendum to the NOTICE text from the +Work, provided that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, +or distribution of Your modifications, or for any such Derivative Works as +a whole, provided Your use, reproduction, and distribution of the Work otherwise +complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any +Contribution intentionally submitted for inclusion in the Work by You to the +Licensor shall be under the terms and conditions of this License, without +any additional terms or conditions. Notwithstanding the above, nothing herein +shall supersede or modify the terms of any separate license agreement you +may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, +trademarks, service marks, or product names of the Licensor, except as required +for reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to +in writing, Licensor provides the Work (and each Contributor provides its +Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied, including, without limitation, any warranties +or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR +A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness +of using or redistributing the Work and assume any risks associated with Your +exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether +in tort (including negligence), contract, or otherwise, unless required by +applicable law (such as deliberate and grossly negligent acts) or agreed to +in writing, shall any Contributor be liable to You for damages, including +any direct, indirect, special, incidental, or consequential damages of any +character arising as a result of this License or out of the use or inability +to use the Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all other commercial +damages or losses), even if such Contributor has been advised of the possibility +of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work +or Derivative Works thereof, You may choose to offer, and charge a fee for, +acceptance of support, warranty, indemnity, or other liability obligations +and/or rights consistent with this License. However, in accepting such obligations, +You may act only on Your own behalf and on Your sole responsibility, not on +behalf of any other Contributor, and only if You agree to indemnify, defend, +and hold each Contributor harmless for any liability incurred by, or claims +asserted against, such Contributor by reason of your accepting any such warranty +or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own identifying +information. (Don't include the brackets!) The text should be enclosed in +the appropriate comment syntax for the file format. We also recommend that +a file or class name and description of purpose be included on the same "printed +page" as the copyright notice for easier identification within third-party +archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. diff --git a/frame/openxr-compat/vendor/loader_interfaces.h b/frame/openxr-compat/vendor/loader_interfaces.h new file mode 100644 index 0000000..020c345 --- /dev/null +++ b/frame/openxr-compat/vendor/loader_interfaces.h @@ -0,0 +1,114 @@ +// Copyright (c) 2017-2023, The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG, Inc. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// Initial Author: Mark Young +// + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Forward declare. +typedef struct XrApiLayerCreateInfo XrApiLayerCreateInfo; + +// Function pointer prototype for the xrCreateApiLayerInstance function used in place of xrCreateInstance. +// This function allows us to pass special API layer information to each layer during the process of creating an Instance. +typedef XrResult(XRAPI_PTR *PFN_xrCreateApiLayerInstance)(const XrInstanceCreateInfo *info, + const XrApiLayerCreateInfo *apiLayerInfo, XrInstance *instance); + +// Loader/API Layer Interface versions +// 1 - First version, introduces negotiation structure and functions +#define XR_CURRENT_LOADER_API_LAYER_VERSION 1 + +// Loader/Runtime Interface versions +// 1 - First version, introduces negotiation structure and functions +#define XR_CURRENT_LOADER_RUNTIME_VERSION 1 + +// Version negotiation values +typedef enum XrLoaderInterfaceStructs { + XR_LOADER_INTERFACE_STRUCT_UNINTIALIZED = 0, + XR_LOADER_INTERFACE_STRUCT_LOADER_INFO, + XR_LOADER_INTERFACE_STRUCT_API_LAYER_REQUEST, + XR_LOADER_INTERFACE_STRUCT_RUNTIME_REQUEST, + XR_LOADER_INTERFACE_STRUCT_API_LAYER_CREATE_INFO, + XR_LOADER_INTERFACE_STRUCT_API_LAYER_NEXT_INFO, +} XrLoaderInterfaceStructs; + +#define XR_LOADER_INFO_STRUCT_VERSION 1 +typedef struct XrNegotiateLoaderInfo { + XrLoaderInterfaceStructs structType; // XR_LOADER_INTERFACE_STRUCT_LOADER_INFO + uint32_t structVersion; // XR_LOADER_INFO_STRUCT_VERSION + size_t structSize; // sizeof(XrNegotiateLoaderInfo) + uint32_t minInterfaceVersion; + uint32_t maxInterfaceVersion; + XrVersion minApiVersion; + XrVersion maxApiVersion; +} XrNegotiateLoaderInfo; + +#define XR_API_LAYER_INFO_STRUCT_VERSION 1 +typedef struct XrNegotiateApiLayerRequest { + XrLoaderInterfaceStructs structType; // XR_LOADER_INTERFACE_STRUCT_API_LAYER_REQUEST + uint32_t structVersion; // XR_API_LAYER_INFO_STRUCT_VERSION + size_t structSize; // sizeof(XrNegotiateApiLayerRequest) + uint32_t layerInterfaceVersion; // CURRENT_LOADER_API_LAYER_VERSION + XrVersion layerApiVersion; + PFN_xrGetInstanceProcAddr getInstanceProcAddr; + PFN_xrCreateApiLayerInstance createApiLayerInstance; +} XrNegotiateApiLayerRequest; + +#define XR_RUNTIME_INFO_STRUCT_VERSION 1 +typedef struct XrNegotiateRuntimeRequest { + XrLoaderInterfaceStructs structType; // XR_LOADER_INTERFACE_STRUCT_RUNTIME_REQUEST + uint32_t structVersion; // XR_RUNTIME_INFO_STRUCT_VERSION + size_t structSize; // sizeof(XrNegotiateRuntimeRequest) + uint32_t runtimeInterfaceVersion; // CURRENT_LOADER_RUNTIME_VERSION + XrVersion runtimeApiVersion; + PFN_xrGetInstanceProcAddr getInstanceProcAddr; +} XrNegotiateRuntimeRequest; + +// Function used to negotiate an interface betewen the loader and an API layer. Each library exposing one or +// more API layers needs to expose at least this function. +typedef XrResult(XRAPI_PTR *PFN_xrNegotiateLoaderApiLayerInterface)(const XrNegotiateLoaderInfo *loaderInfo, + const char *apiLayerName, + XrNegotiateApiLayerRequest *apiLayerRequest); + +// Function used to negotiate an interface betewen the loader and a runtime. Each runtime should expose +// at least this function. +typedef XrResult(XRAPI_PTR *PFN_xrNegotiateLoaderRuntimeInterface)(const XrNegotiateLoaderInfo *loaderInfo, + XrNegotiateRuntimeRequest *runtimeRequest); + +// Forward declare. +typedef struct XrApiLayerNextInfo XrApiLayerNextInfo; + +#define XR_API_LAYER_NEXT_INFO_STRUCT_VERSION 1 +struct XrApiLayerNextInfo { + XrLoaderInterfaceStructs structType; // XR_LOADER_INTERFACE_STRUCT_API_LAYER_NEXT_INFO + uint32_t structVersion; // XR_API_LAYER_NEXT_INFO_STRUCT_VERSION + size_t structSize; // sizeof(XrApiLayerNextInfo) + char layerName[XR_MAX_API_LAYER_NAME_SIZE]; // Name of API layer which should receive this info + PFN_xrGetInstanceProcAddr nextGetInstanceProcAddr; // Pointer to next API layer's xrGetInstanceProcAddr + PFN_xrCreateApiLayerInstance nextCreateApiLayerInstance; // Pointer to next API layer's xrCreateApiLayerInstance + XrApiLayerNextInfo *next; // Pointer to the next API layer info in the sequence +}; + +#define XR_API_LAYER_MAX_SETTINGS_PATH_SIZE 512 +#define XR_API_LAYER_CREATE_INFO_STRUCT_VERSION 1 +typedef struct XrApiLayerCreateInfo { + XrLoaderInterfaceStructs structType; // XR_LOADER_INTERFACE_STRUCT_API_LAYER_CREATE_INFO + uint32_t structVersion; // XR_API_LAYER_CREATE_INFO_STRUCT_VERSION + size_t structSize; // sizeof(XrApiLayerCreateInfo) + void *loaderInstance; // Pointer to the LoaderInstance class + char settings_file_location[XR_API_LAYER_MAX_SETTINGS_PATH_SIZE]; // Location to the found settings file (or empty '\0') + XrApiLayerNextInfo *nextInfo; // Pointer to the next API layer's Info +} XrApiLayerCreateInfo; + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/frame/openxr-compat/vendor/openxr/openxr.h b/frame/openxr-compat/vendor/openxr/openxr.h new file mode 100644 index 0000000..af56c0f --- /dev/null +++ b/frame/openxr-compat/vendor/openxr/openxr.h @@ -0,0 +1,14911 @@ +#ifndef OPENXR_H_ +#define OPENXR_H_ 1 + +/* +** Copyright 2017-2026 The Khronos Group Inc. +** +** SPDX-License-Identifier: Apache-2.0 OR MIT +*/ + +/* +** This header is generated from the Khronos OpenXR XML API Registry. +** +*/ + + +#ifdef __cplusplus +extern "C" { +#endif + + + +// XR_VERSION_1_0 is a preprocessor guard. Do not pass it to API calls. +#define XR_VERSION_1_0 1 +#include "openxr_platform_defines.h" +#define XR_MAKE_VERSION(major, minor, patch) \ + ((((major) & 0xffffULL) << 48) | (((minor) & 0xffffULL) << 32) | ((patch) & 0xffffffffULL)) + +// OpenXR current version number. +#define XR_CURRENT_API_VERSION XR_MAKE_VERSION(1, 1, 63) + +// OpenXR 1.0 version number +#define XR_API_VERSION_1_0 XR_MAKE_VERSION(1, 0, XR_VERSION_PATCH(XR_CURRENT_API_VERSION)) + +#define XR_VERSION_MAJOR(version) (uint16_t)(((uint64_t)(version) >> 48)& 0xffffULL) +#define XR_VERSION_MINOR(version) (uint16_t)(((uint64_t)(version) >> 32) & 0xffffULL) +#define XR_VERSION_PATCH(version) (uint32_t)((uint64_t)(version) & 0xffffffffULL) + +#define XR_MIN_COMPOSITION_LAYERS_SUPPORTED 16 + + +#if !defined(XR_NULL_HANDLE) +#if (XR_PTR_SIZE == 8) && XR_CPP_NULLPTR_SUPPORTED + #define XR_NULL_HANDLE nullptr +#else + #define XR_NULL_HANDLE 0 +#endif +#endif + + +#define XR_NULL_SYSTEM_ID 0 + + +#define XR_NULL_PATH 0 + + +#define XR_SUCCEEDED(result) ((result) >= 0) + + +#define XR_FAILED(result) ((result) < 0) + + +#define XR_UNQUALIFIED_SUCCESS(result) ((result) == 0) + + +#define XR_NO_DURATION 0 + + +#define XR_INFINITE_DURATION 0x7fffffffffffffffLL + + +#define XR_MIN_HAPTIC_DURATION -1 + + +#define XR_FREQUENCY_UNSPECIFIED 0 + + +#define XR_MAX_EVENT_DATA_SIZE sizeof(XrEventDataBuffer) + + +#define XR_EXTENSION_ENUM_BASE 1000000000 + + +#define XR_EXTENSION_ENUM_STRIDE 1000 + + +#if !defined(XR_MAY_ALIAS) +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4)) +#define XR_MAY_ALIAS __attribute__((__may_alias__)) +#else +#define XR_MAY_ALIAS +#endif +#endif + + +#if !defined(XR_DEFINE_HANDLE) +#if (XR_PTR_SIZE == 8) + #define XR_DEFINE_HANDLE(object) typedef struct object##_T* object; +#else + #define XR_DEFINE_HANDLE(object) typedef uint64_t object; +#endif +#endif + + +#if !defined(XR_DEFINE_OPAQUE_64) + #if (XR_PTR_SIZE == 8) + #define XR_DEFINE_OPAQUE_64(object) typedef struct object##_T* object; + #else + #define XR_DEFINE_OPAQUE_64(object) typedef uint64_t object; + #endif +#endif + + +#if !defined(XR_DEFINE_ATOM) + #define XR_DEFINE_ATOM(object) typedef uint64_t object; +#endif + +typedef uint64_t XrVersion; +typedef uint64_t XrFlags64; +XR_DEFINE_ATOM(XrSystemId) +typedef uint32_t XrBool32; +XR_DEFINE_ATOM(XrPath) +typedef int64_t XrTime; +typedef int64_t XrDuration; +XR_DEFINE_HANDLE(XrInstance) +XR_DEFINE_HANDLE(XrSession) +XR_DEFINE_HANDLE(XrSpace) +XR_DEFINE_HANDLE(XrAction) +XR_DEFINE_HANDLE(XrSwapchain) +XR_DEFINE_HANDLE(XrActionSet) +#define XR_TRUE 1 +#define XR_FALSE 0 +#define XR_MAX_EXTENSION_NAME_SIZE 128 +#define XR_MAX_API_LAYER_NAME_SIZE 256 +#define XR_MAX_API_LAYER_DESCRIPTION_SIZE 256 +#define XR_MAX_SYSTEM_NAME_SIZE 256 +#define XR_MAX_APPLICATION_NAME_SIZE 128 +#define XR_MAX_ENGINE_NAME_SIZE 128 +#define XR_MAX_RUNTIME_NAME_SIZE 128 +#define XR_MAX_PATH_LENGTH 256 +#define XR_MAX_STRUCTURE_NAME_SIZE 64 +#define XR_MAX_RESULT_STRING_SIZE 64 +#define XR_MAX_ACTION_SET_NAME_SIZE 64 +#define XR_MAX_LOCALIZED_ACTION_SET_NAME_SIZE 128 +#define XR_MAX_ACTION_NAME_SIZE 64 +#define XR_MAX_LOCALIZED_ACTION_NAME_SIZE 128 + +typedef enum XrResult { + XR_SUCCESS = 0, + XR_TIMEOUT_EXPIRED = 1, + XR_SESSION_LOSS_PENDING = 3, + XR_EVENT_UNAVAILABLE = 4, + XR_SPACE_BOUNDS_UNAVAILABLE = 7, + XR_SESSION_NOT_FOCUSED = 8, + XR_FRAME_DISCARDED = 9, + XR_ERROR_VALIDATION_FAILURE = -1, + XR_ERROR_RUNTIME_FAILURE = -2, + XR_ERROR_OUT_OF_MEMORY = -3, + XR_ERROR_API_VERSION_UNSUPPORTED = -4, + XR_ERROR_INITIALIZATION_FAILED = -6, + XR_ERROR_FUNCTION_UNSUPPORTED = -7, + XR_ERROR_FEATURE_UNSUPPORTED = -8, + XR_ERROR_EXTENSION_NOT_PRESENT = -9, + XR_ERROR_LIMIT_REACHED = -10, + XR_ERROR_SIZE_INSUFFICIENT = -11, + XR_ERROR_HANDLE_INVALID = -12, + XR_ERROR_INSTANCE_LOST = -13, + XR_ERROR_SESSION_RUNNING = -14, + XR_ERROR_SESSION_NOT_RUNNING = -16, + XR_ERROR_SESSION_LOST = -17, + XR_ERROR_SYSTEM_INVALID = -18, + XR_ERROR_PATH_INVALID = -19, + XR_ERROR_PATH_COUNT_EXCEEDED = -20, + XR_ERROR_PATH_FORMAT_INVALID = -21, + XR_ERROR_PATH_UNSUPPORTED = -22, + XR_ERROR_LAYER_INVALID = -23, + XR_ERROR_LAYER_LIMIT_EXCEEDED = -24, + XR_ERROR_SWAPCHAIN_RECT_INVALID = -25, + XR_ERROR_SWAPCHAIN_FORMAT_UNSUPPORTED = -26, + XR_ERROR_ACTION_TYPE_MISMATCH = -27, + XR_ERROR_SESSION_NOT_READY = -28, + XR_ERROR_SESSION_NOT_STOPPING = -29, + XR_ERROR_TIME_INVALID = -30, + XR_ERROR_REFERENCE_SPACE_UNSUPPORTED = -31, + XR_ERROR_FILE_ACCESS_ERROR = -32, + XR_ERROR_FILE_CONTENTS_INVALID = -33, + XR_ERROR_FORM_FACTOR_UNSUPPORTED = -34, + XR_ERROR_FORM_FACTOR_UNAVAILABLE = -35, + XR_ERROR_API_LAYER_NOT_PRESENT = -36, + XR_ERROR_CALL_ORDER_INVALID = -37, + XR_ERROR_GRAPHICS_DEVICE_INVALID = -38, + XR_ERROR_POSE_INVALID = -39, + XR_ERROR_INDEX_OUT_OF_RANGE = -40, + XR_ERROR_VIEW_CONFIGURATION_TYPE_UNSUPPORTED = -41, + XR_ERROR_ENVIRONMENT_BLEND_MODE_UNSUPPORTED = -42, + XR_ERROR_NAME_DUPLICATED = -44, + XR_ERROR_NAME_INVALID = -45, + XR_ERROR_ACTIONSET_NOT_ATTACHED = -46, + XR_ERROR_ACTIONSETS_ALREADY_ATTACHED = -47, + XR_ERROR_LOCALIZED_NAME_DUPLICATED = -48, + XR_ERROR_LOCALIZED_NAME_INVALID = -49, + XR_ERROR_GRAPHICS_REQUIREMENTS_CALL_MISSING = -50, + XR_ERROR_RUNTIME_UNAVAILABLE = -51, + XR_ERROR_EXTENSION_DEPENDENCY_NOT_ENABLED = -1000710001, + XR_ERROR_PERMISSION_INSUFFICIENT = -1000710000, + XR_ERROR_ANDROID_THREAD_SETTINGS_ID_INVALID_KHR = -1000003000, + XR_ERROR_ANDROID_THREAD_SETTINGS_FAILURE_KHR = -1000003001, + XR_ERROR_CREATE_SPATIAL_ANCHOR_FAILED_MSFT = -1000039001, + XR_ERROR_SECONDARY_VIEW_CONFIGURATION_TYPE_NOT_ENABLED_MSFT = -1000053000, + XR_ERROR_CONTROLLER_MODEL_KEY_INVALID_MSFT = -1000055000, + XR_ERROR_REPROJECTION_MODE_UNSUPPORTED_MSFT = -1000066000, + XR_ERROR_COMPUTE_NEW_SCENE_NOT_COMPLETED_MSFT = -1000097000, + XR_ERROR_SCENE_COMPONENT_ID_INVALID_MSFT = -1000097001, + XR_ERROR_SCENE_COMPONENT_TYPE_MISMATCH_MSFT = -1000097002, + XR_ERROR_SCENE_MESH_BUFFER_ID_INVALID_MSFT = -1000097003, + XR_ERROR_SCENE_COMPUTE_FEATURE_INCOMPATIBLE_MSFT = -1000097004, + XR_ERROR_SCENE_COMPUTE_CONSISTENCY_MISMATCH_MSFT = -1000097005, + XR_ERROR_DISPLAY_REFRESH_RATE_UNSUPPORTED_FB = -1000101000, + XR_ERROR_COLOR_SPACE_UNSUPPORTED_FB = -1000108000, + XR_ERROR_SPACE_COMPONENT_NOT_SUPPORTED_FB = -1000113000, + XR_ERROR_SPACE_COMPONENT_NOT_ENABLED_FB = -1000113001, + XR_ERROR_SPACE_COMPONENT_STATUS_PENDING_FB = -1000113002, + XR_ERROR_SPACE_COMPONENT_STATUS_ALREADY_SET_FB = -1000113003, + XR_ERROR_UNEXPECTED_STATE_PASSTHROUGH_FB = -1000118000, + XR_ERROR_FEATURE_ALREADY_CREATED_PASSTHROUGH_FB = -1000118001, + XR_ERROR_FEATURE_REQUIRED_PASSTHROUGH_FB = -1000118002, + XR_ERROR_NOT_PERMITTED_PASSTHROUGH_FB = -1000118003, + XR_ERROR_INSUFFICIENT_RESOURCES_PASSTHROUGH_FB = -1000118004, + XR_ERROR_UNKNOWN_PASSTHROUGH_FB = -1000118050, + XR_ERROR_RENDER_MODEL_KEY_INVALID_FB = -1000119000, + XR_RENDER_MODEL_UNAVAILABLE_FB = 1000119020, + XR_ERROR_MARKER_NOT_TRACKED_VARJO = -1000124000, + XR_ERROR_MARKER_ID_INVALID_VARJO = -1000124001, + XR_ERROR_MARKER_DETECTOR_PERMISSION_DENIED_ML = -1000138000, + XR_ERROR_MARKER_DETECTOR_LOCATE_FAILED_ML = -1000138001, + XR_ERROR_MARKER_DETECTOR_INVALID_DATA_QUERY_ML = -1000138002, + XR_ERROR_MARKER_DETECTOR_INVALID_CREATE_INFO_ML = -1000138003, + XR_ERROR_MARKER_INVALID_ML = -1000138004, + XR_ERROR_LOCALIZATION_MAP_INCOMPATIBLE_ML = -1000139000, + XR_ERROR_LOCALIZATION_MAP_UNAVAILABLE_ML = -1000139001, + XR_ERROR_LOCALIZATION_MAP_FAIL_ML = -1000139002, + XR_ERROR_LOCALIZATION_MAP_IMPORT_EXPORT_PERMISSION_DENIED_ML = -1000139003, + XR_ERROR_LOCALIZATION_MAP_PERMISSION_DENIED_ML = -1000139004, + XR_ERROR_LOCALIZATION_MAP_ALREADY_EXISTS_ML = -1000139005, + XR_ERROR_LOCALIZATION_MAP_CANNOT_EXPORT_CLOUD_MAP_ML = -1000139006, + XR_ERROR_SPATIAL_ANCHORS_PERMISSION_DENIED_ML = -1000140000, + XR_ERROR_SPATIAL_ANCHORS_NOT_LOCALIZED_ML = -1000140001, + XR_ERROR_SPATIAL_ANCHORS_OUT_OF_MAP_BOUNDS_ML = -1000140002, + XR_ERROR_SPATIAL_ANCHORS_SPACE_NOT_LOCATABLE_ML = -1000140003, + XR_ERROR_SPATIAL_ANCHORS_ANCHOR_NOT_FOUND_ML = -1000141000, + XR_ERROR_SPATIAL_ANCHOR_NAME_NOT_FOUND_MSFT = -1000142001, + XR_ERROR_SPATIAL_ANCHOR_NAME_INVALID_MSFT = -1000142002, + XR_SCENE_MARKER_DATA_NOT_STRING_MSFT = 1000147000, + XR_ERROR_SPACE_MAPPING_INSUFFICIENT_FB = -1000169000, + XR_ERROR_SPACE_LOCALIZATION_FAILED_FB = -1000169001, + XR_ERROR_SPACE_NETWORK_TIMEOUT_FB = -1000169002, + XR_ERROR_SPACE_NETWORK_REQUEST_FAILED_FB = -1000169003, + XR_ERROR_SPACE_CLOUD_STORAGE_DISABLED_FB = -1000169004, + XR_ERROR_SPACE_INSUFFICIENT_RESOURCES_META = -1000259000, + XR_ERROR_SPACE_STORAGE_AT_CAPACITY_META = -1000259001, + XR_ERROR_SPACE_INSUFFICIENT_VIEW_META = -1000259002, + XR_ERROR_SPACE_PERMISSION_INSUFFICIENT_META = -1000259003, + XR_ERROR_SPACE_RATE_LIMITED_META = -1000259004, + XR_ERROR_SPACE_TOO_DARK_META = -1000259005, + XR_ERROR_SPACE_TOO_BRIGHT_META = -1000259006, + XR_ERROR_PASSTHROUGH_COLOR_LUT_BUFFER_SIZE_MISMATCH_META = -1000266000, + XR_ENVIRONMENT_DEPTH_NOT_AVAILABLE_META = 1000291000, + XR_ERROR_RENDER_MODEL_ID_INVALID_EXT = -1000300000, + XR_ERROR_RENDER_MODEL_ASSET_UNAVAILABLE_EXT = -1000300001, + XR_ERROR_RENDER_MODEL_GLTF_EXTENSION_REQUIRED_EXT = -1000300002, + XR_ERROR_NOT_INTERACTION_RENDER_MODEL_EXT = -1000301000, + XR_ERROR_HINT_ALREADY_SET_QCOM = -1000306000, + XR_ERROR_NOT_AN_ANCHOR_HTC = -1000319000, + XR_ERROR_SPATIAL_ENTITY_ID_INVALID_BD = -1000389000, + XR_ERROR_SPATIAL_SENSING_SERVICE_UNAVAILABLE_BD = -1000389001, + XR_ERROR_ANCHOR_NOT_SUPPORTED_FOR_ENTITY_BD = -1000389002, + XR_ERROR_SPATIAL_ANCHOR_NOT_FOUND_BD = -1000390000, + XR_ERROR_SPATIAL_ANCHOR_SHARING_NETWORK_TIMEOUT_BD = -1000391000, + XR_ERROR_SPATIAL_ANCHOR_SHARING_AUTHENTICATION_FAILURE_BD = -1000391001, + XR_ERROR_SPATIAL_ANCHOR_SHARING_NETWORK_FAILURE_BD = -1000391002, + XR_ERROR_SPATIAL_ANCHOR_SHARING_LOCALIZATION_FAIL_BD = -1000391003, + XR_ERROR_SPATIAL_ANCHOR_SHARING_MAP_INSUFFICIENT_BD = -1000391004, + XR_ERROR_SCENE_CAPTURE_FAILURE_BD = -1000392000, + XR_ERROR_SPACE_NOT_LOCATABLE_EXT = -1000429000, + XR_ERROR_PLANE_DETECTION_PERMISSION_DENIED_EXT = -1000429001, + XR_ERROR_MISMATCHING_TRACKABLE_TYPE_ANDROID = -1000455000, + XR_ERROR_TRACKABLE_TYPE_NOT_SUPPORTED_ANDROID = -1000455001, + XR_ERROR_ANCHOR_ID_NOT_FOUND_ANDROID = -1000457000, + XR_ERROR_ANCHOR_ALREADY_PERSISTED_ANDROID = -1000457001, + XR_ERROR_ANCHOR_NOT_TRACKING_ANDROID = -1000457002, + XR_ERROR_PERSISTED_DATA_NOT_READY_ANDROID = -1000457003, + XR_ERROR_SERVICE_NOT_READY_ANDROID = -1000458000, + XR_ERROR_MESH_DATA_LIMIT_EXCEEDED_ANDROID = -1000462000, + XR_ERROR_FUTURE_PENDING_EXT = -1000469001, + XR_ERROR_FUTURE_INVALID_EXT = -1000469002, + XR_ERROR_SYSTEM_NOTIFICATION_PERMISSION_DENIED_ML = -1000473000, + XR_ERROR_SYSTEM_NOTIFICATION_INCOMPATIBLE_SKU_ML = -1000473001, + XR_ERROR_WORLD_MESH_DETECTOR_PERMISSION_DENIED_ML = -1000474000, + XR_ERROR_WORLD_MESH_DETECTOR_SPACE_NOT_LOCATABLE_ML = -1000474001, + XR_ERROR_FACIAL_EXPRESSION_PERMISSION_DENIED_ML = 1000482000, + XR_BOUNDARY_VISIBILITY_SUPPRESSION_NOT_ALLOWED_META = 1000528000, + XR_ERROR_COLOCATION_DISCOVERY_NETWORK_FAILED_META = -1000571001, + XR_ERROR_COLOCATION_DISCOVERY_NO_DISCOVERY_METHOD_META = -1000571002, + XR_COLOCATION_DISCOVERY_ALREADY_ADVERTISING_META = 1000571003, + XR_COLOCATION_DISCOVERY_ALREADY_DISCOVERING_META = 1000571004, + XR_ERROR_SPACE_GROUP_NOT_FOUND_META = -1000572002, + XR_ERROR_ANCHOR_NOT_OWNED_BY_CALLER_ANDROID = -1000701000, + XR_ERROR_IMAGE_FORMAT_UNSUPPORTED_ANDROID = -1000709000, + XR_ERROR_SPATIAL_CAPABILITY_UNSUPPORTED_EXT = -1000740001, + XR_ERROR_SPATIAL_ENTITY_ID_INVALID_EXT = -1000740002, + XR_ERROR_SPATIAL_BUFFER_ID_INVALID_EXT = -1000740003, + XR_ERROR_SPATIAL_COMPONENT_UNSUPPORTED_FOR_CAPABILITY_EXT = -1000740004, + XR_ERROR_SPATIAL_CAPABILITY_CONFIGURATION_INVALID_EXT = -1000740005, + XR_ERROR_SPATIAL_COMPONENT_NOT_ENABLED_EXT = -1000740006, + XR_ERROR_CAMERA_UNAVAILABLE_BD = -1000755000, + XR_ERROR_CAMERA_OCCUPIED_BD = -1000755001, + XR_ERROR_CAMERA_CAPTURE_SESSION_CAPTURING_BD = -1000755002, + XR_ERROR_CAMERA_CAPTURE_SESSION_NOT_CAPTURING_BD = -1000755003, + XR_ERROR_CAMERA_ID_INVALID_BD = -1000755004, + XR_ERROR_CAMERA_IMAGE_ID_INVALID_BD = -1000755005, + XR_ERROR_CAMERA_PROPERTY_TYPE_INVALID_BD = -1000755006, + XR_ERROR_CAMERA_CAPABILITY_TYPE_INVALID_BD = -1000755007, + XR_ERROR_IMAGE_BUFFER_EXHAUSTED_BD = -1000755008, + XR_ERROR_SPATIAL_PERSISTENCE_SCOPE_UNSUPPORTED_EXT = -1000763001, + XR_ERROR_SPATIAL_PERSISTENCE_SCOPE_INCOMPATIBLE_EXT = -1000781001, + XR_ERROR_SPATIAL_IMAGE_FORMAT_UNSUPPORTED_EXT = -1000782001, + XR_ERROR_SPATIAL_IMAGE_INVALID_EXT = -1000782002, + XR_ERROR_SPATIAL_IMAGE_SIZE_MISSING_EXT = -1000782003, + XR_ERROR_KEYLESS_AUTH_NOT_SETUP_ANDROID = -1000787000, + XR_ERROR_KEYLESS_AUTH_FAILED_ANDROID = -1000787001, + XR_ERROR_GEOSPATIAL_TRACKER_NOT_RUNNING_ANDROID = -1000789000, + XR_ERROR_GEOSPATIAL_COORDINATES_INVALID_ANDROID = -1000789001, + XR_ERROR_GEOSPATIAL_CLOUD_AUTH_FAILED_ANDROID = -1000789002, + XR_ERROR_SPATIAL_ANCHOR_ATTACHABLE_COMPONENT_NOT_FOUND_ANDROID = -1000790001, + XR_ERROR_SPATIAL_ANCHOR_ENTITY_ID_INVALID_ANDROID = -1000795001, + XR_ERROR_SURFACE_ANCHOR_LOCATION_UNSUPPORTED_ANDROID = -1000797000, + XR_ERROR_SPATIAL_CONTAINER_CLOSED_EXT = -1000810000, + XR_ERROR_SPATIAL_CONTAINERS_ENABLED_EXT = -1000810001, + XR_ERROR_SPATIAL_CONTAINERS_NOT_ENABLED_EXT = -1000810002, + XR_ERROR_COMPATIBLE_SPATIAL_CONTAINER_MISSING_EXT = -1000810003, + XR_ERROR_SPATIAL_CONTAINER_IS_RENDERING_EXT = -1000813001, + XR_ERROR_SPATIAL_CONTAINER_NOT_RENDERING_EXT = -1000813002, + XR_ERROR_SPATIAL_CONTAINER_MISSING_EXT = -1000813003, + XR_ERROR_SPATIAL_CONTAINER_DUPLICATE_EXT = -1000813004, + XR_ERROR_SPATIAL_CONTAINER_GRAPHICS_PRESENTATION_MISSING_EXT = -1000813005, + XR_ERROR_EXTENSION_DEPENDENCY_NOT_ENABLED_KHR = XR_ERROR_EXTENSION_DEPENDENCY_NOT_ENABLED, + XR_ERROR_PERMISSION_INSUFFICIENT_KHR = XR_ERROR_PERMISSION_INSUFFICIENT, + XR_RESULT_MAX_ENUM = 0x7FFFFFFF +} XrResult; + +typedef enum XrStructureType { + XR_TYPE_UNKNOWN = 0, + XR_TYPE_API_LAYER_PROPERTIES = 1, + XR_TYPE_EXTENSION_PROPERTIES = 2, + XR_TYPE_INSTANCE_CREATE_INFO = 3, + XR_TYPE_SYSTEM_GET_INFO = 4, + XR_TYPE_SYSTEM_PROPERTIES = 5, + XR_TYPE_VIEW_LOCATE_INFO = 6, + XR_TYPE_VIEW = 7, + XR_TYPE_SESSION_CREATE_INFO = 8, + XR_TYPE_SWAPCHAIN_CREATE_INFO = 9, + XR_TYPE_SESSION_BEGIN_INFO = 10, + XR_TYPE_VIEW_STATE = 11, + XR_TYPE_FRAME_END_INFO = 12, + XR_TYPE_HAPTIC_VIBRATION = 13, + XR_TYPE_EVENT_DATA_BUFFER = 16, + XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING = 17, + XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED = 18, + XR_TYPE_ACTION_STATE_BOOLEAN = 23, + XR_TYPE_ACTION_STATE_FLOAT = 24, + XR_TYPE_ACTION_STATE_VECTOR2F = 25, + XR_TYPE_ACTION_STATE_POSE = 27, + XR_TYPE_ACTION_SET_CREATE_INFO = 28, + XR_TYPE_ACTION_CREATE_INFO = 29, + XR_TYPE_INSTANCE_PROPERTIES = 32, + XR_TYPE_FRAME_WAIT_INFO = 33, + XR_TYPE_COMPOSITION_LAYER_PROJECTION = 35, + XR_TYPE_COMPOSITION_LAYER_QUAD = 36, + XR_TYPE_REFERENCE_SPACE_CREATE_INFO = 37, + XR_TYPE_ACTION_SPACE_CREATE_INFO = 38, + XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING = 40, + XR_TYPE_VIEW_CONFIGURATION_VIEW = 41, + XR_TYPE_SPACE_LOCATION = 42, + XR_TYPE_SPACE_VELOCITY = 43, + XR_TYPE_FRAME_STATE = 44, + XR_TYPE_VIEW_CONFIGURATION_PROPERTIES = 45, + XR_TYPE_FRAME_BEGIN_INFO = 46, + XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW = 48, + XR_TYPE_EVENT_DATA_EVENTS_LOST = 49, + XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING = 51, + XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED = 52, + XR_TYPE_INTERACTION_PROFILE_STATE = 53, + XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO = 55, + XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO = 56, + XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO = 57, + XR_TYPE_ACTION_STATE_GET_INFO = 58, + XR_TYPE_HAPTIC_ACTION_INFO = 59, + XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO = 60, + XR_TYPE_ACTIONS_SYNC_INFO = 61, + XR_TYPE_BOUND_SOURCES_FOR_ACTION_ENUMERATE_INFO = 62, + XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO = 63, + XR_TYPE_SPACES_LOCATE_INFO = 1000471000, + XR_TYPE_SPACE_LOCATIONS = 1000471001, + XR_TYPE_SPACE_VELOCITIES = 1000471002, + XR_TYPE_COMPOSITION_LAYER_CUBE_KHR = 1000006000, + XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR = 1000008000, + XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR = 1000010000, + XR_TYPE_VULKAN_SWAPCHAIN_FORMAT_LIST_CREATE_INFO_KHR = 1000014000, + XR_TYPE_EVENT_DATA_PERF_SETTINGS_EXT = 1000015000, + XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR = 1000017000, + XR_TYPE_COMPOSITION_LAYER_EQUIRECT_KHR = 1000018000, + XR_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT = 1000019000, + XR_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT = 1000019001, + XR_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT = 1000019002, + XR_TYPE_DEBUG_UTILS_LABEL_EXT = 1000019003, + XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR = 1000023000, + XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR = 1000023001, + XR_TYPE_GRAPHICS_BINDING_OPENGL_XCB_KHR = 1000023002, + XR_TYPE_GRAPHICS_BINDING_OPENGL_WAYLAND_KHR = 1000023003, + XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR = 1000023004, + XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR = 1000023005, + XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR = 1000024001, + XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR = 1000024002, + XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR = 1000024003, + XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR = 1000025000, + XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR = 1000025001, + XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR = 1000025002, + XR_TYPE_GRAPHICS_BINDING_D3D11_KHR = 1000027000, + XR_TYPE_SWAPCHAIN_IMAGE_D3D11_KHR = 1000027001, + XR_TYPE_GRAPHICS_REQUIREMENTS_D3D11_KHR = 1000027002, + XR_TYPE_GRAPHICS_BINDING_D3D12_KHR = 1000028000, + XR_TYPE_SWAPCHAIN_IMAGE_D3D12_KHR = 1000028001, + XR_TYPE_GRAPHICS_REQUIREMENTS_D3D12_KHR = 1000028002, + XR_TYPE_GRAPHICS_BINDING_METAL_KHR = 1000029000, + XR_TYPE_SWAPCHAIN_IMAGE_METAL_KHR = 1000029001, + XR_TYPE_GRAPHICS_REQUIREMENTS_METAL_KHR = 1000029002, + XR_TYPE_SYSTEM_EYE_GAZE_INTERACTION_PROPERTIES_EXT = 1000030000, + XR_TYPE_EYE_GAZE_SAMPLE_TIME_EXT = 1000030001, + XR_TYPE_VISIBILITY_MASK_KHR = 1000031000, + XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR = 1000031001, + XR_TYPE_SESSION_CREATE_INFO_OVERLAY_EXTX = 1000033000, + XR_TYPE_EVENT_DATA_MAIN_SESSION_VISIBILITY_CHANGED_EXTX = 1000033003, + XR_TYPE_COMPOSITION_LAYER_COLOR_SCALE_BIAS_KHR = 1000034000, + XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_MSFT = 1000039000, + XR_TYPE_SPATIAL_ANCHOR_SPACE_CREATE_INFO_MSFT = 1000039001, + XR_TYPE_COMPOSITION_LAYER_IMAGE_LAYOUT_FB = 1000040000, + XR_TYPE_COMPOSITION_LAYER_ALPHA_BLEND_FB = 1000041001, + XR_TYPE_VIEW_CONFIGURATION_DEPTH_RANGE_EXT = 1000046000, + XR_TYPE_GRAPHICS_BINDING_EGL_MNDX = 1000048004, + XR_TYPE_SPATIAL_GRAPH_NODE_SPACE_CREATE_INFO_MSFT = 1000049000, + XR_TYPE_SPATIAL_GRAPH_STATIC_NODE_BINDING_CREATE_INFO_MSFT = 1000049001, + XR_TYPE_SPATIAL_GRAPH_NODE_BINDING_PROPERTIES_GET_INFO_MSFT = 1000049002, + XR_TYPE_SPATIAL_GRAPH_NODE_BINDING_PROPERTIES_MSFT = 1000049003, + XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT = 1000051000, + XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT = 1000051001, + XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT = 1000051002, + XR_TYPE_HAND_JOINT_LOCATIONS_EXT = 1000051003, + XR_TYPE_HAND_JOINT_VELOCITIES_EXT = 1000051004, + XR_TYPE_SYSTEM_HAND_TRACKING_MESH_PROPERTIES_MSFT = 1000052000, + XR_TYPE_HAND_MESH_SPACE_CREATE_INFO_MSFT = 1000052001, + XR_TYPE_HAND_MESH_UPDATE_INFO_MSFT = 1000052002, + XR_TYPE_HAND_MESH_MSFT = 1000052003, + XR_TYPE_HAND_POSE_TYPE_INFO_MSFT = 1000052004, + XR_TYPE_SECONDARY_VIEW_CONFIGURATION_SESSION_BEGIN_INFO_MSFT = 1000053000, + XR_TYPE_SECONDARY_VIEW_CONFIGURATION_STATE_MSFT = 1000053001, + XR_TYPE_SECONDARY_VIEW_CONFIGURATION_FRAME_STATE_MSFT = 1000053002, + XR_TYPE_SECONDARY_VIEW_CONFIGURATION_FRAME_END_INFO_MSFT = 1000053003, + XR_TYPE_SECONDARY_VIEW_CONFIGURATION_LAYER_INFO_MSFT = 1000053004, + XR_TYPE_SECONDARY_VIEW_CONFIGURATION_SWAPCHAIN_CREATE_INFO_MSFT = 1000053005, + XR_TYPE_CONTROLLER_MODEL_KEY_STATE_MSFT = 1000055000, + XR_TYPE_CONTROLLER_MODEL_NODE_PROPERTIES_MSFT = 1000055001, + XR_TYPE_CONTROLLER_MODEL_PROPERTIES_MSFT = 1000055002, + XR_TYPE_CONTROLLER_MODEL_NODE_STATE_MSFT = 1000055003, + XR_TYPE_CONTROLLER_MODEL_STATE_MSFT = 1000055004, + XR_TYPE_VIEW_CONFIGURATION_VIEW_FOV_EPIC = 1000059000, + XR_TYPE_HOLOGRAPHIC_WINDOW_ATTACHMENT_MSFT = 1000063000, + XR_TYPE_COMPOSITION_LAYER_REPROJECTION_INFO_MSFT = 1000066000, + XR_TYPE_COMPOSITION_LAYER_REPROJECTION_PLANE_OVERRIDE_MSFT = 1000066001, + XR_TYPE_ANDROID_SURFACE_SWAPCHAIN_CREATE_INFO_FB = 1000070000, + XR_TYPE_COMPOSITION_LAYER_SECURE_CONTENT_FB = 1000072000, + XR_TYPE_BODY_TRACKER_CREATE_INFO_FB = 1000076001, + XR_TYPE_BODY_JOINTS_LOCATE_INFO_FB = 1000076002, + XR_TYPE_SYSTEM_BODY_TRACKING_PROPERTIES_FB = 1000076004, + XR_TYPE_BODY_JOINT_LOCATIONS_FB = 1000076005, + XR_TYPE_BODY_SKELETON_FB = 1000076006, + XR_TYPE_INTERACTION_PROFILE_DPAD_BINDING_EXT = 1000078000, + XR_TYPE_INTERACTION_PROFILE_ANALOG_THRESHOLD_VALVE = 1000079000, + XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT = 1000080000, + XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR = 1000089000, + XR_TYPE_VULKAN_INSTANCE_CREATE_INFO_KHR = 1000090000, + XR_TYPE_VULKAN_DEVICE_CREATE_INFO_KHR = 1000090001, + XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR = 1000090003, + XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR = 1000091000, + XR_TYPE_SCENE_OBSERVER_CREATE_INFO_MSFT = 1000097000, + XR_TYPE_SCENE_CREATE_INFO_MSFT = 1000097001, + XR_TYPE_NEW_SCENE_COMPUTE_INFO_MSFT = 1000097002, + XR_TYPE_VISUAL_MESH_COMPUTE_LOD_INFO_MSFT = 1000097003, + XR_TYPE_SCENE_COMPONENTS_MSFT = 1000097004, + XR_TYPE_SCENE_COMPONENTS_GET_INFO_MSFT = 1000097005, + XR_TYPE_SCENE_COMPONENT_LOCATIONS_MSFT = 1000097006, + XR_TYPE_SCENE_COMPONENTS_LOCATE_INFO_MSFT = 1000097007, + XR_TYPE_SCENE_OBJECTS_MSFT = 1000097008, + XR_TYPE_SCENE_COMPONENT_PARENT_FILTER_INFO_MSFT = 1000097009, + XR_TYPE_SCENE_OBJECT_TYPES_FILTER_INFO_MSFT = 1000097010, + XR_TYPE_SCENE_PLANES_MSFT = 1000097011, + XR_TYPE_SCENE_PLANE_ALIGNMENT_FILTER_INFO_MSFT = 1000097012, + XR_TYPE_SCENE_MESHES_MSFT = 1000097013, + XR_TYPE_SCENE_MESH_BUFFERS_GET_INFO_MSFT = 1000097014, + XR_TYPE_SCENE_MESH_BUFFERS_MSFT = 1000097015, + XR_TYPE_SCENE_MESH_VERTEX_BUFFER_MSFT = 1000097016, + XR_TYPE_SCENE_MESH_INDICES_UINT32_MSFT = 1000097017, + XR_TYPE_SCENE_MESH_INDICES_UINT16_MSFT = 1000097018, + XR_TYPE_SERIALIZED_SCENE_FRAGMENT_DATA_GET_INFO_MSFT = 1000098000, + XR_TYPE_SCENE_DESERIALIZE_INFO_MSFT = 1000098001, + XR_TYPE_EVENT_DATA_DISPLAY_REFRESH_RATE_CHANGED_FB = 1000101000, + XR_TYPE_VIVE_TRACKER_PATHS_HTCX = 1000103000, + XR_TYPE_EVENT_DATA_VIVE_TRACKER_CONNECTED_HTCX = 1000103001, + XR_TYPE_SYSTEM_FACIAL_TRACKING_PROPERTIES_HTC = 1000104000, + XR_TYPE_FACIAL_TRACKER_CREATE_INFO_HTC = 1000104001, + XR_TYPE_FACIAL_EXPRESSIONS_HTC = 1000104002, + XR_TYPE_SYSTEM_COLOR_SPACE_PROPERTIES_FB = 1000108000, + XR_TYPE_HAND_TRACKING_MESH_FB = 1000110001, + XR_TYPE_HAND_TRACKING_SCALE_FB = 1000110003, + XR_TYPE_HAND_TRACKING_AIM_STATE_FB = 1000111001, + XR_TYPE_HAND_TRACKING_CAPSULES_STATE_FB = 1000112000, + XR_TYPE_SYSTEM_SPATIAL_ENTITY_PROPERTIES_FB = 1000113004, + XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_FB = 1000113003, + XR_TYPE_SPACE_COMPONENT_STATUS_SET_INFO_FB = 1000113007, + XR_TYPE_SPACE_COMPONENT_STATUS_FB = 1000113001, + XR_TYPE_EVENT_DATA_SPATIAL_ANCHOR_CREATE_COMPLETE_FB = 1000113005, + XR_TYPE_EVENT_DATA_SPACE_SET_STATUS_COMPLETE_FB = 1000113006, + XR_TYPE_FOVEATION_PROFILE_CREATE_INFO_FB = 1000114000, + XR_TYPE_SWAPCHAIN_CREATE_INFO_FOVEATION_FB = 1000114001, + XR_TYPE_SWAPCHAIN_STATE_FOVEATION_FB = 1000114002, + XR_TYPE_FOVEATION_LEVEL_PROFILE_CREATE_INFO_FB = 1000115000, + XR_TYPE_KEYBOARD_SPACE_CREATE_INFO_FB = 1000116009, + XR_TYPE_KEYBOARD_TRACKING_QUERY_FB = 1000116004, + XR_TYPE_SYSTEM_KEYBOARD_TRACKING_PROPERTIES_FB = 1000116002, + XR_TYPE_TRIANGLE_MESH_CREATE_INFO_FB = 1000117001, + XR_TYPE_SYSTEM_PASSTHROUGH_PROPERTIES_FB = 1000118000, + XR_TYPE_PASSTHROUGH_CREATE_INFO_FB = 1000118001, + XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB = 1000118002, + XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB = 1000118003, + XR_TYPE_GEOMETRY_INSTANCE_CREATE_INFO_FB = 1000118004, + XR_TYPE_GEOMETRY_INSTANCE_TRANSFORM_FB = 1000118005, + XR_TYPE_SYSTEM_PASSTHROUGH_PROPERTIES2_FB = 1000118006, + XR_TYPE_PASSTHROUGH_STYLE_FB = 1000118020, + XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_RGBA_FB = 1000118021, + XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_MONO_FB = 1000118022, + XR_TYPE_PASSTHROUGH_BRIGHTNESS_CONTRAST_SATURATION_FB = 1000118023, + XR_TYPE_EVENT_DATA_PASSTHROUGH_STATE_CHANGED_FB = 1000118030, + XR_TYPE_RENDER_MODEL_PATH_INFO_FB = 1000119000, + XR_TYPE_RENDER_MODEL_PROPERTIES_FB = 1000119001, + XR_TYPE_RENDER_MODEL_BUFFER_FB = 1000119002, + XR_TYPE_RENDER_MODEL_LOAD_INFO_FB = 1000119003, + XR_TYPE_SYSTEM_RENDER_MODEL_PROPERTIES_FB = 1000119004, + XR_TYPE_RENDER_MODEL_CAPABILITIES_REQUEST_FB = 1000119005, + XR_TYPE_BINDING_MODIFICATIONS_KHR = 1000120000, + XR_TYPE_VIEW_LOCATE_FOVEATED_RENDERING_VARJO = 1000121000, + XR_TYPE_FOVEATED_VIEW_CONFIGURATION_VIEW_VARJO = 1000121001, + XR_TYPE_SYSTEM_FOVEATED_RENDERING_PROPERTIES_VARJO = 1000121002, + XR_TYPE_COMPOSITION_LAYER_DEPTH_TEST_VARJO = 1000122000, + XR_TYPE_SYSTEM_MARKER_TRACKING_PROPERTIES_VARJO = 1000124000, + XR_TYPE_EVENT_DATA_MARKER_TRACKING_UPDATE_VARJO = 1000124001, + XR_TYPE_MARKER_SPACE_CREATE_INFO_VARJO = 1000124002, + XR_TYPE_FRAME_END_INFO_ML = 1000135000, + XR_TYPE_GLOBAL_DIMMER_FRAME_END_INFO_ML = 1000136000, + XR_TYPE_COORDINATE_SPACE_CREATE_INFO_ML = 1000137000, + XR_TYPE_SYSTEM_MARKER_UNDERSTANDING_PROPERTIES_ML = 1000138000, + XR_TYPE_MARKER_DETECTOR_CREATE_INFO_ML = 1000138001, + XR_TYPE_MARKER_DETECTOR_ARUCO_INFO_ML = 1000138002, + XR_TYPE_MARKER_DETECTOR_SIZE_INFO_ML = 1000138003, + XR_TYPE_MARKER_DETECTOR_APRIL_TAG_INFO_ML = 1000138004, + XR_TYPE_MARKER_DETECTOR_CUSTOM_PROFILE_INFO_ML = 1000138005, + XR_TYPE_MARKER_DETECTOR_SNAPSHOT_INFO_ML = 1000138006, + XR_TYPE_MARKER_DETECTOR_STATE_ML = 1000138007, + XR_TYPE_MARKER_SPACE_CREATE_INFO_ML = 1000138008, + XR_TYPE_LOCALIZATION_MAP_ML = 1000139000, + XR_TYPE_EVENT_DATA_LOCALIZATION_CHANGED_ML = 1000139001, + XR_TYPE_MAP_LOCALIZATION_REQUEST_INFO_ML = 1000139002, + XR_TYPE_LOCALIZATION_MAP_IMPORT_INFO_ML = 1000139003, + XR_TYPE_LOCALIZATION_ENABLE_EVENTS_INFO_ML = 1000139004, + XR_TYPE_SPATIAL_ANCHORS_CREATE_INFO_FROM_POSE_ML = 1000140000, + XR_TYPE_CREATE_SPATIAL_ANCHORS_COMPLETION_ML = 1000140001, + XR_TYPE_SPATIAL_ANCHOR_STATE_ML = 1000140002, + XR_TYPE_SPATIAL_ANCHORS_CREATE_STORAGE_INFO_ML = 1000141000, + XR_TYPE_SPATIAL_ANCHORS_QUERY_INFO_RADIUS_ML = 1000141001, + XR_TYPE_SPATIAL_ANCHORS_QUERY_COMPLETION_ML = 1000141002, + XR_TYPE_SPATIAL_ANCHORS_CREATE_INFO_FROM_UUIDS_ML = 1000141003, + XR_TYPE_SPATIAL_ANCHORS_PUBLISH_INFO_ML = 1000141004, + XR_TYPE_SPATIAL_ANCHORS_PUBLISH_COMPLETION_ML = 1000141005, + XR_TYPE_SPATIAL_ANCHORS_DELETE_INFO_ML = 1000141006, + XR_TYPE_SPATIAL_ANCHORS_DELETE_COMPLETION_ML = 1000141007, + XR_TYPE_SPATIAL_ANCHORS_UPDATE_EXPIRATION_INFO_ML = 1000141008, + XR_TYPE_SPATIAL_ANCHORS_UPDATE_EXPIRATION_COMPLETION_ML = 1000141009, + XR_TYPE_SPATIAL_ANCHORS_PUBLISH_COMPLETION_DETAILS_ML = 1000141010, + XR_TYPE_SPATIAL_ANCHORS_DELETE_COMPLETION_DETAILS_ML = 1000141011, + XR_TYPE_SPATIAL_ANCHORS_UPDATE_EXPIRATION_COMPLETION_DETAILS_ML = 1000141012, + XR_TYPE_EVENT_DATA_HEADSET_FIT_CHANGED_ML = 1000472000, + XR_TYPE_EVENT_DATA_EYE_CALIBRATION_CHANGED_ML = 1000472001, + XR_TYPE_USER_CALIBRATION_ENABLE_EVENTS_INFO_ML = 1000472002, + XR_TYPE_SPATIAL_ANCHOR_PERSISTENCE_INFO_MSFT = 1000142000, + XR_TYPE_SPATIAL_ANCHOR_FROM_PERSISTED_ANCHOR_CREATE_INFO_MSFT = 1000142001, + XR_TYPE_SCENE_MARKERS_MSFT = 1000147000, + XR_TYPE_SCENE_MARKER_TYPE_FILTER_MSFT = 1000147001, + XR_TYPE_SCENE_MARKER_QR_CODES_MSFT = 1000147002, + XR_TYPE_SPACE_QUERY_INFO_FB = 1000156001, + XR_TYPE_SPACE_QUERY_RESULTS_FB = 1000156002, + XR_TYPE_SPACE_STORAGE_LOCATION_FILTER_INFO_FB = 1000156003, + XR_TYPE_SPACE_UUID_FILTER_INFO_FB = 1000156054, + XR_TYPE_SPACE_COMPONENT_FILTER_INFO_FB = 1000156052, + XR_TYPE_EVENT_DATA_SPACE_QUERY_RESULTS_AVAILABLE_FB = 1000156103, + XR_TYPE_EVENT_DATA_SPACE_QUERY_COMPLETE_FB = 1000156104, + XR_TYPE_SPACE_SAVE_INFO_FB = 1000158000, + XR_TYPE_SPACE_ERASE_INFO_FB = 1000158001, + XR_TYPE_EVENT_DATA_SPACE_SAVE_COMPLETE_FB = 1000158106, + XR_TYPE_EVENT_DATA_SPACE_ERASE_COMPLETE_FB = 1000158107, + XR_TYPE_SWAPCHAIN_IMAGE_FOVEATION_VULKAN_FB = 1000160000, + XR_TYPE_SWAPCHAIN_STATE_ANDROID_SURFACE_DIMENSIONS_FB = 1000161000, + XR_TYPE_SWAPCHAIN_STATE_SAMPLER_OPENGL_ES_FB = 1000162000, + XR_TYPE_SWAPCHAIN_STATE_SAMPLER_VULKAN_FB = 1000163000, + XR_TYPE_SPACE_SHARE_INFO_FB = 1000169001, + XR_TYPE_EVENT_DATA_SPACE_SHARE_COMPLETE_FB = 1000169002, + XR_TYPE_COMPOSITION_LAYER_SPACE_WARP_INFO_FB = 1000171000, + XR_TYPE_SYSTEM_SPACE_WARP_PROPERTIES_FB = 1000171001, + XR_TYPE_HAPTIC_AMPLITUDE_ENVELOPE_VIBRATION_FB = 1000173001, + XR_TYPE_SEMANTIC_LABELS_FB = 1000175000, + XR_TYPE_ROOM_LAYOUT_FB = 1000175001, + XR_TYPE_BOUNDARY_2D_FB = 1000175002, + XR_TYPE_SEMANTIC_LABELS_SUPPORT_INFO_FB = 1000175010, + XR_TYPE_DIGITAL_LENS_CONTROL_ALMALENCE = 1000196000, + XR_TYPE_EVENT_DATA_SCENE_CAPTURE_COMPLETE_FB = 1000198001, + XR_TYPE_SCENE_CAPTURE_REQUEST_INFO_FB = 1000198050, + XR_TYPE_SPACE_CONTAINER_FB = 1000199000, + XR_TYPE_FOVEATION_EYE_TRACKED_PROFILE_CREATE_INFO_META = 1000200000, + XR_TYPE_FOVEATION_EYE_TRACKED_STATE_META = 1000200001, + XR_TYPE_SYSTEM_FOVEATION_EYE_TRACKED_PROPERTIES_META = 1000200002, + XR_TYPE_SYSTEM_FACE_TRACKING_PROPERTIES_FB = 1000201004, + XR_TYPE_FACE_TRACKER_CREATE_INFO_FB = 1000201005, + XR_TYPE_FACE_EXPRESSION_INFO_FB = 1000201002, + XR_TYPE_FACE_EXPRESSION_WEIGHTS_FB = 1000201006, + XR_TYPE_EYE_TRACKER_CREATE_INFO_FB = 1000202001, + XR_TYPE_EYE_GAZES_INFO_FB = 1000202002, + XR_TYPE_EYE_GAZES_FB = 1000202003, + XR_TYPE_SYSTEM_EYE_TRACKING_PROPERTIES_FB = 1000202004, + XR_TYPE_PASSTHROUGH_KEYBOARD_HANDS_INTENSITY_FB = 1000203002, + XR_TYPE_COMPOSITION_LAYER_SETTINGS_FB = 1000204000, + XR_TYPE_HAPTIC_PCM_VIBRATION_FB = 1000209001, + XR_TYPE_DEVICE_PCM_SAMPLE_RATE_STATE_FB = 1000209002, + XR_TYPE_FRAME_SYNTHESIS_INFO_EXT = 1000211000, + XR_TYPE_FRAME_SYNTHESIS_CONFIG_VIEW_EXT = 1000211001, + XR_TYPE_COMPOSITION_LAYER_DEPTH_TEST_FB = 1000212000, + XR_TYPE_LOCAL_DIMMING_FRAME_END_INFO_META = 1000216000, + XR_TYPE_PASSTHROUGH_PREFERENCES_META = 1000217000, + XR_TYPE_SYSTEM_VIRTUAL_KEYBOARD_PROPERTIES_META = 1000219001, + XR_TYPE_VIRTUAL_KEYBOARD_CREATE_INFO_META = 1000219002, + XR_TYPE_VIRTUAL_KEYBOARD_SPACE_CREATE_INFO_META = 1000219003, + XR_TYPE_VIRTUAL_KEYBOARD_LOCATION_INFO_META = 1000219004, + XR_TYPE_VIRTUAL_KEYBOARD_MODEL_VISIBILITY_SET_INFO_META = 1000219005, + XR_TYPE_VIRTUAL_KEYBOARD_ANIMATION_STATE_META = 1000219006, + XR_TYPE_VIRTUAL_KEYBOARD_MODEL_ANIMATION_STATES_META = 1000219007, + XR_TYPE_VIRTUAL_KEYBOARD_TEXTURE_DATA_META = 1000219009, + XR_TYPE_VIRTUAL_KEYBOARD_INPUT_INFO_META = 1000219010, + XR_TYPE_VIRTUAL_KEYBOARD_TEXT_CONTEXT_CHANGE_INFO_META = 1000219011, + XR_TYPE_EVENT_DATA_VIRTUAL_KEYBOARD_COMMIT_TEXT_META = 1000219014, + XR_TYPE_EVENT_DATA_VIRTUAL_KEYBOARD_BACKSPACE_META = 1000219015, + XR_TYPE_EVENT_DATA_VIRTUAL_KEYBOARD_ENTER_META = 1000219016, + XR_TYPE_EVENT_DATA_VIRTUAL_KEYBOARD_SHOWN_META = 1000219017, + XR_TYPE_EVENT_DATA_VIRTUAL_KEYBOARD_HIDDEN_META = 1000219018, + XR_TYPE_EXTERNAL_CAMERA_OCULUS = 1000226000, + XR_TYPE_VULKAN_SWAPCHAIN_CREATE_INFO_META = 1000227000, + XR_TYPE_PERFORMANCE_METRICS_STATE_META = 1000232001, + XR_TYPE_PERFORMANCE_METRICS_COUNTER_META = 1000232002, + XR_TYPE_SPACE_LIST_SAVE_INFO_FB = 1000238000, + XR_TYPE_EVENT_DATA_SPACE_LIST_SAVE_COMPLETE_FB = 1000238001, + XR_TYPE_SPACE_USER_CREATE_INFO_FB = 1000241001, + XR_TYPE_SYSTEM_HEADSET_ID_PROPERTIES_META = 1000245000, + XR_TYPE_SYSTEM_SPACE_DISCOVERY_PROPERTIES_META = 1000247000, + XR_TYPE_SPACE_DISCOVERY_INFO_META = 1000247001, + XR_TYPE_SPACE_FILTER_UUID_META = 1000247003, + XR_TYPE_SPACE_FILTER_COMPONENT_META = 1000247004, + XR_TYPE_SPACE_DISCOVERY_RESULT_META = 1000247005, + XR_TYPE_SPACE_DISCOVERY_RESULTS_META = 1000247006, + XR_TYPE_EVENT_DATA_SPACE_DISCOVERY_RESULTS_AVAILABLE_META = 1000247007, + XR_TYPE_EVENT_DATA_SPACE_DISCOVERY_COMPLETE_META = 1000247008, + XR_TYPE_RECOMMENDED_LAYER_RESOLUTION_META = 1000254000, + XR_TYPE_RECOMMENDED_LAYER_RESOLUTION_GET_INFO_META = 1000254001, + XR_TYPE_SYSTEM_SPACE_PERSISTENCE_PROPERTIES_META = 1000259000, + XR_TYPE_SPACES_SAVE_INFO_META = 1000259001, + XR_TYPE_EVENT_DATA_SPACES_SAVE_RESULT_META = 1000259002, + XR_TYPE_SPACES_ERASE_INFO_META = 1000259003, + XR_TYPE_EVENT_DATA_SPACES_ERASE_RESULT_META = 1000259004, + XR_TYPE_SYSTEM_PASSTHROUGH_COLOR_LUT_PROPERTIES_META = 1000266000, + XR_TYPE_PASSTHROUGH_COLOR_LUT_CREATE_INFO_META = 1000266001, + XR_TYPE_PASSTHROUGH_COLOR_LUT_UPDATE_INFO_META = 1000266002, + XR_TYPE_PASSTHROUGH_COLOR_MAP_LUT_META = 1000266100, + XR_TYPE_PASSTHROUGH_COLOR_MAP_INTERPOLATED_LUT_META = 1000266101, + XR_TYPE_SPACE_TRIANGLE_MESH_GET_INFO_META = 1000269001, + XR_TYPE_SPACE_TRIANGLE_MESH_META = 1000269002, + XR_TYPE_SYSTEM_PROPERTIES_BODY_TRACKING_FULL_BODY_META = 1000274000, + XR_TYPE_EVENT_DATA_PASSTHROUGH_LAYER_RESUMED_META = 1000282000, + XR_TYPE_BODY_TRACKING_CALIBRATION_INFO_META = 1000283002, + XR_TYPE_BODY_TRACKING_CALIBRATION_STATUS_META = 1000283003, + XR_TYPE_SYSTEM_PROPERTIES_BODY_TRACKING_CALIBRATION_META = 1000283004, + XR_TYPE_BODY_TRACKING_FIDELITY_STATUS_META = 1000284000, + XR_TYPE_SYSTEM_PROPERTIES_BODY_TRACKING_FIDELITY_META = 1000284001, + XR_TYPE_SYSTEM_FACE_TRACKING_PROPERTIES2_FB = 1000287013, + XR_TYPE_FACE_TRACKER_CREATE_INFO2_FB = 1000287014, + XR_TYPE_FACE_EXPRESSION_INFO2_FB = 1000287015, + XR_TYPE_FACE_EXPRESSION_WEIGHTS2_FB = 1000287016, + XR_TYPE_SYSTEM_SPATIAL_ENTITY_SHARING_PROPERTIES_META = 1000290000, + XR_TYPE_SHARE_SPACES_INFO_META = 1000290001, + XR_TYPE_EVENT_DATA_SHARE_SPACES_COMPLETE_META = 1000290002, + XR_TYPE_ENVIRONMENT_DEPTH_PROVIDER_CREATE_INFO_META = 1000291000, + XR_TYPE_ENVIRONMENT_DEPTH_SWAPCHAIN_CREATE_INFO_META = 1000291001, + XR_TYPE_ENVIRONMENT_DEPTH_SWAPCHAIN_STATE_META = 1000291002, + XR_TYPE_ENVIRONMENT_DEPTH_IMAGE_ACQUIRE_INFO_META = 1000291003, + XR_TYPE_ENVIRONMENT_DEPTH_IMAGE_VIEW_META = 1000291004, + XR_TYPE_ENVIRONMENT_DEPTH_IMAGE_META = 1000291005, + XR_TYPE_ENVIRONMENT_DEPTH_HAND_REMOVAL_SET_INFO_META = 1000291006, + XR_TYPE_SYSTEM_ENVIRONMENT_DEPTH_PROPERTIES_META = 1000291007, + XR_TYPE_ENVIRONMENT_DEPTH_IMAGE_TIMESTAMP_META = 1000291008, + XR_TYPE_RENDER_MODEL_CREATE_INFO_EXT = 1000300000, + XR_TYPE_RENDER_MODEL_PROPERTIES_GET_INFO_EXT = 1000300001, + XR_TYPE_RENDER_MODEL_PROPERTIES_EXT = 1000300002, + XR_TYPE_RENDER_MODEL_SPACE_CREATE_INFO_EXT = 1000300003, + XR_TYPE_RENDER_MODEL_STATE_GET_INFO_EXT = 1000300004, + XR_TYPE_RENDER_MODEL_STATE_EXT = 1000300005, + XR_TYPE_RENDER_MODEL_ASSET_CREATE_INFO_EXT = 1000300006, + XR_TYPE_RENDER_MODEL_ASSET_DATA_GET_INFO_EXT = 1000300007, + XR_TYPE_RENDER_MODEL_ASSET_DATA_EXT = 1000300008, + XR_TYPE_RENDER_MODEL_ASSET_PROPERTIES_GET_INFO_EXT = 1000300009, + XR_TYPE_RENDER_MODEL_ASSET_PROPERTIES_EXT = 1000300010, + XR_TYPE_INTERACTION_RENDER_MODEL_IDS_ENUMERATE_INFO_EXT = 1000301000, + XR_TYPE_INTERACTION_RENDER_MODEL_SUBACTION_PATH_INFO_EXT = 1000301001, + XR_TYPE_EVENT_DATA_INTERACTION_RENDER_MODELS_CHANGED_EXT = 1000301002, + XR_TYPE_INTERACTION_RENDER_MODEL_TOP_LEVEL_USER_PATH_GET_INFO_EXT = 1000301003, + XR_TYPE_PASSTHROUGH_CREATE_INFO_HTC = 1000317001, + XR_TYPE_PASSTHROUGH_COLOR_HTC = 1000317002, + XR_TYPE_PASSTHROUGH_MESH_TRANSFORM_INFO_HTC = 1000317003, + XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_HTC = 1000317004, + XR_TYPE_FOVEATION_APPLY_INFO_HTC = 1000318000, + XR_TYPE_FOVEATION_DYNAMIC_MODE_INFO_HTC = 1000318001, + XR_TYPE_FOVEATION_CUSTOM_MODE_INFO_HTC = 1000318002, + XR_TYPE_SYSTEM_ANCHOR_PROPERTIES_HTC = 1000319000, + XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_HTC = 1000319001, + XR_TYPE_SYSTEM_BODY_TRACKING_PROPERTIES_HTC = 1000320000, + XR_TYPE_BODY_TRACKER_CREATE_INFO_HTC = 1000320001, + XR_TYPE_BODY_JOINTS_LOCATE_INFO_HTC = 1000320002, + XR_TYPE_BODY_JOINT_LOCATIONS_HTC = 1000320003, + XR_TYPE_BODY_SKELETON_HTC = 1000320004, + XR_TYPE_ACTIVE_ACTION_SET_PRIORITIES_EXT = 1000373000, + XR_TYPE_SYSTEM_FORCE_FEEDBACK_CURL_PROPERTIES_MNDX = 1000375000, + XR_TYPE_FORCE_FEEDBACK_CURL_APPLY_LOCATIONS_MNDX = 1000375001, + XR_TYPE_BODY_TRACKER_CREATE_INFO_BD = 1000385001, + XR_TYPE_BODY_JOINTS_LOCATE_INFO_BD = 1000385002, + XR_TYPE_BODY_JOINT_LOCATIONS_BD = 1000385003, + XR_TYPE_SYSTEM_BODY_TRACKING_PROPERTIES_BD = 1000385004, + XR_TYPE_SYSTEM_FACIAL_SIMULATION_PROPERTIES_BD = 1000386001, + XR_TYPE_FACE_TRACKER_CREATE_INFO_BD = 1000386002, + XR_TYPE_FACIAL_SIMULATION_DATA_GET_INFO_BD = 1000386003, + XR_TYPE_FACIAL_SIMULATION_DATA_BD = 1000386004, + XR_TYPE_LIP_EXPRESSION_DATA_BD = 1000386005, + XR_TYPE_SYSTEM_SPATIAL_SENSING_PROPERTIES_BD = 1000389000, + XR_TYPE_SPATIAL_ENTITY_COMPONENT_GET_INFO_BD = 1000389001, + XR_TYPE_SPATIAL_ENTITY_LOCATION_GET_INFO_BD = 1000389002, + XR_TYPE_SPATIAL_ENTITY_COMPONENT_DATA_LOCATION_BD = 1000389003, + XR_TYPE_SPATIAL_ENTITY_COMPONENT_DATA_SEMANTIC_BD = 1000389004, + XR_TYPE_SPATIAL_ENTITY_COMPONENT_DATA_BOUNDING_BOX_2D_BD = 1000389005, + XR_TYPE_SPATIAL_ENTITY_COMPONENT_DATA_POLYGON_BD = 1000389006, + XR_TYPE_SPATIAL_ENTITY_COMPONENT_DATA_BOUNDING_BOX_3D_BD = 1000389007, + XR_TYPE_SPATIAL_ENTITY_COMPONENT_DATA_TRIANGLE_MESH_BD = 1000389008, + XR_TYPE_SENSE_DATA_PROVIDER_CREATE_INFO_BD = 1000389009, + XR_TYPE_SENSE_DATA_PROVIDER_START_INFO_BD = 1000389010, + XR_TYPE_EVENT_DATA_SENSE_DATA_PROVIDER_STATE_CHANGED_BD = 1000389011, + XR_TYPE_EVENT_DATA_SENSE_DATA_UPDATED_BD = 1000389012, + XR_TYPE_SENSE_DATA_QUERY_INFO_BD = 1000389013, + XR_TYPE_SENSE_DATA_QUERY_COMPLETION_BD = 1000389014, + XR_TYPE_SENSE_DATA_FILTER_UUID_BD = 1000389015, + XR_TYPE_SENSE_DATA_FILTER_SEMANTIC_BD = 1000389016, + XR_TYPE_QUERIED_SENSE_DATA_GET_INFO_BD = 1000389017, + XR_TYPE_QUERIED_SENSE_DATA_BD = 1000389018, + XR_TYPE_SPATIAL_ENTITY_STATE_BD = 1000389019, + XR_TYPE_SPATIAL_ENTITY_ANCHOR_CREATE_INFO_BD = 1000389020, + XR_TYPE_ANCHOR_SPACE_CREATE_INFO_BD = 1000389021, + XR_TYPE_SPATIAL_ENTITY_COMPONENT_DATA_SPHERE_BD = 1000389022, + XR_TYPE_SYSTEM_SPATIAL_ANCHOR_PROPERTIES_BD = 1000390000, + XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_BD = 1000390001, + XR_TYPE_SPATIAL_ANCHOR_CREATE_COMPLETION_BD = 1000390002, + XR_TYPE_SPATIAL_ANCHOR_PERSIST_INFO_BD = 1000390003, + XR_TYPE_SPATIAL_ANCHOR_UNPERSIST_INFO_BD = 1000390004, + XR_TYPE_SYSTEM_SPATIAL_ANCHOR_SHARING_PROPERTIES_BD = 1000391000, + XR_TYPE_SPATIAL_ANCHOR_SHARE_INFO_BD = 1000391001, + XR_TYPE_SHARED_SPATIAL_ANCHOR_DOWNLOAD_INFO_BD = 1000391002, + XR_TYPE_SYSTEM_SPATIAL_SCENE_PROPERTIES_BD = 1000392000, + XR_TYPE_SCENE_CAPTURE_INFO_BD = 1000392001, + XR_TYPE_SYSTEM_SPATIAL_MESH_PROPERTIES_BD = 1000393000, + XR_TYPE_SENSE_DATA_PROVIDER_CREATE_INFO_SPATIAL_MESH_BD = 1000393001, + XR_TYPE_FUTURE_POLL_RESULT_PROGRESS_BD = 1000394001, + XR_TYPE_BODY_TRACKING_POSTURE_DATA_BD = 1000395001, + XR_TYPE_BODY_JOINT_VELOCITIES_BD = 1000395002, + XR_TYPE_BODY_JOINT_ACCELERATIONS_BD = 1000395003, + XR_TYPE_BODY_TRACKING_STATE_BD = 1000395004, + XR_TYPE_SYSTEM_SPATIAL_PLANE_PROPERTIES_BD = 1000396000, + XR_TYPE_SPATIAL_ENTITY_COMPONENT_DATA_PLANE_ORIENTATION_BD = 1000396001, + XR_TYPE_SENSE_DATA_FILTER_PLANE_ORIENTATION_BD = 1000396002, + XR_TYPE_SYSTEM_LIGHT_ESTIMATION_PROPERTIES_BD = 1000397000, + XR_TYPE_SENSE_DATA_PROVIDER_CREATE_INFO_LIGHT_ESTIMATION_BD = 1000397001, + XR_TYPE_ENVIRONMENT_TEXTURE_CREATE_CONFIG_INFO_BD = 1000397002, + XR_TYPE_LIGHT_ESTIMATION_DATA_ENVIRONMENT_TEXTURE_RAW_BD = 1000397003, + XR_TYPE_LIGHT_ESTIMATION_DATA_SPHERICAL_HARMONICS_BD = 1000397005, + XR_TYPE_SPATIAL_ENTITY_COMPONENT_DATA_LIGHT_ESTIMATION_BD = 1000397006, + XR_TYPE_SPATIAL_AUDIO_RENDERER_CREATE_INFO_BD = 1000409000, + XR_TYPE_AUDIO_BUFFER_BD = 1000409001, + XR_TYPE_SOUND_OBJECT_DIRECTIVITY_CARDIOID_BD = 1000409003, + XR_TYPE_SOUND_OBJECT_SHAPE_SPHERE_BD = 1000409004, + XR_TYPE_SOUND_OBJECT_DISTANCE_ATTENUATION_BD = 1000409005, + XR_TYPE_SOUND_OBJECT_DISTANCE_ATTENUATION_CURVE_BD = 1000409006, + XR_TYPE_SOUND_OBJECT_CONFIG_BD = 1000409007, + XR_TYPE_SOUND_FIELD_CONFIG_BD = 1000409008, + XR_TYPE_SOUND_FIELD_CHANNEL_DEFINITION_SURROUND_BD = 1000409009, + XR_TYPE_SOUND_FIELD_CHANNEL_DEFINITION_AMBIX_BD = 1000409010, + XR_TYPE_SOUND_FIELD_CHANNEL_DEFINITION_FUMA_BD = 1000409011, + XR_TYPE_SOUND_TRIANGLE_MESH_BD = 1000409012, + XR_TYPE_SOUND_OBSTACLE_CONFIG_BD = 1000409013, + XR_TYPE_SOUND_OBSTACLE_MATERIAL_CONFIG_BD = 1000409014, + XR_TYPE_HAND_TRACKING_DATA_SOURCE_INFO_EXT = 1000428000, + XR_TYPE_HAND_TRACKING_DATA_SOURCE_STATE_EXT = 1000428001, + XR_TYPE_PLANE_DETECTOR_CREATE_INFO_EXT = 1000429001, + XR_TYPE_PLANE_DETECTOR_BEGIN_INFO_EXT = 1000429002, + XR_TYPE_PLANE_DETECTOR_GET_INFO_EXT = 1000429003, + XR_TYPE_PLANE_DETECTOR_LOCATIONS_EXT = 1000429004, + XR_TYPE_PLANE_DETECTOR_LOCATION_EXT = 1000429005, + XR_TYPE_PLANE_DETECTOR_POLYGON_BUFFER_EXT = 1000429006, + XR_TYPE_SYSTEM_PLANE_DETECTION_PROPERTIES_EXT = 1000429007, + XR_TYPE_TRACKABLE_GET_INFO_ANDROID = 1000455000, + XR_TYPE_ANCHOR_SPACE_CREATE_INFO_ANDROID = 1000455001, + XR_TYPE_TRACKABLE_PLANE_ANDROID = 1000455003, + XR_TYPE_TRACKABLE_TRACKER_CREATE_INFO_ANDROID = 1000455004, + XR_TYPE_SYSTEM_TRACKABLES_PROPERTIES_ANDROID = 1000455005, + XR_TYPE_EYES_ANDROID = 1000456000, + XR_TYPE_EYE_TRACKER_CREATE_INFO_ANDROID = 1000456001, + XR_TYPE_EYES_GET_INFO_ANDROID = 1000456002, + XR_TYPE_SYSTEM_EYE_TRACKING_PROPERTIES_ANDROID = 1000456004, + XR_TYPE_PERSISTED_ANCHOR_SPACE_CREATE_INFO_ANDROID = 1000457001, + XR_TYPE_PERSISTED_ANCHOR_SPACE_INFO_ANDROID = 1000457002, + XR_TYPE_DEVICE_ANCHOR_PERSISTENCE_CREATE_INFO_ANDROID = 1000457003, + XR_TYPE_SYSTEM_DEVICE_ANCHOR_PERSISTENCE_PROPERTIES_ANDROID = 1000457004, + XR_TYPE_FACE_TRACKER_CREATE_INFO_ANDROID = 1000458000, + XR_TYPE_FACE_STATE_GET_INFO_ANDROID = 1000458001, + XR_TYPE_FACE_STATE_ANDROID = 1000458002, + XR_TYPE_SYSTEM_FACE_TRACKING_PROPERTIES_ANDROID = 1000458003, + XR_TYPE_PASSTHROUGH_CAMERA_STATE_GET_INFO_ANDROID = 1000460000, + XR_TYPE_SYSTEM_PASSTHROUGH_CAMERA_STATE_PROPERTIES_ANDROID = 1000460001, + XR_TYPE_EVENT_DATA_RECOMMENDED_RESOLUTION_CHANGED_ANDROID = 1000461000, + XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_ANDROID = 1000462000, + XR_TYPE_PASSTHROUGH_LAYER_MESH_ANDROID = 1000462001, + XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_ANDROID = 1000462002, + XR_TYPE_SYSTEM_PASSTHROUGH_LAYER_PROPERTIES_ANDROID = 1000462003, + XR_TYPE_RAYCAST_INFO_ANDROID = 1000463000, + XR_TYPE_RAYCAST_HIT_RESULTS_ANDROID = 1000463001, + XR_TYPE_PERFORMANCE_METRICS_STATE_ANDROID = 1000465000, + XR_TYPE_PERFORMANCE_METRICS_COUNTER_ANDROID = 1000465001, + XR_TYPE_TRACKABLE_OBJECT_ANDROID = 1000466000, + XR_TYPE_TRACKABLE_OBJECT_CONFIGURATION_ANDROID = 1000466001, + XR_TYPE_FUTURE_CANCEL_INFO_EXT = 1000469000, + XR_TYPE_FUTURE_POLL_INFO_EXT = 1000469001, + XR_TYPE_FUTURE_COMPLETION_EXT = 1000469002, + XR_TYPE_FUTURE_POLL_RESULT_EXT = 1000469003, + XR_TYPE_EVENT_DATA_USER_PRESENCE_CHANGED_EXT = 1000470000, + XR_TYPE_SYSTEM_USER_PRESENCE_PROPERTIES_EXT = 1000470001, + XR_TYPE_SYSTEM_NOTIFICATIONS_SET_INFO_ML = 1000473000, + XR_TYPE_WORLD_MESH_DETECTOR_CREATE_INFO_ML = 1000474001, + XR_TYPE_WORLD_MESH_STATE_REQUEST_INFO_ML = 1000474002, + XR_TYPE_WORLD_MESH_BLOCK_STATE_ML = 1000474003, + XR_TYPE_WORLD_MESH_STATE_REQUEST_COMPLETION_ML = 1000474004, + XR_TYPE_WORLD_MESH_BUFFER_RECOMMENDED_SIZE_INFO_ML = 1000474005, + XR_TYPE_WORLD_MESH_BUFFER_SIZE_ML = 1000474006, + XR_TYPE_WORLD_MESH_BUFFER_ML = 1000474007, + XR_TYPE_WORLD_MESH_BLOCK_REQUEST_ML = 1000474008, + XR_TYPE_WORLD_MESH_GET_INFO_ML = 1000474009, + XR_TYPE_WORLD_MESH_BLOCK_ML = 1000474010, + XR_TYPE_WORLD_MESH_REQUEST_COMPLETION_ML = 1000474011, + XR_TYPE_WORLD_MESH_REQUEST_COMPLETION_INFO_ML = 1000474012, + XR_TYPE_SYSTEM_FACIAL_EXPRESSION_PROPERTIES_ML = 1000482004, + XR_TYPE_FACIAL_EXPRESSION_CLIENT_CREATE_INFO_ML = 1000482005, + XR_TYPE_FACIAL_EXPRESSION_BLEND_SHAPE_GET_INFO_ML = 1000482006, + XR_TYPE_FACIAL_EXPRESSION_BLEND_SHAPE_PROPERTIES_ML = 1000482007, + XR_TYPE_SYSTEM_BOUNDARY_VISIBILITY_PROPERTIES_META = 1000528000, + XR_TYPE_EVENT_DATA_BOUNDARY_VISIBILITY_CHANGED_META = 1000528001, + XR_TYPE_SYSTEM_SIMULTANEOUS_HANDS_AND_CONTROLLERS_PROPERTIES_META = 1000532001, + XR_TYPE_SIMULTANEOUS_HANDS_AND_CONTROLLERS_TRACKING_RESUME_INFO_META = 1000532002, + XR_TYPE_SIMULTANEOUS_HANDS_AND_CONTROLLERS_TRACKING_PAUSE_INFO_META = 1000532003, + XR_TYPE_FACE_TRACKING_VISEMES_META = 1000541000, + XR_TYPE_SYSTEM_FACE_TRACKING_VISEMES_PROPERTIES_META = 1000541001, + XR_TYPE_ROOM_MESH_FACE_INDICES_META = 1000553000, + XR_TYPE_SPACE_ROOM_MESH_GET_INFO_META = 1000553001, + XR_TYPE_ROOM_MESH_META = 1000553002, + XR_TYPE_COLOCATION_DISCOVERY_START_INFO_META = 1000571010, + XR_TYPE_COLOCATION_DISCOVERY_STOP_INFO_META = 1000571011, + XR_TYPE_COLOCATION_ADVERTISEMENT_START_INFO_META = 1000571012, + XR_TYPE_COLOCATION_ADVERTISEMENT_STOP_INFO_META = 1000571013, + XR_TYPE_EVENT_DATA_START_COLOCATION_ADVERTISEMENT_COMPLETE_META = 1000571020, + XR_TYPE_EVENT_DATA_STOP_COLOCATION_ADVERTISEMENT_COMPLETE_META = 1000571021, + XR_TYPE_EVENT_DATA_COLOCATION_ADVERTISEMENT_COMPLETE_META = 1000571022, + XR_TYPE_EVENT_DATA_START_COLOCATION_DISCOVERY_COMPLETE_META = 1000571023, + XR_TYPE_EVENT_DATA_COLOCATION_DISCOVERY_RESULT_META = 1000571024, + XR_TYPE_EVENT_DATA_COLOCATION_DISCOVERY_COMPLETE_META = 1000571025, + XR_TYPE_EVENT_DATA_STOP_COLOCATION_DISCOVERY_COMPLETE_META = 1000571026, + XR_TYPE_SYSTEM_COLOCATION_DISCOVERY_PROPERTIES_META = 1000571030, + XR_TYPE_SHARE_SPACES_RECIPIENT_GROUPS_META = 1000572000, + XR_TYPE_SPACE_GROUP_UUID_FILTER_INFO_META = 1000572001, + XR_TYPE_SYSTEM_SPATIAL_ENTITY_GROUP_SHARING_PROPERTIES_META = 1000572100, + XR_TYPE_SYSTEM_ENVIRONMENT_RAYCAST_PROPERTIES_META = 1000592000, + XR_TYPE_ENVIRONMENT_RAYCASTER_CREATE_INFO_META = 1000592001, + XR_TYPE_ENVIRONMENT_RAYCASTER_CREATE_COMPLETION_META = 1000592002, + XR_TYPE_ENVIRONMENT_RAYCAST_HIT_GET_INFO_META = 1000592003, + XR_TYPE_ENVIRONMENT_RAYCAST_HIT_META = 1000592004, + XR_TYPE_ENVIRONMENT_RAYCAST_FILTER_DISTANCE_META = 1000592005, + XR_TYPE_TILE_PROPERTIES_META = 1000609000, + XR_TYPE_TILE_PROPERTIES_HINT_META = 1000609001, + XR_TYPE_HAND_TRACKING_UNEXTRAPOLATED_POSES_REQUEST_META = 1000693000, + XR_TYPE_HAND_TRACKING_UNEXTRAPOLATED_POSES_META = 1000693001, + XR_TYPE_LIGHT_ESTIMATOR_CREATE_INFO_ANDROID = 1000700000, + XR_TYPE_LIGHT_ESTIMATE_GET_INFO_ANDROID = 1000700001, + XR_TYPE_LIGHT_ESTIMATE_ANDROID = 1000700002, + XR_TYPE_DIRECTIONAL_LIGHT_ANDROID = 1000700003, + XR_TYPE_SPHERICAL_HARMONICS_ANDROID = 1000700004, + XR_TYPE_AMBIENT_LIGHT_ANDROID = 1000700005, + XR_TYPE_SYSTEM_LIGHT_ESTIMATION_PROPERTIES_ANDROID = 1000700006, + XR_TYPE_ANCHOR_SHARING_INFO_ANDROID = 1000701000, + XR_TYPE_ANCHOR_SHARING_TOKEN_ANDROID = 1000701001, + XR_TYPE_SYSTEM_ANCHOR_SHARING_EXPORT_PROPERTIES_ANDROID = 1000701002, + XR_TYPE_SYSTEM_MARKER_TRACKING_PROPERTIES_ANDROID = 1000707000, + XR_TYPE_TRACKABLE_MARKER_CONFIGURATION_ANDROID = 1000707001, + XR_TYPE_TRACKABLE_MARKER_ANDROID = 1000707002, + XR_TYPE_SYSTEM_QR_CODE_TRACKING_PROPERTIES_ANDROID = 1000708000, + XR_TYPE_TRACKABLE_QR_CODE_CONFIGURATION_ANDROID = 1000708001, + XR_TYPE_TRACKABLE_QR_CODE_ANDROID = 1000708002, + XR_TYPE_SYSTEM_IMAGE_TRACKING_PROPERTIES_ANDROID = 1000709000, + XR_TYPE_TRACKABLE_IMAGE_DATABASE_ENTRY_ANDROID = 1000709001, + XR_TYPE_TRACKABLE_IMAGE_DATABASE_CREATE_INFO_ANDROID = 1000709002, + XR_TYPE_CREATE_TRACKABLE_IMAGE_DATABASE_COMPLETION_ANDROID = 1000709003, + XR_TYPE_TRACKABLE_IMAGE_CONFIGURATION_ANDROID = 1000709004, + XR_TYPE_TRACKABLE_IMAGE_ANDROID = 1000709005, + XR_TYPE_EVENT_DATA_IMAGE_TRACKING_LOST_ANDROID = 1000709006, + XR_TYPE_SYSTEM_SCENE_MESHING_PROPERTIES_ANDROID = 1000718000, + XR_TYPE_SCENE_MESHING_TRACKER_CREATE_INFO_ANDROID = 1000718001, + XR_TYPE_SCENE_MESH_SNAPSHOT_CREATE_INFO_ANDROID = 1000718002, + XR_TYPE_SCENE_MESH_SNAPSHOT_CREATION_RESULT_ANDROID = 1000718003, + XR_TYPE_SCENE_SUBMESH_STATE_ANDROID = 1000718004, + XR_TYPE_SCENE_SUBMESH_DATA_ANDROID = 1000718005, + XR_TYPE_SPATIAL_CAPABILITY_COMPONENT_TYPES_EXT = 1000740000, + XR_TYPE_SPATIAL_CONTEXT_CREATE_INFO_EXT = 1000740001, + XR_TYPE_CREATE_SPATIAL_CONTEXT_COMPLETION_EXT = 1000740002, + XR_TYPE_SPATIAL_DISCOVERY_SNAPSHOT_CREATE_INFO_EXT = 1000740003, + XR_TYPE_CREATE_SPATIAL_DISCOVERY_SNAPSHOT_COMPLETION_INFO_EXT = 1000740004, + XR_TYPE_CREATE_SPATIAL_DISCOVERY_SNAPSHOT_COMPLETION_EXT = 1000740005, + XR_TYPE_SPATIAL_COMPONENT_DATA_QUERY_CONDITION_EXT = 1000740006, + XR_TYPE_SPATIAL_COMPONENT_DATA_QUERY_RESULT_EXT = 1000740007, + XR_TYPE_SPATIAL_BUFFER_GET_INFO_EXT = 1000740008, + XR_TYPE_SPATIAL_COMPONENT_BOUNDED_2D_LIST_EXT = 1000740009, + XR_TYPE_SPATIAL_COMPONENT_BOUNDED_3D_LIST_EXT = 1000740010, + XR_TYPE_SPATIAL_COMPONENT_PARENT_LIST_EXT = 1000740011, + XR_TYPE_SPATIAL_COMPONENT_MESH_3D_LIST_EXT = 1000740012, + XR_TYPE_SPATIAL_ENTITY_FROM_ID_CREATE_INFO_EXT = 1000740013, + XR_TYPE_SPATIAL_UPDATE_SNAPSHOT_CREATE_INFO_EXT = 1000740014, + XR_TYPE_EVENT_DATA_SPATIAL_DISCOVERY_RECOMMENDED_EXT = 1000740015, + XR_TYPE_SPATIAL_FILTER_TRACKING_STATE_EXT = 1000740016, + XR_TYPE_SPATIAL_CAPABILITY_CONFIGURATION_PLANE_TRACKING_EXT = 1000741000, + XR_TYPE_SPATIAL_COMPONENT_PLANE_ALIGNMENT_LIST_EXT = 1000741001, + XR_TYPE_SPATIAL_COMPONENT_MESH_2D_LIST_EXT = 1000741002, + XR_TYPE_SPATIAL_COMPONENT_POLYGON_2D_LIST_EXT = 1000741003, + XR_TYPE_SPATIAL_COMPONENT_PLANE_SEMANTIC_LABEL_LIST_EXT = 1000741004, + XR_TYPE_STATIONARY_REFERENCE_SPACE_GENERATION_ID_GET_INFO_EXT = 1000742001, + XR_TYPE_STATIONARY_REFERENCE_SPACE_GENERATION_ID_RESULT_EXT = 1000742002, + XR_TYPE_SPATIAL_CAPABILITY_CONFIGURATION_QR_CODE_EXT = 1000743000, + XR_TYPE_SPATIAL_CAPABILITY_CONFIGURATION_MICRO_QR_CODE_EXT = 1000743001, + XR_TYPE_SPATIAL_CAPABILITY_CONFIGURATION_ARUCO_MARKER_EXT = 1000743002, + XR_TYPE_SPATIAL_CAPABILITY_CONFIGURATION_APRIL_TAG_EXT = 1000743003, + XR_TYPE_SPATIAL_MARKER_SIZE_EXT = 1000743004, + XR_TYPE_SPATIAL_MARKER_STATIC_OPTIMIZATION_EXT = 1000743005, + XR_TYPE_SPATIAL_COMPONENT_MARKER_LIST_EXT = 1000743006, + XR_TYPE_SYSTEM_DYNAMIC_OBJECT_TRACKING_PROPERTIES_BD = 1000746000, + XR_TYPE_SENSE_DATA_PROVIDER_CREATE_INFO_DYNAMIC_OBJECT_BD = 1000746001, + XR_TYPE_SPATIAL_ENTITY_COMPONENT_DATA_DYNAMIC_OBJECT_BD = 1000746002, + XR_TYPE_DYNAMIC_OBJECT_DATA_BD = 1000746003, + XR_TYPE_SENSE_DATA_FILTER_DYNAMIC_OBJECT_TYPE_BD = 1000746004, + XR_TYPE_SYSTEM_DYNAMIC_OBJECT_KEYBOARD_PROPERTIES_BD = 1000747000, + XR_TYPE_SYSTEM_DYNAMIC_OBJECT_MOUSE_PROPERTIES_BD = 1000748000, + XR_TYPE_AVAILABLE_CAMERAS_ENUMERATE_INFO_BD = 1000755000, + XR_TYPE_CAMERA_PROPERTIES_GET_INFO_BD = 1000755001, + XR_TYPE_CAMERA_PROPERTIES_BD = 1000755002, + XR_TYPE_CAMERA_PROPERTY_FACING_BD = 1000755003, + XR_TYPE_CAMERA_PROPERTY_POSITION_BD = 1000755004, + XR_TYPE_CAMERA_PROPERTY_CAMERA_TYPE_BD = 1000755005, + XR_TYPE_CAMERA_SUPPORTED_CAPABILITIES_GET_INFO_BD = 1000755006, + XR_TYPE_CAMERA_SUPPORTED_CAPABILITIES_BD = 1000755007, + XR_TYPE_CAMERA_SUPPORTED_CAPABILITY_IMAGE_RESOLUTION_AND_FRAME_RATE_BD = 1000755008, + XR_TYPE_CAMERA_CAPABILITY_IMAGE_RESOLUTION_AND_FRAME_RATE_BD = 1000755009, + XR_TYPE_CAMERA_SUPPORTED_CAPABILITY_DATA_TRANSFER_TYPE_BD = 1000755010, + XR_TYPE_CAMERA_CAPABILITY_DATA_TRANSFER_TYPE_BD = 1000755011, + XR_TYPE_CAMERA_SUPPORTED_CAPABILITY_IMAGE_FORMAT_BD = 1000755012, + XR_TYPE_CAMERA_CAPABILITY_IMAGE_FORMAT_BD = 1000755013, + XR_TYPE_CAMERA_SUPPORTED_CAPABILITY_CAMERA_MODEL_BD = 1000755014, + XR_TYPE_CAMERA_CAPABILITY_CAMERA_MODEL_BD = 1000755015, + XR_TYPE_CAMERA_DEVICE_CREATE_INFO_BD = 1000755016, + XR_TYPE_CREATE_CAMERA_DEVICE_COMPLETION_BD = 1000755017, + XR_TYPE_CAMERA_CAPTURE_SESSION_CREATE_INFO_BD = 1000755018, + XR_TYPE_CREATE_CAMERA_CAPTURE_SESSION_COMPLETION_BD = 1000755019, + XR_TYPE_CAMERA_INTRINSICS_BD = 1000755020, + XR_TYPE_CAMERA_EXTRINSICS_BD = 1000755021, + XR_TYPE_CAMERA_CAPTURE_BEGIN_INFO_BD = 1000755022, + XR_TYPE_CAMERA_IMAGE_ACQUIRE_INFO_BD = 1000755023, + XR_TYPE_CAMERA_IMAGE_BD = 1000755024, + XR_TYPE_CAMERA_IMAGE_DATA_RAW_BUFFER_BD = 1000755025, + XR_TYPE_CAMERA_CAPABILITIES_BD = 1000755026, + XR_TYPE_CAMERA_CAPABILITY_TYPES_ENUMERATE_INFO_BD = 1000755027, + XR_TYPE_CAMERA_CAPABILITY_TYPES_BD = 1000755028, + XR_TYPE_CAMERA_PROPERTY_TYPES_ENUMERATE_INFO_BD = 1000755029, + XR_TYPE_CAMERA_PROPERTY_TYPES_BD = 1000755030, + XR_TYPE_AVAILABLE_CAMERA_BD = 1000755031, + XR_TYPE_SPATIAL_BOUNDS_SPHEREF_ANDROID = 1000761000, + XR_TYPE_SPATIAL_BOUNDS_BOXF_ANDROID = 1000761001, + XR_TYPE_SPATIAL_BOUNDS_FRUSTUMF_ANDROID = 1000761002, + XR_TYPE_SPATIAL_CAPABILITY_CONFIGURATION_ANCHOR_EXT = 1000762000, + XR_TYPE_SPATIAL_COMPONENT_ANCHOR_LIST_EXT = 1000762001, + XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_EXT = 1000762002, + XR_TYPE_SPATIAL_PERSISTENCE_CONTEXT_CREATE_INFO_EXT = 1000763000, + XR_TYPE_CREATE_SPATIAL_PERSISTENCE_CONTEXT_COMPLETION_EXT = 1000763001, + XR_TYPE_SPATIAL_CONTEXT_PERSISTENCE_CONFIG_EXT = 1000763002, + XR_TYPE_SPATIAL_DISCOVERY_PERSISTENCE_UUID_FILTER_EXT = 1000763003, + XR_TYPE_SPATIAL_COMPONENT_PERSISTENCE_LIST_EXT = 1000763004, + XR_TYPE_HAPTIC_PARAMETRIC_VIBRATION_EXT = 1000775000, + XR_TYPE_HAPTIC_PARAMETRIC_PROPERTIES_EXT = 1000775001, + XR_TYPE_SYSTEM_HAPTIC_PARAMETRIC_PROPERTIES_EXT = 1000775002, + XR_TYPE_COLOR_SPACES_ENUMERATE_INFO_SONY = 1000776000, + XR_TYPE_SWAPCHAIN_CREATE_INFO_COLOR_SPACE_SONY = 1000776001, + XR_TYPE_HDR_METADATA_SONY = 1000777000, + XR_TYPE_SPATIAL_ENTITY_PERSIST_INFO_EXT = 1000781000, + XR_TYPE_PERSIST_SPATIAL_ENTITY_COMPLETION_EXT = 1000781001, + XR_TYPE_SPATIAL_ENTITY_UNPERSIST_INFO_EXT = 1000781002, + XR_TYPE_UNPERSIST_SPATIAL_ENTITY_COMPLETION_EXT = 1000781003, + XR_TYPE_SPATIAL_REFERENCE_IMAGE_EXT = 1000782000, + XR_TYPE_SPATIAL_IMAGE_STATIC_OPTIMIZATION_EXT = 1000782001, + XR_TYPE_SPATIAL_IMAGE_SIZE_EXT = 1000782002, + XR_TYPE_SPATIAL_CAPABILITY_CONFIGURATION_IMAGE_TRACKING_EXT = 1000782003, + XR_TYPE_SPATIAL_IMAGE_TRACKING_DATABASE_CREATE_INFO_EXT = 1000782004, + XR_TYPE_SPATIAL_COMPONENT_IMAGE_2D_LIST_EXT = 1000782005, + XR_TYPE_CREATE_SPATIAL_IMAGE_TRACKING_DATABASE_COMPLETION_EXT = 1000782006, + XR_TYPE_SPATIAL_CAPABILITY_CONFIGURATION_OBJECT_TRACKING_ANDROID = 1000785000, + XR_TYPE_SPATIAL_COMPONENT_OBJECT_SEMANTIC_LABEL_LIST_ANDROID = 1000785001, + XR_TYPE_SPATIAL_CAPABILITY_CONFIGURATION_DEPTH_RAYCAST_ANDROID = 1000786000, + XR_TYPE_SPATIAL_RAYCAST_INFO_ANDROID = 1000786001, + XR_TYPE_SPATIAL_COMPONENT_RAYCAST_RESULT_LIST_ANDROID = 1000786002, + XR_TYPE_SPATIAL_RAYCAST_SNAPSHOT_CREATE_INFO_ANDROID = 1000786003, + XR_TYPE_GOOGLE_CLOUD_AUTH_INFO_API_KEY_ANDROID = 1000787000, + XR_TYPE_GOOGLE_CLOUD_AUTH_INFO_TOKEN_ANDROID = 1000787001, + XR_TYPE_GOOGLE_CLOUD_AUTH_INFO_KEYLESS_ANDROID = 1000787002, + XR_TYPE_GOOGLE_CLOUD_AUTH_ERROR_RESULT_ANDROID = 1000787003, + XR_TYPE_SYSTEM_GEOSPATIAL_PROPERTIES_ANDROID = 1000789000, + XR_TYPE_GEOSPATIAL_TRACKER_CREATE_INFO_ANDROID = 1000789001, + XR_TYPE_EVENT_DATA_GEOSPATIAL_TRACKER_STATE_CHANGED_ANDROID = 1000789002, + XR_TYPE_GEOSPATIAL_POSE_FROM_POSE_LOCATE_INFO_ANDROID = 1000789003, + XR_TYPE_GEOSPATIAL_POSE_RESULT_ANDROID = 1000789004, + XR_TYPE_GEOSPATIAL_POSE_LOCATE_INFO_ANDROID = 1000789005, + XR_TYPE_VPS_AVAILABILITY_CHECK_COMPLETION_ANDROID = 1000789006, + XR_TYPE_SPATIAL_ANCHOR_PARENT_ANDROID = 1000790000, + XR_TYPE_SPATIAL_DISCOVERY_UNIQUE_ENTITIES_FILTER_ANDROID = 1000791001, + XR_TYPE_SPATIAL_COMPONENT_SUBSUMED_BY_LIST_ANDROID = 1000791002, + XR_TYPE_SPATIAL_ANCHOR_SPACE_FROM_ID_CREATE_INFO_ANDROID = 1000795000, + XR_TYPE_GEOSPATIAL_ANCHOR_CREATE_INFO_ANDROID = 1000797000, + XR_TYPE_SURFACE_ANCHOR_CREATE_INFO_ANDROID = 1000797001, + XR_TYPE_SURFACE_ANCHOR_CREATE_COMPLETION_ANDROID = 1000797002, + XR_TYPE_SYSTEM_GEOSPATIAL_ANCHOR_PROPERTIES_ANDROID = 1000797003, + XR_TYPE_GEOSPATIAL_TRACKER_ANCHOR_TRACKING_INFO_ANDROID = 1000797004, + XR_TYPE_SPATIAL_CONTAINER_CREATE_INFO_EXT = 1000810000, + XR_TYPE_SPATIAL_CONTAINER_SPACE_CREATE_INFO_EXT = 1000810001, + XR_TYPE_EVENT_DATA_SPATIAL_CONTAINER_CLOSED_EXT = 1000810003, + XR_TYPE_SYSTEM_SPATIAL_CONTAINER_PROPERTIES_EXT = 1000810004, + XR_TYPE_SPATIAL_CONTAINER_BOUNDS_EXT = 1000810005, + XR_TYPE_EVENT_DATA_SPATIAL_CONTAINER_BOUNDS_CHANGED_EXT = 1000810006, + XR_TYPE_SPATIAL_CONTAINER_BOUNDS_GET_INFO_EXT = 1000810007, + XR_TYPE_SPATIAL_CONTAINER_STATE_GET_INFO_EXT = 1000810008, + XR_TYPE_SPATIAL_CONTAINER_VISIBLE_REQUEST_INFO_EXT = 1000810009, + XR_TYPE_EVENT_DATA_SPATIAL_CONTAINER_VISIBLE_CHANGED_EXT = 1000810010, + XR_TYPE_EVENT_DATA_SPATIAL_CONTAINER_VISIBLE_REQUEST_DENIED_EXT = 1000810011, + XR_TYPE_EVENT_DATA_SPATIAL_CONTAINER_INTERACTABLE_CHANGED_EXT = 1000810012, + XR_TYPE_SPATIAL_CONTAINER_BOUNDS_MODE_REQUEST_INFO_EXT = 1000810013, + XR_TYPE_EVENT_DATA_SPATIAL_CONTAINER_BOUNDS_MODE_REQUEST_DENIED_EXT = 1000810014, + XR_TYPE_SPATIAL_CONTAINER_STATE_EXT = 1000810015, + XR_TYPE_SESSION_CREATE_INFO_SPATIAL_CONTAINERS_EXT = 1000810016, + XR_TYPE_SPATIAL_CONTAINER_VIEW_LOCATE_INFO_EXT = 1000813000, + XR_TYPE_SPATIAL_CONTAINER_VIEWS_LOCATE_INFO_EXT = 1000813001, + XR_TYPE_SPATIAL_CONTAINER_VIEW_STATE_EXT = 1000813002, + XR_TYPE_SPATIAL_CONTAINER_LAYER_EXT = 1000813003, + XR_TYPE_SPATIAL_CONTAINER_LAYER_FRAME_END_INFO_EXT = 1000813004, + XR_TYPE_SPATIAL_CONTAINER_BEGIN_INFO_EXT = 1000813005, + XR_TYPE_SPATIAL_CONTAINER_COMPOSITION_LAYER_VIEW_CONFIGURATION_EXT = 1000813006, + XR_TYPE_SPATIAL_CONTAINER_END_INFO_EXT = 1000813007, + XR_TYPE_SPATIAL_CONTAINER_LAYER_VOLUME_CLIPPING_EXT = 1000813008, + XR_TYPE_BATTERY_STATE_DISPLAY_EXT = 1000836000, + XR_TYPE_LOADER_INIT_INFO_PROPERTIES_EXT = 1000838000, + XR_TYPE_EVENT_DATA_VIEW_CONFIGURATION_VIEWS_CHANGED_EXT = 1000839000, + XR_TYPE_GRAPHICS_BINDING_VULKAN2_KHR = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR, + XR_TYPE_SWAPCHAIN_IMAGE_VULKAN2_KHR = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR, + XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN2_KHR = XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR, + XR_TYPE_DEVICE_PCM_SAMPLE_RATE_GET_INFO_FB = XR_TYPE_DEVICE_PCM_SAMPLE_RATE_STATE_FB, + XR_TYPE_SPACES_LOCATE_INFO_KHR = XR_TYPE_SPACES_LOCATE_INFO, + XR_TYPE_SPACE_LOCATIONS_KHR = XR_TYPE_SPACE_LOCATIONS, + XR_TYPE_SPACE_VELOCITIES_KHR = XR_TYPE_SPACE_VELOCITIES, + XR_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF +} XrStructureType; + +typedef enum XrFormFactor { + XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY = 1, + XR_FORM_FACTOR_HANDHELD_DISPLAY = 2, + XR_FORM_FACTOR_MAX_ENUM = 0x7FFFFFFF +} XrFormFactor; + +typedef enum XrEnvironmentBlendMode { + XR_ENVIRONMENT_BLEND_MODE_OPAQUE = 1, + XR_ENVIRONMENT_BLEND_MODE_ADDITIVE = 2, + XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND = 3, + XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM = 0x7FFFFFFF +} XrEnvironmentBlendMode; + +typedef enum XrViewConfigurationType { + XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO = 1, + XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO = 2, + XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO_WITH_FOVEATED_INSET = 1000037000, + XR_VIEW_CONFIGURATION_TYPE_SECONDARY_MONO_FIRST_PERSON_OBSERVER_MSFT = 1000054000, + XR_VIEW_CONFIGURATION_TYPE_PRIMARY_QUAD_VARJO = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO_WITH_FOVEATED_INSET, + XR_VIEW_CONFIGURATION_TYPE_MAX_ENUM = 0x7FFFFFFF +} XrViewConfigurationType; + +typedef enum XrReferenceSpaceType { + XR_REFERENCE_SPACE_TYPE_VIEW = 1, + XR_REFERENCE_SPACE_TYPE_LOCAL = 2, + XR_REFERENCE_SPACE_TYPE_STAGE = 3, + XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR = 1000426000, + XR_REFERENCE_SPACE_TYPE_UNBOUNDED_MSFT = 1000038000, + XR_REFERENCE_SPACE_TYPE_COMBINED_EYE_VARJO = 1000121000, + XR_REFERENCE_SPACE_TYPE_LOCALIZATION_MAP_ML = 1000139000, + XR_REFERENCE_SPACE_TYPE_UNBOUNDED_ANDROID = 1000467000, + XR_REFERENCE_SPACE_TYPE_STATIONARY_EXT = 1000742000, + XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR, + XR_REFERENCE_SPACE_TYPE_MAX_ENUM = 0x7FFFFFFF +} XrReferenceSpaceType; + +typedef enum XrEyeVisibility { + XR_EYE_VISIBILITY_BOTH = 0, + XR_EYE_VISIBILITY_LEFT = 1, + XR_EYE_VISIBILITY_RIGHT = 2, + XR_EYE_VISIBILITY_MAX_ENUM = 0x7FFFFFFF +} XrEyeVisibility; + +typedef enum XrActionType { + XR_ACTION_TYPE_BOOLEAN_INPUT = 1, + XR_ACTION_TYPE_FLOAT_INPUT = 2, + XR_ACTION_TYPE_VECTOR2F_INPUT = 3, + XR_ACTION_TYPE_POSE_INPUT = 4, + XR_ACTION_TYPE_VIBRATION_OUTPUT = 100, + XR_ACTION_TYPE_MAX_ENUM = 0x7FFFFFFF +} XrActionType; + +typedef enum XrSessionState { + XR_SESSION_STATE_UNKNOWN = 0, + XR_SESSION_STATE_IDLE = 1, + XR_SESSION_STATE_READY = 2, + XR_SESSION_STATE_SYNCHRONIZED = 3, + XR_SESSION_STATE_VISIBLE = 4, + XR_SESSION_STATE_FOCUSED = 5, + XR_SESSION_STATE_STOPPING = 6, + XR_SESSION_STATE_LOSS_PENDING = 7, + XR_SESSION_STATE_EXITING = 8, + XR_SESSION_STATE_MAX_ENUM = 0x7FFFFFFF +} XrSessionState; + +typedef enum XrObjectType { + XR_OBJECT_TYPE_UNKNOWN = 0, + XR_OBJECT_TYPE_INSTANCE = 1, + XR_OBJECT_TYPE_SESSION = 2, + XR_OBJECT_TYPE_SWAPCHAIN = 3, + XR_OBJECT_TYPE_SPACE = 4, + XR_OBJECT_TYPE_ACTION_SET = 5, + XR_OBJECT_TYPE_ACTION = 6, + XR_OBJECT_TYPE_DEBUG_UTILS_MESSENGER_EXT = 1000019000, + XR_OBJECT_TYPE_SPATIAL_ANCHOR_MSFT = 1000039000, + XR_OBJECT_TYPE_SPATIAL_GRAPH_NODE_BINDING_MSFT = 1000049000, + XR_OBJECT_TYPE_HAND_TRACKER_EXT = 1000051000, + XR_OBJECT_TYPE_BODY_TRACKER_FB = 1000076000, + XR_OBJECT_TYPE_SCENE_OBSERVER_MSFT = 1000097000, + XR_OBJECT_TYPE_SCENE_MSFT = 1000097001, + XR_OBJECT_TYPE_FACIAL_TRACKER_HTC = 1000104000, + XR_OBJECT_TYPE_FOVEATION_PROFILE_FB = 1000114000, + XR_OBJECT_TYPE_TRIANGLE_MESH_FB = 1000117000, + XR_OBJECT_TYPE_PASSTHROUGH_FB = 1000118000, + XR_OBJECT_TYPE_PASSTHROUGH_LAYER_FB = 1000118002, + XR_OBJECT_TYPE_GEOMETRY_INSTANCE_FB = 1000118004, + XR_OBJECT_TYPE_MARKER_DETECTOR_ML = 1000138000, + XR_OBJECT_TYPE_EXPORTED_LOCALIZATION_MAP_ML = 1000139000, + XR_OBJECT_TYPE_SPATIAL_ANCHORS_STORAGE_ML = 1000141000, + XR_OBJECT_TYPE_SPATIAL_ANCHOR_STORE_CONNECTION_MSFT = 1000142000, + XR_OBJECT_TYPE_FACE_TRACKER_FB = 1000201000, + XR_OBJECT_TYPE_EYE_TRACKER_FB = 1000202000, + XR_OBJECT_TYPE_VIRTUAL_KEYBOARD_META = 1000219000, + XR_OBJECT_TYPE_SPACE_USER_FB = 1000241000, + XR_OBJECT_TYPE_PASSTHROUGH_COLOR_LUT_META = 1000266000, + XR_OBJECT_TYPE_FACE_TRACKER2_FB = 1000287012, + XR_OBJECT_TYPE_ENVIRONMENT_DEPTH_PROVIDER_META = 1000291000, + XR_OBJECT_TYPE_ENVIRONMENT_DEPTH_SWAPCHAIN_META = 1000291001, + XR_OBJECT_TYPE_RENDER_MODEL_EXT = 1000300000, + XR_OBJECT_TYPE_RENDER_MODEL_ASSET_EXT = 1000300001, + XR_OBJECT_TYPE_PASSTHROUGH_HTC = 1000317000, + XR_OBJECT_TYPE_BODY_TRACKER_HTC = 1000320000, + XR_OBJECT_TYPE_BODY_TRACKER_BD = 1000385000, + XR_OBJECT_TYPE_FACE_TRACKER_BD = 1000386000, + XR_OBJECT_TYPE_SENSE_DATA_PROVIDER_BD = 1000389000, + XR_OBJECT_TYPE_SENSE_DATA_SNAPSHOT_BD = 1000389001, + XR_OBJECT_TYPE_ANCHOR_BD = 1000389002, + XR_OBJECT_TYPE_SPATIAL_AUDIO_RENDERER_BD = 1000409000, + XR_OBJECT_TYPE_SOUND_FIELD_BD = 1000409001, + XR_OBJECT_TYPE_SOUND_OBJECT_BD = 1000409002, + XR_OBJECT_TYPE_SOUND_OBSTACLE_BD = 1000409003, + XR_OBJECT_TYPE_SOUND_OBSTACLE_MATERIAL_BD = 1000409004, + XR_OBJECT_TYPE_PLANE_DETECTOR_EXT = 1000429000, + XR_OBJECT_TYPE_TRACKABLE_TRACKER_ANDROID = 1000455001, + XR_OBJECT_TYPE_EYE_TRACKER_ANDROID = 1000456000, + XR_OBJECT_TYPE_DEVICE_ANCHOR_PERSISTENCE_ANDROID = 1000457000, + XR_OBJECT_TYPE_FACE_TRACKER_ANDROID = 1000458000, + XR_OBJECT_TYPE_PASSTHROUGH_LAYER_ANDROID = 1000462000, + XR_OBJECT_TYPE_WORLD_MESH_DETECTOR_ML = 1000474000, + XR_OBJECT_TYPE_FACIAL_EXPRESSION_CLIENT_ML = 1000482000, + XR_OBJECT_TYPE_ENVIRONMENT_RAYCASTER_META = 1000592000, + XR_OBJECT_TYPE_LIGHT_ESTIMATOR_ANDROID = 1000700000, + XR_OBJECT_TYPE_TRACKABLE_IMAGE_DATABASE_ANDROID = 1000709000, + XR_OBJECT_TYPE_SCENE_MESHING_TRACKER_ANDROID = 1000718000, + XR_OBJECT_TYPE_SCENE_MESH_SNAPSHOT_ANDROID = 1000718001, + XR_OBJECT_TYPE_SPATIAL_ENTITY_EXT = 1000740000, + XR_OBJECT_TYPE_SPATIAL_CONTEXT_EXT = 1000740001, + XR_OBJECT_TYPE_SPATIAL_SNAPSHOT_EXT = 1000740002, + XR_OBJECT_TYPE_CAMERA_DEVICE_BD = 1000755000, + XR_OBJECT_TYPE_CAMERA_CAPTURE_SESSION_BD = 1000755001, + XR_OBJECT_TYPE_SPATIAL_PERSISTENCE_CONTEXT_EXT = 1000763000, + XR_OBJECT_TYPE_SPATIAL_IMAGE_TRACKING_DATABASE_EXT = 1000782000, + XR_OBJECT_TYPE_GEOSPATIAL_TRACKER_ANDROID = 1000789000, + XR_OBJECT_TYPE_SPATIAL_CONTAINER_EXT = 1000810000, + XR_OBJECT_TYPE_MAX_ENUM = 0x7FFFFFFF +} XrObjectType; +typedef XrFlags64 XrInstanceCreateFlags; + +// Flag bits for XrInstanceCreateFlags + +typedef XrFlags64 XrSessionCreateFlags; + +// Flag bits for XrSessionCreateFlags + +typedef XrFlags64 XrSpaceLocationFlags; + +// Flag bits for XrSpaceLocationFlags +static const XrSpaceLocationFlags XR_SPACE_LOCATION_ORIENTATION_VALID_BIT = 0x00000001; +static const XrSpaceLocationFlags XR_SPACE_LOCATION_POSITION_VALID_BIT = 0x00000002; +static const XrSpaceLocationFlags XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT = 0x00000004; +static const XrSpaceLocationFlags XR_SPACE_LOCATION_POSITION_TRACKED_BIT = 0x00000008; + +typedef XrFlags64 XrSpaceVelocityFlags; + +// Flag bits for XrSpaceVelocityFlags +static const XrSpaceVelocityFlags XR_SPACE_VELOCITY_LINEAR_VALID_BIT = 0x00000001; +static const XrSpaceVelocityFlags XR_SPACE_VELOCITY_ANGULAR_VALID_BIT = 0x00000002; + +typedef XrFlags64 XrSwapchainCreateFlags; + +// Flag bits for XrSwapchainCreateFlags +static const XrSwapchainCreateFlags XR_SWAPCHAIN_CREATE_PROTECTED_CONTENT_BIT = 0x00000001; +static const XrSwapchainCreateFlags XR_SWAPCHAIN_CREATE_STATIC_IMAGE_BIT = 0x00000002; + +typedef XrFlags64 XrSwapchainUsageFlags; + +// Flag bits for XrSwapchainUsageFlags +static const XrSwapchainUsageFlags XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT = 0x00000001; +static const XrSwapchainUsageFlags XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT = 0x00000002; +static const XrSwapchainUsageFlags XR_SWAPCHAIN_USAGE_UNORDERED_ACCESS_BIT = 0x00000004; +static const XrSwapchainUsageFlags XR_SWAPCHAIN_USAGE_TRANSFER_SRC_BIT = 0x00000008; +static const XrSwapchainUsageFlags XR_SWAPCHAIN_USAGE_TRANSFER_DST_BIT = 0x00000010; +static const XrSwapchainUsageFlags XR_SWAPCHAIN_USAGE_SAMPLED_BIT = 0x00000020; +static const XrSwapchainUsageFlags XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT = 0x00000040; +static const XrSwapchainUsageFlags XR_SWAPCHAIN_USAGE_INPUT_ATTACHMENT_BIT_MND = 0x00000080; +static const XrSwapchainUsageFlags XR_SWAPCHAIN_USAGE_INPUT_ATTACHMENT_BIT_KHR = 0x00000080; // alias of XR_SWAPCHAIN_USAGE_INPUT_ATTACHMENT_BIT_MND + +typedef XrFlags64 XrCompositionLayerFlags; + +// Flag bits for XrCompositionLayerFlags +// XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT is deprecated and should not be used +static const XrCompositionLayerFlags XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT = 0x00000001; +static const XrCompositionLayerFlags XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT = 0x00000002; +static const XrCompositionLayerFlags XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT = 0x00000004; +static const XrCompositionLayerFlags XR_COMPOSITION_LAYER_INVERTED_ALPHA_BIT_EXT = 0x00000008; + +typedef XrFlags64 XrViewStateFlags; + +// Flag bits for XrViewStateFlags +static const XrViewStateFlags XR_VIEW_STATE_ORIENTATION_VALID_BIT = 0x00000001; +static const XrViewStateFlags XR_VIEW_STATE_POSITION_VALID_BIT = 0x00000002; +static const XrViewStateFlags XR_VIEW_STATE_ORIENTATION_TRACKED_BIT = 0x00000004; +static const XrViewStateFlags XR_VIEW_STATE_POSITION_TRACKED_BIT = 0x00000008; + +typedef XrFlags64 XrInputSourceLocalizedNameFlags; + +// Flag bits for XrInputSourceLocalizedNameFlags +static const XrInputSourceLocalizedNameFlags XR_INPUT_SOURCE_LOCALIZED_NAME_USER_PATH_BIT = 0x00000001; +static const XrInputSourceLocalizedNameFlags XR_INPUT_SOURCE_LOCALIZED_NAME_INTERACTION_PROFILE_BIT = 0x00000002; +static const XrInputSourceLocalizedNameFlags XR_INPUT_SOURCE_LOCALIZED_NAME_COMPONENT_BIT = 0x00000004; + +typedef void (XRAPI_PTR *PFN_xrVoidFunction)(void); +typedef struct XrApiLayerProperties { + XrStructureType type; + void* XR_MAY_ALIAS next; + char layerName[XR_MAX_API_LAYER_NAME_SIZE]; + XrVersion specVersion; + uint32_t layerVersion; + char description[XR_MAX_API_LAYER_DESCRIPTION_SIZE]; +} XrApiLayerProperties; + +typedef struct XrExtensionProperties { + XrStructureType type; + void* XR_MAY_ALIAS next; + char extensionName[XR_MAX_EXTENSION_NAME_SIZE]; + uint32_t extensionVersion; +} XrExtensionProperties; + +typedef struct XrApplicationInfo { + char applicationName[XR_MAX_APPLICATION_NAME_SIZE]; + uint32_t applicationVersion; + char engineName[XR_MAX_ENGINE_NAME_SIZE]; + uint32_t engineVersion; + XrVersion apiVersion; +} XrApplicationInfo; + +typedef struct XrInstanceCreateInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrInstanceCreateFlags createFlags; + XrApplicationInfo applicationInfo; + uint32_t enabledApiLayerCount; + const char* const* enabledApiLayerNames; + uint32_t enabledExtensionCount; + const char* const* enabledExtensionNames; +} XrInstanceCreateInfo; + +typedef struct XrInstanceProperties { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrVersion runtimeVersion; + char runtimeName[XR_MAX_RUNTIME_NAME_SIZE]; +} XrInstanceProperties; + +typedef struct XrEventDataBuffer { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint8_t varying[4000]; +} XrEventDataBuffer; + +typedef struct XrSystemGetInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrFormFactor formFactor; +} XrSystemGetInfo; + +typedef struct XrSystemGraphicsProperties { + uint32_t maxSwapchainImageHeight; + uint32_t maxSwapchainImageWidth; + uint32_t maxLayerCount; +} XrSystemGraphicsProperties; + +typedef struct XrSystemTrackingProperties { + XrBool32 orientationTracking; + XrBool32 positionTracking; +} XrSystemTrackingProperties; + +typedef struct XrSystemProperties { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrSystemId systemId; + uint32_t vendorId; + char systemName[XR_MAX_SYSTEM_NAME_SIZE]; + XrSystemGraphicsProperties graphicsProperties; + XrSystemTrackingProperties trackingProperties; +} XrSystemProperties; + +typedef struct XrSessionCreateInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSessionCreateFlags createFlags; + XrSystemId systemId; +} XrSessionCreateInfo; + +typedef struct XrQuaternionf { + float x; + float y; + float z; + float w; +} XrQuaternionf; + +typedef struct XrVector3f { + float x; + float y; + float z; +} XrVector3f; + +typedef struct XrPosef { + XrQuaternionf orientation; + XrVector3f position; +} XrPosef; + +typedef struct XrReferenceSpaceCreateInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrReferenceSpaceType referenceSpaceType; + XrPosef poseInReferenceSpace; +} XrReferenceSpaceCreateInfo; + +typedef struct XrActionSpaceCreateInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAction action; + XrPath subactionPath; + XrPosef poseInActionSpace; +} XrActionSpaceCreateInfo; + +typedef struct XrSpaceLocation { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrSpaceLocationFlags locationFlags; + XrPosef pose; +} XrSpaceLocation; + +// XrSpaceVelocity extends XrSpaceLocation +typedef struct XrSpaceVelocity { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrSpaceVelocityFlags velocityFlags; + XrVector3f linearVelocity; + XrVector3f angularVelocity; +} XrSpaceVelocity; + +typedef struct XrExtent2Df { + float width; + float height; +} XrExtent2Df; + +typedef struct XrViewConfigurationProperties { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrViewConfigurationType viewConfigurationType; + XrBool32 fovMutable; +} XrViewConfigurationProperties; + +typedef struct XrViewConfigurationView { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t recommendedImageRectWidth; + uint32_t maxImageRectWidth; + uint32_t recommendedImageRectHeight; + uint32_t maxImageRectHeight; + uint32_t recommendedSwapchainSampleCount; + uint32_t maxSwapchainSampleCount; +} XrViewConfigurationView; + +typedef struct XrSwapchainCreateInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSwapchainCreateFlags createFlags; + XrSwapchainUsageFlags usageFlags; + int64_t format; + uint32_t sampleCount; + uint32_t width; + uint32_t height; + uint32_t faceCount; + uint32_t arraySize; + uint32_t mipCount; +} XrSwapchainCreateInfo; + +typedef struct XR_MAY_ALIAS XrSwapchainImageBaseHeader { + XrStructureType type; + void* XR_MAY_ALIAS next; +} XrSwapchainImageBaseHeader; + +typedef struct XrSwapchainImageAcquireInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSwapchainImageAcquireInfo; + +typedef struct XrSwapchainImageWaitInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrDuration timeout; +} XrSwapchainImageWaitInfo; + +typedef struct XrSwapchainImageReleaseInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSwapchainImageReleaseInfo; + +typedef struct XrSessionBeginInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrViewConfigurationType primaryViewConfigurationType; +} XrSessionBeginInfo; + +typedef struct XrFrameState { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrTime predictedDisplayTime; + XrDuration predictedDisplayPeriod; + XrBool32 shouldRender; +} XrFrameState; + +typedef struct XrFrameWaitInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrFrameWaitInfo; + +typedef struct XrFrameBeginInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrFrameBeginInfo; + +typedef struct XR_MAY_ALIAS XrCompositionLayerBaseHeader { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCompositionLayerFlags layerFlags; + XrSpace space; +} XrCompositionLayerBaseHeader; + +typedef struct XrFrameEndInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrTime displayTime; + XrEnvironmentBlendMode environmentBlendMode; + uint32_t layerCount; + const XrCompositionLayerBaseHeader* const* layers; +} XrFrameEndInfo; + +typedef struct XrViewState { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrViewStateFlags viewStateFlags; +} XrViewState; + +typedef struct XrViewLocateInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrViewConfigurationType viewConfigurationType; + XrTime displayTime; + XrSpace space; +} XrViewLocateInfo; + +typedef struct XrFovf { + float angleLeft; + float angleRight; + float angleUp; + float angleDown; +} XrFovf; + +typedef struct XrView { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrPosef pose; + XrFovf fov; +} XrView; + +typedef struct XrActionSetCreateInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + char actionSetName[XR_MAX_ACTION_SET_NAME_SIZE]; + char localizedActionSetName[XR_MAX_LOCALIZED_ACTION_SET_NAME_SIZE]; + uint32_t priority; +} XrActionSetCreateInfo; + +typedef struct XrActionCreateInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + char actionName[XR_MAX_ACTION_NAME_SIZE]; + XrActionType actionType; + uint32_t countSubactionPaths; + const XrPath* subactionPaths; + char localizedActionName[XR_MAX_LOCALIZED_ACTION_NAME_SIZE]; +} XrActionCreateInfo; + +typedef struct XrActionSuggestedBinding { + XrAction action; + XrPath binding; +} XrActionSuggestedBinding; + +typedef struct XrInteractionProfileSuggestedBinding { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPath interactionProfile; + uint32_t countSuggestedBindings; + const XrActionSuggestedBinding* suggestedBindings; +} XrInteractionProfileSuggestedBinding; + +typedef struct XrSessionActionSetsAttachInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t countActionSets; + const XrActionSet* actionSets; +} XrSessionActionSetsAttachInfo; + +typedef struct XrInteractionProfileState { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrPath interactionProfile; +} XrInteractionProfileState; + +typedef struct XrActionStateGetInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAction action; + XrPath subactionPath; +} XrActionStateGetInfo; + +typedef struct XrActionStateBoolean { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 currentState; + XrBool32 changedSinceLastSync; + XrTime lastChangeTime; + XrBool32 isActive; +} XrActionStateBoolean; + +typedef struct XrActionStateFloat { + XrStructureType type; + void* XR_MAY_ALIAS next; + float currentState; + XrBool32 changedSinceLastSync; + XrTime lastChangeTime; + XrBool32 isActive; +} XrActionStateFloat; + +typedef struct XrVector2f { + float x; + float y; +} XrVector2f; + +typedef struct XrActionStateVector2f { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrVector2f currentState; + XrBool32 changedSinceLastSync; + XrTime lastChangeTime; + XrBool32 isActive; +} XrActionStateVector2f; + +typedef struct XrActionStatePose { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 isActive; +} XrActionStatePose; + +typedef struct XrActiveActionSet { + XrActionSet actionSet; + XrPath subactionPath; +} XrActiveActionSet; + +typedef struct XrActionsSyncInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t countActiveActionSets; + const XrActiveActionSet* activeActionSets; +} XrActionsSyncInfo; + +typedef struct XrBoundSourcesForActionEnumerateInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAction action; +} XrBoundSourcesForActionEnumerateInfo; + +typedef struct XrInputSourceLocalizedNameGetInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPath sourcePath; + XrInputSourceLocalizedNameFlags whichComponents; +} XrInputSourceLocalizedNameGetInfo; + +typedef struct XR_MAY_ALIAS XrHapticBaseHeader { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrHapticBaseHeader; + +typedef struct XrHapticActionInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAction action; + XrPath subactionPath; +} XrHapticActionInfo; + +typedef struct XR_MAY_ALIAS XrBaseInStructure { + XrStructureType type; + const struct XrBaseInStructure* next; +} XrBaseInStructure; + +typedef struct XR_MAY_ALIAS XrBaseOutStructure { + XrStructureType type; + struct XrBaseOutStructure* next; +} XrBaseOutStructure; + +typedef struct XrOffset2Di { + int32_t x; + int32_t y; +} XrOffset2Di; + +typedef struct XrExtent2Di { + int32_t width; + int32_t height; +} XrExtent2Di; + +typedef struct XrRect2Di { + XrOffset2Di offset; + XrExtent2Di extent; +} XrRect2Di; + +typedef struct XrSwapchainSubImage { + XrSwapchain swapchain; + XrRect2Di imageRect; + uint32_t imageArrayIndex; +} XrSwapchainSubImage; + +typedef struct XrCompositionLayerProjectionView { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPosef pose; + XrFovf fov; + XrSwapchainSubImage subImage; +} XrCompositionLayerProjectionView; + +typedef struct XrCompositionLayerProjection { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCompositionLayerFlags layerFlags; + XrSpace space; + uint32_t viewCount; + const XrCompositionLayerProjectionView* views; +} XrCompositionLayerProjection; + +typedef struct XrCompositionLayerQuad { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCompositionLayerFlags layerFlags; + XrSpace space; + XrEyeVisibility eyeVisibility; + XrSwapchainSubImage subImage; + XrPosef pose; + XrExtent2Df size; +} XrCompositionLayerQuad; + +typedef struct XR_MAY_ALIAS XrEventDataBaseHeader { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrEventDataBaseHeader; + +typedef struct XrEventDataEventsLost { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t lostEventCount; +} XrEventDataEventsLost; + +typedef struct XrEventDataInstanceLossPending { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrTime lossTime; +} XrEventDataInstanceLossPending; + +typedef struct XrEventDataSessionStateChanged { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSession session; + XrSessionState state; + XrTime time; +} XrEventDataSessionStateChanged; + +typedef struct XrEventDataReferenceSpaceChangePending { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSession session; + XrReferenceSpaceType referenceSpaceType; + XrTime changeTime; + XrBool32 poseValid; + XrPosef poseInPreviousSpace; +} XrEventDataReferenceSpaceChangePending; + +typedef struct XrEventDataInteractionProfileChanged { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSession session; +} XrEventDataInteractionProfileChanged; + +typedef struct XrHapticVibration { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrDuration duration; + float frequency; + float amplitude; +} XrHapticVibration; + +typedef struct XrOffset2Df { + float x; + float y; +} XrOffset2Df; + +typedef struct XrRect2Df { + XrOffset2Df offset; + XrExtent2Df extent; +} XrRect2Df; + +typedef struct XrVector4f { + float x; + float y; + float z; + float w; +} XrVector4f; + +typedef struct XrColor4f { + float r; + float g; + float b; + float a; +} XrColor4f; + +typedef XrResult (XRAPI_PTR *PFN_xrGetInstanceProcAddr)(XrInstance instance, const char* name, PFN_xrVoidFunction* function); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateApiLayerProperties)(uint32_t propertyCapacityInput, uint32_t* propertyCountOutput, XrApiLayerProperties* properties); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateInstanceExtensionProperties)(const char* layerName, uint32_t propertyCapacityInput, uint32_t* propertyCountOutput, XrExtensionProperties* properties); +typedef XrResult (XRAPI_PTR *PFN_xrCreateInstance)(const XrInstanceCreateInfo* createInfo, XrInstance* instance); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyInstance)(XrInstance instance); +typedef XrResult (XRAPI_PTR *PFN_xrGetInstanceProperties)(XrInstance instance, XrInstanceProperties* instanceProperties); +typedef XrResult (XRAPI_PTR *PFN_xrPollEvent)(XrInstance instance, XrEventDataBuffer* eventData); +typedef XrResult (XRAPI_PTR *PFN_xrResultToString)(XrInstance instance, XrResult value, char buffer[XR_MAX_RESULT_STRING_SIZE]); +typedef XrResult (XRAPI_PTR *PFN_xrStructureTypeToString)(XrInstance instance, XrStructureType value, char buffer[XR_MAX_STRUCTURE_NAME_SIZE]); +typedef XrResult (XRAPI_PTR *PFN_xrGetSystem)(XrInstance instance, const XrSystemGetInfo* getInfo, XrSystemId* systemId); +typedef XrResult (XRAPI_PTR *PFN_xrGetSystemProperties)(XrInstance instance, XrSystemId systemId, XrSystemProperties* properties); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateEnvironmentBlendModes)(XrInstance instance, XrSystemId systemId, XrViewConfigurationType viewConfigurationType, uint32_t environmentBlendModeCapacityInput, uint32_t* environmentBlendModeCountOutput, XrEnvironmentBlendMode* environmentBlendModes); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSession)(XrInstance instance, const XrSessionCreateInfo* createInfo, XrSession* session); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySession)(XrSession session); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateReferenceSpaces)(XrSession session, uint32_t spaceCapacityInput, uint32_t* spaceCountOutput, XrReferenceSpaceType* spaces); +typedef XrResult (XRAPI_PTR *PFN_xrCreateReferenceSpace)(XrSession session, const XrReferenceSpaceCreateInfo* createInfo, XrSpace* space); +typedef XrResult (XRAPI_PTR *PFN_xrGetReferenceSpaceBoundsRect)(XrSession session, XrReferenceSpaceType referenceSpaceType, XrExtent2Df* bounds); +typedef XrResult (XRAPI_PTR *PFN_xrCreateActionSpace)(XrSession session, const XrActionSpaceCreateInfo* createInfo, XrSpace* space); +typedef XrResult (XRAPI_PTR *PFN_xrLocateSpace)(XrSpace space, XrSpace baseSpace, XrTime time, XrSpaceLocation* location); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySpace)(XrSpace space); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateViewConfigurations)(XrInstance instance, XrSystemId systemId, uint32_t viewConfigurationTypeCapacityInput, uint32_t* viewConfigurationTypeCountOutput, XrViewConfigurationType* viewConfigurationTypes); +typedef XrResult (XRAPI_PTR *PFN_xrGetViewConfigurationProperties)(XrInstance instance, XrSystemId systemId, XrViewConfigurationType viewConfigurationType, XrViewConfigurationProperties* configurationProperties); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateViewConfigurationViews)(XrInstance instance, XrSystemId systemId, XrViewConfigurationType viewConfigurationType, uint32_t viewCapacityInput, uint32_t* viewCountOutput, XrViewConfigurationView* views); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateSwapchainFormats)(XrSession session, uint32_t formatCapacityInput, uint32_t* formatCountOutput, int64_t* formats); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSwapchain)(XrSession session, const XrSwapchainCreateInfo* createInfo, XrSwapchain* swapchain); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySwapchain)(XrSwapchain swapchain); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateSwapchainImages)(XrSwapchain swapchain, uint32_t imageCapacityInput, uint32_t* imageCountOutput, XrSwapchainImageBaseHeader* images); +typedef XrResult (XRAPI_PTR *PFN_xrAcquireSwapchainImage)(XrSwapchain swapchain, const XrSwapchainImageAcquireInfo* acquireInfo, uint32_t* index); +typedef XrResult (XRAPI_PTR *PFN_xrWaitSwapchainImage)(XrSwapchain swapchain, const XrSwapchainImageWaitInfo* waitInfo); +typedef XrResult (XRAPI_PTR *PFN_xrReleaseSwapchainImage)(XrSwapchain swapchain, const XrSwapchainImageReleaseInfo* releaseInfo); +typedef XrResult (XRAPI_PTR *PFN_xrBeginSession)(XrSession session, const XrSessionBeginInfo* beginInfo); +typedef XrResult (XRAPI_PTR *PFN_xrEndSession)(XrSession session); +typedef XrResult (XRAPI_PTR *PFN_xrRequestExitSession)(XrSession session); +typedef XrResult (XRAPI_PTR *PFN_xrWaitFrame)(XrSession session, const XrFrameWaitInfo* frameWaitInfo, XrFrameState* frameState); +typedef XrResult (XRAPI_PTR *PFN_xrBeginFrame)(XrSession session, const XrFrameBeginInfo* frameBeginInfo); +typedef XrResult (XRAPI_PTR *PFN_xrEndFrame)(XrSession session, const XrFrameEndInfo* frameEndInfo); +typedef XrResult (XRAPI_PTR *PFN_xrLocateViews)(XrSession session, const XrViewLocateInfo* viewLocateInfo, XrViewState* viewState, uint32_t viewCapacityInput, uint32_t* viewCountOutput, XrView* views); +typedef XrResult (XRAPI_PTR *PFN_xrStringToPath)(XrInstance instance, const char* pathString, XrPath* path); +typedef XrResult (XRAPI_PTR *PFN_xrPathToString)(XrInstance instance, XrPath path, uint32_t bufferCapacityInput, uint32_t* bufferCountOutput, char* buffer); +typedef XrResult (XRAPI_PTR *PFN_xrCreateActionSet)(XrInstance instance, const XrActionSetCreateInfo* createInfo, XrActionSet* actionSet); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyActionSet)(XrActionSet actionSet); +typedef XrResult (XRAPI_PTR *PFN_xrCreateAction)(XrActionSet actionSet, const XrActionCreateInfo* createInfo, XrAction* action); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyAction)(XrAction action); +typedef XrResult (XRAPI_PTR *PFN_xrSuggestInteractionProfileBindings)(XrInstance instance, const XrInteractionProfileSuggestedBinding* suggestedBindings); +typedef XrResult (XRAPI_PTR *PFN_xrAttachSessionActionSets)(XrSession session, const XrSessionActionSetsAttachInfo* attachInfo); +typedef XrResult (XRAPI_PTR *PFN_xrGetCurrentInteractionProfile)(XrSession session, XrPath topLevelUserPath, XrInteractionProfileState* interactionProfile); +typedef XrResult (XRAPI_PTR *PFN_xrGetActionStateBoolean)(XrSession session, const XrActionStateGetInfo* getInfo, XrActionStateBoolean* state); +typedef XrResult (XRAPI_PTR *PFN_xrGetActionStateFloat)(XrSession session, const XrActionStateGetInfo* getInfo, XrActionStateFloat* state); +typedef XrResult (XRAPI_PTR *PFN_xrGetActionStateVector2f)(XrSession session, const XrActionStateGetInfo* getInfo, XrActionStateVector2f* state); +typedef XrResult (XRAPI_PTR *PFN_xrGetActionStatePose)(XrSession session, const XrActionStateGetInfo* getInfo, XrActionStatePose* state); +typedef XrResult (XRAPI_PTR *PFN_xrSyncActions)(XrSession session, const XrActionsSyncInfo* syncInfo); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateBoundSourcesForAction)(XrSession session, const XrBoundSourcesForActionEnumerateInfo* enumerateInfo, uint32_t sourceCapacityInput, uint32_t* sourceCountOutput, XrPath* sources); +typedef XrResult (XRAPI_PTR *PFN_xrGetInputSourceLocalizedName)(XrSession session, const XrInputSourceLocalizedNameGetInfo* getInfo, uint32_t bufferCapacityInput, uint32_t* bufferCountOutput, char* buffer); +typedef XrResult (XRAPI_PTR *PFN_xrApplyHapticFeedback)(XrSession session, const XrHapticActionInfo* hapticActionInfo, const XrHapticBaseHeader* hapticFeedback); +typedef XrResult (XRAPI_PTR *PFN_xrStopHapticFeedback)(XrSession session, const XrHapticActionInfo* hapticActionInfo); + +#ifndef XR_NO_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetInstanceProcAddr( + XrInstance instance, + const char* name, + PFN_xrVoidFunction* function); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateApiLayerProperties( + uint32_t propertyCapacityInput, + uint32_t* propertyCountOutput, + XrApiLayerProperties* properties); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateInstanceExtensionProperties( + const char* layerName, + uint32_t propertyCapacityInput, + uint32_t* propertyCountOutput, + XrExtensionProperties* properties); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateInstance( + const XrInstanceCreateInfo* createInfo, + XrInstance* instance); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyInstance( + XrInstance instance); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetInstanceProperties( + XrInstance instance, + XrInstanceProperties* instanceProperties); + +XRAPI_ATTR XrResult XRAPI_CALL xrPollEvent( + XrInstance instance, + XrEventDataBuffer* eventData); + +XRAPI_ATTR XrResult XRAPI_CALL xrResultToString( + XrInstance instance, + XrResult value, + char buffer[XR_MAX_RESULT_STRING_SIZE]); + +XRAPI_ATTR XrResult XRAPI_CALL xrStructureTypeToString( + XrInstance instance, + XrStructureType value, + char buffer[XR_MAX_STRUCTURE_NAME_SIZE]); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSystem( + XrInstance instance, + const XrSystemGetInfo* getInfo, + XrSystemId* systemId); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSystemProperties( + XrInstance instance, + XrSystemId systemId, + XrSystemProperties* properties); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateEnvironmentBlendModes( + XrInstance instance, + XrSystemId systemId, + XrViewConfigurationType viewConfigurationType, + uint32_t environmentBlendModeCapacityInput, + uint32_t* environmentBlendModeCountOutput, + XrEnvironmentBlendMode* environmentBlendModes); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSession( + XrInstance instance, + const XrSessionCreateInfo* createInfo, + XrSession* session); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySession( + XrSession session); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateReferenceSpaces( + XrSession session, + uint32_t spaceCapacityInput, + uint32_t* spaceCountOutput, + XrReferenceSpaceType* spaces); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateReferenceSpace( + XrSession session, + const XrReferenceSpaceCreateInfo* createInfo, + XrSpace* space); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetReferenceSpaceBoundsRect( + XrSession session, + XrReferenceSpaceType referenceSpaceType, + XrExtent2Df* bounds); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateActionSpace( + XrSession session, + const XrActionSpaceCreateInfo* createInfo, + XrSpace* space); + +XRAPI_ATTR XrResult XRAPI_CALL xrLocateSpace( + XrSpace space, + XrSpace baseSpace, + XrTime time, + XrSpaceLocation* location); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySpace( + XrSpace space); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateViewConfigurations( + XrInstance instance, + XrSystemId systemId, + uint32_t viewConfigurationTypeCapacityInput, + uint32_t* viewConfigurationTypeCountOutput, + XrViewConfigurationType* viewConfigurationTypes); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetViewConfigurationProperties( + XrInstance instance, + XrSystemId systemId, + XrViewConfigurationType viewConfigurationType, + XrViewConfigurationProperties* configurationProperties); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateViewConfigurationViews( + XrInstance instance, + XrSystemId systemId, + XrViewConfigurationType viewConfigurationType, + uint32_t viewCapacityInput, + uint32_t* viewCountOutput, + XrViewConfigurationView* views); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateSwapchainFormats( + XrSession session, + uint32_t formatCapacityInput, + uint32_t* formatCountOutput, + int64_t* formats); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSwapchain( + XrSession session, + const XrSwapchainCreateInfo* createInfo, + XrSwapchain* swapchain); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySwapchain( + XrSwapchain swapchain); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateSwapchainImages( + XrSwapchain swapchain, + uint32_t imageCapacityInput, + uint32_t* imageCountOutput, + XrSwapchainImageBaseHeader* images); + +XRAPI_ATTR XrResult XRAPI_CALL xrAcquireSwapchainImage( + XrSwapchain swapchain, + const XrSwapchainImageAcquireInfo* acquireInfo, + uint32_t* index); + +XRAPI_ATTR XrResult XRAPI_CALL xrWaitSwapchainImage( + XrSwapchain swapchain, + const XrSwapchainImageWaitInfo* waitInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrReleaseSwapchainImage( + XrSwapchain swapchain, + const XrSwapchainImageReleaseInfo* releaseInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrBeginSession( + XrSession session, + const XrSessionBeginInfo* beginInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrEndSession( + XrSession session); + +XRAPI_ATTR XrResult XRAPI_CALL xrRequestExitSession( + XrSession session); + +XRAPI_ATTR XrResult XRAPI_CALL xrWaitFrame( + XrSession session, + const XrFrameWaitInfo* frameWaitInfo, + XrFrameState* frameState); + +XRAPI_ATTR XrResult XRAPI_CALL xrBeginFrame( + XrSession session, + const XrFrameBeginInfo* frameBeginInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrEndFrame( + XrSession session, + const XrFrameEndInfo* frameEndInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrLocateViews( + XrSession session, + const XrViewLocateInfo* viewLocateInfo, + XrViewState* viewState, + uint32_t viewCapacityInput, + uint32_t* viewCountOutput, + XrView* views); + +XRAPI_ATTR XrResult XRAPI_CALL xrStringToPath( + XrInstance instance, + const char* pathString, + XrPath* path); + +XRAPI_ATTR XrResult XRAPI_CALL xrPathToString( + XrInstance instance, + XrPath path, + uint32_t bufferCapacityInput, + uint32_t* bufferCountOutput, + char* buffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateActionSet( + XrInstance instance, + const XrActionSetCreateInfo* createInfo, + XrActionSet* actionSet); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyActionSet( + XrActionSet actionSet); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateAction( + XrActionSet actionSet, + const XrActionCreateInfo* createInfo, + XrAction* action); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyAction( + XrAction action); + +XRAPI_ATTR XrResult XRAPI_CALL xrSuggestInteractionProfileBindings( + XrInstance instance, + const XrInteractionProfileSuggestedBinding* suggestedBindings); + +XRAPI_ATTR XrResult XRAPI_CALL xrAttachSessionActionSets( + XrSession session, + const XrSessionActionSetsAttachInfo* attachInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetCurrentInteractionProfile( + XrSession session, + XrPath topLevelUserPath, + XrInteractionProfileState* interactionProfile); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetActionStateBoolean( + XrSession session, + const XrActionStateGetInfo* getInfo, + XrActionStateBoolean* state); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetActionStateFloat( + XrSession session, + const XrActionStateGetInfo* getInfo, + XrActionStateFloat* state); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetActionStateVector2f( + XrSession session, + const XrActionStateGetInfo* getInfo, + XrActionStateVector2f* state); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetActionStatePose( + XrSession session, + const XrActionStateGetInfo* getInfo, + XrActionStatePose* state); + +XRAPI_ATTR XrResult XRAPI_CALL xrSyncActions( + XrSession session, + const XrActionsSyncInfo* syncInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateBoundSourcesForAction( + XrSession session, + const XrBoundSourcesForActionEnumerateInfo* enumerateInfo, + uint32_t sourceCapacityInput, + uint32_t* sourceCountOutput, + XrPath* sources); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetInputSourceLocalizedName( + XrSession session, + const XrInputSourceLocalizedNameGetInfo* getInfo, + uint32_t bufferCapacityInput, + uint32_t* bufferCountOutput, + char* buffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrApplyHapticFeedback( + XrSession session, + const XrHapticActionInfo* hapticActionInfo, + const XrHapticBaseHeader* hapticFeedback); + +XRAPI_ATTR XrResult XRAPI_CALL xrStopHapticFeedback( + XrSession session, + const XrHapticActionInfo* hapticActionInfo); +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_VERSION_1_1 is a preprocessor guard. Do not pass it to API calls. +#define XR_VERSION_1_1 1 +// OpenXR 1.1 version number +#define XR_API_VERSION_1_1 XR_MAKE_VERSION(1, 1, XR_VERSION_PATCH(XR_CURRENT_API_VERSION)) + +#define XR_UUID_SIZE 16 +typedef struct XrColor3f { + float r; + float g; + float b; +} XrColor3f; + +typedef struct XrExtent3Df { + float width; + float height; + float depth; +} XrExtent3Df; + +typedef struct XrSpheref { + XrPosef center; + float radius; +} XrSpheref; + +typedef struct XrBoxf { + XrPosef center; + XrExtent3Df extents; +} XrBoxf; + +typedef struct XrFrustumf { + XrPosef pose; + XrFovf fov; + float nearZ; + float farZ; +} XrFrustumf; + +typedef struct XrUuid { + uint8_t data[XR_UUID_SIZE]; +} XrUuid; + +typedef struct XrSpacesLocateInfo { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace baseSpace; + XrTime time; + uint32_t spaceCount; + const XrSpace* spaces; +} XrSpacesLocateInfo; + +typedef struct XrSpaceLocationData { + XrSpaceLocationFlags locationFlags; + XrPosef pose; +} XrSpaceLocationData; + +typedef struct XrSpaceLocations { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t locationCount; + XrSpaceLocationData* locations; +} XrSpaceLocations; + +typedef struct XrSpaceVelocityData { + XrSpaceVelocityFlags velocityFlags; + XrVector3f linearVelocity; + XrVector3f angularVelocity; +} XrSpaceVelocityData; + +// XrSpaceVelocities extends XrSpaceLocations +typedef struct XrSpaceVelocities { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t velocityCount; + XrSpaceVelocityData* velocities; +} XrSpaceVelocities; + +typedef XrResult (XRAPI_PTR *PFN_xrLocateSpaces)(XrSession session, const XrSpacesLocateInfo* locateInfo, XrSpaceLocations* spaceLocations); + +#ifndef XR_NO_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrLocateSpaces( + XrSession session, + const XrSpacesLocateInfo* locateInfo, + XrSpaceLocations* spaceLocations); +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_KHR_composition_layer_cube is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_composition_layer_cube 1 +#define XR_KHR_composition_layer_cube_SPEC_VERSION 8 +#define XR_KHR_COMPOSITION_LAYER_CUBE_EXTENSION_NAME "XR_KHR_composition_layer_cube" +typedef struct XrCompositionLayerCubeKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCompositionLayerFlags layerFlags; + XrSpace space; + XrEyeVisibility eyeVisibility; + XrSwapchain swapchain; + uint32_t imageArrayIndex; + XrQuaternionf orientation; +} XrCompositionLayerCubeKHR; + + + +// XR_KHR_composition_layer_depth is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_composition_layer_depth 1 +#define XR_KHR_composition_layer_depth_SPEC_VERSION 6 +#define XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME "XR_KHR_composition_layer_depth" +// XrCompositionLayerDepthInfoKHR extends XrCompositionLayerProjectionView +typedef struct XrCompositionLayerDepthInfoKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSwapchainSubImage subImage; + float minDepth; + float maxDepth; + float nearZ; + float farZ; +} XrCompositionLayerDepthInfoKHR; + + + +// XR_KHR_composition_layer_cylinder is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_composition_layer_cylinder 1 +#define XR_KHR_composition_layer_cylinder_SPEC_VERSION 4 +#define XR_KHR_COMPOSITION_LAYER_CYLINDER_EXTENSION_NAME "XR_KHR_composition_layer_cylinder" +typedef struct XrCompositionLayerCylinderKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCompositionLayerFlags layerFlags; + XrSpace space; + XrEyeVisibility eyeVisibility; + XrSwapchainSubImage subImage; + XrPosef pose; + float radius; + float centralAngle; + float aspectRatio; +} XrCompositionLayerCylinderKHR; + + + +// XR_KHR_composition_layer_equirect is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_composition_layer_equirect 1 +#define XR_KHR_composition_layer_equirect_SPEC_VERSION 3 +#define XR_KHR_COMPOSITION_LAYER_EQUIRECT_EXTENSION_NAME "XR_KHR_composition_layer_equirect" +typedef struct XrCompositionLayerEquirectKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCompositionLayerFlags layerFlags; + XrSpace space; + XrEyeVisibility eyeVisibility; + XrSwapchainSubImage subImage; + XrPosef pose; + float radius; + XrVector2f scale; + XrVector2f bias; +} XrCompositionLayerEquirectKHR; + + + +// XR_KHR_visibility_mask is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_visibility_mask 1 +#define XR_KHR_visibility_mask_SPEC_VERSION 2 +#define XR_KHR_VISIBILITY_MASK_EXTENSION_NAME "XR_KHR_visibility_mask" + +typedef enum XrVisibilityMaskTypeKHR { + XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR = 1, + XR_VISIBILITY_MASK_TYPE_VISIBLE_TRIANGLE_MESH_KHR = 2, + XR_VISIBILITY_MASK_TYPE_LINE_LOOP_KHR = 3, + XR_VISIBILITY_MASK_TYPE_MAX_ENUM_KHR = 0x7FFFFFFF +} XrVisibilityMaskTypeKHR; +typedef struct XrVisibilityMaskKHR { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t vertexCapacityInput; + uint32_t vertexCountOutput; + XrVector2f* vertices; + uint32_t indexCapacityInput; + uint32_t indexCountOutput; + uint32_t* indices; +} XrVisibilityMaskKHR; + +typedef struct XrEventDataVisibilityMaskChangedKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSession session; + XrViewConfigurationType viewConfigurationType; + uint32_t viewIndex; +} XrEventDataVisibilityMaskChangedKHR; + +typedef XrResult (XRAPI_PTR *PFN_xrGetVisibilityMaskKHR)(XrSession session, XrViewConfigurationType viewConfigurationType, uint32_t viewIndex, XrVisibilityMaskTypeKHR visibilityMaskType, XrVisibilityMaskKHR* visibilityMask); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetVisibilityMaskKHR( + XrSession session, + XrViewConfigurationType viewConfigurationType, + uint32_t viewIndex, + XrVisibilityMaskTypeKHR visibilityMaskType, + XrVisibilityMaskKHR* visibilityMask); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_KHR_composition_layer_color_scale_bias is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_composition_layer_color_scale_bias 1 +#define XR_KHR_composition_layer_color_scale_bias_SPEC_VERSION 5 +#define XR_KHR_COMPOSITION_LAYER_COLOR_SCALE_BIAS_EXTENSION_NAME "XR_KHR_composition_layer_color_scale_bias" +// XrCompositionLayerColorScaleBiasKHR extends XrCompositionLayerBaseHeader +typedef struct XrCompositionLayerColorScaleBiasKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrColor4f colorScale; + XrColor4f colorBias; +} XrCompositionLayerColorScaleBiasKHR; + + + +// XR_KHR_loader_init is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_loader_init 1 +#define XR_KHR_loader_init_SPEC_VERSION 2 +#define XR_KHR_LOADER_INIT_EXTENSION_NAME "XR_KHR_loader_init" +typedef struct XR_MAY_ALIAS XrLoaderInitInfoBaseHeaderKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrLoaderInitInfoBaseHeaderKHR; + +typedef XrResult (XRAPI_PTR *PFN_xrInitializeLoaderKHR)(const XrLoaderInitInfoBaseHeaderKHR* loaderInitInfo); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrInitializeLoaderKHR( + const XrLoaderInitInfoBaseHeaderKHR* loaderInitInfo); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_KHR_composition_layer_equirect2 is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_composition_layer_equirect2 1 +#define XR_KHR_composition_layer_equirect2_SPEC_VERSION 1 +#define XR_KHR_COMPOSITION_LAYER_EQUIRECT2_EXTENSION_NAME "XR_KHR_composition_layer_equirect2" +typedef struct XrCompositionLayerEquirect2KHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCompositionLayerFlags layerFlags; + XrSpace space; + XrEyeVisibility eyeVisibility; + XrSwapchainSubImage subImage; + XrPosef pose; + float radius; + float centralHorizontalAngle; + float upperVerticalAngle; + float lowerVerticalAngle; +} XrCompositionLayerEquirect2KHR; + + + +// XR_KHR_binding_modification is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_binding_modification 1 +#define XR_KHR_binding_modification_SPEC_VERSION 1 +#define XR_KHR_BINDING_MODIFICATION_EXTENSION_NAME "XR_KHR_binding_modification" +typedef struct XR_MAY_ALIAS XrBindingModificationBaseHeaderKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrBindingModificationBaseHeaderKHR; + +// XrBindingModificationsKHR extends XrInteractionProfileSuggestedBinding +typedef struct XrBindingModificationsKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t bindingModificationCount; + const XrBindingModificationBaseHeaderKHR* const* bindingModifications; +} XrBindingModificationsKHR; + + + +// XR_KHR_extended_struct_name_lengths is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_extended_struct_name_lengths 1 +#define XR_KHR_extended_struct_name_lengths_SPEC_VERSION 2 +#define XR_KHR_EXTENDED_STRUCT_NAME_LENGTHS_EXTENSION_NAME "XR_KHR_extended_struct_name_lengths" +#define XR_MAX_STRUCTURE_NAME_SIZE_EXTENDED_KHR 256 +typedef XrResult (XRAPI_PTR *PFN_xrStructureTypeToString2KHR)(XrInstance instance, XrStructureType value, char buffer[XR_MAX_STRUCTURE_NAME_SIZE_EXTENDED_KHR]); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrStructureTypeToString2KHR( + XrInstance instance, + XrStructureType value, + char buffer[XR_MAX_STRUCTURE_NAME_SIZE_EXTENDED_KHR]); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_KHR_swapchain_usage_input_attachment_bit is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_swapchain_usage_input_attachment_bit 1 +#define XR_KHR_swapchain_usage_input_attachment_bit_SPEC_VERSION 3 +#define XR_KHR_SWAPCHAIN_USAGE_INPUT_ATTACHMENT_BIT_EXTENSION_NAME "XR_KHR_swapchain_usage_input_attachment_bit" + + +// XR_KHR_locate_spaces is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_locate_spaces 1 +#define XR_KHR_locate_spaces_SPEC_VERSION 1 +#define XR_KHR_LOCATE_SPACES_EXTENSION_NAME "XR_KHR_locate_spaces" +typedef XrSpacesLocateInfo XrSpacesLocateInfoKHR; + +typedef XrSpaceLocationData XrSpaceLocationDataKHR; + +typedef XrSpaceLocations XrSpaceLocationsKHR; + +typedef XrSpaceVelocityData XrSpaceVelocityDataKHR; + +typedef XrSpaceVelocities XrSpaceVelocitiesKHR; + +typedef XrResult (XRAPI_PTR *PFN_xrLocateSpacesKHR)(XrSession session, const XrSpacesLocateInfo* locateInfo, XrSpaceLocations* spaceLocations); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrLocateSpacesKHR( + XrSession session, + const XrSpacesLocateInfo* locateInfo, + XrSpaceLocations* spaceLocations); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_KHR_maintenance1 is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_maintenance1 1 +#define XR_KHR_maintenance1_SPEC_VERSION 1 +#define XR_KHR_MAINTENANCE1_EXTENSION_NAME "XR_KHR_maintenance1" +typedef XrColor3f XrColor3fKHR; + +typedef XrExtent3Df XrExtent3DfKHR; + +typedef XrSpheref XrSpherefKHR; + +typedef XrBoxf XrBoxfKHR; + +typedef XrFrustumf XrFrustumfKHR; + + + +// XR_KHR_generic_controller is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_generic_controller 1 +#define XR_KHR_generic_controller_SPEC_VERSION 1 +#define XR_KHR_GENERIC_CONTROLLER_EXTENSION_NAME "XR_KHR_generic_controller" + + +// XR_KHR_extended_result_name_lengths is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_extended_result_name_lengths 1 +#define XR_KHR_extended_result_name_lengths_SPEC_VERSION 1 +#define XR_KHR_EXTENDED_RESULT_NAME_LENGTHS_EXTENSION_NAME "XR_KHR_extended_result_name_lengths" +#define XR_MAX_RESULT_STRING_SIZE_EXTENDED_KHR 256 +typedef XrResult (XRAPI_PTR *PFN_xrResultToString2KHR)(XrInstance instance, XrResult value, char buffer[XR_MAX_RESULT_STRING_SIZE_EXTENDED_KHR]); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrResultToString2KHR( + XrInstance instance, + XrResult value, + char buffer[XR_MAX_RESULT_STRING_SIZE_EXTENDED_KHR]); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_performance_settings is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_performance_settings 1 +#define XR_EXT_performance_settings_SPEC_VERSION 4 +#define XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME "XR_EXT_performance_settings" + +typedef enum XrPerfSettingsDomainEXT { + XR_PERF_SETTINGS_DOMAIN_CPU_EXT = 1, + XR_PERF_SETTINGS_DOMAIN_GPU_EXT = 2, + XR_PERF_SETTINGS_DOMAIN_MAX_ENUM_EXT = 0x7FFFFFFF +} XrPerfSettingsDomainEXT; + +typedef enum XrPerfSettingsSubDomainEXT { + XR_PERF_SETTINGS_SUB_DOMAIN_COMPOSITING_EXT = 1, + XR_PERF_SETTINGS_SUB_DOMAIN_RENDERING_EXT = 2, + XR_PERF_SETTINGS_SUB_DOMAIN_THERMAL_EXT = 3, + XR_PERF_SETTINGS_SUB_DOMAIN_MAX_ENUM_EXT = 0x7FFFFFFF +} XrPerfSettingsSubDomainEXT; + +typedef enum XrPerfSettingsLevelEXT { + XR_PERF_SETTINGS_LEVEL_POWER_SAVINGS_EXT = 0, + XR_PERF_SETTINGS_LEVEL_SUSTAINED_LOW_EXT = 25, + XR_PERF_SETTINGS_LEVEL_SUSTAINED_HIGH_EXT = 50, + XR_PERF_SETTINGS_LEVEL_BOOST_EXT = 75, + XR_PERF_SETTINGS_LEVEL_MAX_ENUM_EXT = 0x7FFFFFFF +} XrPerfSettingsLevelEXT; + +typedef enum XrPerfSettingsNotificationLevelEXT { + XR_PERF_SETTINGS_NOTIF_LEVEL_NORMAL_EXT = 0, + XR_PERF_SETTINGS_NOTIF_LEVEL_WARNING_EXT = 25, + XR_PERF_SETTINGS_NOTIF_LEVEL_IMPAIRED_EXT = 75, + XR_PERF_SETTINGS_NOTIFICATION_LEVEL_MAX_ENUM_EXT = 0x7FFFFFFF +} XrPerfSettingsNotificationLevelEXT; +typedef struct XrEventDataPerfSettingsEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPerfSettingsDomainEXT domain; + XrPerfSettingsSubDomainEXT subDomain; + XrPerfSettingsNotificationLevelEXT fromLevel; + XrPerfSettingsNotificationLevelEXT toLevel; +} XrEventDataPerfSettingsEXT; + +typedef XrResult (XRAPI_PTR *PFN_xrPerfSettingsSetPerformanceLevelEXT)(XrSession session, XrPerfSettingsDomainEXT domain, XrPerfSettingsLevelEXT level); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrPerfSettingsSetPerformanceLevelEXT( + XrSession session, + XrPerfSettingsDomainEXT domain, + XrPerfSettingsLevelEXT level); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_thermal_query is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_thermal_query 1 +#define XR_EXT_thermal_query_SPEC_VERSION 2 +#define XR_EXT_THERMAL_QUERY_EXTENSION_NAME "XR_EXT_thermal_query" +typedef XrResult (XRAPI_PTR *PFN_xrThermalGetTemperatureTrendEXT)(XrSession session, XrPerfSettingsDomainEXT domain, XrPerfSettingsNotificationLevelEXT* notificationLevel, float* tempHeadroom, float* tempSlope); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrThermalGetTemperatureTrendEXT( + XrSession session, + XrPerfSettingsDomainEXT domain, + XrPerfSettingsNotificationLevelEXT* notificationLevel, + float* tempHeadroom, + float* tempSlope); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_debug_utils is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_debug_utils 1 +XR_DEFINE_HANDLE(XrDebugUtilsMessengerEXT) +#define XR_EXT_debug_utils_SPEC_VERSION 5 +#define XR_EXT_DEBUG_UTILS_EXTENSION_NAME "XR_EXT_debug_utils" +typedef XrFlags64 XrDebugUtilsMessageSeverityFlagsEXT; + +// Flag bits for XrDebugUtilsMessageSeverityFlagsEXT +static const XrDebugUtilsMessageSeverityFlagsEXT XR_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT = 0x00000001; +static const XrDebugUtilsMessageSeverityFlagsEXT XR_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT = 0x00000010; +static const XrDebugUtilsMessageSeverityFlagsEXT XR_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT = 0x00000100; +static const XrDebugUtilsMessageSeverityFlagsEXT XR_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT = 0x00001000; + +typedef XrFlags64 XrDebugUtilsMessageTypeFlagsEXT; + +// Flag bits for XrDebugUtilsMessageTypeFlagsEXT +static const XrDebugUtilsMessageTypeFlagsEXT XR_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT = 0x00000001; +static const XrDebugUtilsMessageTypeFlagsEXT XR_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT = 0x00000002; +static const XrDebugUtilsMessageTypeFlagsEXT XR_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT = 0x00000004; +static const XrDebugUtilsMessageTypeFlagsEXT XR_DEBUG_UTILS_MESSAGE_TYPE_CONFORMANCE_BIT_EXT = 0x00000008; + +typedef struct XrDebugUtilsObjectNameInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrObjectType objectType; + uint64_t objectHandle; + const char* objectName; +} XrDebugUtilsObjectNameInfoEXT; + +typedef struct XrDebugUtilsLabelEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + const char* labelName; +} XrDebugUtilsLabelEXT; + +typedef struct XrDebugUtilsMessengerCallbackDataEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + const char* messageId; + const char* functionName; + const char* message; + uint32_t objectCount; + XrDebugUtilsObjectNameInfoEXT* objects; + uint32_t sessionLabelCount; + XrDebugUtilsLabelEXT* sessionLabels; +} XrDebugUtilsMessengerCallbackDataEXT; + +typedef XrBool32 (XRAPI_PTR *PFN_xrDebugUtilsMessengerCallbackEXT)( + XrDebugUtilsMessageSeverityFlagsEXT messageSeverity, + XrDebugUtilsMessageTypeFlagsEXT messageTypes, + const XrDebugUtilsMessengerCallbackDataEXT* callbackData, + void* userData); + + +// XrDebugUtilsMessengerCreateInfoEXT extends XrInstanceCreateInfo +typedef struct XrDebugUtilsMessengerCreateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrDebugUtilsMessageSeverityFlagsEXT messageSeverities; + XrDebugUtilsMessageTypeFlagsEXT messageTypes; + PFN_xrDebugUtilsMessengerCallbackEXT userCallback; + void* XR_MAY_ALIAS userData; +} XrDebugUtilsMessengerCreateInfoEXT; + +typedef XrResult (XRAPI_PTR *PFN_xrSetDebugUtilsObjectNameEXT)(XrInstance instance, const XrDebugUtilsObjectNameInfoEXT* nameInfo); +typedef XrResult (XRAPI_PTR *PFN_xrCreateDebugUtilsMessengerEXT)(XrInstance instance, const XrDebugUtilsMessengerCreateInfoEXT* createInfo, XrDebugUtilsMessengerEXT* messenger); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyDebugUtilsMessengerEXT)(XrDebugUtilsMessengerEXT messenger); +typedef XrResult (XRAPI_PTR *PFN_xrSubmitDebugUtilsMessageEXT)(XrInstance instance, XrDebugUtilsMessageSeverityFlagsEXT messageSeverity, XrDebugUtilsMessageTypeFlagsEXT messageTypes, const XrDebugUtilsMessengerCallbackDataEXT* callbackData); +typedef XrResult (XRAPI_PTR *PFN_xrSessionBeginDebugUtilsLabelRegionEXT)(XrSession session, const XrDebugUtilsLabelEXT* labelInfo); +typedef XrResult (XRAPI_PTR *PFN_xrSessionEndDebugUtilsLabelRegionEXT)(XrSession session); +typedef XrResult (XRAPI_PTR *PFN_xrSessionInsertDebugUtilsLabelEXT)(XrSession session, const XrDebugUtilsLabelEXT* labelInfo); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrSetDebugUtilsObjectNameEXT( + XrInstance instance, + const XrDebugUtilsObjectNameInfoEXT* nameInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateDebugUtilsMessengerEXT( + XrInstance instance, + const XrDebugUtilsMessengerCreateInfoEXT* createInfo, + XrDebugUtilsMessengerEXT* messenger); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyDebugUtilsMessengerEXT( + XrDebugUtilsMessengerEXT messenger); + +XRAPI_ATTR XrResult XRAPI_CALL xrSubmitDebugUtilsMessageEXT( + XrInstance instance, + XrDebugUtilsMessageSeverityFlagsEXT messageSeverity, + XrDebugUtilsMessageTypeFlagsEXT messageTypes, + const XrDebugUtilsMessengerCallbackDataEXT* callbackData); + +XRAPI_ATTR XrResult XRAPI_CALL xrSessionBeginDebugUtilsLabelRegionEXT( + XrSession session, + const XrDebugUtilsLabelEXT* labelInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrSessionEndDebugUtilsLabelRegionEXT( + XrSession session); + +XRAPI_ATTR XrResult XRAPI_CALL xrSessionInsertDebugUtilsLabelEXT( + XrSession session, + const XrDebugUtilsLabelEXT* labelInfo); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_eye_gaze_interaction is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_eye_gaze_interaction 1 +#define XR_EXT_eye_gaze_interaction_SPEC_VERSION 2 +#define XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME "XR_EXT_eye_gaze_interaction" +// XrSystemEyeGazeInteractionPropertiesEXT extends XrSystemProperties +typedef struct XrSystemEyeGazeInteractionPropertiesEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsEyeGazeInteraction; +} XrSystemEyeGazeInteractionPropertiesEXT; + +// XrEyeGazeSampleTimeEXT extends XrSpaceLocation +typedef struct XrEyeGazeSampleTimeEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrTime time; +} XrEyeGazeSampleTimeEXT; + + + +// XR_EXTX_overlay is a preprocessor guard. Do not pass it to API calls. +#define XR_EXTX_overlay 1 +#define XR_EXTX_overlay_SPEC_VERSION 5 +#define XR_EXTX_OVERLAY_EXTENSION_NAME "XR_EXTX_overlay" +typedef XrFlags64 XrOverlaySessionCreateFlagsEXTX; + +// Flag bits for XrOverlaySessionCreateFlagsEXTX + +typedef XrFlags64 XrOverlayMainSessionFlagsEXTX; + +// Flag bits for XrOverlayMainSessionFlagsEXTX +static const XrOverlayMainSessionFlagsEXTX XR_OVERLAY_MAIN_SESSION_ENABLED_COMPOSITION_LAYER_INFO_DEPTH_BIT_EXTX = 0x00000001; + +// XrSessionCreateInfoOverlayEXTX extends XrSessionCreateInfo +typedef struct XrSessionCreateInfoOverlayEXTX { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrOverlaySessionCreateFlagsEXTX createFlags; + uint32_t sessionLayersPlacement; +} XrSessionCreateInfoOverlayEXTX; + +typedef struct XrEventDataMainSessionVisibilityChangedEXTX { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 visible; + XrOverlayMainSessionFlagsEXTX flags; +} XrEventDataMainSessionVisibilityChangedEXTX; + + + +// XR_VARJO_quad_views is a preprocessor guard. Do not pass it to API calls. +#define XR_VARJO_quad_views 1 +#define XR_VARJO_quad_views_SPEC_VERSION 2 +#define XR_VARJO_QUAD_VIEWS_EXTENSION_NAME "XR_VARJO_quad_views" + + +// XR_MSFT_unbounded_reference_space is a preprocessor guard. Do not pass it to API calls. +#define XR_MSFT_unbounded_reference_space 1 +#define XR_MSFT_unbounded_reference_space_SPEC_VERSION 1 +#define XR_MSFT_UNBOUNDED_REFERENCE_SPACE_EXTENSION_NAME "XR_MSFT_unbounded_reference_space" + + +// XR_MSFT_spatial_anchor is a preprocessor guard. Do not pass it to API calls. +#define XR_MSFT_spatial_anchor 1 +XR_DEFINE_HANDLE(XrSpatialAnchorMSFT) +#define XR_MSFT_spatial_anchor_SPEC_VERSION 2 +#define XR_MSFT_SPATIAL_ANCHOR_EXTENSION_NAME "XR_MSFT_spatial_anchor" +typedef struct XrSpatialAnchorCreateInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace space; + XrPosef pose; + XrTime time; +} XrSpatialAnchorCreateInfoMSFT; + +typedef struct XrSpatialAnchorSpaceCreateInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialAnchorMSFT anchor; + XrPosef poseInAnchorSpace; +} XrSpatialAnchorSpaceCreateInfoMSFT; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorMSFT)(XrSession session, const XrSpatialAnchorCreateInfoMSFT* createInfo, XrSpatialAnchorMSFT* anchor); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorSpaceMSFT)(XrSession session, const XrSpatialAnchorSpaceCreateInfoMSFT* createInfo, XrSpace* space); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySpatialAnchorMSFT)(XrSpatialAnchorMSFT anchor); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorMSFT( + XrSession session, + const XrSpatialAnchorCreateInfoMSFT* createInfo, + XrSpatialAnchorMSFT* anchor); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorSpaceMSFT( + XrSession session, + const XrSpatialAnchorSpaceCreateInfoMSFT* createInfo, + XrSpace* space); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySpatialAnchorMSFT( + XrSpatialAnchorMSFT anchor); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_composition_layer_image_layout is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_composition_layer_image_layout 1 +#define XR_FB_composition_layer_image_layout_SPEC_VERSION 1 +#define XR_FB_COMPOSITION_LAYER_IMAGE_LAYOUT_EXTENSION_NAME "XR_FB_composition_layer_image_layout" +typedef XrFlags64 XrCompositionLayerImageLayoutFlagsFB; + +// Flag bits for XrCompositionLayerImageLayoutFlagsFB +static const XrCompositionLayerImageLayoutFlagsFB XR_COMPOSITION_LAYER_IMAGE_LAYOUT_VERTICAL_FLIP_BIT_FB = 0x00000001; + +// XrCompositionLayerImageLayoutFB extends XrCompositionLayerBaseHeader +typedef struct XrCompositionLayerImageLayoutFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrCompositionLayerImageLayoutFlagsFB flags; +} XrCompositionLayerImageLayoutFB; + + + +// XR_FB_composition_layer_alpha_blend is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_composition_layer_alpha_blend 1 +#define XR_FB_composition_layer_alpha_blend_SPEC_VERSION 3 +#define XR_FB_COMPOSITION_LAYER_ALPHA_BLEND_EXTENSION_NAME "XR_FB_composition_layer_alpha_blend" + +typedef enum XrBlendFactorFB { + XR_BLEND_FACTOR_ZERO_FB = 0, + XR_BLEND_FACTOR_ONE_FB = 1, + XR_BLEND_FACTOR_SRC_ALPHA_FB = 2, + XR_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA_FB = 3, + XR_BLEND_FACTOR_DST_ALPHA_FB = 4, + XR_BLEND_FACTOR_ONE_MINUS_DST_ALPHA_FB = 5, + XR_BLEND_FACTOR_MAX_ENUM_FB = 0x7FFFFFFF +} XrBlendFactorFB; +typedef XrFlags64 XrCompositionLayerSecureContentFlagsFB; + +// Flag bits for XrCompositionLayerSecureContentFlagsFB +static const XrCompositionLayerSecureContentFlagsFB XR_COMPOSITION_LAYER_SECURE_CONTENT_EXCLUDE_LAYER_BIT_FB = 0x00000001; +static const XrCompositionLayerSecureContentFlagsFB XR_COMPOSITION_LAYER_SECURE_CONTENT_REPLACE_LAYER_BIT_FB = 0x00000002; + +// XrCompositionLayerAlphaBlendFB extends XrCompositionLayerBaseHeader +typedef struct XrCompositionLayerAlphaBlendFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBlendFactorFB srcFactorColor; + XrBlendFactorFB dstFactorColor; + XrBlendFactorFB srcFactorAlpha; + XrBlendFactorFB dstFactorAlpha; +} XrCompositionLayerAlphaBlendFB; + + + +// XR_MND_headless is a preprocessor guard. Do not pass it to API calls. +#define XR_MND_headless 1 +#define XR_MND_headless_SPEC_VERSION 3 +#define XR_MND_HEADLESS_EXTENSION_NAME "XR_MND_headless" + + +// XR_OCULUS_android_session_state_enable is a preprocessor guard. Do not pass it to API calls. +#define XR_OCULUS_android_session_state_enable 1 +#define XR_OCULUS_android_session_state_enable_SPEC_VERSION 1 +#define XR_OCULUS_ANDROID_SESSION_STATE_ENABLE_EXTENSION_NAME "XR_OCULUS_android_session_state_enable" + + +// XR_EXT_view_configuration_depth_range is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_view_configuration_depth_range 1 +#define XR_EXT_view_configuration_depth_range_SPEC_VERSION 1 +#define XR_EXT_VIEW_CONFIGURATION_DEPTH_RANGE_EXTENSION_NAME "XR_EXT_view_configuration_depth_range" +// XrViewConfigurationDepthRangeEXT extends XrViewConfigurationView +typedef struct XrViewConfigurationDepthRangeEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + float recommendedNearZ; + float minNearZ; + float recommendedFarZ; + float maxFarZ; +} XrViewConfigurationDepthRangeEXT; + + + +// XR_EXT_conformance_automation is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_conformance_automation 1 +#define XR_EXT_conformance_automation_SPEC_VERSION 3 +#define XR_EXT_CONFORMANCE_AUTOMATION_EXTENSION_NAME "XR_EXT_conformance_automation" +typedef XrResult (XRAPI_PTR *PFN_xrSetInputDeviceActiveEXT)(XrSession session, XrPath interactionProfile, XrPath topLevelPath, XrBool32 isActive); +typedef XrResult (XRAPI_PTR *PFN_xrSetInputDeviceStateBoolEXT)(XrSession session, XrPath topLevelPath, XrPath inputSourcePath, XrBool32 state); +typedef XrResult (XRAPI_PTR *PFN_xrSetInputDeviceStateFloatEXT)(XrSession session, XrPath topLevelPath, XrPath inputSourcePath, float state); +typedef XrResult (XRAPI_PTR *PFN_xrSetInputDeviceStateVector2fEXT)(XrSession session, XrPath topLevelPath, XrPath inputSourcePath, XrVector2f state); +typedef XrResult (XRAPI_PTR *PFN_xrSetInputDeviceLocationEXT)(XrSession session, XrPath topLevelPath, XrPath inputSourcePath, XrSpace space, XrPosef pose); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrSetInputDeviceActiveEXT( + XrSession session, + XrPath interactionProfile, + XrPath topLevelPath, + XrBool32 isActive); + +XRAPI_ATTR XrResult XRAPI_CALL xrSetInputDeviceStateBoolEXT( + XrSession session, + XrPath topLevelPath, + XrPath inputSourcePath, + XrBool32 state); + +XRAPI_ATTR XrResult XRAPI_CALL xrSetInputDeviceStateFloatEXT( + XrSession session, + XrPath topLevelPath, + XrPath inputSourcePath, + float state); + +XRAPI_ATTR XrResult XRAPI_CALL xrSetInputDeviceStateVector2fEXT( + XrSession session, + XrPath topLevelPath, + XrPath inputSourcePath, + XrVector2f state); + +XRAPI_ATTR XrResult XRAPI_CALL xrSetInputDeviceLocationEXT( + XrSession session, + XrPath topLevelPath, + XrPath inputSourcePath, + XrSpace space, + XrPosef pose); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_MSFT_spatial_graph_bridge is a preprocessor guard. Do not pass it to API calls. +#define XR_MSFT_spatial_graph_bridge 1 +XR_DEFINE_HANDLE(XrSpatialGraphNodeBindingMSFT) +#define XR_GUID_SIZE_MSFT 16 +#define XR_MSFT_spatial_graph_bridge_SPEC_VERSION 2 +#define XR_MSFT_SPATIAL_GRAPH_BRIDGE_EXTENSION_NAME "XR_MSFT_spatial_graph_bridge" + +typedef enum XrSpatialGraphNodeTypeMSFT { + XR_SPATIAL_GRAPH_NODE_TYPE_STATIC_MSFT = 1, + XR_SPATIAL_GRAPH_NODE_TYPE_DYNAMIC_MSFT = 2, + XR_SPATIAL_GRAPH_NODE_TYPE_MAX_ENUM_MSFT = 0x7FFFFFFF +} XrSpatialGraphNodeTypeMSFT; +typedef struct XrSpatialGraphNodeSpaceCreateInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialGraphNodeTypeMSFT nodeType; + uint8_t nodeId[XR_GUID_SIZE_MSFT]; + XrPosef pose; +} XrSpatialGraphNodeSpaceCreateInfoMSFT; + +typedef struct XrSpatialGraphStaticNodeBindingCreateInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace space; + XrPosef poseInSpace; + XrTime time; +} XrSpatialGraphStaticNodeBindingCreateInfoMSFT; + +typedef struct XrSpatialGraphNodeBindingPropertiesGetInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSpatialGraphNodeBindingPropertiesGetInfoMSFT; + +typedef struct XrSpatialGraphNodeBindingPropertiesMSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint8_t nodeId[XR_GUID_SIZE_MSFT]; + XrPosef poseInNodeSpace; +} XrSpatialGraphNodeBindingPropertiesMSFT; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialGraphNodeSpaceMSFT)(XrSession session, const XrSpatialGraphNodeSpaceCreateInfoMSFT* createInfo, XrSpace* space); +typedef XrResult (XRAPI_PTR *PFN_xrTryCreateSpatialGraphStaticNodeBindingMSFT)(XrSession session, const XrSpatialGraphStaticNodeBindingCreateInfoMSFT* createInfo, XrSpatialGraphNodeBindingMSFT* nodeBinding); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySpatialGraphNodeBindingMSFT)(XrSpatialGraphNodeBindingMSFT nodeBinding); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpatialGraphNodeBindingPropertiesMSFT)(XrSpatialGraphNodeBindingMSFT nodeBinding, const XrSpatialGraphNodeBindingPropertiesGetInfoMSFT* getInfo, XrSpatialGraphNodeBindingPropertiesMSFT* properties); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialGraphNodeSpaceMSFT( + XrSession session, + const XrSpatialGraphNodeSpaceCreateInfoMSFT* createInfo, + XrSpace* space); + +XRAPI_ATTR XrResult XRAPI_CALL xrTryCreateSpatialGraphStaticNodeBindingMSFT( + XrSession session, + const XrSpatialGraphStaticNodeBindingCreateInfoMSFT* createInfo, + XrSpatialGraphNodeBindingMSFT* nodeBinding); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySpatialGraphNodeBindingMSFT( + XrSpatialGraphNodeBindingMSFT nodeBinding); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpatialGraphNodeBindingPropertiesMSFT( + XrSpatialGraphNodeBindingMSFT nodeBinding, + const XrSpatialGraphNodeBindingPropertiesGetInfoMSFT* getInfo, + XrSpatialGraphNodeBindingPropertiesMSFT* properties); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_MSFT_hand_interaction is a preprocessor guard. Do not pass it to API calls. +#define XR_MSFT_hand_interaction 1 +#define XR_MSFT_hand_interaction_SPEC_VERSION 1 +#define XR_MSFT_HAND_INTERACTION_EXTENSION_NAME "XR_MSFT_hand_interaction" + + +// XR_EXT_hand_tracking is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_hand_tracking 1 + +#define XR_HAND_JOINT_COUNT_EXT 26 + +XR_DEFINE_HANDLE(XrHandTrackerEXT) +#define XR_EXT_hand_tracking_SPEC_VERSION 4 +#define XR_EXT_HAND_TRACKING_EXTENSION_NAME "XR_EXT_hand_tracking" + +typedef enum XrHandEXT { + XR_HAND_LEFT_EXT = 1, + XR_HAND_RIGHT_EXT = 2, + XR_HAND_MAX_ENUM_EXT = 0x7FFFFFFF +} XrHandEXT; + +typedef enum XrHandJointEXT { + XR_HAND_JOINT_PALM_EXT = 0, + XR_HAND_JOINT_WRIST_EXT = 1, + XR_HAND_JOINT_THUMB_METACARPAL_EXT = 2, + XR_HAND_JOINT_THUMB_PROXIMAL_EXT = 3, + XR_HAND_JOINT_THUMB_DISTAL_EXT = 4, + XR_HAND_JOINT_THUMB_TIP_EXT = 5, + XR_HAND_JOINT_INDEX_METACARPAL_EXT = 6, + XR_HAND_JOINT_INDEX_PROXIMAL_EXT = 7, + XR_HAND_JOINT_INDEX_INTERMEDIATE_EXT = 8, + XR_HAND_JOINT_INDEX_DISTAL_EXT = 9, + XR_HAND_JOINT_INDEX_TIP_EXT = 10, + XR_HAND_JOINT_MIDDLE_METACARPAL_EXT = 11, + XR_HAND_JOINT_MIDDLE_PROXIMAL_EXT = 12, + XR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT = 13, + XR_HAND_JOINT_MIDDLE_DISTAL_EXT = 14, + XR_HAND_JOINT_MIDDLE_TIP_EXT = 15, + XR_HAND_JOINT_RING_METACARPAL_EXT = 16, + XR_HAND_JOINT_RING_PROXIMAL_EXT = 17, + XR_HAND_JOINT_RING_INTERMEDIATE_EXT = 18, + XR_HAND_JOINT_RING_DISTAL_EXT = 19, + XR_HAND_JOINT_RING_TIP_EXT = 20, + XR_HAND_JOINT_LITTLE_METACARPAL_EXT = 21, + XR_HAND_JOINT_LITTLE_PROXIMAL_EXT = 22, + XR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT = 23, + XR_HAND_JOINT_LITTLE_DISTAL_EXT = 24, + XR_HAND_JOINT_LITTLE_TIP_EXT = 25, + XR_HAND_JOINT_MAX_ENUM_EXT = 0x7FFFFFFF +} XrHandJointEXT; + +typedef enum XrHandJointSetEXT { + XR_HAND_JOINT_SET_DEFAULT_EXT = 0, + XR_HAND_JOINT_SET_HAND_WITH_FOREARM_ULTRALEAP = 1000149000, + XR_HAND_JOINT_SET_MAX_ENUM_EXT = 0x7FFFFFFF +} XrHandJointSetEXT; +// XrSystemHandTrackingPropertiesEXT extends XrSystemProperties +typedef struct XrSystemHandTrackingPropertiesEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsHandTracking; +} XrSystemHandTrackingPropertiesEXT; + +typedef struct XrHandTrackerCreateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrHandEXT hand; + XrHandJointSetEXT handJointSet; +} XrHandTrackerCreateInfoEXT; + +typedef struct XrHandJointsLocateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace baseSpace; + XrTime time; +} XrHandJointsLocateInfoEXT; + +typedef struct XrHandJointLocationEXT { + XrSpaceLocationFlags locationFlags; + XrPosef pose; + float radius; +} XrHandJointLocationEXT; + +typedef struct XrHandJointVelocityEXT { + XrSpaceVelocityFlags velocityFlags; + XrVector3f linearVelocity; + XrVector3f angularVelocity; +} XrHandJointVelocityEXT; + +typedef struct XrHandJointLocationsEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 isActive; + uint32_t jointCount; + XrHandJointLocationEXT* jointLocations; +} XrHandJointLocationsEXT; + +// XrHandJointVelocitiesEXT extends XrHandJointLocationsEXT +typedef struct XrHandJointVelocitiesEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t jointCount; + XrHandJointVelocityEXT* jointVelocities; +} XrHandJointVelocitiesEXT; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateHandTrackerEXT)(XrSession session, const XrHandTrackerCreateInfoEXT* createInfo, XrHandTrackerEXT* handTracker); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyHandTrackerEXT)(XrHandTrackerEXT handTracker); +typedef XrResult (XRAPI_PTR *PFN_xrLocateHandJointsEXT)(XrHandTrackerEXT handTracker, const XrHandJointsLocateInfoEXT* locateInfo, XrHandJointLocationsEXT* locations); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateHandTrackerEXT( + XrSession session, + const XrHandTrackerCreateInfoEXT* createInfo, + XrHandTrackerEXT* handTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyHandTrackerEXT( + XrHandTrackerEXT handTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrLocateHandJointsEXT( + XrHandTrackerEXT handTracker, + const XrHandJointsLocateInfoEXT* locateInfo, + XrHandJointLocationsEXT* locations); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_MSFT_hand_tracking_mesh is a preprocessor guard. Do not pass it to API calls. +#define XR_MSFT_hand_tracking_mesh 1 +#define XR_MSFT_hand_tracking_mesh_SPEC_VERSION 4 +#define XR_MSFT_HAND_TRACKING_MESH_EXTENSION_NAME "XR_MSFT_hand_tracking_mesh" + +typedef enum XrHandPoseTypeMSFT { + XR_HAND_POSE_TYPE_TRACKED_MSFT = 0, + XR_HAND_POSE_TYPE_REFERENCE_OPEN_PALM_MSFT = 1, + XR_HAND_POSE_TYPE_MAX_ENUM_MSFT = 0x7FFFFFFF +} XrHandPoseTypeMSFT; +// XrSystemHandTrackingMeshPropertiesMSFT extends XrSystemProperties +typedef struct XrSystemHandTrackingMeshPropertiesMSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsHandTrackingMesh; + uint32_t maxHandMeshIndexCount; + uint32_t maxHandMeshVertexCount; +} XrSystemHandTrackingMeshPropertiesMSFT; + +typedef struct XrHandMeshSpaceCreateInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrHandPoseTypeMSFT handPoseType; + XrPosef poseInHandMeshSpace; +} XrHandMeshSpaceCreateInfoMSFT; + +typedef struct XrHandMeshUpdateInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrTime time; + XrHandPoseTypeMSFT handPoseType; +} XrHandMeshUpdateInfoMSFT; + +typedef struct XrHandMeshIndexBufferMSFT { + uint32_t indexBufferKey; + uint32_t indexCapacityInput; + uint32_t indexCountOutput; + uint32_t* indices; +} XrHandMeshIndexBufferMSFT; + +typedef struct XrHandMeshVertexMSFT { + XrVector3f position; + XrVector3f normal; +} XrHandMeshVertexMSFT; + +typedef struct XrHandMeshVertexBufferMSFT { + XrTime vertexUpdateTime; + uint32_t vertexCapacityInput; + uint32_t vertexCountOutput; + XrHandMeshVertexMSFT* vertices; +} XrHandMeshVertexBufferMSFT; + +typedef struct XrHandMeshMSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 isActive; + XrBool32 indexBufferChanged; + XrBool32 vertexBufferChanged; + XrHandMeshIndexBufferMSFT indexBuffer; + XrHandMeshVertexBufferMSFT vertexBuffer; +} XrHandMeshMSFT; + +// XrHandPoseTypeInfoMSFT extends XrHandTrackerCreateInfoEXT +typedef struct XrHandPoseTypeInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrHandPoseTypeMSFT handPoseType; +} XrHandPoseTypeInfoMSFT; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateHandMeshSpaceMSFT)(XrHandTrackerEXT handTracker, const XrHandMeshSpaceCreateInfoMSFT* createInfo, XrSpace* space); +typedef XrResult (XRAPI_PTR *PFN_xrUpdateHandMeshMSFT)(XrHandTrackerEXT handTracker, const XrHandMeshUpdateInfoMSFT* updateInfo, XrHandMeshMSFT* handMesh); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateHandMeshSpaceMSFT( + XrHandTrackerEXT handTracker, + const XrHandMeshSpaceCreateInfoMSFT* createInfo, + XrSpace* space); + +XRAPI_ATTR XrResult XRAPI_CALL xrUpdateHandMeshMSFT( + XrHandTrackerEXT handTracker, + const XrHandMeshUpdateInfoMSFT* updateInfo, + XrHandMeshMSFT* handMesh); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_MSFT_secondary_view_configuration is a preprocessor guard. Do not pass it to API calls. +#define XR_MSFT_secondary_view_configuration 1 +#define XR_MSFT_secondary_view_configuration_SPEC_VERSION 1 +#define XR_MSFT_SECONDARY_VIEW_CONFIGURATION_EXTENSION_NAME "XR_MSFT_secondary_view_configuration" +// XrSecondaryViewConfigurationSessionBeginInfoMSFT extends XrSessionBeginInfo +typedef struct XrSecondaryViewConfigurationSessionBeginInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t viewConfigurationCount; + const XrViewConfigurationType* enabledViewConfigurationTypes; +} XrSecondaryViewConfigurationSessionBeginInfoMSFT; + +typedef struct XrSecondaryViewConfigurationStateMSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrViewConfigurationType viewConfigurationType; + XrBool32 active; +} XrSecondaryViewConfigurationStateMSFT; + +// XrSecondaryViewConfigurationFrameStateMSFT extends XrFrameState +typedef struct XrSecondaryViewConfigurationFrameStateMSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t viewConfigurationCount; + XrSecondaryViewConfigurationStateMSFT* viewConfigurationStates; +} XrSecondaryViewConfigurationFrameStateMSFT; + +typedef struct XrSecondaryViewConfigurationLayerInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrViewConfigurationType viewConfigurationType; + XrEnvironmentBlendMode environmentBlendMode; + uint32_t layerCount; + const XrCompositionLayerBaseHeader* const* layers; +} XrSecondaryViewConfigurationLayerInfoMSFT; + +// XrSecondaryViewConfigurationFrameEndInfoMSFT extends XrFrameEndInfo +typedef struct XrSecondaryViewConfigurationFrameEndInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t viewConfigurationCount; + const XrSecondaryViewConfigurationLayerInfoMSFT* viewConfigurationLayersInfo; +} XrSecondaryViewConfigurationFrameEndInfoMSFT; + +// XrSecondaryViewConfigurationSwapchainCreateInfoMSFT extends XrSwapchainCreateInfo +typedef struct XrSecondaryViewConfigurationSwapchainCreateInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrViewConfigurationType viewConfigurationType; +} XrSecondaryViewConfigurationSwapchainCreateInfoMSFT; + + + +// XR_MSFT_first_person_observer is a preprocessor guard. Do not pass it to API calls. +#define XR_MSFT_first_person_observer 1 +#define XR_MSFT_first_person_observer_SPEC_VERSION 1 +#define XR_MSFT_FIRST_PERSON_OBSERVER_EXTENSION_NAME "XR_MSFT_first_person_observer" + + +// XR_MSFT_controller_model is a preprocessor guard. Do not pass it to API calls. +#define XR_MSFT_controller_model 1 + +#define XR_NULL_CONTROLLER_MODEL_KEY_MSFT 0 + +XR_DEFINE_ATOM(XrControllerModelKeyMSFT) +#define XR_MAX_CONTROLLER_MODEL_NODE_NAME_SIZE_MSFT 64 +#define XR_MSFT_controller_model_SPEC_VERSION 2 +#define XR_MSFT_CONTROLLER_MODEL_EXTENSION_NAME "XR_MSFT_controller_model" +typedef struct XrControllerModelKeyStateMSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrControllerModelKeyMSFT modelKey; +} XrControllerModelKeyStateMSFT; + +typedef struct XrControllerModelNodePropertiesMSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; + char parentNodeName[XR_MAX_CONTROLLER_MODEL_NODE_NAME_SIZE_MSFT]; + char nodeName[XR_MAX_CONTROLLER_MODEL_NODE_NAME_SIZE_MSFT]; +} XrControllerModelNodePropertiesMSFT; + +typedef struct XrControllerModelPropertiesMSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t nodeCapacityInput; + uint32_t nodeCountOutput; + XrControllerModelNodePropertiesMSFT* nodeProperties; +} XrControllerModelPropertiesMSFT; + +typedef struct XrControllerModelNodeStateMSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrPosef nodePose; +} XrControllerModelNodeStateMSFT; + +typedef struct XrControllerModelStateMSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t nodeCapacityInput; + uint32_t nodeCountOutput; + XrControllerModelNodeStateMSFT* nodeStates; +} XrControllerModelStateMSFT; + +typedef XrResult (XRAPI_PTR *PFN_xrGetControllerModelKeyMSFT)(XrSession session, XrPath topLevelUserPath, XrControllerModelKeyStateMSFT* controllerModelKeyState); +typedef XrResult (XRAPI_PTR *PFN_xrLoadControllerModelMSFT)(XrSession session, XrControllerModelKeyMSFT modelKey, uint32_t bufferCapacityInput, uint32_t* bufferCountOutput, uint8_t* buffer); +typedef XrResult (XRAPI_PTR *PFN_xrGetControllerModelPropertiesMSFT)(XrSession session, XrControllerModelKeyMSFT modelKey, XrControllerModelPropertiesMSFT* properties); +typedef XrResult (XRAPI_PTR *PFN_xrGetControllerModelStateMSFT)(XrSession session, XrControllerModelKeyMSFT modelKey, XrControllerModelStateMSFT* state); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetControllerModelKeyMSFT( + XrSession session, + XrPath topLevelUserPath, + XrControllerModelKeyStateMSFT* controllerModelKeyState); + +XRAPI_ATTR XrResult XRAPI_CALL xrLoadControllerModelMSFT( + XrSession session, + XrControllerModelKeyMSFT modelKey, + uint32_t bufferCapacityInput, + uint32_t* bufferCountOutput, + uint8_t* buffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetControllerModelPropertiesMSFT( + XrSession session, + XrControllerModelKeyMSFT modelKey, + XrControllerModelPropertiesMSFT* properties); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetControllerModelStateMSFT( + XrSession session, + XrControllerModelKeyMSFT modelKey, + XrControllerModelStateMSFT* state); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_win32_appcontainer_compatible is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_win32_appcontainer_compatible 1 +#define XR_EXT_win32_appcontainer_compatible_SPEC_VERSION 1 +#define XR_EXT_WIN32_APPCONTAINER_COMPATIBLE_EXTENSION_NAME "XR_EXT_win32_appcontainer_compatible" + + +// XR_EPIC_view_configuration_fov is a preprocessor guard. Do not pass it to API calls. +#define XR_EPIC_view_configuration_fov 1 +#define XR_EPIC_view_configuration_fov_SPEC_VERSION 2 +#define XR_EPIC_VIEW_CONFIGURATION_FOV_EXTENSION_NAME "XR_EPIC_view_configuration_fov" +// XrViewConfigurationViewFovEPIC extends XrViewConfigurationView +typedef struct XrViewConfigurationViewFovEPIC { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrFovf recommendedFov; + XrFovf maxMutableFov; +} XrViewConfigurationViewFovEPIC; + + + +// XR_MSFT_composition_layer_reprojection is a preprocessor guard. Do not pass it to API calls. +#define XR_MSFT_composition_layer_reprojection 1 +#define XR_MSFT_composition_layer_reprojection_SPEC_VERSION 1 +#define XR_MSFT_COMPOSITION_LAYER_REPROJECTION_EXTENSION_NAME "XR_MSFT_composition_layer_reprojection" + +typedef enum XrReprojectionModeMSFT { + XR_REPROJECTION_MODE_DEPTH_MSFT = 1, + XR_REPROJECTION_MODE_PLANAR_FROM_DEPTH_MSFT = 2, + XR_REPROJECTION_MODE_PLANAR_MANUAL_MSFT = 3, + XR_REPROJECTION_MODE_ORIENTATION_ONLY_MSFT = 4, + XR_REPROJECTION_MODE_MAX_ENUM_MSFT = 0x7FFFFFFF +} XrReprojectionModeMSFT; +// XrCompositionLayerReprojectionInfoMSFT extends XrCompositionLayerProjection +typedef struct XrCompositionLayerReprojectionInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrReprojectionModeMSFT reprojectionMode; +} XrCompositionLayerReprojectionInfoMSFT; + +// XrCompositionLayerReprojectionPlaneOverrideMSFT extends XrCompositionLayerProjection +typedef struct XrCompositionLayerReprojectionPlaneOverrideMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrVector3f position; + XrVector3f normal; + XrVector3f velocity; +} XrCompositionLayerReprojectionPlaneOverrideMSFT; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateReprojectionModesMSFT)(XrInstance instance, XrSystemId systemId, XrViewConfigurationType viewConfigurationType, uint32_t modeCapacityInput, uint32_t* modeCountOutput, XrReprojectionModeMSFT* modes); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateReprojectionModesMSFT( + XrInstance instance, + XrSystemId systemId, + XrViewConfigurationType viewConfigurationType, + uint32_t modeCapacityInput, + uint32_t* modeCountOutput, + XrReprojectionModeMSFT* modes); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_HUAWEI_controller_interaction is a preprocessor guard. Do not pass it to API calls. +#define XR_HUAWEI_controller_interaction 1 +#define XR_HUAWEI_controller_interaction_SPEC_VERSION 1 +#define XR_HUAWEI_CONTROLLER_INTERACTION_EXTENSION_NAME "XR_HUAWEI_controller_interaction" + + +// XR_FB_swapchain_update_state is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_swapchain_update_state 1 +#define XR_FB_swapchain_update_state_SPEC_VERSION 3 +#define XR_FB_SWAPCHAIN_UPDATE_STATE_EXTENSION_NAME "XR_FB_swapchain_update_state" +typedef struct XR_MAY_ALIAS XrSwapchainStateBaseHeaderFB { + XrStructureType type; + void* XR_MAY_ALIAS next; +} XrSwapchainStateBaseHeaderFB; + +typedef XrResult (XRAPI_PTR *PFN_xrUpdateSwapchainFB)(XrSwapchain swapchain, const XrSwapchainStateBaseHeaderFB* state); +typedef XrResult (XRAPI_PTR *PFN_xrGetSwapchainStateFB)(XrSwapchain swapchain, XrSwapchainStateBaseHeaderFB* state); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrUpdateSwapchainFB( + XrSwapchain swapchain, + const XrSwapchainStateBaseHeaderFB* state); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSwapchainStateFB( + XrSwapchain swapchain, + XrSwapchainStateBaseHeaderFB* state); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_composition_layer_secure_content is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_composition_layer_secure_content 1 +#define XR_FB_composition_layer_secure_content_SPEC_VERSION 1 +#define XR_FB_COMPOSITION_LAYER_SECURE_CONTENT_EXTENSION_NAME "XR_FB_composition_layer_secure_content" +// XrCompositionLayerSecureContentFB extends XrCompositionLayerBaseHeader +typedef struct XrCompositionLayerSecureContentFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCompositionLayerSecureContentFlagsFB flags; +} XrCompositionLayerSecureContentFB; + + + +// XR_FB_body_tracking is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_body_tracking 1 +XR_DEFINE_HANDLE(XrBodyTrackerFB) +#define XR_FB_body_tracking_SPEC_VERSION 1 +#define XR_FB_BODY_TRACKING_EXTENSION_NAME "XR_FB_body_tracking" + +typedef enum XrBodyJointFB { + XR_BODY_JOINT_ROOT_FB = 0, + XR_BODY_JOINT_HIPS_FB = 1, + XR_BODY_JOINT_SPINE_LOWER_FB = 2, + XR_BODY_JOINT_SPINE_MIDDLE_FB = 3, + XR_BODY_JOINT_SPINE_UPPER_FB = 4, + XR_BODY_JOINT_CHEST_FB = 5, + XR_BODY_JOINT_NECK_FB = 6, + XR_BODY_JOINT_HEAD_FB = 7, + XR_BODY_JOINT_LEFT_SHOULDER_FB = 8, + XR_BODY_JOINT_LEFT_SCAPULA_FB = 9, + XR_BODY_JOINT_LEFT_ARM_UPPER_FB = 10, + XR_BODY_JOINT_LEFT_ARM_LOWER_FB = 11, + XR_BODY_JOINT_LEFT_HAND_WRIST_TWIST_FB = 12, + XR_BODY_JOINT_RIGHT_SHOULDER_FB = 13, + XR_BODY_JOINT_RIGHT_SCAPULA_FB = 14, + XR_BODY_JOINT_RIGHT_ARM_UPPER_FB = 15, + XR_BODY_JOINT_RIGHT_ARM_LOWER_FB = 16, + XR_BODY_JOINT_RIGHT_HAND_WRIST_TWIST_FB = 17, + XR_BODY_JOINT_LEFT_HAND_PALM_FB = 18, + XR_BODY_JOINT_LEFT_HAND_WRIST_FB = 19, + XR_BODY_JOINT_LEFT_HAND_THUMB_METACARPAL_FB = 20, + XR_BODY_JOINT_LEFT_HAND_THUMB_PROXIMAL_FB = 21, + XR_BODY_JOINT_LEFT_HAND_THUMB_DISTAL_FB = 22, + XR_BODY_JOINT_LEFT_HAND_THUMB_TIP_FB = 23, + XR_BODY_JOINT_LEFT_HAND_INDEX_METACARPAL_FB = 24, + XR_BODY_JOINT_LEFT_HAND_INDEX_PROXIMAL_FB = 25, + XR_BODY_JOINT_LEFT_HAND_INDEX_INTERMEDIATE_FB = 26, + XR_BODY_JOINT_LEFT_HAND_INDEX_DISTAL_FB = 27, + XR_BODY_JOINT_LEFT_HAND_INDEX_TIP_FB = 28, + XR_BODY_JOINT_LEFT_HAND_MIDDLE_METACARPAL_FB = 29, + XR_BODY_JOINT_LEFT_HAND_MIDDLE_PROXIMAL_FB = 30, + XR_BODY_JOINT_LEFT_HAND_MIDDLE_INTERMEDIATE_FB = 31, + XR_BODY_JOINT_LEFT_HAND_MIDDLE_DISTAL_FB = 32, + XR_BODY_JOINT_LEFT_HAND_MIDDLE_TIP_FB = 33, + XR_BODY_JOINT_LEFT_HAND_RING_METACARPAL_FB = 34, + XR_BODY_JOINT_LEFT_HAND_RING_PROXIMAL_FB = 35, + XR_BODY_JOINT_LEFT_HAND_RING_INTERMEDIATE_FB = 36, + XR_BODY_JOINT_LEFT_HAND_RING_DISTAL_FB = 37, + XR_BODY_JOINT_LEFT_HAND_RING_TIP_FB = 38, + XR_BODY_JOINT_LEFT_HAND_LITTLE_METACARPAL_FB = 39, + XR_BODY_JOINT_LEFT_HAND_LITTLE_PROXIMAL_FB = 40, + XR_BODY_JOINT_LEFT_HAND_LITTLE_INTERMEDIATE_FB = 41, + XR_BODY_JOINT_LEFT_HAND_LITTLE_DISTAL_FB = 42, + XR_BODY_JOINT_LEFT_HAND_LITTLE_TIP_FB = 43, + XR_BODY_JOINT_RIGHT_HAND_PALM_FB = 44, + XR_BODY_JOINT_RIGHT_HAND_WRIST_FB = 45, + XR_BODY_JOINT_RIGHT_HAND_THUMB_METACARPAL_FB = 46, + XR_BODY_JOINT_RIGHT_HAND_THUMB_PROXIMAL_FB = 47, + XR_BODY_JOINT_RIGHT_HAND_THUMB_DISTAL_FB = 48, + XR_BODY_JOINT_RIGHT_HAND_THUMB_TIP_FB = 49, + XR_BODY_JOINT_RIGHT_HAND_INDEX_METACARPAL_FB = 50, + XR_BODY_JOINT_RIGHT_HAND_INDEX_PROXIMAL_FB = 51, + XR_BODY_JOINT_RIGHT_HAND_INDEX_INTERMEDIATE_FB = 52, + XR_BODY_JOINT_RIGHT_HAND_INDEX_DISTAL_FB = 53, + XR_BODY_JOINT_RIGHT_HAND_INDEX_TIP_FB = 54, + XR_BODY_JOINT_RIGHT_HAND_MIDDLE_METACARPAL_FB = 55, + XR_BODY_JOINT_RIGHT_HAND_MIDDLE_PROXIMAL_FB = 56, + XR_BODY_JOINT_RIGHT_HAND_MIDDLE_INTERMEDIATE_FB = 57, + XR_BODY_JOINT_RIGHT_HAND_MIDDLE_DISTAL_FB = 58, + XR_BODY_JOINT_RIGHT_HAND_MIDDLE_TIP_FB = 59, + XR_BODY_JOINT_RIGHT_HAND_RING_METACARPAL_FB = 60, + XR_BODY_JOINT_RIGHT_HAND_RING_PROXIMAL_FB = 61, + XR_BODY_JOINT_RIGHT_HAND_RING_INTERMEDIATE_FB = 62, + XR_BODY_JOINT_RIGHT_HAND_RING_DISTAL_FB = 63, + XR_BODY_JOINT_RIGHT_HAND_RING_TIP_FB = 64, + XR_BODY_JOINT_RIGHT_HAND_LITTLE_METACARPAL_FB = 65, + XR_BODY_JOINT_RIGHT_HAND_LITTLE_PROXIMAL_FB = 66, + XR_BODY_JOINT_RIGHT_HAND_LITTLE_INTERMEDIATE_FB = 67, + XR_BODY_JOINT_RIGHT_HAND_LITTLE_DISTAL_FB = 68, + XR_BODY_JOINT_RIGHT_HAND_LITTLE_TIP_FB = 69, + XR_BODY_JOINT_COUNT_FB = 70, + XR_BODY_JOINT_NONE_FB = -1, + XR_BODY_JOINT_MAX_ENUM_FB = 0x7FFFFFFF +} XrBodyJointFB; + +typedef enum XrBodyJointSetFB { + XR_BODY_JOINT_SET_DEFAULT_FB = 0, + XR_BODY_JOINT_SET_FULL_BODY_META = 1000274000, + XR_BODY_JOINT_SET_MAX_ENUM_FB = 0x7FFFFFFF +} XrBodyJointSetFB; +typedef struct XrBodyJointLocationFB { + XrSpaceLocationFlags locationFlags; + XrPosef pose; +} XrBodyJointLocationFB; + +// XrSystemBodyTrackingPropertiesFB extends XrSystemProperties +typedef struct XrSystemBodyTrackingPropertiesFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsBodyTracking; +} XrSystemBodyTrackingPropertiesFB; + +typedef struct XrBodyTrackerCreateInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBodyJointSetFB bodyJointSet; +} XrBodyTrackerCreateInfoFB; + +typedef struct XrBodySkeletonJointFB { + int32_t joint; + int32_t parentJoint; + XrPosef pose; +} XrBodySkeletonJointFB; + +typedef struct XrBodySkeletonFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t jointCount; + XrBodySkeletonJointFB* joints; +} XrBodySkeletonFB; + +typedef struct XrBodyJointsLocateInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace baseSpace; + XrTime time; +} XrBodyJointsLocateInfoFB; + +typedef struct XrBodyJointLocationsFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 isActive; + float confidence; + uint32_t jointCount; + XrBodyJointLocationFB* jointLocations; + uint32_t skeletonChangedCount; + XrTime time; +} XrBodyJointLocationsFB; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateBodyTrackerFB)(XrSession session, const XrBodyTrackerCreateInfoFB* createInfo, XrBodyTrackerFB* bodyTracker); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyBodyTrackerFB)(XrBodyTrackerFB bodyTracker); +typedef XrResult (XRAPI_PTR *PFN_xrLocateBodyJointsFB)(XrBodyTrackerFB bodyTracker, const XrBodyJointsLocateInfoFB* locateInfo, XrBodyJointLocationsFB* locations); +typedef XrResult (XRAPI_PTR *PFN_xrGetBodySkeletonFB)(XrBodyTrackerFB bodyTracker, XrBodySkeletonFB* skeleton); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateBodyTrackerFB( + XrSession session, + const XrBodyTrackerCreateInfoFB* createInfo, + XrBodyTrackerFB* bodyTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyBodyTrackerFB( + XrBodyTrackerFB bodyTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrLocateBodyJointsFB( + XrBodyTrackerFB bodyTracker, + const XrBodyJointsLocateInfoFB* locateInfo, + XrBodyJointLocationsFB* locations); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetBodySkeletonFB( + XrBodyTrackerFB bodyTracker, + XrBodySkeletonFB* skeleton); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_dpad_binding is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_dpad_binding 1 +#define XR_EXT_dpad_binding_SPEC_VERSION 1 +#define XR_EXT_DPAD_BINDING_EXTENSION_NAME "XR_EXT_dpad_binding" +typedef struct XrInteractionProfileDpadBindingEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPath binding; + XrActionSet actionSet; + float forceThreshold; + float forceThresholdReleased; + float centerRegion; + float wedgeAngle; + XrBool32 isSticky; + const XrHapticBaseHeader* onHaptic; + const XrHapticBaseHeader* offHaptic; +} XrInteractionProfileDpadBindingEXT; + + + +// XR_VALVE_analog_threshold is a preprocessor guard. Do not pass it to API calls. +#define XR_VALVE_analog_threshold 1 +#define XR_VALVE_analog_threshold_SPEC_VERSION 2 +#define XR_VALVE_ANALOG_THRESHOLD_EXTENSION_NAME "XR_VALVE_analog_threshold" +typedef struct XrInteractionProfileAnalogThresholdVALVE { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAction action; + XrPath binding; + float onThreshold; + float offThreshold; + const XrHapticBaseHeader* onHaptic; + const XrHapticBaseHeader* offHaptic; +} XrInteractionProfileAnalogThresholdVALVE; + + + +// XR_EXT_hand_joints_motion_range is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_hand_joints_motion_range 1 +#define XR_EXT_hand_joints_motion_range_SPEC_VERSION 1 +#define XR_EXT_HAND_JOINTS_MOTION_RANGE_EXTENSION_NAME "XR_EXT_hand_joints_motion_range" + +typedef enum XrHandJointsMotionRangeEXT { + XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT = 1, + XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT = 2, + XR_HAND_JOINTS_MOTION_RANGE_MAX_ENUM_EXT = 0x7FFFFFFF +} XrHandJointsMotionRangeEXT; +// XrHandJointsMotionRangeInfoEXT extends XrHandJointsLocateInfoEXT +typedef struct XrHandJointsMotionRangeInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrHandJointsMotionRangeEXT handJointsMotionRange; +} XrHandJointsMotionRangeInfoEXT; + + + +// XR_EXT_samsung_odyssey_controller is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_samsung_odyssey_controller 1 +#define XR_EXT_samsung_odyssey_controller_SPEC_VERSION 1 +#define XR_EXT_SAMSUNG_ODYSSEY_CONTROLLER_EXTENSION_NAME "XR_EXT_samsung_odyssey_controller" + + +// XR_EXT_hp_mixed_reality_controller is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_hp_mixed_reality_controller 1 +#define XR_EXT_hp_mixed_reality_controller_SPEC_VERSION 1 +#define XR_EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME "XR_EXT_hp_mixed_reality_controller" + + +// XR_MND_swapchain_usage_input_attachment_bit is a preprocessor guard. Do not pass it to API calls. +#define XR_MND_swapchain_usage_input_attachment_bit 1 +#define XR_MND_swapchain_usage_input_attachment_bit_SPEC_VERSION 2 +#define XR_MND_SWAPCHAIN_USAGE_INPUT_ATTACHMENT_BIT_EXTENSION_NAME "XR_MND_swapchain_usage_input_attachment_bit" + + +// XR_MSFT_scene_understanding is a preprocessor guard. Do not pass it to API calls. +#define XR_MSFT_scene_understanding 1 + + XR_DEFINE_HANDLE(XrSceneMSFT) + + + XR_DEFINE_HANDLE(XrSceneObserverMSFT) + +#define XR_MSFT_scene_understanding_SPEC_VERSION 2 +#define XR_MSFT_SCENE_UNDERSTANDING_EXTENSION_NAME "XR_MSFT_scene_understanding" + +typedef enum XrSceneComputeFeatureMSFT { + XR_SCENE_COMPUTE_FEATURE_PLANE_MSFT = 1, + XR_SCENE_COMPUTE_FEATURE_PLANE_MESH_MSFT = 2, + XR_SCENE_COMPUTE_FEATURE_VISUAL_MESH_MSFT = 3, + XR_SCENE_COMPUTE_FEATURE_COLLIDER_MESH_MSFT = 4, + XR_SCENE_COMPUTE_FEATURE_SERIALIZE_SCENE_MSFT = 1000098000, + XR_SCENE_COMPUTE_FEATURE_MARKER_MSFT = 1000147000, + XR_SCENE_COMPUTE_FEATURE_MAX_ENUM_MSFT = 0x7FFFFFFF +} XrSceneComputeFeatureMSFT; + +typedef enum XrSceneComputeConsistencyMSFT { + XR_SCENE_COMPUTE_CONSISTENCY_SNAPSHOT_COMPLETE_MSFT = 1, + XR_SCENE_COMPUTE_CONSISTENCY_SNAPSHOT_INCOMPLETE_FAST_MSFT = 2, + XR_SCENE_COMPUTE_CONSISTENCY_OCCLUSION_OPTIMIZED_MSFT = 3, + XR_SCENE_COMPUTE_CONSISTENCY_MAX_ENUM_MSFT = 0x7FFFFFFF +} XrSceneComputeConsistencyMSFT; + +typedef enum XrMeshComputeLodMSFT { + XR_MESH_COMPUTE_LOD_COARSE_MSFT = 1, + XR_MESH_COMPUTE_LOD_MEDIUM_MSFT = 2, + XR_MESH_COMPUTE_LOD_FINE_MSFT = 3, + XR_MESH_COMPUTE_LOD_UNLIMITED_MSFT = 4, + XR_MESH_COMPUTE_LOD_MAX_ENUM_MSFT = 0x7FFFFFFF +} XrMeshComputeLodMSFT; + +typedef enum XrSceneComponentTypeMSFT { + XR_SCENE_COMPONENT_TYPE_INVALID_MSFT = -1, + XR_SCENE_COMPONENT_TYPE_OBJECT_MSFT = 1, + XR_SCENE_COMPONENT_TYPE_PLANE_MSFT = 2, + XR_SCENE_COMPONENT_TYPE_VISUAL_MESH_MSFT = 3, + XR_SCENE_COMPONENT_TYPE_COLLIDER_MESH_MSFT = 4, + XR_SCENE_COMPONENT_TYPE_SERIALIZED_SCENE_FRAGMENT_MSFT = 1000098000, + XR_SCENE_COMPONENT_TYPE_MARKER_MSFT = 1000147000, + XR_SCENE_COMPONENT_TYPE_MAX_ENUM_MSFT = 0x7FFFFFFF +} XrSceneComponentTypeMSFT; + +typedef enum XrSceneObjectTypeMSFT { + XR_SCENE_OBJECT_TYPE_UNCATEGORIZED_MSFT = -1, + XR_SCENE_OBJECT_TYPE_BACKGROUND_MSFT = 1, + XR_SCENE_OBJECT_TYPE_WALL_MSFT = 2, + XR_SCENE_OBJECT_TYPE_FLOOR_MSFT = 3, + XR_SCENE_OBJECT_TYPE_CEILING_MSFT = 4, + XR_SCENE_OBJECT_TYPE_PLATFORM_MSFT = 5, + XR_SCENE_OBJECT_TYPE_INFERRED_MSFT = 6, + XR_SCENE_OBJECT_TYPE_MAX_ENUM_MSFT = 0x7FFFFFFF +} XrSceneObjectTypeMSFT; + +typedef enum XrScenePlaneAlignmentTypeMSFT { + XR_SCENE_PLANE_ALIGNMENT_TYPE_NON_ORTHOGONAL_MSFT = 0, + XR_SCENE_PLANE_ALIGNMENT_TYPE_HORIZONTAL_MSFT = 1, + XR_SCENE_PLANE_ALIGNMENT_TYPE_VERTICAL_MSFT = 2, + XR_SCENE_PLANE_ALIGNMENT_TYPE_MAX_ENUM_MSFT = 0x7FFFFFFF +} XrScenePlaneAlignmentTypeMSFT; + +typedef enum XrSceneComputeStateMSFT { + XR_SCENE_COMPUTE_STATE_NONE_MSFT = 0, + XR_SCENE_COMPUTE_STATE_UPDATING_MSFT = 1, + XR_SCENE_COMPUTE_STATE_COMPLETED_MSFT = 2, + XR_SCENE_COMPUTE_STATE_COMPLETED_WITH_ERROR_MSFT = 3, + XR_SCENE_COMPUTE_STATE_MAX_ENUM_MSFT = 0x7FFFFFFF +} XrSceneComputeStateMSFT; +typedef struct XrUuidMSFT { + uint8_t bytes[16]; +} XrUuidMSFT; + +typedef struct XrSceneObserverCreateInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSceneObserverCreateInfoMSFT; + +typedef struct XrSceneCreateInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSceneCreateInfoMSFT; + +typedef struct XrSceneSphereBoundMSFT { + XrVector3f center; + float radius; +} XrSceneSphereBoundMSFT; + +typedef struct XrSceneOrientedBoxBoundMSFT { + XrPosef pose; + XrVector3f extents; +} XrSceneOrientedBoxBoundMSFT; + +typedef struct XrSceneFrustumBoundMSFT { + XrPosef pose; + XrFovf fov; + float farDistance; +} XrSceneFrustumBoundMSFT; + +typedef struct XrSceneBoundsMSFT { + XrSpace space; + XrTime time; + uint32_t sphereCount; + const XrSceneSphereBoundMSFT* spheres; + uint32_t boxCount; + const XrSceneOrientedBoxBoundMSFT* boxes; + uint32_t frustumCount; + const XrSceneFrustumBoundMSFT* frustums; +} XrSceneBoundsMSFT; + +typedef struct XrNewSceneComputeInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t requestedFeatureCount; + const XrSceneComputeFeatureMSFT* requestedFeatures; + XrSceneComputeConsistencyMSFT consistency; + XrSceneBoundsMSFT bounds; +} XrNewSceneComputeInfoMSFT; + +// XrVisualMeshComputeLodInfoMSFT extends XrNewSceneComputeInfoMSFT +typedef struct XrVisualMeshComputeLodInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrMeshComputeLodMSFT lod; +} XrVisualMeshComputeLodInfoMSFT; + +typedef struct XrSceneComponentMSFT { + XrSceneComponentTypeMSFT componentType; + XrUuidMSFT id; + XrUuidMSFT parentId; + XrTime updateTime; +} XrSceneComponentMSFT; + +typedef struct XrSceneComponentsMSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t componentCapacityInput; + uint32_t componentCountOutput; + XrSceneComponentMSFT* components; +} XrSceneComponentsMSFT; + +typedef struct XrSceneComponentsGetInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSceneComponentTypeMSFT componentType; +} XrSceneComponentsGetInfoMSFT; + +typedef struct XrSceneComponentLocationMSFT { + XrSpaceLocationFlags flags; + XrPosef pose; +} XrSceneComponentLocationMSFT; + +typedef struct XrSceneComponentLocationsMSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t locationCount; + XrSceneComponentLocationMSFT* locations; +} XrSceneComponentLocationsMSFT; + +typedef struct XrSceneComponentsLocateInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace baseSpace; + XrTime time; + uint32_t componentIdCount; + const XrUuidMSFT* componentIds; +} XrSceneComponentsLocateInfoMSFT; + +typedef struct XrSceneObjectMSFT { + XrSceneObjectTypeMSFT objectType; +} XrSceneObjectMSFT; + +// XrSceneObjectsMSFT extends XrSceneComponentsMSFT +typedef struct XrSceneObjectsMSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t sceneObjectCount; + XrSceneObjectMSFT* sceneObjects; +} XrSceneObjectsMSFT; + +// XrSceneComponentParentFilterInfoMSFT extends XrSceneComponentsGetInfoMSFT +typedef struct XrSceneComponentParentFilterInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrUuidMSFT parentId; +} XrSceneComponentParentFilterInfoMSFT; + +// XrSceneObjectTypesFilterInfoMSFT extends XrSceneComponentsGetInfoMSFT +typedef struct XrSceneObjectTypesFilterInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t objectTypeCount; + const XrSceneObjectTypeMSFT* objectTypes; +} XrSceneObjectTypesFilterInfoMSFT; + +typedef struct XrScenePlaneMSFT { + XrScenePlaneAlignmentTypeMSFT alignment; + XrExtent2Df size; + uint64_t meshBufferId; + XrBool32 supportsIndicesUint16; +} XrScenePlaneMSFT; + +// XrScenePlanesMSFT extends XrSceneComponentsMSFT +typedef struct XrScenePlanesMSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t scenePlaneCount; + XrScenePlaneMSFT* scenePlanes; +} XrScenePlanesMSFT; + +// XrScenePlaneAlignmentFilterInfoMSFT extends XrSceneComponentsGetInfoMSFT +typedef struct XrScenePlaneAlignmentFilterInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t alignmentCount; + const XrScenePlaneAlignmentTypeMSFT* alignments; +} XrScenePlaneAlignmentFilterInfoMSFT; + +typedef struct XrSceneMeshMSFT { + uint64_t meshBufferId; + XrBool32 supportsIndicesUint16; +} XrSceneMeshMSFT; + +// XrSceneMeshesMSFT extends XrSceneComponentsMSFT +typedef struct XrSceneMeshesMSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t sceneMeshCount; + XrSceneMeshMSFT* sceneMeshes; +} XrSceneMeshesMSFT; + +typedef struct XrSceneMeshBuffersGetInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint64_t meshBufferId; +} XrSceneMeshBuffersGetInfoMSFT; + +typedef struct XrSceneMeshBuffersMSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; +} XrSceneMeshBuffersMSFT; + +// XrSceneMeshVertexBufferMSFT extends XrSceneMeshBuffersMSFT +typedef struct XrSceneMeshVertexBufferMSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t vertexCapacityInput; + uint32_t vertexCountOutput; + XrVector3f* vertices; +} XrSceneMeshVertexBufferMSFT; + +// XrSceneMeshIndicesUint32MSFT extends XrSceneMeshBuffersMSFT +typedef struct XrSceneMeshIndicesUint32MSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t indexCapacityInput; + uint32_t indexCountOutput; + uint32_t* indices; +} XrSceneMeshIndicesUint32MSFT; + +// XrSceneMeshIndicesUint16MSFT extends XrSceneMeshBuffersMSFT +typedef struct XrSceneMeshIndicesUint16MSFT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t indexCapacityInput; + uint32_t indexCountOutput; + uint16_t* indices; +} XrSceneMeshIndicesUint16MSFT; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateSceneComputeFeaturesMSFT)(XrInstance instance, XrSystemId systemId, uint32_t featureCapacityInput, uint32_t* featureCountOutput, XrSceneComputeFeatureMSFT* features); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSceneObserverMSFT)(XrSession session, const XrSceneObserverCreateInfoMSFT* createInfo, XrSceneObserverMSFT* sceneObserver); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySceneObserverMSFT)(XrSceneObserverMSFT sceneObserver); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSceneMSFT)(XrSceneObserverMSFT sceneObserver, const XrSceneCreateInfoMSFT* createInfo, XrSceneMSFT* scene); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySceneMSFT)(XrSceneMSFT scene); +typedef XrResult (XRAPI_PTR *PFN_xrComputeNewSceneMSFT)(XrSceneObserverMSFT sceneObserver, const XrNewSceneComputeInfoMSFT* computeInfo); +typedef XrResult (XRAPI_PTR *PFN_xrGetSceneComputeStateMSFT)(XrSceneObserverMSFT sceneObserver, XrSceneComputeStateMSFT* state); +typedef XrResult (XRAPI_PTR *PFN_xrGetSceneComponentsMSFT)(XrSceneMSFT scene, const XrSceneComponentsGetInfoMSFT* getInfo, XrSceneComponentsMSFT* components); +typedef XrResult (XRAPI_PTR *PFN_xrLocateSceneComponentsMSFT)(XrSceneMSFT scene, const XrSceneComponentsLocateInfoMSFT* locateInfo, XrSceneComponentLocationsMSFT* locations); +typedef XrResult (XRAPI_PTR *PFN_xrGetSceneMeshBuffersMSFT)(XrSceneMSFT scene, const XrSceneMeshBuffersGetInfoMSFT* getInfo, XrSceneMeshBuffersMSFT* buffers); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateSceneComputeFeaturesMSFT( + XrInstance instance, + XrSystemId systemId, + uint32_t featureCapacityInput, + uint32_t* featureCountOutput, + XrSceneComputeFeatureMSFT* features); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSceneObserverMSFT( + XrSession session, + const XrSceneObserverCreateInfoMSFT* createInfo, + XrSceneObserverMSFT* sceneObserver); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySceneObserverMSFT( + XrSceneObserverMSFT sceneObserver); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSceneMSFT( + XrSceneObserverMSFT sceneObserver, + const XrSceneCreateInfoMSFT* createInfo, + XrSceneMSFT* scene); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySceneMSFT( + XrSceneMSFT scene); + +XRAPI_ATTR XrResult XRAPI_CALL xrComputeNewSceneMSFT( + XrSceneObserverMSFT sceneObserver, + const XrNewSceneComputeInfoMSFT* computeInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSceneComputeStateMSFT( + XrSceneObserverMSFT sceneObserver, + XrSceneComputeStateMSFT* state); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSceneComponentsMSFT( + XrSceneMSFT scene, + const XrSceneComponentsGetInfoMSFT* getInfo, + XrSceneComponentsMSFT* components); + +XRAPI_ATTR XrResult XRAPI_CALL xrLocateSceneComponentsMSFT( + XrSceneMSFT scene, + const XrSceneComponentsLocateInfoMSFT* locateInfo, + XrSceneComponentLocationsMSFT* locations); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSceneMeshBuffersMSFT( + XrSceneMSFT scene, + const XrSceneMeshBuffersGetInfoMSFT* getInfo, + XrSceneMeshBuffersMSFT* buffers); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_MSFT_scene_understanding_serialization is a preprocessor guard. Do not pass it to API calls. +#define XR_MSFT_scene_understanding_serialization 1 +#define XR_MSFT_scene_understanding_serialization_SPEC_VERSION 2 +#define XR_MSFT_SCENE_UNDERSTANDING_SERIALIZATION_EXTENSION_NAME "XR_MSFT_scene_understanding_serialization" +typedef struct XrSerializedSceneFragmentDataGetInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrUuidMSFT sceneFragmentId; +} XrSerializedSceneFragmentDataGetInfoMSFT; + +typedef struct XrDeserializeSceneFragmentMSFT { + uint32_t bufferSize; + const uint8_t* buffer; +} XrDeserializeSceneFragmentMSFT; + +typedef struct XrSceneDeserializeInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t fragmentCount; + const XrDeserializeSceneFragmentMSFT* fragments; +} XrSceneDeserializeInfoMSFT; + +typedef XrResult (XRAPI_PTR *PFN_xrDeserializeSceneMSFT)(XrSceneObserverMSFT sceneObserver, const XrSceneDeserializeInfoMSFT* deserializeInfo); +typedef XrResult (XRAPI_PTR *PFN_xrGetSerializedSceneFragmentDataMSFT)(XrSceneMSFT scene, const XrSerializedSceneFragmentDataGetInfoMSFT* getInfo, uint32_t countInput, uint32_t* readOutput, uint8_t* buffer); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrDeserializeSceneMSFT( + XrSceneObserverMSFT sceneObserver, + const XrSceneDeserializeInfoMSFT* deserializeInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSerializedSceneFragmentDataMSFT( + XrSceneMSFT scene, + const XrSerializedSceneFragmentDataGetInfoMSFT* getInfo, + uint32_t countInput, + uint32_t* readOutput, + uint8_t* buffer); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_display_refresh_rate is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_display_refresh_rate 1 +#define XR_FB_display_refresh_rate_SPEC_VERSION 1 +#define XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME "XR_FB_display_refresh_rate" +typedef struct XrEventDataDisplayRefreshRateChangedFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + float fromDisplayRefreshRate; + float toDisplayRefreshRate; +} XrEventDataDisplayRefreshRateChangedFB; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateDisplayRefreshRatesFB)(XrSession session, uint32_t displayRefreshRateCapacityInput, uint32_t* displayRefreshRateCountOutput, float* displayRefreshRates); +typedef XrResult (XRAPI_PTR *PFN_xrGetDisplayRefreshRateFB)(XrSession session, float* displayRefreshRate); +typedef XrResult (XRAPI_PTR *PFN_xrRequestDisplayRefreshRateFB)(XrSession session, float displayRefreshRate); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateDisplayRefreshRatesFB( + XrSession session, + uint32_t displayRefreshRateCapacityInput, + uint32_t* displayRefreshRateCountOutput, + float* displayRefreshRates); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetDisplayRefreshRateFB( + XrSession session, + float* displayRefreshRate); + +XRAPI_ATTR XrResult XRAPI_CALL xrRequestDisplayRefreshRateFB( + XrSession session, + float displayRefreshRate); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_HTC_vive_cosmos_controller_interaction is a preprocessor guard. Do not pass it to API calls. +#define XR_HTC_vive_cosmos_controller_interaction 1 +#define XR_HTC_vive_cosmos_controller_interaction_SPEC_VERSION 1 +#define XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME "XR_HTC_vive_cosmos_controller_interaction" + + +// XR_HTCX_vive_tracker_interaction is a preprocessor guard. Do not pass it to API calls. +#define XR_HTCX_vive_tracker_interaction 1 +#define XR_HTCX_vive_tracker_interaction_SPEC_VERSION 3 +#define XR_HTCX_VIVE_TRACKER_INTERACTION_EXTENSION_NAME "XR_HTCX_vive_tracker_interaction" +typedef struct XrViveTrackerPathsHTCX { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrPath persistentPath; + XrPath rolePath; +} XrViveTrackerPathsHTCX; + +typedef struct XrEventDataViveTrackerConnectedHTCX { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrViveTrackerPathsHTCX* paths; +} XrEventDataViveTrackerConnectedHTCX; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateViveTrackerPathsHTCX)(XrInstance instance, uint32_t pathCapacityInput, uint32_t* pathCountOutput, XrViveTrackerPathsHTCX* paths); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateViveTrackerPathsHTCX( + XrInstance instance, + uint32_t pathCapacityInput, + uint32_t* pathCountOutput, + XrViveTrackerPathsHTCX* paths); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_HTC_facial_tracking is a preprocessor guard. Do not pass it to API calls. +#define XR_HTC_facial_tracking 1 + +#define XR_FACIAL_EXPRESSION_EYE_COUNT_HTC 14 + + +#define XR_FACIAL_EXPRESSION_LIP_COUNT_HTC 37 + +XR_DEFINE_HANDLE(XrFacialTrackerHTC) +#define XR_HTC_facial_tracking_SPEC_VERSION 3 +#define XR_HTC_FACIAL_TRACKING_EXTENSION_NAME "XR_HTC_facial_tracking" + +typedef enum XrEyeExpressionHTC { + XR_EYE_EXPRESSION_LEFT_BLINK_HTC = 0, + XR_EYE_EXPRESSION_LEFT_WIDE_HTC = 1, + XR_EYE_EXPRESSION_RIGHT_BLINK_HTC = 2, + XR_EYE_EXPRESSION_RIGHT_WIDE_HTC = 3, + XR_EYE_EXPRESSION_LEFT_SQUEEZE_HTC = 4, + XR_EYE_EXPRESSION_RIGHT_SQUEEZE_HTC = 5, + XR_EYE_EXPRESSION_LEFT_DOWN_HTC = 6, + XR_EYE_EXPRESSION_RIGHT_DOWN_HTC = 7, + XR_EYE_EXPRESSION_LEFT_OUT_HTC = 8, + XR_EYE_EXPRESSION_RIGHT_IN_HTC = 9, + XR_EYE_EXPRESSION_LEFT_IN_HTC = 10, + XR_EYE_EXPRESSION_RIGHT_OUT_HTC = 11, + XR_EYE_EXPRESSION_LEFT_UP_HTC = 12, + XR_EYE_EXPRESSION_RIGHT_UP_HTC = 13, + XR_EYE_EXPRESSION_MAX_ENUM_HTC = 0x7FFFFFFF +} XrEyeExpressionHTC; + +typedef enum XrLipExpressionHTC { + XR_LIP_EXPRESSION_JAW_RIGHT_HTC = 0, + XR_LIP_EXPRESSION_JAW_LEFT_HTC = 1, + XR_LIP_EXPRESSION_JAW_FORWARD_HTC = 2, + XR_LIP_EXPRESSION_JAW_OPEN_HTC = 3, + XR_LIP_EXPRESSION_MOUTH_APE_SHAPE_HTC = 4, + XR_LIP_EXPRESSION_MOUTH_UPPER_RIGHT_HTC = 5, + XR_LIP_EXPRESSION_MOUTH_UPPER_LEFT_HTC = 6, + XR_LIP_EXPRESSION_MOUTH_LOWER_RIGHT_HTC = 7, + XR_LIP_EXPRESSION_MOUTH_LOWER_LEFT_HTC = 8, + XR_LIP_EXPRESSION_MOUTH_UPPER_OVERTURN_HTC = 9, + XR_LIP_EXPRESSION_MOUTH_LOWER_OVERTURN_HTC = 10, + XR_LIP_EXPRESSION_MOUTH_POUT_HTC = 11, + XR_LIP_EXPRESSION_MOUTH_RAISER_RIGHT_HTC = 12, + XR_LIP_EXPRESSION_MOUTH_RAISER_LEFT_HTC = 13, + XR_LIP_EXPRESSION_MOUTH_STRETCHER_RIGHT_HTC = 14, + XR_LIP_EXPRESSION_MOUTH_STRETCHER_LEFT_HTC = 15, + XR_LIP_EXPRESSION_CHEEK_PUFF_RIGHT_HTC = 16, + XR_LIP_EXPRESSION_CHEEK_PUFF_LEFT_HTC = 17, + XR_LIP_EXPRESSION_CHEEK_SUCK_HTC = 18, + XR_LIP_EXPRESSION_MOUTH_UPPER_UPRIGHT_HTC = 19, + XR_LIP_EXPRESSION_MOUTH_UPPER_UPLEFT_HTC = 20, + XR_LIP_EXPRESSION_MOUTH_LOWER_DOWNRIGHT_HTC = 21, + XR_LIP_EXPRESSION_MOUTH_LOWER_DOWNLEFT_HTC = 22, + XR_LIP_EXPRESSION_MOUTH_UPPER_INSIDE_HTC = 23, + XR_LIP_EXPRESSION_MOUTH_LOWER_INSIDE_HTC = 24, + XR_LIP_EXPRESSION_MOUTH_LOWER_OVERLAY_HTC = 25, + XR_LIP_EXPRESSION_TONGUE_LONGSTEP1_HTC = 26, + XR_LIP_EXPRESSION_TONGUE_LEFT_HTC = 27, + XR_LIP_EXPRESSION_TONGUE_RIGHT_HTC = 28, + XR_LIP_EXPRESSION_TONGUE_UP_HTC = 29, + XR_LIP_EXPRESSION_TONGUE_DOWN_HTC = 30, + XR_LIP_EXPRESSION_TONGUE_ROLL_HTC = 31, + XR_LIP_EXPRESSION_TONGUE_LONGSTEP2_HTC = 32, + XR_LIP_EXPRESSION_TONGUE_UPRIGHT_MORPH_HTC = 33, + XR_LIP_EXPRESSION_TONGUE_UPLEFT_MORPH_HTC = 34, + XR_LIP_EXPRESSION_TONGUE_DOWNRIGHT_MORPH_HTC = 35, + XR_LIP_EXPRESSION_TONGUE_DOWNLEFT_MORPH_HTC = 36, + XR_LIP_EXPRESSION_MOUTH_SMILE_RIGHT_HTC = XR_LIP_EXPRESSION_MOUTH_RAISER_RIGHT_HTC, + XR_LIP_EXPRESSION_MOUTH_SMILE_LEFT_HTC = XR_LIP_EXPRESSION_MOUTH_RAISER_LEFT_HTC, + XR_LIP_EXPRESSION_MOUTH_SAD_RIGHT_HTC = XR_LIP_EXPRESSION_MOUTH_STRETCHER_RIGHT_HTC, + XR_LIP_EXPRESSION_MOUTH_SAD_LEFT_HTC = XR_LIP_EXPRESSION_MOUTH_STRETCHER_LEFT_HTC, + XR_LIP_EXPRESSION_MAX_ENUM_HTC = 0x7FFFFFFF +} XrLipExpressionHTC; + +typedef enum XrFacialTrackingTypeHTC { + XR_FACIAL_TRACKING_TYPE_EYE_DEFAULT_HTC = 1, + XR_FACIAL_TRACKING_TYPE_LIP_DEFAULT_HTC = 2, + XR_FACIAL_TRACKING_TYPE_MAX_ENUM_HTC = 0x7FFFFFFF +} XrFacialTrackingTypeHTC; +// XrSystemFacialTrackingPropertiesHTC extends XrSystemProperties +typedef struct XrSystemFacialTrackingPropertiesHTC { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportEyeFacialTracking; + XrBool32 supportLipFacialTracking; +} XrSystemFacialTrackingPropertiesHTC; + +typedef struct XrFacialExpressionsHTC { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 isActive; + XrTime sampleTime; + uint32_t expressionCount; + float* expressionWeightings; +} XrFacialExpressionsHTC; + +typedef struct XrFacialTrackerCreateInfoHTC { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrFacialTrackingTypeHTC facialTrackingType; +} XrFacialTrackerCreateInfoHTC; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateFacialTrackerHTC)(XrSession session, const XrFacialTrackerCreateInfoHTC* createInfo, XrFacialTrackerHTC* facialTracker); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyFacialTrackerHTC)(XrFacialTrackerHTC facialTracker); +typedef XrResult (XRAPI_PTR *PFN_xrGetFacialExpressionsHTC)(XrFacialTrackerHTC facialTracker, XrFacialExpressionsHTC* facialExpressions); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateFacialTrackerHTC( + XrSession session, + const XrFacialTrackerCreateInfoHTC* createInfo, + XrFacialTrackerHTC* facialTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyFacialTrackerHTC( + XrFacialTrackerHTC facialTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetFacialExpressionsHTC( + XrFacialTrackerHTC facialTracker, + XrFacialExpressionsHTC* facialExpressions); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_HTC_vive_focus3_controller_interaction is a preprocessor guard. Do not pass it to API calls. +#define XR_HTC_vive_focus3_controller_interaction 1 +#define XR_HTC_vive_focus3_controller_interaction_SPEC_VERSION 2 +#define XR_HTC_VIVE_FOCUS3_CONTROLLER_INTERACTION_EXTENSION_NAME "XR_HTC_vive_focus3_controller_interaction" + + +// XR_HTC_hand_interaction is a preprocessor guard. Do not pass it to API calls. +#define XR_HTC_hand_interaction 1 +#define XR_HTC_hand_interaction_SPEC_VERSION 1 +#define XR_HTC_HAND_INTERACTION_EXTENSION_NAME "XR_HTC_hand_interaction" + + +// XR_HTC_vive_wrist_tracker_interaction is a preprocessor guard. Do not pass it to API calls. +#define XR_HTC_vive_wrist_tracker_interaction 1 +#define XR_HTC_vive_wrist_tracker_interaction_SPEC_VERSION 1 +#define XR_HTC_VIVE_WRIST_TRACKER_INTERACTION_EXTENSION_NAME "XR_HTC_vive_wrist_tracker_interaction" + + +// XR_FB_color_space is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_color_space 1 +#define XR_FB_color_space_SPEC_VERSION 3 +#define XR_FB_COLOR_SPACE_EXTENSION_NAME "XR_FB_color_space" + +typedef enum XrColorSpaceFB { + XR_COLOR_SPACE_UNMANAGED_FB = 0, + XR_COLOR_SPACE_REC2020_FB = 1, + XR_COLOR_SPACE_REC709_FB = 2, + XR_COLOR_SPACE_RIFT_CV1_FB = 3, + XR_COLOR_SPACE_RIFT_S_FB = 4, + XR_COLOR_SPACE_QUEST_FB = 5, + XR_COLOR_SPACE_P3_FB = 6, + XR_COLOR_SPACE_ADOBE_RGB_FB = 7, + XR_COLOR_SPACE_MAX_ENUM_FB = 0x7FFFFFFF +} XrColorSpaceFB; +// XrSystemColorSpacePropertiesFB extends XrSystemProperties +typedef struct XrSystemColorSpacePropertiesFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrColorSpaceFB colorSpace; +} XrSystemColorSpacePropertiesFB; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateColorSpacesFB)(XrSession session, uint32_t colorSpaceCapacityInput, uint32_t* colorSpaceCountOutput, XrColorSpaceFB* colorSpaces); +typedef XrResult (XRAPI_PTR *PFN_xrSetColorSpaceFB)(XrSession session, const XrColorSpaceFB colorSpace); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateColorSpacesFB( + XrSession session, + uint32_t colorSpaceCapacityInput, + uint32_t* colorSpaceCountOutput, + XrColorSpaceFB* colorSpaces); + +XRAPI_ATTR XrResult XRAPI_CALL xrSetColorSpaceFB( + XrSession session, + const XrColorSpaceFB colorSpace); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_hand_tracking_mesh is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_hand_tracking_mesh 1 +#define XR_FB_hand_tracking_mesh_SPEC_VERSION 3 +#define XR_FB_HAND_TRACKING_MESH_EXTENSION_NAME "XR_FB_hand_tracking_mesh" +typedef struct XrVector4sFB { + int16_t x; + int16_t y; + int16_t z; + int16_t w; +} XrVector4sFB; + +typedef struct XrHandTrackingMeshFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t jointCapacityInput; + uint32_t jointCountOutput; + XrPosef* jointBindPoses; + float* jointRadii; + XrHandJointEXT* jointParents; + uint32_t vertexCapacityInput; + uint32_t vertexCountOutput; + XrVector3f* vertexPositions; + XrVector3f* vertexNormals; + XrVector2f* vertexUVs; + XrVector4sFB* vertexBlendIndices; + XrVector4f* vertexBlendWeights; + uint32_t indexCapacityInput; + uint32_t indexCountOutput; + int16_t* indices; +} XrHandTrackingMeshFB; + +// XrHandTrackingScaleFB extends XrHandJointLocationsEXT +typedef struct XrHandTrackingScaleFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + float sensorOutput; + float currentOutput; + XrBool32 overrideHandScale; + float overrideValueInput; +} XrHandTrackingScaleFB; + +typedef XrResult (XRAPI_PTR *PFN_xrGetHandMeshFB)(XrHandTrackerEXT handTracker, XrHandTrackingMeshFB* mesh); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetHandMeshFB( + XrHandTrackerEXT handTracker, + XrHandTrackingMeshFB* mesh); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_hand_tracking_aim is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_hand_tracking_aim 1 +#define XR_FB_hand_tracking_aim_SPEC_VERSION 2 +#define XR_FB_HAND_TRACKING_AIM_EXTENSION_NAME "XR_FB_hand_tracking_aim" +typedef XrFlags64 XrHandTrackingAimFlagsFB; + +// Flag bits for XrHandTrackingAimFlagsFB +static const XrHandTrackingAimFlagsFB XR_HAND_TRACKING_AIM_COMPUTED_BIT_FB = 0x00000001; +static const XrHandTrackingAimFlagsFB XR_HAND_TRACKING_AIM_VALID_BIT_FB = 0x00000002; +static const XrHandTrackingAimFlagsFB XR_HAND_TRACKING_AIM_INDEX_PINCHING_BIT_FB = 0x00000004; +static const XrHandTrackingAimFlagsFB XR_HAND_TRACKING_AIM_MIDDLE_PINCHING_BIT_FB = 0x00000008; +static const XrHandTrackingAimFlagsFB XR_HAND_TRACKING_AIM_RING_PINCHING_BIT_FB = 0x00000010; +static const XrHandTrackingAimFlagsFB XR_HAND_TRACKING_AIM_LITTLE_PINCHING_BIT_FB = 0x00000020; +static const XrHandTrackingAimFlagsFB XR_HAND_TRACKING_AIM_SYSTEM_GESTURE_BIT_FB = 0x00000040; +static const XrHandTrackingAimFlagsFB XR_HAND_TRACKING_AIM_DOMINANT_HAND_BIT_FB = 0x00000080; +static const XrHandTrackingAimFlagsFB XR_HAND_TRACKING_AIM_MENU_PRESSED_BIT_FB = 0x00000100; + +// XrHandTrackingAimStateFB extends XrHandJointLocationsEXT +typedef struct XrHandTrackingAimStateFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrHandTrackingAimFlagsFB status; + XrPosef aimPose; + float pinchStrengthIndex; + float pinchStrengthMiddle; + float pinchStrengthRing; + float pinchStrengthLittle; +} XrHandTrackingAimStateFB; + + + +// XR_FB_hand_tracking_capsules is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_hand_tracking_capsules 1 +#define XR_HAND_TRACKING_CAPSULE_POINT_COUNT_FB 2 +#define XR_HAND_TRACKING_CAPSULE_COUNT_FB 19 +#define XR_FB_hand_tracking_capsules_SPEC_VERSION 3 +#define XR_FB_HAND_TRACKING_CAPSULES_EXTENSION_NAME "XR_FB_hand_tracking_capsules" +#define XR_FB_HAND_TRACKING_CAPSULE_POINT_COUNT XR_HAND_TRACKING_CAPSULE_POINT_COUNT_FB +#define XR_FB_HAND_TRACKING_CAPSULE_COUNT XR_HAND_TRACKING_CAPSULE_COUNT_FB +typedef struct XrHandCapsuleFB { + XrVector3f points[XR_HAND_TRACKING_CAPSULE_POINT_COUNT_FB]; + float radius; + XrHandJointEXT joint; +} XrHandCapsuleFB; + +// XrHandTrackingCapsulesStateFB extends XrHandJointLocationsEXT +typedef struct XrHandTrackingCapsulesStateFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrHandCapsuleFB capsules[XR_HAND_TRACKING_CAPSULE_COUNT_FB]; +} XrHandTrackingCapsulesStateFB; + + + +// XR_FB_spatial_entity is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_spatial_entity 1 +XR_DEFINE_ATOM(XrAsyncRequestIdFB) +#define XR_FB_spatial_entity_SPEC_VERSION 3 +#define XR_FB_SPATIAL_ENTITY_EXTENSION_NAME "XR_FB_spatial_entity" + +typedef enum XrSpaceComponentTypeFB { + XR_SPACE_COMPONENT_TYPE_LOCATABLE_FB = 0, + XR_SPACE_COMPONENT_TYPE_STORABLE_FB = 1, + XR_SPACE_COMPONENT_TYPE_SHARABLE_FB = 2, + XR_SPACE_COMPONENT_TYPE_BOUNDED_2D_FB = 3, + XR_SPACE_COMPONENT_TYPE_BOUNDED_3D_FB = 4, + XR_SPACE_COMPONENT_TYPE_SEMANTIC_LABELS_FB = 5, + XR_SPACE_COMPONENT_TYPE_ROOM_LAYOUT_FB = 6, + XR_SPACE_COMPONENT_TYPE_SPACE_CONTAINER_FB = 7, + XR_SPACE_COMPONENT_TYPE_TRIANGLE_MESH_META = 1000269000, + XR_SPACE_COMPONENT_TYPE_ROOM_MESH_META = 1000553000, + XR_SPACE_COMPONENT_TYPE_MAX_ENUM_FB = 0x7FFFFFFF +} XrSpaceComponentTypeFB; +// XrSystemSpatialEntityPropertiesFB extends XrSystemProperties +typedef struct XrSystemSpatialEntityPropertiesFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 supportsSpatialEntity; +} XrSystemSpatialEntityPropertiesFB; + +typedef struct XrSpatialAnchorCreateInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace space; + XrPosef poseInSpace; + XrTime time; +} XrSpatialAnchorCreateInfoFB; + +typedef struct XrSpaceComponentStatusSetInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpaceComponentTypeFB componentType; + XrBool32 enabled; + XrDuration timeout; +} XrSpaceComponentStatusSetInfoFB; + +typedef struct XrSpaceComponentStatusFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 enabled; + XrBool32 changePending; +} XrSpaceComponentStatusFB; + +typedef XrUuid XrUuidEXT; + +typedef struct XrEventDataSpatialAnchorCreateCompleteFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; + XrResult result; + XrSpace space; + XrUuidEXT uuid; +} XrEventDataSpatialAnchorCreateCompleteFB; + +typedef struct XrEventDataSpaceSetStatusCompleteFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; + XrResult result; + XrSpace space; + XrUuidEXT uuid; + XrSpaceComponentTypeFB componentType; + XrBool32 enabled; +} XrEventDataSpaceSetStatusCompleteFB; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorFB)(XrSession session, const XrSpatialAnchorCreateInfoFB* info, XrAsyncRequestIdFB* requestId); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpaceUuidFB)(XrSpace space, XrUuidEXT* uuid); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateSpaceSupportedComponentsFB)(XrSpace space, uint32_t componentTypeCapacityInput, uint32_t* componentTypeCountOutput, XrSpaceComponentTypeFB* componentTypes); +typedef XrResult (XRAPI_PTR *PFN_xrSetSpaceComponentStatusFB)(XrSpace space, const XrSpaceComponentStatusSetInfoFB* info, XrAsyncRequestIdFB* requestId); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpaceComponentStatusFB)(XrSpace space, XrSpaceComponentTypeFB componentType, XrSpaceComponentStatusFB* status); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorFB( + XrSession session, + const XrSpatialAnchorCreateInfoFB* info, + XrAsyncRequestIdFB* requestId); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpaceUuidFB( + XrSpace space, + XrUuidEXT* uuid); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateSpaceSupportedComponentsFB( + XrSpace space, + uint32_t componentTypeCapacityInput, + uint32_t* componentTypeCountOutput, + XrSpaceComponentTypeFB* componentTypes); + +XRAPI_ATTR XrResult XRAPI_CALL xrSetSpaceComponentStatusFB( + XrSpace space, + const XrSpaceComponentStatusSetInfoFB* info, + XrAsyncRequestIdFB* requestId); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpaceComponentStatusFB( + XrSpace space, + XrSpaceComponentTypeFB componentType, + XrSpaceComponentStatusFB* status); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_foveation is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_foveation 1 +XR_DEFINE_HANDLE(XrFoveationProfileFB) +#define XR_FB_foveation_SPEC_VERSION 1 +#define XR_FB_FOVEATION_EXTENSION_NAME "XR_FB_foveation" +typedef XrFlags64 XrSwapchainCreateFoveationFlagsFB; + +// Flag bits for XrSwapchainCreateFoveationFlagsFB +static const XrSwapchainCreateFoveationFlagsFB XR_SWAPCHAIN_CREATE_FOVEATION_SCALED_BIN_BIT_FB = 0x00000001; +static const XrSwapchainCreateFoveationFlagsFB XR_SWAPCHAIN_CREATE_FOVEATION_FRAGMENT_DENSITY_MAP_BIT_FB = 0x00000002; + +typedef XrFlags64 XrSwapchainStateFoveationFlagsFB; + +// Flag bits for XrSwapchainStateFoveationFlagsFB + +typedef struct XrFoveationProfileCreateInfoFB { + XrStructureType type; + void* XR_MAY_ALIAS next; +} XrFoveationProfileCreateInfoFB; + +// XrSwapchainCreateInfoFoveationFB extends XrSwapchainCreateInfo +typedef struct XrSwapchainCreateInfoFoveationFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrSwapchainCreateFoveationFlagsFB flags; +} XrSwapchainCreateInfoFoveationFB; + +typedef struct XrSwapchainStateFoveationFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrSwapchainStateFoveationFlagsFB flags; + XrFoveationProfileFB profile; +} XrSwapchainStateFoveationFB; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateFoveationProfileFB)(XrSession session, const XrFoveationProfileCreateInfoFB* createInfo, XrFoveationProfileFB* profile); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyFoveationProfileFB)(XrFoveationProfileFB profile); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateFoveationProfileFB( + XrSession session, + const XrFoveationProfileCreateInfoFB* createInfo, + XrFoveationProfileFB* profile); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyFoveationProfileFB( + XrFoveationProfileFB profile); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_foveation_configuration is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_foveation_configuration 1 +#define XR_FB_foveation_configuration_SPEC_VERSION 1 +#define XR_FB_FOVEATION_CONFIGURATION_EXTENSION_NAME "XR_FB_foveation_configuration" + +typedef enum XrFoveationLevelFB { + XR_FOVEATION_LEVEL_NONE_FB = 0, + XR_FOVEATION_LEVEL_LOW_FB = 1, + XR_FOVEATION_LEVEL_MEDIUM_FB = 2, + XR_FOVEATION_LEVEL_HIGH_FB = 3, + XR_FOVEATION_LEVEL_MAX_ENUM_FB = 0x7FFFFFFF +} XrFoveationLevelFB; + +typedef enum XrFoveationDynamicFB { + XR_FOVEATION_DYNAMIC_DISABLED_FB = 0, + XR_FOVEATION_DYNAMIC_LEVEL_ENABLED_FB = 1, + XR_FOVEATION_DYNAMIC_MAX_ENUM_FB = 0x7FFFFFFF +} XrFoveationDynamicFB; +// XrFoveationLevelProfileCreateInfoFB extends XrFoveationProfileCreateInfoFB +typedef struct XrFoveationLevelProfileCreateInfoFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrFoveationLevelFB level; + float verticalOffset; + XrFoveationDynamicFB dynamic; +} XrFoveationLevelProfileCreateInfoFB; + + + +// XR_FB_keyboard_tracking is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_keyboard_tracking 1 +#define XR_MAX_KEYBOARD_TRACKING_NAME_SIZE_FB 128 +#define XR_FB_keyboard_tracking_SPEC_VERSION 1 +#define XR_FB_KEYBOARD_TRACKING_EXTENSION_NAME "XR_FB_keyboard_tracking" +typedef XrFlags64 XrKeyboardTrackingFlagsFB; + +// Flag bits for XrKeyboardTrackingFlagsFB +static const XrKeyboardTrackingFlagsFB XR_KEYBOARD_TRACKING_EXISTS_BIT_FB = 0x00000001; +static const XrKeyboardTrackingFlagsFB XR_KEYBOARD_TRACKING_LOCAL_BIT_FB = 0x00000002; +static const XrKeyboardTrackingFlagsFB XR_KEYBOARD_TRACKING_REMOTE_BIT_FB = 0x00000004; +static const XrKeyboardTrackingFlagsFB XR_KEYBOARD_TRACKING_CONNECTED_BIT_FB = 0x00000008; + +typedef XrFlags64 XrKeyboardTrackingQueryFlagsFB; + +// Flag bits for XrKeyboardTrackingQueryFlagsFB +static const XrKeyboardTrackingQueryFlagsFB XR_KEYBOARD_TRACKING_QUERY_LOCAL_BIT_FB = 0x00000002; +static const XrKeyboardTrackingQueryFlagsFB XR_KEYBOARD_TRACKING_QUERY_REMOTE_BIT_FB = 0x00000004; + +// XrSystemKeyboardTrackingPropertiesFB extends XrSystemProperties +typedef struct XrSystemKeyboardTrackingPropertiesFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsKeyboardTracking; +} XrSystemKeyboardTrackingPropertiesFB; + +typedef struct XrKeyboardTrackingDescriptionFB { + uint64_t trackedKeyboardId; + XrVector3f size; + XrKeyboardTrackingFlagsFB flags; + char name[XR_MAX_KEYBOARD_TRACKING_NAME_SIZE_FB]; +} XrKeyboardTrackingDescriptionFB; + +typedef struct XrKeyboardSpaceCreateInfoFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint64_t trackedKeyboardId; +} XrKeyboardSpaceCreateInfoFB; + +typedef struct XrKeyboardTrackingQueryFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrKeyboardTrackingQueryFlagsFB flags; +} XrKeyboardTrackingQueryFB; + +typedef XrResult (XRAPI_PTR *PFN_xrQuerySystemTrackedKeyboardFB)(XrSession session, const XrKeyboardTrackingQueryFB* queryInfo, XrKeyboardTrackingDescriptionFB* keyboard); +typedef XrResult (XRAPI_PTR *PFN_xrCreateKeyboardSpaceFB)(XrSession session, const XrKeyboardSpaceCreateInfoFB* createInfo, XrSpace* keyboardSpace); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrQuerySystemTrackedKeyboardFB( + XrSession session, + const XrKeyboardTrackingQueryFB* queryInfo, + XrKeyboardTrackingDescriptionFB* keyboard); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateKeyboardSpaceFB( + XrSession session, + const XrKeyboardSpaceCreateInfoFB* createInfo, + XrSpace* keyboardSpace); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_triangle_mesh is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_triangle_mesh 1 +XR_DEFINE_HANDLE(XrTriangleMeshFB) +#define XR_FB_triangle_mesh_SPEC_VERSION 2 +#define XR_FB_TRIANGLE_MESH_EXTENSION_NAME "XR_FB_triangle_mesh" + +typedef enum XrWindingOrderFB { + XR_WINDING_ORDER_UNKNOWN_FB = 0, + XR_WINDING_ORDER_CW_FB = 1, + XR_WINDING_ORDER_CCW_FB = 2, + XR_WINDING_ORDER_MAX_ENUM_FB = 0x7FFFFFFF +} XrWindingOrderFB; +typedef XrFlags64 XrTriangleMeshFlagsFB; + +// Flag bits for XrTriangleMeshFlagsFB +static const XrTriangleMeshFlagsFB XR_TRIANGLE_MESH_MUTABLE_BIT_FB = 0x00000001; + +typedef struct XrTriangleMeshCreateInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrTriangleMeshFlagsFB flags; + XrWindingOrderFB windingOrder; + uint32_t vertexCount; + const XrVector3f* vertexBuffer; + uint32_t triangleCount; + const uint32_t* indexBuffer; +} XrTriangleMeshCreateInfoFB; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateTriangleMeshFB)(XrSession session, const XrTriangleMeshCreateInfoFB* createInfo, XrTriangleMeshFB* outTriangleMesh); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyTriangleMeshFB)(XrTriangleMeshFB mesh); +typedef XrResult (XRAPI_PTR *PFN_xrTriangleMeshGetVertexBufferFB)(XrTriangleMeshFB mesh, XrVector3f** outVertexBuffer); +typedef XrResult (XRAPI_PTR *PFN_xrTriangleMeshGetIndexBufferFB)(XrTriangleMeshFB mesh, uint32_t** outIndexBuffer); +typedef XrResult (XRAPI_PTR *PFN_xrTriangleMeshBeginUpdateFB)(XrTriangleMeshFB mesh); +typedef XrResult (XRAPI_PTR *PFN_xrTriangleMeshEndUpdateFB)(XrTriangleMeshFB mesh, uint32_t vertexCount, uint32_t triangleCount); +typedef XrResult (XRAPI_PTR *PFN_xrTriangleMeshBeginVertexBufferUpdateFB)(XrTriangleMeshFB mesh, uint32_t* outVertexCount); +typedef XrResult (XRAPI_PTR *PFN_xrTriangleMeshEndVertexBufferUpdateFB)(XrTriangleMeshFB mesh); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateTriangleMeshFB( + XrSession session, + const XrTriangleMeshCreateInfoFB* createInfo, + XrTriangleMeshFB* outTriangleMesh); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyTriangleMeshFB( + XrTriangleMeshFB mesh); + +XRAPI_ATTR XrResult XRAPI_CALL xrTriangleMeshGetVertexBufferFB( + XrTriangleMeshFB mesh, + XrVector3f** outVertexBuffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrTriangleMeshGetIndexBufferFB( + XrTriangleMeshFB mesh, + uint32_t** outIndexBuffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrTriangleMeshBeginUpdateFB( + XrTriangleMeshFB mesh); + +XRAPI_ATTR XrResult XRAPI_CALL xrTriangleMeshEndUpdateFB( + XrTriangleMeshFB mesh, + uint32_t vertexCount, + uint32_t triangleCount); + +XRAPI_ATTR XrResult XRAPI_CALL xrTriangleMeshBeginVertexBufferUpdateFB( + XrTriangleMeshFB mesh, + uint32_t* outVertexCount); + +XRAPI_ATTR XrResult XRAPI_CALL xrTriangleMeshEndVertexBufferUpdateFB( + XrTriangleMeshFB mesh); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_passthrough is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_passthrough 1 +XR_DEFINE_HANDLE(XrPassthroughFB) +XR_DEFINE_HANDLE(XrPassthroughLayerFB) +XR_DEFINE_HANDLE(XrGeometryInstanceFB) +#define XR_PASSTHROUGH_COLOR_MAP_MONO_SIZE_FB 256 +#define XR_FB_passthrough_SPEC_VERSION 5 +#define XR_FB_PASSTHROUGH_EXTENSION_NAME "XR_FB_passthrough" + +typedef enum XrPassthroughLayerPurposeFB { + XR_PASSTHROUGH_LAYER_PURPOSE_RECONSTRUCTION_FB = 0, + XR_PASSTHROUGH_LAYER_PURPOSE_PROJECTED_FB = 1, + XR_PASSTHROUGH_LAYER_PURPOSE_TRACKED_KEYBOARD_HANDS_FB = 1000203001, + XR_PASSTHROUGH_LAYER_PURPOSE_TRACKED_KEYBOARD_MASKED_HANDS_FB = 1000203002, + XR_PASSTHROUGH_LAYER_PURPOSE_MAX_ENUM_FB = 0x7FFFFFFF +} XrPassthroughLayerPurposeFB; +typedef XrFlags64 XrPassthroughCapabilityFlagsFB; + +// Flag bits for XrPassthroughCapabilityFlagsFB +static const XrPassthroughCapabilityFlagsFB XR_PASSTHROUGH_CAPABILITY_BIT_FB = 0x00000001; +static const XrPassthroughCapabilityFlagsFB XR_PASSTHROUGH_CAPABILITY_COLOR_BIT_FB = 0x00000002; +static const XrPassthroughCapabilityFlagsFB XR_PASSTHROUGH_CAPABILITY_LAYER_DEPTH_BIT_FB = 0x00000004; + +typedef XrFlags64 XrPassthroughFlagsFB; + +// Flag bits for XrPassthroughFlagsFB +static const XrPassthroughFlagsFB XR_PASSTHROUGH_IS_RUNNING_AT_CREATION_BIT_FB = 0x00000001; +static const XrPassthroughFlagsFB XR_PASSTHROUGH_LAYER_DEPTH_BIT_FB = 0x00000002; + +typedef XrFlags64 XrPassthroughStateChangedFlagsFB; + +// Flag bits for XrPassthroughStateChangedFlagsFB +static const XrPassthroughStateChangedFlagsFB XR_PASSTHROUGH_STATE_CHANGED_REINIT_REQUIRED_BIT_FB = 0x00000001; +static const XrPassthroughStateChangedFlagsFB XR_PASSTHROUGH_STATE_CHANGED_NON_RECOVERABLE_ERROR_BIT_FB = 0x00000002; +static const XrPassthroughStateChangedFlagsFB XR_PASSTHROUGH_STATE_CHANGED_RECOVERABLE_ERROR_BIT_FB = 0x00000004; +static const XrPassthroughStateChangedFlagsFB XR_PASSTHROUGH_STATE_CHANGED_RESTORED_ERROR_BIT_FB = 0x00000008; + +// XrSystemPassthroughPropertiesFB extends XrSystemProperties +typedef struct XrSystemPassthroughPropertiesFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 supportsPassthrough; +} XrSystemPassthroughPropertiesFB; + +// XrSystemPassthroughProperties2FB extends XrSystemProperties +typedef struct XrSystemPassthroughProperties2FB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPassthroughCapabilityFlagsFB capabilities; +} XrSystemPassthroughProperties2FB; + +typedef struct XrPassthroughCreateInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPassthroughFlagsFB flags; +} XrPassthroughCreateInfoFB; + +typedef struct XrPassthroughLayerCreateInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPassthroughFB passthrough; + XrPassthroughFlagsFB flags; + XrPassthroughLayerPurposeFB purpose; +} XrPassthroughLayerCreateInfoFB; + +typedef struct XrCompositionLayerPassthroughFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCompositionLayerFlags flags; + XrSpace space; + XrPassthroughLayerFB layerHandle; +} XrCompositionLayerPassthroughFB; + +typedef struct XrGeometryInstanceCreateInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPassthroughLayerFB layer; + XrTriangleMeshFB mesh; + XrSpace baseSpace; + XrPosef pose; + XrVector3f scale; +} XrGeometryInstanceCreateInfoFB; + +typedef struct XrGeometryInstanceTransformFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace baseSpace; + XrTime time; + XrPosef pose; + XrVector3f scale; +} XrGeometryInstanceTransformFB; + +typedef struct XrPassthroughStyleFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + float textureOpacityFactor; + XrColor4f edgeColor; +} XrPassthroughStyleFB; + +// XrPassthroughColorMapMonoToRgbaFB extends XrPassthroughStyleFB +typedef struct XrPassthroughColorMapMonoToRgbaFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrColor4f textureColorMap[XR_PASSTHROUGH_COLOR_MAP_MONO_SIZE_FB]; +} XrPassthroughColorMapMonoToRgbaFB; + +// XrPassthroughColorMapMonoToMonoFB extends XrPassthroughStyleFB +typedef struct XrPassthroughColorMapMonoToMonoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint8_t textureColorMap[XR_PASSTHROUGH_COLOR_MAP_MONO_SIZE_FB]; +} XrPassthroughColorMapMonoToMonoFB; + +// XrPassthroughBrightnessContrastSaturationFB extends XrPassthroughStyleFB +typedef struct XrPassthroughBrightnessContrastSaturationFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + float brightness; + float contrast; + float saturation; +} XrPassthroughBrightnessContrastSaturationFB; + +typedef struct XrEventDataPassthroughStateChangedFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPassthroughStateChangedFlagsFB flags; +} XrEventDataPassthroughStateChangedFB; + +typedef XrResult (XRAPI_PTR *PFN_xrCreatePassthroughFB)(XrSession session, const XrPassthroughCreateInfoFB* createInfo, XrPassthroughFB* outPassthrough); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyPassthroughFB)(XrPassthroughFB passthrough); +typedef XrResult (XRAPI_PTR *PFN_xrPassthroughStartFB)(XrPassthroughFB passthrough); +typedef XrResult (XRAPI_PTR *PFN_xrPassthroughPauseFB)(XrPassthroughFB passthrough); +typedef XrResult (XRAPI_PTR *PFN_xrCreatePassthroughLayerFB)(XrSession session, const XrPassthroughLayerCreateInfoFB* createInfo, XrPassthroughLayerFB* outLayer); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyPassthroughLayerFB)(XrPassthroughLayerFB layer); +typedef XrResult (XRAPI_PTR *PFN_xrPassthroughLayerPauseFB)(XrPassthroughLayerFB layer); +typedef XrResult (XRAPI_PTR *PFN_xrPassthroughLayerResumeFB)(XrPassthroughLayerFB layer); +typedef XrResult (XRAPI_PTR *PFN_xrPassthroughLayerSetStyleFB)(XrPassthroughLayerFB layer, const XrPassthroughStyleFB* style); +typedef XrResult (XRAPI_PTR *PFN_xrCreateGeometryInstanceFB)(XrSession session, const XrGeometryInstanceCreateInfoFB* createInfo, XrGeometryInstanceFB* outGeometryInstance); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyGeometryInstanceFB)(XrGeometryInstanceFB instance); +typedef XrResult (XRAPI_PTR *PFN_xrGeometryInstanceSetTransformFB)(XrGeometryInstanceFB instance, const XrGeometryInstanceTransformFB* transformation); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreatePassthroughFB( + XrSession session, + const XrPassthroughCreateInfoFB* createInfo, + XrPassthroughFB* outPassthrough); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyPassthroughFB( + XrPassthroughFB passthrough); + +XRAPI_ATTR XrResult XRAPI_CALL xrPassthroughStartFB( + XrPassthroughFB passthrough); + +XRAPI_ATTR XrResult XRAPI_CALL xrPassthroughPauseFB( + XrPassthroughFB passthrough); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreatePassthroughLayerFB( + XrSession session, + const XrPassthroughLayerCreateInfoFB* createInfo, + XrPassthroughLayerFB* outLayer); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyPassthroughLayerFB( + XrPassthroughLayerFB layer); + +XRAPI_ATTR XrResult XRAPI_CALL xrPassthroughLayerPauseFB( + XrPassthroughLayerFB layer); + +XRAPI_ATTR XrResult XRAPI_CALL xrPassthroughLayerResumeFB( + XrPassthroughLayerFB layer); + +XRAPI_ATTR XrResult XRAPI_CALL xrPassthroughLayerSetStyleFB( + XrPassthroughLayerFB layer, + const XrPassthroughStyleFB* style); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateGeometryInstanceFB( + XrSession session, + const XrGeometryInstanceCreateInfoFB* createInfo, + XrGeometryInstanceFB* outGeometryInstance); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyGeometryInstanceFB( + XrGeometryInstanceFB instance); + +XRAPI_ATTR XrResult XRAPI_CALL xrGeometryInstanceSetTransformFB( + XrGeometryInstanceFB instance, + const XrGeometryInstanceTransformFB* transformation); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_render_model is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_render_model 1 + +#define XR_NULL_RENDER_MODEL_KEY_FB 0 + +XR_DEFINE_ATOM(XrRenderModelKeyFB) +#define XR_MAX_RENDER_MODEL_NAME_SIZE_FB 64 +#define XR_FB_render_model_SPEC_VERSION 4 +#define XR_FB_RENDER_MODEL_EXTENSION_NAME "XR_FB_render_model" +typedef XrFlags64 XrRenderModelFlagsFB; + +// Flag bits for XrRenderModelFlagsFB +static const XrRenderModelFlagsFB XR_RENDER_MODEL_SUPPORTS_GLTF_2_0_SUBSET_1_BIT_FB = 0x00000001; +static const XrRenderModelFlagsFB XR_RENDER_MODEL_SUPPORTS_GLTF_2_0_SUBSET_2_BIT_FB = 0x00000002; + +typedef struct XrRenderModelPathInfoFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrPath path; +} XrRenderModelPathInfoFB; + +typedef struct XrRenderModelPropertiesFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t vendorId; + char modelName[XR_MAX_RENDER_MODEL_NAME_SIZE_FB]; + XrRenderModelKeyFB modelKey; + uint32_t modelVersion; + XrRenderModelFlagsFB flags; +} XrRenderModelPropertiesFB; + +typedef struct XrRenderModelBufferFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t bufferCapacityInput; + uint32_t bufferCountOutput; + uint8_t* buffer; +} XrRenderModelBufferFB; + +typedef struct XrRenderModelLoadInfoFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrRenderModelKeyFB modelKey; +} XrRenderModelLoadInfoFB; + +// XrSystemRenderModelPropertiesFB extends XrSystemProperties +typedef struct XrSystemRenderModelPropertiesFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsRenderModelLoading; +} XrSystemRenderModelPropertiesFB; + +// XrRenderModelCapabilitiesRequestFB extends XrRenderModelPropertiesFB +typedef struct XrRenderModelCapabilitiesRequestFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrRenderModelFlagsFB flags; +} XrRenderModelCapabilitiesRequestFB; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateRenderModelPathsFB)(XrSession session, uint32_t pathCapacityInput, uint32_t* pathCountOutput, XrRenderModelPathInfoFB* paths); +typedef XrResult (XRAPI_PTR *PFN_xrGetRenderModelPropertiesFB)(XrSession session, XrPath path, XrRenderModelPropertiesFB* properties); +typedef XrResult (XRAPI_PTR *PFN_xrLoadRenderModelFB)(XrSession session, const XrRenderModelLoadInfoFB* info, XrRenderModelBufferFB* buffer); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateRenderModelPathsFB( + XrSession session, + uint32_t pathCapacityInput, + uint32_t* pathCountOutput, + XrRenderModelPathInfoFB* paths); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetRenderModelPropertiesFB( + XrSession session, + XrPath path, + XrRenderModelPropertiesFB* properties); + +XRAPI_ATTR XrResult XRAPI_CALL xrLoadRenderModelFB( + XrSession session, + const XrRenderModelLoadInfoFB* info, + XrRenderModelBufferFB* buffer); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_VARJO_foveated_rendering is a preprocessor guard. Do not pass it to API calls. +#define XR_VARJO_foveated_rendering 1 +#define XR_VARJO_foveated_rendering_SPEC_VERSION 3 +#define XR_VARJO_FOVEATED_RENDERING_EXTENSION_NAME "XR_VARJO_foveated_rendering" +// XrViewLocateFoveatedRenderingVARJO extends XrViewLocateInfo +typedef struct XrViewLocateFoveatedRenderingVARJO { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 foveatedRenderingActive; +} XrViewLocateFoveatedRenderingVARJO; + +// XrFoveatedViewConfigurationViewVARJO extends XrViewConfigurationView +typedef struct XrFoveatedViewConfigurationViewVARJO { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 foveatedRenderingActive; +} XrFoveatedViewConfigurationViewVARJO; + +// XrSystemFoveatedRenderingPropertiesVARJO extends XrSystemProperties +typedef struct XrSystemFoveatedRenderingPropertiesVARJO { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsFoveatedRendering; +} XrSystemFoveatedRenderingPropertiesVARJO; + + + +// XR_VARJO_composition_layer_depth_test is a preprocessor guard. Do not pass it to API calls. +#define XR_VARJO_composition_layer_depth_test 1 +#define XR_VARJO_composition_layer_depth_test_SPEC_VERSION 2 +#define XR_VARJO_COMPOSITION_LAYER_DEPTH_TEST_EXTENSION_NAME "XR_VARJO_composition_layer_depth_test" +// XrCompositionLayerDepthTestVARJO extends XrCompositionLayerProjection +typedef struct XrCompositionLayerDepthTestVARJO { + XrStructureType type; + const void* XR_MAY_ALIAS next; + float depthTestRangeNearZ; + float depthTestRangeFarZ; +} XrCompositionLayerDepthTestVARJO; + + + +// XR_VARJO_environment_depth_estimation is a preprocessor guard. Do not pass it to API calls. +#define XR_VARJO_environment_depth_estimation 1 +#define XR_VARJO_environment_depth_estimation_SPEC_VERSION 1 +#define XR_VARJO_ENVIRONMENT_DEPTH_ESTIMATION_EXTENSION_NAME "XR_VARJO_environment_depth_estimation" +typedef XrResult (XRAPI_PTR *PFN_xrSetEnvironmentDepthEstimationVARJO)(XrSession session, XrBool32 enabled); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrSetEnvironmentDepthEstimationVARJO( + XrSession session, + XrBool32 enabled); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_VARJO_marker_tracking is a preprocessor guard. Do not pass it to API calls. +#define XR_VARJO_marker_tracking 1 +#define XR_VARJO_marker_tracking_SPEC_VERSION 1 +#define XR_VARJO_MARKER_TRACKING_EXTENSION_NAME "XR_VARJO_marker_tracking" +// XrSystemMarkerTrackingPropertiesVARJO extends XrSystemProperties +typedef struct XrSystemMarkerTrackingPropertiesVARJO { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsMarkerTracking; +} XrSystemMarkerTrackingPropertiesVARJO; + +typedef struct XrEventDataMarkerTrackingUpdateVARJO { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint64_t markerId; + XrBool32 isActive; + XrBool32 isPredicted; + XrTime time; +} XrEventDataMarkerTrackingUpdateVARJO; + +typedef struct XrMarkerSpaceCreateInfoVARJO { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint64_t markerId; + XrPosef poseInMarkerSpace; +} XrMarkerSpaceCreateInfoVARJO; + +typedef XrResult (XRAPI_PTR *PFN_xrSetMarkerTrackingVARJO)(XrSession session, XrBool32 enabled); +typedef XrResult (XRAPI_PTR *PFN_xrSetMarkerTrackingTimeoutVARJO)(XrSession session, uint64_t markerId, XrDuration timeout); +typedef XrResult (XRAPI_PTR *PFN_xrSetMarkerTrackingPredictionVARJO)(XrSession session, uint64_t markerId, XrBool32 enable); +typedef XrResult (XRAPI_PTR *PFN_xrGetMarkerSizeVARJO)(XrSession session, uint64_t markerId, XrExtent2Df* size); +typedef XrResult (XRAPI_PTR *PFN_xrCreateMarkerSpaceVARJO)(XrSession session, const XrMarkerSpaceCreateInfoVARJO* createInfo, XrSpace* space); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrSetMarkerTrackingVARJO( + XrSession session, + XrBool32 enabled); + +XRAPI_ATTR XrResult XRAPI_CALL xrSetMarkerTrackingTimeoutVARJO( + XrSession session, + uint64_t markerId, + XrDuration timeout); + +XRAPI_ATTR XrResult XRAPI_CALL xrSetMarkerTrackingPredictionVARJO( + XrSession session, + uint64_t markerId, + XrBool32 enable); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetMarkerSizeVARJO( + XrSession session, + uint64_t markerId, + XrExtent2Df* size); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateMarkerSpaceVARJO( + XrSession session, + const XrMarkerSpaceCreateInfoVARJO* createInfo, + XrSpace* space); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_VARJO_view_offset is a preprocessor guard. Do not pass it to API calls. +#define XR_VARJO_view_offset 1 +#define XR_VARJO_view_offset_SPEC_VERSION 1 +#define XR_VARJO_VIEW_OFFSET_EXTENSION_NAME "XR_VARJO_view_offset" +typedef XrResult (XRAPI_PTR *PFN_xrSetViewOffsetVARJO)(XrSession session, float offset); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrSetViewOffsetVARJO( + XrSession session, + float offset); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_VARJO_xr4_controller_interaction is a preprocessor guard. Do not pass it to API calls. +#define XR_VARJO_xr4_controller_interaction 1 +#define XR_VARJO_xr4_controller_interaction_SPEC_VERSION 2 +#define XR_VARJO_XR4_CONTROLLER_INTERACTION_EXTENSION_NAME "XR_VARJO_xr4_controller_interaction" + + +// XR_ML_ml2_controller_interaction is a preprocessor guard. Do not pass it to API calls. +#define XR_ML_ml2_controller_interaction 1 +#define XR_ML_ml2_controller_interaction_SPEC_VERSION 1 +#define XR_ML_ML2_CONTROLLER_INTERACTION_EXTENSION_NAME "XR_ML_ml2_controller_interaction" + + +// XR_ML_frame_end_info is a preprocessor guard. Do not pass it to API calls. +#define XR_ML_frame_end_info 1 +#define XR_ML_frame_end_info_SPEC_VERSION 1 +#define XR_ML_FRAME_END_INFO_EXTENSION_NAME "XR_ML_frame_end_info" +typedef XrFlags64 XrFrameEndInfoFlagsML; + +// Flag bits for XrFrameEndInfoFlagsML +static const XrFrameEndInfoFlagsML XR_FRAME_END_INFO_PROTECTED_BIT_ML = 0x00000001; +static const XrFrameEndInfoFlagsML XR_FRAME_END_INFO_VIGNETTE_BIT_ML = 0x00000002; + +// XrFrameEndInfoML extends XrFrameEndInfo +typedef struct XrFrameEndInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + float focusDistance; + XrFrameEndInfoFlagsML flags; +} XrFrameEndInfoML; + + + +// XR_ML_global_dimmer is a preprocessor guard. Do not pass it to API calls. +#define XR_ML_global_dimmer 1 +#define XR_ML_global_dimmer_SPEC_VERSION 1 +#define XR_ML_GLOBAL_DIMMER_EXTENSION_NAME "XR_ML_global_dimmer" +typedef XrFlags64 XrGlobalDimmerFrameEndInfoFlagsML; + +// Flag bits for XrGlobalDimmerFrameEndInfoFlagsML +static const XrGlobalDimmerFrameEndInfoFlagsML XR_GLOBAL_DIMMER_FRAME_END_INFO_ENABLED_BIT_ML = 0x00000001; + +// XrGlobalDimmerFrameEndInfoML extends XrFrameEndInfo +typedef struct XrGlobalDimmerFrameEndInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + float dimmerValue; + XrGlobalDimmerFrameEndInfoFlagsML flags; +} XrGlobalDimmerFrameEndInfoML; + + + +// XR_ML_marker_understanding is a preprocessor guard. Do not pass it to API calls. +#define XR_ML_marker_understanding 1 +XR_DEFINE_ATOM(XrMarkerML) +XR_DEFINE_HANDLE(XrMarkerDetectorML) +#define XR_ML_marker_understanding_SPEC_VERSION 1 +#define XR_ML_MARKER_UNDERSTANDING_EXTENSION_NAME "XR_ML_marker_understanding" + +typedef enum XrMarkerDetectorProfileML { + XR_MARKER_DETECTOR_PROFILE_DEFAULT_ML = 0, + XR_MARKER_DETECTOR_PROFILE_SPEED_ML = 1, + XR_MARKER_DETECTOR_PROFILE_ACCURACY_ML = 2, + XR_MARKER_DETECTOR_PROFILE_SMALL_TARGETS_ML = 3, + XR_MARKER_DETECTOR_PROFILE_LARGE_FOV_ML = 4, + XR_MARKER_DETECTOR_PROFILE_CUSTOM_ML = 5, + XR_MARKER_DETECTOR_PROFILE_MAX_ENUM_ML = 0x7FFFFFFF +} XrMarkerDetectorProfileML; + +typedef enum XrMarkerTypeML { + XR_MARKER_TYPE_ARUCO_ML = 0, + XR_MARKER_TYPE_APRIL_TAG_ML = 1, + XR_MARKER_TYPE_QR_ML = 2, + XR_MARKER_TYPE_EAN_13_ML = 3, + XR_MARKER_TYPE_UPC_A_ML = 4, + XR_MARKER_TYPE_CODE_128_ML = 5, + XR_MARKER_TYPE_MAX_ENUM_ML = 0x7FFFFFFF +} XrMarkerTypeML; + +typedef enum XrMarkerArucoDictML { + XR_MARKER_ARUCO_DICT_4X4_50_ML = 0, + XR_MARKER_ARUCO_DICT_4X4_100_ML = 1, + XR_MARKER_ARUCO_DICT_4X4_250_ML = 2, + XR_MARKER_ARUCO_DICT_4X4_1000_ML = 3, + XR_MARKER_ARUCO_DICT_5X5_50_ML = 4, + XR_MARKER_ARUCO_DICT_5X5_100_ML = 5, + XR_MARKER_ARUCO_DICT_5X5_250_ML = 6, + XR_MARKER_ARUCO_DICT_5X5_1000_ML = 7, + XR_MARKER_ARUCO_DICT_6X6_50_ML = 8, + XR_MARKER_ARUCO_DICT_6X6_100_ML = 9, + XR_MARKER_ARUCO_DICT_6X6_250_ML = 10, + XR_MARKER_ARUCO_DICT_6X6_1000_ML = 11, + XR_MARKER_ARUCO_DICT_7X7_50_ML = 12, + XR_MARKER_ARUCO_DICT_7X7_100_ML = 13, + XR_MARKER_ARUCO_DICT_7X7_250_ML = 14, + XR_MARKER_ARUCO_DICT_7X7_1000_ML = 15, + XR_MARKER_ARUCO_DICT_MAX_ENUM_ML = 0x7FFFFFFF +} XrMarkerArucoDictML; + +typedef enum XrMarkerAprilTagDictML { + XR_MARKER_APRIL_TAG_DICT_16H5_ML = 0, + XR_MARKER_APRIL_TAG_DICT_25H9_ML = 1, + XR_MARKER_APRIL_TAG_DICT_36H10_ML = 2, + XR_MARKER_APRIL_TAG_DICT_36H11_ML = 3, + XR_MARKER_APRIL_TAG_DICT_MAX_ENUM_ML = 0x7FFFFFFF +} XrMarkerAprilTagDictML; + +typedef enum XrMarkerDetectorFpsML { + XR_MARKER_DETECTOR_FPS_LOW_ML = 0, + XR_MARKER_DETECTOR_FPS_MEDIUM_ML = 1, + XR_MARKER_DETECTOR_FPS_HIGH_ML = 2, + XR_MARKER_DETECTOR_FPS_MAX_ML = 3, + XR_MARKER_DETECTOR_FPS_MAX_ENUM_ML = 0x7FFFFFFF +} XrMarkerDetectorFpsML; + +typedef enum XrMarkerDetectorResolutionML { + XR_MARKER_DETECTOR_RESOLUTION_LOW_ML = 0, + XR_MARKER_DETECTOR_RESOLUTION_MEDIUM_ML = 1, + XR_MARKER_DETECTOR_RESOLUTION_HIGH_ML = 2, + XR_MARKER_DETECTOR_RESOLUTION_MAX_ENUM_ML = 0x7FFFFFFF +} XrMarkerDetectorResolutionML; + +typedef enum XrMarkerDetectorCameraML { + XR_MARKER_DETECTOR_CAMERA_RGB_CAMERA_ML = 0, + XR_MARKER_DETECTOR_CAMERA_WORLD_CAMERAS_ML = 1, + XR_MARKER_DETECTOR_CAMERA_MAX_ENUM_ML = 0x7FFFFFFF +} XrMarkerDetectorCameraML; + +typedef enum XrMarkerDetectorCornerRefineMethodML { + XR_MARKER_DETECTOR_CORNER_REFINE_METHOD_NONE_ML = 0, + XR_MARKER_DETECTOR_CORNER_REFINE_METHOD_SUBPIX_ML = 1, + XR_MARKER_DETECTOR_CORNER_REFINE_METHOD_CONTOUR_ML = 2, + XR_MARKER_DETECTOR_CORNER_REFINE_METHOD_APRIL_TAG_ML = 3, + XR_MARKER_DETECTOR_CORNER_REFINE_METHOD_MAX_ENUM_ML = 0x7FFFFFFF +} XrMarkerDetectorCornerRefineMethodML; + +typedef enum XrMarkerDetectorFullAnalysisIntervalML { + XR_MARKER_DETECTOR_FULL_ANALYSIS_INTERVAL_MAX_ML = 0, + XR_MARKER_DETECTOR_FULL_ANALYSIS_INTERVAL_FAST_ML = 1, + XR_MARKER_DETECTOR_FULL_ANALYSIS_INTERVAL_MEDIUM_ML = 2, + XR_MARKER_DETECTOR_FULL_ANALYSIS_INTERVAL_SLOW_ML = 3, + XR_MARKER_DETECTOR_FULL_ANALYSIS_INTERVAL_MAX_ENUM_ML = 0x7FFFFFFF +} XrMarkerDetectorFullAnalysisIntervalML; + +typedef enum XrMarkerDetectorStatusML { + XR_MARKER_DETECTOR_STATUS_PENDING_ML = 0, + XR_MARKER_DETECTOR_STATUS_READY_ML = 1, + XR_MARKER_DETECTOR_STATUS_ERROR_ML = 2, + XR_MARKER_DETECTOR_STATUS_MAX_ENUM_ML = 0x7FFFFFFF +} XrMarkerDetectorStatusML; +// XrSystemMarkerUnderstandingPropertiesML extends XrSystemProperties +typedef struct XrSystemMarkerUnderstandingPropertiesML { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsMarkerUnderstanding; +} XrSystemMarkerUnderstandingPropertiesML; + +typedef struct XrMarkerDetectorCreateInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrMarkerDetectorProfileML profile; + XrMarkerTypeML markerType; +} XrMarkerDetectorCreateInfoML; + +// XrMarkerDetectorArucoInfoML extends XrMarkerDetectorCreateInfoML +typedef struct XrMarkerDetectorArucoInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrMarkerArucoDictML arucoDict; +} XrMarkerDetectorArucoInfoML; + +// XrMarkerDetectorSizeInfoML extends XrMarkerDetectorCreateInfoML +typedef struct XrMarkerDetectorSizeInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + float markerLength; +} XrMarkerDetectorSizeInfoML; + +// XrMarkerDetectorAprilTagInfoML extends XrMarkerDetectorCreateInfoML +typedef struct XrMarkerDetectorAprilTagInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrMarkerAprilTagDictML aprilTagDict; +} XrMarkerDetectorAprilTagInfoML; + +// XrMarkerDetectorCustomProfileInfoML extends XrMarkerDetectorCreateInfoML +typedef struct XrMarkerDetectorCustomProfileInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrMarkerDetectorFpsML fpsHint; + XrMarkerDetectorResolutionML resolutionHint; + XrMarkerDetectorCameraML cameraHint; + XrMarkerDetectorCornerRefineMethodML cornerRefineMethod; + XrBool32 useEdgeRefinement; + XrMarkerDetectorFullAnalysisIntervalML fullAnalysisIntervalHint; +} XrMarkerDetectorCustomProfileInfoML; + +typedef struct XrMarkerDetectorSnapshotInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrMarkerDetectorSnapshotInfoML; + +typedef struct XrMarkerDetectorStateML { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrMarkerDetectorStatusML state; +} XrMarkerDetectorStateML; + +typedef struct XrMarkerSpaceCreateInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrMarkerDetectorML markerDetector; + XrMarkerML marker; + XrPosef poseInMarkerSpace; +} XrMarkerSpaceCreateInfoML; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateMarkerDetectorML)(XrSession session, const XrMarkerDetectorCreateInfoML* createInfo, XrMarkerDetectorML* markerDetector); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyMarkerDetectorML)(XrMarkerDetectorML markerDetector); +typedef XrResult (XRAPI_PTR *PFN_xrSnapshotMarkerDetectorML)(XrMarkerDetectorML markerDetector, XrMarkerDetectorSnapshotInfoML* snapshotInfo); +typedef XrResult (XRAPI_PTR *PFN_xrGetMarkerDetectorStateML)(XrMarkerDetectorML markerDetector, XrMarkerDetectorStateML* state); +typedef XrResult (XRAPI_PTR *PFN_xrGetMarkersML)(XrMarkerDetectorML markerDetector, uint32_t markerCapacityInput, uint32_t* markerCountOutput, XrMarkerML* markers); +typedef XrResult (XRAPI_PTR *PFN_xrGetMarkerReprojectionErrorML)(XrMarkerDetectorML markerDetector, XrMarkerML marker, float* reprojectionErrorMeters); +typedef XrResult (XRAPI_PTR *PFN_xrGetMarkerLengthML)(XrMarkerDetectorML markerDetector, XrMarkerML marker, float* meters); +typedef XrResult (XRAPI_PTR *PFN_xrGetMarkerNumberML)(XrMarkerDetectorML markerDetector, XrMarkerML marker, uint64_t* number); +typedef XrResult (XRAPI_PTR *PFN_xrGetMarkerStringML)(XrMarkerDetectorML markerDetector, XrMarkerML marker, uint32_t bufferCapacityInput, uint32_t* bufferCountOutput, char* buffer); +typedef XrResult (XRAPI_PTR *PFN_xrCreateMarkerSpaceML)(XrSession session, const XrMarkerSpaceCreateInfoML* createInfo, XrSpace* space); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateMarkerDetectorML( + XrSession session, + const XrMarkerDetectorCreateInfoML* createInfo, + XrMarkerDetectorML* markerDetector); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyMarkerDetectorML( + XrMarkerDetectorML markerDetector); + +XRAPI_ATTR XrResult XRAPI_CALL xrSnapshotMarkerDetectorML( + XrMarkerDetectorML markerDetector, + XrMarkerDetectorSnapshotInfoML* snapshotInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetMarkerDetectorStateML( + XrMarkerDetectorML markerDetector, + XrMarkerDetectorStateML* state); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetMarkersML( + XrMarkerDetectorML markerDetector, + uint32_t markerCapacityInput, + uint32_t* markerCountOutput, + XrMarkerML* markers); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetMarkerReprojectionErrorML( + XrMarkerDetectorML markerDetector, + XrMarkerML marker, + float* reprojectionErrorMeters); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetMarkerLengthML( + XrMarkerDetectorML markerDetector, + XrMarkerML marker, + float* meters); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetMarkerNumberML( + XrMarkerDetectorML markerDetector, + XrMarkerML marker, + uint64_t* number); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetMarkerStringML( + XrMarkerDetectorML markerDetector, + XrMarkerML marker, + uint32_t bufferCapacityInput, + uint32_t* bufferCountOutput, + char* buffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateMarkerSpaceML( + XrSession session, + const XrMarkerSpaceCreateInfoML* createInfo, + XrSpace* space); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ML_localization_map is a preprocessor guard. Do not pass it to API calls. +#define XR_ML_localization_map 1 +XR_DEFINE_HANDLE(XrExportedLocalizationMapML) +#define XR_MAX_LOCALIZATION_MAP_NAME_LENGTH_ML 64 +#define XR_ML_localization_map_SPEC_VERSION 1 +#define XR_ML_LOCALIZATION_MAP_EXTENSION_NAME "XR_ML_localization_map" + +typedef enum XrLocalizationMapStateML { + XR_LOCALIZATION_MAP_STATE_NOT_LOCALIZED_ML = 0, + XR_LOCALIZATION_MAP_STATE_LOCALIZED_ML = 1, + XR_LOCALIZATION_MAP_STATE_LOCALIZATION_PENDING_ML = 2, + XR_LOCALIZATION_MAP_STATE_LOCALIZATION_SLEEPING_BEFORE_RETRY_ML = 3, + XR_LOCALIZATION_MAP_STATE_MAX_ENUM_ML = 0x7FFFFFFF +} XrLocalizationMapStateML; + +typedef enum XrLocalizationMapTypeML { + XR_LOCALIZATION_MAP_TYPE_ON_DEVICE_ML = 0, + XR_LOCALIZATION_MAP_TYPE_CLOUD_ML = 1, + XR_LOCALIZATION_MAP_TYPE_MAX_ENUM_ML = 0x7FFFFFFF +} XrLocalizationMapTypeML; + +typedef enum XrLocalizationMapConfidenceML { + XR_LOCALIZATION_MAP_CONFIDENCE_POOR_ML = 0, + XR_LOCALIZATION_MAP_CONFIDENCE_FAIR_ML = 1, + XR_LOCALIZATION_MAP_CONFIDENCE_GOOD_ML = 2, + XR_LOCALIZATION_MAP_CONFIDENCE_EXCELLENT_ML = 3, + XR_LOCALIZATION_MAP_CONFIDENCE_MAX_ENUM_ML = 0x7FFFFFFF +} XrLocalizationMapConfidenceML; +typedef XrFlags64 XrLocalizationMapErrorFlagsML; + +// Flag bits for XrLocalizationMapErrorFlagsML +static const XrLocalizationMapErrorFlagsML XR_LOCALIZATION_MAP_ERROR_UNKNOWN_BIT_ML = 0x00000001; +static const XrLocalizationMapErrorFlagsML XR_LOCALIZATION_MAP_ERROR_OUT_OF_MAPPED_AREA_BIT_ML = 0x00000002; +static const XrLocalizationMapErrorFlagsML XR_LOCALIZATION_MAP_ERROR_LOW_FEATURE_COUNT_BIT_ML = 0x00000004; +static const XrLocalizationMapErrorFlagsML XR_LOCALIZATION_MAP_ERROR_EXCESSIVE_MOTION_BIT_ML = 0x00000008; +static const XrLocalizationMapErrorFlagsML XR_LOCALIZATION_MAP_ERROR_LOW_LIGHT_BIT_ML = 0x00000010; +static const XrLocalizationMapErrorFlagsML XR_LOCALIZATION_MAP_ERROR_HEADPOSE_BIT_ML = 0x00000020; + +typedef struct XrLocalizationMapML { + XrStructureType type; + void* XR_MAY_ALIAS next; + char name[XR_MAX_LOCALIZATION_MAP_NAME_LENGTH_ML]; + XrUuidEXT mapUuid; + XrLocalizationMapTypeML mapType; +} XrLocalizationMapML; + +typedef struct XrEventDataLocalizationChangedML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSession session; + XrLocalizationMapStateML state; + XrLocalizationMapML map; + XrLocalizationMapConfidenceML confidence; + XrLocalizationMapErrorFlagsML errorFlags; +} XrEventDataLocalizationChangedML; + +typedef struct XrLocalizationMapQueryInfoBaseHeaderML { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrLocalizationMapQueryInfoBaseHeaderML; + +typedef struct XrMapLocalizationRequestInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrUuidEXT mapUuid; +} XrMapLocalizationRequestInfoML; + +typedef struct XrLocalizationMapImportInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t size; + char* data; +} XrLocalizationMapImportInfoML; + +typedef struct XrLocalizationEnableEventsInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 enabled; +} XrLocalizationEnableEventsInfoML; + +typedef XrResult (XRAPI_PTR *PFN_xrEnableLocalizationEventsML)(XrSession session, const XrLocalizationEnableEventsInfoML * info); +typedef XrResult (XRAPI_PTR *PFN_xrQueryLocalizationMapsML)(XrSession session, const XrLocalizationMapQueryInfoBaseHeaderML* queryInfo, uint32_t mapCapacityInput, uint32_t * mapCountOutput, XrLocalizationMapML* maps); +typedef XrResult (XRAPI_PTR *PFN_xrRequestMapLocalizationML)(XrSession session, const XrMapLocalizationRequestInfoML* requestInfo); +typedef XrResult (XRAPI_PTR *PFN_xrImportLocalizationMapML)(XrSession session, const XrLocalizationMapImportInfoML* importInfo, XrUuidEXT* mapUuid); +typedef XrResult (XRAPI_PTR *PFN_xrCreateExportedLocalizationMapML)(XrSession session, const XrUuidEXT* mapUuid, XrExportedLocalizationMapML* map); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyExportedLocalizationMapML)(XrExportedLocalizationMapML map); +typedef XrResult (XRAPI_PTR *PFN_xrGetExportedLocalizationMapDataML)(XrExportedLocalizationMapML map, uint32_t bufferCapacityInput, uint32_t* bufferCountOutput, char* buffer); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnableLocalizationEventsML( + XrSession session, + const XrLocalizationEnableEventsInfoML * info); + +XRAPI_ATTR XrResult XRAPI_CALL xrQueryLocalizationMapsML( + XrSession session, + const XrLocalizationMapQueryInfoBaseHeaderML* queryInfo, + uint32_t mapCapacityInput, + uint32_t * mapCountOutput, + XrLocalizationMapML* maps); + +XRAPI_ATTR XrResult XRAPI_CALL xrRequestMapLocalizationML( + XrSession session, + const XrMapLocalizationRequestInfoML* requestInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrImportLocalizationMapML( + XrSession session, + const XrLocalizationMapImportInfoML* importInfo, + XrUuidEXT* mapUuid); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateExportedLocalizationMapML( + XrSession session, + const XrUuidEXT* mapUuid, + XrExportedLocalizationMapML* map); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyExportedLocalizationMapML( + XrExportedLocalizationMapML map); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetExportedLocalizationMapDataML( + XrExportedLocalizationMapML map, + uint32_t bufferCapacityInput, + uint32_t* bufferCountOutput, + char* buffer); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ML_spatial_anchors is a preprocessor guard. Do not pass it to API calls. +#define XR_ML_spatial_anchors 1 +XR_DEFINE_OPAQUE_64(XrFutureEXT) +#define XR_ML_spatial_anchors_SPEC_VERSION 1 +#define XR_ML_SPATIAL_ANCHORS_EXTENSION_NAME "XR_ML_spatial_anchors" + +typedef enum XrSpatialAnchorConfidenceML { + XR_SPATIAL_ANCHOR_CONFIDENCE_LOW_ML = 0, + XR_SPATIAL_ANCHOR_CONFIDENCE_MEDIUM_ML = 1, + XR_SPATIAL_ANCHOR_CONFIDENCE_HIGH_ML = 2, + XR_SPATIAL_ANCHOR_CONFIDENCE_MAX_ENUM_ML = 0x7FFFFFFF +} XrSpatialAnchorConfidenceML; +typedef struct XR_MAY_ALIAS XrSpatialAnchorsCreateInfoBaseHeaderML { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSpatialAnchorsCreateInfoBaseHeaderML; + +typedef struct XrSpatialAnchorsCreateInfoFromPoseML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace baseSpace; + XrPosef poseInBaseSpace; + XrTime time; +} XrSpatialAnchorsCreateInfoFromPoseML; + +typedef struct XrCreateSpatialAnchorsCompletionML { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + uint32_t spaceCount; + XrSpace* spaces; +} XrCreateSpatialAnchorsCompletionML; + +typedef struct XrSpatialAnchorStateML { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrSpatialAnchorConfidenceML confidence; +} XrSpatialAnchorStateML; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorsAsyncML)(XrSession session, const XrSpatialAnchorsCreateInfoBaseHeaderML* createInfo, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorsCompleteML)(XrSession session, XrFutureEXT future, XrCreateSpatialAnchorsCompletionML* completion); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpatialAnchorStateML)(XrSpace anchor, XrSpatialAnchorStateML* state); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorsAsyncML( + XrSession session, + const XrSpatialAnchorsCreateInfoBaseHeaderML* createInfo, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorsCompleteML( + XrSession session, + XrFutureEXT future, + XrCreateSpatialAnchorsCompletionML* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpatialAnchorStateML( + XrSpace anchor, + XrSpatialAnchorStateML* state); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ML_spatial_anchors_storage is a preprocessor guard. Do not pass it to API calls. +#define XR_ML_spatial_anchors_storage 1 +XR_DEFINE_HANDLE(XrSpatialAnchorsStorageML) +#define XR_ML_spatial_anchors_storage_SPEC_VERSION 1 +#define XR_ML_SPATIAL_ANCHORS_STORAGE_EXTENSION_NAME "XR_ML_spatial_anchors_storage" +typedef struct XrSpatialAnchorsCreateStorageInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSpatialAnchorsCreateStorageInfoML; + +typedef struct XR_MAY_ALIAS XrSpatialAnchorsQueryInfoBaseHeaderML { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSpatialAnchorsQueryInfoBaseHeaderML; + +typedef struct XrSpatialAnchorsQueryInfoRadiusML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace baseSpace; + XrVector3f center; + XrTime time; + float radius; +} XrSpatialAnchorsQueryInfoRadiusML; + +typedef struct XrSpatialAnchorsQueryCompletionML { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + uint32_t uuidCapacityInput; + uint32_t uuidCountOutput; + XrUuidEXT* uuids; +} XrSpatialAnchorsQueryCompletionML; + +typedef struct XrSpatialAnchorsCreateInfoFromUuidsML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialAnchorsStorageML storage; + uint32_t uuidCount; + const XrUuidEXT* uuids; +} XrSpatialAnchorsCreateInfoFromUuidsML; + +typedef struct XrSpatialAnchorsPublishInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t anchorCount; + const XrSpace* anchors; + uint64_t expiration; +} XrSpatialAnchorsPublishInfoML; + +typedef struct XrSpatialAnchorsPublishCompletionML { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + uint32_t uuidCount; + XrUuidEXT* uuids; +} XrSpatialAnchorsPublishCompletionML; + +typedef struct XrSpatialAnchorsDeleteInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t uuidCount; + const XrUuidEXT* uuids; +} XrSpatialAnchorsDeleteInfoML; + +typedef struct XrSpatialAnchorsDeleteCompletionML { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; +} XrSpatialAnchorsDeleteCompletionML; + +typedef struct XrSpatialAnchorsUpdateExpirationInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t uuidCount; + const XrUuidEXT* uuids; + uint64_t expiration; +} XrSpatialAnchorsUpdateExpirationInfoML; + +typedef struct XrSpatialAnchorsUpdateExpirationCompletionML { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; +} XrSpatialAnchorsUpdateExpirationCompletionML; + +typedef struct XrSpatialAnchorCompletionResultML { + XrUuidEXT uuid; + XrResult result; +} XrSpatialAnchorCompletionResultML; + +// XrSpatialAnchorsPublishCompletionDetailsML extends XrSpatialAnchorsPublishCompletionML +typedef struct XrSpatialAnchorsPublishCompletionDetailsML { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t resultCount; + XrSpatialAnchorCompletionResultML* results; +} XrSpatialAnchorsPublishCompletionDetailsML; + +// XrSpatialAnchorsDeleteCompletionDetailsML extends XrSpatialAnchorsDeleteCompletionML +typedef struct XrSpatialAnchorsDeleteCompletionDetailsML { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t resultCount; + XrSpatialAnchorCompletionResultML* results; +} XrSpatialAnchorsDeleteCompletionDetailsML; + +// XrSpatialAnchorsUpdateExpirationCompletionDetailsML extends XrSpatialAnchorsUpdateExpirationCompletionML +typedef struct XrSpatialAnchorsUpdateExpirationCompletionDetailsML { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t resultCount; + XrSpatialAnchorCompletionResultML* results; +} XrSpatialAnchorsUpdateExpirationCompletionDetailsML; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorsStorageML)(XrSession session, const XrSpatialAnchorsCreateStorageInfoML* createInfo, XrSpatialAnchorsStorageML* storage); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySpatialAnchorsStorageML)(XrSpatialAnchorsStorageML storage); +typedef XrResult (XRAPI_PTR *PFN_xrQuerySpatialAnchorsAsyncML)(XrSpatialAnchorsStorageML storage, const XrSpatialAnchorsQueryInfoBaseHeaderML* queryInfo, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrQuerySpatialAnchorsCompleteML)(XrSpatialAnchorsStorageML storage, XrFutureEXT future, XrSpatialAnchorsQueryCompletionML* completion); +typedef XrResult (XRAPI_PTR *PFN_xrPublishSpatialAnchorsAsyncML)(XrSpatialAnchorsStorageML storage, const XrSpatialAnchorsPublishInfoML* publishInfo, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrPublishSpatialAnchorsCompleteML)(XrSpatialAnchorsStorageML storage, XrFutureEXT future, XrSpatialAnchorsPublishCompletionML* completion); +typedef XrResult (XRAPI_PTR *PFN_xrDeleteSpatialAnchorsAsyncML)(XrSpatialAnchorsStorageML storage, const XrSpatialAnchorsDeleteInfoML* deleteInfo, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrDeleteSpatialAnchorsCompleteML)(XrSpatialAnchorsStorageML storage, XrFutureEXT future, XrSpatialAnchorsDeleteCompletionML* completion); +typedef XrResult (XRAPI_PTR *PFN_xrUpdateSpatialAnchorsExpirationAsyncML)(XrSpatialAnchorsStorageML storage, const XrSpatialAnchorsUpdateExpirationInfoML* updateInfo, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrUpdateSpatialAnchorsExpirationCompleteML)(XrSpatialAnchorsStorageML storage, XrFutureEXT future, XrSpatialAnchorsUpdateExpirationCompletionML* completion); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorsStorageML( + XrSession session, + const XrSpatialAnchorsCreateStorageInfoML* createInfo, + XrSpatialAnchorsStorageML* storage); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySpatialAnchorsStorageML( + XrSpatialAnchorsStorageML storage); + +XRAPI_ATTR XrResult XRAPI_CALL xrQuerySpatialAnchorsAsyncML( + XrSpatialAnchorsStorageML storage, + const XrSpatialAnchorsQueryInfoBaseHeaderML* queryInfo, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrQuerySpatialAnchorsCompleteML( + XrSpatialAnchorsStorageML storage, + XrFutureEXT future, + XrSpatialAnchorsQueryCompletionML* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrPublishSpatialAnchorsAsyncML( + XrSpatialAnchorsStorageML storage, + const XrSpatialAnchorsPublishInfoML* publishInfo, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrPublishSpatialAnchorsCompleteML( + XrSpatialAnchorsStorageML storage, + XrFutureEXT future, + XrSpatialAnchorsPublishCompletionML* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrDeleteSpatialAnchorsAsyncML( + XrSpatialAnchorsStorageML storage, + const XrSpatialAnchorsDeleteInfoML* deleteInfo, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrDeleteSpatialAnchorsCompleteML( + XrSpatialAnchorsStorageML storage, + XrFutureEXT future, + XrSpatialAnchorsDeleteCompletionML* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrUpdateSpatialAnchorsExpirationAsyncML( + XrSpatialAnchorsStorageML storage, + const XrSpatialAnchorsUpdateExpirationInfoML* updateInfo, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrUpdateSpatialAnchorsExpirationCompleteML( + XrSpatialAnchorsStorageML storage, + XrFutureEXT future, + XrSpatialAnchorsUpdateExpirationCompletionML* completion); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_MSFT_spatial_anchor_persistence is a preprocessor guard. Do not pass it to API calls. +#define XR_MSFT_spatial_anchor_persistence 1 +XR_DEFINE_HANDLE(XrSpatialAnchorStoreConnectionMSFT) +#define XR_MAX_SPATIAL_ANCHOR_NAME_SIZE_MSFT 256 +#define XR_MSFT_spatial_anchor_persistence_SPEC_VERSION 2 +#define XR_MSFT_SPATIAL_ANCHOR_PERSISTENCE_EXTENSION_NAME "XR_MSFT_spatial_anchor_persistence" +typedef struct XrSpatialAnchorPersistenceNameMSFT { + char name[XR_MAX_SPATIAL_ANCHOR_NAME_SIZE_MSFT]; +} XrSpatialAnchorPersistenceNameMSFT; + +typedef struct XrSpatialAnchorPersistenceInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialAnchorPersistenceNameMSFT spatialAnchorPersistenceName; + XrSpatialAnchorMSFT spatialAnchor; +} XrSpatialAnchorPersistenceInfoMSFT; + +typedef struct XrSpatialAnchorFromPersistedAnchorCreateInfoMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore; + XrSpatialAnchorPersistenceNameMSFT spatialAnchorPersistenceName; +} XrSpatialAnchorFromPersistedAnchorCreateInfoMSFT; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorStoreConnectionMSFT)(XrSession session, XrSpatialAnchorStoreConnectionMSFT* spatialAnchorStore); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySpatialAnchorStoreConnectionMSFT)(XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore); +typedef XrResult (XRAPI_PTR *PFN_xrPersistSpatialAnchorMSFT)(XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore, const XrSpatialAnchorPersistenceInfoMSFT* spatialAnchorPersistenceInfo); +typedef XrResult (XRAPI_PTR *PFN_xrEnumeratePersistedSpatialAnchorNamesMSFT)(XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore, uint32_t spatialAnchorNameCapacityInput, uint32_t* spatialAnchorNameCountOutput, XrSpatialAnchorPersistenceNameMSFT* spatialAnchorNames); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorFromPersistedNameMSFT)(XrSession session, const XrSpatialAnchorFromPersistedAnchorCreateInfoMSFT* spatialAnchorCreateInfo, XrSpatialAnchorMSFT* spatialAnchor); +typedef XrResult (XRAPI_PTR *PFN_xrUnpersistSpatialAnchorMSFT)(XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore, const XrSpatialAnchorPersistenceNameMSFT* spatialAnchorPersistenceName); +typedef XrResult (XRAPI_PTR *PFN_xrClearSpatialAnchorStoreMSFT)(XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorStoreConnectionMSFT( + XrSession session, + XrSpatialAnchorStoreConnectionMSFT* spatialAnchorStore); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySpatialAnchorStoreConnectionMSFT( + XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore); + +XRAPI_ATTR XrResult XRAPI_CALL xrPersistSpatialAnchorMSFT( + XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore, + const XrSpatialAnchorPersistenceInfoMSFT* spatialAnchorPersistenceInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumeratePersistedSpatialAnchorNamesMSFT( + XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore, + uint32_t spatialAnchorNameCapacityInput, + uint32_t* spatialAnchorNameCountOutput, + XrSpatialAnchorPersistenceNameMSFT* spatialAnchorNames); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorFromPersistedNameMSFT( + XrSession session, + const XrSpatialAnchorFromPersistedAnchorCreateInfoMSFT* spatialAnchorCreateInfo, + XrSpatialAnchorMSFT* spatialAnchor); + +XRAPI_ATTR XrResult XRAPI_CALL xrUnpersistSpatialAnchorMSFT( + XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore, + const XrSpatialAnchorPersistenceNameMSFT* spatialAnchorPersistenceName); + +XRAPI_ATTR XrResult XRAPI_CALL xrClearSpatialAnchorStoreMSFT( + XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_MSFT_scene_marker is a preprocessor guard. Do not pass it to API calls. +#define XR_MSFT_scene_marker 1 +#define XR_MSFT_scene_marker_SPEC_VERSION 1 +#define XR_MSFT_SCENE_MARKER_EXTENSION_NAME "XR_MSFT_scene_marker" + +typedef enum XrSceneMarkerTypeMSFT { + XR_SCENE_MARKER_TYPE_QR_CODE_MSFT = 1, + XR_SCENE_MARKER_TYPE_MAX_ENUM_MSFT = 0x7FFFFFFF +} XrSceneMarkerTypeMSFT; + +typedef enum XrSceneMarkerQRCodeSymbolTypeMSFT { + XR_SCENE_MARKER_QR_CODE_SYMBOL_TYPE_QR_CODE_MSFT = 1, + XR_SCENE_MARKER_QR_CODE_SYMBOL_TYPE_MICRO_QR_CODE_MSFT = 2, + XR_SCENE_MARKER_QRCODE_SYMBOL_TYPE_MAX_ENUM_MSFT = 0x7FFFFFFF +} XrSceneMarkerQRCodeSymbolTypeMSFT; +typedef struct XrSceneMarkerMSFT { + XrSceneMarkerTypeMSFT markerType; + XrTime lastSeenTime; + XrOffset2Df center; + XrExtent2Df size; +} XrSceneMarkerMSFT; + +// XrSceneMarkersMSFT extends XrSceneComponentsMSFT +typedef struct XrSceneMarkersMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t sceneMarkerCapacityInput; + XrSceneMarkerMSFT* sceneMarkers; +} XrSceneMarkersMSFT; + +// XrSceneMarkerTypeFilterMSFT extends XrSceneComponentsGetInfoMSFT +typedef struct XrSceneMarkerTypeFilterMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t markerTypeCount; + XrSceneMarkerTypeMSFT* markerTypes; +} XrSceneMarkerTypeFilterMSFT; + +typedef struct XrSceneMarkerQRCodeMSFT { + XrSceneMarkerQRCodeSymbolTypeMSFT symbolType; + uint8_t version; +} XrSceneMarkerQRCodeMSFT; + +// XrSceneMarkerQRCodesMSFT extends XrSceneComponentsMSFT +typedef struct XrSceneMarkerQRCodesMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t qrCodeCapacityInput; + XrSceneMarkerQRCodeMSFT* qrCodes; +} XrSceneMarkerQRCodesMSFT; + +typedef XrResult (XRAPI_PTR *PFN_xrGetSceneMarkerRawDataMSFT)(XrSceneMSFT scene, const XrUuidMSFT* markerId, uint32_t bufferCapacityInput, uint32_t* bufferCountOutput, uint8_t* buffer); +typedef XrResult (XRAPI_PTR *PFN_xrGetSceneMarkerDecodedStringMSFT)(XrSceneMSFT scene, const XrUuidMSFT* markerId, uint32_t bufferCapacityInput, uint32_t* bufferCountOutput, char* buffer); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetSceneMarkerRawDataMSFT( + XrSceneMSFT scene, + const XrUuidMSFT* markerId, + uint32_t bufferCapacityInput, + uint32_t* bufferCountOutput, + uint8_t* buffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSceneMarkerDecodedStringMSFT( + XrSceneMSFT scene, + const XrUuidMSFT* markerId, + uint32_t bufferCapacityInput, + uint32_t* bufferCountOutput, + char* buffer); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ULTRALEAP_hand_tracking_forearm is a preprocessor guard. Do not pass it to API calls. +#define XR_ULTRALEAP_hand_tracking_forearm 1 + +#define XR_HAND_FOREARM_JOINT_COUNT_ULTRALEAP 27 + +#define XR_ULTRALEAP_hand_tracking_forearm_SPEC_VERSION 1 +#define XR_ULTRALEAP_HAND_TRACKING_FOREARM_EXTENSION_NAME "XR_ULTRALEAP_hand_tracking_forearm" + +typedef enum XrHandForearmJointULTRALEAP { + XR_HAND_FOREARM_JOINT_PALM_ULTRALEAP = 0, + XR_HAND_FOREARM_JOINT_WRIST_ULTRALEAP = 1, + XR_HAND_FOREARM_JOINT_THUMB_METACARPAL_ULTRALEAP = 2, + XR_HAND_FOREARM_JOINT_THUMB_PROXIMAL_ULTRALEAP = 3, + XR_HAND_FOREARM_JOINT_THUMB_DISTAL_ULTRALEAP = 4, + XR_HAND_FOREARM_JOINT_THUMB_TIP_ULTRALEAP = 5, + XR_HAND_FOREARM_JOINT_INDEX_METACARPAL_ULTRALEAP = 6, + XR_HAND_FOREARM_JOINT_INDEX_PROXIMAL_ULTRALEAP = 7, + XR_HAND_FOREARM_JOINT_INDEX_INTERMEDIATE_ULTRALEAP = 8, + XR_HAND_FOREARM_JOINT_INDEX_DISTAL_ULTRALEAP = 9, + XR_HAND_FOREARM_JOINT_INDEX_TIP_ULTRALEAP = 10, + XR_HAND_FOREARM_JOINT_MIDDLE_METACARPAL_ULTRALEAP = 11, + XR_HAND_FOREARM_JOINT_MIDDLE_PROXIMAL_ULTRALEAP = 12, + XR_HAND_FOREARM_JOINT_MIDDLE_INTERMEDIATE_ULTRALEAP = 13, + XR_HAND_FOREARM_JOINT_MIDDLE_DISTAL_ULTRALEAP = 14, + XR_HAND_FOREARM_JOINT_MIDDLE_TIP_ULTRALEAP = 15, + XR_HAND_FOREARM_JOINT_RING_METACARPAL_ULTRALEAP = 16, + XR_HAND_FOREARM_JOINT_RING_PROXIMAL_ULTRALEAP = 17, + XR_HAND_FOREARM_JOINT_RING_INTERMEDIATE_ULTRALEAP = 18, + XR_HAND_FOREARM_JOINT_RING_DISTAL_ULTRALEAP = 19, + XR_HAND_FOREARM_JOINT_RING_TIP_ULTRALEAP = 20, + XR_HAND_FOREARM_JOINT_LITTLE_METACARPAL_ULTRALEAP = 21, + XR_HAND_FOREARM_JOINT_LITTLE_PROXIMAL_ULTRALEAP = 22, + XR_HAND_FOREARM_JOINT_LITTLE_INTERMEDIATE_ULTRALEAP = 23, + XR_HAND_FOREARM_JOINT_LITTLE_DISTAL_ULTRALEAP = 24, + XR_HAND_FOREARM_JOINT_LITTLE_TIP_ULTRALEAP = 25, + XR_HAND_FOREARM_JOINT_ELBOW_ULTRALEAP = 26, + XR_HAND_FOREARM_JOINT_MAX_ENUM_ULTRALEAP = 0x7FFFFFFF +} XrHandForearmJointULTRALEAP; + + +// XR_FB_spatial_entity_query is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_spatial_entity_query 1 +#define XR_FB_spatial_entity_query_SPEC_VERSION 1 +#define XR_FB_SPATIAL_ENTITY_QUERY_EXTENSION_NAME "XR_FB_spatial_entity_query" + +typedef enum XrSpaceQueryActionFB { + XR_SPACE_QUERY_ACTION_LOAD_FB = 0, + XR_SPACE_QUERY_ACTION_MAX_ENUM_FB = 0x7FFFFFFF +} XrSpaceQueryActionFB; + +typedef enum XrSpaceStorageLocationFB { + XR_SPACE_STORAGE_LOCATION_INVALID_FB = 0, + XR_SPACE_STORAGE_LOCATION_LOCAL_FB = 1, + XR_SPACE_STORAGE_LOCATION_CLOUD_FB = 2, + XR_SPACE_STORAGE_LOCATION_MAX_ENUM_FB = 0x7FFFFFFF +} XrSpaceStorageLocationFB; +typedef struct XR_MAY_ALIAS XrSpaceQueryInfoBaseHeaderFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSpaceQueryInfoBaseHeaderFB; + +typedef struct XR_MAY_ALIAS XrSpaceFilterInfoBaseHeaderFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSpaceFilterInfoBaseHeaderFB; + +typedef struct XrSpaceQueryInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpaceQueryActionFB queryAction; + uint32_t maxResultCount; + XrDuration timeout; + const XrSpaceFilterInfoBaseHeaderFB* filter; + const XrSpaceFilterInfoBaseHeaderFB* excludeFilter; +} XrSpaceQueryInfoFB; + +// XrSpaceStorageLocationFilterInfoFB extends XrSpaceFilterInfoBaseHeaderFB +typedef struct XrSpaceStorageLocationFilterInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpaceStorageLocationFB location; +} XrSpaceStorageLocationFilterInfoFB; + +typedef struct XrSpaceUuidFilterInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t uuidCount; + XrUuidEXT* uuids; +} XrSpaceUuidFilterInfoFB; + +typedef struct XrSpaceComponentFilterInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpaceComponentTypeFB componentType; +} XrSpaceComponentFilterInfoFB; + +typedef struct XrSpaceQueryResultFB { + XrSpace space; + XrUuidEXT uuid; +} XrSpaceQueryResultFB; + +typedef struct XrSpaceQueryResultsFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t resultCapacityInput; + uint32_t resultCountOutput; + XrSpaceQueryResultFB* results; +} XrSpaceQueryResultsFB; + +typedef struct XrEventDataSpaceQueryResultsAvailableFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; +} XrEventDataSpaceQueryResultsAvailableFB; + +typedef struct XrEventDataSpaceQueryCompleteFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; + XrResult result; +} XrEventDataSpaceQueryCompleteFB; + +typedef XrResult (XRAPI_PTR *PFN_xrQuerySpacesFB)(XrSession session, const XrSpaceQueryInfoBaseHeaderFB* info, XrAsyncRequestIdFB* requestId); +typedef XrResult (XRAPI_PTR *PFN_xrRetrieveSpaceQueryResultsFB)(XrSession session, XrAsyncRequestIdFB requestId, XrSpaceQueryResultsFB* results); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrQuerySpacesFB( + XrSession session, + const XrSpaceQueryInfoBaseHeaderFB* info, + XrAsyncRequestIdFB* requestId); + +XRAPI_ATTR XrResult XRAPI_CALL xrRetrieveSpaceQueryResultsFB( + XrSession session, + XrAsyncRequestIdFB requestId, + XrSpaceQueryResultsFB* results); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_spatial_entity_storage is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_spatial_entity_storage 1 +#define XR_FB_spatial_entity_storage_SPEC_VERSION 1 +#define XR_FB_SPATIAL_ENTITY_STORAGE_EXTENSION_NAME "XR_FB_spatial_entity_storage" + +typedef enum XrSpacePersistenceModeFB { + XR_SPACE_PERSISTENCE_MODE_INVALID_FB = 0, + XR_SPACE_PERSISTENCE_MODE_INDEFINITE_FB = 1, + XR_SPACE_PERSISTENCE_MODE_MAX_ENUM_FB = 0x7FFFFFFF +} XrSpacePersistenceModeFB; +typedef struct XrSpaceSaveInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace space; + XrSpaceStorageLocationFB location; + XrSpacePersistenceModeFB persistenceMode; +} XrSpaceSaveInfoFB; + +typedef struct XrSpaceEraseInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace space; + XrSpaceStorageLocationFB location; +} XrSpaceEraseInfoFB; + +typedef struct XrEventDataSpaceSaveCompleteFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; + XrResult result; + XrSpace space; + XrUuidEXT uuid; + XrSpaceStorageLocationFB location; +} XrEventDataSpaceSaveCompleteFB; + +typedef struct XrEventDataSpaceEraseCompleteFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; + XrResult result; + XrSpace space; + XrUuidEXT uuid; + XrSpaceStorageLocationFB location; +} XrEventDataSpaceEraseCompleteFB; + +typedef XrResult (XRAPI_PTR *PFN_xrSaveSpaceFB)(XrSession session, const XrSpaceSaveInfoFB* info, XrAsyncRequestIdFB* requestId); +typedef XrResult (XRAPI_PTR *PFN_xrEraseSpaceFB)(XrSession session, const XrSpaceEraseInfoFB* info, XrAsyncRequestIdFB* requestId); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrSaveSpaceFB( + XrSession session, + const XrSpaceSaveInfoFB* info, + XrAsyncRequestIdFB* requestId); + +XRAPI_ATTR XrResult XRAPI_CALL xrEraseSpaceFB( + XrSession session, + const XrSpaceEraseInfoFB* info, + XrAsyncRequestIdFB* requestId); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_touch_controller_pro is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_touch_controller_pro 1 +#define XR_FB_touch_controller_pro_SPEC_VERSION 1 +#define XR_FB_TOUCH_CONTROLLER_PRO_EXTENSION_NAME "XR_FB_touch_controller_pro" + + +// XR_FB_spatial_entity_sharing is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_spatial_entity_sharing 1 +XR_DEFINE_HANDLE(XrSpaceUserFB) +#define XR_FB_spatial_entity_sharing_SPEC_VERSION 1 +#define XR_FB_SPATIAL_ENTITY_SHARING_EXTENSION_NAME "XR_FB_spatial_entity_sharing" +typedef struct XrSpaceShareInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t spaceCount; + XrSpace* spaces; + uint32_t userCount; + XrSpaceUserFB* users; +} XrSpaceShareInfoFB; + +typedef struct XrEventDataSpaceShareCompleteFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; + XrResult result; +} XrEventDataSpaceShareCompleteFB; + +typedef XrResult (XRAPI_PTR *PFN_xrShareSpacesFB)(XrSession session, const XrSpaceShareInfoFB* info, XrAsyncRequestIdFB* requestId); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrShareSpacesFB( + XrSession session, + const XrSpaceShareInfoFB* info, + XrAsyncRequestIdFB* requestId); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_space_warp is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_space_warp 1 +#define XR_FB_space_warp_SPEC_VERSION 2 +#define XR_FB_SPACE_WARP_EXTENSION_NAME "XR_FB_space_warp" +typedef XrFlags64 XrCompositionLayerSpaceWarpInfoFlagsFB; + +// Flag bits for XrCompositionLayerSpaceWarpInfoFlagsFB +static const XrCompositionLayerSpaceWarpInfoFlagsFB XR_COMPOSITION_LAYER_SPACE_WARP_INFO_FRAME_SKIP_BIT_FB = 0x00000001; + +// XrCompositionLayerSpaceWarpInfoFB extends XrCompositionLayerProjectionView +typedef struct XrCompositionLayerSpaceWarpInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCompositionLayerSpaceWarpInfoFlagsFB layerFlags; + XrSwapchainSubImage motionVectorSubImage; + XrPosef appSpaceDeltaPose; + XrSwapchainSubImage depthSubImage; + float minDepth; + float maxDepth; + float nearZ; + float farZ; +} XrCompositionLayerSpaceWarpInfoFB; + +// XrSystemSpaceWarpPropertiesFB extends XrSystemProperties +typedef struct XrSystemSpaceWarpPropertiesFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t recommendedMotionVectorImageRectWidth; + uint32_t recommendedMotionVectorImageRectHeight; +} XrSystemSpaceWarpPropertiesFB; + + + +// XR_FB_haptic_amplitude_envelope is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_haptic_amplitude_envelope 1 + +#define XR_MAX_HAPTIC_AMPLITUDE_ENVELOPE_SAMPLES_FB 4000u + +#define XR_FB_haptic_amplitude_envelope_SPEC_VERSION 1 +#define XR_FB_HAPTIC_AMPLITUDE_ENVELOPE_EXTENSION_NAME "XR_FB_haptic_amplitude_envelope" +typedef struct XrHapticAmplitudeEnvelopeVibrationFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrDuration duration; + uint32_t amplitudeCount; + const float* amplitudes; +} XrHapticAmplitudeEnvelopeVibrationFB; + + + +// XR_FB_scene is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_scene 1 +#define XR_FB_scene_SPEC_VERSION 4 +#define XR_FB_SCENE_EXTENSION_NAME "XR_FB_scene" +typedef XrFlags64 XrSemanticLabelsSupportFlagsFB; + +// Flag bits for XrSemanticLabelsSupportFlagsFB +static const XrSemanticLabelsSupportFlagsFB XR_SEMANTIC_LABELS_SUPPORT_MULTIPLE_SEMANTIC_LABELS_BIT_FB = 0x00000001; +static const XrSemanticLabelsSupportFlagsFB XR_SEMANTIC_LABELS_SUPPORT_ACCEPT_DESK_TO_TABLE_MIGRATION_BIT_FB = 0x00000002; +static const XrSemanticLabelsSupportFlagsFB XR_SEMANTIC_LABELS_SUPPORT_ACCEPT_INVISIBLE_WALL_FACE_BIT_FB = 0x00000004; + +typedef XrExtent3Df XrExtent3DfFB; + +typedef struct XrOffset3DfFB { + float x; + float y; + float z; +} XrOffset3DfFB; + +typedef struct XrRect3DfFB { + XrOffset3DfFB offset; + XrExtent3DfFB extent; +} XrRect3DfFB; + +typedef struct XrSemanticLabelsFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t bufferCapacityInput; + uint32_t bufferCountOutput; + char* buffer; +} XrSemanticLabelsFB; + +typedef struct XrRoomLayoutFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrUuidEXT floorUuid; + XrUuidEXT ceilingUuid; + uint32_t wallUuidCapacityInput; + uint32_t wallUuidCountOutput; + XrUuidEXT* wallUuids; +} XrRoomLayoutFB; + +typedef struct XrBoundary2DFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t vertexCapacityInput; + uint32_t vertexCountOutput; + XrVector2f* vertices; +} XrBoundary2DFB; + +// XrSemanticLabelsSupportInfoFB extends XrSemanticLabelsFB +typedef struct XrSemanticLabelsSupportInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSemanticLabelsSupportFlagsFB flags; + const char* recognizedLabels; +} XrSemanticLabelsSupportInfoFB; + +typedef XrResult (XRAPI_PTR *PFN_xrGetSpaceBoundingBox2DFB)(XrSession session, XrSpace space, XrRect2Df* boundingBox2DOutput); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpaceBoundingBox3DFB)(XrSession session, XrSpace space, XrRect3DfFB* boundingBox3DOutput); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpaceSemanticLabelsFB)(XrSession session, XrSpace space, XrSemanticLabelsFB* semanticLabelsOutput); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpaceBoundary2DFB)(XrSession session, XrSpace space, XrBoundary2DFB* boundary2DOutput); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpaceRoomLayoutFB)(XrSession session, XrSpace space, XrRoomLayoutFB* roomLayoutOutput); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpaceBoundingBox2DFB( + XrSession session, + XrSpace space, + XrRect2Df* boundingBox2DOutput); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpaceBoundingBox3DFB( + XrSession session, + XrSpace space, + XrRect3DfFB* boundingBox3DOutput); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpaceSemanticLabelsFB( + XrSession session, + XrSpace space, + XrSemanticLabelsFB* semanticLabelsOutput); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpaceBoundary2DFB( + XrSession session, + XrSpace space, + XrBoundary2DFB* boundary2DOutput); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpaceRoomLayoutFB( + XrSession session, + XrSpace space, + XrRoomLayoutFB* roomLayoutOutput); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_palm_pose is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_palm_pose 1 +#define XR_EXT_palm_pose_SPEC_VERSION 3 +#define XR_EXT_PALM_POSE_EXTENSION_NAME "XR_EXT_palm_pose" + + +// XR_ALMALENCE_digital_lens_control is a preprocessor guard. Do not pass it to API calls. +#define XR_ALMALENCE_digital_lens_control 1 +#define XR_ALMALENCE_digital_lens_control_SPEC_VERSION 1 +#define XR_ALMALENCE_DIGITAL_LENS_CONTROL_EXTENSION_NAME "XR_ALMALENCE_digital_lens_control" +typedef XrFlags64 XrDigitalLensControlFlagsALMALENCE; + +// Flag bits for XrDigitalLensControlFlagsALMALENCE +static const XrDigitalLensControlFlagsALMALENCE XR_DIGITAL_LENS_CONTROL_PROCESSING_DISABLE_BIT_ALMALENCE = 0x00000001; + +typedef struct XrDigitalLensControlALMALENCE { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrDigitalLensControlFlagsALMALENCE flags; +} XrDigitalLensControlALMALENCE; + +typedef XrResult (XRAPI_PTR *PFN_xrSetDigitalLensControlALMALENCE)(XrSession session, const XrDigitalLensControlALMALENCE* digitalLensControl); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrSetDigitalLensControlALMALENCE( + XrSession session, + const XrDigitalLensControlALMALENCE* digitalLensControl); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_scene_capture is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_scene_capture 1 +#define XR_FB_scene_capture_SPEC_VERSION 1 +#define XR_FB_SCENE_CAPTURE_EXTENSION_NAME "XR_FB_scene_capture" +typedef struct XrEventDataSceneCaptureCompleteFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; + XrResult result; +} XrEventDataSceneCaptureCompleteFB; + +typedef struct XrSceneCaptureRequestInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t requestByteCount; + const char* request; +} XrSceneCaptureRequestInfoFB; + +typedef XrResult (XRAPI_PTR *PFN_xrRequestSceneCaptureFB)(XrSession session, const XrSceneCaptureRequestInfoFB* info, XrAsyncRequestIdFB* requestId); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrRequestSceneCaptureFB( + XrSession session, + const XrSceneCaptureRequestInfoFB* info, + XrAsyncRequestIdFB* requestId); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_spatial_entity_container is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_spatial_entity_container 1 +#define XR_FB_spatial_entity_container_SPEC_VERSION 2 +#define XR_FB_SPATIAL_ENTITY_CONTAINER_EXTENSION_NAME "XR_FB_spatial_entity_container" +typedef struct XrSpaceContainerFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t uuidCapacityInput; + uint32_t uuidCountOutput; + XrUuidEXT* uuids; +} XrSpaceContainerFB; + +typedef XrResult (XRAPI_PTR *PFN_xrGetSpaceContainerFB)(XrSession session, XrSpace space, XrSpaceContainerFB* spaceContainerOutput); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpaceContainerFB( + XrSession session, + XrSpace space, + XrSpaceContainerFB* spaceContainerOutput); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_foveation_eye_tracked is a preprocessor guard. Do not pass it to API calls. +#define XR_META_foveation_eye_tracked 1 +#define XR_FOVEATION_CENTER_SIZE_META 2 +#define XR_META_foveation_eye_tracked_SPEC_VERSION 1 +#define XR_META_FOVEATION_EYE_TRACKED_EXTENSION_NAME "XR_META_foveation_eye_tracked" +typedef XrFlags64 XrFoveationEyeTrackedProfileCreateFlagsMETA; + +// Flag bits for XrFoveationEyeTrackedProfileCreateFlagsMETA + +typedef XrFlags64 XrFoveationEyeTrackedStateFlagsMETA; + +// Flag bits for XrFoveationEyeTrackedStateFlagsMETA +static const XrFoveationEyeTrackedStateFlagsMETA XR_FOVEATION_EYE_TRACKED_STATE_VALID_BIT_META = 0x00000001; + +// XrFoveationEyeTrackedProfileCreateInfoMETA extends XrFoveationLevelProfileCreateInfoFB +typedef struct XrFoveationEyeTrackedProfileCreateInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrFoveationEyeTrackedProfileCreateFlagsMETA flags; +} XrFoveationEyeTrackedProfileCreateInfoMETA; + +typedef struct XrFoveationEyeTrackedStateMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrVector2f foveationCenter[XR_FOVEATION_CENTER_SIZE_META]; + XrFoveationEyeTrackedStateFlagsMETA flags; +} XrFoveationEyeTrackedStateMETA; + +// XrSystemFoveationEyeTrackedPropertiesMETA extends XrSystemProperties +typedef struct XrSystemFoveationEyeTrackedPropertiesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsFoveationEyeTracked; +} XrSystemFoveationEyeTrackedPropertiesMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrGetFoveationEyeTrackedStateMETA)(XrSession session, XrFoveationEyeTrackedStateMETA* foveationState); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetFoveationEyeTrackedStateMETA( + XrSession session, + XrFoveationEyeTrackedStateMETA* foveationState); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_face_tracking is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_face_tracking 1 + +#define XR_FACE_EXPRESSSION_SET_DEFAULT_FB XR_FACE_EXPRESSION_SET_DEFAULT_FB + +XR_DEFINE_HANDLE(XrFaceTrackerFB) +#define XR_FB_face_tracking_SPEC_VERSION 1 +#define XR_FB_FACE_TRACKING_EXTENSION_NAME "XR_FB_face_tracking" + +typedef enum XrFaceExpressionFB { + XR_FACE_EXPRESSION_BROW_LOWERER_L_FB = 0, + XR_FACE_EXPRESSION_BROW_LOWERER_R_FB = 1, + XR_FACE_EXPRESSION_CHEEK_PUFF_L_FB = 2, + XR_FACE_EXPRESSION_CHEEK_PUFF_R_FB = 3, + XR_FACE_EXPRESSION_CHEEK_RAISER_L_FB = 4, + XR_FACE_EXPRESSION_CHEEK_RAISER_R_FB = 5, + XR_FACE_EXPRESSION_CHEEK_SUCK_L_FB = 6, + XR_FACE_EXPRESSION_CHEEK_SUCK_R_FB = 7, + XR_FACE_EXPRESSION_CHIN_RAISER_B_FB = 8, + XR_FACE_EXPRESSION_CHIN_RAISER_T_FB = 9, + XR_FACE_EXPRESSION_DIMPLER_L_FB = 10, + XR_FACE_EXPRESSION_DIMPLER_R_FB = 11, + XR_FACE_EXPRESSION_EYES_CLOSED_L_FB = 12, + XR_FACE_EXPRESSION_EYES_CLOSED_R_FB = 13, + XR_FACE_EXPRESSION_EYES_LOOK_DOWN_L_FB = 14, + XR_FACE_EXPRESSION_EYES_LOOK_DOWN_R_FB = 15, + XR_FACE_EXPRESSION_EYES_LOOK_LEFT_L_FB = 16, + XR_FACE_EXPRESSION_EYES_LOOK_LEFT_R_FB = 17, + XR_FACE_EXPRESSION_EYES_LOOK_RIGHT_L_FB = 18, + XR_FACE_EXPRESSION_EYES_LOOK_RIGHT_R_FB = 19, + XR_FACE_EXPRESSION_EYES_LOOK_UP_L_FB = 20, + XR_FACE_EXPRESSION_EYES_LOOK_UP_R_FB = 21, + XR_FACE_EXPRESSION_INNER_BROW_RAISER_L_FB = 22, + XR_FACE_EXPRESSION_INNER_BROW_RAISER_R_FB = 23, + XR_FACE_EXPRESSION_JAW_DROP_FB = 24, + XR_FACE_EXPRESSION_JAW_SIDEWAYS_LEFT_FB = 25, + XR_FACE_EXPRESSION_JAW_SIDEWAYS_RIGHT_FB = 26, + XR_FACE_EXPRESSION_JAW_THRUST_FB = 27, + XR_FACE_EXPRESSION_LID_TIGHTENER_L_FB = 28, + XR_FACE_EXPRESSION_LID_TIGHTENER_R_FB = 29, + XR_FACE_EXPRESSION_LIP_CORNER_DEPRESSOR_L_FB = 30, + XR_FACE_EXPRESSION_LIP_CORNER_DEPRESSOR_R_FB = 31, + XR_FACE_EXPRESSION_LIP_CORNER_PULLER_L_FB = 32, + XR_FACE_EXPRESSION_LIP_CORNER_PULLER_R_FB = 33, + XR_FACE_EXPRESSION_LIP_FUNNELER_LB_FB = 34, + XR_FACE_EXPRESSION_LIP_FUNNELER_LT_FB = 35, + XR_FACE_EXPRESSION_LIP_FUNNELER_RB_FB = 36, + XR_FACE_EXPRESSION_LIP_FUNNELER_RT_FB = 37, + XR_FACE_EXPRESSION_LIP_PRESSOR_L_FB = 38, + XR_FACE_EXPRESSION_LIP_PRESSOR_R_FB = 39, + XR_FACE_EXPRESSION_LIP_PUCKER_L_FB = 40, + XR_FACE_EXPRESSION_LIP_PUCKER_R_FB = 41, + XR_FACE_EXPRESSION_LIP_STRETCHER_L_FB = 42, + XR_FACE_EXPRESSION_LIP_STRETCHER_R_FB = 43, + XR_FACE_EXPRESSION_LIP_SUCK_LB_FB = 44, + XR_FACE_EXPRESSION_LIP_SUCK_LT_FB = 45, + XR_FACE_EXPRESSION_LIP_SUCK_RB_FB = 46, + XR_FACE_EXPRESSION_LIP_SUCK_RT_FB = 47, + XR_FACE_EXPRESSION_LIP_TIGHTENER_L_FB = 48, + XR_FACE_EXPRESSION_LIP_TIGHTENER_R_FB = 49, + XR_FACE_EXPRESSION_LIPS_TOWARD_FB = 50, + XR_FACE_EXPRESSION_LOWER_LIP_DEPRESSOR_L_FB = 51, + XR_FACE_EXPRESSION_LOWER_LIP_DEPRESSOR_R_FB = 52, + XR_FACE_EXPRESSION_MOUTH_LEFT_FB = 53, + XR_FACE_EXPRESSION_MOUTH_RIGHT_FB = 54, + XR_FACE_EXPRESSION_NOSE_WRINKLER_L_FB = 55, + XR_FACE_EXPRESSION_NOSE_WRINKLER_R_FB = 56, + XR_FACE_EXPRESSION_OUTER_BROW_RAISER_L_FB = 57, + XR_FACE_EXPRESSION_OUTER_BROW_RAISER_R_FB = 58, + XR_FACE_EXPRESSION_UPPER_LID_RAISER_L_FB = 59, + XR_FACE_EXPRESSION_UPPER_LID_RAISER_R_FB = 60, + XR_FACE_EXPRESSION_UPPER_LIP_RAISER_L_FB = 61, + XR_FACE_EXPRESSION_UPPER_LIP_RAISER_R_FB = 62, + XR_FACE_EXPRESSION_COUNT_FB = 63, + XR_FACE_EXPRESSION_MAX_ENUM_FB = 0x7FFFFFFF +} XrFaceExpressionFB; + +typedef enum XrFaceExpressionSetFB { + XR_FACE_EXPRESSION_SET_DEFAULT_FB = 0, + XR_FACE_EXPRESSION_SET_MAX_ENUM_FB = 0x7FFFFFFF +} XrFaceExpressionSetFB; + +typedef enum XrFaceConfidenceFB { + XR_FACE_CONFIDENCE_LOWER_FACE_FB = 0, + XR_FACE_CONFIDENCE_UPPER_FACE_FB = 1, + XR_FACE_CONFIDENCE_COUNT_FB = 2, + XR_FACE_CONFIDENCE_MAX_ENUM_FB = 0x7FFFFFFF +} XrFaceConfidenceFB; +// XrSystemFaceTrackingPropertiesFB extends XrSystemProperties +typedef struct XrSystemFaceTrackingPropertiesFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsFaceTracking; +} XrSystemFaceTrackingPropertiesFB; + +typedef struct XrFaceTrackerCreateInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrFaceExpressionSetFB faceExpressionSet; +} XrFaceTrackerCreateInfoFB; + +typedef struct XrFaceExpressionInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrTime time; +} XrFaceExpressionInfoFB; + +typedef struct XrFaceExpressionStatusFB { + XrBool32 isValid; + XrBool32 isEyeFollowingBlendshapesValid; +} XrFaceExpressionStatusFB; + +typedef struct XrFaceExpressionWeightsFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t weightCount; + float* weights; + uint32_t confidenceCount; + float* confidences; + XrFaceExpressionStatusFB status; + XrTime time; +} XrFaceExpressionWeightsFB; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateFaceTrackerFB)(XrSession session, const XrFaceTrackerCreateInfoFB* createInfo, XrFaceTrackerFB* faceTracker); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyFaceTrackerFB)(XrFaceTrackerFB faceTracker); +typedef XrResult (XRAPI_PTR *PFN_xrGetFaceExpressionWeightsFB)(XrFaceTrackerFB faceTracker, const XrFaceExpressionInfoFB* expressionInfo, XrFaceExpressionWeightsFB* expressionWeights); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateFaceTrackerFB( + XrSession session, + const XrFaceTrackerCreateInfoFB* createInfo, + XrFaceTrackerFB* faceTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyFaceTrackerFB( + XrFaceTrackerFB faceTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetFaceExpressionWeightsFB( + XrFaceTrackerFB faceTracker, + const XrFaceExpressionInfoFB* expressionInfo, + XrFaceExpressionWeightsFB* expressionWeights); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_eye_tracking_social is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_eye_tracking_social 1 +XR_DEFINE_HANDLE(XrEyeTrackerFB) +#define XR_FB_eye_tracking_social_SPEC_VERSION 1 +#define XR_FB_EYE_TRACKING_SOCIAL_EXTENSION_NAME "XR_FB_eye_tracking_social" + +typedef enum XrEyePositionFB { + XR_EYE_POSITION_LEFT_FB = 0, + XR_EYE_POSITION_RIGHT_FB = 1, + XR_EYE_POSITION_COUNT_FB = 2, + XR_EYE_POSITION_MAX_ENUM_FB = 0x7FFFFFFF +} XrEyePositionFB; +typedef struct XrEyeGazeFB { + XrBool32 isValid; + XrPosef gazePose; + float gazeConfidence; +} XrEyeGazeFB; + +typedef struct XrEyeTrackerCreateInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrEyeTrackerCreateInfoFB; + +typedef struct XrEyeGazesInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace baseSpace; + XrTime time; +} XrEyeGazesInfoFB; + +// XrSystemEyeTrackingPropertiesFB extends XrSystemProperties +typedef struct XrSystemEyeTrackingPropertiesFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsEyeTracking; +} XrSystemEyeTrackingPropertiesFB; + +typedef struct XrEyeGazesFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrEyeGazeFB gaze[XR_EYE_POSITION_COUNT_FB]; + XrTime time; +} XrEyeGazesFB; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateEyeTrackerFB)(XrSession session, const XrEyeTrackerCreateInfoFB* createInfo, XrEyeTrackerFB* eyeTracker); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyEyeTrackerFB)(XrEyeTrackerFB eyeTracker); +typedef XrResult (XRAPI_PTR *PFN_xrGetEyeGazesFB)(XrEyeTrackerFB eyeTracker, const XrEyeGazesInfoFB* gazeInfo, XrEyeGazesFB* eyeGazes); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateEyeTrackerFB( + XrSession session, + const XrEyeTrackerCreateInfoFB* createInfo, + XrEyeTrackerFB* eyeTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyEyeTrackerFB( + XrEyeTrackerFB eyeTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetEyeGazesFB( + XrEyeTrackerFB eyeTracker, + const XrEyeGazesInfoFB* gazeInfo, + XrEyeGazesFB* eyeGazes); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_passthrough_keyboard_hands is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_passthrough_keyboard_hands 1 +#define XR_FB_passthrough_keyboard_hands_SPEC_VERSION 2 +#define XR_FB_PASSTHROUGH_KEYBOARD_HANDS_EXTENSION_NAME "XR_FB_passthrough_keyboard_hands" +typedef struct XrPassthroughKeyboardHandsIntensityFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + float leftHandIntensity; + float rightHandIntensity; +} XrPassthroughKeyboardHandsIntensityFB; + +typedef XrResult (XRAPI_PTR *PFN_xrPassthroughLayerSetKeyboardHandsIntensityFB)(XrPassthroughLayerFB layer, const XrPassthroughKeyboardHandsIntensityFB* intensity); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrPassthroughLayerSetKeyboardHandsIntensityFB( + XrPassthroughLayerFB layer, + const XrPassthroughKeyboardHandsIntensityFB* intensity); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_composition_layer_settings is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_composition_layer_settings 1 +#define XR_FB_composition_layer_settings_SPEC_VERSION 1 +#define XR_FB_COMPOSITION_LAYER_SETTINGS_EXTENSION_NAME "XR_FB_composition_layer_settings" +typedef XrFlags64 XrCompositionLayerSettingsFlagsFB; + +// Flag bits for XrCompositionLayerSettingsFlagsFB +static const XrCompositionLayerSettingsFlagsFB XR_COMPOSITION_LAYER_SETTINGS_NORMAL_SUPER_SAMPLING_BIT_FB = 0x00000001; +static const XrCompositionLayerSettingsFlagsFB XR_COMPOSITION_LAYER_SETTINGS_QUALITY_SUPER_SAMPLING_BIT_FB = 0x00000002; +static const XrCompositionLayerSettingsFlagsFB XR_COMPOSITION_LAYER_SETTINGS_NORMAL_SHARPENING_BIT_FB = 0x00000004; +static const XrCompositionLayerSettingsFlagsFB XR_COMPOSITION_LAYER_SETTINGS_QUALITY_SHARPENING_BIT_FB = 0x00000008; +static const XrCompositionLayerSettingsFlagsFB XR_COMPOSITION_LAYER_SETTINGS_AUTO_LAYER_FILTER_BIT_META = 0x00000020; + +// XrCompositionLayerSettingsFB extends XrCompositionLayerBaseHeader +typedef struct XrCompositionLayerSettingsFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCompositionLayerSettingsFlagsFB layerFlags; +} XrCompositionLayerSettingsFB; + + + +// XR_FB_touch_controller_proximity is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_touch_controller_proximity 1 +#define XR_FB_touch_controller_proximity_SPEC_VERSION 1 +#define XR_FB_TOUCH_CONTROLLER_PROXIMITY_EXTENSION_NAME "XR_FB_touch_controller_proximity" + + +// XR_FB_haptic_pcm is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_haptic_pcm 1 + +#define XR_MAX_HAPTIC_PCM_BUFFER_SIZE_FB 4000 + +#define XR_FB_haptic_pcm_SPEC_VERSION 1 +#define XR_FB_HAPTIC_PCM_EXTENSION_NAME "XR_FB_haptic_pcm" +typedef struct XrHapticPcmVibrationFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t bufferSize; + const float* buffer; + float sampleRate; + XrBool32 append; + uint32_t* samplesConsumed; +} XrHapticPcmVibrationFB; + +typedef struct XrDevicePcmSampleRateStateFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + float sampleRate; +} XrDevicePcmSampleRateStateFB; + +typedef XrDevicePcmSampleRateStateFB XrDevicePcmSampleRateGetInfoFB; + +typedef XrResult (XRAPI_PTR *PFN_xrGetDeviceSampleRateFB)(XrSession session, const XrHapticActionInfo* hapticActionInfo, XrDevicePcmSampleRateGetInfoFB* deviceSampleRate); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetDeviceSampleRateFB( + XrSession session, + const XrHapticActionInfo* hapticActionInfo, + XrDevicePcmSampleRateGetInfoFB* deviceSampleRate); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_frame_synthesis is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_frame_synthesis 1 +#define XR_EXT_frame_synthesis_SPEC_VERSION 1 +#define XR_EXT_FRAME_SYNTHESIS_EXTENSION_NAME "XR_EXT_frame_synthesis" +typedef XrFlags64 XrFrameSynthesisInfoFlagsEXT; + +// Flag bits for XrFrameSynthesisInfoFlagsEXT +static const XrFrameSynthesisInfoFlagsEXT XR_FRAME_SYNTHESIS_INFO_USE_2D_MOTION_VECTOR_BIT_EXT = 0x00000001; +static const XrFrameSynthesisInfoFlagsEXT XR_FRAME_SYNTHESIS_INFO_REQUEST_RELAXED_FRAME_INTERVAL_BIT_EXT = 0x00000002; + +// XrFrameSynthesisInfoEXT extends XrCompositionLayerProjectionView +typedef struct XrFrameSynthesisInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrFrameSynthesisInfoFlagsEXT layerFlags; + XrSwapchainSubImage motionVectorSubImage; + XrVector4f motionVectorScale; + XrVector4f motionVectorOffset; + XrPosef appSpaceDeltaPose; + XrSwapchainSubImage depthSubImage; + float minDepth; + float maxDepth; + float nearZ; + float farZ; +} XrFrameSynthesisInfoEXT; + +// XrFrameSynthesisConfigViewEXT extends XrViewConfigurationView +typedef struct XrFrameSynthesisConfigViewEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t recommendedMotionVectorImageRectWidth; + uint32_t recommendedMotionVectorImageRectHeight; +} XrFrameSynthesisConfigViewEXT; + + + +// XR_FB_composition_layer_depth_test is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_composition_layer_depth_test 1 +#define XR_FB_composition_layer_depth_test_SPEC_VERSION 1 +#define XR_FB_COMPOSITION_LAYER_DEPTH_TEST_EXTENSION_NAME "XR_FB_composition_layer_depth_test" + +typedef enum XrCompareOpFB { + XR_COMPARE_OP_NEVER_FB = 0, + XR_COMPARE_OP_LESS_FB = 1, + XR_COMPARE_OP_EQUAL_FB = 2, + XR_COMPARE_OP_LESS_OR_EQUAL_FB = 3, + XR_COMPARE_OP_GREATER_FB = 4, + XR_COMPARE_OP_NOT_EQUAL_FB = 5, + XR_COMPARE_OP_GREATER_OR_EQUAL_FB = 6, + XR_COMPARE_OP_ALWAYS_FB = 7, + XR_COMPARE_OP_MAX_ENUM_FB = 0x7FFFFFFF +} XrCompareOpFB; +// XrCompositionLayerDepthTestFB extends XrCompositionLayerBaseHeader +typedef struct XrCompositionLayerDepthTestFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 depthMask; + XrCompareOpFB compareOp; +} XrCompositionLayerDepthTestFB; + + + +// XR_META_local_dimming is a preprocessor guard. Do not pass it to API calls. +#define XR_META_local_dimming 1 +#define XR_META_local_dimming_SPEC_VERSION 1 +#define XR_META_LOCAL_DIMMING_EXTENSION_NAME "XR_META_local_dimming" + +typedef enum XrLocalDimmingModeMETA { + XR_LOCAL_DIMMING_MODE_OFF_META = 0, + XR_LOCAL_DIMMING_MODE_ON_META = 1, + XR_LOCAL_DIMMING_MODE_MAX_ENUM_META = 0x7FFFFFFF +} XrLocalDimmingModeMETA; +// XrLocalDimmingFrameEndInfoMETA extends XrFrameEndInfo +typedef struct XrLocalDimmingFrameEndInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrLocalDimmingModeMETA localDimmingMode; +} XrLocalDimmingFrameEndInfoMETA; + + + +// XR_META_passthrough_preferences is a preprocessor guard. Do not pass it to API calls. +#define XR_META_passthrough_preferences 1 +#define XR_META_passthrough_preferences_SPEC_VERSION 1 +#define XR_META_PASSTHROUGH_PREFERENCES_EXTENSION_NAME "XR_META_passthrough_preferences" +typedef XrFlags64 XrPassthroughPreferenceFlagsMETA; + +// Flag bits for XrPassthroughPreferenceFlagsMETA +static const XrPassthroughPreferenceFlagsMETA XR_PASSTHROUGH_PREFERENCE_DEFAULT_TO_ACTIVE_BIT_META = 0x00000001; + +typedef struct XrPassthroughPreferencesMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPassthroughPreferenceFlagsMETA flags; +} XrPassthroughPreferencesMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrGetPassthroughPreferencesMETA)(XrSession session, XrPassthroughPreferencesMETA* preferences); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetPassthroughPreferencesMETA( + XrSession session, + XrPassthroughPreferencesMETA* preferences); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_virtual_keyboard is a preprocessor guard. Do not pass it to API calls. +#define XR_META_virtual_keyboard 1 +XR_DEFINE_HANDLE(XrVirtualKeyboardMETA) +#define XR_MAX_VIRTUAL_KEYBOARD_COMMIT_TEXT_SIZE_META 3992 +#define XR_META_virtual_keyboard_SPEC_VERSION 1 +#define XR_META_VIRTUAL_KEYBOARD_EXTENSION_NAME "XR_META_virtual_keyboard" + +typedef enum XrVirtualKeyboardLocationTypeMETA { + XR_VIRTUAL_KEYBOARD_LOCATION_TYPE_CUSTOM_META = 0, + XR_VIRTUAL_KEYBOARD_LOCATION_TYPE_FAR_META = 1, + XR_VIRTUAL_KEYBOARD_LOCATION_TYPE_DIRECT_META = 2, + XR_VIRTUAL_KEYBOARD_LOCATION_TYPE_MAX_ENUM_META = 0x7FFFFFFF +} XrVirtualKeyboardLocationTypeMETA; + +typedef enum XrVirtualKeyboardInputSourceMETA { + XR_VIRTUAL_KEYBOARD_INPUT_SOURCE_CONTROLLER_RAY_LEFT_META = 1, + XR_VIRTUAL_KEYBOARD_INPUT_SOURCE_CONTROLLER_RAY_RIGHT_META = 2, + XR_VIRTUAL_KEYBOARD_INPUT_SOURCE_HAND_RAY_LEFT_META = 3, + XR_VIRTUAL_KEYBOARD_INPUT_SOURCE_HAND_RAY_RIGHT_META = 4, + XR_VIRTUAL_KEYBOARD_INPUT_SOURCE_CONTROLLER_DIRECT_LEFT_META = 5, + XR_VIRTUAL_KEYBOARD_INPUT_SOURCE_CONTROLLER_DIRECT_RIGHT_META = 6, + XR_VIRTUAL_KEYBOARD_INPUT_SOURCE_HAND_DIRECT_INDEX_TIP_LEFT_META = 7, + XR_VIRTUAL_KEYBOARD_INPUT_SOURCE_HAND_DIRECT_INDEX_TIP_RIGHT_META = 8, + XR_VIRTUAL_KEYBOARD_INPUT_SOURCE_MAX_ENUM_META = 0x7FFFFFFF +} XrVirtualKeyboardInputSourceMETA; +typedef XrFlags64 XrVirtualKeyboardInputStateFlagsMETA; + +// Flag bits for XrVirtualKeyboardInputStateFlagsMETA +static const XrVirtualKeyboardInputStateFlagsMETA XR_VIRTUAL_KEYBOARD_INPUT_STATE_PRESSED_BIT_META = 0x00000001; + +// XrSystemVirtualKeyboardPropertiesMETA extends XrSystemProperties +typedef struct XrSystemVirtualKeyboardPropertiesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsVirtualKeyboard; +} XrSystemVirtualKeyboardPropertiesMETA; + +typedef struct XrVirtualKeyboardCreateInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrVirtualKeyboardCreateInfoMETA; + +typedef struct XrVirtualKeyboardSpaceCreateInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrVirtualKeyboardLocationTypeMETA locationType; + XrSpace space; + XrPosef poseInSpace; +} XrVirtualKeyboardSpaceCreateInfoMETA; + +typedef struct XrVirtualKeyboardLocationInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrVirtualKeyboardLocationTypeMETA locationType; + XrSpace space; + XrPosef poseInSpace; + float scale; +} XrVirtualKeyboardLocationInfoMETA; + +typedef struct XrVirtualKeyboardModelVisibilitySetInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 visible; +} XrVirtualKeyboardModelVisibilitySetInfoMETA; + +typedef struct XrVirtualKeyboardAnimationStateMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + int32_t animationIndex; + float fraction; +} XrVirtualKeyboardAnimationStateMETA; + +typedef struct XrVirtualKeyboardModelAnimationStatesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t stateCapacityInput; + uint32_t stateCountOutput; + XrVirtualKeyboardAnimationStateMETA* states; +} XrVirtualKeyboardModelAnimationStatesMETA; + +typedef struct XrVirtualKeyboardTextureDataMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t textureWidth; + uint32_t textureHeight; + uint32_t bufferCapacityInput; + uint32_t bufferCountOutput; + uint8_t* buffer; +} XrVirtualKeyboardTextureDataMETA; + +typedef struct XrVirtualKeyboardInputInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrVirtualKeyboardInputSourceMETA inputSource; + XrSpace inputSpace; + XrPosef inputPoseInSpace; + XrVirtualKeyboardInputStateFlagsMETA inputState; +} XrVirtualKeyboardInputInfoMETA; + +typedef struct XrVirtualKeyboardTextContextChangeInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + const char* textContext; +} XrVirtualKeyboardTextContextChangeInfoMETA; + +typedef struct XrEventDataVirtualKeyboardCommitTextMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrVirtualKeyboardMETA keyboard; + char text[XR_MAX_VIRTUAL_KEYBOARD_COMMIT_TEXT_SIZE_META]; +} XrEventDataVirtualKeyboardCommitTextMETA; + +typedef struct XrEventDataVirtualKeyboardBackspaceMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrVirtualKeyboardMETA keyboard; +} XrEventDataVirtualKeyboardBackspaceMETA; + +typedef struct XrEventDataVirtualKeyboardEnterMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrVirtualKeyboardMETA keyboard; +} XrEventDataVirtualKeyboardEnterMETA; + +typedef struct XrEventDataVirtualKeyboardShownMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrVirtualKeyboardMETA keyboard; +} XrEventDataVirtualKeyboardShownMETA; + +typedef struct XrEventDataVirtualKeyboardHiddenMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrVirtualKeyboardMETA keyboard; +} XrEventDataVirtualKeyboardHiddenMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateVirtualKeyboardMETA)(XrSession session, const XrVirtualKeyboardCreateInfoMETA* createInfo, XrVirtualKeyboardMETA* keyboard); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyVirtualKeyboardMETA)(XrVirtualKeyboardMETA keyboard); +typedef XrResult (XRAPI_PTR *PFN_xrCreateVirtualKeyboardSpaceMETA)(XrSession session, XrVirtualKeyboardMETA keyboard, const XrVirtualKeyboardSpaceCreateInfoMETA* createInfo, XrSpace* keyboardSpace); +typedef XrResult (XRAPI_PTR *PFN_xrSuggestVirtualKeyboardLocationMETA)(XrVirtualKeyboardMETA keyboard, const XrVirtualKeyboardLocationInfoMETA* locationInfo); +typedef XrResult (XRAPI_PTR *PFN_xrGetVirtualKeyboardScaleMETA)(XrVirtualKeyboardMETA keyboard, float* scale); +typedef XrResult (XRAPI_PTR *PFN_xrSetVirtualKeyboardModelVisibilityMETA)(XrVirtualKeyboardMETA keyboard, const XrVirtualKeyboardModelVisibilitySetInfoMETA* modelVisibility); +typedef XrResult (XRAPI_PTR *PFN_xrGetVirtualKeyboardModelAnimationStatesMETA)(XrVirtualKeyboardMETA keyboard, XrVirtualKeyboardModelAnimationStatesMETA* animationStates); +typedef XrResult (XRAPI_PTR *PFN_xrGetVirtualKeyboardDirtyTexturesMETA)(XrVirtualKeyboardMETA keyboard, uint32_t textureIdCapacityInput, uint32_t* textureIdCountOutput, uint64_t* textureIds); +typedef XrResult (XRAPI_PTR *PFN_xrGetVirtualKeyboardTextureDataMETA)(XrVirtualKeyboardMETA keyboard, uint64_t textureId, XrVirtualKeyboardTextureDataMETA* textureData); +typedef XrResult (XRAPI_PTR *PFN_xrSendVirtualKeyboardInputMETA)(XrVirtualKeyboardMETA keyboard, const XrVirtualKeyboardInputInfoMETA* info, XrPosef* interactorRootPose); +typedef XrResult (XRAPI_PTR *PFN_xrChangeVirtualKeyboardTextContextMETA)(XrVirtualKeyboardMETA keyboard, const XrVirtualKeyboardTextContextChangeInfoMETA* changeInfo); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateVirtualKeyboardMETA( + XrSession session, + const XrVirtualKeyboardCreateInfoMETA* createInfo, + XrVirtualKeyboardMETA* keyboard); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyVirtualKeyboardMETA( + XrVirtualKeyboardMETA keyboard); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateVirtualKeyboardSpaceMETA( + XrSession session, + XrVirtualKeyboardMETA keyboard, + const XrVirtualKeyboardSpaceCreateInfoMETA* createInfo, + XrSpace* keyboardSpace); + +XRAPI_ATTR XrResult XRAPI_CALL xrSuggestVirtualKeyboardLocationMETA( + XrVirtualKeyboardMETA keyboard, + const XrVirtualKeyboardLocationInfoMETA* locationInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetVirtualKeyboardScaleMETA( + XrVirtualKeyboardMETA keyboard, + float* scale); + +XRAPI_ATTR XrResult XRAPI_CALL xrSetVirtualKeyboardModelVisibilityMETA( + XrVirtualKeyboardMETA keyboard, + const XrVirtualKeyboardModelVisibilitySetInfoMETA* modelVisibility); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetVirtualKeyboardModelAnimationStatesMETA( + XrVirtualKeyboardMETA keyboard, + XrVirtualKeyboardModelAnimationStatesMETA* animationStates); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetVirtualKeyboardDirtyTexturesMETA( + XrVirtualKeyboardMETA keyboard, + uint32_t textureIdCapacityInput, + uint32_t* textureIdCountOutput, + uint64_t* textureIds); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetVirtualKeyboardTextureDataMETA( + XrVirtualKeyboardMETA keyboard, + uint64_t textureId, + XrVirtualKeyboardTextureDataMETA* textureData); + +XRAPI_ATTR XrResult XRAPI_CALL xrSendVirtualKeyboardInputMETA( + XrVirtualKeyboardMETA keyboard, + const XrVirtualKeyboardInputInfoMETA* info, + XrPosef* interactorRootPose); + +XRAPI_ATTR XrResult XRAPI_CALL xrChangeVirtualKeyboardTextContextMETA( + XrVirtualKeyboardMETA keyboard, + const XrVirtualKeyboardTextContextChangeInfoMETA* changeInfo); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_OCULUS_external_camera is a preprocessor guard. Do not pass it to API calls. +#define XR_OCULUS_external_camera 1 +#define XR_MAX_EXTERNAL_CAMERA_NAME_SIZE_OCULUS 32 +#define XR_OCULUS_external_camera_SPEC_VERSION 1 +#define XR_OCULUS_EXTERNAL_CAMERA_EXTENSION_NAME "XR_OCULUS_external_camera" + +typedef enum XrExternalCameraAttachedToDeviceOCULUS { + XR_EXTERNAL_CAMERA_ATTACHED_TO_DEVICE_NONE_OCULUS = 0, + XR_EXTERNAL_CAMERA_ATTACHED_TO_DEVICE_HMD_OCULUS = 1, + XR_EXTERNAL_CAMERA_ATTACHED_TO_DEVICE_LTOUCH_OCULUS = 2, + XR_EXTERNAL_CAMERA_ATTACHED_TO_DEVICE_RTOUCH_OCULUS = 3, + XR_EXTERNAL_CAMERA_ATTACHED_TO_DEVICE_MAX_ENUM_OCULUS = 0x7FFFFFFF +} XrExternalCameraAttachedToDeviceOCULUS; +typedef XrFlags64 XrExternalCameraStatusFlagsOCULUS; + +// Flag bits for XrExternalCameraStatusFlagsOCULUS +static const XrExternalCameraStatusFlagsOCULUS XR_EXTERNAL_CAMERA_STATUS_CONNECTED_BIT_OCULUS = 0x00000001; +static const XrExternalCameraStatusFlagsOCULUS XR_EXTERNAL_CAMERA_STATUS_CALIBRATING_BIT_OCULUS = 0x00000002; +static const XrExternalCameraStatusFlagsOCULUS XR_EXTERNAL_CAMERA_STATUS_CALIBRATION_FAILED_BIT_OCULUS = 0x00000004; +static const XrExternalCameraStatusFlagsOCULUS XR_EXTERNAL_CAMERA_STATUS_CALIBRATED_BIT_OCULUS = 0x00000008; +static const XrExternalCameraStatusFlagsOCULUS XR_EXTERNAL_CAMERA_STATUS_CAPTURING_BIT_OCULUS = 0x00000010; + +typedef struct XrExternalCameraIntrinsicsOCULUS { + XrTime lastChangeTime; + XrFovf fov; + float virtualNearPlaneDistance; + float virtualFarPlaneDistance; + XrExtent2Di imageSensorPixelResolution; +} XrExternalCameraIntrinsicsOCULUS; + +typedef struct XrExternalCameraExtrinsicsOCULUS { + XrTime lastChangeTime; + XrExternalCameraStatusFlagsOCULUS cameraStatusFlags; + XrExternalCameraAttachedToDeviceOCULUS attachedToDevice; + XrPosef relativePose; +} XrExternalCameraExtrinsicsOCULUS; + +typedef struct XrExternalCameraOCULUS { + XrStructureType type; + const void* XR_MAY_ALIAS next; + char name[XR_MAX_EXTERNAL_CAMERA_NAME_SIZE_OCULUS]; + XrExternalCameraIntrinsicsOCULUS intrinsics; + XrExternalCameraExtrinsicsOCULUS extrinsics; +} XrExternalCameraOCULUS; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateExternalCamerasOCULUS)(XrSession session, uint32_t cameraCapacityInput, uint32_t* cameraCountOutput, XrExternalCameraOCULUS* cameras); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateExternalCamerasOCULUS( + XrSession session, + uint32_t cameraCapacityInput, + uint32_t* cameraCountOutput, + XrExternalCameraOCULUS* cameras); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_performance_metrics is a preprocessor guard. Do not pass it to API calls. +#define XR_META_performance_metrics 1 +#define XR_META_performance_metrics_SPEC_VERSION 2 +#define XR_META_PERFORMANCE_METRICS_EXTENSION_NAME "XR_META_performance_metrics" + +typedef enum XrPerformanceMetricsCounterUnitMETA { + XR_PERFORMANCE_METRICS_COUNTER_UNIT_GENERIC_META = 0, + XR_PERFORMANCE_METRICS_COUNTER_UNIT_PERCENTAGE_META = 1, + XR_PERFORMANCE_METRICS_COUNTER_UNIT_MILLISECONDS_META = 2, + XR_PERFORMANCE_METRICS_COUNTER_UNIT_BYTES_META = 3, + XR_PERFORMANCE_METRICS_COUNTER_UNIT_HERTZ_META = 4, + XR_PERFORMANCE_METRICS_COUNTER_UNIT_MAX_ENUM_META = 0x7FFFFFFF +} XrPerformanceMetricsCounterUnitMETA; +typedef XrFlags64 XrPerformanceMetricsCounterFlagsMETA; + +// Flag bits for XrPerformanceMetricsCounterFlagsMETA +static const XrPerformanceMetricsCounterFlagsMETA XR_PERFORMANCE_METRICS_COUNTER_ANY_VALUE_VALID_BIT_META = 0x00000001; +static const XrPerformanceMetricsCounterFlagsMETA XR_PERFORMANCE_METRICS_COUNTER_UINT_VALUE_VALID_BIT_META = 0x00000002; +static const XrPerformanceMetricsCounterFlagsMETA XR_PERFORMANCE_METRICS_COUNTER_FLOAT_VALUE_VALID_BIT_META = 0x00000004; + +typedef struct XrPerformanceMetricsStateMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 enabled; +} XrPerformanceMetricsStateMETA; + +typedef struct XrPerformanceMetricsCounterMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPerformanceMetricsCounterFlagsMETA counterFlags; + XrPerformanceMetricsCounterUnitMETA counterUnit; + uint32_t uintValue; + float floatValue; +} XrPerformanceMetricsCounterMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumeratePerformanceMetricsCounterPathsMETA)(XrInstance instance, uint32_t counterPathCapacityInput, uint32_t* counterPathCountOutput, XrPath* counterPaths); +typedef XrResult (XRAPI_PTR *PFN_xrSetPerformanceMetricsStateMETA)(XrSession session, const XrPerformanceMetricsStateMETA* state); +typedef XrResult (XRAPI_PTR *PFN_xrGetPerformanceMetricsStateMETA)(XrSession session, XrPerformanceMetricsStateMETA* state); +typedef XrResult (XRAPI_PTR *PFN_xrQueryPerformanceMetricsCounterMETA)(XrSession session, XrPath counterPath, XrPerformanceMetricsCounterMETA* counter); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumeratePerformanceMetricsCounterPathsMETA( + XrInstance instance, + uint32_t counterPathCapacityInput, + uint32_t* counterPathCountOutput, + XrPath* counterPaths); + +XRAPI_ATTR XrResult XRAPI_CALL xrSetPerformanceMetricsStateMETA( + XrSession session, + const XrPerformanceMetricsStateMETA* state); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetPerformanceMetricsStateMETA( + XrSession session, + XrPerformanceMetricsStateMETA* state); + +XRAPI_ATTR XrResult XRAPI_CALL xrQueryPerformanceMetricsCounterMETA( + XrSession session, + XrPath counterPath, + XrPerformanceMetricsCounterMETA* counter); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_spatial_entity_storage_batch is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_spatial_entity_storage_batch 1 +#define XR_FB_spatial_entity_storage_batch_SPEC_VERSION 1 +#define XR_FB_SPATIAL_ENTITY_STORAGE_BATCH_EXTENSION_NAME "XR_FB_spatial_entity_storage_batch" +typedef struct XrSpaceListSaveInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t spaceCount; + XrSpace* spaces; + XrSpaceStorageLocationFB location; +} XrSpaceListSaveInfoFB; + +typedef struct XrEventDataSpaceListSaveCompleteFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; + XrResult result; +} XrEventDataSpaceListSaveCompleteFB; + +typedef XrResult (XRAPI_PTR *PFN_xrSaveSpaceListFB)(XrSession session, const XrSpaceListSaveInfoFB* info, XrAsyncRequestIdFB* requestId); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrSaveSpaceListFB( + XrSession session, + const XrSpaceListSaveInfoFB* info, + XrAsyncRequestIdFB* requestId); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_detached_controllers is a preprocessor guard. Do not pass it to API calls. +#define XR_META_detached_controllers 1 +#define XR_META_detached_controllers_SPEC_VERSION 1 +#define XR_META_DETACHED_CONTROLLERS_EXTENSION_NAME "XR_META_detached_controllers" + + +// XR_FB_spatial_entity_user is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_spatial_entity_user 1 +typedef uint64_t XrSpaceUserIdFB; +#define XR_FB_spatial_entity_user_SPEC_VERSION 1 +#define XR_FB_SPATIAL_ENTITY_USER_EXTENSION_NAME "XR_FB_spatial_entity_user" +typedef struct XrSpaceUserCreateInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpaceUserIdFB userId; +} XrSpaceUserCreateInfoFB; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpaceUserFB)(XrSession session, const XrSpaceUserCreateInfoFB* info, XrSpaceUserFB* user); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpaceUserIdFB)(XrSpaceUserFB user, XrSpaceUserIdFB* userId); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySpaceUserFB)(XrSpaceUserFB user); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpaceUserFB( + XrSession session, + const XrSpaceUserCreateInfoFB* info, + XrSpaceUserFB* user); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpaceUserIdFB( + XrSpaceUserFB user, + XrSpaceUserIdFB* userId); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySpaceUserFB( + XrSpaceUserFB user); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_headset_id is a preprocessor guard. Do not pass it to API calls. +#define XR_META_headset_id 1 +#define XR_META_headset_id_SPEC_VERSION 2 +#define XR_META_HEADSET_ID_EXTENSION_NAME "XR_META_headset_id" +// XrSystemHeadsetIdPropertiesMETA extends XrSystemProperties +typedef struct XrSystemHeadsetIdPropertiesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrUuidEXT id; +} XrSystemHeadsetIdPropertiesMETA; + + + +// XR_META_spatial_entity_discovery is a preprocessor guard. Do not pass it to API calls. +#define XR_META_spatial_entity_discovery 1 +#define XR_META_spatial_entity_discovery_SPEC_VERSION 1 +#define XR_META_SPATIAL_ENTITY_DISCOVERY_EXTENSION_NAME "XR_META_spatial_entity_discovery" +// XrSystemSpaceDiscoveryPropertiesMETA extends XrSystemProperties +typedef struct XrSystemSpaceDiscoveryPropertiesMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 supportsSpaceDiscovery; +} XrSystemSpaceDiscoveryPropertiesMETA; + +typedef struct XR_MAY_ALIAS XrSpaceFilterBaseHeaderMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSpaceFilterBaseHeaderMETA; + +typedef struct XrSpaceDiscoveryInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t filterCount; + const XrSpaceFilterBaseHeaderMETA* const * filters; +} XrSpaceDiscoveryInfoMETA; + +typedef struct XrSpaceFilterUuidMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t uuidCount; + const XrUuidEXT* uuids; +} XrSpaceFilterUuidMETA; + +typedef struct XrSpaceFilterComponentMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpaceComponentTypeFB componentType; +} XrSpaceFilterComponentMETA; + +typedef struct XrSpaceDiscoveryResultMETA { + XrSpace space; + XrUuidEXT uuid; +} XrSpaceDiscoveryResultMETA; + +typedef struct XrSpaceDiscoveryResultsMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t resultCapacityInput; + uint32_t resultCountOutput; + XrSpaceDiscoveryResultMETA* results; +} XrSpaceDiscoveryResultsMETA; + +typedef struct XrEventDataSpaceDiscoveryResultsAvailableMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; +} XrEventDataSpaceDiscoveryResultsAvailableMETA; + +typedef struct XrEventDataSpaceDiscoveryCompleteMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; + XrResult result; +} XrEventDataSpaceDiscoveryCompleteMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrDiscoverSpacesMETA)(XrSession session, const XrSpaceDiscoveryInfoMETA* info, XrAsyncRequestIdFB* requestId); +typedef XrResult (XRAPI_PTR *PFN_xrRetrieveSpaceDiscoveryResultsMETA)(XrSession session, XrAsyncRequestIdFB requestId, XrSpaceDiscoveryResultsMETA* results); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrDiscoverSpacesMETA( + XrSession session, + const XrSpaceDiscoveryInfoMETA* info, + XrAsyncRequestIdFB* requestId); + +XRAPI_ATTR XrResult XRAPI_CALL xrRetrieveSpaceDiscoveryResultsMETA( + XrSession session, + XrAsyncRequestIdFB requestId, + XrSpaceDiscoveryResultsMETA* results); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_hand_tracking_microgestures is a preprocessor guard. Do not pass it to API calls. +#define XR_META_hand_tracking_microgestures 1 +#define XR_META_hand_tracking_microgestures_SPEC_VERSION 1 +#define XR_META_HAND_TRACKING_MICROGESTURES_EXTENSION_NAME "XR_META_hand_tracking_microgestures" + + +// XR_META_recommended_layer_resolution is a preprocessor guard. Do not pass it to API calls. +#define XR_META_recommended_layer_resolution 1 +#define XR_META_recommended_layer_resolution_SPEC_VERSION 1 +#define XR_META_RECOMMENDED_LAYER_RESOLUTION_EXTENSION_NAME "XR_META_recommended_layer_resolution" +typedef struct XrRecommendedLayerResolutionMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrExtent2Di recommendedImageDimensions; + XrBool32 isValid; +} XrRecommendedLayerResolutionMETA; + +typedef struct XrRecommendedLayerResolutionGetInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + const XrCompositionLayerBaseHeader* layer; + XrTime predictedDisplayTime; +} XrRecommendedLayerResolutionGetInfoMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrGetRecommendedLayerResolutionMETA)(XrSession session, const XrRecommendedLayerResolutionGetInfoMETA* info, XrRecommendedLayerResolutionMETA* resolution); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetRecommendedLayerResolutionMETA( + XrSession session, + const XrRecommendedLayerResolutionGetInfoMETA* info, + XrRecommendedLayerResolutionMETA* resolution); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_spatial_entity_persistence is a preprocessor guard. Do not pass it to API calls. +#define XR_META_spatial_entity_persistence 1 +#define XR_META_spatial_entity_persistence_SPEC_VERSION 1 +#define XR_META_SPATIAL_ENTITY_PERSISTENCE_EXTENSION_NAME "XR_META_spatial_entity_persistence" +// XrSystemSpacePersistencePropertiesMETA extends XrSystemProperties +typedef struct XrSystemSpacePersistencePropertiesMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 supportsSpacePersistence; +} XrSystemSpacePersistencePropertiesMETA; + +typedef struct XrSpacesSaveInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t spaceCount; + XrSpace* spaces; +} XrSpacesSaveInfoMETA; + +typedef struct XrEventDataSpacesSaveResultMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; + XrResult result; +} XrEventDataSpacesSaveResultMETA; + +typedef struct XrSpacesEraseInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t spaceCount; + XrSpace* spaces; + uint32_t uuidCount; + XrUuidEXT* uuids; +} XrSpacesEraseInfoMETA; + +typedef struct XrEventDataSpacesEraseResultMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; + XrResult result; +} XrEventDataSpacesEraseResultMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrSaveSpacesMETA)(XrSession session, const XrSpacesSaveInfoMETA* info, XrAsyncRequestIdFB* requestId); +typedef XrResult (XRAPI_PTR *PFN_xrEraseSpacesMETA)(XrSession session, const XrSpacesEraseInfoMETA* info, XrAsyncRequestIdFB* requestId); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrSaveSpacesMETA( + XrSession session, + const XrSpacesSaveInfoMETA* info, + XrAsyncRequestIdFB* requestId); + +XRAPI_ATTR XrResult XRAPI_CALL xrEraseSpacesMETA( + XrSession session, + const XrSpacesEraseInfoMETA* info, + XrAsyncRequestIdFB* requestId); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_passthrough_color_lut is a preprocessor guard. Do not pass it to API calls. +#define XR_META_passthrough_color_lut 1 +XR_DEFINE_HANDLE(XrPassthroughColorLutMETA) +#define XR_META_passthrough_color_lut_SPEC_VERSION 1 +#define XR_META_PASSTHROUGH_COLOR_LUT_EXTENSION_NAME "XR_META_passthrough_color_lut" + +typedef enum XrPassthroughColorLutChannelsMETA { + XR_PASSTHROUGH_COLOR_LUT_CHANNELS_RGB_META = 1, + XR_PASSTHROUGH_COLOR_LUT_CHANNELS_RGBA_META = 2, + XR_PASSTHROUGH_COLOR_LUT_CHANNELS_MAX_ENUM_META = 0x7FFFFFFF +} XrPassthroughColorLutChannelsMETA; +typedef struct XrPassthroughColorLutDataMETA { + uint32_t bufferSize; + const uint8_t* buffer; +} XrPassthroughColorLutDataMETA; + +typedef struct XrPassthroughColorLutCreateInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPassthroughColorLutChannelsMETA channels; + uint32_t resolution; + XrPassthroughColorLutDataMETA data; +} XrPassthroughColorLutCreateInfoMETA; + +typedef struct XrPassthroughColorLutUpdateInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPassthroughColorLutDataMETA data; +} XrPassthroughColorLutUpdateInfoMETA; + +// XrPassthroughColorMapLutMETA extends XrPassthroughStyleFB +typedef struct XrPassthroughColorMapLutMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPassthroughColorLutMETA colorLut; + float weight; +} XrPassthroughColorMapLutMETA; + +// XrPassthroughColorMapInterpolatedLutMETA extends XrPassthroughStyleFB +typedef struct XrPassthroughColorMapInterpolatedLutMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPassthroughColorLutMETA sourceColorLut; + XrPassthroughColorLutMETA targetColorLut; + float weight; +} XrPassthroughColorMapInterpolatedLutMETA; + +// XrSystemPassthroughColorLutPropertiesMETA extends XrSystemProperties +typedef struct XrSystemPassthroughColorLutPropertiesMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t maxColorLutResolution; +} XrSystemPassthroughColorLutPropertiesMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrCreatePassthroughColorLutMETA)(XrPassthroughFB passthrough, const XrPassthroughColorLutCreateInfoMETA* createInfo, XrPassthroughColorLutMETA* colorLut); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyPassthroughColorLutMETA)(XrPassthroughColorLutMETA colorLut); +typedef XrResult (XRAPI_PTR *PFN_xrUpdatePassthroughColorLutMETA)(XrPassthroughColorLutMETA colorLut, const XrPassthroughColorLutUpdateInfoMETA* updateInfo); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreatePassthroughColorLutMETA( + XrPassthroughFB passthrough, + const XrPassthroughColorLutCreateInfoMETA* createInfo, + XrPassthroughColorLutMETA* colorLut); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyPassthroughColorLutMETA( + XrPassthroughColorLutMETA colorLut); + +XRAPI_ATTR XrResult XRAPI_CALL xrUpdatePassthroughColorLutMETA( + XrPassthroughColorLutMETA colorLut, + const XrPassthroughColorLutUpdateInfoMETA* updateInfo); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_spatial_entity_mesh is a preprocessor guard. Do not pass it to API calls. +#define XR_META_spatial_entity_mesh 1 +#define XR_META_spatial_entity_mesh_SPEC_VERSION 1 +#define XR_META_SPATIAL_ENTITY_MESH_EXTENSION_NAME "XR_META_spatial_entity_mesh" +typedef struct XrSpaceTriangleMeshGetInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSpaceTriangleMeshGetInfoMETA; + +typedef struct XrSpaceTriangleMeshMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t vertexCapacityInput; + uint32_t vertexCountOutput; + XrVector3f* vertices; + uint32_t indexCapacityInput; + uint32_t indexCountOutput; + uint32_t* indices; +} XrSpaceTriangleMeshMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrGetSpaceTriangleMeshMETA)(XrSpace space, const XrSpaceTriangleMeshGetInfoMETA* getInfo, XrSpaceTriangleMeshMETA* triangleMeshOutput); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpaceTriangleMeshMETA( + XrSpace space, + const XrSpaceTriangleMeshGetInfoMETA* getInfo, + XrSpaceTriangleMeshMETA* triangleMeshOutput); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_automatic_layer_filter is a preprocessor guard. Do not pass it to API calls. +#define XR_META_automatic_layer_filter 1 +#define XR_META_automatic_layer_filter_SPEC_VERSION 1 +#define XR_META_AUTOMATIC_LAYER_FILTER_EXTENSION_NAME "XR_META_automatic_layer_filter" + + +// XR_META_body_tracking_full_body is a preprocessor guard. Do not pass it to API calls. +#define XR_META_body_tracking_full_body 1 +#define XR_META_body_tracking_full_body_SPEC_VERSION 1 +#define XR_META_BODY_TRACKING_FULL_BODY_EXTENSION_NAME "XR_META_body_tracking_full_body" + +typedef enum XrFullBodyJointMETA { + XR_FULL_BODY_JOINT_ROOT_META = 0, + XR_FULL_BODY_JOINT_HIPS_META = 1, + XR_FULL_BODY_JOINT_SPINE_LOWER_META = 2, + XR_FULL_BODY_JOINT_SPINE_MIDDLE_META = 3, + XR_FULL_BODY_JOINT_SPINE_UPPER_META = 4, + XR_FULL_BODY_JOINT_CHEST_META = 5, + XR_FULL_BODY_JOINT_NECK_META = 6, + XR_FULL_BODY_JOINT_HEAD_META = 7, + XR_FULL_BODY_JOINT_LEFT_SHOULDER_META = 8, + XR_FULL_BODY_JOINT_LEFT_SCAPULA_META = 9, + XR_FULL_BODY_JOINT_LEFT_ARM_UPPER_META = 10, + XR_FULL_BODY_JOINT_LEFT_ARM_LOWER_META = 11, + XR_FULL_BODY_JOINT_LEFT_HAND_WRIST_TWIST_META = 12, + XR_FULL_BODY_JOINT_RIGHT_SHOULDER_META = 13, + XR_FULL_BODY_JOINT_RIGHT_SCAPULA_META = 14, + XR_FULL_BODY_JOINT_RIGHT_ARM_UPPER_META = 15, + XR_FULL_BODY_JOINT_RIGHT_ARM_LOWER_META = 16, + XR_FULL_BODY_JOINT_RIGHT_HAND_WRIST_TWIST_META = 17, + XR_FULL_BODY_JOINT_LEFT_HAND_PALM_META = 18, + XR_FULL_BODY_JOINT_LEFT_HAND_WRIST_META = 19, + XR_FULL_BODY_JOINT_LEFT_HAND_THUMB_METACARPAL_META = 20, + XR_FULL_BODY_JOINT_LEFT_HAND_THUMB_PROXIMAL_META = 21, + XR_FULL_BODY_JOINT_LEFT_HAND_THUMB_DISTAL_META = 22, + XR_FULL_BODY_JOINT_LEFT_HAND_THUMB_TIP_META = 23, + XR_FULL_BODY_JOINT_LEFT_HAND_INDEX_METACARPAL_META = 24, + XR_FULL_BODY_JOINT_LEFT_HAND_INDEX_PROXIMAL_META = 25, + XR_FULL_BODY_JOINT_LEFT_HAND_INDEX_INTERMEDIATE_META = 26, + XR_FULL_BODY_JOINT_LEFT_HAND_INDEX_DISTAL_META = 27, + XR_FULL_BODY_JOINT_LEFT_HAND_INDEX_TIP_META = 28, + XR_FULL_BODY_JOINT_LEFT_HAND_MIDDLE_METACARPAL_META = 29, + XR_FULL_BODY_JOINT_LEFT_HAND_MIDDLE_PROXIMAL_META = 30, + XR_FULL_BODY_JOINT_LEFT_HAND_MIDDLE_INTERMEDIATE_META = 31, + XR_FULL_BODY_JOINT_LEFT_HAND_MIDDLE_DISTAL_META = 32, + XR_FULL_BODY_JOINT_LEFT_HAND_MIDDLE_TIP_META = 33, + XR_FULL_BODY_JOINT_LEFT_HAND_RING_METACARPAL_META = 34, + XR_FULL_BODY_JOINT_LEFT_HAND_RING_PROXIMAL_META = 35, + XR_FULL_BODY_JOINT_LEFT_HAND_RING_INTERMEDIATE_META = 36, + XR_FULL_BODY_JOINT_LEFT_HAND_RING_DISTAL_META = 37, + XR_FULL_BODY_JOINT_LEFT_HAND_RING_TIP_META = 38, + XR_FULL_BODY_JOINT_LEFT_HAND_LITTLE_METACARPAL_META = 39, + XR_FULL_BODY_JOINT_LEFT_HAND_LITTLE_PROXIMAL_META = 40, + XR_FULL_BODY_JOINT_LEFT_HAND_LITTLE_INTERMEDIATE_META = 41, + XR_FULL_BODY_JOINT_LEFT_HAND_LITTLE_DISTAL_META = 42, + XR_FULL_BODY_JOINT_LEFT_HAND_LITTLE_TIP_META = 43, + XR_FULL_BODY_JOINT_RIGHT_HAND_PALM_META = 44, + XR_FULL_BODY_JOINT_RIGHT_HAND_WRIST_META = 45, + XR_FULL_BODY_JOINT_RIGHT_HAND_THUMB_METACARPAL_META = 46, + XR_FULL_BODY_JOINT_RIGHT_HAND_THUMB_PROXIMAL_META = 47, + XR_FULL_BODY_JOINT_RIGHT_HAND_THUMB_DISTAL_META = 48, + XR_FULL_BODY_JOINT_RIGHT_HAND_THUMB_TIP_META = 49, + XR_FULL_BODY_JOINT_RIGHT_HAND_INDEX_METACARPAL_META = 50, + XR_FULL_BODY_JOINT_RIGHT_HAND_INDEX_PROXIMAL_META = 51, + XR_FULL_BODY_JOINT_RIGHT_HAND_INDEX_INTERMEDIATE_META = 52, + XR_FULL_BODY_JOINT_RIGHT_HAND_INDEX_DISTAL_META = 53, + XR_FULL_BODY_JOINT_RIGHT_HAND_INDEX_TIP_META = 54, + XR_FULL_BODY_JOINT_RIGHT_HAND_MIDDLE_METACARPAL_META = 55, + XR_FULL_BODY_JOINT_RIGHT_HAND_MIDDLE_PROXIMAL_META = 56, + XR_FULL_BODY_JOINT_RIGHT_HAND_MIDDLE_INTERMEDIATE_META = 57, + XR_FULL_BODY_JOINT_RIGHT_HAND_MIDDLE_DISTAL_META = 58, + XR_FULL_BODY_JOINT_RIGHT_HAND_MIDDLE_TIP_META = 59, + XR_FULL_BODY_JOINT_RIGHT_HAND_RING_METACARPAL_META = 60, + XR_FULL_BODY_JOINT_RIGHT_HAND_RING_PROXIMAL_META = 61, + XR_FULL_BODY_JOINT_RIGHT_HAND_RING_INTERMEDIATE_META = 62, + XR_FULL_BODY_JOINT_RIGHT_HAND_RING_DISTAL_META = 63, + XR_FULL_BODY_JOINT_RIGHT_HAND_RING_TIP_META = 64, + XR_FULL_BODY_JOINT_RIGHT_HAND_LITTLE_METACARPAL_META = 65, + XR_FULL_BODY_JOINT_RIGHT_HAND_LITTLE_PROXIMAL_META = 66, + XR_FULL_BODY_JOINT_RIGHT_HAND_LITTLE_INTERMEDIATE_META = 67, + XR_FULL_BODY_JOINT_RIGHT_HAND_LITTLE_DISTAL_META = 68, + XR_FULL_BODY_JOINT_RIGHT_HAND_LITTLE_TIP_META = 69, + XR_FULL_BODY_JOINT_LEFT_UPPER_LEG_META = 70, + XR_FULL_BODY_JOINT_LEFT_LOWER_LEG_META = 71, + XR_FULL_BODY_JOINT_LEFT_FOOT_ANKLE_TWIST_META = 72, + XR_FULL_BODY_JOINT_LEFT_FOOT_ANKLE_META = 73, + XR_FULL_BODY_JOINT_LEFT_FOOT_SUBTALAR_META = 74, + XR_FULL_BODY_JOINT_LEFT_FOOT_TRANSVERSE_META = 75, + XR_FULL_BODY_JOINT_LEFT_FOOT_BALL_META = 76, + XR_FULL_BODY_JOINT_RIGHT_UPPER_LEG_META = 77, + XR_FULL_BODY_JOINT_RIGHT_LOWER_LEG_META = 78, + XR_FULL_BODY_JOINT_RIGHT_FOOT_ANKLE_TWIST_META = 79, + XR_FULL_BODY_JOINT_RIGHT_FOOT_ANKLE_META = 80, + XR_FULL_BODY_JOINT_RIGHT_FOOT_SUBTALAR_META = 81, + XR_FULL_BODY_JOINT_RIGHT_FOOT_TRANSVERSE_META = 82, + XR_FULL_BODY_JOINT_RIGHT_FOOT_BALL_META = 83, + XR_FULL_BODY_JOINT_COUNT_META = 84, + XR_FULL_BODY_JOINT_NONE_META = 85, + XR_FULL_BODY_JOINT_MAX_ENUM_META = 0x7FFFFFFF +} XrFullBodyJointMETA; +// XrSystemPropertiesBodyTrackingFullBodyMETA extends XrSystemProperties +typedef struct XrSystemPropertiesBodyTrackingFullBodyMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsFullBodyTracking; +} XrSystemPropertiesBodyTrackingFullBodyMETA; + + + +// XR_META_touch_controller_plus is a preprocessor guard. Do not pass it to API calls. +#define XR_META_touch_controller_plus 1 +#define XR_META_touch_controller_plus_SPEC_VERSION 1 +#define XR_META_TOUCH_CONTROLLER_PLUS_EXTENSION_NAME "XR_META_touch_controller_plus" + + +// XR_META_passthrough_layer_resumed_event is a preprocessor guard. Do not pass it to API calls. +#define XR_META_passthrough_layer_resumed_event 1 +#define XR_META_passthrough_layer_resumed_event_SPEC_VERSION 1 +#define XR_META_PASSTHROUGH_LAYER_RESUMED_EVENT_EXTENSION_NAME "XR_META_passthrough_layer_resumed_event" +typedef struct XrEventDataPassthroughLayerResumedMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPassthroughLayerFB layer; +} XrEventDataPassthroughLayerResumedMETA; + + + +// XR_META_body_tracking_calibration is a preprocessor guard. Do not pass it to API calls. +#define XR_META_body_tracking_calibration 1 +#define XR_META_body_tracking_calibration_SPEC_VERSION 1 +#define XR_META_BODY_TRACKING_CALIBRATION_EXTENSION_NAME "XR_META_body_tracking_calibration" + +typedef enum XrBodyTrackingCalibrationStateMETA { + XR_BODY_TRACKING_CALIBRATION_STATE_VALID_META = 1, + XR_BODY_TRACKING_CALIBRATION_STATE_CALIBRATING_META = 2, + XR_BODY_TRACKING_CALIBRATION_STATE_INVALID_META = 3, + XR_BODY_TRACKING_CALIBRATION_STATE_MAX_ENUM_META = 0x7FFFFFFF +} XrBodyTrackingCalibrationStateMETA; +// XrBodyTrackingCalibrationStatusMETA extends XrBodyJointLocationsFB +typedef struct XrBodyTrackingCalibrationStatusMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBodyTrackingCalibrationStateMETA status; +} XrBodyTrackingCalibrationStatusMETA; + +typedef struct XrBodyTrackingCalibrationInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + float bodyHeight; +} XrBodyTrackingCalibrationInfoMETA; + +// XrSystemPropertiesBodyTrackingCalibrationMETA extends XrSystemProperties +typedef struct XrSystemPropertiesBodyTrackingCalibrationMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsHeightOverride; +} XrSystemPropertiesBodyTrackingCalibrationMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrSuggestBodyTrackingCalibrationOverrideMETA)(XrBodyTrackerFB bodyTracker, const XrBodyTrackingCalibrationInfoMETA* calibrationInfo); +typedef XrResult (XRAPI_PTR *PFN_xrResetBodyTrackingCalibrationMETA)(XrBodyTrackerFB bodyTracker); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrSuggestBodyTrackingCalibrationOverrideMETA( + XrBodyTrackerFB bodyTracker, + const XrBodyTrackingCalibrationInfoMETA* calibrationInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrResetBodyTrackingCalibrationMETA( + XrBodyTrackerFB bodyTracker); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_body_tracking_fidelity is a preprocessor guard. Do not pass it to API calls. +#define XR_META_body_tracking_fidelity 1 +#define XR_META_body_tracking_fidelity_SPEC_VERSION 1 +#define XR_META_BODY_TRACKING_FIDELITY_EXTENSION_NAME "XR_META_body_tracking_fidelity" + +typedef enum XrBodyTrackingFidelityMETA { + XR_BODY_TRACKING_FIDELITY_LOW_META = 1, + XR_BODY_TRACKING_FIDELITY_HIGH_META = 2, + XR_BODY_TRACKING_FIDELITY_MAX_ENUM_META = 0x7FFFFFFF +} XrBodyTrackingFidelityMETA; +// XrSystemPropertiesBodyTrackingFidelityMETA extends XrSystemProperties +typedef struct XrSystemPropertiesBodyTrackingFidelityMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsBodyTrackingFidelity; +} XrSystemPropertiesBodyTrackingFidelityMETA; + +// XrBodyTrackingFidelityStatusMETA extends XrBodyJointLocationsFB +typedef struct XrBodyTrackingFidelityStatusMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBodyTrackingFidelityMETA fidelity; +} XrBodyTrackingFidelityStatusMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrRequestBodyTrackingFidelityMETA)(XrBodyTrackerFB bodyTracker, const XrBodyTrackingFidelityMETA fidelity); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrRequestBodyTrackingFidelityMETA( + XrBodyTrackerFB bodyTracker, + const XrBodyTrackingFidelityMETA fidelity); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_FB_face_tracking2 is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_face_tracking2 1 +XR_DEFINE_HANDLE(XrFaceTracker2FB) +#define XR_FB_face_tracking2_SPEC_VERSION 1 +#define XR_FB_FACE_TRACKING2_EXTENSION_NAME "XR_FB_face_tracking2" + +typedef enum XrFaceExpression2FB { + XR_FACE_EXPRESSION2_BROW_LOWERER_L_FB = 0, + XR_FACE_EXPRESSION2_BROW_LOWERER_R_FB = 1, + XR_FACE_EXPRESSION2_CHEEK_PUFF_L_FB = 2, + XR_FACE_EXPRESSION2_CHEEK_PUFF_R_FB = 3, + XR_FACE_EXPRESSION2_CHEEK_RAISER_L_FB = 4, + XR_FACE_EXPRESSION2_CHEEK_RAISER_R_FB = 5, + XR_FACE_EXPRESSION2_CHEEK_SUCK_L_FB = 6, + XR_FACE_EXPRESSION2_CHEEK_SUCK_R_FB = 7, + XR_FACE_EXPRESSION2_CHIN_RAISER_B_FB = 8, + XR_FACE_EXPRESSION2_CHIN_RAISER_T_FB = 9, + XR_FACE_EXPRESSION2_DIMPLER_L_FB = 10, + XR_FACE_EXPRESSION2_DIMPLER_R_FB = 11, + XR_FACE_EXPRESSION2_EYES_CLOSED_L_FB = 12, + XR_FACE_EXPRESSION2_EYES_CLOSED_R_FB = 13, + XR_FACE_EXPRESSION2_EYES_LOOK_DOWN_L_FB = 14, + XR_FACE_EXPRESSION2_EYES_LOOK_DOWN_R_FB = 15, + XR_FACE_EXPRESSION2_EYES_LOOK_LEFT_L_FB = 16, + XR_FACE_EXPRESSION2_EYES_LOOK_LEFT_R_FB = 17, + XR_FACE_EXPRESSION2_EYES_LOOK_RIGHT_L_FB = 18, + XR_FACE_EXPRESSION2_EYES_LOOK_RIGHT_R_FB = 19, + XR_FACE_EXPRESSION2_EYES_LOOK_UP_L_FB = 20, + XR_FACE_EXPRESSION2_EYES_LOOK_UP_R_FB = 21, + XR_FACE_EXPRESSION2_INNER_BROW_RAISER_L_FB = 22, + XR_FACE_EXPRESSION2_INNER_BROW_RAISER_R_FB = 23, + XR_FACE_EXPRESSION2_JAW_DROP_FB = 24, + XR_FACE_EXPRESSION2_JAW_SIDEWAYS_LEFT_FB = 25, + XR_FACE_EXPRESSION2_JAW_SIDEWAYS_RIGHT_FB = 26, + XR_FACE_EXPRESSION2_JAW_THRUST_FB = 27, + XR_FACE_EXPRESSION2_LID_TIGHTENER_L_FB = 28, + XR_FACE_EXPRESSION2_LID_TIGHTENER_R_FB = 29, + XR_FACE_EXPRESSION2_LIP_CORNER_DEPRESSOR_L_FB = 30, + XR_FACE_EXPRESSION2_LIP_CORNER_DEPRESSOR_R_FB = 31, + XR_FACE_EXPRESSION2_LIP_CORNER_PULLER_L_FB = 32, + XR_FACE_EXPRESSION2_LIP_CORNER_PULLER_R_FB = 33, + XR_FACE_EXPRESSION2_LIP_FUNNELER_LB_FB = 34, + XR_FACE_EXPRESSION2_LIP_FUNNELER_LT_FB = 35, + XR_FACE_EXPRESSION2_LIP_FUNNELER_RB_FB = 36, + XR_FACE_EXPRESSION2_LIP_FUNNELER_RT_FB = 37, + XR_FACE_EXPRESSION2_LIP_PRESSOR_L_FB = 38, + XR_FACE_EXPRESSION2_LIP_PRESSOR_R_FB = 39, + XR_FACE_EXPRESSION2_LIP_PUCKER_L_FB = 40, + XR_FACE_EXPRESSION2_LIP_PUCKER_R_FB = 41, + XR_FACE_EXPRESSION2_LIP_STRETCHER_L_FB = 42, + XR_FACE_EXPRESSION2_LIP_STRETCHER_R_FB = 43, + XR_FACE_EXPRESSION2_LIP_SUCK_LB_FB = 44, + XR_FACE_EXPRESSION2_LIP_SUCK_LT_FB = 45, + XR_FACE_EXPRESSION2_LIP_SUCK_RB_FB = 46, + XR_FACE_EXPRESSION2_LIP_SUCK_RT_FB = 47, + XR_FACE_EXPRESSION2_LIP_TIGHTENER_L_FB = 48, + XR_FACE_EXPRESSION2_LIP_TIGHTENER_R_FB = 49, + XR_FACE_EXPRESSION2_LIPS_TOWARD_FB = 50, + XR_FACE_EXPRESSION2_LOWER_LIP_DEPRESSOR_L_FB = 51, + XR_FACE_EXPRESSION2_LOWER_LIP_DEPRESSOR_R_FB = 52, + XR_FACE_EXPRESSION2_MOUTH_LEFT_FB = 53, + XR_FACE_EXPRESSION2_MOUTH_RIGHT_FB = 54, + XR_FACE_EXPRESSION2_NOSE_WRINKLER_L_FB = 55, + XR_FACE_EXPRESSION2_NOSE_WRINKLER_R_FB = 56, + XR_FACE_EXPRESSION2_OUTER_BROW_RAISER_L_FB = 57, + XR_FACE_EXPRESSION2_OUTER_BROW_RAISER_R_FB = 58, + XR_FACE_EXPRESSION2_UPPER_LID_RAISER_L_FB = 59, + XR_FACE_EXPRESSION2_UPPER_LID_RAISER_R_FB = 60, + XR_FACE_EXPRESSION2_UPPER_LIP_RAISER_L_FB = 61, + XR_FACE_EXPRESSION2_UPPER_LIP_RAISER_R_FB = 62, + XR_FACE_EXPRESSION2_TONGUE_TIP_INTERDENTAL_FB = 63, + XR_FACE_EXPRESSION2_TONGUE_TIP_ALVEOLAR_FB = 64, + XR_FACE_EXPRESSION2_TONGUE_FRONT_DORSAL_PALATE_FB = 65, + XR_FACE_EXPRESSION2_TONGUE_MID_DORSAL_PALATE_FB = 66, + XR_FACE_EXPRESSION2_TONGUE_BACK_DORSAL_VELAR_FB = 67, + XR_FACE_EXPRESSION2_TONGUE_OUT_FB = 68, + XR_FACE_EXPRESSION2_TONGUE_RETREAT_FB = 69, + XR_FACE_EXPRESSION2_COUNT_FB = 70, + XR_FACE_EXPRESSION_2FB_MAX_ENUM_FB = 0x7FFFFFFF +} XrFaceExpression2FB; + +typedef enum XrFaceExpressionSet2FB { + XR_FACE_EXPRESSION_SET2_DEFAULT_FB = 0, + XR_FACE_EXPRESSION_SET_2FB_MAX_ENUM_FB = 0x7FFFFFFF +} XrFaceExpressionSet2FB; + +typedef enum XrFaceTrackingDataSource2FB { + XR_FACE_TRACKING_DATA_SOURCE2_VISUAL_FB = 0, + XR_FACE_TRACKING_DATA_SOURCE2_AUDIO_FB = 1, + XR_FACE_TRACKING_DATA_SOURCE_2FB_MAX_ENUM_FB = 0x7FFFFFFF +} XrFaceTrackingDataSource2FB; + +typedef enum XrFaceConfidence2FB { + XR_FACE_CONFIDENCE2_LOWER_FACE_FB = 0, + XR_FACE_CONFIDENCE2_UPPER_FACE_FB = 1, + XR_FACE_CONFIDENCE2_COUNT_FB = 2, + XR_FACE_CONFIDENCE_2FB_MAX_ENUM_FB = 0x7FFFFFFF +} XrFaceConfidence2FB; +// XrSystemFaceTrackingProperties2FB extends XrSystemProperties +typedef struct XrSystemFaceTrackingProperties2FB { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsVisualFaceTracking; + XrBool32 supportsAudioFaceTracking; +} XrSystemFaceTrackingProperties2FB; + +typedef struct XrFaceTrackerCreateInfo2FB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrFaceExpressionSet2FB faceExpressionSet; + uint32_t requestedDataSourceCount; + XrFaceTrackingDataSource2FB* requestedDataSources; +} XrFaceTrackerCreateInfo2FB; + +typedef struct XrFaceExpressionInfo2FB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrTime time; +} XrFaceExpressionInfo2FB; + +typedef struct XrFaceExpressionWeights2FB { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t weightCount; + float* weights; + uint32_t confidenceCount; + float* confidences; + XrBool32 isValid; + XrBool32 isEyeFollowingBlendshapesValid; + XrFaceTrackingDataSource2FB dataSource; + XrTime time; +} XrFaceExpressionWeights2FB; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateFaceTracker2FB)(XrSession session, const XrFaceTrackerCreateInfo2FB* createInfo, XrFaceTracker2FB* faceTracker); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyFaceTracker2FB)(XrFaceTracker2FB faceTracker); +typedef XrResult (XRAPI_PTR *PFN_xrGetFaceExpressionWeights2FB)(XrFaceTracker2FB faceTracker, const XrFaceExpressionInfo2FB* expressionInfo, XrFaceExpressionWeights2FB* expressionWeights); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateFaceTracker2FB( + XrSession session, + const XrFaceTrackerCreateInfo2FB* createInfo, + XrFaceTracker2FB* faceTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyFaceTracker2FB( + XrFaceTracker2FB faceTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetFaceExpressionWeights2FB( + XrFaceTracker2FB faceTracker, + const XrFaceExpressionInfo2FB* expressionInfo, + XrFaceExpressionWeights2FB* expressionWeights); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_spatial_entity_sharing is a preprocessor guard. Do not pass it to API calls. +#define XR_META_spatial_entity_sharing 1 +#define XR_META_spatial_entity_sharing_SPEC_VERSION 1 +#define XR_META_SPATIAL_ENTITY_SHARING_EXTENSION_NAME "XR_META_spatial_entity_sharing" +#define XR_MAX_SPACES_PER_SHARE_REQUEST_META 32 +// XrSystemSpatialEntitySharingPropertiesMETA extends XrSystemProperties +typedef struct XrSystemSpatialEntitySharingPropertiesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsSpatialEntitySharing; +} XrSystemSpatialEntitySharingPropertiesMETA; + +typedef struct XR_MAY_ALIAS XrShareSpacesRecipientBaseHeaderMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrShareSpacesRecipientBaseHeaderMETA; + +typedef struct XrShareSpacesInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t spaceCount; + XrSpace* spaces; + const XrShareSpacesRecipientBaseHeaderMETA* recipientInfo; +} XrShareSpacesInfoMETA; + +typedef struct XrEventDataShareSpacesCompleteMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; + XrResult result; +} XrEventDataShareSpacesCompleteMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrShareSpacesMETA)(XrSession session, const XrShareSpacesInfoMETA* info, XrAsyncRequestIdFB* requestId); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrShareSpacesMETA( + XrSession session, + const XrShareSpacesInfoMETA* info, + XrAsyncRequestIdFB* requestId); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_environment_depth is a preprocessor guard. Do not pass it to API calls. +#define XR_META_environment_depth 1 +XR_DEFINE_HANDLE(XrEnvironmentDepthProviderMETA) +XR_DEFINE_HANDLE(XrEnvironmentDepthSwapchainMETA) +#define XR_META_environment_depth_SPEC_VERSION 2 +#define XR_META_ENVIRONMENT_DEPTH_EXTENSION_NAME "XR_META_environment_depth" +typedef XrFlags64 XrEnvironmentDepthProviderCreateFlagsMETA; + +// Flag bits for XrEnvironmentDepthProviderCreateFlagsMETA + +typedef XrFlags64 XrEnvironmentDepthSwapchainCreateFlagsMETA; + +// Flag bits for XrEnvironmentDepthSwapchainCreateFlagsMETA + +typedef struct XrEnvironmentDepthProviderCreateInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrEnvironmentDepthProviderCreateFlagsMETA createFlags; +} XrEnvironmentDepthProviderCreateInfoMETA; + +typedef struct XrEnvironmentDepthSwapchainCreateInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrEnvironmentDepthSwapchainCreateFlagsMETA createFlags; +} XrEnvironmentDepthSwapchainCreateInfoMETA; + +typedef struct XrEnvironmentDepthSwapchainStateMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t width; + uint32_t height; +} XrEnvironmentDepthSwapchainStateMETA; + +typedef struct XrEnvironmentDepthImageAcquireInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace space; + XrTime displayTime; +} XrEnvironmentDepthImageAcquireInfoMETA; + +typedef struct XrEnvironmentDepthImageViewMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrFovf fov; + XrPosef pose; +} XrEnvironmentDepthImageViewMETA; + +typedef struct XrEnvironmentDepthImageMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t swapchainIndex; + float nearZ; + float farZ; + XrEnvironmentDepthImageViewMETA views[2]; +} XrEnvironmentDepthImageMETA; + +// XrEnvironmentDepthImageTimestampMETA extends XrEnvironmentDepthImageMETA +typedef struct XrEnvironmentDepthImageTimestampMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrTime captureTime; +} XrEnvironmentDepthImageTimestampMETA; + +typedef struct XrEnvironmentDepthHandRemovalSetInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 enabled; +} XrEnvironmentDepthHandRemovalSetInfoMETA; + +// XrSystemEnvironmentDepthPropertiesMETA extends XrSystemProperties +typedef struct XrSystemEnvironmentDepthPropertiesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsEnvironmentDepth; + XrBool32 supportsHandRemoval; +} XrSystemEnvironmentDepthPropertiesMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateEnvironmentDepthProviderMETA)(XrSession session, const XrEnvironmentDepthProviderCreateInfoMETA* createInfo, XrEnvironmentDepthProviderMETA* environmentDepthProvider); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyEnvironmentDepthProviderMETA)(XrEnvironmentDepthProviderMETA environmentDepthProvider); +typedef XrResult (XRAPI_PTR *PFN_xrStartEnvironmentDepthProviderMETA)(XrEnvironmentDepthProviderMETA environmentDepthProvider); +typedef XrResult (XRAPI_PTR *PFN_xrStopEnvironmentDepthProviderMETA)(XrEnvironmentDepthProviderMETA environmentDepthProvider); +typedef XrResult (XRAPI_PTR *PFN_xrCreateEnvironmentDepthSwapchainMETA)(XrEnvironmentDepthProviderMETA environmentDepthProvider, const XrEnvironmentDepthSwapchainCreateInfoMETA* createInfo, XrEnvironmentDepthSwapchainMETA* swapchain); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyEnvironmentDepthSwapchainMETA)(XrEnvironmentDepthSwapchainMETA swapchain); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateEnvironmentDepthSwapchainImagesMETA)(XrEnvironmentDepthSwapchainMETA swapchain, uint32_t imageCapacityInput, uint32_t* imageCountOutput, XrSwapchainImageBaseHeader* images); +typedef XrResult (XRAPI_PTR *PFN_xrGetEnvironmentDepthSwapchainStateMETA)(XrEnvironmentDepthSwapchainMETA swapchain, XrEnvironmentDepthSwapchainStateMETA* state); +typedef XrResult (XRAPI_PTR *PFN_xrAcquireEnvironmentDepthImageMETA)(XrEnvironmentDepthProviderMETA environmentDepthProvider, const XrEnvironmentDepthImageAcquireInfoMETA* acquireInfo, XrEnvironmentDepthImageMETA* environmentDepthImage); +typedef XrResult (XRAPI_PTR *PFN_xrSetEnvironmentDepthHandRemovalMETA)(XrEnvironmentDepthProviderMETA environmentDepthProvider, const XrEnvironmentDepthHandRemovalSetInfoMETA* setInfo); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateEnvironmentDepthProviderMETA( + XrSession session, + const XrEnvironmentDepthProviderCreateInfoMETA* createInfo, + XrEnvironmentDepthProviderMETA* environmentDepthProvider); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyEnvironmentDepthProviderMETA( + XrEnvironmentDepthProviderMETA environmentDepthProvider); + +XRAPI_ATTR XrResult XRAPI_CALL xrStartEnvironmentDepthProviderMETA( + XrEnvironmentDepthProviderMETA environmentDepthProvider); + +XRAPI_ATTR XrResult XRAPI_CALL xrStopEnvironmentDepthProviderMETA( + XrEnvironmentDepthProviderMETA environmentDepthProvider); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateEnvironmentDepthSwapchainMETA( + XrEnvironmentDepthProviderMETA environmentDepthProvider, + const XrEnvironmentDepthSwapchainCreateInfoMETA* createInfo, + XrEnvironmentDepthSwapchainMETA* swapchain); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyEnvironmentDepthSwapchainMETA( + XrEnvironmentDepthSwapchainMETA swapchain); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateEnvironmentDepthSwapchainImagesMETA( + XrEnvironmentDepthSwapchainMETA swapchain, + uint32_t imageCapacityInput, + uint32_t* imageCountOutput, + XrSwapchainImageBaseHeader* images); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetEnvironmentDepthSwapchainStateMETA( + XrEnvironmentDepthSwapchainMETA swapchain, + XrEnvironmentDepthSwapchainStateMETA* state); + +XRAPI_ATTR XrResult XRAPI_CALL xrAcquireEnvironmentDepthImageMETA( + XrEnvironmentDepthProviderMETA environmentDepthProvider, + const XrEnvironmentDepthImageAcquireInfoMETA* acquireInfo, + XrEnvironmentDepthImageMETA* environmentDepthImage); + +XRAPI_ATTR XrResult XRAPI_CALL xrSetEnvironmentDepthHandRemovalMETA( + XrEnvironmentDepthProviderMETA environmentDepthProvider, + const XrEnvironmentDepthHandRemovalSetInfoMETA* setInfo); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_uuid is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_uuid 1 +#define XR_EXT_uuid_SPEC_VERSION 1 +#define XR_EXT_UUID_EXTENSION_NAME "XR_EXT_uuid" +#define XR_UUID_SIZE_EXT 16 + + +// XR_EXT_render_model is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_render_model 1 +XR_DEFINE_ATOM(XrRenderModelIdEXT) +XR_DEFINE_HANDLE(XrRenderModelEXT) +XR_DEFINE_HANDLE(XrRenderModelAssetEXT) +#define XR_MAX_RENDER_MODEL_ASSET_NODE_NAME_SIZE_EXT 64 +#define XR_EXT_render_model_SPEC_VERSION 1 +#define XR_EXT_RENDER_MODEL_EXTENSION_NAME "XR_EXT_render_model" +#define XR_NULL_RENDER_MODEL_ID_EXT 0 +typedef struct XrRenderModelCreateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrRenderModelIdEXT renderModelId; + uint32_t gltfExtensionCount; + const char* const* gltfExtensions; +} XrRenderModelCreateInfoEXT; + +typedef struct XrRenderModelPropertiesGetInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrRenderModelPropertiesGetInfoEXT; + +typedef struct XrRenderModelPropertiesEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrUuidEXT cacheId; + uint32_t animatableNodeCount; +} XrRenderModelPropertiesEXT; + +typedef struct XrRenderModelSpaceCreateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrRenderModelEXT renderModel; +} XrRenderModelSpaceCreateInfoEXT; + +typedef struct XrRenderModelStateGetInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrTime displayTime; +} XrRenderModelStateGetInfoEXT; + +typedef struct XrRenderModelNodeStateEXT { + XrPosef nodePose; + XrBool32 isVisible; +} XrRenderModelNodeStateEXT; + +typedef struct XrRenderModelStateEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t nodeStateCount; + XrRenderModelNodeStateEXT* nodeStates; +} XrRenderModelStateEXT; + +typedef struct XrRenderModelAssetCreateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrUuidEXT cacheId; +} XrRenderModelAssetCreateInfoEXT; + +typedef struct XrRenderModelAssetDataGetInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrRenderModelAssetDataGetInfoEXT; + +typedef struct XrRenderModelAssetDataEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t bufferCapacityInput; + uint32_t bufferCountOutput; + uint8_t* buffer; +} XrRenderModelAssetDataEXT; + +typedef struct XrRenderModelAssetPropertiesGetInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrRenderModelAssetPropertiesGetInfoEXT; + +typedef struct XrRenderModelAssetNodePropertiesEXT { + char uniqueName[XR_MAX_RENDER_MODEL_ASSET_NODE_NAME_SIZE_EXT]; +} XrRenderModelAssetNodePropertiesEXT; + +typedef struct XrRenderModelAssetPropertiesEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t nodePropertyCount; + XrRenderModelAssetNodePropertiesEXT* nodeProperties; +} XrRenderModelAssetPropertiesEXT; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateRenderModelEXT)(XrSession session, const XrRenderModelCreateInfoEXT* createInfo, XrRenderModelEXT* renderModel); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyRenderModelEXT)(XrRenderModelEXT renderModel); +typedef XrResult (XRAPI_PTR *PFN_xrGetRenderModelPropertiesEXT)(XrRenderModelEXT renderModel, const XrRenderModelPropertiesGetInfoEXT* getInfo, XrRenderModelPropertiesEXT* properties); +typedef XrResult (XRAPI_PTR *PFN_xrCreateRenderModelSpaceEXT)(XrSession session, const XrRenderModelSpaceCreateInfoEXT* createInfo, XrSpace* space); +typedef XrResult (XRAPI_PTR *PFN_xrCreateRenderModelAssetEXT)(XrSession session, const XrRenderModelAssetCreateInfoEXT* createInfo, XrRenderModelAssetEXT* asset); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyRenderModelAssetEXT)(XrRenderModelAssetEXT asset); +typedef XrResult (XRAPI_PTR *PFN_xrGetRenderModelAssetDataEXT)(XrRenderModelAssetEXT asset, const XrRenderModelAssetDataGetInfoEXT* getInfo, XrRenderModelAssetDataEXT* buffer); +typedef XrResult (XRAPI_PTR *PFN_xrGetRenderModelAssetPropertiesEXT)(XrRenderModelAssetEXT asset, const XrRenderModelAssetPropertiesGetInfoEXT* getInfo, XrRenderModelAssetPropertiesEXT* properties); +typedef XrResult (XRAPI_PTR *PFN_xrGetRenderModelStateEXT)(XrRenderModelEXT renderModel, const XrRenderModelStateGetInfoEXT* getInfo, XrRenderModelStateEXT* state); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateRenderModelEXT( + XrSession session, + const XrRenderModelCreateInfoEXT* createInfo, + XrRenderModelEXT* renderModel); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyRenderModelEXT( + XrRenderModelEXT renderModel); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetRenderModelPropertiesEXT( + XrRenderModelEXT renderModel, + const XrRenderModelPropertiesGetInfoEXT* getInfo, + XrRenderModelPropertiesEXT* properties); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateRenderModelSpaceEXT( + XrSession session, + const XrRenderModelSpaceCreateInfoEXT* createInfo, + XrSpace* space); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateRenderModelAssetEXT( + XrSession session, + const XrRenderModelAssetCreateInfoEXT* createInfo, + XrRenderModelAssetEXT* asset); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyRenderModelAssetEXT( + XrRenderModelAssetEXT asset); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetRenderModelAssetDataEXT( + XrRenderModelAssetEXT asset, + const XrRenderModelAssetDataGetInfoEXT* getInfo, + XrRenderModelAssetDataEXT* buffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetRenderModelAssetPropertiesEXT( + XrRenderModelAssetEXT asset, + const XrRenderModelAssetPropertiesGetInfoEXT* getInfo, + XrRenderModelAssetPropertiesEXT* properties); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetRenderModelStateEXT( + XrRenderModelEXT renderModel, + const XrRenderModelStateGetInfoEXT* getInfo, + XrRenderModelStateEXT* state); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_interaction_render_model is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_interaction_render_model 1 +#define XR_EXT_interaction_render_model_SPEC_VERSION 1 +#define XR_EXT_INTERACTION_RENDER_MODEL_EXTENSION_NAME "XR_EXT_interaction_render_model" +typedef struct XrInteractionRenderModelIdsEnumerateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrInteractionRenderModelIdsEnumerateInfoEXT; + +typedef struct XrInteractionRenderModelSubactionPathInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrInteractionRenderModelSubactionPathInfoEXT; + +typedef struct XrInteractionRenderModelTopLevelUserPathGetInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t topLevelUserPathCount; + const XrPath* topLevelUserPaths; +} XrInteractionRenderModelTopLevelUserPathGetInfoEXT; + +typedef struct XrEventDataInteractionRenderModelsChangedEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrEventDataInteractionRenderModelsChangedEXT; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateInteractionRenderModelIdsEXT)(XrSession session, const XrInteractionRenderModelIdsEnumerateInfoEXT* getInfo, uint32_t renderModelIdCapacityInput, uint32_t* renderModelIdCountOutput, XrRenderModelIdEXT* renderModelIds); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateRenderModelSubactionPathsEXT)(XrRenderModelEXT renderModel, const XrInteractionRenderModelSubactionPathInfoEXT* info, uint32_t pathCapacityInput, uint32_t* pathCountOutput, XrPath* paths); +typedef XrResult (XRAPI_PTR *PFN_xrGetRenderModelPoseTopLevelUserPathEXT)(XrRenderModelEXT renderModel, const XrInteractionRenderModelTopLevelUserPathGetInfoEXT* info, XrPath* topLevelUserPath); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateInteractionRenderModelIdsEXT( + XrSession session, + const XrInteractionRenderModelIdsEnumerateInfoEXT* getInfo, + uint32_t renderModelIdCapacityInput, + uint32_t* renderModelIdCountOutput, + XrRenderModelIdEXT* renderModelIds); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateRenderModelSubactionPathsEXT( + XrRenderModelEXT renderModel, + const XrInteractionRenderModelSubactionPathInfoEXT* info, + uint32_t pathCapacityInput, + uint32_t* pathCountOutput, + XrPath* paths); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetRenderModelPoseTopLevelUserPathEXT( + XrRenderModelEXT renderModel, + const XrInteractionRenderModelTopLevelUserPathGetInfoEXT* info, + XrPath* topLevelUserPath); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_hand_interaction is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_hand_interaction 1 +#define XR_EXT_hand_interaction_SPEC_VERSION 2 +#define XR_EXT_HAND_INTERACTION_EXTENSION_NAME "XR_EXT_hand_interaction" + + +// XR_QCOM_tracking_optimization_settings is a preprocessor guard. Do not pass it to API calls. +#define XR_QCOM_tracking_optimization_settings 1 +#define XR_QCOM_tracking_optimization_settings_SPEC_VERSION 2 +#define XR_QCOM_TRACKING_OPTIMIZATION_SETTINGS_EXTENSION_NAME "XR_QCOM_tracking_optimization_settings" + +typedef enum XrTrackingOptimizationSettingsDomainQCOM { + XR_TRACKING_OPTIMIZATION_SETTINGS_DOMAIN_ALL_QCOM = 1, + XR_TRACKING_OPTIMIZATION_SETTINGS_DOMAIN_MAX_ENUM_QCOM = 0x7FFFFFFF +} XrTrackingOptimizationSettingsDomainQCOM; + +typedef enum XrTrackingOptimizationSettingsHintQCOM { + XR_TRACKING_OPTIMIZATION_SETTINGS_HINT_NONE_QCOM = 0, + XR_TRACKING_OPTIMIZATION_SETTINGS_HINT_LONG_RANGE_PRIORITIZATION_QCOM = 1, + XR_TRACKING_OPTIMIZATION_SETTINGS_HINT_CLOSE_RANGE_PRIORITIZATION_QCOM = 2, + XR_TRACKING_OPTIMIZATION_SETTINGS_HINT_LOW_POWER_PRIORITIZATION_QCOM = 3, + XR_TRACKING_OPTIMIZATION_SETTINGS_HINT_HIGH_POWER_PRIORITIZATION_QCOM = 4, + XR_TRACKING_OPTIMIZATION_SETTINGS_HINT_LONG_RANGE_PRIORIZATION_QCOM = XR_TRACKING_OPTIMIZATION_SETTINGS_HINT_LONG_RANGE_PRIORITIZATION_QCOM, + XR_TRACKING_OPTIMIZATION_SETTINGS_HINT_CLOSE_RANGE_PRIORIZATION_QCOM = XR_TRACKING_OPTIMIZATION_SETTINGS_HINT_CLOSE_RANGE_PRIORITIZATION_QCOM, + XR_TRACKING_OPTIMIZATION_SETTINGS_HINT_LOW_POWER_PRIORIZATION_QCOM = XR_TRACKING_OPTIMIZATION_SETTINGS_HINT_LOW_POWER_PRIORITIZATION_QCOM, + XR_TRACKING_OPTIMIZATION_SETTINGS_HINT_HIGH_POWER_PRIORIZATION_QCOM = XR_TRACKING_OPTIMIZATION_SETTINGS_HINT_HIGH_POWER_PRIORITIZATION_QCOM, + XR_TRACKING_OPTIMIZATION_SETTINGS_HINT_MAX_ENUM_QCOM = 0x7FFFFFFF +} XrTrackingOptimizationSettingsHintQCOM; +typedef XrResult (XRAPI_PTR *PFN_xrSetTrackingOptimizationSettingsHintQCOM)(XrSession session, XrTrackingOptimizationSettingsDomainQCOM domain, XrTrackingOptimizationSettingsHintQCOM hint); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrSetTrackingOptimizationSettingsHintQCOM( + XrSession session, + XrTrackingOptimizationSettingsDomainQCOM domain, + XrTrackingOptimizationSettingsHintQCOM hint); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_QCOM_hand_tracking_gesture is a preprocessor guard. Do not pass it to API calls. +#define XR_QCOM_hand_tracking_gesture 1 +#define XR_QCOM_hand_tracking_gesture_SPEC_VERSION 1 +#define XR_QCOM_HAND_TRACKING_GESTURE_EXTENSION_NAME "XR_QCOM_hand_tracking_gesture" + +typedef enum XrHandGestureTypeQCOM { + XR_HAND_GESTURE_TYPE_UNKNOWN_QCOM = -1, + XR_HAND_GESTURE_TYPE_OPEN_HAND_QCOM = 0, + XR_HAND_GESTURE_TYPE_GRAB_QCOM = 2, + XR_HAND_GESTURE_TYPE_PINCH_QCOM = 7, + XR_HAND_GESTURE_TYPE_MAX_ENUM_QCOM = 0x7FFFFFFF +} XrHandGestureTypeQCOM; +typedef struct XrHandGestureQCOM { + XrHandGestureTypeQCOM gesture; + float gestureRatio; + float flipRatio; +} XrHandGestureQCOM; + +typedef XrResult (XRAPI_PTR *PFN_xrGetHandGestureQCOM)(XrHandTrackerEXT handTracker, XrTime time, XrHandGestureQCOM* handGesture); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetHandGestureQCOM( + XrHandTrackerEXT handTracker, + XrTime time, + XrHandGestureQCOM* handGesture); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_HTC_passthrough is a preprocessor guard. Do not pass it to API calls. +#define XR_HTC_passthrough 1 +XR_DEFINE_HANDLE(XrPassthroughHTC) +#define XR_HTC_passthrough_SPEC_VERSION 1 +#define XR_HTC_PASSTHROUGH_EXTENSION_NAME "XR_HTC_passthrough" + +typedef enum XrPassthroughFormHTC { + XR_PASSTHROUGH_FORM_PLANAR_HTC = 0, + XR_PASSTHROUGH_FORM_PROJECTED_HTC = 1, + XR_PASSTHROUGH_FORM_MAX_ENUM_HTC = 0x7FFFFFFF +} XrPassthroughFormHTC; +typedef struct XrPassthroughCreateInfoHTC { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPassthroughFormHTC form; +} XrPassthroughCreateInfoHTC; + +typedef struct XrPassthroughColorHTC { + XrStructureType type; + const void* XR_MAY_ALIAS next; + float alpha; +} XrPassthroughColorHTC; + +// XrPassthroughMeshTransformInfoHTC extends XrCompositionLayerPassthroughHTC +typedef struct XrPassthroughMeshTransformInfoHTC { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t vertexCount; + const XrVector3f* vertices; + uint32_t indexCount; + const uint32_t* indices; + XrSpace baseSpace; + XrTime time; + XrPosef pose; + XrVector3f scale; +} XrPassthroughMeshTransformInfoHTC; + +typedef struct XrCompositionLayerPassthroughHTC { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCompositionLayerFlags layerFlags; + XrSpace space; + XrPassthroughHTC passthrough; + XrPassthroughColorHTC color; +} XrCompositionLayerPassthroughHTC; + +typedef XrResult (XRAPI_PTR *PFN_xrCreatePassthroughHTC)(XrSession session, const XrPassthroughCreateInfoHTC* createInfo, XrPassthroughHTC* passthrough); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyPassthroughHTC)(XrPassthroughHTC passthrough); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreatePassthroughHTC( + XrSession session, + const XrPassthroughCreateInfoHTC* createInfo, + XrPassthroughHTC* passthrough); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyPassthroughHTC( + XrPassthroughHTC passthrough); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_HTC_foveation is a preprocessor guard. Do not pass it to API calls. +#define XR_HTC_foveation 1 +#define XR_HTC_foveation_SPEC_VERSION 1 +#define XR_HTC_FOVEATION_EXTENSION_NAME "XR_HTC_foveation" + +typedef enum XrFoveationModeHTC { + XR_FOVEATION_MODE_DISABLE_HTC = 0, + XR_FOVEATION_MODE_FIXED_HTC = 1, + XR_FOVEATION_MODE_DYNAMIC_HTC = 2, + XR_FOVEATION_MODE_CUSTOM_HTC = 3, + XR_FOVEATION_MODE_MAX_ENUM_HTC = 0x7FFFFFFF +} XrFoveationModeHTC; + +typedef enum XrFoveationLevelHTC { + XR_FOVEATION_LEVEL_NONE_HTC = 0, + XR_FOVEATION_LEVEL_LOW_HTC = 1, + XR_FOVEATION_LEVEL_MEDIUM_HTC = 2, + XR_FOVEATION_LEVEL_HIGH_HTC = 3, + XR_FOVEATION_LEVEL_MAX_ENUM_HTC = 0x7FFFFFFF +} XrFoveationLevelHTC; +typedef XrFlags64 XrFoveationDynamicFlagsHTC; + +// Flag bits for XrFoveationDynamicFlagsHTC +static const XrFoveationDynamicFlagsHTC XR_FOVEATION_DYNAMIC_LEVEL_ENABLED_BIT_HTC = 0x00000001; +static const XrFoveationDynamicFlagsHTC XR_FOVEATION_DYNAMIC_CLEAR_FOV_ENABLED_BIT_HTC = 0x00000002; +static const XrFoveationDynamicFlagsHTC XR_FOVEATION_DYNAMIC_FOCAL_CENTER_OFFSET_ENABLED_BIT_HTC = 0x00000004; + +typedef struct XrFoveationApplyInfoHTC { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrFoveationModeHTC mode; + uint32_t subImageCount; + XrSwapchainSubImage* subImages; +} XrFoveationApplyInfoHTC; + +typedef struct XrFoveationConfigurationHTC { + XrFoveationLevelHTC level; + float clearFovDegree; + XrVector2f focalCenterOffset; +} XrFoveationConfigurationHTC; + +// XrFoveationDynamicModeInfoHTC extends XrFoveationApplyInfoHTC +typedef struct XrFoveationDynamicModeInfoHTC { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrFoveationDynamicFlagsHTC dynamicFlags; +} XrFoveationDynamicModeInfoHTC; + +// XrFoveationCustomModeInfoHTC extends XrFoveationApplyInfoHTC +typedef struct XrFoveationCustomModeInfoHTC { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t configCount; + const XrFoveationConfigurationHTC* configs; +} XrFoveationCustomModeInfoHTC; + +typedef XrResult (XRAPI_PTR *PFN_xrApplyFoveationHTC)(XrSession session, const XrFoveationApplyInfoHTC* applyInfo); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrApplyFoveationHTC( + XrSession session, + const XrFoveationApplyInfoHTC* applyInfo); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_HTC_anchor is a preprocessor guard. Do not pass it to API calls. +#define XR_HTC_anchor 1 +#define XR_MAX_SPATIAL_ANCHOR_NAME_SIZE_HTC 256 +#define XR_HTC_anchor_SPEC_VERSION 1 +#define XR_HTC_ANCHOR_EXTENSION_NAME "XR_HTC_anchor" +// XrSystemAnchorPropertiesHTC extends XrSystemProperties +typedef struct XrSystemAnchorPropertiesHTC { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsAnchor; +} XrSystemAnchorPropertiesHTC; + +typedef struct XrSpatialAnchorNameHTC { + char name[XR_MAX_SPATIAL_ANCHOR_NAME_SIZE_HTC]; +} XrSpatialAnchorNameHTC; + +typedef struct XrSpatialAnchorCreateInfoHTC { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace space; + XrPosef poseInSpace; + XrSpatialAnchorNameHTC name; +} XrSpatialAnchorCreateInfoHTC; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorHTC)(XrSession session, const XrSpatialAnchorCreateInfoHTC* createInfo, XrSpace* anchor); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpatialAnchorNameHTC)(XrSpace anchor, XrSpatialAnchorNameHTC* name); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorHTC( + XrSession session, + const XrSpatialAnchorCreateInfoHTC* createInfo, + XrSpace* anchor); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpatialAnchorNameHTC( + XrSpace anchor, + XrSpatialAnchorNameHTC* name); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_HTC_body_tracking is a preprocessor guard. Do not pass it to API calls. +#define XR_HTC_body_tracking 1 + +#define XR_BODY_JOINT_COUNT_HTC 26 + +XR_DEFINE_HANDLE(XrBodyTrackerHTC) +#define XR_HTC_body_tracking_SPEC_VERSION 1 +#define XR_HTC_BODY_TRACKING_EXTENSION_NAME "XR_HTC_body_tracking" + +typedef enum XrBodyJointHTC { + XR_BODY_JOINT_PELVIS_HTC = 0, + XR_BODY_JOINT_LEFT_HIP_HTC = 1, + XR_BODY_JOINT_LEFT_KNEE_HTC = 2, + XR_BODY_JOINT_LEFT_ANKLE_HTC = 3, + XR_BODY_JOINT_LEFT_FEET_HTC = 4, + XR_BODY_JOINT_RIGHT_HIP_HTC = 5, + XR_BODY_JOINT_RIGHT_KNEE_HTC = 6, + XR_BODY_JOINT_RIGHT_ANKLE_HTC = 7, + XR_BODY_JOINT_RIGHT_FEET_HTC = 8, + XR_BODY_JOINT_WAIST_HTC = 9, + XR_BODY_JOINT_SPINE_LOWER_HTC = 10, + XR_BODY_JOINT_SPINE_MIDDLE_HTC = 11, + XR_BODY_JOINT_SPINE_HIGH_HTC = 12, + XR_BODY_JOINT_CHEST_HTC = 13, + XR_BODY_JOINT_NECK_HTC = 14, + XR_BODY_JOINT_HEAD_HTC = 15, + XR_BODY_JOINT_LEFT_CLAVICLE_HTC = 16, + XR_BODY_JOINT_LEFT_SCAPULA_HTC = 17, + XR_BODY_JOINT_LEFT_ARM_HTC = 18, + XR_BODY_JOINT_LEFT_ELBOW_HTC = 19, + XR_BODY_JOINT_LEFT_WRIST_HTC = 20, + XR_BODY_JOINT_RIGHT_CLAVICLE_HTC = 21, + XR_BODY_JOINT_RIGHT_SCAPULA_HTC = 22, + XR_BODY_JOINT_RIGHT_ARM_HTC = 23, + XR_BODY_JOINT_RIGHT_ELBOW_HTC = 24, + XR_BODY_JOINT_RIGHT_WRIST_HTC = 25, + XR_BODY_JOINT_MAX_ENUM_HTC = 0x7FFFFFFF +} XrBodyJointHTC; + +typedef enum XrBodyJointSetHTC { + XR_BODY_JOINT_SET_FULL_HTC = 0, + XR_BODY_JOINT_SET_MAX_ENUM_HTC = 0x7FFFFFFF +} XrBodyJointSetHTC; + +typedef enum XrBodyJointConfidenceHTC { + XR_BODY_JOINT_CONFIDENCE_NONE_HTC = 0, + XR_BODY_JOINT_CONFIDENCE_LOW_HTC = 1, + XR_BODY_JOINT_CONFIDENCE_HIGH_HTC = 2, + XR_BODY_JOINT_CONFIDENCE_MAX_ENUM_HTC = 0x7FFFFFFF +} XrBodyJointConfidenceHTC; +// XrSystemBodyTrackingPropertiesHTC extends XrSystemProperties +typedef struct XrSystemBodyTrackingPropertiesHTC { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsBodyTracking; +} XrSystemBodyTrackingPropertiesHTC; + +typedef struct XrBodyTrackerCreateInfoHTC { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBodyJointSetHTC bodyJointSet; +} XrBodyTrackerCreateInfoHTC; + +typedef struct XrBodyJointsLocateInfoHTC { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace baseSpace; + XrTime time; +} XrBodyJointsLocateInfoHTC; + +typedef struct XrBodyJointLocationHTC { + XrSpaceLocationFlags locationFlags; + XrPosef pose; +} XrBodyJointLocationHTC; + +typedef struct XrBodyJointLocationsHTC { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrSpaceLocationFlags combinedLocationFlags; + XrBodyJointConfidenceHTC confidenceLevel; + uint32_t jointLocationCount; + XrBodyJointLocationHTC* jointLocations; + uint32_t skeletonGenerationId; +} XrBodyJointLocationsHTC; + +typedef struct XrBodySkeletonJointHTC { + XrPosef pose; +} XrBodySkeletonJointHTC; + +typedef struct XrBodySkeletonHTC { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t jointCount; + XrBodySkeletonJointHTC* joints; +} XrBodySkeletonHTC; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateBodyTrackerHTC)(XrSession session, const XrBodyTrackerCreateInfoHTC* createInfo, XrBodyTrackerHTC* bodyTracker); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyBodyTrackerHTC)(XrBodyTrackerHTC bodyTracker); +typedef XrResult (XRAPI_PTR *PFN_xrLocateBodyJointsHTC)(XrBodyTrackerHTC bodyTracker, const XrBodyJointsLocateInfoHTC* locateInfo, XrBodyJointLocationsHTC* locations); +typedef XrResult (XRAPI_PTR *PFN_xrGetBodySkeletonHTC)(XrBodyTrackerHTC bodyTracker, XrSpace baseSpace, uint32_t skeletonGenerationId, XrBodySkeletonHTC* skeleton); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateBodyTrackerHTC( + XrSession session, + const XrBodyTrackerCreateInfoHTC* createInfo, + XrBodyTrackerHTC* bodyTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyBodyTrackerHTC( + XrBodyTrackerHTC bodyTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrLocateBodyJointsHTC( + XrBodyTrackerHTC bodyTracker, + const XrBodyJointsLocateInfoHTC* locateInfo, + XrBodyJointLocationsHTC* locations); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetBodySkeletonHTC( + XrBodyTrackerHTC bodyTracker, + XrSpace baseSpace, + uint32_t skeletonGenerationId, + XrBodySkeletonHTC* skeleton); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_active_action_set_priority is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_active_action_set_priority 1 +#define XR_EXT_active_action_set_priority_SPEC_VERSION 1 +#define XR_EXT_ACTIVE_ACTION_SET_PRIORITY_EXTENSION_NAME "XR_EXT_active_action_set_priority" +typedef struct XrActiveActionSetPriorityEXT { + XrActionSet actionSet; + uint32_t priorityOverride; +} XrActiveActionSetPriorityEXT; + +// XrActiveActionSetPrioritiesEXT extends XrActionsSyncInfo +typedef struct XrActiveActionSetPrioritiesEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t actionSetPriorityCount; + const XrActiveActionSetPriorityEXT* actionSetPriorities; +} XrActiveActionSetPrioritiesEXT; + + + +// XR_MNDX_force_feedback_curl is a preprocessor guard. Do not pass it to API calls. +#define XR_MNDX_force_feedback_curl 1 +#define XR_MNDX_force_feedback_curl_SPEC_VERSION 1 +#define XR_MNDX_FORCE_FEEDBACK_CURL_EXTENSION_NAME "XR_MNDX_force_feedback_curl" + +typedef enum XrForceFeedbackCurlLocationMNDX { + XR_FORCE_FEEDBACK_CURL_LOCATION_THUMB_CURL_MNDX = 0, + XR_FORCE_FEEDBACK_CURL_LOCATION_INDEX_CURL_MNDX = 1, + XR_FORCE_FEEDBACK_CURL_LOCATION_MIDDLE_CURL_MNDX = 2, + XR_FORCE_FEEDBACK_CURL_LOCATION_RING_CURL_MNDX = 3, + XR_FORCE_FEEDBACK_CURL_LOCATION_LITTLE_CURL_MNDX = 4, + XR_FORCE_FEEDBACK_CURL_LOCATION_MAX_ENUM_MNDX = 0x7FFFFFFF +} XrForceFeedbackCurlLocationMNDX; +// XrSystemForceFeedbackCurlPropertiesMNDX extends XrSystemProperties +typedef struct XrSystemForceFeedbackCurlPropertiesMNDX { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsForceFeedbackCurl; +} XrSystemForceFeedbackCurlPropertiesMNDX; + +typedef struct XrForceFeedbackCurlApplyLocationMNDX { + XrForceFeedbackCurlLocationMNDX location; + float value; +} XrForceFeedbackCurlApplyLocationMNDX; + +typedef struct XrForceFeedbackCurlApplyLocationsMNDX { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t locationCount; + XrForceFeedbackCurlApplyLocationMNDX* locations; +} XrForceFeedbackCurlApplyLocationsMNDX; + +typedef XrResult (XRAPI_PTR *PFN_xrApplyForceFeedbackCurlMNDX)(XrHandTrackerEXT handTracker, const XrForceFeedbackCurlApplyLocationsMNDX* locations); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrApplyForceFeedbackCurlMNDX( + XrHandTrackerEXT handTracker, + const XrForceFeedbackCurlApplyLocationsMNDX* locations); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_BD_controller_interaction is a preprocessor guard. Do not pass it to API calls. +#define XR_BD_controller_interaction 1 +#define XR_BD_controller_interaction_SPEC_VERSION 2 +#define XR_BD_CONTROLLER_INTERACTION_EXTENSION_NAME "XR_BD_controller_interaction" + + +// XR_BD_body_tracking is a preprocessor guard. Do not pass it to API calls. +#define XR_BD_body_tracking 1 + +#define XR_BODY_JOINT_COUNT_BD 24 + + +#define XR_BODY_JOINT_WITHOUT_ARM_COUNT_BD 16 + +XR_DEFINE_HANDLE(XrBodyTrackerBD) +#define XR_BD_body_tracking_SPEC_VERSION 1 +#define XR_BD_BODY_TRACKING_EXTENSION_NAME "XR_BD_body_tracking" + +typedef enum XrBodyJointBD { + XR_BODY_JOINT_PELVIS_BD = 0, + XR_BODY_JOINT_LEFT_HIP_BD = 1, + XR_BODY_JOINT_RIGHT_HIP_BD = 2, + XR_BODY_JOINT_SPINE1_BD = 3, + XR_BODY_JOINT_LEFT_KNEE_BD = 4, + XR_BODY_JOINT_RIGHT_KNEE_BD = 5, + XR_BODY_JOINT_SPINE2_BD = 6, + XR_BODY_JOINT_LEFT_ANKLE_BD = 7, + XR_BODY_JOINT_RIGHT_ANKLE_BD = 8, + XR_BODY_JOINT_SPINE3_BD = 9, + XR_BODY_JOINT_LEFT_FOOT_BD = 10, + XR_BODY_JOINT_RIGHT_FOOT_BD = 11, + XR_BODY_JOINT_NECK_BD = 12, + XR_BODY_JOINT_LEFT_COLLAR_BD = 13, + XR_BODY_JOINT_RIGHT_COLLAR_BD = 14, + XR_BODY_JOINT_HEAD_BD = 15, + XR_BODY_JOINT_LEFT_SHOULDER_BD = 16, + XR_BODY_JOINT_RIGHT_SHOULDER_BD = 17, + XR_BODY_JOINT_LEFT_ELBOW_BD = 18, + XR_BODY_JOINT_RIGHT_ELBOW_BD = 19, + XR_BODY_JOINT_LEFT_WRIST_BD = 20, + XR_BODY_JOINT_RIGHT_WRIST_BD = 21, + XR_BODY_JOINT_LEFT_HAND_BD = 22, + XR_BODY_JOINT_RIGHT_HAND_BD = 23, + XR_BODY_JOINT_MAX_ENUM_BD = 0x7FFFFFFF +} XrBodyJointBD; + +typedef enum XrBodyJointSetBD { + XR_BODY_JOINT_SET_BODY_WITHOUT_ARM_BD = 1, + XR_BODY_JOINT_SET_FULL_BODY_JOINTS_BD = 2, + XR_BODY_JOINT_SET_MAX_ENUM_BD = 0x7FFFFFFF +} XrBodyJointSetBD; +// XrSystemBodyTrackingPropertiesBD extends XrSystemProperties +typedef struct XrSystemBodyTrackingPropertiesBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsBodyTracking; +} XrSystemBodyTrackingPropertiesBD; + +typedef struct XrBodyTrackerCreateInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBodyJointSetBD jointSet; +} XrBodyTrackerCreateInfoBD; + +typedef struct XrBodyJointsLocateInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace baseSpace; + XrTime time; +} XrBodyJointsLocateInfoBD; + +typedef struct XrBodyJointLocationBD { + XrSpaceLocationFlags locationFlags; + XrPosef pose; +} XrBodyJointLocationBD; + +typedef struct XrBodyJointLocationsBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 allJointPosesTracked; + uint32_t jointLocationCount; + XrBodyJointLocationBD* jointLocations; +} XrBodyJointLocationsBD; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateBodyTrackerBD)(XrSession session, const XrBodyTrackerCreateInfoBD* createInfo, XrBodyTrackerBD* bodyTracker); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyBodyTrackerBD)(XrBodyTrackerBD bodyTracker); +typedef XrResult (XRAPI_PTR *PFN_xrLocateBodyJointsBD)(XrBodyTrackerBD bodyTracker, const XrBodyJointsLocateInfoBD* locateInfo, XrBodyJointLocationsBD* locations); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateBodyTrackerBD( + XrSession session, + const XrBodyTrackerCreateInfoBD* createInfo, + XrBodyTrackerBD* bodyTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyBodyTrackerBD( + XrBodyTrackerBD bodyTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrLocateBodyJointsBD( + XrBodyTrackerBD bodyTracker, + const XrBodyJointsLocateInfoBD* locateInfo, + XrBodyJointLocationsBD* locations); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_BD_facial_simulation is a preprocessor guard. Do not pass it to API calls. +#define XR_BD_facial_simulation 1 + +#define XR_FACE_EXPRESSION_COUNT_BD 52 + + +#define XR_LIP_EXPRESSION_COUNT_BD 20 + + XR_DEFINE_HANDLE(XrFaceTrackerBD) +#define XR_BD_facial_simulation_SPEC_VERSION 1 +#define XR_BD_FACIAL_SIMULATION_EXTENSION_NAME "XR_BD_facial_simulation" + +typedef enum XrFacialSimulationModeBD { + XR_FACIAL_SIMULATION_MODE_DEFAULT_BD = 0, + XR_FACIAL_SIMULATION_MODE_COMBINED_AUDIO_BD = 1, + XR_FACIAL_SIMULATION_MODE_COMBINED_AUDIO_WITH_LIP_BD = 2, + XR_FACIAL_SIMULATION_MODE_ONLY_AUDIO_WITH_LIP_BD = 3, + XR_FACIAL_SIMULATION_MODE_MAX_ENUM_BD = 0x7FFFFFFF +} XrFacialSimulationModeBD; + +typedef enum XrFaceExpressionBD { + XR_FACE_EXPRESSION_BROW_DROP_L_BD = 0, + XR_FACE_EXPRESSION_BROW_DROP_R_BD = 1, + XR_FACE_EXPRESSION_BROW_INNER_UPWARDS_BD = 2, + XR_FACE_EXPRESSION_BROW_OUTER_UPWARDS_L_BD = 3, + XR_FACE_EXPRESSION_BROW_OUTER_UPWARDS_R_BD = 4, + XR_FACE_EXPRESSION_EYE_BLINK_L_BD = 5, + XR_FACE_EXPRESSION_EYE_LOOK_DROP_L_BD = 6, + XR_FACE_EXPRESSION_EYE_LOOK_IN_L_BD = 7, + XR_FACE_EXPRESSION_EYE_LOOK_OUT_L_BD = 8, + XR_FACE_EXPRESSION_EYE_LOOK_UPWARDS_L_BD = 9, + XR_FACE_EXPRESSION_EYE_LOOK_SQUINT_L_BD = 10, + XR_FACE_EXPRESSION_EYE_LOOK_WIDE_L_BD = 11, + XR_FACE_EXPRESSION_EYE_BLINK_R_BD = 12, + XR_FACE_EXPRESSION_EYE_LOOK_DROP_R_BD = 13, + XR_FACE_EXPRESSION_EYE_LOOK_IN_R_BD = 14, + XR_FACE_EXPRESSION_EYE_LOOK_OUT_R_BD = 15, + XR_FACE_EXPRESSION_EYE_LOOK_UPWARDS_R_BD = 16, + XR_FACE_EXPRESSION_EYE_LOOK_SQUINT_R_BD = 17, + XR_FACE_EXPRESSION_EYE_LOOK_WIDE_R_BD = 18, + XR_FACE_EXPRESSION_NOSE_SNEER_L_BD = 19, + XR_FACE_EXPRESSION_NOSE_SNEER_R_BD = 20, + XR_FACE_EXPRESSION_CHEEK_PUFF_BD = 21, + XR_FACE_EXPRESSION_CHEEK_SQUINT_L_BD = 22, + XR_FACE_EXPRESSION_CHEEK_SQUINT_R_BD = 23, + XR_FACE_EXPRESSION_MOUTH_CLOSE_BD = 24, + XR_FACE_EXPRESSION_MOUTH_FUNNEL_BD = 25, + XR_FACE_EXPRESSION_MOUTH_PUCKER_BD = 26, + XR_FACE_EXPRESSION_MOUTH_L_BD = 27, + XR_FACE_EXPRESSION_MOUTH_R_BD = 28, + XR_FACE_EXPRESSION_MOUTH_SMILE_L_BD = 29, + XR_FACE_EXPRESSION_MOUTH_SMILE_R_BD = 30, + XR_FACE_EXPRESSION_MOUTH_FROWN_L_BD = 31, + XR_FACE_EXPRESSION_MOUTH_FROWN_R_BD = 32, + XR_FACE_EXPRESSION_MOUTH_DIMPLE_L_BD = 33, + XR_FACE_EXPRESSION_MOUTH_DIMPLE_R_BD = 34, + XR_FACE_EXPRESSION_MOUTH_STRETCH_L_BD = 35, + XR_FACE_EXPRESSION_MOUTH_STRETCH_R_BD = 36, + XR_FACE_EXPRESSION_MOUTH_ROLL_LOWER_BD = 37, + XR_FACE_EXPRESSION_MOUTH_ROLL_UPPER_BD = 38, + XR_FACE_EXPRESSION_MOUTH_SHRUG_LOWER_BD = 39, + XR_FACE_EXPRESSION_MOUTH_SHRUG_UPPER_BD = 40, + XR_FACE_EXPRESSION_MOUTH_PRESS_L_BD = 41, + XR_FACE_EXPRESSION_MOUTH_PRESS_R_BD = 42, + XR_FACE_EXPRESSION_MOUTH_LOWER_DROP_L_BD = 43, + XR_FACE_EXPRESSION_MOUTH_LOWER_DROP_R_BD = 44, + XR_FACE_EXPRESSION_MOUTH_UPPER_UPWARDS_L_BD = 45, + XR_FACE_EXPRESSION_MOUTH_UPPER_UPWARDS_R_BD = 46, + XR_FACE_EXPRESSION_JAW_FORWARD_BD = 47, + XR_FACE_EXPRESSION_JAW_L_BD = 48, + XR_FACE_EXPRESSION_JAW_R_BD = 49, + XR_FACE_EXPRESSION_JAW_OPEN_BD = 50, + XR_FACE_EXPRESSION_TONGUE_OUT_BD = 51, + XR_FACE_EXPRESSION_MAX_ENUM_BD = 0x7FFFFFFF +} XrFaceExpressionBD; + +typedef enum XrLipExpressionBD { + XR_LIP_EXPRESSION_PP_BD = 0, + XR_LIP_EXPRESSION_CH_BD = 1, + XR_LIP_EXPRESSION_LO_BD = 2, + XR_LIP_EXPRESSION_O_BD = 3, + XR_LIP_EXPRESSION_I_BD = 4, + XR_LIP_EXPRESSION_LU_BD = 5, + XR_LIP_EXPRESSION_RR_BD = 6, + XR_LIP_EXPRESSION_XX_BD = 7, + XR_LIP_EXPRESSION_LAA_BD = 8, + XR_LIP_EXPRESSION_LI_BD = 9, + XR_LIP_EXPRESSION_FF_BD = 10, + XR_LIP_EXPRESSION_U_BD = 11, + XR_LIP_EXPRESSION_TH_BD = 12, + XR_LIP_EXPRESSION_LKK_BD = 13, + XR_LIP_EXPRESSION_SS_BD = 14, + XR_LIP_EXPRESSION_LE_BD = 15, + XR_LIP_EXPRESSION_DD_BD = 16, + XR_LIP_EXPRESSION_E_BD = 17, + XR_LIP_EXPRESSION_LNN_BD = 18, + XR_LIP_EXPRESSION_SIL_BD = 19, + XR_LIP_EXPRESSION_MAX_ENUM_BD = 0x7FFFFFFF +} XrLipExpressionBD; +// XrSystemFacialSimulationPropertiesBD extends XrSystemProperties +typedef struct XrSystemFacialSimulationPropertiesBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsFaceTracking; +} XrSystemFacialSimulationPropertiesBD; + +typedef struct XrFaceTrackerCreateInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrFacialSimulationModeBD mode; +} XrFaceTrackerCreateInfoBD; + +typedef struct XrFacialSimulationDataGetInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrTime time; +} XrFacialSimulationDataGetInfoBD; + +typedef struct XrFacialSimulationDataBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t faceExpressionWeightCount; + float* faceExpressionWeights; + XrBool32 isUpperFaceDataValid; + XrBool32 isLowerFaceDataValid; + XrTime time; +} XrFacialSimulationDataBD; + +// XrLipExpressionDataBD extends XrFacialSimulationDataBD +typedef struct XrLipExpressionDataBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t lipsyncExpressionWeightCount; + float* lipsyncExpressionWeights; +} XrLipExpressionDataBD; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateFacialSimulationModesBD)(XrSession session, uint32_t modeCapacityInput, uint32_t* modeCountOutput, XrFacialSimulationModeBD* modes); +typedef XrResult (XRAPI_PTR *PFN_xrCreateFaceTrackerBD)(XrSession session, const XrFaceTrackerCreateInfoBD* createInfo, XrFaceTrackerBD* tracker); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyFaceTrackerBD)(XrFaceTrackerBD tracker); +typedef XrResult (XRAPI_PTR *PFN_xrGetFacialSimulationDataBD)(XrFaceTrackerBD tracker, const XrFacialSimulationDataGetInfoBD*info, XrFacialSimulationDataBD* facialData); +typedef XrResult (XRAPI_PTR *PFN_xrSetFacialSimulationModeBD)(XrFaceTrackerBD tracker, XrFacialSimulationModeBD mode); +typedef XrResult (XRAPI_PTR *PFN_xrGetFacialSimulationModeBD)(XrFaceTrackerBD tracker, XrFacialSimulationModeBD*mode); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateFacialSimulationModesBD( + XrSession session, + uint32_t modeCapacityInput, + uint32_t* modeCountOutput, + XrFacialSimulationModeBD* modes); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateFaceTrackerBD( + XrSession session, + const XrFaceTrackerCreateInfoBD* createInfo, + XrFaceTrackerBD* tracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyFaceTrackerBD( + XrFaceTrackerBD tracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetFacialSimulationDataBD( + XrFaceTrackerBD tracker, + const XrFacialSimulationDataGetInfoBD* info, + XrFacialSimulationDataBD* facialData); + +XRAPI_ATTR XrResult XRAPI_CALL xrSetFacialSimulationModeBD( + XrFaceTrackerBD tracker, + XrFacialSimulationModeBD mode); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetFacialSimulationModeBD( + XrFaceTrackerBD tracker, + XrFacialSimulationModeBD* mode); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_BD_spatial_sensing is a preprocessor guard. Do not pass it to API calls. +#define XR_BD_spatial_sensing 1 +XR_DEFINE_ATOM(XrSpatialEntityIdBD) +XR_DEFINE_HANDLE(XrSenseDataProviderBD) +XR_DEFINE_HANDLE(XrSenseDataSnapshotBD) +XR_DEFINE_HANDLE(XrAnchorBD) +#define XR_BD_spatial_sensing_SPEC_VERSION 3 +#define XR_BD_SPATIAL_SENSING_EXTENSION_NAME "XR_BD_spatial_sensing" + +typedef enum XrSpatialEntityComponentTypeBD { + XR_SPATIAL_ENTITY_COMPONENT_TYPE_LOCATION_BD = 0, + XR_SPATIAL_ENTITY_COMPONENT_TYPE_SEMANTIC_BD = 1, + XR_SPATIAL_ENTITY_COMPONENT_TYPE_BOUNDING_BOX_2D_BD = 2, + XR_SPATIAL_ENTITY_COMPONENT_TYPE_POLYGON_BD = 3, + XR_SPATIAL_ENTITY_COMPONENT_TYPE_BOUNDING_BOX_3D_BD = 4, + XR_SPATIAL_ENTITY_COMPONENT_TYPE_TRIANGLE_MESH_BD = 5, + XR_SPATIAL_ENTITY_COMPONENT_TYPE_SPHERE_BD = 6, + XR_SPATIAL_ENTITY_COMPONENT_TYPE_PLANE_ORIENTATION_BD = 1000396000, + XR_SPATIAL_ENTITY_COMPONENT_TYPE_LIGHT_ESTIMATION_BD = 1000397000, + XR_SPATIAL_ENTITY_COMPONENT_TYPE_DYNAMIC_OBJECT_BD = 1000746000, + XR_SPATIAL_ENTITY_COMPONENT_TYPE_MAX_ENUM_BD = 0x7FFFFFFF +} XrSpatialEntityComponentTypeBD; + +typedef enum XrSemanticLabelBD { + XR_SEMANTIC_LABEL_UNKNOWN_BD = 0, + XR_SEMANTIC_LABEL_FLOOR_BD = 1, + XR_SEMANTIC_LABEL_CEILING_BD = 2, + XR_SEMANTIC_LABEL_WALL_BD = 3, + XR_SEMANTIC_LABEL_DOOR_BD = 4, + XR_SEMANTIC_LABEL_WINDOW_BD = 5, + XR_SEMANTIC_LABEL_OPENING_BD = 6, + XR_SEMANTIC_LABEL_TABLE_BD = 7, + XR_SEMANTIC_LABEL_SOFA_BD = 8, + XR_SEMANTIC_LABEL_CHAIR_BD = 9, + XR_SEMANTIC_LABEL_HUMAN_BD = 10, + XR_SEMANTIC_LABEL_BEAM_BD = 11, + XR_SEMANTIC_LABEL_COLUMN_BD = 12, + XR_SEMANTIC_LABEL_CURTAIN_BD = 13, + XR_SEMANTIC_LABEL_CABINET_BD = 14, + XR_SEMANTIC_LABEL_BED_BD = 15, + XR_SEMANTIC_LABEL_PLANT_BD = 16, + XR_SEMANTIC_LABEL_SCREEN_BD = 17, + XR_SEMANTIC_LABEL_VIRTUAL_WALL_BD = 18, + XR_SEMANTIC_LABEL_REFRIGERATOR_BD = 19, + XR_SEMANTIC_LABEL_WASHING_MACHINE_BD = 20, + XR_SEMANTIC_LABEL_AIR_CONDITIONER_BD = 21, + XR_SEMANTIC_LABEL_LAMP_BD = 22, + XR_SEMANTIC_LABEL_WALL_ART_BD = 23, + XR_SEMANTIC_LABEL_STAIRWAY_BD = 24, + XR_SEMANTIC_LABEL_KEYBOARD_BD = 25, + XR_SEMANTIC_LABEL_MOUSE_BD = 26, + XR_SEMANTIC_LABEL_LAPTOP_BD = 27, + XR_SEMANTIC_LABEL_MAX_ENUM_BD = 0x7FFFFFFF +} XrSemanticLabelBD; + +typedef enum XrSenseDataProviderTypeBD { + XR_SENSE_DATA_PROVIDER_TYPE_ANCHOR_BD = 1000390000, + XR_SENSE_DATA_PROVIDER_TYPE_SCENE_BD = 1000392000, + XR_SENSE_DATA_PROVIDER_TYPE_MESH_BD = 1000393000, + XR_SENSE_DATA_PROVIDER_TYPE_PLANE_BD = 1000396000, + XR_SENSE_DATA_PROVIDER_TYPE_LIGHT_ESTIMATION_BD = 1000397000, + XR_SENSE_DATA_PROVIDER_TYPE_DYNAMIC_OBJECT_BD = 1000746000, + XR_SENSE_DATA_PROVIDER_TYPE_MAX_ENUM_BD = 0x7FFFFFFF +} XrSenseDataProviderTypeBD; + +typedef enum XrSenseDataProviderStateBD { + XR_SENSE_DATA_PROVIDER_STATE_INITIALIZED_BD = 0, + XR_SENSE_DATA_PROVIDER_STATE_RUNNING_BD = 1, + XR_SENSE_DATA_PROVIDER_STATE_STOPPED_BD = 2, + XR_SENSE_DATA_PROVIDER_STATE_MAX_ENUM_BD = 0x7FFFFFFF +} XrSenseDataProviderStateBD; +// XrSystemSpatialSensingPropertiesBD extends XrSystemProperties +typedef struct XrSystemSpatialSensingPropertiesBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsSpatialSensing; +} XrSystemSpatialSensingPropertiesBD; + +typedef struct XrSpatialEntityComponentGetInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialEntityIdBD entityId; + XrSpatialEntityComponentTypeBD componentType; +} XrSpatialEntityComponentGetInfoBD; + +typedef struct XrSpatialEntityComponentDataBaseHeaderBD { + XrStructureType type; + void* XR_MAY_ALIAS next; +} XrSpatialEntityComponentDataBaseHeaderBD; + +// XrSpatialEntityLocationGetInfoBD extends XrSpatialEntityComponentGetInfoBD +typedef struct XrSpatialEntityLocationGetInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace baseSpace; +} XrSpatialEntityLocationGetInfoBD; + +// XrSpatialEntityComponentDataLocationBD extends XrSpatialEntityComponentDataBaseHeaderBD +typedef struct XrSpatialEntityComponentDataLocationBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrSpaceLocation location; +} XrSpatialEntityComponentDataLocationBD; + +// XrSpatialEntityComponentDataSemanticBD extends XrSpatialEntityComponentDataBaseHeaderBD +typedef struct XrSpatialEntityComponentDataSemanticBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t labelCapacityInput; + uint32_t labelCountOutput; + XrSemanticLabelBD* labels; +} XrSpatialEntityComponentDataSemanticBD; + +// XrSpatialEntityComponentDataBoundingBox2DBD extends XrSpatialEntityComponentDataBaseHeaderBD +typedef struct XrSpatialEntityComponentDataBoundingBox2DBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrRect2Df boundingBox2D; +} XrSpatialEntityComponentDataBoundingBox2DBD; + +// XrSpatialEntityComponentDataPolygonBD extends XrSpatialEntityComponentDataBaseHeaderBD +typedef struct XrSpatialEntityComponentDataPolygonBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t vertexCapacityInput; + uint32_t vertexCountOutput; + XrVector2f* vertices; +} XrSpatialEntityComponentDataPolygonBD; + +// XrSpatialEntityComponentDataBoundingBox3DBD extends XrSpatialEntityComponentDataBaseHeaderBD +typedef struct XrSpatialEntityComponentDataBoundingBox3DBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBoxf boundingBox3D; +} XrSpatialEntityComponentDataBoundingBox3DBD; + +// XrSpatialEntityComponentDataTriangleMeshBD extends XrSpatialEntityComponentDataBaseHeaderBD +typedef struct XrSpatialEntityComponentDataTriangleMeshBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t vertexCapacityInput; + uint32_t vertexCountOutput; + XrVector3f* vertices; + uint32_t indexCapacityInput; + uint32_t indexCountOutput; + uint16_t* indices; +} XrSpatialEntityComponentDataTriangleMeshBD; + +// XrSpatialEntityComponentDataSphereBD extends XrSpatialEntityComponentDataBaseHeaderBD +typedef struct XrSpatialEntityComponentDataSphereBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrSpheref sphere; +} XrSpatialEntityComponentDataSphereBD; + +typedef struct XrSenseDataProviderCreateInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSenseDataProviderTypeBD providerType; +} XrSenseDataProviderCreateInfoBD; + +typedef struct XrSenseDataProviderStartInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSenseDataProviderStartInfoBD; + +typedef struct XrEventDataSenseDataProviderStateChangedBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSenseDataProviderBD provider; + XrSenseDataProviderStateBD newState; +} XrEventDataSenseDataProviderStateChangedBD; + +typedef struct XrEventDataSenseDataUpdatedBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSenseDataProviderBD provider; +} XrEventDataSenseDataUpdatedBD; + +typedef struct XrSenseDataQueryInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSenseDataQueryInfoBD; + +typedef struct XrSenseDataQueryCompletionBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + XrSenseDataSnapshotBD snapshot; +} XrSenseDataQueryCompletionBD; + +typedef struct XrQueriedSenseDataGetInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrQueriedSenseDataGetInfoBD; + +typedef struct XrSpatialEntityStateBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrSpatialEntityIdBD entityId; + XrTime lastUpdateTime; + XrUuidEXT uuid; +} XrSpatialEntityStateBD; + +typedef struct XrQueriedSenseDataBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t stateCapacityInput; + uint32_t stateCountOutput; + XrSpatialEntityStateBD* states; +} XrQueriedSenseDataBD; + +// XrSenseDataFilterUuidBD extends XrSenseDataQueryInfoBD +typedef struct XrSenseDataFilterUuidBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t uuidCount; + const XrUuidEXT* uuids; +} XrSenseDataFilterUuidBD; + +// XrSenseDataFilterSemanticBD extends XrSenseDataQueryInfoBD +typedef struct XrSenseDataFilterSemanticBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t labelCount; + const XrSemanticLabelBD* labels; +} XrSenseDataFilterSemanticBD; + +typedef struct XrSpatialEntityAnchorCreateInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSenseDataSnapshotBD snapshot; + XrSpatialEntityIdBD entityId; +} XrSpatialEntityAnchorCreateInfoBD; + +typedef struct XrAnchorSpaceCreateInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAnchorBD anchor; + XrPosef poseInAnchorSpace; +} XrAnchorSpaceCreateInfoBD; + +typedef struct XrFutureCompletionEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; +} XrFutureCompletionEXT; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateSpatialEntityComponentTypesBD)(XrSenseDataSnapshotBD snapshot, XrSpatialEntityIdBD entityId, uint32_t componentTypeCapacityInput, uint32_t* componentTypeCountOutput, XrSpatialEntityComponentTypeBD* componentTypes); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpatialEntityUuidBD)(XrSenseDataSnapshotBD snapshot, XrSpatialEntityIdBD entityId, XrUuidEXT* uuid); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpatialEntityComponentDataBD)(XrSenseDataSnapshotBD snapshot, const XrSpatialEntityComponentGetInfoBD* getInfo, XrSpatialEntityComponentDataBaseHeaderBD* componentData); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSenseDataProviderBD)(XrSession session, const XrSenseDataProviderCreateInfoBD* createInfo, XrSenseDataProviderBD* provider); +typedef XrResult (XRAPI_PTR *PFN_xrStartSenseDataProviderAsyncBD)(XrSenseDataProviderBD provider, const XrSenseDataProviderStartInfoBD* startInfo, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrStartSenseDataProviderCompleteBD)(XrSession session, XrFutureEXT future, XrFutureCompletionEXT* completion); +typedef XrResult (XRAPI_PTR *PFN_xrGetSenseDataProviderStateBD)(XrSenseDataProviderBD provider, XrSenseDataProviderStateBD* state); +typedef XrResult (XRAPI_PTR *PFN_xrQuerySenseDataAsyncBD)(XrSenseDataProviderBD provider, const XrSenseDataQueryInfoBD* queryInfo, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrQuerySenseDataCompleteBD)(XrSenseDataProviderBD provider, XrFutureEXT future, XrSenseDataQueryCompletionBD* completion); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySenseDataSnapshotBD)(XrSenseDataSnapshotBD snapshot); +typedef XrResult (XRAPI_PTR *PFN_xrGetQueriedSenseDataBD)(XrSenseDataSnapshotBD snapshot, XrQueriedSenseDataGetInfoBD* getInfo, XrQueriedSenseDataBD* queriedSenseData); +typedef XrResult (XRAPI_PTR *PFN_xrStopSenseDataProviderBD)(XrSenseDataProviderBD provider); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySenseDataProviderBD)(XrSenseDataProviderBD provider); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialEntityAnchorBD)(XrSenseDataProviderBD provider, const XrSpatialEntityAnchorCreateInfoBD* createInfo, XrAnchorBD* anchor); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyAnchorBD)(XrAnchorBD anchor); +typedef XrResult (XRAPI_PTR *PFN_xrGetAnchorUuidBD)(XrAnchorBD anchor, XrUuidEXT* uuid); +typedef XrResult (XRAPI_PTR *PFN_xrCreateAnchorSpaceBD)(XrSession session, const XrAnchorSpaceCreateInfoBD* createInfo, XrSpace* space); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateSpatialEntityComponentTypesBD( + XrSenseDataSnapshotBD snapshot, + XrSpatialEntityIdBD entityId, + uint32_t componentTypeCapacityInput, + uint32_t* componentTypeCountOutput, + XrSpatialEntityComponentTypeBD* componentTypes); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpatialEntityUuidBD( + XrSenseDataSnapshotBD snapshot, + XrSpatialEntityIdBD entityId, + XrUuidEXT* uuid); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpatialEntityComponentDataBD( + XrSenseDataSnapshotBD snapshot, + const XrSpatialEntityComponentGetInfoBD* getInfo, + XrSpatialEntityComponentDataBaseHeaderBD* componentData); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSenseDataProviderBD( + XrSession session, + const XrSenseDataProviderCreateInfoBD* createInfo, + XrSenseDataProviderBD* provider); + +XRAPI_ATTR XrResult XRAPI_CALL xrStartSenseDataProviderAsyncBD( + XrSenseDataProviderBD provider, + const XrSenseDataProviderStartInfoBD* startInfo, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrStartSenseDataProviderCompleteBD( + XrSession session, + XrFutureEXT future, + XrFutureCompletionEXT* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSenseDataProviderStateBD( + XrSenseDataProviderBD provider, + XrSenseDataProviderStateBD* state); + +XRAPI_ATTR XrResult XRAPI_CALL xrQuerySenseDataAsyncBD( + XrSenseDataProviderBD provider, + const XrSenseDataQueryInfoBD* queryInfo, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrQuerySenseDataCompleteBD( + XrSenseDataProviderBD provider, + XrFutureEXT future, + XrSenseDataQueryCompletionBD* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySenseDataSnapshotBD( + XrSenseDataSnapshotBD snapshot); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetQueriedSenseDataBD( + XrSenseDataSnapshotBD snapshot, + XrQueriedSenseDataGetInfoBD* getInfo, + XrQueriedSenseDataBD* queriedSenseData); + +XRAPI_ATTR XrResult XRAPI_CALL xrStopSenseDataProviderBD( + XrSenseDataProviderBD provider); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySenseDataProviderBD( + XrSenseDataProviderBD provider); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialEntityAnchorBD( + XrSenseDataProviderBD provider, + const XrSpatialEntityAnchorCreateInfoBD* createInfo, + XrAnchorBD* anchor); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyAnchorBD( + XrAnchorBD anchor); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetAnchorUuidBD( + XrAnchorBD anchor, + XrUuidEXT* uuid); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateAnchorSpaceBD( + XrSession session, + const XrAnchorSpaceCreateInfoBD* createInfo, + XrSpace* space); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_BD_spatial_anchor is a preprocessor guard. Do not pass it to API calls. +#define XR_BD_spatial_anchor 1 +#define XR_BD_spatial_anchor_SPEC_VERSION 2 +#define XR_BD_SPATIAL_ANCHOR_EXTENSION_NAME "XR_BD_spatial_anchor" + +typedef enum XrPersistenceLocationBD { + XR_PERSISTENCE_LOCATION_LOCAL_BD = 0, + XR_PERSISTENCE_LOCATION_MAX_ENUM_BD = 0x7FFFFFFF +} XrPersistenceLocationBD; +// XrSystemSpatialAnchorPropertiesBD extends XrSystemProperties +typedef struct XrSystemSpatialAnchorPropertiesBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsSpatialAnchor; +} XrSystemSpatialAnchorPropertiesBD; + +typedef struct XrSpatialAnchorCreateInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace space; + XrPosef pose; + XrTime time; +} XrSpatialAnchorCreateInfoBD; + +typedef struct XrSpatialAnchorCreateCompletionBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + XrUuidEXT uuid; + XrAnchorBD anchor; +} XrSpatialAnchorCreateCompletionBD; + +typedef struct XrSpatialAnchorPersistInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPersistenceLocationBD location; + XrAnchorBD anchor; +} XrSpatialAnchorPersistInfoBD; + +typedef struct XrSpatialAnchorUnpersistInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPersistenceLocationBD location; + XrAnchorBD anchor; +} XrSpatialAnchorUnpersistInfoBD; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorAsyncBD)(XrSenseDataProviderBD provider, const XrSpatialAnchorCreateInfoBD* info, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorCompleteBD)(XrSenseDataProviderBD provider, XrFutureEXT future, XrSpatialAnchorCreateCompletionBD* completion); +typedef XrResult (XRAPI_PTR *PFN_xrPersistSpatialAnchorAsyncBD)(XrSenseDataProviderBD provider, const XrSpatialAnchorPersistInfoBD* info, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrPersistSpatialAnchorCompleteBD)(XrSenseDataProviderBD provider, XrFutureEXT future, XrFutureCompletionEXT* completion); +typedef XrResult (XRAPI_PTR *PFN_xrUnpersistSpatialAnchorAsyncBD)(XrSenseDataProviderBD provider, const XrSpatialAnchorUnpersistInfoBD* info, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrUnpersistSpatialAnchorCompleteBD)(XrSenseDataProviderBD provider, XrFutureEXT future, XrFutureCompletionEXT* completion); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorAsyncBD( + XrSenseDataProviderBD provider, + const XrSpatialAnchorCreateInfoBD* info, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorCompleteBD( + XrSenseDataProviderBD provider, + XrFutureEXT future, + XrSpatialAnchorCreateCompletionBD* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrPersistSpatialAnchorAsyncBD( + XrSenseDataProviderBD provider, + const XrSpatialAnchorPersistInfoBD* info, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrPersistSpatialAnchorCompleteBD( + XrSenseDataProviderBD provider, + XrFutureEXT future, + XrFutureCompletionEXT* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrUnpersistSpatialAnchorAsyncBD( + XrSenseDataProviderBD provider, + const XrSpatialAnchorUnpersistInfoBD* info, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrUnpersistSpatialAnchorCompleteBD( + XrSenseDataProviderBD provider, + XrFutureEXT future, + XrFutureCompletionEXT* completion); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_BD_spatial_anchor_sharing is a preprocessor guard. Do not pass it to API calls. +#define XR_BD_spatial_anchor_sharing 1 +#define XR_BD_spatial_anchor_sharing_SPEC_VERSION 2 +#define XR_BD_SPATIAL_ANCHOR_SHARING_EXTENSION_NAME "XR_BD_spatial_anchor_sharing" +// XrSystemSpatialAnchorSharingPropertiesBD extends XrSystemProperties +typedef struct XrSystemSpatialAnchorSharingPropertiesBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsSpatialAnchorSharing; +} XrSystemSpatialAnchorSharingPropertiesBD; + +typedef struct XrSpatialAnchorShareInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAnchorBD anchor; +} XrSpatialAnchorShareInfoBD; + +typedef struct XrSharedSpatialAnchorDownloadInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrUuidEXT uuid; +} XrSharedSpatialAnchorDownloadInfoBD; + +typedef XrResult (XRAPI_PTR *PFN_xrShareSpatialAnchorAsyncBD)(XrSenseDataProviderBD provider, const XrSpatialAnchorShareInfoBD* info, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrShareSpatialAnchorCompleteBD)(XrSenseDataProviderBD provider, XrFutureEXT future, XrFutureCompletionEXT* completion); +typedef XrResult (XRAPI_PTR *PFN_xrDownloadSharedSpatialAnchorAsyncBD)(XrSenseDataProviderBD provider, const XrSharedSpatialAnchorDownloadInfoBD* info, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrDownloadSharedSpatialAnchorCompleteBD)(XrSenseDataProviderBD provider, XrFutureEXT future, XrFutureCompletionEXT* completion); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrShareSpatialAnchorAsyncBD( + XrSenseDataProviderBD provider, + const XrSpatialAnchorShareInfoBD* info, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrShareSpatialAnchorCompleteBD( + XrSenseDataProviderBD provider, + XrFutureEXT future, + XrFutureCompletionEXT* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrDownloadSharedSpatialAnchorAsyncBD( + XrSenseDataProviderBD provider, + const XrSharedSpatialAnchorDownloadInfoBD* info, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrDownloadSharedSpatialAnchorCompleteBD( + XrSenseDataProviderBD provider, + XrFutureEXT future, + XrFutureCompletionEXT* completion); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_BD_spatial_scene is a preprocessor guard. Do not pass it to API calls. +#define XR_BD_spatial_scene 1 +#define XR_BD_spatial_scene_SPEC_VERSION 1 +#define XR_BD_SPATIAL_SCENE_EXTENSION_NAME "XR_BD_spatial_scene" +// XrSystemSpatialScenePropertiesBD extends XrSystemProperties +typedef struct XrSystemSpatialScenePropertiesBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsSpatialScene; +} XrSystemSpatialScenePropertiesBD; + +typedef struct XrSceneCaptureInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSceneCaptureInfoBD; + +typedef XrResult (XRAPI_PTR *PFN_xrCaptureSceneAsyncBD)(XrSenseDataProviderBD provider, const XrSceneCaptureInfoBD* info, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrCaptureSceneCompleteBD)(XrSenseDataProviderBD provider, XrFutureEXT future, XrFutureCompletionEXT* completion); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCaptureSceneAsyncBD( + XrSenseDataProviderBD provider, + const XrSceneCaptureInfoBD* info, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrCaptureSceneCompleteBD( + XrSenseDataProviderBD provider, + XrFutureEXT future, + XrFutureCompletionEXT* completion); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_BD_spatial_mesh is a preprocessor guard. Do not pass it to API calls. +#define XR_BD_spatial_mesh 1 +#define XR_BD_spatial_mesh_SPEC_VERSION 1 +#define XR_BD_SPATIAL_MESH_EXTENSION_NAME "XR_BD_spatial_mesh" + +typedef enum XrSpatialMeshLodBD { + XR_SPATIAL_MESH_LOD_COARSE_BD = 0, + XR_SPATIAL_MESH_LOD_MEDIUM_BD = 1, + XR_SPATIAL_MESH_LOD_FINE_BD = 2, + XR_SPATIAL_MESH_LOD_MAX_ENUM_BD = 0x7FFFFFFF +} XrSpatialMeshLodBD; +typedef XrFlags64 XrSpatialMeshConfigFlagsBD; + +// Flag bits for XrSpatialMeshConfigFlagsBD +static const XrSpatialMeshConfigFlagsBD XR_SPATIAL_MESH_CONFIG_SEMANTIC_BIT_BD = 0x00000001; +static const XrSpatialMeshConfigFlagsBD XR_SPATIAL_MESH_CONFIG_ALIGN_SEMANTIC_WITH_VERTEX_BIT_BD = 0x00000002; + +// XrSystemSpatialMeshPropertiesBD extends XrSystemProperties +typedef struct XrSystemSpatialMeshPropertiesBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsSpatialMesh; +} XrSystemSpatialMeshPropertiesBD; + +// XrSenseDataProviderCreateInfoSpatialMeshBD extends XrSenseDataProviderCreateInfoBD +typedef struct XrSenseDataProviderCreateInfoSpatialMeshBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialMeshConfigFlagsBD configFlags; + XrSpatialMeshLodBD lod; +} XrSenseDataProviderCreateInfoSpatialMeshBD; + + + +// XR_BD_future_progress is a preprocessor guard. Do not pass it to API calls. +#define XR_BD_future_progress 1 +#define XR_BD_future_progress_SPEC_VERSION 1 +#define XR_BD_FUTURE_PROGRESS_EXTENSION_NAME "XR_BD_future_progress" +// XrFuturePollResultProgressBD extends XrFuturePollResultEXT +typedef struct XrFuturePollResultProgressBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 isSupported; + uint32_t progressPercentage; +} XrFuturePollResultProgressBD; + + + +// XR_BD_body_tracking_auxiliary_metrics is a preprocessor guard. Do not pass it to API calls. +#define XR_BD_body_tracking_auxiliary_metrics 1 +#define XR_BD_body_tracking_auxiliary_metrics_SPEC_VERSION 1 +#define XR_BD_BODY_TRACKING_AUXILIARY_METRICS_EXTENSION_NAME "XR_BD_body_tracking_auxiliary_metrics" + +typedef enum XrBodyTrackingPostureBD { + XR_BODY_TRACKING_POSTURE_STOMP_BD = 1, + XR_BODY_TRACKING_POSTURE_STATIC_BD = 2, + XR_BODY_TRACKING_POSTURE_MAX_ENUM_BD = 0x7FFFFFFF +} XrBodyTrackingPostureBD; + +typedef enum XrBodyTrackingStatusBD { + XR_BODY_TRACKING_STATUS_INVALID_BD = 0, + XR_BODY_TRACKING_STATUS_VALID_BD = 1, + XR_BODY_TRACKING_STATUS_LIMITED_BD = 2, + XR_BODY_TRACKING_STATUS_MAX_ENUM_BD = 0x7FFFFFFF +} XrBodyTrackingStatusBD; + +typedef enum XrBodyTrackingMessageBD { + XR_BODY_TRACKING_MESSAGE_NO_ERROR_BD = 0, + XR_BODY_TRACKING_MESSAGE_TRACKER_NOT_CALIBRATED_BD = 1, + XR_BODY_TRACKING_MESSAGE_TRACKER_NUM_NOT_ENOUGH_BD = 2, + XR_BODY_TRACKING_MESSAGE_TRACKER_STATE_NOT_SATISFIED_BD = 3, + XR_BODY_TRACKING_MESSAGE_TRACKER_PERSISTENT_INVISIBILITY_BD = 4, + XR_BODY_TRACKING_MESSAGE_TRACKER_DATA_ERROR_BD = 5, + XR_BODY_TRACKING_MESSAGE_USER_CHANGE_BD = 6, + XR_BODY_TRACKING_MESSAGE_TRACKING_POSE_ERROR_BD = 7, + XR_BODY_TRACKING_MESSAGE_MAX_ENUM_BD = 0x7FFFFFFF +} XrBodyTrackingMessageBD; +typedef XrFlags64 XrSpaceAccelerationFlagsBD; + +// Flag bits for XrSpaceAccelerationFlagsBD +static const XrSpaceAccelerationFlagsBD XR_SPACE_ACCELERATION_LINEAR_VALID_BIT_BD = 0x00000001; +static const XrSpaceAccelerationFlagsBD XR_SPACE_ACCELERATION_ANGULAR_VALID_BIT_BD = 0x00000002; + +// XrBodyTrackingPostureDataBD extends XrBodyJointLocationsBD +typedef struct XrBodyTrackingPostureDataBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t postureCount; + XrBodyTrackingPostureBD* postureData; +} XrBodyTrackingPostureDataBD; + +typedef struct XrBodyJointVelocityBD { + XrSpaceVelocityFlags velocityFlags; + XrVector3f linearVelocity; + XrVector3f angularVelocity; +} XrBodyJointVelocityBD; + +// XrBodyJointVelocitiesBD extends XrBodyJointLocationsBD +typedef struct XrBodyJointVelocitiesBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t velocityCount; + XrBodyJointVelocityBD* velocities; +} XrBodyJointVelocitiesBD; + +typedef struct XrBodyJointAccelerationBD { + XrSpaceAccelerationFlagsBD accelerationFlags; + XrVector3f linearAcceleration; + XrVector3f angularAcceleration; +} XrBodyJointAccelerationBD; + +// XrBodyJointAccelerationsBD extends XrBodyJointLocationsBD +typedef struct XrBodyJointAccelerationsBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t accelerationCount; + XrBodyJointAccelerationBD* accelerations; +} XrBodyJointAccelerationsBD; + +typedef struct XrBodyTrackingStateBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBodyTrackingStatusBD status; + XrBodyTrackingMessageBD message; +} XrBodyTrackingStateBD; + +typedef XrResult (XRAPI_PTR *PFN_xrStartBodyTrackingCalibrationAppBD)(XrSession session); +typedef XrResult (XRAPI_PTR *PFN_xrGetBodyTrackingStateBD)(XrSession session, XrBodyTrackingStateBD* state); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrStartBodyTrackingCalibrationAppBD( + XrSession session); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetBodyTrackingStateBD( + XrSession session, + XrBodyTrackingStateBD* state); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_BD_spatial_plane is a preprocessor guard. Do not pass it to API calls. +#define XR_BD_spatial_plane 1 +#define XR_BD_spatial_plane_SPEC_VERSION 1 +#define XR_BD_SPATIAL_PLANE_EXTENSION_NAME "XR_BD_spatial_plane" + +typedef enum XrPlaneOrientationBD { + XR_PLANE_ORIENTATION_HORIZONTAL_UPWARD_BD = 0, + XR_PLANE_ORIENTATION_HORIZONTAL_DOWNWARD_BD = 1, + XR_PLANE_ORIENTATION_VERTICAL_BD = 2, + XR_PLANE_ORIENTATION_ARBITRARY_BD = 3, + XR_PLANE_ORIENTATION_MAX_ENUM_BD = 0x7FFFFFFF +} XrPlaneOrientationBD; +// XrSystemSpatialPlanePropertiesBD extends XrSystemProperties +typedef struct XrSystemSpatialPlanePropertiesBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsSpatialPlane; +} XrSystemSpatialPlanePropertiesBD; + +// XrSpatialEntityComponentDataPlaneOrientationBD extends XrSpatialEntityComponentDataBaseHeaderBD +typedef struct XrSpatialEntityComponentDataPlaneOrientationBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrPlaneOrientationBD orientation; +} XrSpatialEntityComponentDataPlaneOrientationBD; + +// XrSenseDataFilterPlaneOrientationBD extends XrSenseDataQueryInfoBD +typedef struct XrSenseDataFilterPlaneOrientationBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t orientationCount; + XrPlaneOrientationBD* orientations; +} XrSenseDataFilterPlaneOrientationBD; + + + +// XR_BD_spatial_light_estimation is a preprocessor guard. Do not pass it to API calls. +#define XR_BD_spatial_light_estimation 1 +#define XR_BD_spatial_light_estimation_SPEC_VERSION 1 +#define XR_BD_SPATIAL_LIGHT_ESTIMATION_EXTENSION_NAME "XR_BD_spatial_light_estimation" + +typedef enum XrEnvironmentTexturePixelFormatBD { + XR_ENVIRONMENT_TEXTURE_PIXEL_FORMAT_RGB_16FLOAT_BD = 0, + XR_ENVIRONMENT_TEXTURE_PIXEL_FORMAT_RGBA_16FLOAT_BD = 1, + XR_ENVIRONMENT_TEXTURE_PIXEL_FORMAT_MAX_ENUM_BD = 0x7FFFFFFF +} XrEnvironmentTexturePixelFormatBD; + +typedef enum XrEnvironmentTextureResolutionBD { + XR_ENVIRONMENT_TEXTURE_RESOLUTION_8_8_BD = 0, + XR_ENVIRONMENT_TEXTURE_RESOLUTION_16_16_BD = 1, + XR_ENVIRONMENT_TEXTURE_RESOLUTION_32_32_BD = 2, + XR_ENVIRONMENT_TEXTURE_RESOLUTION_64_64_BD = 3, + XR_ENVIRONMENT_TEXTURE_RESOLUTION_128_128_BD = 4, + XR_ENVIRONMENT_TEXTURE_RESOLUTION_MAX_ENUM_BD = 0x7FFFFFFF +} XrEnvironmentTextureResolutionBD; + +typedef enum XrEnvironmentTextureTransferTypeBD { + XR_ENVIRONMENT_TEXTURE_TRANSFER_TYPE_RAW_BD = 0, + XR_ENVIRONMENT_TEXTURE_TRANSFER_TYPE_MAX_ENUM_BD = 0x7FFFFFFF +} XrEnvironmentTextureTransferTypeBD; + +typedef enum XrSphericalHarmonicsKindBD { + XR_SPHERICAL_HARMONICS_KIND_TOTAL_BD = 0, + XR_SPHERICAL_HARMONICS_KIND_MAX_ENUM_BD = 0x7FFFFFFF +} XrSphericalHarmonicsKindBD; +typedef XrFlags64 XrLightEstimationCreateFlagsBD; + +// Flag bits for XrLightEstimationCreateFlagsBD +static const XrLightEstimationCreateFlagsBD XR_LIGHT_ESTIMATION_CREATE_SPHERICAL_HARMONICS_BIT_BD = 0x00000001; +static const XrLightEstimationCreateFlagsBD XR_LIGHT_ESTIMATION_CREATE_ENVIRONMENT_TEXTURE_BIT_BD = 0x00000002; + +// XrSystemLightEstimationPropertiesBD extends XrSystemProperties +typedef struct XrSystemLightEstimationPropertiesBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsLightEstimation; + XrBool32 supportsEnvironmentTexture; + XrBool32 supportsSphericalHarmonics; +} XrSystemLightEstimationPropertiesBD; + +// XrSenseDataProviderCreateInfoLightEstimationBD extends XrSenseDataProviderCreateInfoBD +typedef struct XrSenseDataProviderCreateInfoLightEstimationBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrLightEstimationCreateFlagsBD createFlags; +} XrSenseDataProviderCreateInfoLightEstimationBD; + +// XrEnvironmentTextureCreateConfigInfoBD extends XrSenseDataProviderCreateInfoBD +typedef struct XrEnvironmentTextureCreateConfigInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrEnvironmentTexturePixelFormatBD pixelFormat; + XrEnvironmentTextureResolutionBD resolution; + XrEnvironmentTextureTransferTypeBD transferType; +} XrEnvironmentTextureCreateConfigInfoBD; + +// XrLightEstimationDataEnvironmentTextureRawBD extends XrSpatialEntityComponentDataLightEstimationBD +typedef struct XrLightEstimationDataEnvironmentTextureRawBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrEnvironmentTexturePixelFormatBD pixelFormat; + uint32_t cubemapFaceBufferSize; + uint8_t* rightCubemapFaceBuffer; + uint8_t* leftCubemapFaceBuffer; + uint8_t* topCubemapFaceBuffer; + uint8_t* bottomCubemapFaceBuffer; + uint8_t* frontCubemapFaceBuffer; + uint8_t* backCubemapFaceBuffer; +} XrLightEstimationDataEnvironmentTextureRawBD; + +// XrLightEstimationDataSphericalHarmonicsBD extends XrSpatialEntityComponentDataLightEstimationBD +typedef struct XrLightEstimationDataSphericalHarmonicsBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSphericalHarmonicsKindBD kind; + uint32_t coefficientCapacityInput; + uint32_t coefficientCountOutput; + float* coefficients; +} XrLightEstimationDataSphericalHarmonicsBD; + +// XrSpatialEntityComponentDataLightEstimationBD extends XrSpatialEntityComponentDataBaseHeaderBD +typedef struct XrSpatialEntityComponentDataLightEstimationBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 isValid; +} XrSpatialEntityComponentDataLightEstimationBD; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateEnvironmentTextureResolutionsBD)(XrSession session, uint32_t resolutionCapacityInput, uint32_t* resolutionCountOutput, XrEnvironmentTextureResolutionBD* resolutions); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateEnvironmentTexturePixelFormatsBD)(XrSession session, uint32_t pixelFormatCapacityInput, uint32_t* pixelFormatCountOutput, XrEnvironmentTexturePixelFormatBD* pixelFormats); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateEnvironmentTextureTransferTypesBD)(XrSession session, uint32_t transferTypeCapacityInput, uint32_t* transferTypeCountOutput, XrEnvironmentTextureTransferTypeBD* transferTypes); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateEnvironmentTextureResolutionsBD( + XrSession session, + uint32_t resolutionCapacityInput, + uint32_t* resolutionCountOutput, + XrEnvironmentTextureResolutionBD* resolutions); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateEnvironmentTexturePixelFormatsBD( + XrSession session, + uint32_t pixelFormatCapacityInput, + uint32_t* pixelFormatCountOutput, + XrEnvironmentTexturePixelFormatBD* pixelFormats); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateEnvironmentTextureTransferTypesBD( + XrSession session, + uint32_t transferTypeCapacityInput, + uint32_t* transferTypeCountOutput, + XrEnvironmentTextureTransferTypeBD* transferTypes); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_BD_ultra_controller_interaction is a preprocessor guard. Do not pass it to API calls. +#define XR_BD_ultra_controller_interaction 1 +#define XR_BD_ultra_controller_interaction_SPEC_VERSION 1 +#define XR_BD_ULTRA_CONTROLLER_INTERACTION_EXTENSION_NAME "XR_BD_ultra_controller_interaction" + + +// XR_BD_spatial_audio_rendering is a preprocessor guard. Do not pass it to API calls. +#define XR_BD_spatial_audio_rendering 1 +XR_DEFINE_HANDLE(XrSpatialAudioRendererBD) +XR_DEFINE_HANDLE(XrSoundFieldBD) +XR_DEFINE_HANDLE(XrSoundObjectBD) +XR_DEFINE_HANDLE(XrSoundObstacleBD) +XR_DEFINE_HANDLE(XrSoundObstacleMaterialBD) +#define XR_BD_spatial_audio_rendering_SPEC_VERSION 1 +#define XR_BD_SPATIAL_AUDIO_RENDERING_EXTENSION_NAME "XR_BD_spatial_audio_rendering" + +typedef enum XrAudioSampleRateBD { + XR_AUDIO_SAMPLE_RATE_192000_HZ_BD = 1, + XR_AUDIO_SAMPLE_RATE_96000_HZ_BD = 2, + XR_AUDIO_SAMPLE_RATE_48000_HZ_BD = 3, + XR_AUDIO_SAMPLE_RATE_44100_HZ_BD = 4, + XR_AUDIO_SAMPLE_RATE_32000_HZ_BD = 5, + XR_AUDIO_SAMPLE_RATE_24000_HZ_BD = 6, + XR_AUDIO_SAMPLE_RATE_22050_HZ_BD = 7, + XR_AUDIO_SAMPLE_RATE_16000_HZ_BD = 8, + XR_AUDIO_SAMPLE_RATE_12000_HZ_BD = 9, + XR_AUDIO_SAMPLE_RATE_11025_HZ_BD = 10, + XR_AUDIO_SAMPLE_RATE_8000_HZ_BD = 11, + XR_AUDIO_SAMPLE_RATE_MAX_ENUM_BD = 0x7FFFFFFF +} XrAudioSampleRateBD; + +typedef enum XrAudioBufferChannelLayoutBD { + XR_AUDIO_BUFFER_CHANNEL_LAYOUT_INTERLEAVED_BD = 0, + XR_AUDIO_BUFFER_CHANNEL_LAYOUT_PLANAR_BD = 1, + XR_AUDIO_BUFFER_CHANNEL_LAYOUT_MAX_ENUM_BD = 0x7FFFFFFF +} XrAudioBufferChannelLayoutBD; + +typedef enum XrSoundObjectDistanceAttenuationTypeBD { + XR_SOUND_OBJECT_DISTANCE_ATTENUATION_TYPE_NONE_BD = 0, + XR_SOUND_OBJECT_DISTANCE_ATTENUATION_TYPE_INVERSE_SQUARE_BD = 1, + XR_SOUND_OBJECT_DISTANCE_ATTENUATION_TYPE_ROLLOFF_BD = 2, + XR_SOUND_OBJECT_DISTANCE_ATTENUATION_TYPE_CUSTOMIZED_BD = 100, + XR_SOUND_OBJECT_DISTANCE_ATTENUATION_TYPE_MAX_ENUM_BD = 0x7FFFFFFF +} XrSoundObjectDistanceAttenuationTypeBD; + +typedef enum XrSoundFieldChannelMaskSurroundBD { + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_FRONT_LEFT_BD = 1, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_FRONT_RIGHT_BD = 2, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_FRONT_CENTER_BD = 4, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_LOW_FREQUENCY_BD = 8, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_BACK_LEFT_BD = 16, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_BACK_RIGHT_BD = 32, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SIDE_LEFT_BD = 64, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SIDE_RIGHT_BD = 128, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_TOP_FRONT_LEFT_BD = 256, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_TOP_FRONT_RIGHT_BD = 512, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_TOP_BACK_LEFT_BD = 1024, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_TOP_BACK_RIGHT_BD = 2048, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_BACK_CENTER_BD = 4096, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_TOP_SIDE_LEFT_BD = 8192, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_TOP_SIDE_RIGHT_BD = 16384, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SETUP_STEREO_BD = 3, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SETUP_2_1_BD = 11, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SETUP_3_0_BD = 7, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SETUP_4_0_BD = 4099, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SETUP_BACK_SURROUND_BD = 48, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SETUP_QUAD_BD = 51, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SETUP_3_1_BD = 15, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SETUP_5_0_BD = 55, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SETUP_SIDE_SURROUND_BD = 192, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SETUP_5_0_SIDE_BD = 199, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SETUP_5_1_BD = 63, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SETUP_5_1_SIDE_BD = 207, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SETUP_7_0_BD = 247, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SETUP_7_1_BD = 255, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SETUP_5_1_2_BD = 831, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SETUP_5_1_4_BD = 3903, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SETUP_7_1_2_BD = 24831, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_SETUP_7_1_4_BD = 4095, + XR_SOUND_FIELD_CHANNEL_MASK_SURROUND_MAX_ENUM_BD = 0x7FFFFFFF +} XrSoundFieldChannelMaskSurroundBD; + +typedef enum XrSoundFieldChannelMaskAmbixBD { + XR_SOUND_FIELD_CHANNEL_MASK_AMBIX_1ST_ORDER_BD = 1, + XR_SOUND_FIELD_CHANNEL_MASK_AMBIX_2ND_ORDER_BD = 2, + XR_SOUND_FIELD_CHANNEL_MASK_AMBIX_3RD_ORDER_BD = 3, + XR_SOUND_FIELD_CHANNEL_MASK_AMBIX_4TH_ORDER_BD = 4, + XR_SOUND_FIELD_CHANNEL_MASK_AMBIX_5TH_ORDER_BD = 5, + XR_SOUND_FIELD_CHANNEL_MASK_AMBIX_6TH_ORDER_BD = 6, + XR_SOUND_FIELD_CHANNEL_MASK_AMBIX_7TH_ORDER_BD = 7, + XR_SOUND_FIELD_CHANNEL_MASK_AMBIX_MAX_ENUM_BD = 0x7FFFFFFF +} XrSoundFieldChannelMaskAmbixBD; + +typedef enum XrSoundFieldChannelMaskFumaBD { + XR_SOUND_FIELD_CHANNEL_MASK_FUMA_1ST_ORDER_BD = 1, + XR_SOUND_FIELD_CHANNEL_MASK_FUMA_MAX_ENUM_BD = 0x7FFFFFFF +} XrSoundFieldChannelMaskFumaBD; + +typedef enum XrSoundObstacleMaterialTypeBD { + XR_SOUND_OBSTACLE_MATERIAL_TYPE_ACOUSTIC_TILE_BD = 0, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_BRICK_BD = 1, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_BRICK_PAINTED_BD = 2, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_CARPET_BD = 3, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_CARPET_HEAVY_BD = 4, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_CARPET_HEAVY_PADDED_BD = 5, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_CERAMIC_TILE_BD = 6, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_CONCRETE_BD = 7, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_CONCRETE_ROUGH_BD = 8, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_CONCRETE_BLOCK_BD = 9, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_CONCRETE_BLOCK_PAINTED_BD = 10, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_CURTAIN_BD = 11, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_FOLIAGE_BD = 12, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_GLASS_BD = 13, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_GLASS_HEAVY_BD = 14, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_GRASS_BD = 15, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_GRAVEL_BD = 16, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_GYPSUM_BOARD_BD = 17, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_PLASTER_ON_BRICK_BD = 18, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_PLASTER_ON_CONCRETE_BLOCK_BD = 19, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_SOIL_BD = 20, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_SOUND_PROOF_BD = 21, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_SNOW_BD = 22, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_STEEL_BD = 23, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_WATER_BD = 24, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_WOOD_THIN_BD = 25, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_WOOD_THICK_BD = 26, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_WOOD_FLOOR_BD = 27, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_WOOD_ON_CONCRETE_BD = 28, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_CUSTOM_BD = 29, + XR_SOUND_OBSTACLE_MATERIAL_TYPE_MAX_ENUM_BD = 0x7FFFFFFF +} XrSoundObstacleMaterialTypeBD; +typedef XrFlags64 XrSoundObstacleFlagsBD; + +// Flag bits for XrSoundObstacleFlagsBD +static const XrSoundObstacleFlagsBD XR_SOUND_OBSTACLE_ENABLED_BIT_BD = 0x00000001; +static const XrSoundObstacleFlagsBD XR_SOUND_OBSTACLE_POSE_BIT_BD = 0x00000002; +static const XrSoundObstacleFlagsBD XR_SOUND_OBSTACLE_MESH_BIT_BD = 0x00000004; +static const XrSoundObstacleFlagsBD XR_SOUND_OBSTACLE_MATERIALS_BIT_BD = 0x00000008; + +typedef XrFlags64 XrSoundObjectFlagsBD; + +// Flag bits for XrSoundObjectFlagsBD +static const XrSoundObjectFlagsBD XR_SOUND_OBJECT_ENABLED_BIT_BD = 0x00000001; +static const XrSoundObjectFlagsBD XR_SOUND_OBJECT_POSE_BIT_BD = 0x00000002; +static const XrSoundObjectFlagsBD XR_SOUND_OBJECT_DIRECTIVITY_BIT_BD = 0x00000004; +static const XrSoundObjectFlagsBD XR_SOUND_OBJECT_SHAPE_BIT_BD = 0x00000008; +static const XrSoundObjectFlagsBD XR_SOUND_OBJECT_MAIN_VOLUME_BIT_BD = 0x00000010; +static const XrSoundObjectFlagsBD XR_SOUND_OBJECT_REFLECTION_GAIN_BIT_BD = 0x00000020; +static const XrSoundObjectFlagsBD XR_SOUND_OBJECT_ENABLE_DOPPLER_BIT_BD = 0x00000040; +static const XrSoundObjectFlagsBD XR_SOUND_OBJECT_DIRECT_SOUND_ATTENUATION_BIT_BD = 0x00000080; +static const XrSoundObjectFlagsBD XR_SOUND_OBJECT_INDIRECT_SOUND_ATTENUATION_BIT_BD = 0x00000100; + +typedef XrFlags64 XrSoundFieldFlagsBD; + +// Flag bits for XrSoundFieldFlagsBD +static const XrSoundFieldFlagsBD XR_SOUND_FIELD_ENABLED_BIT_BD = 0x00000001; +static const XrSoundFieldFlagsBD XR_SOUND_FIELD_ORIENTATION_BIT_BD = 0x00000002; +static const XrSoundFieldFlagsBD XR_SOUND_FIELD_MAIN_VOLUME_BIT_BD = 0x00000004; +static const XrSoundFieldFlagsBD XR_SOUND_FIELD_LFE_GAIN_BIT_BD = 0x00000008; + +typedef struct XrSpatialAudioRendererCreateInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t framesPerBuffer; + XrAudioSampleRateBD sampleRate; +} XrSpatialAudioRendererCreateInfoBD; + +typedef struct XrAudioBufferBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAudioBufferChannelLayoutBD channelLayout; + uint32_t bufferChannels; + uint32_t bufferLength; + float* buffer; +} XrAudioBufferBD; + +// XrSoundObjectDirectivityCardioidBD extends XrSoundObjectConfigBD +typedef struct XrSoundObjectDirectivityCardioidBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + float alpha; + float order; +} XrSoundObjectDirectivityCardioidBD; + +// XrSoundObjectShapeSphereBD extends XrSoundObjectConfigBD +typedef struct XrSoundObjectShapeSphereBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + float radius; +} XrSoundObjectShapeSphereBD; + +typedef struct XrAttenuationCurvePointBD { + float distance; + float gain; +} XrAttenuationCurvePointBD; + +typedef struct XrSoundObjectDistanceAttenuationCurveBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t curvePointCount; + XrAttenuationCurvePointBD* curvePoints; +} XrSoundObjectDistanceAttenuationCurveBD; + +typedef struct XrSoundObjectDistanceAttenuationBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSoundObjectDistanceAttenuationTypeBD distanceAttenuationType; + float minAttenuationRange; + float maxAttenuationRange; + float referenceDistance; + float rolloffFactor; + XrSoundObjectDistanceAttenuationCurveBD* customDistanceAttenuationCurve; +} XrSoundObjectDistanceAttenuationBD; + +typedef struct XrSoundObjectConfigBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 enabled; + XrPosef pose; + XrSpace baseSpace; + float mainVolume; + float reflectionGain; + XrBool32 enableDoppler; + const XrSoundObjectDistanceAttenuationBD* directSoundAttenuation; + const XrSoundObjectDistanceAttenuationBD* indirectSoundAttenuation; +} XrSoundObjectConfigBD; + +typedef struct XrSoundFieldConfigBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 enabled; + XrQuaternionf orientation; + XrSpace baseSpace; + float mainVolume; + float lfeGain; +} XrSoundFieldConfigBD; + +// XrSoundFieldChannelDefinitionSurroundBD extends XrSoundFieldConfigBD +typedef struct XrSoundFieldChannelDefinitionSurroundBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSoundFieldChannelMaskSurroundBD channelMask; +} XrSoundFieldChannelDefinitionSurroundBD; + +// XrSoundFieldChannelDefinitionAmbixBD extends XrSoundFieldConfigBD +typedef struct XrSoundFieldChannelDefinitionAmbixBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSoundFieldChannelMaskAmbixBD channelMask; +} XrSoundFieldChannelDefinitionAmbixBD; + +// XrSoundFieldChannelDefinitionFumaBD extends XrSoundFieldConfigBD +typedef struct XrSoundFieldChannelDefinitionFumaBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSoundFieldChannelMaskFumaBD channelMask; +} XrSoundFieldChannelDefinitionFumaBD; + +typedef struct XrSoundTriangleMeshBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t vertexCount; + XrVector3f* vertices; + uint32_t indexCount; + uint32_t* indices; +} XrSoundTriangleMeshBD; + +typedef struct XrSoundObstacleConfigBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 enabled; + XrPosef pose; + XrSpace baseSpace; + uint32_t materialCount; + XrSoundObstacleMaterialBD* materials; +} XrSoundObstacleConfigBD; + +typedef struct XrSoundObstacleMaterialConfigBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSoundObstacleMaterialTypeBD materialType; + uint32_t bandCount; + float* bandFrequencies; + float* bandAbsorptions; + float* bandScatterings; + float* bandTransmissions; +} XrSoundObstacleMaterialConfigBD; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateSupportedAudioSampleRateBD)(XrSession session, uint32_t sampleRateCapacityInput, uint32_t* sampleRateCountOutput, XrAudioSampleRateBD* sampleRates); +typedef XrResult (XRAPI_PTR *PFN_xrQueryFramesPerBufferRangeBD)(XrSession session, XrAudioSampleRateBD sampleRate, uint32_t* min, uint32_t* max); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAudioRendererBD)(XrSession session, const XrSpatialAudioRendererCreateInfoBD* createInfo, XrSpatialAudioRendererBD* renderer); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySpatialAudioRendererBD)(XrSpatialAudioRendererBD renderer); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSoundObstacleMaterialBD)(XrSpatialAudioRendererBD renderer, const XrSoundObstacleMaterialConfigBD* config, XrSoundObstacleMaterialBD* material); +typedef XrResult (XRAPI_PTR *PFN_xrUpdateSoundObstacleMaterialConfigBD)(XrSoundObstacleMaterialBD material, const XrSoundObstacleMaterialConfigBD* config); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySoundObstacleMaterialBD)(XrSoundObstacleMaterialBD material); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSoundObstacleBD)(XrSpatialAudioRendererBD renderer, const XrSoundObstacleConfigBD* config, const XrSoundTriangleMeshBD* mesh, XrSoundObstacleBD* soundObstacle); +typedef XrResult (XRAPI_PTR *PFN_xrUpdateSoundObstacleConfigBD)(XrSoundObstacleBD soundObstacle, const XrSoundObstacleConfigBD* config, const XrSoundTriangleMeshBD* mesh, XrSoundObstacleFlagsBD flags); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySoundObstacleBD)(XrSoundObstacleBD soundObstacle); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSoundObjectBD)(XrSpatialAudioRendererBD renderer, const XrSoundObjectConfigBD* config, XrSoundObjectBD* soundObject); +typedef XrResult (XRAPI_PTR *PFN_xrUpdateSoundObjectConfigBD)(XrSoundObjectBD soundObject, const XrSoundObjectConfigBD* config, XrSoundObjectFlagsBD flags); +typedef XrResult (XRAPI_PTR *PFN_xrSubmitSoundObjectBufferBD)(XrSoundObjectBD soundObject, const XrAudioBufferBD* buffer); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySoundObjectBD)(XrSoundObjectBD soundObject); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSoundFieldBD)(XrSpatialAudioRendererBD renderer, const XrSoundFieldConfigBD* config, XrSoundFieldBD* soundField); +typedef XrResult (XRAPI_PTR *PFN_xrUpdateSoundFieldConfigBD)(XrSoundFieldBD soundField, const XrSoundFieldConfigBD* config, XrSoundFieldFlagsBD flags); +typedef XrResult (XRAPI_PTR *PFN_xrSubmitSoundFieldBufferBD)(XrSoundFieldBD soundField, const XrAudioBufferBD* buffer); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySoundFieldBD)(XrSoundFieldBD soundField); +typedef XrResult (XRAPI_PTR *PFN_xrWaitAudioPeriodBD)(XrSpatialAudioRendererBD renderer, XrDuration timeout); +typedef XrResult (XRAPI_PTR *PFN_xrEndAudioPeriodBD)(XrSpatialAudioRendererBD renderer); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateSupportedAudioSampleRateBD( + XrSession session, + uint32_t sampleRateCapacityInput, + uint32_t* sampleRateCountOutput, + XrAudioSampleRateBD* sampleRates); + +XRAPI_ATTR XrResult XRAPI_CALL xrQueryFramesPerBufferRangeBD( + XrSession session, + XrAudioSampleRateBD sampleRate, + uint32_t* min, + uint32_t* max); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAudioRendererBD( + XrSession session, + const XrSpatialAudioRendererCreateInfoBD* createInfo, + XrSpatialAudioRendererBD* renderer); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySpatialAudioRendererBD( + XrSpatialAudioRendererBD renderer); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSoundObstacleMaterialBD( + XrSpatialAudioRendererBD renderer, + const XrSoundObstacleMaterialConfigBD* config, + XrSoundObstacleMaterialBD* material); + +XRAPI_ATTR XrResult XRAPI_CALL xrUpdateSoundObstacleMaterialConfigBD( + XrSoundObstacleMaterialBD material, + const XrSoundObstacleMaterialConfigBD* config); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySoundObstacleMaterialBD( + XrSoundObstacleMaterialBD material); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSoundObstacleBD( + XrSpatialAudioRendererBD renderer, + const XrSoundObstacleConfigBD* config, + const XrSoundTriangleMeshBD* mesh, + XrSoundObstacleBD* soundObstacle); + +XRAPI_ATTR XrResult XRAPI_CALL xrUpdateSoundObstacleConfigBD( + XrSoundObstacleBD soundObstacle, + const XrSoundObstacleConfigBD* config, + const XrSoundTriangleMeshBD* mesh, + XrSoundObstacleFlagsBD flags); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySoundObstacleBD( + XrSoundObstacleBD soundObstacle); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSoundObjectBD( + XrSpatialAudioRendererBD renderer, + const XrSoundObjectConfigBD* config, + XrSoundObjectBD* soundObject); + +XRAPI_ATTR XrResult XRAPI_CALL xrUpdateSoundObjectConfigBD( + XrSoundObjectBD soundObject, + const XrSoundObjectConfigBD* config, + XrSoundObjectFlagsBD flags); + +XRAPI_ATTR XrResult XRAPI_CALL xrSubmitSoundObjectBufferBD( + XrSoundObjectBD soundObject, + const XrAudioBufferBD* buffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySoundObjectBD( + XrSoundObjectBD soundObject); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSoundFieldBD( + XrSpatialAudioRendererBD renderer, + const XrSoundFieldConfigBD* config, + XrSoundFieldBD* soundField); + +XRAPI_ATTR XrResult XRAPI_CALL xrUpdateSoundFieldConfigBD( + XrSoundFieldBD soundField, + const XrSoundFieldConfigBD* config, + XrSoundFieldFlagsBD flags); + +XRAPI_ATTR XrResult XRAPI_CALL xrSubmitSoundFieldBufferBD( + XrSoundFieldBD soundField, + const XrAudioBufferBD* buffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySoundFieldBD( + XrSoundFieldBD soundField); + +XRAPI_ATTR XrResult XRAPI_CALL xrWaitAudioPeriodBD( + XrSpatialAudioRendererBD renderer, + XrDuration timeout); + +XRAPI_ATTR XrResult XRAPI_CALL xrEndAudioPeriodBD( + XrSpatialAudioRendererBD renderer); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_local_floor is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_local_floor 1 +#define XR_EXT_local_floor_SPEC_VERSION 1 +#define XR_EXT_LOCAL_FLOOR_EXTENSION_NAME "XR_EXT_local_floor" + + +// XR_EXT_hand_tracking_data_source is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_hand_tracking_data_source 1 +#define XR_EXT_hand_tracking_data_source_SPEC_VERSION 1 +#define XR_EXT_HAND_TRACKING_DATA_SOURCE_EXTENSION_NAME "XR_EXT_hand_tracking_data_source" + +typedef enum XrHandTrackingDataSourceEXT { + XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT = 1, + XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT = 2, + XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_WIDE_MOTION_META = 1000695000, + XR_HAND_TRACKING_DATA_SOURCE_MAX_ENUM_EXT = 0x7FFFFFFF +} XrHandTrackingDataSourceEXT; +// XrHandTrackingDataSourceInfoEXT extends XrHandTrackerCreateInfoEXT +typedef struct XrHandTrackingDataSourceInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t requestedDataSourceCount; + XrHandTrackingDataSourceEXT* requestedDataSources; +} XrHandTrackingDataSourceInfoEXT; + +// XrHandTrackingDataSourceStateEXT extends XrHandJointLocationsEXT +typedef struct XrHandTrackingDataSourceStateEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 isActive; + XrHandTrackingDataSourceEXT dataSource; +} XrHandTrackingDataSourceStateEXT; + + + +// XR_EXT_plane_detection is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_plane_detection 1 +XR_DEFINE_HANDLE(XrPlaneDetectorEXT) +#define XR_EXT_plane_detection_SPEC_VERSION 2 +#define XR_EXT_PLANE_DETECTION_EXTENSION_NAME "XR_EXT_plane_detection" + +typedef enum XrPlaneDetectorOrientationEXT { + XR_PLANE_DETECTOR_ORIENTATION_HORIZONTAL_UPWARD_EXT = 0, + XR_PLANE_DETECTOR_ORIENTATION_HORIZONTAL_DOWNWARD_EXT = 1, + XR_PLANE_DETECTOR_ORIENTATION_VERTICAL_EXT = 2, + XR_PLANE_DETECTOR_ORIENTATION_ARBITRARY_EXT = 3, + XR_PLANE_DETECTOR_ORIENTATION_MAX_ENUM_EXT = 0x7FFFFFFF +} XrPlaneDetectorOrientationEXT; + +typedef enum XrPlaneDetectorSemanticTypeEXT { + XR_PLANE_DETECTOR_SEMANTIC_TYPE_UNDEFINED_EXT = 0, + XR_PLANE_DETECTOR_SEMANTIC_TYPE_CEILING_EXT = 1, + XR_PLANE_DETECTOR_SEMANTIC_TYPE_FLOOR_EXT = 2, + XR_PLANE_DETECTOR_SEMANTIC_TYPE_WALL_EXT = 3, + XR_PLANE_DETECTOR_SEMANTIC_TYPE_PLATFORM_EXT = 4, + XR_PLANE_DETECTOR_SEMANTIC_TYPE_MAX_ENUM_EXT = 0x7FFFFFFF +} XrPlaneDetectorSemanticTypeEXT; + +typedef enum XrPlaneDetectionStateEXT { + XR_PLANE_DETECTION_STATE_NONE_EXT = 0, + XR_PLANE_DETECTION_STATE_PENDING_EXT = 1, + XR_PLANE_DETECTION_STATE_DONE_EXT = 2, + XR_PLANE_DETECTION_STATE_ERROR_EXT = 3, + XR_PLANE_DETECTION_STATE_FATAL_EXT = 4, + XR_PLANE_DETECTION_STATE_MAX_ENUM_EXT = 0x7FFFFFFF +} XrPlaneDetectionStateEXT; +typedef XrFlags64 XrPlaneDetectionCapabilityFlagsEXT; + +// Flag bits for XrPlaneDetectionCapabilityFlagsEXT +static const XrPlaneDetectionCapabilityFlagsEXT XR_PLANE_DETECTION_CAPABILITY_PLANE_DETECTION_BIT_EXT = 0x00000001; +static const XrPlaneDetectionCapabilityFlagsEXT XR_PLANE_DETECTION_CAPABILITY_PLANE_HOLES_BIT_EXT = 0x00000002; +static const XrPlaneDetectionCapabilityFlagsEXT XR_PLANE_DETECTION_CAPABILITY_SEMANTIC_CEILING_BIT_EXT = 0x00000004; +static const XrPlaneDetectionCapabilityFlagsEXT XR_PLANE_DETECTION_CAPABILITY_SEMANTIC_FLOOR_BIT_EXT = 0x00000008; +static const XrPlaneDetectionCapabilityFlagsEXT XR_PLANE_DETECTION_CAPABILITY_SEMANTIC_WALL_BIT_EXT = 0x00000010; +static const XrPlaneDetectionCapabilityFlagsEXT XR_PLANE_DETECTION_CAPABILITY_SEMANTIC_PLATFORM_BIT_EXT = 0x00000020; +static const XrPlaneDetectionCapabilityFlagsEXT XR_PLANE_DETECTION_CAPABILITY_ORIENTATION_BIT_EXT = 0x00000040; + +typedef XrFlags64 XrPlaneDetectorFlagsEXT; + +// Flag bits for XrPlaneDetectorFlagsEXT +static const XrPlaneDetectorFlagsEXT XR_PLANE_DETECTOR_ENABLE_CONTOUR_BIT_EXT = 0x00000001; + +// XrSystemPlaneDetectionPropertiesEXT extends XrSystemProperties +typedef struct XrSystemPlaneDetectionPropertiesEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrPlaneDetectionCapabilityFlagsEXT supportedFeatures; +} XrSystemPlaneDetectionPropertiesEXT; + +typedef struct XrPlaneDetectorCreateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrPlaneDetectorFlagsEXT flags; +} XrPlaneDetectorCreateInfoEXT; + +typedef XrExtent3Df XrExtent3DfEXT; + +typedef struct XrPlaneDetectorBeginInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace baseSpace; + XrTime time; + uint32_t orientationCount; + const XrPlaneDetectorOrientationEXT* orientations; + uint32_t semanticTypeCount; + const XrPlaneDetectorSemanticTypeEXT* semanticTypes; + uint32_t maxPlanes; + float minArea; + XrPosef boundingBoxPose; + XrExtent3DfEXT boundingBoxExtent; +} XrPlaneDetectorBeginInfoEXT; + +typedef struct XrPlaneDetectorGetInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace baseSpace; + XrTime time; +} XrPlaneDetectorGetInfoEXT; + +typedef struct XrPlaneDetectorLocationEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint64_t planeId; + XrSpaceLocationFlags locationFlags; + XrPosef pose; + XrExtent2Df extents; + XrPlaneDetectorOrientationEXT orientation; + XrPlaneDetectorSemanticTypeEXT semanticType; + uint32_t polygonBufferCount; +} XrPlaneDetectorLocationEXT; + +typedef struct XrPlaneDetectorLocationsEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t planeLocationCapacityInput; + uint32_t planeLocationCountOutput; + XrPlaneDetectorLocationEXT* planeLocations; +} XrPlaneDetectorLocationsEXT; + +typedef struct XrPlaneDetectorPolygonBufferEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t vertexCapacityInput; + uint32_t vertexCountOutput; + XrVector2f* vertices; +} XrPlaneDetectorPolygonBufferEXT; + +typedef XrResult (XRAPI_PTR *PFN_xrCreatePlaneDetectorEXT)(XrSession session, const XrPlaneDetectorCreateInfoEXT* createInfo, XrPlaneDetectorEXT* planeDetector); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyPlaneDetectorEXT)(XrPlaneDetectorEXT planeDetector); +typedef XrResult (XRAPI_PTR *PFN_xrBeginPlaneDetectionEXT)(XrPlaneDetectorEXT planeDetector, const XrPlaneDetectorBeginInfoEXT* beginInfo); +typedef XrResult (XRAPI_PTR *PFN_xrGetPlaneDetectionStateEXT)(XrPlaneDetectorEXT planeDetector, XrPlaneDetectionStateEXT* state); +typedef XrResult (XRAPI_PTR *PFN_xrGetPlaneDetectionsEXT)(XrPlaneDetectorEXT planeDetector, const XrPlaneDetectorGetInfoEXT* info, XrPlaneDetectorLocationsEXT* locations); +typedef XrResult (XRAPI_PTR *PFN_xrGetPlanePolygonBufferEXT)(XrPlaneDetectorEXT planeDetector, uint64_t planeId, uint32_t polygonBufferIndex, XrPlaneDetectorPolygonBufferEXT* polygonBuffer); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreatePlaneDetectorEXT( + XrSession session, + const XrPlaneDetectorCreateInfoEXT* createInfo, + XrPlaneDetectorEXT* planeDetector); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyPlaneDetectorEXT( + XrPlaneDetectorEXT planeDetector); + +XRAPI_ATTR XrResult XRAPI_CALL xrBeginPlaneDetectionEXT( + XrPlaneDetectorEXT planeDetector, + const XrPlaneDetectorBeginInfoEXT* beginInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetPlaneDetectionStateEXT( + XrPlaneDetectorEXT planeDetector, + XrPlaneDetectionStateEXT* state); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetPlaneDetectionsEXT( + XrPlaneDetectorEXT planeDetector, + const XrPlaneDetectorGetInfoEXT* info, + XrPlaneDetectorLocationsEXT* locations); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetPlanePolygonBufferEXT( + XrPlaneDetectorEXT planeDetector, + uint64_t planeId, + uint32_t polygonBufferIndex, + XrPlaneDetectorPolygonBufferEXT* polygonBuffer); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_OPPO_controller_interaction is a preprocessor guard. Do not pass it to API calls. +#define XR_OPPO_controller_interaction 1 +#define XR_OPPO_controller_interaction_SPEC_VERSION 1 +#define XR_OPPO_CONTROLLER_INTERACTION_EXTENSION_NAME "XR_OPPO_controller_interaction" + + +// XR_ANDROID_trackables is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_trackables 1 + +#define XR_NULL_TRACKABLE_ANDROID 0 + +XR_DEFINE_ATOM(XrTrackableANDROID) +XR_DEFINE_HANDLE(XrTrackableTrackerANDROID) +#define XR_ANDROID_trackables_SPEC_VERSION 2 +#define XR_ANDROID_TRACKABLES_EXTENSION_NAME "XR_ANDROID_trackables" + +typedef enum XrTrackingStateANDROID { + XR_TRACKING_STATE_PAUSED_ANDROID = 0, + XR_TRACKING_STATE_STOPPED_ANDROID = 1, + XR_TRACKING_STATE_TRACKING_ANDROID = 2, + XR_TRACKING_STATE_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrTrackingStateANDROID; + +typedef enum XrTrackableTypeANDROID { + XR_TRACKABLE_TYPE_NOT_VALID_ANDROID = 0, + XR_TRACKABLE_TYPE_PLANE_ANDROID = 1, + XR_TRACKABLE_TYPE_DEPTH_ANDROID = 1000463000, + XR_TRACKABLE_TYPE_OBJECT_ANDROID = 1000466000, + XR_TRACKABLE_TYPE_MARKER_ANDROID = 1000707000, + XR_TRACKABLE_TYPE_QR_CODE_ANDROID = 1000708000, + XR_TRACKABLE_TYPE_IMAGE_ANDROID = 1000709000, + XR_TRACKABLE_TYPE_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrTrackableTypeANDROID; + +typedef enum XrPlaneTypeANDROID { + XR_PLANE_TYPE_HORIZONTAL_DOWNWARD_FACING_ANDROID = 0, + XR_PLANE_TYPE_HORIZONTAL_UPWARD_FACING_ANDROID = 1, + XR_PLANE_TYPE_VERTICAL_ANDROID = 2, + XR_PLANE_TYPE_ARBITRARY_ANDROID = 3, + XR_PLANE_TYPE_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrPlaneTypeANDROID; + +typedef enum XrPlaneLabelANDROID { + XR_PLANE_LABEL_UNKNOWN_ANDROID = 0, + XR_PLANE_LABEL_WALL_ANDROID = 1, + XR_PLANE_LABEL_FLOOR_ANDROID = 2, + XR_PLANE_LABEL_CEILING_ANDROID = 3, + XR_PLANE_LABEL_TABLE_ANDROID = 4, + XR_PLANE_LABEL_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrPlaneLabelANDROID; +typedef struct XrTrackableTrackerCreateInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrTrackableTypeANDROID trackableType; +} XrTrackableTrackerCreateInfoANDROID; + +typedef struct XrTrackableGetInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrTrackableANDROID trackable; + XrSpace baseSpace; + XrTime time; +} XrTrackableGetInfoANDROID; + +typedef struct XrTrackablePlaneANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrTrackingStateANDROID trackingState; + XrPosef centerPose; + XrExtent2Df extents; + XrPlaneTypeANDROID planeType; + XrPlaneLabelANDROID planeLabel; + XrTrackableANDROID subsumedByPlane; + XrTime lastUpdatedTime; + uint32_t vertexCapacityInput; + uint32_t* vertexCountOutput; + XrVector2f* vertices; +} XrTrackablePlaneANDROID; + +typedef struct XrAnchorSpaceCreateInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace space; + XrTime time; + XrPosef pose; + XrTrackableANDROID trackable; +} XrAnchorSpaceCreateInfoANDROID; + +// XrSystemTrackablesPropertiesANDROID extends XrSystemProperties +typedef struct XrSystemTrackablesPropertiesANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 supportsAnchor; + uint32_t maxAnchors; +} XrSystemTrackablesPropertiesANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateSupportedTrackableTypesANDROID)(XrInstance instance, XrSystemId systemId, uint32_t trackableTypeCapacityInput, uint32_t* trackableTypeCountOutput, XrTrackableTypeANDROID* trackableTypes); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateSupportedAnchorTrackableTypesANDROID)(XrInstance instance, XrSystemId systemId, uint32_t trackableTypeCapacityInput, uint32_t* trackableTypeCountOutput, XrTrackableTypeANDROID* trackableTypes); +typedef XrResult (XRAPI_PTR *PFN_xrCreateTrackableTrackerANDROID)(XrSession session, const XrTrackableTrackerCreateInfoANDROID* createInfo, XrTrackableTrackerANDROID* trackableTracker); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyTrackableTrackerANDROID)(XrTrackableTrackerANDROID trackableTracker); +typedef XrResult (XRAPI_PTR *PFN_xrGetAllTrackablesANDROID)(XrTrackableTrackerANDROID trackableTracker, uint32_t trackableCapacityInput, uint32_t* trackableCountOutput, XrTrackableANDROID* trackables); +typedef XrResult (XRAPI_PTR *PFN_xrGetTrackablePlaneANDROID)(XrTrackableTrackerANDROID trackableTracker, const XrTrackableGetInfoANDROID* getInfo, XrTrackablePlaneANDROID* planeOutput); +typedef XrResult (XRAPI_PTR *PFN_xrCreateAnchorSpaceANDROID)(XrSession session, const XrAnchorSpaceCreateInfoANDROID* createInfo, XrSpace* anchorOutput); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateSupportedTrackableTypesANDROID( + XrInstance instance, + XrSystemId systemId, + uint32_t trackableTypeCapacityInput, + uint32_t* trackableTypeCountOutput, + XrTrackableTypeANDROID* trackableTypes); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateSupportedAnchorTrackableTypesANDROID( + XrInstance instance, + XrSystemId systemId, + uint32_t trackableTypeCapacityInput, + uint32_t* trackableTypeCountOutput, + XrTrackableTypeANDROID* trackableTypes); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateTrackableTrackerANDROID( + XrSession session, + const XrTrackableTrackerCreateInfoANDROID* createInfo, + XrTrackableTrackerANDROID* trackableTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyTrackableTrackerANDROID( + XrTrackableTrackerANDROID trackableTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetAllTrackablesANDROID( + XrTrackableTrackerANDROID trackableTracker, + uint32_t trackableCapacityInput, + uint32_t* trackableCountOutput, + XrTrackableANDROID* trackables); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetTrackablePlaneANDROID( + XrTrackableTrackerANDROID trackableTracker, + const XrTrackableGetInfoANDROID* getInfo, + XrTrackablePlaneANDROID* planeOutput); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateAnchorSpaceANDROID( + XrSession session, + const XrAnchorSpaceCreateInfoANDROID* createInfo, + XrSpace* anchorOutput); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_eye_tracking is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_eye_tracking 1 +XR_DEFINE_HANDLE(XrEyeTrackerANDROID) +#define XR_EYE_MAX_ANDROID 2 +#define XR_ANDROID_eye_tracking_SPEC_VERSION 1 +#define XR_ANDROID_EYE_TRACKING_EXTENSION_NAME "XR_ANDROID_eye_tracking" + +typedef enum XrEyeIndexANDROID { + XR_EYE_INDEX_LEFT_ANDROID = 0, + XR_EYE_INDEX_RIGHT_ANDROID = 1, + XR_EYE_INDEX_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrEyeIndexANDROID; + +typedef enum XrEyeStateANDROID { + XR_EYE_STATE_INVALID_ANDROID = 0, + XR_EYE_STATE_GAZING_ANDROID = 1, + XR_EYE_STATE_SHUT_ANDROID = 2, + XR_EYE_STATE_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrEyeStateANDROID; + +typedef enum XrEyeTrackingModeANDROID { + XR_EYE_TRACKING_MODE_NOT_TRACKING_ANDROID = 0, + XR_EYE_TRACKING_MODE_RIGHT_ANDROID = 1, + XR_EYE_TRACKING_MODE_LEFT_ANDROID = 2, + XR_EYE_TRACKING_MODE_BOTH_ANDROID = 3, + XR_EYE_TRACKING_MODE_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrEyeTrackingModeANDROID; +// XrSystemEyeTrackingPropertiesANDROID extends XrSystemProperties +typedef struct XrSystemEyeTrackingPropertiesANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsEyeTracking; +} XrSystemEyeTrackingPropertiesANDROID; + +typedef struct XrEyeANDROID { + XrEyeStateANDROID eyeState; + XrPosef eyePose; +} XrEyeANDROID; + +typedef struct XrEyesANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrEyeANDROID eyes[XR_EYE_MAX_ANDROID]; + XrEyeTrackingModeANDROID mode; +} XrEyesANDROID; + +typedef struct XrEyesGetInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrTime time; + XrSpace baseSpace; +} XrEyesGetInfoANDROID; + +typedef struct XrEyeTrackerCreateInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrEyeTrackerCreateInfoANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateEyeTrackerANDROID)(XrSession session, const XrEyeTrackerCreateInfoANDROID* createInfo, XrEyeTrackerANDROID* eyeTracker); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyEyeTrackerANDROID)(XrEyeTrackerANDROID eyeTracker); +typedef XrResult (XRAPI_PTR *PFN_xrGetFineTrackingEyesInfoANDROID)(XrEyeTrackerANDROID eyeTracker, const XrEyesGetInfoANDROID* getInfo, XrEyesANDROID* eyesOutput); +typedef XrResult (XRAPI_PTR *PFN_xrGetCoarseTrackingEyesInfoANDROID)(XrEyeTrackerANDROID eyeTracker, const XrEyesGetInfoANDROID* getInfo, XrEyesANDROID* eyesOutput); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateEyeTrackerANDROID( + XrSession session, + const XrEyeTrackerCreateInfoANDROID* createInfo, + XrEyeTrackerANDROID* eyeTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyEyeTrackerANDROID( + XrEyeTrackerANDROID eyeTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetFineTrackingEyesInfoANDROID( + XrEyeTrackerANDROID eyeTracker, + const XrEyesGetInfoANDROID* getInfo, + XrEyesANDROID* eyesOutput); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetCoarseTrackingEyesInfoANDROID( + XrEyeTrackerANDROID eyeTracker, + const XrEyesGetInfoANDROID* getInfo, + XrEyesANDROID* eyesOutput); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_device_anchor_persistence is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_device_anchor_persistence 1 +XR_DEFINE_HANDLE(XrDeviceAnchorPersistenceANDROID) +#define XR_ANDROID_device_anchor_persistence_SPEC_VERSION 1 +#define XR_ANDROID_DEVICE_ANCHOR_PERSISTENCE_EXTENSION_NAME "XR_ANDROID_device_anchor_persistence" + +typedef enum XrAnchorPersistStateANDROID { + XR_ANCHOR_PERSIST_STATE_PERSIST_NOT_REQUESTED_ANDROID = 0, + XR_ANCHOR_PERSIST_STATE_PERSIST_PENDING_ANDROID = 1, + XR_ANCHOR_PERSIST_STATE_PERSISTED_ANDROID = 2, + XR_ANCHOR_PERSIST_STATE_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrAnchorPersistStateANDROID; +typedef struct XrDeviceAnchorPersistenceCreateInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrDeviceAnchorPersistenceCreateInfoANDROID; + +typedef struct XrPersistedAnchorSpaceCreateInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrUuidEXT anchorId; +} XrPersistedAnchorSpaceCreateInfoANDROID; + +typedef struct XrPersistedAnchorSpaceInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace anchor; +} XrPersistedAnchorSpaceInfoANDROID; + +// XrSystemDeviceAnchorPersistencePropertiesANDROID extends XrSystemProperties +typedef struct XrSystemDeviceAnchorPersistencePropertiesANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsAnchorPersistence; +} XrSystemDeviceAnchorPersistencePropertiesANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateSupportedPersistenceAnchorTypesANDROID)(XrInstance instance, XrSystemId systemId, uint32_t trackableTypeCapacityInput, uint32_t* trackableTypeCountOutput, XrTrackableTypeANDROID* trackableTypes); +typedef XrResult (XRAPI_PTR *PFN_xrCreateDeviceAnchorPersistenceANDROID)(XrSession session, const XrDeviceAnchorPersistenceCreateInfoANDROID* createInfo, XrDeviceAnchorPersistenceANDROID* outHandle); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyDeviceAnchorPersistenceANDROID)(XrDeviceAnchorPersistenceANDROID handle); +typedef XrResult (XRAPI_PTR *PFN_xrPersistAnchorANDROID)(XrDeviceAnchorPersistenceANDROID handle, const XrPersistedAnchorSpaceInfoANDROID* persistedInfo, XrUuidEXT* anchorIdOutput); +typedef XrResult (XRAPI_PTR *PFN_xrGetAnchorPersistStateANDROID)(XrDeviceAnchorPersistenceANDROID handle, const XrUuidEXT* anchorId, XrAnchorPersistStateANDROID* persistState); +typedef XrResult (XRAPI_PTR *PFN_xrCreatePersistedAnchorSpaceANDROID)(XrDeviceAnchorPersistenceANDROID handle, const XrPersistedAnchorSpaceCreateInfoANDROID* createInfo, XrSpace* anchorOutput); +typedef XrResult (XRAPI_PTR *PFN_xrEnumeratePersistedAnchorsANDROID)(XrDeviceAnchorPersistenceANDROID handle, uint32_t anchorIdCapacityInput, uint32_t* anchorIdCountOutput, XrUuidEXT* anchorIds); +typedef XrResult (XRAPI_PTR *PFN_xrUnpersistAnchorANDROID)(XrDeviceAnchorPersistenceANDROID handle, const XrUuidEXT* anchorId); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateSupportedPersistenceAnchorTypesANDROID( + XrInstance instance, + XrSystemId systemId, + uint32_t trackableTypeCapacityInput, + uint32_t* trackableTypeCountOutput, + XrTrackableTypeANDROID* trackableTypes); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateDeviceAnchorPersistenceANDROID( + XrSession session, + const XrDeviceAnchorPersistenceCreateInfoANDROID* createInfo, + XrDeviceAnchorPersistenceANDROID* outHandle); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyDeviceAnchorPersistenceANDROID( + XrDeviceAnchorPersistenceANDROID handle); + +XRAPI_ATTR XrResult XRAPI_CALL xrPersistAnchorANDROID( + XrDeviceAnchorPersistenceANDROID handle, + const XrPersistedAnchorSpaceInfoANDROID* persistedInfo, + XrUuidEXT* anchorIdOutput); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetAnchorPersistStateANDROID( + XrDeviceAnchorPersistenceANDROID handle, + const XrUuidEXT* anchorId, + XrAnchorPersistStateANDROID* persistState); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreatePersistedAnchorSpaceANDROID( + XrDeviceAnchorPersistenceANDROID handle, + const XrPersistedAnchorSpaceCreateInfoANDROID* createInfo, + XrSpace* anchorOutput); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumeratePersistedAnchorsANDROID( + XrDeviceAnchorPersistenceANDROID handle, + uint32_t anchorIdCapacityInput, + uint32_t* anchorIdCountOutput, + XrUuidEXT* anchorIds); + +XRAPI_ATTR XrResult XRAPI_CALL xrUnpersistAnchorANDROID( + XrDeviceAnchorPersistenceANDROID handle, + const XrUuidEXT* anchorId); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_face_tracking is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_face_tracking 1 +XR_DEFINE_HANDLE(XrFaceTrackerANDROID) +#define XR_ANDROID_face_tracking_SPEC_VERSION 1 +#define XR_ANDROID_FACE_TRACKING_EXTENSION_NAME "XR_ANDROID_face_tracking" +#define XR_FACE_PARAMETER_COUNT_ANDROID 68 +#define XR_FACE_REGION_CONFIDENCE_COUNT_ANDROID 3 + +typedef enum XrFaceParameterIndicesANDROID { + XR_FACE_PARAMETER_INDICES_BROW_LOWERER_L_ANDROID = 0, + XR_FACE_PARAMETER_INDICES_BROW_LOWERER_R_ANDROID = 1, + XR_FACE_PARAMETER_INDICES_CHEEK_PUFF_L_ANDROID = 2, + XR_FACE_PARAMETER_INDICES_CHEEK_PUFF_R_ANDROID = 3, + XR_FACE_PARAMETER_INDICES_CHEEK_RAISER_L_ANDROID = 4, + XR_FACE_PARAMETER_INDICES_CHEEK_RAISER_R_ANDROID = 5, + XR_FACE_PARAMETER_INDICES_CHEEK_SUCK_L_ANDROID = 6, + XR_FACE_PARAMETER_INDICES_CHEEK_SUCK_R_ANDROID = 7, + XR_FACE_PARAMETER_INDICES_CHIN_RAISER_B_ANDROID = 8, + XR_FACE_PARAMETER_INDICES_CHIN_RAISER_T_ANDROID = 9, + XR_FACE_PARAMETER_INDICES_DIMPLER_L_ANDROID = 10, + XR_FACE_PARAMETER_INDICES_DIMPLER_R_ANDROID = 11, + XR_FACE_PARAMETER_INDICES_EYES_CLOSED_L_ANDROID = 12, + XR_FACE_PARAMETER_INDICES_EYES_CLOSED_R_ANDROID = 13, + XR_FACE_PARAMETER_INDICES_EYES_LOOK_DOWN_L_ANDROID = 14, + XR_FACE_PARAMETER_INDICES_EYES_LOOK_DOWN_R_ANDROID = 15, + XR_FACE_PARAMETER_INDICES_EYES_LOOK_LEFT_L_ANDROID = 16, + XR_FACE_PARAMETER_INDICES_EYES_LOOK_LEFT_R_ANDROID = 17, + XR_FACE_PARAMETER_INDICES_EYES_LOOK_RIGHT_L_ANDROID = 18, + XR_FACE_PARAMETER_INDICES_EYES_LOOK_RIGHT_R_ANDROID = 19, + XR_FACE_PARAMETER_INDICES_EYES_LOOK_UP_L_ANDROID = 20, + XR_FACE_PARAMETER_INDICES_EYES_LOOK_UP_R_ANDROID = 21, + XR_FACE_PARAMETER_INDICES_INNER_BROW_RAISER_L_ANDROID = 22, + XR_FACE_PARAMETER_INDICES_INNER_BROW_RAISER_R_ANDROID = 23, + XR_FACE_PARAMETER_INDICES_JAW_DROP_ANDROID = 24, + XR_FACE_PARAMETER_INDICES_JAW_SIDEWAYS_LEFT_ANDROID = 25, + XR_FACE_PARAMETER_INDICES_JAW_SIDEWAYS_RIGHT_ANDROID = 26, + XR_FACE_PARAMETER_INDICES_JAW_THRUST_ANDROID = 27, + XR_FACE_PARAMETER_INDICES_LID_TIGHTENER_L_ANDROID = 28, + XR_FACE_PARAMETER_INDICES_LID_TIGHTENER_R_ANDROID = 29, + XR_FACE_PARAMETER_INDICES_LIP_CORNER_DEPRESSOR_L_ANDROID = 30, + XR_FACE_PARAMETER_INDICES_LIP_CORNER_DEPRESSOR_R_ANDROID = 31, + XR_FACE_PARAMETER_INDICES_LIP_CORNER_PULLER_L_ANDROID = 32, + XR_FACE_PARAMETER_INDICES_LIP_CORNER_PULLER_R_ANDROID = 33, + XR_FACE_PARAMETER_INDICES_LIP_FUNNELER_LB_ANDROID = 34, + XR_FACE_PARAMETER_INDICES_LIP_FUNNELER_LT_ANDROID = 35, + XR_FACE_PARAMETER_INDICES_LIP_FUNNELER_RB_ANDROID = 36, + XR_FACE_PARAMETER_INDICES_LIP_FUNNELER_RT_ANDROID = 37, + XR_FACE_PARAMETER_INDICES_LIP_PRESSOR_L_ANDROID = 38, + XR_FACE_PARAMETER_INDICES_LIP_PRESSOR_R_ANDROID = 39, + XR_FACE_PARAMETER_INDICES_LIP_PUCKER_L_ANDROID = 40, + XR_FACE_PARAMETER_INDICES_LIP_PUCKER_R_ANDROID = 41, + XR_FACE_PARAMETER_INDICES_LIP_STRETCHER_L_ANDROID = 42, + XR_FACE_PARAMETER_INDICES_LIP_STRETCHER_R_ANDROID = 43, + XR_FACE_PARAMETER_INDICES_LIP_SUCK_LB_ANDROID = 44, + XR_FACE_PARAMETER_INDICES_LIP_SUCK_LT_ANDROID = 45, + XR_FACE_PARAMETER_INDICES_LIP_SUCK_RB_ANDROID = 46, + XR_FACE_PARAMETER_INDICES_LIP_SUCK_RT_ANDROID = 47, + XR_FACE_PARAMETER_INDICES_LIP_TIGHTENER_L_ANDROID = 48, + XR_FACE_PARAMETER_INDICES_LIP_TIGHTENER_R_ANDROID = 49, + XR_FACE_PARAMETER_INDICES_LIPS_TOWARD_ANDROID = 50, + XR_FACE_PARAMETER_INDICES_LOWER_LIP_DEPRESSOR_L_ANDROID = 51, + XR_FACE_PARAMETER_INDICES_LOWER_LIP_DEPRESSOR_R_ANDROID = 52, + XR_FACE_PARAMETER_INDICES_MOUTH_LEFT_ANDROID = 53, + XR_FACE_PARAMETER_INDICES_MOUTH_RIGHT_ANDROID = 54, + XR_FACE_PARAMETER_INDICES_NOSE_WRINKLER_L_ANDROID = 55, + XR_FACE_PARAMETER_INDICES_NOSE_WRINKLER_R_ANDROID = 56, + XR_FACE_PARAMETER_INDICES_OUTER_BROW_RAISER_L_ANDROID = 57, + XR_FACE_PARAMETER_INDICES_OUTER_BROW_RAISER_R_ANDROID = 58, + XR_FACE_PARAMETER_INDICES_UPPER_LID_RAISER_L_ANDROID = 59, + XR_FACE_PARAMETER_INDICES_UPPER_LID_RAISER_R_ANDROID = 60, + XR_FACE_PARAMETER_INDICES_UPPER_LIP_RAISER_L_ANDROID = 61, + XR_FACE_PARAMETER_INDICES_UPPER_LIP_RAISER_R_ANDROID = 62, + XR_FACE_PARAMETER_INDICES_TONGUE_OUT_ANDROID = 63, + XR_FACE_PARAMETER_INDICES_TONGUE_LEFT_ANDROID = 64, + XR_FACE_PARAMETER_INDICES_TONGUE_RIGHT_ANDROID = 65, + XR_FACE_PARAMETER_INDICES_TONGUE_UP_ANDROID = 66, + XR_FACE_PARAMETER_INDICES_TONGUE_DOWN_ANDROID = 67, + XR_FACE_PARAMETER_INDICES_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrFaceParameterIndicesANDROID; + +typedef enum XrFaceTrackingStateANDROID { + XR_FACE_TRACKING_STATE_PAUSED_ANDROID = 0, + XR_FACE_TRACKING_STATE_STOPPED_ANDROID = 1, + XR_FACE_TRACKING_STATE_TRACKING_ANDROID = 2, + XR_FACE_TRACKING_STATE_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrFaceTrackingStateANDROID; + +typedef enum XrFaceConfidenceRegionsANDROID { + XR_FACE_CONFIDENCE_REGIONS_LOWER_ANDROID = 0, + XR_FACE_CONFIDENCE_REGIONS_LEFT_UPPER_ANDROID = 1, + XR_FACE_CONFIDENCE_REGIONS_RIGHT_UPPER_ANDROID = 2, + XR_FACE_CONFIDENCE_REGIONS_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrFaceConfidenceRegionsANDROID; +typedef struct XrFaceTrackerCreateInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrFaceTrackerCreateInfoANDROID; + +typedef struct XrFaceStateGetInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrTime time; +} XrFaceStateGetInfoANDROID; + +typedef struct XrFaceStateANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t parametersCapacityInput; + uint32_t parametersCountOutput; + float* parameters; + XrFaceTrackingStateANDROID faceTrackingState; + XrTime sampleTime; + XrBool32 isValid; + uint32_t regionConfidencesCapacityInput; + uint32_t regionConfidencesCountOutput; + float* regionConfidences; +} XrFaceStateANDROID; + +// XrSystemFaceTrackingPropertiesANDROID extends XrSystemProperties +typedef struct XrSystemFaceTrackingPropertiesANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsFaceTracking; +} XrSystemFaceTrackingPropertiesANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateFaceTrackerANDROID)(XrSession session, const XrFaceTrackerCreateInfoANDROID* createInfo, XrFaceTrackerANDROID* faceTracker); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyFaceTrackerANDROID)(XrFaceTrackerANDROID faceTracker); +typedef XrResult (XRAPI_PTR *PFN_xrGetFaceStateANDROID)(XrFaceTrackerANDROID faceTracker, const XrFaceStateGetInfoANDROID* getInfo, XrFaceStateANDROID* faceStateOutput); +typedef XrResult (XRAPI_PTR *PFN_xrGetFaceCalibrationStateANDROID)(XrFaceTrackerANDROID faceTracker, XrBool32* faceIsCalibratedOutput); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateFaceTrackerANDROID( + XrSession session, + const XrFaceTrackerCreateInfoANDROID* createInfo, + XrFaceTrackerANDROID* faceTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyFaceTrackerANDROID( + XrFaceTrackerANDROID faceTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetFaceStateANDROID( + XrFaceTrackerANDROID faceTracker, + const XrFaceStateGetInfoANDROID* getInfo, + XrFaceStateANDROID* faceStateOutput); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetFaceCalibrationStateANDROID( + XrFaceTrackerANDROID faceTracker, + XrBool32* faceIsCalibratedOutput); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_passthrough_camera_state is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_passthrough_camera_state 1 +#define XR_ANDROID_passthrough_camera_state_SPEC_VERSION 1 +#define XR_ANDROID_PASSTHROUGH_CAMERA_STATE_EXTENSION_NAME "XR_ANDROID_passthrough_camera_state" + +typedef enum XrPassthroughCameraStateANDROID { + XR_PASSTHROUGH_CAMERA_STATE_DISABLED_ANDROID = 0, + XR_PASSTHROUGH_CAMERA_STATE_INITIALIZING_ANDROID = 1, + XR_PASSTHROUGH_CAMERA_STATE_READY_ANDROID = 2, + XR_PASSTHROUGH_CAMERA_STATE_ERROR_ANDROID = 3, + XR_PASSTHROUGH_CAMERA_STATE_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrPassthroughCameraStateANDROID; +// XrSystemPassthroughCameraStatePropertiesANDROID extends XrSystemProperties +typedef struct XrSystemPassthroughCameraStatePropertiesANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsPassthroughCameraState; +} XrSystemPassthroughCameraStatePropertiesANDROID; + +typedef struct XrPassthroughCameraStateGetInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrPassthroughCameraStateGetInfoANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrGetPassthroughCameraStateANDROID)(XrSession session, const XrPassthroughCameraStateGetInfoANDROID* getInfo, XrPassthroughCameraStateANDROID* cameraStateOutput); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetPassthroughCameraStateANDROID( + XrSession session, + const XrPassthroughCameraStateGetInfoANDROID* getInfo, + XrPassthroughCameraStateANDROID* cameraStateOutput); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_recommended_resolution is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_recommended_resolution 1 +#define XR_ANDROID_recommended_resolution_SPEC_VERSION 1 +#define XR_ANDROID_RECOMMENDED_RESOLUTION_EXTENSION_NAME "XR_ANDROID_recommended_resolution" +typedef struct XrEventDataRecommendedResolutionChangedANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrEventDataRecommendedResolutionChangedANDROID; + + + +// XR_ANDROID_composition_layer_passthrough_mesh is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_composition_layer_passthrough_mesh 1 +XR_DEFINE_HANDLE(XrPassthroughLayerANDROID) +#define XR_ANDROID_composition_layer_passthrough_mesh_SPEC_VERSION 1 +#define XR_ANDROID_COMPOSITION_LAYER_PASSTHROUGH_MESH_EXTENSION_NAME "XR_ANDROID_composition_layer_passthrough_mesh" + +typedef enum XrWindingOrderANDROID { + XR_WINDING_ORDER_UNKNOWN_ANDROID = 0, + XR_WINDING_ORDER_CW_ANDROID = 1, + XR_WINDING_ORDER_CCW_ANDROID = 2, + XR_WINDING_ORDER_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrWindingOrderANDROID; +typedef struct XrPassthroughLayerCreateInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t vertexCapacity; + uint32_t indexCapacity; +} XrPassthroughLayerCreateInfoANDROID; + +// XrPassthroughLayerMeshANDROID extends XrPassthroughLayerCreateInfoANDROID +typedef struct XrPassthroughLayerMeshANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrWindingOrderANDROID windingOrder; + uint32_t vertexCount; + const XrVector3f* vertices; + uint32_t indexCount; + const uint16_t* indices; +} XrPassthroughLayerMeshANDROID; + +typedef struct XrCompositionLayerPassthroughANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCompositionLayerFlags layerFlags; + XrSpace space; + XrPosef pose; + XrVector3f scale; + float opacity; + XrPassthroughLayerANDROID layer; +} XrCompositionLayerPassthroughANDROID; + +// XrSystemPassthroughLayerPropertiesANDROID extends XrSystemProperties +typedef struct XrSystemPassthroughLayerPropertiesANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsPassthroughLayer; + uint32_t maxMeshIndexCount; + uint32_t maxMeshVertexCount; +} XrSystemPassthroughLayerPropertiesANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrCreatePassthroughLayerANDROID)(XrSession session, const XrPassthroughLayerCreateInfoANDROID* createInfo, XrPassthroughLayerANDROID* layer); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyPassthroughLayerANDROID)(XrPassthroughLayerANDROID layer); +typedef XrResult (XRAPI_PTR *PFN_xrSetPassthroughLayerMeshANDROID)(XrPassthroughLayerANDROID layer, const XrPassthroughLayerMeshANDROID* mesh); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreatePassthroughLayerANDROID( + XrSession session, + const XrPassthroughLayerCreateInfoANDROID* createInfo, + XrPassthroughLayerANDROID* layer); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyPassthroughLayerANDROID( + XrPassthroughLayerANDROID layer); + +XRAPI_ATTR XrResult XRAPI_CALL xrSetPassthroughLayerMeshANDROID( + XrPassthroughLayerANDROID layer, + const XrPassthroughLayerMeshANDROID* mesh); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_raycast is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_raycast 1 +#define XR_ANDROID_raycast_SPEC_VERSION 1 +#define XR_ANDROID_RAYCAST_EXTENSION_NAME "XR_ANDROID_raycast" +typedef struct XrRaycastInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t maxResults; + uint32_t trackerCount; + const XrTrackableTrackerANDROID* trackers; + XrVector3f origin; + XrVector3f trajectory; + XrSpace space; + XrTime time; +} XrRaycastInfoANDROID; + +typedef struct XrRaycastHitResultANDROID { + XrTrackableTypeANDROID type; + XrTrackableANDROID trackable; + XrPosef pose; +} XrRaycastHitResultANDROID; + +typedef struct XrRaycastHitResultsANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t resultsCapacityInput; + uint32_t resultsCountOutput; + XrRaycastHitResultANDROID* results; +} XrRaycastHitResultsANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateRaycastSupportedTrackableTypesANDROID)(XrInstance instance, XrSystemId systemId, uint32_t trackableTypeCapacityInput, uint32_t* trackableTypeCountOutput, XrTrackableTypeANDROID* trackableTypes); +typedef XrResult (XRAPI_PTR *PFN_xrRaycastANDROID)(XrSession session, const XrRaycastInfoANDROID* rayInfo, XrRaycastHitResultsANDROID* results); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateRaycastSupportedTrackableTypesANDROID( + XrInstance instance, + XrSystemId systemId, + uint32_t trackableTypeCapacityInput, + uint32_t* trackableTypeCountOutput, + XrTrackableTypeANDROID* trackableTypes); + +XRAPI_ATTR XrResult XRAPI_CALL xrRaycastANDROID( + XrSession session, + const XrRaycastInfoANDROID* rayInfo, + XrRaycastHitResultsANDROID* results); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_performance_metrics is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_performance_metrics 1 +#define XR_ANDROID_performance_metrics_SPEC_VERSION 1 +#define XR_ANDROID_PERFORMANCE_METRICS_EXTENSION_NAME "XR_ANDROID_performance_metrics" + +typedef enum XrPerformanceMetricsCounterUnitANDROID { + XR_PERFORMANCE_METRICS_COUNTER_UNIT_GENERIC_ANDROID = 0, + XR_PERFORMANCE_METRICS_COUNTER_UNIT_PERCENTAGE_ANDROID = 1, + XR_PERFORMANCE_METRICS_COUNTER_UNIT_MILLISECONDS_ANDROID = 2, + XR_PERFORMANCE_METRICS_COUNTER_UNIT_BYTES_ANDROID = 3, + XR_PERFORMANCE_METRICS_COUNTER_UNIT_HERTZ_ANDROID = 4, + XR_PERFORMANCE_METRICS_COUNTER_UNIT_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrPerformanceMetricsCounterUnitANDROID; +typedef XrFlags64 XrPerformanceMetricsCounterFlagsANDROID; + +// Flag bits for XrPerformanceMetricsCounterFlagsANDROID +static const XrPerformanceMetricsCounterFlagsANDROID XR_PERFORMANCE_METRICS_COUNTER_ANY_VALUE_VALID_BIT_ANDROID = 0x00000001; +static const XrPerformanceMetricsCounterFlagsANDROID XR_PERFORMANCE_METRICS_COUNTER_UINT_VALUE_VALID_BIT_ANDROID = 0x00000002; +static const XrPerformanceMetricsCounterFlagsANDROID XR_PERFORMANCE_METRICS_COUNTER_FLOAT_VALUE_VALID_BIT_ANDROID = 0x00000004; + +typedef struct XrPerformanceMetricsStateANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 enabled; +} XrPerformanceMetricsStateANDROID; + +typedef struct XrPerformanceMetricsCounterANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrPerformanceMetricsCounterFlagsANDROID counterFlags; + XrPerformanceMetricsCounterUnitANDROID counterUnit; + uint32_t uintValue; + float floatValue; +} XrPerformanceMetricsCounterANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumeratePerformanceMetricsCounterPathsANDROID)(XrInstance instance, uint32_t counterPathCapacityInput, uint32_t* counterPathCountOutput, XrPath* counterPaths); +typedef XrResult (XRAPI_PTR *PFN_xrSetPerformanceMetricsStateANDROID)(XrSession session, const XrPerformanceMetricsStateANDROID* state); +typedef XrResult (XRAPI_PTR *PFN_xrGetPerformanceMetricsStateANDROID)(XrSession session, XrPerformanceMetricsStateANDROID* state); +typedef XrResult (XRAPI_PTR *PFN_xrQueryPerformanceMetricsCounterANDROID)(XrSession session, XrPath counterPath, XrPerformanceMetricsCounterANDROID* counter); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumeratePerformanceMetricsCounterPathsANDROID( + XrInstance instance, + uint32_t counterPathCapacityInput, + uint32_t* counterPathCountOutput, + XrPath* counterPaths); + +XRAPI_ATTR XrResult XRAPI_CALL xrSetPerformanceMetricsStateANDROID( + XrSession session, + const XrPerformanceMetricsStateANDROID* state); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetPerformanceMetricsStateANDROID( + XrSession session, + XrPerformanceMetricsStateANDROID* state); + +XRAPI_ATTR XrResult XRAPI_CALL xrQueryPerformanceMetricsCounterANDROID( + XrSession session, + XrPath counterPath, + XrPerformanceMetricsCounterANDROID* counter); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_trackables_object is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_trackables_object 1 +#define XR_ANDROID_trackables_object_SPEC_VERSION 2 +#define XR_ANDROID_TRACKABLES_OBJECT_EXTENSION_NAME "XR_ANDROID_trackables_object" + +typedef enum XrObjectLabelANDROID { + XR_OBJECT_LABEL_UNKNOWN_ANDROID = 0, + XR_OBJECT_LABEL_KEYBOARD_ANDROID = 1, + XR_OBJECT_LABEL_MOUSE_ANDROID = 2, + XR_OBJECT_LABEL_LAPTOP_ANDROID = 3, + XR_OBJECT_LABEL_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrObjectLabelANDROID; +typedef struct XrTrackableObjectANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrTrackingStateANDROID trackingState; + XrPosef centerPose; + XrExtent3DfEXT extents; + XrObjectLabelANDROID objectLabel; + XrTime lastUpdatedTime; +} XrTrackableObjectANDROID; + +// XrTrackableObjectConfigurationANDROID extends XrTrackableTrackerCreateInfoANDROID +typedef struct XrTrackableObjectConfigurationANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t labelCount; + const XrObjectLabelANDROID* activeLabels; +} XrTrackableObjectConfigurationANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrGetTrackableObjectANDROID)(XrTrackableTrackerANDROID tracker, const XrTrackableGetInfoANDROID* getInfo, XrTrackableObjectANDROID* objectOutput); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetTrackableObjectANDROID( + XrTrackableTrackerANDROID tracker, + const XrTrackableGetInfoANDROID* getInfo, + XrTrackableObjectANDROID* objectOutput); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_unbounded_reference_space is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_unbounded_reference_space 1 +#define XR_ANDROID_unbounded_reference_space_SPEC_VERSION 1 +#define XR_ANDROID_UNBOUNDED_REFERENCE_SPACE_EXTENSION_NAME "XR_ANDROID_unbounded_reference_space" + + +// XR_EXT_future is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_future 1 +#define XR_EXT_future_SPEC_VERSION 2 +#define XR_EXT_FUTURE_EXTENSION_NAME "XR_EXT_future" +#define XR_NULL_FUTURE_EXT 0 + +typedef enum XrFutureStateEXT { + XR_FUTURE_STATE_PENDING_EXT = 1, + XR_FUTURE_STATE_READY_EXT = 2, + XR_FUTURE_STATE_MAX_ENUM_EXT = 0x7FFFFFFF +} XrFutureStateEXT; +typedef struct XrFutureCancelInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrFutureEXT future; +} XrFutureCancelInfoEXT; + +typedef struct XrFuturePollInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrFutureEXT future; +} XrFuturePollInfoEXT; + +typedef struct XR_MAY_ALIAS XrFutureCompletionBaseHeaderEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; +} XrFutureCompletionBaseHeaderEXT; + +typedef struct XrFuturePollResultEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrFutureStateEXT state; +} XrFuturePollResultEXT; + +typedef XrResult (XRAPI_PTR *PFN_xrPollFutureEXT)(XrInstance instance, const XrFuturePollInfoEXT* pollInfo, XrFuturePollResultEXT* pollResult); +typedef XrResult (XRAPI_PTR *PFN_xrCancelFutureEXT)(XrInstance instance, const XrFutureCancelInfoEXT* cancelInfo); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrPollFutureEXT( + XrInstance instance, + const XrFuturePollInfoEXT* pollInfo, + XrFuturePollResultEXT* pollResult); + +XRAPI_ATTR XrResult XRAPI_CALL xrCancelFutureEXT( + XrInstance instance, + const XrFutureCancelInfoEXT* cancelInfo); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_user_presence is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_user_presence 1 +#define XR_EXT_user_presence_SPEC_VERSION 1 +#define XR_EXT_USER_PRESENCE_EXTENSION_NAME "XR_EXT_user_presence" +typedef struct XrEventDataUserPresenceChangedEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSession session; + XrBool32 isUserPresent; +} XrEventDataUserPresenceChangedEXT; + +// XrSystemUserPresencePropertiesEXT extends XrSystemProperties +typedef struct XrSystemUserPresencePropertiesEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsUserPresence; +} XrSystemUserPresencePropertiesEXT; + + + +// XR_ML_user_calibration is a preprocessor guard. Do not pass it to API calls. +#define XR_ML_user_calibration 1 +#define XR_ML_user_calibration_SPEC_VERSION 1 +#define XR_ML_USER_CALIBRATION_EXTENSION_NAME "XR_ML_user_calibration" + +typedef enum XrHeadsetFitStatusML { + XR_HEADSET_FIT_STATUS_UNKNOWN_ML = 0, + XR_HEADSET_FIT_STATUS_NOT_WORN_ML = 1, + XR_HEADSET_FIT_STATUS_GOOD_FIT_ML = 2, + XR_HEADSET_FIT_STATUS_BAD_FIT_ML = 3, + XR_HEADSET_FIT_STATUS_MAX_ENUM_ML = 0x7FFFFFFF +} XrHeadsetFitStatusML; + +typedef enum XrEyeCalibrationStatusML { + XR_EYE_CALIBRATION_STATUS_UNKNOWN_ML = 0, + XR_EYE_CALIBRATION_STATUS_NONE_ML = 1, + XR_EYE_CALIBRATION_STATUS_COARSE_ML = 2, + XR_EYE_CALIBRATION_STATUS_FINE_ML = 3, + XR_EYE_CALIBRATION_STATUS_MAX_ENUM_ML = 0x7FFFFFFF +} XrEyeCalibrationStatusML; +typedef struct XrEventDataHeadsetFitChangedML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrHeadsetFitStatusML status; + XrTime time; +} XrEventDataHeadsetFitChangedML; + +typedef struct XrEventDataEyeCalibrationChangedML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrEyeCalibrationStatusML status; +} XrEventDataEyeCalibrationChangedML; + +typedef struct XrUserCalibrationEnableEventsInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 enabled; +} XrUserCalibrationEnableEventsInfoML; + +typedef XrResult (XRAPI_PTR *PFN_xrEnableUserCalibrationEventsML)(XrInstance instance, const XrUserCalibrationEnableEventsInfoML* enableInfo); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnableUserCalibrationEventsML( + XrInstance instance, + const XrUserCalibrationEnableEventsInfoML* enableInfo); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ML_system_notifications is a preprocessor guard. Do not pass it to API calls. +#define XR_ML_system_notifications 1 +#define XR_ML_system_notifications_SPEC_VERSION 1 +#define XR_ML_SYSTEM_NOTIFICATIONS_EXTENSION_NAME "XR_ML_system_notifications" +typedef struct XrSystemNotificationsSetInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 suppressNotifications; +} XrSystemNotificationsSetInfoML; + +typedef XrResult (XRAPI_PTR *PFN_xrSetSystemNotificationsML)(XrInstance instance, const XrSystemNotificationsSetInfoML* info); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrSetSystemNotificationsML( + XrInstance instance, + const XrSystemNotificationsSetInfoML* info); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ML_world_mesh_detection is a preprocessor guard. Do not pass it to API calls. +#define XR_ML_world_mesh_detection 1 +XR_DEFINE_HANDLE(XrWorldMeshDetectorML) +#define XR_ML_world_mesh_detection_SPEC_VERSION 1 +#define XR_ML_WORLD_MESH_DETECTION_EXTENSION_NAME "XR_ML_world_mesh_detection" + +typedef enum XrWorldMeshDetectorLodML { + XR_WORLD_MESH_DETECTOR_LOD_MINIMUM_ML = 0, + XR_WORLD_MESH_DETECTOR_LOD_MEDIUM_ML = 1, + XR_WORLD_MESH_DETECTOR_LOD_MAXIMUM_ML = 2, + XR_WORLD_MESH_DETECTOR_LOD_MAX_ENUM_ML = 0x7FFFFFFF +} XrWorldMeshDetectorLodML; + +typedef enum XrWorldMeshBlockStatusML { + XR_WORLD_MESH_BLOCK_STATUS_NEW_ML = 0, + XR_WORLD_MESH_BLOCK_STATUS_UPDATED_ML = 1, + XR_WORLD_MESH_BLOCK_STATUS_DELETED_ML = 2, + XR_WORLD_MESH_BLOCK_STATUS_UNCHANGED_ML = 3, + XR_WORLD_MESH_BLOCK_STATUS_MAX_ENUM_ML = 0x7FFFFFFF +} XrWorldMeshBlockStatusML; + +typedef enum XrWorldMeshBlockResultML { + XR_WORLD_MESH_BLOCK_RESULT_SUCCESS_ML = 0, + XR_WORLD_MESH_BLOCK_RESULT_FAILED_ML = 1, + XR_WORLD_MESH_BLOCK_RESULT_PENDING_ML = 2, + XR_WORLD_MESH_BLOCK_RESULT_PARTIAL_UPDATE_ML = 3, + XR_WORLD_MESH_BLOCK_RESULT_MAX_ENUM_ML = 0x7FFFFFFF +} XrWorldMeshBlockResultML; +typedef XrFlags64 XrWorldMeshDetectorFlagsML; + +// Flag bits for XrWorldMeshDetectorFlagsML +static const XrWorldMeshDetectorFlagsML XR_WORLD_MESH_DETECTOR_POINT_CLOUD_BIT_ML = 0x00000001; +static const XrWorldMeshDetectorFlagsML XR_WORLD_MESH_DETECTOR_COMPUTE_NORMALS_BIT_ML = 0x00000002; +static const XrWorldMeshDetectorFlagsML XR_WORLD_MESH_DETECTOR_COMPUTE_CONFIDENCE_BIT_ML = 0x00000004; +static const XrWorldMeshDetectorFlagsML XR_WORLD_MESH_DETECTOR_PLANARIZE_BIT_ML = 0x00000008; +static const XrWorldMeshDetectorFlagsML XR_WORLD_MESH_DETECTOR_REMOVE_MESH_SKIRT_BIT_ML = 0x00000010; +static const XrWorldMeshDetectorFlagsML XR_WORLD_MESH_DETECTOR_INDEX_ORDER_CW_BIT_ML = 0x00000020; + +typedef struct XrWorldMeshDetectorCreateInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrWorldMeshDetectorCreateInfoML; + +typedef struct XrWorldMeshBlockStateML { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrUuidEXT uuid; + XrPosef meshBoundingBoxCenter; + XrExtent3DfEXT meshBoundingBoxExtents; + XrTime lastUpdateTime; + XrWorldMeshBlockStatusML status; +} XrWorldMeshBlockStateML; + +typedef struct XrWorldMeshStateRequestInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace baseSpace; + XrTime time; + XrPosef boundingBoxCenter; + XrExtent3DfEXT boundingBoxExtents; +} XrWorldMeshStateRequestInfoML; + +typedef struct XrWorldMeshStateRequestCompletionML { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + XrTime timestamp; + uint32_t meshBlockStateCapacityInput; + uint32_t meshBlockStateCountOutput; + XrWorldMeshBlockStateML* meshBlockStates; +} XrWorldMeshStateRequestCompletionML; + +typedef struct XrWorldMeshBufferRecommendedSizeInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t maxBlockCount; +} XrWorldMeshBufferRecommendedSizeInfoML; + +typedef struct XrWorldMeshBufferSizeML { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t size; +} XrWorldMeshBufferSizeML; + +typedef struct XrWorldMeshBufferML { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t bufferSize; + void* XR_MAY_ALIAS buffer; +} XrWorldMeshBufferML; + +typedef struct XrWorldMeshBlockRequestML { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrUuidEXT uuid; + XrWorldMeshDetectorLodML lod; +} XrWorldMeshBlockRequestML; + +typedef struct XrWorldMeshGetInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrWorldMeshDetectorFlagsML flags; + float fillHoleLength; + float disconnectedComponentArea; + uint32_t blockCount; + XrWorldMeshBlockRequestML* blocks; +} XrWorldMeshGetInfoML; + +typedef struct XrWorldMeshBlockML { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrUuidEXT uuid; + XrWorldMeshBlockResultML blockResult; + XrWorldMeshDetectorLodML lod; + XrWorldMeshDetectorFlagsML flags; + uint32_t indexCount; + uint16_t* indexBuffer; + uint32_t vertexCount; + XrVector3f* vertexBuffer; + uint32_t normalCount; + XrVector3f* normalBuffer; + uint32_t confidenceCount; + float* confidenceBuffer; +} XrWorldMeshBlockML; + +typedef struct XrWorldMeshRequestCompletionInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace meshSpace; + XrTime meshSpaceLocateTime; +} XrWorldMeshRequestCompletionInfoML; + +typedef struct XrWorldMeshRequestCompletionML { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + uint32_t blockCount; + XrWorldMeshBlockML* blocks; +} XrWorldMeshRequestCompletionML; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateWorldMeshDetectorML)(XrSession session, const XrWorldMeshDetectorCreateInfoML* createInfo, XrWorldMeshDetectorML* detector); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyWorldMeshDetectorML)(XrWorldMeshDetectorML detector); +typedef XrResult (XRAPI_PTR *PFN_xrRequestWorldMeshStateAsyncML)(XrWorldMeshDetectorML detector, const XrWorldMeshStateRequestInfoML* stateRequest, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrRequestWorldMeshStateCompleteML)(XrWorldMeshDetectorML detector, XrFutureEXT future, XrWorldMeshStateRequestCompletionML* completion); +typedef XrResult (XRAPI_PTR *PFN_xrGetWorldMeshBufferRecommendSizeML)(XrWorldMeshDetectorML detector, const XrWorldMeshBufferRecommendedSizeInfoML* sizeInfo, XrWorldMeshBufferSizeML* size); +typedef XrResult (XRAPI_PTR *PFN_xrAllocateWorldMeshBufferML)(XrWorldMeshDetectorML detector, const XrWorldMeshBufferSizeML* size, XrWorldMeshBufferML* buffer); +typedef XrResult (XRAPI_PTR *PFN_xrFreeWorldMeshBufferML)(XrWorldMeshDetectorML detector, const XrWorldMeshBufferML* buffer); +typedef XrResult (XRAPI_PTR *PFN_xrRequestWorldMeshAsyncML)(XrWorldMeshDetectorML detector, const XrWorldMeshGetInfoML* getInfo, XrWorldMeshBufferML* buffer, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrRequestWorldMeshCompleteML)(XrWorldMeshDetectorML detector, const XrWorldMeshRequestCompletionInfoML* completionInfo, XrFutureEXT future, XrWorldMeshRequestCompletionML* completion); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateWorldMeshDetectorML( + XrSession session, + const XrWorldMeshDetectorCreateInfoML* createInfo, + XrWorldMeshDetectorML* detector); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyWorldMeshDetectorML( + XrWorldMeshDetectorML detector); + +XRAPI_ATTR XrResult XRAPI_CALL xrRequestWorldMeshStateAsyncML( + XrWorldMeshDetectorML detector, + const XrWorldMeshStateRequestInfoML* stateRequest, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrRequestWorldMeshStateCompleteML( + XrWorldMeshDetectorML detector, + XrFutureEXT future, + XrWorldMeshStateRequestCompletionML* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetWorldMeshBufferRecommendSizeML( + XrWorldMeshDetectorML detector, + const XrWorldMeshBufferRecommendedSizeInfoML* sizeInfo, + XrWorldMeshBufferSizeML* size); + +XRAPI_ATTR XrResult XRAPI_CALL xrAllocateWorldMeshBufferML( + XrWorldMeshDetectorML detector, + const XrWorldMeshBufferSizeML* size, + XrWorldMeshBufferML* buffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrFreeWorldMeshBufferML( + XrWorldMeshDetectorML detector, + const XrWorldMeshBufferML* buffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrRequestWorldMeshAsyncML( + XrWorldMeshDetectorML detector, + const XrWorldMeshGetInfoML* getInfo, + XrWorldMeshBufferML* buffer, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrRequestWorldMeshCompleteML( + XrWorldMeshDetectorML detector, + const XrWorldMeshRequestCompletionInfoML* completionInfo, + XrFutureEXT future, + XrWorldMeshRequestCompletionML* completion); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ML_facial_expression is a preprocessor guard. Do not pass it to API calls. +#define XR_ML_facial_expression 1 +XR_DEFINE_HANDLE(XrFacialExpressionClientML) +#define XR_ML_facial_expression_SPEC_VERSION 1 +#define XR_ML_FACIAL_EXPRESSION_EXTENSION_NAME "XR_ML_facial_expression" + +typedef enum XrFacialBlendShapeML { + XR_FACIAL_BLEND_SHAPE_BROW_LOWERER_L_ML = 0, + XR_FACIAL_BLEND_SHAPE_BROW_LOWERER_R_ML = 1, + XR_FACIAL_BLEND_SHAPE_CHEEK_RAISER_L_ML = 2, + XR_FACIAL_BLEND_SHAPE_CHEEK_RAISER_R_ML = 3, + XR_FACIAL_BLEND_SHAPE_CHIN_RAISER_ML = 4, + XR_FACIAL_BLEND_SHAPE_DIMPLER_L_ML = 5, + XR_FACIAL_BLEND_SHAPE_DIMPLER_R_ML = 6, + XR_FACIAL_BLEND_SHAPE_EYES_CLOSED_L_ML = 7, + XR_FACIAL_BLEND_SHAPE_EYES_CLOSED_R_ML = 8, + XR_FACIAL_BLEND_SHAPE_INNER_BROW_RAISER_L_ML = 9, + XR_FACIAL_BLEND_SHAPE_INNER_BROW_RAISER_R_ML = 10, + XR_FACIAL_BLEND_SHAPE_JAW_DROP_ML = 11, + XR_FACIAL_BLEND_SHAPE_LID_TIGHTENER_L_ML = 12, + XR_FACIAL_BLEND_SHAPE_LID_TIGHTENER_R_ML = 13, + XR_FACIAL_BLEND_SHAPE_LIP_CORNER_DEPRESSOR_L_ML = 14, + XR_FACIAL_BLEND_SHAPE_LIP_CORNER_DEPRESSOR_R_ML = 15, + XR_FACIAL_BLEND_SHAPE_LIP_CORNER_PULLER_L_ML = 16, + XR_FACIAL_BLEND_SHAPE_LIP_CORNER_PULLER_R_ML = 17, + XR_FACIAL_BLEND_SHAPE_LIP_FUNNELER_LB_ML = 18, + XR_FACIAL_BLEND_SHAPE_LIP_FUNNELER_LT_ML = 19, + XR_FACIAL_BLEND_SHAPE_LIP_FUNNELER_RB_ML = 20, + XR_FACIAL_BLEND_SHAPE_LIP_FUNNELER_RT_ML = 21, + XR_FACIAL_BLEND_SHAPE_LIP_PRESSOR_L_ML = 22, + XR_FACIAL_BLEND_SHAPE_LIP_PRESSOR_R_ML = 23, + XR_FACIAL_BLEND_SHAPE_LIP_PUCKER_L_ML = 24, + XR_FACIAL_BLEND_SHAPE_LIP_PUCKER_R_ML = 25, + XR_FACIAL_BLEND_SHAPE_LIP_STRETCHER_L_ML = 26, + XR_FACIAL_BLEND_SHAPE_LIP_STRETCHER_R_ML = 27, + XR_FACIAL_BLEND_SHAPE_LIP_SUCK_LB_ML = 28, + XR_FACIAL_BLEND_SHAPE_LIP_SUCK_LT_ML = 29, + XR_FACIAL_BLEND_SHAPE_LIP_SUCK_RB_ML = 30, + XR_FACIAL_BLEND_SHAPE_LIP_SUCK_RT_ML = 31, + XR_FACIAL_BLEND_SHAPE_LIP_TIGHTENER_L_ML = 32, + XR_FACIAL_BLEND_SHAPE_LIP_TIGHTENER_R_ML = 33, + XR_FACIAL_BLEND_SHAPE_LIPS_TOWARD_ML = 34, + XR_FACIAL_BLEND_SHAPE_LOWER_LIP_DEPRESSOR_L_ML = 35, + XR_FACIAL_BLEND_SHAPE_LOWER_LIP_DEPRESSOR_R_ML = 36, + XR_FACIAL_BLEND_SHAPE_NOSE_WRINKLER_L_ML = 37, + XR_FACIAL_BLEND_SHAPE_NOSE_WRINKLER_R_ML = 38, + XR_FACIAL_BLEND_SHAPE_OUTER_BROW_RAISER_L_ML = 39, + XR_FACIAL_BLEND_SHAPE_OUTER_BROW_RAISER_R_ML = 40, + XR_FACIAL_BLEND_SHAPE_UPPER_LID_RAISER_L_ML = 41, + XR_FACIAL_BLEND_SHAPE_UPPER_LID_RAISER_R_ML = 42, + XR_FACIAL_BLEND_SHAPE_UPPER_LIP_RAISER_L_ML = 43, + XR_FACIAL_BLEND_SHAPE_UPPER_LIP_RAISER_R_ML = 44, + XR_FACIAL_BLEND_SHAPE_TONGUE_OUT_ML = 45, + XR_FACIAL_BLEND_SHAPE_MAX_ENUM_ML = 0x7FFFFFFF +} XrFacialBlendShapeML; +typedef XrFlags64 XrFacialExpressionBlendShapePropertiesFlagsML; + +// Flag bits for XrFacialExpressionBlendShapePropertiesFlagsML +static const XrFacialExpressionBlendShapePropertiesFlagsML XR_FACIAL_EXPRESSION_BLEND_SHAPE_PROPERTIES_VALID_BIT_ML = 0x00000001; +static const XrFacialExpressionBlendShapePropertiesFlagsML XR_FACIAL_EXPRESSION_BLEND_SHAPE_PROPERTIES_TRACKED_BIT_ML = 0x00000002; + +// XrSystemFacialExpressionPropertiesML extends XrSystemProperties +typedef struct XrSystemFacialExpressionPropertiesML { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsFacialExpression; +} XrSystemFacialExpressionPropertiesML; + +typedef struct XrFacialExpressionClientCreateInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t requestedCount; + const XrFacialBlendShapeML* requestedFacialBlendShapes; +} XrFacialExpressionClientCreateInfoML; + +typedef struct XrFacialExpressionBlendShapeGetInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrFacialExpressionBlendShapeGetInfoML; + +typedef struct XrFacialExpressionBlendShapePropertiesML { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrFacialBlendShapeML requestedFacialBlendShape; + float weight; + XrFacialExpressionBlendShapePropertiesFlagsML flags; + XrTime time; +} XrFacialExpressionBlendShapePropertiesML; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateFacialExpressionClientML)(XrSession session, const XrFacialExpressionClientCreateInfoML* createInfo, XrFacialExpressionClientML* facialExpressionClient); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyFacialExpressionClientML)(XrFacialExpressionClientML facialExpressionClient); +typedef XrResult (XRAPI_PTR *PFN_xrGetFacialExpressionBlendShapePropertiesML)(XrFacialExpressionClientML facialExpressionClient, const XrFacialExpressionBlendShapeGetInfoML* blendShapeGetInfo, uint32_t blendShapeCount, XrFacialExpressionBlendShapePropertiesML* blendShapes); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateFacialExpressionClientML( + XrSession session, + const XrFacialExpressionClientCreateInfoML* createInfo, + XrFacialExpressionClientML* facialExpressionClient); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyFacialExpressionClientML( + XrFacialExpressionClientML facialExpressionClient); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetFacialExpressionBlendShapePropertiesML( + XrFacialExpressionClientML facialExpressionClient, + const XrFacialExpressionBlendShapeGetInfoML* blendShapeGetInfo, + uint32_t blendShapeCount, + XrFacialExpressionBlendShapePropertiesML* blendShapes); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ML_view_configuration_depth_range_change is a preprocessor guard. Do not pass it to API calls. +#define XR_ML_view_configuration_depth_range_change 1 +#define XR_ML_view_configuration_depth_range_change_SPEC_VERSION 1 +#define XR_ML_VIEW_CONFIGURATION_DEPTH_RANGE_CHANGE_EXTENSION_NAME "XR_ML_view_configuration_depth_range_change" + + +// XR_YVR_controller_interaction is a preprocessor guard. Do not pass it to API calls. +#define XR_YVR_controller_interaction 1 +#define XR_YVR_controller_interaction_SPEC_VERSION 1 +#define XR_YVR_CONTROLLER_INTERACTION_EXTENSION_NAME "XR_YVR_controller_interaction" + + +// XR_META_boundary_visibility is a preprocessor guard. Do not pass it to API calls. +#define XR_META_boundary_visibility 1 +#define XR_META_boundary_visibility_SPEC_VERSION 1 +#define XR_META_BOUNDARY_VISIBILITY_EXTENSION_NAME "XR_META_boundary_visibility" + +typedef enum XrBoundaryVisibilityMETA { + XR_BOUNDARY_VISIBILITY_NOT_SUPPRESSED_META = 1, + XR_BOUNDARY_VISIBILITY_SUPPRESSED_META = 2, + XR_BOUNDARY_VISIBILITY_MAX_ENUM_META = 0x7FFFFFFF +} XrBoundaryVisibilityMETA; +// XrSystemBoundaryVisibilityPropertiesMETA extends XrSystemProperties +typedef struct XrSystemBoundaryVisibilityPropertiesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsBoundaryVisibility; +} XrSystemBoundaryVisibilityPropertiesMETA; + +typedef struct XrEventDataBoundaryVisibilityChangedMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBoundaryVisibilityMETA boundaryVisibility; +} XrEventDataBoundaryVisibilityChangedMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrRequestBoundaryVisibilityMETA)(XrSession session, XrBoundaryVisibilityMETA boundaryVisibility); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrRequestBoundaryVisibilityMETA( + XrSession session, + XrBoundaryVisibilityMETA boundaryVisibility); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_simultaneous_hands_and_controllers is a preprocessor guard. Do not pass it to API calls. +#define XR_META_simultaneous_hands_and_controllers 1 +#define XR_META_simultaneous_hands_and_controllers_SPEC_VERSION 1 +#define XR_META_SIMULTANEOUS_HANDS_AND_CONTROLLERS_EXTENSION_NAME "XR_META_simultaneous_hands_and_controllers" +// XrSystemSimultaneousHandsAndControllersPropertiesMETA extends XrSystemProperties +typedef struct XrSystemSimultaneousHandsAndControllersPropertiesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsSimultaneousHandsAndControllers; +} XrSystemSimultaneousHandsAndControllersPropertiesMETA; + +typedef struct XrSimultaneousHandsAndControllersTrackingResumeInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSimultaneousHandsAndControllersTrackingResumeInfoMETA; + +typedef struct XrSimultaneousHandsAndControllersTrackingPauseInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSimultaneousHandsAndControllersTrackingPauseInfoMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrResumeSimultaneousHandsAndControllersTrackingMETA)(XrSession session, const XrSimultaneousHandsAndControllersTrackingResumeInfoMETA* resumeInfo); +typedef XrResult (XRAPI_PTR *PFN_xrPauseSimultaneousHandsAndControllersTrackingMETA)(XrSession session, const XrSimultaneousHandsAndControllersTrackingPauseInfoMETA* pauseInfo); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrResumeSimultaneousHandsAndControllersTrackingMETA( + XrSession session, + const XrSimultaneousHandsAndControllersTrackingResumeInfoMETA* resumeInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrPauseSimultaneousHandsAndControllersTrackingMETA( + XrSession session, + const XrSimultaneousHandsAndControllersTrackingPauseInfoMETA* pauseInfo); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_face_tracking_visemes is a preprocessor guard. Do not pass it to API calls. +#define XR_META_face_tracking_visemes 1 +#define XR_FACE_TRACKING_VISEME_COUNT_META 15 +#define XR_META_face_tracking_visemes_SPEC_VERSION 1 +#define XR_META_FACE_TRACKING_VISEMES_EXTENSION_NAME "XR_META_face_tracking_visemes" + +typedef enum XrFaceTrackingVisemeMETA { + XR_FACE_TRACKING_VISEME_SIL_META = 0, + XR_FACE_TRACKING_VISEME_PP_META = 1, + XR_FACE_TRACKING_VISEME_FF_META = 2, + XR_FACE_TRACKING_VISEME_TH_META = 3, + XR_FACE_TRACKING_VISEME_DD_META = 4, + XR_FACE_TRACKING_VISEME_KK_META = 5, + XR_FACE_TRACKING_VISEME_CH_META = 6, + XR_FACE_TRACKING_VISEME_SS_META = 7, + XR_FACE_TRACKING_VISEME_NN_META = 8, + XR_FACE_TRACKING_VISEME_RR_META = 9, + XR_FACE_TRACKING_VISEME_AA_META = 10, + XR_FACE_TRACKING_VISEME_E_META = 11, + XR_FACE_TRACKING_VISEME_IH_META = 12, + XR_FACE_TRACKING_VISEME_OH_META = 13, + XR_FACE_TRACKING_VISEME_OU_META = 14, + XR_FACE_TRACKING_VISEME_MAX_ENUM_META = 0x7FFFFFFF +} XrFaceTrackingVisemeMETA; +// XrFaceTrackingVisemesMETA extends XrFaceExpressionWeights2FB +typedef struct XrFaceTrackingVisemesMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 isValid; + float visemes[XR_FACE_TRACKING_VISEME_COUNT_META]; +} XrFaceTrackingVisemesMETA; + +// XrSystemFaceTrackingVisemesPropertiesMETA extends XrSystemProperties +typedef struct XrSystemFaceTrackingVisemesPropertiesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsVisemes; +} XrSystemFaceTrackingVisemesPropertiesMETA; + + + +// XR_META_spatial_entity_semantic_label is a preprocessor guard. Do not pass it to API calls. +#define XR_META_spatial_entity_semantic_label 1 +#define XR_META_spatial_entity_semantic_label_SPEC_VERSION 1 +#define XR_META_SPATIAL_ENTITY_SEMANTIC_LABEL_EXTENSION_NAME "XR_META_spatial_entity_semantic_label" + +typedef enum XrSemanticLabelMETA { + XR_SEMANTIC_LABEL_UNKNOWN_META = 0, + XR_SEMANTIC_LABEL_FLOOR_META = 1, + XR_SEMANTIC_LABEL_CEILING_META = 2, + XR_SEMANTIC_LABEL_WALL_FACE_META = 3, + XR_SEMANTIC_LABEL_INNER_WALL_FACE_META = 4, + XR_SEMANTIC_LABEL_INVISIBLE_WALL_FACE_META = 5, + XR_SEMANTIC_LABEL_DOOR_FRAME_META = 6, + XR_SEMANTIC_LABEL_WINDOW_FRAME_META = 7, + XR_SEMANTIC_LABEL_MAX_ENUM_META = 0x7FFFFFFF +} XrSemanticLabelMETA; + + +// XR_META_spatial_entity_room_mesh is a preprocessor guard. Do not pass it to API calls. +#define XR_META_spatial_entity_room_mesh 1 +#define XR_META_spatial_entity_room_mesh_SPEC_VERSION 1 +#define XR_META_SPATIAL_ENTITY_ROOM_MESH_EXTENSION_NAME "XR_META_spatial_entity_room_mesh" +typedef struct XrRoomMeshFaceMETA { + XrUuid uuid; + XrUuid parentUuid; + XrSemanticLabelMETA semanticLabel; +} XrRoomMeshFaceMETA; + +typedef struct XrRoomMeshFaceIndicesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t indexCapacityInput; + uint32_t indexCountOutput; + uint32_t* indices; +} XrRoomMeshFaceIndicesMETA; + +typedef struct XrSpaceRoomMeshGetInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t recognizedSemanticLabelCount; + const XrSemanticLabelMETA* recognizedSemanticLabels; +} XrSpaceRoomMeshGetInfoMETA; + +typedef struct XrRoomMeshMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t vertexCapacityInput; + uint32_t vertexCountOutput; + XrVector3f* vertices; + uint32_t faceCapacityInput; + uint32_t faceCountOutput; + XrRoomMeshFaceMETA* faces; +} XrRoomMeshMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrGetSpaceRoomMeshMETA)(XrSpace space, const XrSpaceRoomMeshGetInfoMETA* getInfo, XrRoomMeshMETA* roomMeshOutput); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpaceRoomMeshFaceIndicesMETA)(XrSpace space, const XrUuid* faceUuid, XrRoomMeshFaceIndicesMETA* roomMeshFaceIndicesOutput); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpaceRoomMeshMETA( + XrSpace space, + const XrSpaceRoomMeshGetInfoMETA* getInfo, + XrRoomMeshMETA* roomMeshOutput); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpaceRoomMeshFaceIndicesMETA( + XrSpace space, + const XrUuid* faceUuid, + XrRoomMeshFaceIndicesMETA* roomMeshFaceIndicesOutput); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_composition_layer_inverted_alpha is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_composition_layer_inverted_alpha 1 +#define XR_EXT_composition_layer_inverted_alpha_SPEC_VERSION 1 +#define XR_EXT_COMPOSITION_LAYER_INVERTED_ALPHA_EXTENSION_NAME "XR_EXT_composition_layer_inverted_alpha" + + +// XR_META_colocation_discovery is a preprocessor guard. Do not pass it to API calls. +#define XR_META_colocation_discovery 1 +#define XR_MAX_COLOCATION_DISCOVERY_BUFFER_SIZE_META 1024 +#define XR_META_colocation_discovery_SPEC_VERSION 1 +#define XR_META_COLOCATION_DISCOVERY_EXTENSION_NAME "XR_META_colocation_discovery" +typedef struct XrColocationDiscoveryStartInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrColocationDiscoveryStartInfoMETA; + +typedef struct XrColocationDiscoveryStopInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrColocationDiscoveryStopInfoMETA; + +typedef struct XrColocationAdvertisementStartInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t bufferSize; + uint8_t* buffer; +} XrColocationAdvertisementStartInfoMETA; + +typedef struct XrColocationAdvertisementStopInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrColocationAdvertisementStopInfoMETA; + +typedef struct XrEventDataStartColocationAdvertisementCompleteMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB advertisementRequestId; + XrResult result; + XrUuid advertisementUuid; +} XrEventDataStartColocationAdvertisementCompleteMETA; + +typedef struct XrEventDataStopColocationAdvertisementCompleteMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; + XrResult result; +} XrEventDataStopColocationAdvertisementCompleteMETA; + +typedef struct XrEventDataColocationAdvertisementCompleteMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB advertisementRequestId; + XrResult result; +} XrEventDataColocationAdvertisementCompleteMETA; + +typedef struct XrEventDataStartColocationDiscoveryCompleteMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB discoveryRequestId; + XrResult result; +} XrEventDataStartColocationDiscoveryCompleteMETA; + +typedef struct XrEventDataColocationDiscoveryResultMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB discoveryRequestId; + XrUuid advertisementUuid; + uint32_t bufferSize; + uint8_t buffer[XR_MAX_COLOCATION_DISCOVERY_BUFFER_SIZE_META]; +} XrEventDataColocationDiscoveryResultMETA; + +typedef struct XrEventDataColocationDiscoveryCompleteMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB discoveryRequestId; + XrResult result; +} XrEventDataColocationDiscoveryCompleteMETA; + +typedef struct XrEventDataStopColocationDiscoveryCompleteMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAsyncRequestIdFB requestId; + XrResult result; +} XrEventDataStopColocationDiscoveryCompleteMETA; + +// XrSystemColocationDiscoveryPropertiesMETA extends XrSystemProperties +typedef struct XrSystemColocationDiscoveryPropertiesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsColocationDiscovery; +} XrSystemColocationDiscoveryPropertiesMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrStartColocationDiscoveryMETA)(XrSession session, const XrColocationDiscoveryStartInfoMETA* info, XrAsyncRequestIdFB* discoveryRequestId); +typedef XrResult (XRAPI_PTR *PFN_xrStopColocationDiscoveryMETA)(XrSession session, const XrColocationDiscoveryStopInfoMETA* info, XrAsyncRequestIdFB* requestId); +typedef XrResult (XRAPI_PTR *PFN_xrStartColocationAdvertisementMETA)(XrSession session, const XrColocationAdvertisementStartInfoMETA* info, XrAsyncRequestIdFB* advertisementRequestId); +typedef XrResult (XRAPI_PTR *PFN_xrStopColocationAdvertisementMETA)(XrSession session, const XrColocationAdvertisementStopInfoMETA* info, XrAsyncRequestIdFB* requestId); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrStartColocationDiscoveryMETA( + XrSession session, + const XrColocationDiscoveryStartInfoMETA* info, + XrAsyncRequestIdFB* discoveryRequestId); + +XRAPI_ATTR XrResult XRAPI_CALL xrStopColocationDiscoveryMETA( + XrSession session, + const XrColocationDiscoveryStopInfoMETA* info, + XrAsyncRequestIdFB* requestId); + +XRAPI_ATTR XrResult XRAPI_CALL xrStartColocationAdvertisementMETA( + XrSession session, + const XrColocationAdvertisementStartInfoMETA* info, + XrAsyncRequestIdFB* advertisementRequestId); + +XRAPI_ATTR XrResult XRAPI_CALL xrStopColocationAdvertisementMETA( + XrSession session, + const XrColocationAdvertisementStopInfoMETA* info, + XrAsyncRequestIdFB* requestId); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_spatial_entity_group_sharing is a preprocessor guard. Do not pass it to API calls. +#define XR_META_spatial_entity_group_sharing 1 +#define XR_META_spatial_entity_group_sharing_SPEC_VERSION 1 +#define XR_META_SPATIAL_ENTITY_GROUP_SHARING_EXTENSION_NAME "XR_META_spatial_entity_group_sharing" +// XrSystemSpatialEntityGroupSharingPropertiesMETA extends XrSystemProperties +typedef struct XrSystemSpatialEntityGroupSharingPropertiesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsSpatialEntityGroupSharing; +} XrSystemSpatialEntityGroupSharingPropertiesMETA; + +typedef struct XrShareSpacesRecipientGroupsMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t groupCount; + XrUuid* groups; +} XrShareSpacesRecipientGroupsMETA; + +// XrSpaceGroupUuidFilterInfoMETA extends XrSpaceQueryInfoBaseHeaderFB +typedef struct XrSpaceGroupUuidFilterInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrUuid groupUuid; +} XrSpaceGroupUuidFilterInfoMETA; + + + +// XR_META_environment_raycast is a preprocessor guard. Do not pass it to API calls. +#define XR_META_environment_raycast 1 +XR_DEFINE_HANDLE(XrEnvironmentRaycasterMETA) +#define XR_META_environment_raycast_SPEC_VERSION 1 +#define XR_META_ENVIRONMENT_RAYCAST_EXTENSION_NAME "XR_META_environment_raycast" + +typedef enum XrEnvironmentRaycastHitStatusMETA { + XR_ENVIRONMENT_RAYCAST_HIT_STATUS_HIT_META = 1, + XR_ENVIRONMENT_RAYCAST_HIT_STATUS_NO_HIT_META = 2, + XR_ENVIRONMENT_RAYCAST_HIT_STATUS_HIT_POINT_OCCLUDED_META = 3, + XR_ENVIRONMENT_RAYCAST_HIT_STATUS_HIT_POINT_OUTSIDE_OF_FOV_META = 4, + XR_ENVIRONMENT_RAYCAST_HIT_STATUS_RAY_OCCLUDED_META = 5, + XR_ENVIRONMENT_RAYCAST_HIT_STATUS_HIT_INVALID_ORIENTATION_META = 6, + XR_ENVIRONMENT_RAYCAST_HIT_STATUS_MAX_ENUM_META = 0x7FFFFFFF +} XrEnvironmentRaycastHitStatusMETA; +// XrSystemEnvironmentRaycastPropertiesMETA extends XrSystemProperties +typedef struct XrSystemEnvironmentRaycastPropertiesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsEnvironmentRaycast; +} XrSystemEnvironmentRaycastPropertiesMETA; + +typedef struct XrEnvironmentRaycasterCreateInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrEnvironmentRaycasterCreateInfoMETA; + +typedef struct XrEnvironmentRaycasterCreateCompletionMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + XrEnvironmentRaycasterMETA environmentRaycaster; +} XrEnvironmentRaycasterCreateCompletionMETA; + +typedef struct XR_MAY_ALIAS XrEnvironmentRaycastFilterBaseHeaderMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrEnvironmentRaycastFilterBaseHeaderMETA; + +typedef struct XrEnvironmentRaycastHitGetInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace baseSpace; + XrTime time; + XrVector3f origin; + XrVector3f direction; + uint32_t filterCount; + const XrEnvironmentRaycastFilterBaseHeaderMETA* const * filters; +} XrEnvironmentRaycastHitGetInfoMETA; + +typedef struct XrEnvironmentRaycastHitMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrEnvironmentRaycastHitStatusMETA status; + XrPosef pose; +} XrEnvironmentRaycastHitMETA; + +typedef struct XrEnvironmentRaycastFilterDistanceMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + float maxDistance; +} XrEnvironmentRaycastFilterDistanceMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateEnvironmentRaycasterAsyncMETA)(XrSession session, const XrEnvironmentRaycasterCreateInfoMETA* info, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrCreateEnvironmentRaycasterCompleteMETA)(XrSession session, XrFutureEXT future, XrEnvironmentRaycasterCreateCompletionMETA* completion); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyEnvironmentRaycasterMETA)(XrEnvironmentRaycasterMETA environmentRaycaster); +typedef XrResult (XRAPI_PTR *PFN_xrPerformEnvironmentRaycastMETA)(XrEnvironmentRaycasterMETA environmentRaycaster, const XrEnvironmentRaycastHitGetInfoMETA* info, XrEnvironmentRaycastHitMETA* hitPoint); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateEnvironmentRaycasterAsyncMETA( + XrSession session, + const XrEnvironmentRaycasterCreateInfoMETA* info, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateEnvironmentRaycasterCompleteMETA( + XrSession session, + XrFutureEXT future, + XrEnvironmentRaycasterCreateCompletionMETA* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyEnvironmentRaycasterMETA( + XrEnvironmentRaycasterMETA environmentRaycaster); + +XRAPI_ATTR XrResult XRAPI_CALL xrPerformEnvironmentRaycastMETA( + XrEnvironmentRaycasterMETA environmentRaycaster, + const XrEnvironmentRaycastHitGetInfoMETA* info, + XrEnvironmentRaycastHitMETA* hitPoint); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_tile_properties_hint is a preprocessor guard. Do not pass it to API calls. +#define XR_META_tile_properties_hint 1 +#define XR_META_tile_properties_hint_SPEC_VERSION 1 +#define XR_META_TILE_PROPERTIES_HINT_EXTENSION_NAME "XR_META_tile_properties_hint" +typedef struct XrExtent3DiMETA { + int32_t width; + int32_t height; + int32_t depth; +} XrExtent3DiMETA; + +typedef struct XrTilePropertiesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrExtent3DiMETA tileDimensions; + XrExtent2Di apronDimensions; + XrOffset2Di origin; +} XrTilePropertiesMETA; + +typedef struct XrTilePropertiesHintMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t propertiesCount; + const XrTilePropertiesMETA* properties; +} XrTilePropertiesHintMETA; + +typedef XrResult (XRAPI_PTR *PFN_xrSetTilePropertiesHintMETA)(XrSession session, const XrTilePropertiesHintMETA* properties); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrSetTilePropertiesHintMETA( + XrSession session, + const XrTilePropertiesHintMETA* properties); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_hand_tracking_unextrapolated_poses is a preprocessor guard. Do not pass it to API calls. +#define XR_META_hand_tracking_unextrapolated_poses 1 +#define XR_META_hand_tracking_unextrapolated_poses_SPEC_VERSION 1 +#define XR_META_HAND_TRACKING_UNEXTRAPOLATED_POSES_EXTENSION_NAME "XR_META_hand_tracking_unextrapolated_poses" +// XrHandTrackingUnextrapolatedPosesRequestMETA extends XrHandJointsLocateInfoEXT +typedef struct XrHandTrackingUnextrapolatedPosesRequestMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrHandTrackingUnextrapolatedPosesRequestMETA; + +// XrHandTrackingUnextrapolatedPosesMETA extends XrHandJointLocationsEXT +typedef struct XrHandTrackingUnextrapolatedPosesMETA { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrTime captureTime; +} XrHandTrackingUnextrapolatedPosesMETA; + + + +// XR_META_hand_tracking_frequency_hint is a preprocessor guard. Do not pass it to API calls. +#define XR_META_hand_tracking_frequency_hint 1 +#define XR_META_hand_tracking_frequency_hint_SPEC_VERSION 1 +#define XR_META_HAND_TRACKING_FREQUENCY_HINT_EXTENSION_NAME "XR_META_hand_tracking_frequency_hint" + +typedef enum XrHandTrackingFrequencyHintMETA { + XR_HAND_TRACKING_FREQUENCY_HINT_DEFAULT_META = 1, + XR_HAND_TRACKING_FREQUENCY_HINT_HIGH_META = 2, + XR_HAND_TRACKING_FREQUENCY_HINT_MAX_ENUM_META = 0x7FFFFFFF +} XrHandTrackingFrequencyHintMETA; +typedef XrResult (XRAPI_PTR *PFN_xrSetHandTrackingFrequencyHintMETA)(XrSession session, XrHandTrackingFrequencyHintMETA frequencyHint); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrSetHandTrackingFrequencyHintMETA( + XrSession session, + XrHandTrackingFrequencyHintMETA frequencyHint); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_META_hand_tracking_wide_motion_mode2 is a preprocessor guard. Do not pass it to API calls. +#define XR_META_hand_tracking_wide_motion_mode2 1 +#define XR_META_hand_tracking_wide_motion_mode2_SPEC_VERSION 1 +#define XR_META_HAND_TRACKING_WIDE_MOTION_MODE2_EXTENSION_NAME "XR_META_hand_tracking_wide_motion_mode2" + + +// XR_ANDROID_light_estimation is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_light_estimation 1 +XR_DEFINE_HANDLE(XrLightEstimatorANDROID) +#define XR_ANDROID_light_estimation_SPEC_VERSION 1 +#define XR_ANDROID_LIGHT_ESTIMATION_EXTENSION_NAME "XR_ANDROID_light_estimation" + +typedef enum XrLightEstimateStateANDROID { + XR_LIGHT_ESTIMATE_STATE_VALID_ANDROID = 0, + XR_LIGHT_ESTIMATE_STATE_INVALID_ANDROID = 1, + XR_LIGHT_ESTIMATE_STATE_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrLightEstimateStateANDROID; + +typedef enum XrSphericalHarmonicsKindANDROID { + XR_SPHERICAL_HARMONICS_KIND_TOTAL_ANDROID = 0, + XR_SPHERICAL_HARMONICS_KIND_AMBIENT_ANDROID = 1, + XR_SPHERICAL_HARMONICS_KIND_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrSphericalHarmonicsKindANDROID; +// XrSystemLightEstimationPropertiesANDROID extends XrSystemProperties +typedef struct XrSystemLightEstimationPropertiesANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsLightEstimation; +} XrSystemLightEstimationPropertiesANDROID; + +typedef struct XrLightEstimatorCreateInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrLightEstimatorCreateInfoANDROID; + +typedef struct XrLightEstimateGetInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace space; + XrTime time; +} XrLightEstimateGetInfoANDROID; + +typedef struct XrLightEstimateANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrLightEstimateStateANDROID state; + XrTime lastUpdatedTime; +} XrLightEstimateANDROID; + +// XrDirectionalLightANDROID extends XrLightEstimateANDROID +typedef struct XrDirectionalLightANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrLightEstimateStateANDROID state; + XrVector3f intensity; + XrVector3f direction; +} XrDirectionalLightANDROID; + +// XrAmbientLightANDROID extends XrLightEstimateANDROID +typedef struct XrAmbientLightANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrLightEstimateStateANDROID state; + XrVector3f intensity; + XrVector3f colorCorrection; +} XrAmbientLightANDROID; + +// XrSphericalHarmonicsANDROID extends XrLightEstimateANDROID +typedef struct XrSphericalHarmonicsANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrLightEstimateStateANDROID state; + XrSphericalHarmonicsKindANDROID kind; + float coefficients[9][3]; +} XrSphericalHarmonicsANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateLightEstimatorANDROID)(XrSession session, XrLightEstimatorCreateInfoANDROID* createInfo, XrLightEstimatorANDROID* outHandle); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyLightEstimatorANDROID)(XrLightEstimatorANDROID estimator); +typedef XrResult (XRAPI_PTR *PFN_xrGetLightEstimateANDROID)(XrLightEstimatorANDROID estimator, const XrLightEstimateGetInfoANDROID* input, XrLightEstimateANDROID* output); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateLightEstimatorANDROID( + XrSession session, + XrLightEstimatorCreateInfoANDROID* createInfo, + XrLightEstimatorANDROID* outHandle); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyLightEstimatorANDROID( + XrLightEstimatorANDROID estimator); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetLightEstimateANDROID( + XrLightEstimatorANDROID estimator, + const XrLightEstimateGetInfoANDROID* input, + XrLightEstimateANDROID* output); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_mouse_interaction is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_mouse_interaction 1 +#define XR_ANDROID_mouse_interaction_SPEC_VERSION 1 +#define XR_ANDROID_MOUSE_INTERACTION_EXTENSION_NAME "XR_ANDROID_mouse_interaction" + + +// XR_ANDROID_trackables_marker is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_trackables_marker 1 +#define XR_ANDROID_trackables_marker_SPEC_VERSION 1 +#define XR_ANDROID_TRACKABLES_MARKER_EXTENSION_NAME "XR_ANDROID_trackables_marker" + +typedef enum XrTrackableMarkerTrackingModeANDROID { + XR_TRACKABLE_MARKER_TRACKING_MODE_DYNAMIC_ANDROID = 0, + XR_TRACKABLE_MARKER_TRACKING_MODE_STATIC_ANDROID = 1, + XR_TRACKABLE_MARKER_TRACKING_MODE_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrTrackableMarkerTrackingModeANDROID; + +typedef enum XrTrackableMarkerDictionaryANDROID { + XR_TRACKABLE_MARKER_DICTIONARY_ARUCO_4X4_50_ANDROID = 0, + XR_TRACKABLE_MARKER_DICTIONARY_ARUCO_4X4_100_ANDROID = 1, + XR_TRACKABLE_MARKER_DICTIONARY_ARUCO_4X4_250_ANDROID = 2, + XR_TRACKABLE_MARKER_DICTIONARY_ARUCO_4X4_1000_ANDROID = 3, + XR_TRACKABLE_MARKER_DICTIONARY_ARUCO_5X5_50_ANDROID = 4, + XR_TRACKABLE_MARKER_DICTIONARY_ARUCO_5X5_100_ANDROID = 5, + XR_TRACKABLE_MARKER_DICTIONARY_ARUCO_5X5_250_ANDROID = 6, + XR_TRACKABLE_MARKER_DICTIONARY_ARUCO_5X5_1000_ANDROID = 7, + XR_TRACKABLE_MARKER_DICTIONARY_ARUCO_6X6_50_ANDROID = 8, + XR_TRACKABLE_MARKER_DICTIONARY_ARUCO_6X6_100_ANDROID = 9, + XR_TRACKABLE_MARKER_DICTIONARY_ARUCO_6X6_250_ANDROID = 10, + XR_TRACKABLE_MARKER_DICTIONARY_ARUCO_6X6_1000_ANDROID = 11, + XR_TRACKABLE_MARKER_DICTIONARY_ARUCO_7X7_50_ANDROID = 12, + XR_TRACKABLE_MARKER_DICTIONARY_ARUCO_7X7_100_ANDROID = 13, + XR_TRACKABLE_MARKER_DICTIONARY_ARUCO_7X7_250_ANDROID = 14, + XR_TRACKABLE_MARKER_DICTIONARY_ARUCO_7X7_1000_ANDROID = 15, + XR_TRACKABLE_MARKER_DICTIONARY_APRILTAG_16H5_ANDROID = 16, + XR_TRACKABLE_MARKER_DICTIONARY_APRILTAG_25H9_ANDROID = 17, + XR_TRACKABLE_MARKER_DICTIONARY_APRILTAG_36H10_ANDROID = 18, + XR_TRACKABLE_MARKER_DICTIONARY_APRILTAG_36H11_ANDROID = 19, + XR_TRACKABLE_MARKER_DICTIONARY_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrTrackableMarkerDictionaryANDROID; +// XrSystemMarkerTrackingPropertiesANDROID extends XrSystemProperties +typedef struct XrSystemMarkerTrackingPropertiesANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsMarkerTracking; + XrBool32 supportsMarkerSizeEstimation; + uint16_t maxMarkerCount; +} XrSystemMarkerTrackingPropertiesANDROID; + +typedef struct XrTrackableMarkerDatabaseEntryANDROID { + int32_t id; + float edgeSize; +} XrTrackableMarkerDatabaseEntryANDROID; + +typedef struct XrTrackableMarkerDatabaseANDROID { + XrTrackableMarkerDictionaryANDROID dictionary; + uint32_t entryCount; + const XrTrackableMarkerDatabaseEntryANDROID* entries; +} XrTrackableMarkerDatabaseANDROID; + +// XrTrackableMarkerConfigurationANDROID extends XrTrackableTrackerCreateInfoANDROID +typedef struct XrTrackableMarkerConfigurationANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrTrackableMarkerTrackingModeANDROID trackingMode; + uint32_t databaseCount; + const XrTrackableMarkerDatabaseANDROID* databases; +} XrTrackableMarkerConfigurationANDROID; + +typedef struct XrTrackableMarkerANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrTrackingStateANDROID trackingState; + XrTime lastUpdatedTime; + XrTrackableMarkerDictionaryANDROID dictionary; + int32_t markerId; + XrPosef centerPose; + XrExtent2Df extents; +} XrTrackableMarkerANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrGetTrackableMarkerANDROID)(XrTrackableTrackerANDROID tracker, const XrTrackableGetInfoANDROID* getInfo, XrTrackableMarkerANDROID* markerOutput); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetTrackableMarkerANDROID( + XrTrackableTrackerANDROID tracker, + const XrTrackableGetInfoANDROID* getInfo, + XrTrackableMarkerANDROID* markerOutput); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_trackables_qr_code is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_trackables_qr_code 1 +#define XR_ANDROID_trackables_qr_code_SPEC_VERSION 1 +#define XR_ANDROID_TRACKABLES_QR_CODE_EXTENSION_NAME "XR_ANDROID_trackables_qr_code" + +typedef enum XrQrCodeTrackingModeANDROID { + XR_QR_CODE_TRACKING_MODE_DYNAMIC_ANDROID = 0, + XR_QR_CODE_TRACKING_MODE_STATIC_ANDROID = 1, + XR_QR_CODE_TRACKING_MODE_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrQrCodeTrackingModeANDROID; +// XrSystemQrCodeTrackingPropertiesANDROID extends XrSystemProperties +typedef struct XrSystemQrCodeTrackingPropertiesANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsQrCodeTracking; + XrBool32 supportsQrCodeSizeEstimation; + uint16_t maxQrCodeCount; +} XrSystemQrCodeTrackingPropertiesANDROID; + +// XrTrackableQrCodeConfigurationANDROID extends XrTrackableTrackerCreateInfoANDROID +typedef struct XrTrackableQrCodeConfigurationANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrQrCodeTrackingModeANDROID trackingMode; + float qrCodeEdgeSize; +} XrTrackableQrCodeConfigurationANDROID; + +typedef struct XrTrackableQrCodeANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrTrackingStateANDROID trackingState; + XrTime lastUpdatedTime; + XrPosef centerPose; + XrExtent2Df extents; + uint32_t bufferCapacityInput; + uint32_t bufferCountOutput; + char* buffer; +} XrTrackableQrCodeANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrGetTrackableQrCodeANDROID)(XrTrackableTrackerANDROID tracker, const XrTrackableGetInfoANDROID* getInfo, XrTrackableQrCodeANDROID* qrCodeOutput); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetTrackableQrCodeANDROID( + XrTrackableTrackerANDROID tracker, + const XrTrackableGetInfoANDROID* getInfo, + XrTrackableQrCodeANDROID* qrCodeOutput); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_trackables_image is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_trackables_image 1 +XR_DEFINE_HANDLE(XrTrackableImageDatabaseANDROID) +#define XR_ANDROID_trackables_image_SPEC_VERSION 1 +#define XR_ANDROID_TRACKABLES_IMAGE_EXTENSION_NAME "XR_ANDROID_trackables_image" + +typedef enum XrTrackableImageTrackingModeANDROID { + XR_TRACKABLE_IMAGE_TRACKING_MODE_DYNAMIC_ANDROID = 1, + XR_TRACKABLE_IMAGE_TRACKING_MODE_STATIC_ANDROID = 2, + XR_TRACKABLE_IMAGE_TRACKING_MODE_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrTrackableImageTrackingModeANDROID; + +typedef enum XrTrackableImageFormatANDROID { + XR_TRACKABLE_IMAGE_FORMAT_R8G8B8A8_ANDROID = 1, + XR_TRACKABLE_IMAGE_FORMAT_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrTrackableImageFormatANDROID; +// XrSystemImageTrackingPropertiesANDROID extends XrSystemProperties +typedef struct XrSystemImageTrackingPropertiesANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsImageTracking; + XrBool32 supportsPhysicalSizeEstimation; + uint32_t maxTrackedImageCount; + uint32_t maxLoadedImageCount; +} XrSystemImageTrackingPropertiesANDROID; + +typedef struct XrTrackableImageDatabaseEntryANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrTrackableImageTrackingModeANDROID trackingMode; + float physicalWidth; + uint32_t imageWidth; + uint32_t imageHeight; + XrTrackableImageFormatANDROID format; + uint32_t bufferSize; + const uint8_t* buffer; +} XrTrackableImageDatabaseEntryANDROID; + +typedef struct XrTrackableImageDatabaseCreateInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t entryCount; + const XrTrackableImageDatabaseEntryANDROID* entries; +} XrTrackableImageDatabaseCreateInfoANDROID; + +typedef struct XrCreateTrackableImageDatabaseCompletionANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + XrTrackableImageDatabaseANDROID database; +} XrCreateTrackableImageDatabaseCompletionANDROID; + +typedef struct XrTrackableImageConfigurationANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t databaseCount; + const XrTrackableImageDatabaseANDROID* databases; +} XrTrackableImageConfigurationANDROID; + +typedef struct XrTrackableImageANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrTrackingStateANDROID trackingState; + XrTime lastUpdatedTime; + XrTrackableImageDatabaseANDROID database; + uint32_t databaseEntryIndex; + XrPosef centerPose; + XrExtent2Df extents; +} XrTrackableImageANDROID; + +typedef struct XrEventDataImageTrackingLostANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrTime time; +} XrEventDataImageTrackingLostANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateTrackableImageDatabaseAsyncANDROID)(XrSession session, const XrTrackableImageDatabaseCreateInfoANDROID* createInfo, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrCreateTrackableImageDatabaseCompleteANDROID)(XrSession session, XrFutureEXT future, XrCreateTrackableImageDatabaseCompletionANDROID* completion); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyTrackableImageDatabaseANDROID)(XrTrackableImageDatabaseANDROID database); +typedef XrResult (XRAPI_PTR *PFN_xrAddTrackableImageDatabaseANDROID)(XrTrackableTrackerANDROID tracker, XrTrackableImageDatabaseANDROID database); +typedef XrResult (XRAPI_PTR *PFN_xrRemoveTrackableImageDatabaseANDROID)(XrTrackableTrackerANDROID tracker, XrTrackableImageDatabaseANDROID database); +typedef XrResult (XRAPI_PTR *PFN_xrGetTrackableImageANDROID)(XrTrackableTrackerANDROID tracker, const XrTrackableGetInfoANDROID* getInfo, XrTrackableImageANDROID* trackable); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateTrackableImageDatabaseAsyncANDROID( + XrSession session, + const XrTrackableImageDatabaseCreateInfoANDROID* createInfo, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateTrackableImageDatabaseCompleteANDROID( + XrSession session, + XrFutureEXT future, + XrCreateTrackableImageDatabaseCompletionANDROID* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyTrackableImageDatabaseANDROID( + XrTrackableImageDatabaseANDROID database); + +XRAPI_ATTR XrResult XRAPI_CALL xrAddTrackableImageDatabaseANDROID( + XrTrackableTrackerANDROID tracker, + XrTrackableImageDatabaseANDROID database); + +XRAPI_ATTR XrResult XRAPI_CALL xrRemoveTrackableImageDatabaseANDROID( + XrTrackableTrackerANDROID tracker, + XrTrackableImageDatabaseANDROID database); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetTrackableImageANDROID( + XrTrackableTrackerANDROID tracker, + const XrTrackableGetInfoANDROID* getInfo, + XrTrackableImageANDROID* trackable); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_scene_meshing is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_scene_meshing 1 +XR_DEFINE_HANDLE(XrSceneMeshingTrackerANDROID) +XR_DEFINE_HANDLE(XrSceneMeshSnapshotANDROID) +#define XR_ANDROID_scene_meshing_SPEC_VERSION 3 +#define XR_ANDROID_SCENE_MESHING_EXTENSION_NAME "XR_ANDROID_scene_meshing" + +typedef enum XrSceneMeshSemanticLabelSetANDROID { + XR_SCENE_MESH_SEMANTIC_LABEL_SET_NONE_ANDROID = 0, + XR_SCENE_MESH_SEMANTIC_LABEL_SET_DEFAULT_ANDROID = 1, + XR_SCENE_MESH_SEMANTIC_LABEL_SET_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrSceneMeshSemanticLabelSetANDROID; + +typedef enum XrSceneMeshTrackingStateANDROID { + XR_SCENE_MESH_TRACKING_STATE_INITIALIZING_ANDROID = 0, + XR_SCENE_MESH_TRACKING_STATE_TRACKING_ANDROID = 1, + XR_SCENE_MESH_TRACKING_STATE_WAITING_ANDROID = 2, + XR_SCENE_MESH_TRACKING_STATE_ERROR_ANDROID = 3, + XR_SCENE_MESH_TRACKING_STATE_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrSceneMeshTrackingStateANDROID; + +typedef enum XrSceneMeshSemanticLabelANDROID { + XR_SCENE_MESH_SEMANTIC_LABEL_OTHER_ANDROID = 0, + XR_SCENE_MESH_SEMANTIC_LABEL_FLOOR_ANDROID = 1, + XR_SCENE_MESH_SEMANTIC_LABEL_CEILING_ANDROID = 2, + XR_SCENE_MESH_SEMANTIC_LABEL_WALL_ANDROID = 3, + XR_SCENE_MESH_SEMANTIC_LABEL_TABLE_ANDROID = 4, + XR_SCENE_MESH_SEMANTIC_LABEL_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrSceneMeshSemanticLabelANDROID; +// XrSystemSceneMeshingPropertiesANDROID extends XrSystemProperties +typedef struct XrSystemSceneMeshingPropertiesANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsSceneMeshing; +} XrSystemSceneMeshingPropertiesANDROID; + +typedef struct XrSceneMeshingTrackerCreateInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSceneMeshSemanticLabelSetANDROID semanticLabelSet; + XrBool32 enableNormals; +} XrSceneMeshingTrackerCreateInfoANDROID; + +typedef struct XrSceneMeshSnapshotCreateInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace baseSpace; + XrTime time; + XrBoxf boundingBox; +} XrSceneMeshSnapshotCreateInfoANDROID; + +typedef struct XrSceneMeshSnapshotCreationResultANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSceneMeshSnapshotANDROID snapshot; + XrSceneMeshTrackingStateANDROID trackingState; +} XrSceneMeshSnapshotCreationResultANDROID; + +typedef struct XrSceneSubmeshStateANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrUuid submeshId; + XrTime lastUpdatedTime; + XrPosef submeshPoseInBaseSpace; + XrExtent3Df bounds; +} XrSceneSubmeshStateANDROID; + +typedef struct XrSceneSubmeshDataANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrUuid submeshId; + uint32_t vertexCapacityInput; + uint32_t vertexCountOutput; + XrVector3f* vertexPositions; + XrVector3f* vertexNormals; + uint8_t* vertexSemantics; + uint32_t indexCapacityInput; + uint32_t indexCountOutput; + uint32_t* indices; +} XrSceneSubmeshDataANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateSupportedSemanticLabelSetsANDROID)(XrInstance instance, XrSystemId systemId, uint32_t supportedSemanticLabelSetsInputCapacity, uint32_t* supportedSemanticLabelSetsOutputCount, XrSceneMeshSemanticLabelSetANDROID* supportedSemanticLabelSets); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSceneMeshingTrackerANDROID)(XrSession session, const XrSceneMeshingTrackerCreateInfoANDROID* createInfo, XrSceneMeshingTrackerANDROID* tracker); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySceneMeshingTrackerANDROID)(XrSceneMeshingTrackerANDROID tracker); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSceneMeshSnapshotANDROID)(XrSceneMeshingTrackerANDROID tracker, const XrSceneMeshSnapshotCreateInfoANDROID* createInfo, XrSceneMeshSnapshotCreationResultANDROID* outSnapshotCreationResult); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySceneMeshSnapshotANDROID)(XrSceneMeshSnapshotANDROID snapshot); +typedef XrResult (XRAPI_PTR *PFN_xrGetAllSubmeshStatesANDROID)(XrSceneMeshSnapshotANDROID snapshot, uint32_t submeshStateCapacityInput, uint32_t* submeshStateCountOutput, XrSceneSubmeshStateANDROID* submeshStates); +typedef XrResult (XRAPI_PTR *PFN_xrGetSubmeshDataANDROID)(XrSceneMeshSnapshotANDROID snapshot, uint32_t submeshDataCount, XrSceneSubmeshDataANDROID* inoutSubmeshData); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateSupportedSemanticLabelSetsANDROID( + XrInstance instance, + XrSystemId systemId, + uint32_t supportedSemanticLabelSetsInputCapacity, + uint32_t* supportedSemanticLabelSetsOutputCount, + XrSceneMeshSemanticLabelSetANDROID* supportedSemanticLabelSets); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSceneMeshingTrackerANDROID( + XrSession session, + const XrSceneMeshingTrackerCreateInfoANDROID* createInfo, + XrSceneMeshingTrackerANDROID* tracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySceneMeshingTrackerANDROID( + XrSceneMeshingTrackerANDROID tracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSceneMeshSnapshotANDROID( + XrSceneMeshingTrackerANDROID tracker, + const XrSceneMeshSnapshotCreateInfoANDROID* createInfo, + XrSceneMeshSnapshotCreationResultANDROID* outSnapshotCreationResult); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySceneMeshSnapshotANDROID( + XrSceneMeshSnapshotANDROID snapshot); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetAllSubmeshStatesANDROID( + XrSceneMeshSnapshotANDROID snapshot, + uint32_t submeshStateCapacityInput, + uint32_t* submeshStateCountOutput, + XrSceneSubmeshStateANDROID* submeshStates); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSubmeshDataANDROID( + XrSceneMeshSnapshotANDROID snapshot, + uint32_t submeshDataCount, + XrSceneSubmeshDataANDROID* inoutSubmeshData); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_spatial_entity is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_spatial_entity 1 + +#define XR_NULL_SPATIAL_ENTITY_ID_EXT 0 + + +#define XR_NULL_SPATIAL_BUFFER_ID_EXT 0 + +XR_DEFINE_ATOM(XrSpatialEntityIdEXT) +XR_DEFINE_ATOM(XrSpatialBufferIdEXT) +XR_DEFINE_HANDLE(XrSpatialEntityEXT) +XR_DEFINE_HANDLE(XrSpatialContextEXT) +XR_DEFINE_HANDLE(XrSpatialSnapshotEXT) +#define XR_EXT_spatial_entity_SPEC_VERSION 1 +#define XR_EXT_SPATIAL_ENTITY_EXTENSION_NAME "XR_EXT_spatial_entity" + +typedef enum XrSpatialCapabilityEXT { + XR_SPATIAL_CAPABILITY_PLANE_TRACKING_EXT = 1000741000, + XR_SPATIAL_CAPABILITY_MARKER_TRACKING_QR_CODE_EXT = 1000743000, + XR_SPATIAL_CAPABILITY_MARKER_TRACKING_MICRO_QR_CODE_EXT = 1000743001, + XR_SPATIAL_CAPABILITY_MARKER_TRACKING_ARUCO_MARKER_EXT = 1000743002, + XR_SPATIAL_CAPABILITY_MARKER_TRACKING_APRIL_TAG_EXT = 1000743003, + XR_SPATIAL_CAPABILITY_ANCHOR_EXT = 1000762000, + XR_SPATIAL_CAPABILITY_IMAGE_TRACKING_EXT = 1000782000, + XR_SPATIAL_CAPABILITY_OBJECT_TRACKING_ANDROID = 1000785000, + XR_SPATIAL_CAPABILITY_DEPTH_RAYCAST_ANDROID = 1000786000, + XR_SPATIAL_CAPABILITY_MAX_ENUM_EXT = 0x7FFFFFFF +} XrSpatialCapabilityEXT; + +typedef enum XrSpatialCapabilityFeatureEXT { + XR_SPATIAL_CAPABILITY_FEATURE_MARKER_TRACKING_FIXED_SIZE_MARKERS_EXT = 1000743000, + XR_SPATIAL_CAPABILITY_FEATURE_MARKER_TRACKING_STATIC_MARKERS_EXT = 1000743001, + XR_SPATIAL_CAPABILITY_FEATURE_SPHERE_BOUNDS_FILTER_ANDROID = 1000761000, + XR_SPATIAL_CAPABILITY_FEATURE_BOX_BOUNDS_FILTER_ANDROID = 1000761001, + XR_SPATIAL_CAPABILITY_FEATURE_FRUSTUM_BOUNDS_FILTER_ANDROID = 1000761002, + XR_SPATIAL_CAPABILITY_FEATURE_IMAGE_TRACKING_AUTOMATIC_SIZE_IMAGES_EXT = 1000782000, + XR_SPATIAL_CAPABILITY_FEATURE_IMAGE_TRACKING_STATIC_IMAGES_EXT = 1000782001, + XR_SPATIAL_CAPABILITY_FEATURE_IMAGE_TRACKING_FIXED_SIZE_IMAGES_EXT = 1000782002, + XR_SPATIAL_CAPABILITY_FEATURE_MAX_ENUM_EXT = 0x7FFFFFFF +} XrSpatialCapabilityFeatureEXT; + +typedef enum XrSpatialComponentTypeEXT { + XR_SPATIAL_COMPONENT_TYPE_BOUNDED_2D_EXT = 1, + XR_SPATIAL_COMPONENT_TYPE_BOUNDED_3D_EXT = 2, + XR_SPATIAL_COMPONENT_TYPE_PARENT_EXT = 3, + XR_SPATIAL_COMPONENT_TYPE_MESH_3D_EXT = 4, + XR_SPATIAL_COMPONENT_TYPE_PLANE_ALIGNMENT_EXT = 1000741000, + XR_SPATIAL_COMPONENT_TYPE_MESH_2D_EXT = 1000741001, + XR_SPATIAL_COMPONENT_TYPE_POLYGON_2D_EXT = 1000741002, + XR_SPATIAL_COMPONENT_TYPE_PLANE_SEMANTIC_LABEL_EXT = 1000741003, + XR_SPATIAL_COMPONENT_TYPE_MARKER_EXT = 1000743000, + XR_SPATIAL_COMPONENT_TYPE_ANCHOR_EXT = 1000762000, + XR_SPATIAL_COMPONENT_TYPE_PERSISTENCE_EXT = 1000763000, + XR_SPATIAL_COMPONENT_TYPE_IMAGE_2D_EXT = 1000782000, + XR_SPATIAL_COMPONENT_TYPE_OBJECT_SEMANTIC_LABEL_ANDROID = 1000785000, + XR_SPATIAL_COMPONENT_TYPE_RAYCAST_RESULT_ANDROID = 1000786000, + XR_SPATIAL_COMPONENT_TYPE_SUBSUMED_BY_ANDROID = 1000791000, + XR_SPATIAL_COMPONENT_TYPE_MAX_ENUM_EXT = 0x7FFFFFFF +} XrSpatialComponentTypeEXT; + +typedef enum XrSpatialEntityTrackingStateEXT { + XR_SPATIAL_ENTITY_TRACKING_STATE_STOPPED_EXT = 1, + XR_SPATIAL_ENTITY_TRACKING_STATE_PAUSED_EXT = 2, + XR_SPATIAL_ENTITY_TRACKING_STATE_TRACKING_EXT = 3, + XR_SPATIAL_ENTITY_TRACKING_STATE_MAX_ENUM_EXT = 0x7FFFFFFF +} XrSpatialEntityTrackingStateEXT; + +typedef enum XrSpatialBufferTypeEXT { + XR_SPATIAL_BUFFER_TYPE_UNKNOWN_EXT = 0, + XR_SPATIAL_BUFFER_TYPE_STRING_EXT = 1, + XR_SPATIAL_BUFFER_TYPE_UINT8_EXT = 2, + XR_SPATIAL_BUFFER_TYPE_UINT16_EXT = 3, + XR_SPATIAL_BUFFER_TYPE_UINT32_EXT = 4, + XR_SPATIAL_BUFFER_TYPE_FLOAT_EXT = 5, + XR_SPATIAL_BUFFER_TYPE_VECTOR2F_EXT = 6, + XR_SPATIAL_BUFFER_TYPE_VECTOR3F_EXT = 7, + XR_SPATIAL_BUFFER_TYPE_MAX_ENUM_EXT = 0x7FFFFFFF +} XrSpatialBufferTypeEXT; +typedef struct XrSpatialCapabilityComponentTypesEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t componentTypeCapacityInput; + uint32_t componentTypeCountOutput; + XrSpatialComponentTypeEXT* componentTypes; +} XrSpatialCapabilityComponentTypesEXT; + +typedef struct XR_MAY_ALIAS XrSpatialCapabilityConfigurationBaseHeaderEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialCapabilityEXT capability; + uint32_t enabledComponentCount; + const XrSpatialComponentTypeEXT* enabledComponents; +} XrSpatialCapabilityConfigurationBaseHeaderEXT; + +typedef struct XrSpatialContextCreateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t capabilityConfigCount; + const XrSpatialCapabilityConfigurationBaseHeaderEXT* const* capabilityConfigs; +} XrSpatialContextCreateInfoEXT; + +typedef struct XrCreateSpatialContextCompletionEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + XrSpatialContextEXT spatialContext; +} XrCreateSpatialContextCompletionEXT; + +typedef struct XrSpatialDiscoverySnapshotCreateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t componentTypeCount; + const XrSpatialComponentTypeEXT* componentTypes; +} XrSpatialDiscoverySnapshotCreateInfoEXT; + +typedef struct XrCreateSpatialDiscoverySnapshotCompletionInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace baseSpace; + XrTime time; + XrFutureEXT future; +} XrCreateSpatialDiscoverySnapshotCompletionInfoEXT; + +typedef struct XrCreateSpatialDiscoverySnapshotCompletionEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + XrSpatialSnapshotEXT snapshot; +} XrCreateSpatialDiscoverySnapshotCompletionEXT; + +typedef struct XrSpatialComponentDataQueryConditionEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t componentTypeCount; + const XrSpatialComponentTypeEXT* componentTypes; +} XrSpatialComponentDataQueryConditionEXT; + +typedef struct XrSpatialComponentDataQueryResultEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t entityIdCapacityInput; + uint32_t entityIdCountOutput; + XrSpatialEntityIdEXT* entityIds; + uint32_t entityStateCapacityInput; + uint32_t entityStateCountOutput; + XrSpatialEntityTrackingStateEXT* entityStates; +} XrSpatialComponentDataQueryResultEXT; + +typedef struct XrSpatialBufferEXT { + XrSpatialBufferIdEXT bufferId; + XrSpatialBufferTypeEXT bufferType; +} XrSpatialBufferEXT; + +typedef struct XrSpatialBufferGetInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialBufferIdEXT bufferId; +} XrSpatialBufferGetInfoEXT; + +typedef struct XrSpatialBounded2DDataEXT { + XrPosef center; + XrExtent2Df extents; +} XrSpatialBounded2DDataEXT; + +// XrSpatialComponentBounded2DListEXT extends XrSpatialComponentDataQueryResultEXT +typedef struct XrSpatialComponentBounded2DListEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t boundCount; + XrSpatialBounded2DDataEXT* bounds; +} XrSpatialComponentBounded2DListEXT; + +// XrSpatialComponentBounded3DListEXT extends XrSpatialComponentDataQueryResultEXT +typedef struct XrSpatialComponentBounded3DListEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t boundCount; + XrBoxf* bounds; +} XrSpatialComponentBounded3DListEXT; + +// XrSpatialComponentParentListEXT extends XrSpatialComponentDataQueryResultEXT +typedef struct XrSpatialComponentParentListEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t parentCount; + XrSpatialEntityIdEXT* parents; +} XrSpatialComponentParentListEXT; + +typedef struct XrSpatialMeshDataEXT { + XrPosef origin; + XrSpatialBufferEXT vertexBuffer; + XrSpatialBufferEXT indexBuffer; +} XrSpatialMeshDataEXT; + +// XrSpatialComponentMesh3DListEXT extends XrSpatialComponentDataQueryResultEXT +typedef struct XrSpatialComponentMesh3DListEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t meshCount; + XrSpatialMeshDataEXT* meshes; +} XrSpatialComponentMesh3DListEXT; + +typedef struct XrSpatialEntityFromIdCreateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialEntityIdEXT entityId; +} XrSpatialEntityFromIdCreateInfoEXT; + +typedef struct XrSpatialUpdateSnapshotCreateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t entityCount; + const XrSpatialEntityEXT* entities; + uint32_t componentTypeCount; + const XrSpatialComponentTypeEXT* componentTypes; + XrSpace baseSpace; + XrTime time; +} XrSpatialUpdateSnapshotCreateInfoEXT; + +typedef struct XrEventDataSpatialDiscoveryRecommendedEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialContextEXT spatialContext; +} XrEventDataSpatialDiscoveryRecommendedEXT; + +// XrSpatialFilterTrackingStateEXT extends XrSpatialDiscoverySnapshotCreateInfoEXT,XrSpatialComponentDataQueryConditionEXT +typedef struct XrSpatialFilterTrackingStateEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialEntityTrackingStateEXT trackingState; +} XrSpatialFilterTrackingStateEXT; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateSpatialCapabilitiesEXT)(XrInstance instance, XrSystemId systemId, uint32_t capabilityCapacityInput, uint32_t* capabilityCountOutput, XrSpatialCapabilityEXT* capabilities); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateSpatialCapabilityComponentTypesEXT)(XrInstance instance, XrSystemId systemId, XrSpatialCapabilityEXT capability, XrSpatialCapabilityComponentTypesEXT* capabilityComponents); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateSpatialCapabilityFeaturesEXT)(XrInstance instance, XrSystemId systemId, XrSpatialCapabilityEXT capability, uint32_t capabilityFeatureCapacityInput, uint32_t* capabilityFeatureCountOutput, XrSpatialCapabilityFeatureEXT* capabilityFeatures); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialContextAsyncEXT)(XrSession session, const XrSpatialContextCreateInfoEXT* createInfo, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialContextCompleteEXT)(XrSession session, XrFutureEXT future, XrCreateSpatialContextCompletionEXT* completion); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySpatialContextEXT)(XrSpatialContextEXT spatialContext); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialDiscoverySnapshotAsyncEXT)(XrSpatialContextEXT spatialContext, const XrSpatialDiscoverySnapshotCreateInfoEXT* createInfo, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialDiscoverySnapshotCompleteEXT)(XrSpatialContextEXT spatialContext, const XrCreateSpatialDiscoverySnapshotCompletionInfoEXT* createSnapshotCompletionInfo, XrCreateSpatialDiscoverySnapshotCompletionEXT* completion); +typedef XrResult (XRAPI_PTR *PFN_xrQuerySpatialComponentDataEXT)(XrSpatialSnapshotEXT snapshot, const XrSpatialComponentDataQueryConditionEXT* queryCondition, XrSpatialComponentDataQueryResultEXT* queryResult); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySpatialSnapshotEXT)(XrSpatialSnapshotEXT snapshot); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialEntityFromIdEXT)(XrSpatialContextEXT spatialContext, const XrSpatialEntityFromIdCreateInfoEXT* createInfo, XrSpatialEntityEXT* spatialEntity); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySpatialEntityEXT)(XrSpatialEntityEXT spatialEntity); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialUpdateSnapshotEXT)(XrSpatialContextEXT spatialContext, const XrSpatialUpdateSnapshotCreateInfoEXT* createInfo, XrSpatialSnapshotEXT* snapshot); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpatialBufferStringEXT)(XrSpatialSnapshotEXT snapshot, const XrSpatialBufferGetInfoEXT* info, uint32_t bufferCapacityInput, uint32_t* bufferCountOutput, char* buffer); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpatialBufferUint8EXT)(XrSpatialSnapshotEXT snapshot, const XrSpatialBufferGetInfoEXT* info, uint32_t bufferCapacityInput, uint32_t* bufferCountOutput, uint8_t* buffer); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpatialBufferUint16EXT)(XrSpatialSnapshotEXT snapshot, const XrSpatialBufferGetInfoEXT* info, uint32_t bufferCapacityInput, uint32_t* bufferCountOutput, uint16_t* buffer); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpatialBufferUint32EXT)(XrSpatialSnapshotEXT snapshot, const XrSpatialBufferGetInfoEXT* info, uint32_t bufferCapacityInput, uint32_t* bufferCountOutput, uint32_t* buffer); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpatialBufferFloatEXT)(XrSpatialSnapshotEXT snapshot, const XrSpatialBufferGetInfoEXT* info, uint32_t bufferCapacityInput, uint32_t* bufferCountOutput, float* buffer); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpatialBufferVector2fEXT)(XrSpatialSnapshotEXT snapshot, const XrSpatialBufferGetInfoEXT* info, uint32_t bufferCapacityInput, uint32_t* bufferCountOutput, XrVector2f* buffer); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpatialBufferVector3fEXT)(XrSpatialSnapshotEXT snapshot, const XrSpatialBufferGetInfoEXT* info, uint32_t bufferCapacityInput, uint32_t* bufferCountOutput, XrVector3f* buffer); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateSpatialCapabilitiesEXT( + XrInstance instance, + XrSystemId systemId, + uint32_t capabilityCapacityInput, + uint32_t* capabilityCountOutput, + XrSpatialCapabilityEXT* capabilities); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateSpatialCapabilityComponentTypesEXT( + XrInstance instance, + XrSystemId systemId, + XrSpatialCapabilityEXT capability, + XrSpatialCapabilityComponentTypesEXT* capabilityComponents); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateSpatialCapabilityFeaturesEXT( + XrInstance instance, + XrSystemId systemId, + XrSpatialCapabilityEXT capability, + uint32_t capabilityFeatureCapacityInput, + uint32_t* capabilityFeatureCountOutput, + XrSpatialCapabilityFeatureEXT* capabilityFeatures); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialContextAsyncEXT( + XrSession session, + const XrSpatialContextCreateInfoEXT* createInfo, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialContextCompleteEXT( + XrSession session, + XrFutureEXT future, + XrCreateSpatialContextCompletionEXT* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySpatialContextEXT( + XrSpatialContextEXT spatialContext); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialDiscoverySnapshotAsyncEXT( + XrSpatialContextEXT spatialContext, + const XrSpatialDiscoverySnapshotCreateInfoEXT* createInfo, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialDiscoverySnapshotCompleteEXT( + XrSpatialContextEXT spatialContext, + const XrCreateSpatialDiscoverySnapshotCompletionInfoEXT* createSnapshotCompletionInfo, + XrCreateSpatialDiscoverySnapshotCompletionEXT* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrQuerySpatialComponentDataEXT( + XrSpatialSnapshotEXT snapshot, + const XrSpatialComponentDataQueryConditionEXT* queryCondition, + XrSpatialComponentDataQueryResultEXT* queryResult); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySpatialSnapshotEXT( + XrSpatialSnapshotEXT snapshot); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialEntityFromIdEXT( + XrSpatialContextEXT spatialContext, + const XrSpatialEntityFromIdCreateInfoEXT* createInfo, + XrSpatialEntityEXT* spatialEntity); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySpatialEntityEXT( + XrSpatialEntityEXT spatialEntity); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialUpdateSnapshotEXT( + XrSpatialContextEXT spatialContext, + const XrSpatialUpdateSnapshotCreateInfoEXT* createInfo, + XrSpatialSnapshotEXT* snapshot); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpatialBufferStringEXT( + XrSpatialSnapshotEXT snapshot, + const XrSpatialBufferGetInfoEXT* info, + uint32_t bufferCapacityInput, + uint32_t* bufferCountOutput, + char* buffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpatialBufferUint8EXT( + XrSpatialSnapshotEXT snapshot, + const XrSpatialBufferGetInfoEXT* info, + uint32_t bufferCapacityInput, + uint32_t* bufferCountOutput, + uint8_t* buffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpatialBufferUint16EXT( + XrSpatialSnapshotEXT snapshot, + const XrSpatialBufferGetInfoEXT* info, + uint32_t bufferCapacityInput, + uint32_t* bufferCountOutput, + uint16_t* buffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpatialBufferUint32EXT( + XrSpatialSnapshotEXT snapshot, + const XrSpatialBufferGetInfoEXT* info, + uint32_t bufferCapacityInput, + uint32_t* bufferCountOutput, + uint32_t* buffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpatialBufferFloatEXT( + XrSpatialSnapshotEXT snapshot, + const XrSpatialBufferGetInfoEXT* info, + uint32_t bufferCapacityInput, + uint32_t* bufferCountOutput, + float* buffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpatialBufferVector2fEXT( + XrSpatialSnapshotEXT snapshot, + const XrSpatialBufferGetInfoEXT* info, + uint32_t bufferCapacityInput, + uint32_t* bufferCountOutput, + XrVector2f* buffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpatialBufferVector3fEXT( + XrSpatialSnapshotEXT snapshot, + const XrSpatialBufferGetInfoEXT* info, + uint32_t bufferCapacityInput, + uint32_t* bufferCountOutput, + XrVector3f* buffer); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_spatial_plane_tracking is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_spatial_plane_tracking 1 +#define XR_EXT_spatial_plane_tracking_SPEC_VERSION 1 +#define XR_EXT_SPATIAL_PLANE_TRACKING_EXTENSION_NAME "XR_EXT_spatial_plane_tracking" + +typedef enum XrSpatialPlaneAlignmentEXT { + XR_SPATIAL_PLANE_ALIGNMENT_HORIZONTAL_UPWARD_EXT = 0, + XR_SPATIAL_PLANE_ALIGNMENT_HORIZONTAL_DOWNWARD_EXT = 1, + XR_SPATIAL_PLANE_ALIGNMENT_VERTICAL_EXT = 2, + XR_SPATIAL_PLANE_ALIGNMENT_ARBITRARY_EXT = 3, + XR_SPATIAL_PLANE_ALIGNMENT_MAX_ENUM_EXT = 0x7FFFFFFF +} XrSpatialPlaneAlignmentEXT; + +typedef enum XrSpatialPlaneSemanticLabelEXT { + XR_SPATIAL_PLANE_SEMANTIC_LABEL_UNCATEGORIZED_EXT = 1, + XR_SPATIAL_PLANE_SEMANTIC_LABEL_FLOOR_EXT = 2, + XR_SPATIAL_PLANE_SEMANTIC_LABEL_WALL_EXT = 3, + XR_SPATIAL_PLANE_SEMANTIC_LABEL_CEILING_EXT = 4, + XR_SPATIAL_PLANE_SEMANTIC_LABEL_TABLE_EXT = 5, + XR_SPATIAL_PLANE_SEMANTIC_LABEL_MAX_ENUM_EXT = 0x7FFFFFFF +} XrSpatialPlaneSemanticLabelEXT; +typedef struct XrSpatialCapabilityConfigurationPlaneTrackingEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialCapabilityEXT capability; + uint32_t enabledComponentCount; + const XrSpatialComponentTypeEXT* enabledComponents; +} XrSpatialCapabilityConfigurationPlaneTrackingEXT; + +// XrSpatialComponentPlaneAlignmentListEXT extends XrSpatialComponentDataQueryResultEXT +typedef struct XrSpatialComponentPlaneAlignmentListEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t planeAlignmentCount; + XrSpatialPlaneAlignmentEXT* planeAlignments; +} XrSpatialComponentPlaneAlignmentListEXT; + +// XrSpatialComponentMesh2DListEXT extends XrSpatialComponentDataQueryResultEXT +typedef struct XrSpatialComponentMesh2DListEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t meshCount; + XrSpatialMeshDataEXT* meshes; +} XrSpatialComponentMesh2DListEXT; + +typedef struct XrSpatialPolygon2DDataEXT { + XrPosef origin; + XrSpatialBufferEXT vertexBuffer; +} XrSpatialPolygon2DDataEXT; + +// XrSpatialComponentPolygon2DListEXT extends XrSpatialComponentDataQueryResultEXT +typedef struct XrSpatialComponentPolygon2DListEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t polygonCount; + XrSpatialPolygon2DDataEXT* polygons; +} XrSpatialComponentPolygon2DListEXT; + +// XrSpatialComponentPlaneSemanticLabelListEXT extends XrSpatialComponentDataQueryResultEXT +typedef struct XrSpatialComponentPlaneSemanticLabelListEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t semanticLabelCount; + XrSpatialPlaneSemanticLabelEXT* semanticLabels; +} XrSpatialComponentPlaneSemanticLabelListEXT; + + + +// XR_EXT_stationary_reference_space is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_stationary_reference_space 1 +#define XR_EXT_stationary_reference_space_SPEC_VERSION 1 +#define XR_EXT_STATIONARY_REFERENCE_SPACE_EXTENSION_NAME "XR_EXT_stationary_reference_space" +typedef struct XrStationaryReferenceSpaceGenerationIdGetInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrStationaryReferenceSpaceGenerationIdGetInfoEXT; + +typedef struct XrStationaryReferenceSpaceGenerationIdResultEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrUuid generationId; +} XrStationaryReferenceSpaceGenerationIdResultEXT; + +typedef XrResult (XRAPI_PTR *PFN_xrGetStationaryReferenceSpaceGenerationIdEXT)(XrSession session, const XrStationaryReferenceSpaceGenerationIdGetInfoEXT* getInfo, XrStationaryReferenceSpaceGenerationIdResultEXT* generationIdResult); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetStationaryReferenceSpaceGenerationIdEXT( + XrSession session, + const XrStationaryReferenceSpaceGenerationIdGetInfoEXT* getInfo, + XrStationaryReferenceSpaceGenerationIdResultEXT* generationIdResult); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_spatial_marker_tracking is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_spatial_marker_tracking 1 +#define XR_EXT_spatial_marker_tracking_SPEC_VERSION 1 +#define XR_EXT_SPATIAL_MARKER_TRACKING_EXTENSION_NAME "XR_EXT_spatial_marker_tracking" + +typedef enum XrSpatialMarkerArucoDictEXT { + XR_SPATIAL_MARKER_ARUCO_DICT_4X4_50_EXT = 1, + XR_SPATIAL_MARKER_ARUCO_DICT_4X4_100_EXT = 2, + XR_SPATIAL_MARKER_ARUCO_DICT_4X4_250_EXT = 3, + XR_SPATIAL_MARKER_ARUCO_DICT_4X4_1000_EXT = 4, + XR_SPATIAL_MARKER_ARUCO_DICT_5X5_50_EXT = 5, + XR_SPATIAL_MARKER_ARUCO_DICT_5X5_100_EXT = 6, + XR_SPATIAL_MARKER_ARUCO_DICT_5X5_250_EXT = 7, + XR_SPATIAL_MARKER_ARUCO_DICT_5X5_1000_EXT = 8, + XR_SPATIAL_MARKER_ARUCO_DICT_6X6_50_EXT = 9, + XR_SPATIAL_MARKER_ARUCO_DICT_6X6_100_EXT = 10, + XR_SPATIAL_MARKER_ARUCO_DICT_6X6_250_EXT = 11, + XR_SPATIAL_MARKER_ARUCO_DICT_6X6_1000_EXT = 12, + XR_SPATIAL_MARKER_ARUCO_DICT_7X7_50_EXT = 13, + XR_SPATIAL_MARKER_ARUCO_DICT_7X7_100_EXT = 14, + XR_SPATIAL_MARKER_ARUCO_DICT_7X7_250_EXT = 15, + XR_SPATIAL_MARKER_ARUCO_DICT_7X7_1000_EXT = 16, + XR_SPATIAL_MARKER_ARUCO_DICT_MAX_ENUM_EXT = 0x7FFFFFFF +} XrSpatialMarkerArucoDictEXT; + +typedef enum XrSpatialMarkerAprilTagDictEXT { + XR_SPATIAL_MARKER_APRIL_TAG_DICT_16H5_EXT = 1, + XR_SPATIAL_MARKER_APRIL_TAG_DICT_25H9_EXT = 2, + XR_SPATIAL_MARKER_APRIL_TAG_DICT_36H10_EXT = 3, + XR_SPATIAL_MARKER_APRIL_TAG_DICT_36H11_EXT = 4, + XR_SPATIAL_MARKER_APRIL_TAG_DICT_MAX_ENUM_EXT = 0x7FFFFFFF +} XrSpatialMarkerAprilTagDictEXT; +typedef struct XrSpatialCapabilityConfigurationQrCodeEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialCapabilityEXT capability; + uint32_t enabledComponentCount; + const XrSpatialComponentTypeEXT* enabledComponents; +} XrSpatialCapabilityConfigurationQrCodeEXT; + +typedef struct XrSpatialCapabilityConfigurationMicroQrCodeEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialCapabilityEXT capability; + uint32_t enabledComponentCount; + const XrSpatialComponentTypeEXT* enabledComponents; +} XrSpatialCapabilityConfigurationMicroQrCodeEXT; + +typedef struct XrSpatialCapabilityConfigurationArucoMarkerEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialCapabilityEXT capability; + uint32_t enabledComponentCount; + const XrSpatialComponentTypeEXT* enabledComponents; + XrSpatialMarkerArucoDictEXT arUcoDict; +} XrSpatialCapabilityConfigurationArucoMarkerEXT; + +typedef struct XrSpatialCapabilityConfigurationAprilTagEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialCapabilityEXT capability; + uint32_t enabledComponentCount; + const XrSpatialComponentTypeEXT* enabledComponents; + XrSpatialMarkerAprilTagDictEXT aprilDict; +} XrSpatialCapabilityConfigurationAprilTagEXT; + +// XrSpatialMarkerSizeEXT extends XrSpatialCapabilityConfigurationArucoMarkerEXT,XrSpatialCapabilityConfigurationAprilTagEXT,XrSpatialCapabilityConfigurationQrCodeEXT,XrSpatialCapabilityConfigurationMicroQrCodeEXT +typedef struct XrSpatialMarkerSizeEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + float markerSideLength; +} XrSpatialMarkerSizeEXT; + +// XrSpatialMarkerStaticOptimizationEXT extends XrSpatialCapabilityConfigurationArucoMarkerEXT,XrSpatialCapabilityConfigurationAprilTagEXT,XrSpatialCapabilityConfigurationQrCodeEXT,XrSpatialCapabilityConfigurationMicroQrCodeEXT +typedef struct XrSpatialMarkerStaticOptimizationEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 optimizeForStaticMarker; +} XrSpatialMarkerStaticOptimizationEXT; + +typedef struct XrSpatialMarkerDataEXT { + XrSpatialCapabilityEXT capability; + uint32_t markerId; + XrSpatialBufferEXT data; +} XrSpatialMarkerDataEXT; + +// XrSpatialComponentMarkerListEXT extends XrSpatialComponentDataQueryResultEXT +typedef struct XrSpatialComponentMarkerListEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t markerCount; + XrSpatialMarkerDataEXT* markers; +} XrSpatialComponentMarkerListEXT; + + + +// XR_LOGITECH_mx_ink_stylus_interaction is a preprocessor guard. Do not pass it to API calls. +#define XR_LOGITECH_mx_ink_stylus_interaction 1 +#define XR_LOGITECH_mx_ink_stylus_interaction_SPEC_VERSION 1 +#define XR_LOGITECH_MX_INK_STYLUS_INTERACTION_EXTENSION_NAME "XR_LOGITECH_mx_ink_stylus_interaction" + + +// XR_BD_dynamic_object_tracking is a preprocessor guard. Do not pass it to API calls. +#define XR_BD_dynamic_object_tracking 1 +#define XR_BD_dynamic_object_tracking_SPEC_VERSION 1 +#define XR_BD_DYNAMIC_OBJECT_TRACKING_EXTENSION_NAME "XR_BD_dynamic_object_tracking" + +typedef enum XrDynamicObjectTypeBD { + XR_DYNAMIC_OBJECT_TYPE_UNKNOWN_BD = 0, + XR_DYNAMIC_OBJECT_TYPE_KEYBOARD_BD = 1000747000, + XR_DYNAMIC_OBJECT_TYPE_MOUSE_BD = 1000748000, + XR_DYNAMIC_OBJECT_TYPE_MAX_ENUM_BD = 0x7FFFFFFF +} XrDynamicObjectTypeBD; +// XrSystemDynamicObjectTrackingPropertiesBD extends XrSystemProperties +typedef struct XrSystemDynamicObjectTrackingPropertiesBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsDynamicObjectTracking; +} XrSystemDynamicObjectTrackingPropertiesBD; + +// XrSenseDataProviderCreateInfoDynamicObjectBD extends XrSenseDataProviderCreateInfoBD +typedef struct XrSenseDataProviderCreateInfoDynamicObjectBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t trackingTypeCount; + const XrDynamicObjectTypeBD* trackingTypes; +} XrSenseDataProviderCreateInfoDynamicObjectBD; + +typedef struct XrDynamicObjectDataBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrDynamicObjectTypeBD objectType; +} XrDynamicObjectDataBD; + +// XrSpatialEntityComponentDataDynamicObjectBD extends XrSpatialEntityComponentDataBaseHeaderBD +typedef struct XrSpatialEntityComponentDataDynamicObjectBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrDynamicObjectDataBD data; +} XrSpatialEntityComponentDataDynamicObjectBD; + +// XrSenseDataFilterDynamicObjectTypeBD extends XrSenseDataQueryInfoBD +typedef struct XrSenseDataFilterDynamicObjectTypeBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t typeCount; + const XrDynamicObjectTypeBD* types; +} XrSenseDataFilterDynamicObjectTypeBD; + + + +// XR_BD_dynamic_object_keyboard is a preprocessor guard. Do not pass it to API calls. +#define XR_BD_dynamic_object_keyboard 1 +#define XR_BD_dynamic_object_keyboard_SPEC_VERSION 1 +#define XR_BD_DYNAMIC_OBJECT_KEYBOARD_EXTENSION_NAME "XR_BD_dynamic_object_keyboard" +// XrSystemDynamicObjectKeyboardPropertiesBD extends XrSystemProperties +typedef struct XrSystemDynamicObjectKeyboardPropertiesBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsDynamicObjectKeyboard; +} XrSystemDynamicObjectKeyboardPropertiesBD; + + + +// XR_BD_dynamic_object_mouse is a preprocessor guard. Do not pass it to API calls. +#define XR_BD_dynamic_object_mouse 1 +#define XR_BD_dynamic_object_mouse_SPEC_VERSION 1 +#define XR_BD_DYNAMIC_OBJECT_MOUSE_EXTENSION_NAME "XR_BD_dynamic_object_mouse" +// XrSystemDynamicObjectMousePropertiesBD extends XrSystemProperties +typedef struct XrSystemDynamicObjectMousePropertiesBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsDynamicObjectMouse; +} XrSystemDynamicObjectMousePropertiesBD; + + + +// XR_BD_camera_image is a preprocessor guard. Do not pass it to API calls. +#define XR_BD_camera_image 1 +XR_DEFINE_ATOM(XrCameraIdBD) +XR_DEFINE_ATOM(XrCameraImageIdBD) +XR_DEFINE_HANDLE(XrCameraDeviceBD) +XR_DEFINE_HANDLE(XrCameraCaptureSessionBD) +#define XR_BD_camera_image_SPEC_VERSION 1 +#define XR_BD_CAMERA_IMAGE_EXTENSION_NAME "XR_BD_camera_image" + +typedef enum XrCameraPropertyTypeBD { + XR_CAMERA_PROPERTY_TYPE_FACING_BD = 1, + XR_CAMERA_PROPERTY_TYPE_POSITION_BD = 2, + XR_CAMERA_PROPERTY_TYPE_CAMERA_TYPE_BD = 3, + XR_CAMERA_PROPERTY_TYPE_MAX_ENUM_BD = 0x7FFFFFFF +} XrCameraPropertyTypeBD; + +typedef enum XrCameraFacingBD { + XR_CAMERA_FACING_WORLD_BD = 1, + XR_CAMERA_FACING_MAX_ENUM_BD = 0x7FFFFFFF +} XrCameraFacingBD; + +typedef enum XrCameraPositionBD { + XR_CAMERA_POSITION_UNSPECIFIED_BD = 1, + XR_CAMERA_POSITION_LEFT_BD = 2, + XR_CAMERA_POSITION_RIGHT_BD = 3, + XR_CAMERA_POSITION_MAX_ENUM_BD = 0x7FFFFFFF +} XrCameraPositionBD; + +typedef enum XrCameraTypeBD { + XR_CAMERA_TYPE_PASSTHROUGH_COLOR_BD = 1, + XR_CAMERA_TYPE_MAX_ENUM_BD = 0x7FFFFFFF +} XrCameraTypeBD; + +typedef enum XrCameraCapabilityTypeBD { + XR_CAMERA_CAPABILITY_TYPE_IMAGE_RESOLUTION_AND_FRAME_RATE_BD = 1, + XR_CAMERA_CAPABILITY_TYPE_IMAGE_FORMAT_BD = 2, + XR_CAMERA_CAPABILITY_TYPE_DATA_TRANSFER_TYPE_BD = 3, + XR_CAMERA_CAPABILITY_TYPE_CAMERA_MODEL_BD = 4, + XR_CAMERA_CAPABILITY_TYPE_MAX_ENUM_BD = 0x7FFFFFFF +} XrCameraCapabilityTypeBD; + +typedef enum XrCameraDataTransferTypeBD { + XR_CAMERA_DATA_TRANSFER_TYPE_RAW_BUFFER_BD = 1, + XR_CAMERA_DATA_TRANSFER_TYPE_MAX_ENUM_BD = 0x7FFFFFFF +} XrCameraDataTransferTypeBD; + +typedef enum XrCameraImageFormatBD { + XR_CAMERA_IMAGE_FORMAT_RGBA_8888_BD = 1, + XR_CAMERA_IMAGE_FORMAT_MAX_ENUM_BD = 0x7FFFFFFF +} XrCameraImageFormatBD; + +typedef enum XrCameraModelBD { + XR_CAMERA_MODEL_PINHOLE_BD = 1, + XR_CAMERA_MODEL_MAX_ENUM_BD = 0x7FFFFFFF +} XrCameraModelBD; +typedef struct XR_MAY_ALIAS XrCameraPropertyBaseHeaderBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrCameraPropertyBaseHeaderBD; + +typedef struct XrCameraPropertiesBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t propertyCount; + XrCameraPropertyBaseHeaderBD** properties; +} XrCameraPropertiesBD; + +typedef struct XR_MAY_ALIAS XrCameraCapabilityBaseHeaderBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrCameraCapabilityBaseHeaderBD; + +typedef struct XrCameraCapabilitiesBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t capabilityCount; + XrCameraCapabilityBaseHeaderBD** capabilities; +} XrCameraCapabilitiesBD; + +typedef struct XrAvailableCamerasEnumerateInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + const XrCameraPropertiesBD* properties; + const XrCameraCapabilitiesBD* capabilities; +} XrAvailableCamerasEnumerateInfoBD; + +typedef struct XrAvailableCameraBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrCameraIdBD cameraId; +} XrAvailableCameraBD; + +typedef struct XrCameraPropertyTypesEnumerateInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCameraIdBD cameraId; +} XrCameraPropertyTypesEnumerateInfoBD; + +typedef struct XrCameraPropertyTypesBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t propertyTypeCapacityInput; + uint32_t propertyTypeCountOutput; + XrCameraPropertyTypeBD* propertyTypes; +} XrCameraPropertyTypesBD; + +typedef struct XrCameraPropertiesGetInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCameraIdBD cameraId; +} XrCameraPropertiesGetInfoBD; + +typedef struct XrCameraPropertyFacingBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCameraFacingBD facing; +} XrCameraPropertyFacingBD; + +typedef struct XrCameraPropertyPositionBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCameraPositionBD position; +} XrCameraPropertyPositionBD; + +typedef struct XrCameraPropertyCameraTypeBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCameraTypeBD cameraType; +} XrCameraPropertyCameraTypeBD; + +typedef struct XrCameraCapabilityTypesEnumerateInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCameraIdBD cameraId; +} XrCameraCapabilityTypesEnumerateInfoBD; + +typedef struct XrCameraCapabilityTypesBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t capabilityTypeCapacityInput; + uint32_t capabilityTypeCountOutput; + XrCameraCapabilityTypeBD* capabilityTypes; +} XrCameraCapabilityTypesBD; + +typedef struct XrCameraSupportedCapabilitiesGetInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCameraIdBD id; +} XrCameraSupportedCapabilitiesGetInfoBD; + +typedef struct XR_MAY_ALIAS XrCameraSupportedCapabilityBaseHeaderBD { + XrStructureType type; + void* XR_MAY_ALIAS next; +} XrCameraSupportedCapabilityBaseHeaderBD; + +typedef struct XrCameraSupportedCapabilitiesBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t capabilityCount; + XrCameraSupportedCapabilityBaseHeaderBD** capabilities; +} XrCameraSupportedCapabilitiesBD; + +typedef struct XrCameraImageResolutionAndFrameRateBD { + XrExtent2Di resolution; + uint32_t frameRate; +} XrCameraImageResolutionAndFrameRateBD; + +typedef struct XrCameraSupportedCapabilityImageResolutionAndFrameRateBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t resolutionAndFrameRateCapacityInput; + uint32_t resolutionAndFrameRateCountOutput; + XrCameraImageResolutionAndFrameRateBD* resolutionAndFrameRates; +} XrCameraSupportedCapabilityImageResolutionAndFrameRateBD; + +typedef struct XrCameraCapabilityImageResolutionAndFrameRateBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrExtent2Di resolution; + uint32_t frameRate; +} XrCameraCapabilityImageResolutionAndFrameRateBD; + +typedef struct XrCameraSupportedCapabilityDataTransferTypeBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t transferTypeCapacityInput; + uint32_t transferTypeCountOutput; + XrCameraDataTransferTypeBD* transferTypes; +} XrCameraSupportedCapabilityDataTransferTypeBD; + +typedef struct XrCameraCapabilityDataTransferTypeBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCameraDataTransferTypeBD transferType; +} XrCameraCapabilityDataTransferTypeBD; + +typedef struct XrCameraSupportedCapabilityImageFormatBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t formatCapacityInput; + uint32_t formatCountOutput; + XrCameraImageFormatBD* formats; +} XrCameraSupportedCapabilityImageFormatBD; + +typedef struct XrCameraCapabilityImageFormatBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCameraImageFormatBD format; +} XrCameraCapabilityImageFormatBD; + +typedef struct XrCameraSupportedCapabilityCameraModelBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t modelCapacityInput; + uint32_t modelCountOutput; + XrCameraModelBD* models; +} XrCameraSupportedCapabilityCameraModelBD; + +typedef struct XrCameraCapabilityCameraModelBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCameraModelBD model; +} XrCameraCapabilityCameraModelBD; + +typedef struct XrCameraDeviceCreateInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCameraIdBD cameraId; +} XrCameraDeviceCreateInfoBD; + +typedef struct XrCreateCameraDeviceCompletionBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + XrCameraDeviceBD device; +} XrCreateCameraDeviceCompletionBD; + +typedef struct XrCameraCaptureSessionCreateInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrCameraDeviceBD camera; + uint32_t configCount; + const XrCameraCapabilityBaseHeaderBD* const* configs; +} XrCameraCaptureSessionCreateInfoBD; + +typedef struct XrCreateCameraCaptureSessionCompletionBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + XrCameraCaptureSessionBD captureSession; +} XrCreateCameraCaptureSessionCompletionBD; + +// XrCameraIntrinsicsBD extends XrCameraImageDataBaseHeaderBD +typedef struct XrCameraIntrinsicsBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrVector2f focalLength; + XrVector2f principalPoint; + XrVector2f fov; +} XrCameraIntrinsicsBD; + +// XrCameraExtrinsicsBD extends XrCameraImageDataBaseHeaderBD +typedef struct XrCameraExtrinsicsBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrPosef pose; +} XrCameraExtrinsicsBD; + +typedef struct XrCameraCaptureBeginInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrCameraCaptureBeginInfoBD; + +typedef struct XrCameraImageAcquireInfoBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrTime lastCaptureTime; +} XrCameraImageAcquireInfoBD; + +typedef struct XrCameraImageBD { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 available; + XrTime captureTime; + XrCameraImageIdBD imageId; +} XrCameraImageBD; + +typedef struct XR_MAY_ALIAS XrCameraImageDataBaseHeaderBD { + XrStructureType type; + void* XR_MAY_ALIAS next; +} XrCameraImageDataBaseHeaderBD; + +typedef struct XrCameraImageDataRawBufferBD { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t width; + uint32_t height; + uint32_t stride; + uint32_t bytesPerPixel; + uint32_t pixelStride; + uint32_t bufferSize; + uint8_t* buffer; +} XrCameraImageDataRawBufferBD; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateAvailableCamerasBD)(XrInstance instance, const XrAvailableCamerasEnumerateInfoBD* enumerateInfo, uint32_t cameraCapacityInput, uint32_t* cameraCountOutput, XrAvailableCameraBD* cameras); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateCameraPropertyTypesBD)(XrInstance instance, const XrCameraPropertyTypesEnumerateInfoBD* enumerateInfo, XrCameraPropertyTypesBD* propertyTypes); +typedef XrResult (XRAPI_PTR *PFN_xrGetCameraPropertiesBD)(XrInstance instance, const XrCameraPropertiesGetInfoBD* getInfo, XrCameraPropertiesBD* properties); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateCameraCapabilityTypesBD)(XrInstance instance, const XrCameraCapabilityTypesEnumerateInfoBD* enumerateInfo, XrCameraCapabilityTypesBD* capabilityTypes); +typedef XrResult (XRAPI_PTR *PFN_xrGetCameraSupportedCapabilitiesBD)(XrInstance instance, const XrCameraSupportedCapabilitiesGetInfoBD* getInfo, XrCameraSupportedCapabilitiesBD* capabilities); +typedef XrResult (XRAPI_PTR *PFN_xrCreateCameraDeviceAsyncBD)(XrInstance instance, const XrCameraDeviceCreateInfoBD* createInfo, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrCreateCameraDeviceCompleteBD)(XrInstance instance, XrFutureEXT future, XrCreateCameraDeviceCompletionBD* completion); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyCameraDeviceBD)(XrCameraDeviceBD device); +typedef XrResult (XRAPI_PTR *PFN_xrCreateCameraCaptureSessionAsyncBD)(XrSession session, const XrCameraCaptureSessionCreateInfoBD* createInfo, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrCreateCameraCaptureSessionCompleteBD)(XrSession session, XrFutureEXT future, XrCreateCameraCaptureSessionCompletionBD* completion); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyCameraCaptureSessionBD)(XrCameraCaptureSessionBD captureSession); +typedef XrResult (XRAPI_PTR *PFN_xrBeginCameraCaptureBD)(XrCameraCaptureSessionBD captureSession, const XrCameraCaptureBeginInfoBD* beginInfo); +typedef XrResult (XRAPI_PTR *PFN_xrEndCameraCaptureBD)(XrCameraCaptureSessionBD captureSession); +typedef XrResult (XRAPI_PTR *PFN_xrAcquireCameraImageBD)(XrCameraCaptureSessionBD captureSession, const XrCameraImageAcquireInfoBD* acquireInfo, XrCameraImageBD* image); +typedef XrResult (XRAPI_PTR *PFN_xrGetCameraImageDataBD)(XrCameraCaptureSessionBD captureSession, XrCameraImageIdBD imageId, XrCameraImageDataBaseHeaderBD* imageData); +typedef XrResult (XRAPI_PTR *PFN_xrReleaseCameraImageBD)(XrCameraCaptureSessionBD captureSession, XrCameraImageIdBD imageId); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateAvailableCamerasBD( + XrInstance instance, + const XrAvailableCamerasEnumerateInfoBD* enumerateInfo, + uint32_t cameraCapacityInput, + uint32_t* cameraCountOutput, + XrAvailableCameraBD* cameras); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateCameraPropertyTypesBD( + XrInstance instance, + const XrCameraPropertyTypesEnumerateInfoBD* enumerateInfo, + XrCameraPropertyTypesBD* propertyTypes); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetCameraPropertiesBD( + XrInstance instance, + const XrCameraPropertiesGetInfoBD* getInfo, + XrCameraPropertiesBD* properties); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateCameraCapabilityTypesBD( + XrInstance instance, + const XrCameraCapabilityTypesEnumerateInfoBD* enumerateInfo, + XrCameraCapabilityTypesBD* capabilityTypes); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetCameraSupportedCapabilitiesBD( + XrInstance instance, + const XrCameraSupportedCapabilitiesGetInfoBD* getInfo, + XrCameraSupportedCapabilitiesBD* capabilities); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateCameraDeviceAsyncBD( + XrInstance instance, + const XrCameraDeviceCreateInfoBD* createInfo, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateCameraDeviceCompleteBD( + XrInstance instance, + XrFutureEXT future, + XrCreateCameraDeviceCompletionBD* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyCameraDeviceBD( + XrCameraDeviceBD device); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateCameraCaptureSessionAsyncBD( + XrSession session, + const XrCameraCaptureSessionCreateInfoBD* createInfo, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateCameraCaptureSessionCompleteBD( + XrSession session, + XrFutureEXT future, + XrCreateCameraCaptureSessionCompletionBD* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyCameraCaptureSessionBD( + XrCameraCaptureSessionBD captureSession); + +XRAPI_ATTR XrResult XRAPI_CALL xrBeginCameraCaptureBD( + XrCameraCaptureSessionBD captureSession, + const XrCameraCaptureBeginInfoBD* beginInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrEndCameraCaptureBD( + XrCameraCaptureSessionBD captureSession); + +XRAPI_ATTR XrResult XRAPI_CALL xrAcquireCameraImageBD( + XrCameraCaptureSessionBD captureSession, + const XrCameraImageAcquireInfoBD* acquireInfo, + XrCameraImageBD* image); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetCameraImageDataBD( + XrCameraCaptureSessionBD captureSession, + XrCameraImageIdBD imageId, + XrCameraImageDataBaseHeaderBD* imageData); + +XRAPI_ATTR XrResult XRAPI_CALL xrReleaseCameraImageBD( + XrCameraCaptureSessionBD captureSession, + XrCameraImageIdBD imageId); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_spatial_discovery_bounds is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_spatial_discovery_bounds 1 +#define XR_ANDROID_spatial_discovery_bounds_SPEC_VERSION 1 +#define XR_ANDROID_SPATIAL_DISCOVERY_BOUNDS_EXTENSION_NAME "XR_ANDROID_spatial_discovery_bounds" +// XrSpatialBoundsSpherefANDROID extends XrSpatialDiscoverySnapshotCreateInfoEXT +typedef struct XrSpatialBoundsSpherefANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace space; + XrTime time; + XrSpheref sphere; +} XrSpatialBoundsSpherefANDROID; + +// XrSpatialBoundsBoxfANDROID extends XrSpatialDiscoverySnapshotCreateInfoEXT +typedef struct XrSpatialBoundsBoxfANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace space; + XrTime time; + XrBoxf box; +} XrSpatialBoundsBoxfANDROID; + +// XrSpatialBoundsFrustumfANDROID extends XrSpatialDiscoverySnapshotCreateInfoEXT +typedef struct XrSpatialBoundsFrustumfANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace space; + XrTime time; + XrFrustumf frustum; +} XrSpatialBoundsFrustumfANDROID; + + + +// XR_EXT_spatial_anchor is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_spatial_anchor 1 +#define XR_EXT_spatial_anchor_SPEC_VERSION 1 +#define XR_EXT_SPATIAL_ANCHOR_EXTENSION_NAME "XR_EXT_spatial_anchor" +typedef struct XrSpatialCapabilityConfigurationAnchorEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialCapabilityEXT capability; + uint32_t enabledComponentCount; + const XrSpatialComponentTypeEXT* enabledComponents; +} XrSpatialCapabilityConfigurationAnchorEXT; + +// XrSpatialComponentAnchorListEXT extends XrSpatialComponentDataQueryResultEXT +typedef struct XrSpatialComponentAnchorListEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t locationCount; + XrPosef* locations; +} XrSpatialComponentAnchorListEXT; + +typedef struct XrSpatialAnchorCreateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace baseSpace; + XrTime time; + XrPosef pose; +} XrSpatialAnchorCreateInfoEXT; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorEXT)(XrSpatialContextEXT spatialContext, const XrSpatialAnchorCreateInfoEXT* createInfo, XrSpatialEntityIdEXT* anchorEntityId, XrSpatialEntityEXT* anchorEntity); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorEXT( + XrSpatialContextEXT spatialContext, + const XrSpatialAnchorCreateInfoEXT* createInfo, + XrSpatialEntityIdEXT* anchorEntityId, + XrSpatialEntityEXT* anchorEntity); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_spatial_persistence is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_spatial_persistence 1 +XR_DEFINE_HANDLE(XrSpatialPersistenceContextEXT) +#define XR_EXT_spatial_persistence_SPEC_VERSION 1 +#define XR_EXT_SPATIAL_PERSISTENCE_EXTENSION_NAME "XR_EXT_spatial_persistence" + +typedef enum XrSpatialPersistenceScopeEXT { + XR_SPATIAL_PERSISTENCE_SCOPE_SYSTEM_MANAGED_EXT = 1, + XR_SPATIAL_PERSISTENCE_SCOPE_LOCAL_ANCHORS_EXT = 1000781000, + XR_SPATIAL_PERSISTENCE_SCOPE_MAX_ENUM_EXT = 0x7FFFFFFF +} XrSpatialPersistenceScopeEXT; + +typedef enum XrSpatialPersistenceContextResultEXT { + XR_SPATIAL_PERSISTENCE_CONTEXT_RESULT_SUCCESS_EXT = 0, + XR_SPATIAL_PERSISTENCE_CONTEXT_RESULT_ENTITY_NOT_TRACKING_EXT = -1000781001, + XR_SPATIAL_PERSISTENCE_CONTEXT_RESULT_PERSIST_UUID_NOT_FOUND_EXT = -1000781002, + XR_SPATIAL_PERSISTENCE_CONTEXT_RESULT_MAX_ENUM_EXT = 0x7FFFFFFF +} XrSpatialPersistenceContextResultEXT; + +typedef enum XrSpatialPersistenceStateEXT { + XR_SPATIAL_PERSISTENCE_STATE_LOADED_EXT = 1, + XR_SPATIAL_PERSISTENCE_STATE_NOT_FOUND_EXT = 2, + XR_SPATIAL_PERSISTENCE_STATE_MAX_ENUM_EXT = 0x7FFFFFFF +} XrSpatialPersistenceStateEXT; +typedef struct XrSpatialPersistenceContextCreateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialPersistenceScopeEXT scope; +} XrSpatialPersistenceContextCreateInfoEXT; + +typedef struct XrCreateSpatialPersistenceContextCompletionEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + XrSpatialPersistenceContextResultEXT createResult; + XrSpatialPersistenceContextEXT persistenceContext; +} XrCreateSpatialPersistenceContextCompletionEXT; + +// XrSpatialContextPersistenceConfigEXT extends XrSpatialContextCreateInfoEXT +typedef struct XrSpatialContextPersistenceConfigEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t persistenceContextCount; + const XrSpatialPersistenceContextEXT* persistenceContexts; +} XrSpatialContextPersistenceConfigEXT; + +// XrSpatialDiscoveryPersistenceUuidFilterEXT extends XrSpatialDiscoverySnapshotCreateInfoEXT,XrSpatialComponentDataQueryConditionEXT +typedef struct XrSpatialDiscoveryPersistenceUuidFilterEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t persistedUuidCount; + const XrUuid* persistedUuids; +} XrSpatialDiscoveryPersistenceUuidFilterEXT; + +typedef struct XrSpatialPersistenceDataEXT { + XrUuid persistUuid; + XrSpatialPersistenceStateEXT persistState; +} XrSpatialPersistenceDataEXT; + +// XrSpatialComponentPersistenceListEXT extends XrSpatialComponentDataQueryResultEXT +typedef struct XrSpatialComponentPersistenceListEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t persistDataCount; + XrSpatialPersistenceDataEXT* persistData; +} XrSpatialComponentPersistenceListEXT; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateSpatialPersistenceScopesEXT)(XrInstance instance, XrSystemId systemId, uint32_t persistenceScopeCapacityInput, uint32_t* persistenceScopeCountOutput, XrSpatialPersistenceScopeEXT* persistenceScopes); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialPersistenceContextAsyncEXT)(XrSession session, const XrSpatialPersistenceContextCreateInfoEXT* createInfo, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialPersistenceContextCompleteEXT)(XrSession session, XrFutureEXT future, XrCreateSpatialPersistenceContextCompletionEXT* completion); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySpatialPersistenceContextEXT)(XrSpatialPersistenceContextEXT persistenceContext); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateSpatialPersistenceScopesEXT( + XrInstance instance, + XrSystemId systemId, + uint32_t persistenceScopeCapacityInput, + uint32_t* persistenceScopeCountOutput, + XrSpatialPersistenceScopeEXT* persistenceScopes); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialPersistenceContextAsyncEXT( + XrSession session, + const XrSpatialPersistenceContextCreateInfoEXT* createInfo, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialPersistenceContextCompleteEXT( + XrSession session, + XrFutureEXT future, + XrCreateSpatialPersistenceContextCompletionEXT* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySpatialPersistenceContextEXT( + XrSpatialPersistenceContextEXT persistenceContext); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_haptic_parametric is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_haptic_parametric 1 + +#define XR_HAPTIC_PARAMETRIC_MAX_POINTS_TRANSIENTS_EXT 500 + + +#define XR_HAPTIC_PARAMETRIC_VIBRATION_EXTEND_DURATION_EXT 50000000 + + +#define XR_HAPTIC_PARAMETRIC_FREQUENCY_MIN_HZ_EXT 1 + + +#define XR_HAPTIC_PARAMETRIC_FREQUENCY_MAX_HZ_EXT 1000 + +#define XR_EXT_haptic_parametric_SPEC_VERSION 1 +#define XR_EXT_HAPTIC_PARAMETRIC_EXTENSION_NAME "XR_EXT_haptic_parametric" + +typedef enum XrHapticParametricStreamFrameTypeEXT { + XR_HAPTIC_PARAMETRIC_STREAM_FRAME_TYPE_NONE_EXT = 0, + XR_HAPTIC_PARAMETRIC_STREAM_FRAME_TYPE_FIRST_FRAME_EXT = 1, + XR_HAPTIC_PARAMETRIC_STREAM_FRAME_TYPE_INTERMEDIATE_FRAME_EXT = 2, + XR_HAPTIC_PARAMETRIC_STREAM_FRAME_TYPE_LAST_FRAME_EXT = 3, + XR_HAPTIC_PARAMETRIC_STREAM_FRAME_TYPE_MAX_ENUM_EXT = 0x7FFFFFFF +} XrHapticParametricStreamFrameTypeEXT; +typedef struct XrHapticParametricPropertiesEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrDuration idealFrameSubmissionRate; + XrDuration minimumFirstFrameDuration; + float minFrequencyHz; + float maxFrequencyHz; +} XrHapticParametricPropertiesEXT; + +typedef struct XrHapticParametricPointEXT { + XrDuration time; + float value; +} XrHapticParametricPointEXT; + +typedef struct XrHapticParametricTransientEXT { + XrDuration time; + float amplitude; + float frequency; +} XrHapticParametricTransientEXT; + +typedef struct XrHapticParametricVibrationEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t amplitudePointCount; + const XrHapticParametricPointEXT* amplitudePoints; + uint32_t frequencyPointCount; + const XrHapticParametricPointEXT* frequencyPoints; + uint32_t transientCount; + const XrHapticParametricTransientEXT* transients; + float minFrequencyHz; + float maxFrequencyHz; + XrHapticParametricStreamFrameTypeEXT streamFrameType; +} XrHapticParametricVibrationEXT; + +// XrSystemHapticParametricPropertiesEXT extends XrSystemProperties +typedef struct XrSystemHapticParametricPropertiesEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsParametricHaptics; +} XrSystemHapticParametricPropertiesEXT; + +typedef XrResult (XRAPI_PTR *PFN_xrHapticParametricGetPropertiesEXT)(XrSession session, const XrHapticActionInfo* hapticActionInfo, XrHapticParametricPropertiesEXT* parametricProperties); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrHapticParametricGetPropertiesEXT( + XrSession session, + const XrHapticActionInfo* hapticActionInfo, + XrHapticParametricPropertiesEXT* parametricProperties); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_SONY_swapchain_color_space is a preprocessor guard. Do not pass it to API calls. +#define XR_SONY_swapchain_color_space 1 +#define XR_SONY_swapchain_color_space_SPEC_VERSION 1 +#define XR_SONY_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME "XR_SONY_swapchain_color_space" + +typedef enum XrColorSpaceSONY { + XR_COLOR_SPACE_SRGB_NONLINEAR_SONY = 0, + XR_COLOR_SPACE_DISPLAY_P3_LINEAR_SONY = 1, + XR_COLOR_SPACE_DISPLAY_P3_NONLINEAR_SONY = 2, + XR_COLOR_SPACE_DCI_P3_LINEAR_SONY = 3, + XR_COLOR_SPACE_DCI_P3_NONLINEAR_SONY = 4, + XR_COLOR_SPACE_EXTENDED_SRGB_LINEAR_SONY = 5, + XR_COLOR_SPACE_BT709_LINEAR_SONY = 6, + XR_COLOR_SPACE_BT709_NONLINEAR_SONY = 7, + XR_COLOR_SPACE_BT2020_LINEAR_SONY = 8, + XR_COLOR_SPACE_BT2020_PQ_SONY = 9, + XR_COLOR_SPACE_BT2020_HLG_SONY = 10, + XR_COLOR_SPACE_MAX_ENUM_SONY = 0x7FFFFFFF +} XrColorSpaceSONY; +typedef struct XrColorSpacesEnumerateInfoSONY { + XrStructureType type; + const void* XR_MAY_ALIAS next; + int64_t format; +} XrColorSpacesEnumerateInfoSONY; + +// XrSwapchainCreateInfoColorSpaceSONY extends XrSwapchainCreateInfo +typedef struct XrSwapchainCreateInfoColorSpaceSONY { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrColorSpaceSONY colorSpace; +} XrSwapchainCreateInfoColorSpaceSONY; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateColorSpacesSONY)(XrSession session, const XrColorSpacesEnumerateInfoSONY* enumerateInfo, uint32_t colorSpaceCapacityInput, uint32_t* colorSpaceCountOutput, XrColorSpaceSONY* colorSpaces); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateColorSpacesSONY( + XrSession session, + const XrColorSpacesEnumerateInfoSONY* enumerateInfo, + uint32_t colorSpaceCapacityInput, + uint32_t* colorSpaceCountOutput, + XrColorSpaceSONY* colorSpaces); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_SONY_hdr_metadata is a preprocessor guard. Do not pass it to API calls. +#define XR_SONY_hdr_metadata 1 +#define XR_SONY_hdr_metadata_SPEC_VERSION 1 +#define XR_SONY_HDR_METADATA_EXTENSION_NAME "XR_SONY_hdr_metadata" +typedef struct XrXYColorSONY { + float x; + float y; +} XrXYColorSONY; + +typedef struct XrHdrMetadataSONY { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrXYColorSONY displayPrimaryRed; + XrXYColorSONY displayPrimaryGreen; + XrXYColorSONY displayPrimaryBlue; + XrXYColorSONY whitePoint; + float maxLuminance; + float minLuminance; + float maxContentLightLevel; + float maxFrameAverageLightLevel; +} XrHdrMetadataSONY; + +typedef XrResult (XRAPI_PTR *PFN_xrSetHdrMetadataSONY)(XrSwapchain swapchain, const XrHdrMetadataSONY* hdrMetadata); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrSetHdrMetadataSONY( + XrSwapchain swapchain, + const XrHdrMetadataSONY* hdrMetadata); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_spatial_persistence_operations is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_spatial_persistence_operations 1 +#define XR_EXT_spatial_persistence_operations_SPEC_VERSION 1 +#define XR_EXT_SPATIAL_PERSISTENCE_OPERATIONS_EXTENSION_NAME "XR_EXT_spatial_persistence_operations" +typedef struct XrSpatialEntityPersistInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialContextEXT spatialContext; + XrSpatialEntityIdEXT spatialEntityId; +} XrSpatialEntityPersistInfoEXT; + +typedef struct XrPersistSpatialEntityCompletionEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + XrSpatialPersistenceContextResultEXT persistResult; + XrUuid persistUuid; +} XrPersistSpatialEntityCompletionEXT; + +typedef struct XrSpatialEntityUnpersistInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrUuid persistUuid; +} XrSpatialEntityUnpersistInfoEXT; + +typedef struct XrUnpersistSpatialEntityCompletionEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + XrSpatialPersistenceContextResultEXT unpersistResult; +} XrUnpersistSpatialEntityCompletionEXT; + +typedef XrResult (XRAPI_PTR *PFN_xrPersistSpatialEntityAsyncEXT)(XrSpatialPersistenceContextEXT persistenceContext, const XrSpatialEntityPersistInfoEXT* persistInfo, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrPersistSpatialEntityCompleteEXT)(XrSpatialPersistenceContextEXT persistenceContext, XrFutureEXT future, XrPersistSpatialEntityCompletionEXT* completion); +typedef XrResult (XRAPI_PTR *PFN_xrUnpersistSpatialEntityAsyncEXT)(XrSpatialPersistenceContextEXT persistenceContext, const XrSpatialEntityUnpersistInfoEXT* unpersistInfo, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrUnpersistSpatialEntityCompleteEXT)(XrSpatialPersistenceContextEXT persistenceContext, XrFutureEXT future, XrUnpersistSpatialEntityCompletionEXT* completion); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrPersistSpatialEntityAsyncEXT( + XrSpatialPersistenceContextEXT persistenceContext, + const XrSpatialEntityPersistInfoEXT* persistInfo, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrPersistSpatialEntityCompleteEXT( + XrSpatialPersistenceContextEXT persistenceContext, + XrFutureEXT future, + XrPersistSpatialEntityCompletionEXT* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrUnpersistSpatialEntityAsyncEXT( + XrSpatialPersistenceContextEXT persistenceContext, + const XrSpatialEntityUnpersistInfoEXT* unpersistInfo, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrUnpersistSpatialEntityCompleteEXT( + XrSpatialPersistenceContextEXT persistenceContext, + XrFutureEXT future, + XrUnpersistSpatialEntityCompletionEXT* completion); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_spatial_image_tracking is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_spatial_image_tracking 1 +XR_DEFINE_HANDLE(XrSpatialImageTrackingDatabaseEXT) +#define XR_EXT_spatial_image_tracking_SPEC_VERSION 1 +#define XR_EXT_SPATIAL_IMAGE_TRACKING_EXTENSION_NAME "XR_EXT_spatial_image_tracking" + +typedef enum XrSpatialReferenceImageFormatEXT { + XR_SPATIAL_REFERENCE_IMAGE_FORMAT_RGBA_8888_EXT = 1, + XR_SPATIAL_REFERENCE_IMAGE_FORMAT_RGB_888_EXT = 2, + XR_SPATIAL_REFERENCE_IMAGE_FORMAT_YUV_420_888_EXT = 3, + XR_SPATIAL_REFERENCE_IMAGE_FORMAT_MAX_ENUM_EXT = 0x7FFFFFFF +} XrSpatialReferenceImageFormatEXT; +typedef struct XrSpatialReferenceImagePlaneEXT { + uint32_t bufferSize; + const uint8_t* buffer; + uint32_t rowStride; + uint32_t pixelStride; +} XrSpatialReferenceImagePlaneEXT; + +typedef struct XrSpatialReferenceImageEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t width; + uint32_t height; + XrSpatialReferenceImageFormatEXT format; + uint32_t planeCount; + const XrSpatialReferenceImagePlaneEXT* planes; +} XrSpatialReferenceImageEXT; + +// XrSpatialImageStaticOptimizationEXT extends XrSpatialReferenceImageEXT,XrSpatialImageTrackingDatabaseCreateInfoEXT +typedef struct XrSpatialImageStaticOptimizationEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 optimizeForStaticImage; +} XrSpatialImageStaticOptimizationEXT; + +// XrSpatialImageSizeEXT extends XrSpatialReferenceImageEXT,XrSpatialImageTrackingDatabaseCreateInfoEXT +typedef struct XrSpatialImageSizeEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + float physicalWidth; +} XrSpatialImageSizeEXT; + +typedef struct XrSpatialCapabilityConfigurationImageTrackingEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialCapabilityEXT capability; + uint32_t enabledComponentCount; + const XrSpatialComponentTypeEXT* enabledComponents; + uint32_t imageTrackingDatabaseCount; + const XrSpatialImageTrackingDatabaseEXT* imageTrackingDatabases; +} XrSpatialCapabilityConfigurationImageTrackingEXT; + +typedef struct XrSpatialImageTrackingDatabaseCreateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t spatialReferenceImageCount; + const XrSpatialReferenceImageEXT* spatialReferenceImages; +} XrSpatialImageTrackingDatabaseCreateInfoEXT; + +typedef struct XrSpatialImage2DDataEXT { + XrSpatialImageTrackingDatabaseEXT imageTrackingDatabase; + uint32_t referenceImageIndex; +} XrSpatialImage2DDataEXT; + +// XrSpatialComponentImage2DListEXT extends XrSpatialComponentDataQueryResultEXT +typedef struct XrSpatialComponentImage2DListEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t imageCount; + XrSpatialImage2DDataEXT* images; +} XrSpatialComponentImage2DListEXT; + +typedef struct XrCreateSpatialImageTrackingDatabaseCompletionEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + XrSpatialImageTrackingDatabaseEXT database; +} XrCreateSpatialImageTrackingDatabaseCompletionEXT; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateSpatialReferenceImageFormatsEXT)(XrInstance instance, XrSystemId systemId, XrSpatialCapabilityEXT capability, uint32_t formatCapacityInput, uint32_t* formatCountOutput, XrSpatialReferenceImageFormatEXT* formats); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialImageTrackingDatabaseAsyncEXT)(XrSession session, const XrSpatialImageTrackingDatabaseCreateInfoEXT* createInfo, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialImageTrackingDatabaseCompleteEXT)(XrSession session, XrFutureEXT future, XrCreateSpatialImageTrackingDatabaseCompletionEXT* completion); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySpatialImageTrackingDatabaseEXT)(XrSpatialImageTrackingDatabaseEXT database); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateSpatialReferenceImageFormatsEXT( + XrInstance instance, + XrSystemId systemId, + XrSpatialCapabilityEXT capability, + uint32_t formatCapacityInput, + uint32_t* formatCountOutput, + XrSpatialReferenceImageFormatEXT* formats); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialImageTrackingDatabaseAsyncEXT( + XrSession session, + const XrSpatialImageTrackingDatabaseCreateInfoEXT* createInfo, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialImageTrackingDatabaseCompleteEXT( + XrSession session, + XrFutureEXT future, + XrCreateSpatialImageTrackingDatabaseCompletionEXT* completion); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySpatialImageTrackingDatabaseEXT( + XrSpatialImageTrackingDatabaseEXT database); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_spatial_object_tracking is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_spatial_object_tracking 1 +#define XR_ANDROID_spatial_object_tracking_SPEC_VERSION 2 +#define XR_ANDROID_SPATIAL_OBJECT_TRACKING_EXTENSION_NAME "XR_ANDROID_spatial_object_tracking" + +typedef enum XrSpatialObjectSemanticLabelANDROID { + XR_SPATIAL_OBJECT_SEMANTIC_LABEL_UNCATEGORIZED_ANDROID = 0, + XR_SPATIAL_OBJECT_SEMANTIC_LABEL_KEYBOARD_ANDROID = 1, + XR_SPATIAL_OBJECT_SEMANTIC_LABEL_MOUSE_ANDROID = 2, + XR_SPATIAL_OBJECT_SEMANTIC_LABEL_LAPTOP_BASE_ANDROID = 3, + XR_SPATIAL_OBJECT_SEMANTIC_LABEL_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrSpatialObjectSemanticLabelANDROID; +typedef struct XrSpatialCapabilityConfigurationObjectTrackingANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialCapabilityEXT capability; + uint32_t enabledComponentCount; + const XrSpatialComponentTypeEXT* enabledComponents; + uint32_t activeSemanticLabelCount; + const XrSpatialObjectSemanticLabelANDROID* activeSemanticLabels; +} XrSpatialCapabilityConfigurationObjectTrackingANDROID; + +// XrSpatialComponentObjectSemanticLabelListANDROID extends XrSpatialComponentDataQueryResultEXT +typedef struct XrSpatialComponentObjectSemanticLabelListANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t semanticLabelCount; + XrSpatialObjectSemanticLabelANDROID* semanticLabels; +} XrSpatialComponentObjectSemanticLabelListANDROID; + + + +// XR_ANDROID_spatial_discovery_raycast is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_spatial_discovery_raycast 1 +#define XR_ANDROID_spatial_discovery_raycast_SPEC_VERSION 1 +#define XR_ANDROID_SPATIAL_DISCOVERY_RAYCAST_EXTENSION_NAME "XR_ANDROID_spatial_discovery_raycast" +typedef struct XrSpatialRaycastResultDataANDROID { + XrPosef hitPose; + float distanceSquared; +} XrSpatialRaycastResultDataANDROID; + +typedef struct XrSpatialCapabilityConfigurationDepthRaycastANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialCapabilityEXT capability; + uint32_t enabledComponentCount; + const XrSpatialComponentTypeEXT* enabledComponents; +} XrSpatialCapabilityConfigurationDepthRaycastANDROID; + +// XrSpatialRaycastInfoANDROID extends XrSpatialDiscoverySnapshotCreateInfoEXT +typedef struct XrSpatialRaycastInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace space; + XrTime time; + XrVector3f origin; + XrVector3f direction; + float maxDistance; +} XrSpatialRaycastInfoANDROID; + +// XrSpatialComponentRaycastResultListANDROID extends XrSpatialComponentDataQueryResultEXT +typedef struct XrSpatialComponentRaycastResultListANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t raycastResultCount; + XrSpatialRaycastResultDataANDROID* raycastResults; +} XrSpatialComponentRaycastResultListANDROID; + +typedef struct XrSpatialRaycastSnapshotCreateInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t componentTypeCount; + const XrSpatialComponentTypeEXT* componentTypes; + const XrSpatialRaycastInfoANDROID* raycastInfo; +} XrSpatialRaycastSnapshotCreateInfoANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialRaycastSnapshotANDROID)(XrSpatialContextEXT spatialContext, const XrSpatialRaycastSnapshotCreateInfoANDROID* createInfo, XrSpatialSnapshotEXT* snapshot); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialRaycastSnapshotANDROID( + XrSpatialContextEXT spatialContext, + const XrSpatialRaycastSnapshotCreateInfoANDROID* createInfo, + XrSpatialSnapshotEXT* snapshot); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_google_cloud_auth is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_google_cloud_auth 1 +#define XR_ANDROID_google_cloud_auth_SPEC_VERSION 1 +#define XR_ANDROID_GOOGLE_CLOUD_AUTH_EXTENSION_NAME "XR_ANDROID_google_cloud_auth" + +typedef enum XrGoogleCloudAuthErrorANDROID { + XR_GOOGLE_CLOUD_AUTH_ERROR_NONE_ANDROID = 0, + XR_GOOGLE_CLOUD_AUTH_ERROR_QUOTA_EXCEEDED_ANDROID = -1, + XR_GOOGLE_CLOUD_AUTH_ERROR_UNREACHABLE_ANDROID = -2, + XR_GOOGLE_CLOUD_AUTH_ERROR_ANDROID = -3, + XR_GOOGLE_CLOUD_AUTH_ERROR_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrGoogleCloudAuthErrorANDROID; +typedef struct XR_MAY_ALIAS XrGoogleCloudAuthInfoBaseHeaderANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrGoogleCloudAuthInfoBaseHeaderANDROID; + +typedef struct XrGoogleCloudAuthInfoApiKeyANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + const char* apiKey; +} XrGoogleCloudAuthInfoApiKeyANDROID; + +typedef struct XrGoogleCloudAuthInfoTokenANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + const char* authToken; +} XrGoogleCloudAuthInfoTokenANDROID; + +typedef struct XrGoogleCloudAuthInfoKeylessANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrGoogleCloudAuthInfoKeylessANDROID; + +// XrGoogleCloudAuthErrorResultANDROID extends XrGoogleCloudAuthInfoBaseHeaderANDROID +typedef struct XrGoogleCloudAuthErrorResultANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrGoogleCloudAuthErrorANDROID error; +} XrGoogleCloudAuthErrorResultANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrSetGoogleCloudAuthAsyncANDROID)(XrSession session, const XrGoogleCloudAuthInfoBaseHeaderANDROID* authInfo, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrSetGoogleCloudAuthCompleteANDROID)(XrSession session, XrFutureEXT future, XrFutureCompletionEXT* completion); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrSetGoogleCloudAuthAsyncANDROID( + XrSession session, + const XrGoogleCloudAuthInfoBaseHeaderANDROID* authInfo, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrSetGoogleCloudAuthCompleteANDROID( + XrSession session, + XrFutureEXT future, + XrFutureCompletionEXT* completion); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_geospatial is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_geospatial 1 +XR_DEFINE_HANDLE(XrGeospatialTrackerANDROID) +#define XR_ANDROID_geospatial_SPEC_VERSION 1 +#define XR_ANDROID_GEOSPATIAL_EXTENSION_NAME "XR_ANDROID_geospatial" + +typedef enum XrGeospatialTrackerStateANDROID { + XR_GEOSPATIAL_TRACKER_STATE_STOPPED_ANDROID = 0, + XR_GEOSPATIAL_TRACKER_STATE_RUNNING_ANDROID = 1, + XR_GEOSPATIAL_TRACKER_STATE_INITIALIZATION_FAILED_ANDROID = 2, + XR_GEOSPATIAL_TRACKER_STATE_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrGeospatialTrackerStateANDROID; + +typedef enum XrVPSAvailabilityANDROID { + XR_VPS_AVAILABILITY_UNAVAILABLE_ANDROID = 1, + XR_VPS_AVAILABILITY_AVAILABLE_ANDROID = 2, + XR_VPSAVAILABILITY_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrVPSAvailabilityANDROID; +typedef XrFlags64 XrGeospatialPoseFlagsANDROID; + +// Flag bits for XrGeospatialPoseFlagsANDROID +static const XrGeospatialPoseFlagsANDROID XR_GEOSPATIAL_POSE_ORIENTATION_VALID_BIT_ANDROID = 0x00000001; +static const XrGeospatialPoseFlagsANDROID XR_GEOSPATIAL_POSE_POSITION_VALID_BIT_ANDROID = 0x00000002; + +typedef struct XrGeospatialPoseANDROID { + XrQuaternionf eastUpSouthOrientation; + double latitude; + double longitude; + double altitude; +} XrGeospatialPoseANDROID; + +// XrSystemGeospatialPropertiesANDROID extends XrSystemProperties +typedef struct XrSystemGeospatialPropertiesANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsGeospatial; +} XrSystemGeospatialPropertiesANDROID; + +typedef struct XrGeospatialTrackerCreateInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrGeospatialTrackerCreateInfoANDROID; + +typedef struct XrEventDataGeospatialTrackerStateChangedANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrGeospatialTrackerANDROID geospatialTracker; + XrGeospatialTrackerStateANDROID state; + XrResult initializationResult; + XrTime time; +} XrEventDataGeospatialTrackerStateChangedANDROID; + +typedef struct XrGeospatialPoseFromPoseLocateInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace space; + XrTime time; + XrPosef pose; +} XrGeospatialPoseFromPoseLocateInfoANDROID; + +typedef struct XrGeospatialPoseResultANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrGeospatialPoseFlagsANDROID poseFlags; + XrGeospatialPoseANDROID geospatialPose; + double horizontalAccuracy; + double verticalAccuracy; + double orientationYawAccuracy; +} XrGeospatialPoseResultANDROID; + +typedef struct XrGeospatialPoseLocateInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace space; + XrTime time; + XrGeospatialPoseANDROID geospatialPose; +} XrGeospatialPoseLocateInfoANDROID; + +typedef struct XrVPSAvailabilityCheckCompletionANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + XrVPSAvailabilityANDROID availability; +} XrVPSAvailabilityCheckCompletionANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateGeospatialTrackerANDROID)(XrSession session, const XrGeospatialTrackerCreateInfoANDROID* createInfo, XrGeospatialTrackerANDROID* geospatialTrackerOutput); +typedef XrResult (XRAPI_PTR *PFN_xrDestroyGeospatialTrackerANDROID)(XrGeospatialTrackerANDROID geospatialTracker); +typedef XrResult (XRAPI_PTR *PFN_xrLocateGeospatialPoseFromPoseANDROID)(XrGeospatialTrackerANDROID geospatialTracker, const XrGeospatialPoseFromPoseLocateInfoANDROID* locateInfo, XrGeospatialPoseResultANDROID* geospatialPoseResult); +typedef XrResult (XRAPI_PTR *PFN_xrLocateGeospatialPoseANDROID)(XrGeospatialTrackerANDROID geospatialTracker, const XrGeospatialPoseLocateInfoANDROID* locateInfo, XrSpaceLocation* location); +typedef XrResult (XRAPI_PTR *PFN_xrCheckVpsAvailabilityAsyncANDROID)(XrSession session, double latitude, double longitude, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrCheckVpsAvailabilityCompleteANDROID)(XrSession session, XrFutureEXT future, XrVPSAvailabilityCheckCompletionANDROID* completion); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateGeospatialTrackerANDROID( + XrSession session, + const XrGeospatialTrackerCreateInfoANDROID* createInfo, + XrGeospatialTrackerANDROID* geospatialTrackerOutput); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroyGeospatialTrackerANDROID( + XrGeospatialTrackerANDROID geospatialTracker); + +XRAPI_ATTR XrResult XRAPI_CALL xrLocateGeospatialPoseFromPoseANDROID( + XrGeospatialTrackerANDROID geospatialTracker, + const XrGeospatialPoseFromPoseLocateInfoANDROID* locateInfo, + XrGeospatialPoseResultANDROID* geospatialPoseResult); + +XRAPI_ATTR XrResult XRAPI_CALL xrLocateGeospatialPoseANDROID( + XrGeospatialTrackerANDROID geospatialTracker, + const XrGeospatialPoseLocateInfoANDROID* locateInfo, + XrSpaceLocation* location); + +XRAPI_ATTR XrResult XRAPI_CALL xrCheckVpsAvailabilityAsyncANDROID( + XrSession session, + double latitude, + double longitude, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrCheckVpsAvailabilityCompleteANDROID( + XrSession session, + XrFutureEXT future, + XrVPSAvailabilityCheckCompletionANDROID* completion); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_spatial_entity_bound_anchor is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_spatial_entity_bound_anchor 1 +#define XR_ANDROID_spatial_entity_bound_anchor_SPEC_VERSION 2 +#define XR_ANDROID_SPATIAL_ENTITY_BOUND_ANCHOR_EXTENSION_NAME "XR_ANDROID_spatial_entity_bound_anchor" +// XrSpatialAnchorParentANDROID extends XrSpatialAnchorCreateInfoEXT +typedef struct XrSpatialAnchorParentANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialEntityIdEXT parentId; +} XrSpatialAnchorParentANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateSpatialAnchorAttachableComponentsANDROID)(XrInstance instance, XrSystemId systemId, uint32_t attachableComponentCapacityInput, uint32_t* attachableComponentCountOutput, XrSpatialComponentTypeEXT* attachableComponents); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateSpatialAnchorAttachableComponentsANDROID( + XrInstance instance, + XrSystemId systemId, + uint32_t attachableComponentCapacityInput, + uint32_t* attachableComponentCountOutput, + XrSpatialComponentTypeEXT* attachableComponents); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_spatial_component_subsumed_by is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_spatial_component_subsumed_by 1 +#define XR_ANDROID_spatial_component_subsumed_by_SPEC_VERSION 1 +#define XR_ANDROID_SPATIAL_COMPONENT_SUBSUMED_BY_EXTENSION_NAME "XR_ANDROID_spatial_component_subsumed_by" +// XrSpatialDiscoveryUniqueEntitiesFilterANDROID extends XrSpatialDiscoverySnapshotCreateInfoEXT +typedef struct XrSpatialDiscoveryUniqueEntitiesFilterANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSpatialDiscoveryUniqueEntitiesFilterANDROID; + +// XrSpatialComponentSubsumedByListANDROID extends XrSpatialComponentDataQueryResultEXT +typedef struct XrSpatialComponentSubsumedByListANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t subsumedUniqueIdCount; + XrSpatialEntityIdEXT* subsumedUniqueIds; +} XrSpatialComponentSubsumedByListANDROID; + + + +// XR_ANDROID_spatial_anchor_space is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_spatial_anchor_space 1 +#define XR_ANDROID_spatial_anchor_space_SPEC_VERSION 1 +#define XR_ANDROID_SPATIAL_ANCHOR_SPACE_EXTENSION_NAME "XR_ANDROID_spatial_anchor_space" +typedef struct XrSpatialAnchorSpaceFromIdCreateInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialEntityIdEXT anchorEntityId; +} XrSpatialAnchorSpaceFromIdCreateInfoANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorSpaceANDROID)(XrSession session, XrSpatialContextEXT spatialContext, const XrSpatialAnchorCreateInfoEXT* createInfo, XrSpatialEntityIdEXT* anchorEntityId, XrSpace* anchorSpace); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorSpaceFromIdANDROID)(XrSession session, XrSpatialContextEXT spatialContext, const XrSpatialAnchorSpaceFromIdCreateInfoANDROID* createInfo, XrSpace* anchorSpace); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorSpaceANDROID( + XrSession session, + XrSpatialContextEXT spatialContext, + const XrSpatialAnchorCreateInfoEXT* createInfo, + XrSpatialEntityIdEXT* anchorEntityId, + XrSpace* anchorSpace); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorSpaceFromIdANDROID( + XrSession session, + XrSpatialContextEXT spatialContext, + const XrSpatialAnchorSpaceFromIdCreateInfoANDROID* createInfo, + XrSpace* anchorSpace); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_ANDROID_geospatial_anchor is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_geospatial_anchor 1 +#define XR_ANDROID_geospatial_anchor_SPEC_VERSION 1 +#define XR_ANDROID_GEOSPATIAL_ANCHOR_EXTENSION_NAME "XR_ANDROID_geospatial_anchor" + +typedef enum XrSurfaceAnchorTypeANDROID { + XR_SURFACE_ANCHOR_TYPE_TERRAIN_ANDROID = 1, + XR_SURFACE_ANCHOR_TYPE_ROOFTOP_ANDROID = 2, + XR_SURFACE_ANCHOR_TYPE_MAX_ENUM_ANDROID = 0x7FFFFFFF +} XrSurfaceAnchorTypeANDROID; +// XrSystemGeospatialAnchorPropertiesANDROID extends XrSystemProperties +typedef struct XrSystemGeospatialAnchorPropertiesANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t maxSurfaceAnchorCount; +} XrSystemGeospatialAnchorPropertiesANDROID; + +// XrGeospatialTrackerAnchorTrackingInfoANDROID extends XrGeospatialTrackerCreateInfoANDROID +typedef struct XrGeospatialTrackerAnchorTrackingInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 shouldTrackPlanes; +} XrGeospatialTrackerAnchorTrackingInfoANDROID; + +typedef struct XrGeospatialAnchorCreateInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrGeospatialTrackerANDROID geospatialTracker; + XrGeospatialPoseANDROID geospatialPose; +} XrGeospatialAnchorCreateInfoANDROID; + +typedef struct XrSurfaceAnchorCreateInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrGeospatialTrackerANDROID geospatialTracker; + XrSurfaceAnchorTypeANDROID surfaceAnchorType; + XrQuaternionf eastUpSouthOrientation; + double latitude; + double longitude; + double altitudeRelativeToSurface; +} XrSurfaceAnchorCreateInfoANDROID; + +typedef struct XrSurfaceAnchorCreateCompletionANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrResult futureResult; + XrSpatialEntityIdEXT anchorEntityId; +} XrSurfaceAnchorCreateCompletionANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateGeospatialAnchorANDROID)(XrSpatialContextEXT spatialContext, const XrGeospatialAnchorCreateInfoANDROID* createInfo, XrSpatialEntityIdEXT* anchorEntityId); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSurfaceAnchorAsyncANDROID)(XrSpatialContextEXT spatialContext, const XrSurfaceAnchorCreateInfoANDROID* createInfo, XrFutureEXT* future); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSurfaceAnchorCompleteANDROID)(XrSpatialContextEXT spatialContext, XrFutureEXT future, XrSurfaceAnchorCreateCompletionANDROID* completion); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateGeospatialAnchorANDROID( + XrSpatialContextEXT spatialContext, + const XrGeospatialAnchorCreateInfoANDROID* createInfo, + XrSpatialEntityIdEXT* anchorEntityId); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSurfaceAnchorAsyncANDROID( + XrSpatialContextEXT spatialContext, + const XrSurfaceAnchorCreateInfoANDROID* createInfo, + XrFutureEXT* future); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSurfaceAnchorCompleteANDROID( + XrSpatialContextEXT spatialContext, + XrFutureEXT future, + XrSurfaceAnchorCreateCompletionANDROID* completion); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_spatial_container is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_spatial_container 1 +XR_DEFINE_HANDLE(XrSpatialContainerEXT) +#define XR_EXT_spatial_container_SPEC_VERSION 1 +#define XR_EXT_SPATIAL_CONTAINER_EXTENSION_NAME "XR_EXT_spatial_container" + +typedef enum XrSpatialContainerGraphicsPresentationEXT { + XR_SPATIAL_CONTAINER_GRAPHICS_PRESENTATION_SELF_RENDERING_EXT = 1000813000, + XR_SPATIAL_CONTAINER_GRAPHICS_PRESENTATION_MAX_ENUM_EXT = 0x7FFFFFFF +} XrSpatialContainerGraphicsPresentationEXT; + +typedef enum XrSpatialContainerBoundsModeEXT { + XR_SPATIAL_CONTAINER_BOUNDS_MODE_BOUNDED_EXT = 1, + XR_SPATIAL_CONTAINER_BOUNDS_MODE_IMMERSIVE_EXT = 2, + XR_SPATIAL_CONTAINER_BOUNDS_MODE_MAX_ENUM_EXT = 0x7FFFFFFF +} XrSpatialContainerBoundsModeEXT; + +typedef enum XrSpatialContainerVolumeClippingEXT { + XR_SPATIAL_CONTAINER_VOLUME_CLIPPING_NONE_EXT = 0, + XR_SPATIAL_CONTAINER_VOLUME_CLIPPING_STRICT_EXT = 1, + XR_SPATIAL_CONTAINER_VOLUME_CLIPPING_MAX_ENUM_EXT = 0x7FFFFFFF +} XrSpatialContainerVolumeClippingEXT; +// XrSessionCreateInfoSpatialContainersEXT extends XrSessionCreateInfo +typedef struct XrSessionCreateInfoSpatialContainersEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSessionCreateInfoSpatialContainersEXT; + +typedef struct XrSpatialContainerCreateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialContainerGraphicsPresentationEXT graphicsPresentation; + XrExtent3Df suggestedBounds; +} XrSpatialContainerCreateInfoEXT; + +typedef struct XrSpatialContainerSpaceCreateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialContainerEXT spatialContainer; +} XrSpatialContainerSpaceCreateInfoEXT; + +typedef struct XrEventDataSpatialContainerClosedEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialContainerEXT spatialContainer; +} XrEventDataSpatialContainerClosedEXT; + +// XrSystemSpatialContainerPropertiesEXT extends XrSystemProperties +typedef struct XrSystemSpatialContainerPropertiesEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t maxSpatialContainerCount; + XrBool32 supportsBounded; + XrBool32 supportsImmersive; +} XrSystemSpatialContainerPropertiesEXT; + +typedef struct XrSpatialContainerBoundsEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrExtent3Df bounds; + XrBool32 infiniteBounds; +} XrSpatialContainerBoundsEXT; + +typedef struct XrEventDataSpatialContainerBoundsChangedEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialContainerEXT spatialContainer; + XrExtent3Df bounds; + XrBool32 infiniteBounds; + XrSpatialContainerBoundsModeEXT boundsMode; +} XrEventDataSpatialContainerBoundsChangedEXT; + +typedef struct XrSpatialContainerBoundsGetInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSpatialContainerBoundsGetInfoEXT; + +typedef struct XrSpatialContainerStateGetInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; +} XrSpatialContainerStateGetInfoEXT; + +typedef struct XrSpatialContainerVisibleRequestInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrBool32 visible; +} XrSpatialContainerVisibleRequestInfoEXT; + +typedef struct XrEventDataSpatialContainerVisibleChangedEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialContainerEXT spatialContainer; + XrBool32 visible; +} XrEventDataSpatialContainerVisibleChangedEXT; + +typedef struct XrEventDataSpatialContainerVisibleRequestDeniedEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialContainerEXT spatialContainer; +} XrEventDataSpatialContainerVisibleRequestDeniedEXT; + +typedef struct XrEventDataSpatialContainerInteractableChangedEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialContainerEXT spatialContainer; + XrBool32 interactable; +} XrEventDataSpatialContainerInteractableChangedEXT; + +typedef struct XrSpatialContainerBoundsModeRequestInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialContainerBoundsModeEXT boundsMode; +} XrSpatialContainerBoundsModeRequestInfoEXT; + +typedef struct XrEventDataSpatialContainerBoundsModeRequestDeniedEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialContainerEXT spatialContainer; +} XrEventDataSpatialContainerBoundsModeRequestDeniedEXT; + +typedef struct XrSpatialContainerStateEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 visible; + XrBool32 interactable; + XrSpatialContainerBoundsModeEXT boundsMode; +} XrSpatialContainerStateEXT; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialContainerEXT)(XrSession session, const XrSpatialContainerCreateInfoEXT* createInfo, XrSpatialContainerEXT* spatialContainer); +typedef XrResult (XRAPI_PTR *PFN_xrDestroySpatialContainerEXT)(XrSpatialContainerEXT spatialContainer); +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialContainerSpaceEXT)(XrSession session, const XrSpatialContainerSpaceCreateInfoEXT* createInfo, XrSpace* space); +typedef XrResult (XRAPI_PTR *PFN_xrRequestSpatialContainerVisibleEXT)(XrSpatialContainerEXT spatialContainer, const XrSpatialContainerVisibleRequestInfoEXT* info); +typedef XrResult (XRAPI_PTR *PFN_xrRequestSpatialContainerBoundsModeEXT)(XrSpatialContainerEXT spatialContainer, const XrSpatialContainerBoundsModeRequestInfoEXT* info); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpatialContainerBoundsEXT)(XrSpatialContainerEXT spatialContainer, const XrSpatialContainerBoundsGetInfoEXT* getInfo, XrSpatialContainerBoundsEXT* bounds); +typedef XrResult (XRAPI_PTR *PFN_xrGetSpatialContainerStateEXT)(XrSpatialContainerEXT spatialContainer, const XrSpatialContainerStateGetInfoEXT* getInfo, XrSpatialContainerStateEXT* state); +typedef XrResult (XRAPI_PTR *PFN_xrEnumerateSupportedSpatialContainerGraphicsPresentationsEXT)(XrInstance instance, XrSystemId systemId, uint32_t graphicsPresentationCapacityInput, uint32_t* graphicsPresentationCountOutput, XrSpatialContainerGraphicsPresentationEXT* graphicsPresentations); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialContainerEXT( + XrSession session, + const XrSpatialContainerCreateInfoEXT* createInfo, + XrSpatialContainerEXT* spatialContainer); + +XRAPI_ATTR XrResult XRAPI_CALL xrDestroySpatialContainerEXT( + XrSpatialContainerEXT spatialContainer); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialContainerSpaceEXT( + XrSession session, + const XrSpatialContainerSpaceCreateInfoEXT* createInfo, + XrSpace* space); + +XRAPI_ATTR XrResult XRAPI_CALL xrRequestSpatialContainerVisibleEXT( + XrSpatialContainerEXT spatialContainer, + const XrSpatialContainerVisibleRequestInfoEXT* info); + +XRAPI_ATTR XrResult XRAPI_CALL xrRequestSpatialContainerBoundsModeEXT( + XrSpatialContainerEXT spatialContainer, + const XrSpatialContainerBoundsModeRequestInfoEXT* info); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpatialContainerBoundsEXT( + XrSpatialContainerEXT spatialContainer, + const XrSpatialContainerBoundsGetInfoEXT* getInfo, + XrSpatialContainerBoundsEXT* bounds); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetSpatialContainerStateEXT( + XrSpatialContainerEXT spatialContainer, + const XrSpatialContainerStateGetInfoEXT* getInfo, + XrSpatialContainerStateEXT* state); + +XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateSupportedSpatialContainerGraphicsPresentationsEXT( + XrInstance instance, + XrSystemId systemId, + uint32_t graphicsPresentationCapacityInput, + uint32_t* graphicsPresentationCountOutput, + XrSpatialContainerGraphicsPresentationEXT* graphicsPresentations); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_spatial_container_self_rendering is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_spatial_container_self_rendering 1 +#define XR_EXT_spatial_container_self_rendering_SPEC_VERSION 1 +#define XR_EXT_SPATIAL_CONTAINER_SELF_RENDERING_EXTENSION_NAME "XR_EXT_spatial_container_self_rendering" +typedef struct XrSpatialContainerViewLocateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrViewConfigurationType viewConfigurationType; + XrSpace space; + XrSpatialContainerEXT spatialContainer; +} XrSpatialContainerViewLocateInfoEXT; + +typedef struct XrSpatialContainerViewsLocateInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrTime displayTime; + uint32_t viewLocateInfoCount; + const XrSpatialContainerViewLocateInfoEXT* viewLocateInfos; +} XrSpatialContainerViewsLocateInfoEXT; + +typedef struct XrSpatialContainerViewStateEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrViewStateFlags viewStateFlags; + XrViewConfigurationType viewConfigurationType; + XrBool32 shouldSubmitLayers; + XrExtent2Di recommendedImageExtent; +} XrSpatialContainerViewStateEXT; + +typedef struct XrSpatialContainerLayerEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialContainerEXT spatialContainer; + XrBool32 retainPreviousSubmission; + uint32_t layerCount; + const XrCompositionLayerBaseHeader* const* layers; +} XrSpatialContainerLayerEXT; + +// XrSpatialContainerLayerFrameEndInfoEXT extends XrFrameEndInfo +typedef struct XrSpatialContainerLayerFrameEndInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t containerLayerCount; + const XrSpatialContainerLayerEXT* containerLayers; +} XrSpatialContainerLayerFrameEndInfoEXT; + +typedef struct XrSpatialContainerBeginInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialContainerEXT spatialContainer; + XrViewConfigurationType primaryViewConfigurationType; +} XrSpatialContainerBeginInfoEXT; + +typedef struct XrSpatialContainerEndInfoEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialContainerEXT spatialContainer; +} XrSpatialContainerEndInfoEXT; + +// XrSpatialContainerCompositionLayerViewConfigurationEXT extends XrCompositionLayerProjection +typedef struct XrSpatialContainerCompositionLayerViewConfigurationEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrViewConfigurationType viewConfigurationType; +} XrSpatialContainerCompositionLayerViewConfigurationEXT; + +// XrSpatialContainerLayerVolumeClippingEXT extends XrSpatialContainerLayerEXT +typedef struct XrSpatialContainerLayerVolumeClippingEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpatialContainerVolumeClippingEXT volumeClipping; +} XrSpatialContainerLayerVolumeClippingEXT; + +typedef XrResult (XRAPI_PTR *PFN_xrBeginSpatialContainerRenderingEXT)(XrSession session, const XrSpatialContainerBeginInfoEXT* beginInfo); +typedef XrResult (XRAPI_PTR *PFN_xrEndSpatialContainerRenderingEXT)(XrSession session, const XrSpatialContainerEndInfoEXT* endInfo); +typedef XrResult (XRAPI_PTR *PFN_xrLocateSpatialContainerViewsEXT)(XrSession session, const XrSpatialContainerViewsLocateInfoEXT* locateInfo, uint32_t viewStateCount, XrSpatialContainerViewStateEXT* viewStates, uint32_t viewCount, XrView* views); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrBeginSpatialContainerRenderingEXT( + XrSession session, + const XrSpatialContainerBeginInfoEXT* beginInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrEndSpatialContainerRenderingEXT( + XrSession session, + const XrSpatialContainerEndInfoEXT* endInfo); + +XRAPI_ATTR XrResult XRAPI_CALL xrLocateSpatialContainerViewsEXT( + XrSession session, + const XrSpatialContainerViewsLocateInfoEXT* locateInfo, + uint32_t viewStateCount, + XrSpatialContainerViewStateEXT* viewStates, + uint32_t viewCount, + XrView* views); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ + + +// XR_EXT_interaction_profile_battery_state_display is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_interaction_profile_battery_state_display 1 +#define XR_EXT_interaction_profile_battery_state_display_SPEC_VERSION 1 +#define XR_EXT_INTERACTION_PROFILE_BATTERY_STATE_DISPLAY_EXTENSION_NAME "XR_EXT_interaction_profile_battery_state_display" +typedef XrFlags64 XrBatteryStateDisplayStateFlagsEXT; + +// Flag bits for XrBatteryStateDisplayStateFlagsEXT +static const XrBatteryStateDisplayStateFlagsEXT XR_BATTERY_STATE_DISPLAY_STATE_VALID_BIT_EXT = 0x00000001; +static const XrBatteryStateDisplayStateFlagsEXT XR_BATTERY_STATE_DISPLAY_STATE_CHARGING_BIT_EXT = 0x00000002; +static const XrBatteryStateDisplayStateFlagsEXT XR_BATTERY_STATE_DISPLAY_STATE_PLUGGED_IN_BIT_EXT = 0x00000004; +static const XrBatteryStateDisplayStateFlagsEXT XR_BATTERY_STATE_DISPLAY_STATE_NO_BATTERY_BIT_EXT = 0x00000008; + +// XrBatteryStateDisplayEXT extends XrInteractionProfileState +typedef struct XrBatteryStateDisplayEXT { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBatteryStateDisplayStateFlagsEXT stateFlags; + float batteryLevel; +} XrBatteryStateDisplayEXT; + + + +// XR_EXT_loader_init_properties is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_loader_init_properties 1 +#define XR_EXT_loader_init_properties_SPEC_VERSION 1 +#define XR_EXT_LOADER_INIT_PROPERTIES_EXTENSION_NAME "XR_EXT_loader_init_properties" +typedef struct XrLoaderInitPropertyValueEXT { + const char* name; + const char* value; +} XrLoaderInitPropertyValueEXT; + +// XrLoaderInitInfoPropertiesEXT extends XrLoaderInitInfoBaseHeaderKHR +typedef struct XrLoaderInitInfoPropertiesEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t propertyValueCount; + const XrLoaderInitPropertyValueEXT* propertyValues; +} XrLoaderInitInfoPropertiesEXT; + + + +// XR_EXT_view_configuration_views_change is a preprocessor guard. Do not pass it to API calls. +#define XR_EXT_view_configuration_views_change 1 +#define XR_EXT_view_configuration_views_change_SPEC_VERSION 1 +#define XR_EXT_VIEW_CONFIGURATION_VIEWS_CHANGE_EXTENSION_NAME "XR_EXT_view_configuration_views_change" +typedef struct XrEventDataViewConfigurationViewsChangedEXT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSystemId systemId; + XrViewConfigurationType viewConfigurationType; +} XrEventDataViewConfigurationViewsChangedEXT; + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/frame/openxr-compat/vendor/openxr/openxr_platform.h b/frame/openxr-compat/vendor/openxr/openxr_platform.h new file mode 100644 index 0000000..9de27ad --- /dev/null +++ b/frame/openxr-compat/vendor/openxr/openxr_platform.h @@ -0,0 +1,819 @@ +#ifndef OPENXR_PLATFORM_H_ +#define OPENXR_PLATFORM_H_ 1 + +/* +** Copyright 2017-2026 The Khronos Group Inc. +** +** SPDX-License-Identifier: Apache-2.0 OR MIT +*/ + +/* +** This header is generated from the Khronos OpenXR XML API Registry. +** +*/ + +#include "openxr.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifdef XR_USE_PLATFORM_ANDROID + +// XR_KHR_android_thread_settings is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_android_thread_settings 1 +#define XR_KHR_android_thread_settings_SPEC_VERSION 6 +#define XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME "XR_KHR_android_thread_settings" + +typedef enum XrAndroidThreadTypeKHR { + XR_ANDROID_THREAD_TYPE_APPLICATION_MAIN_KHR = 1, + XR_ANDROID_THREAD_TYPE_APPLICATION_WORKER_KHR = 2, + XR_ANDROID_THREAD_TYPE_RENDERER_MAIN_KHR = 3, + XR_ANDROID_THREAD_TYPE_RENDERER_WORKER_KHR = 4, + XR_ANDROID_THREAD_TYPE_MAX_ENUM_KHR = 0x7FFFFFFF +} XrAndroidThreadTypeKHR; +typedef XrResult (XRAPI_PTR *PFN_xrSetAndroidApplicationThreadKHR)(XrSession session, XrAndroidThreadTypeKHR threadType, uint32_t threadId); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrSetAndroidApplicationThreadKHR( + XrSession session, + XrAndroidThreadTypeKHR threadType, + uint32_t threadId); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ +#endif /* XR_USE_PLATFORM_ANDROID */ + +#ifdef XR_USE_PLATFORM_ANDROID + +// XR_KHR_android_surface_swapchain is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_android_surface_swapchain 1 +#define XR_KHR_android_surface_swapchain_SPEC_VERSION 4 +#define XR_KHR_ANDROID_SURFACE_SWAPCHAIN_EXTENSION_NAME "XR_KHR_android_surface_swapchain" +typedef XrResult (XRAPI_PTR *PFN_xrCreateSwapchainAndroidSurfaceKHR)(XrSession session, const XrSwapchainCreateInfo* info, XrSwapchain* swapchain, jobject* surface); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSwapchainAndroidSurfaceKHR( + XrSession session, + const XrSwapchainCreateInfo* info, + XrSwapchain* swapchain, + jobject* surface); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ +#endif /* XR_USE_PLATFORM_ANDROID */ + +#ifdef XR_USE_PLATFORM_ANDROID + +// XR_KHR_android_create_instance is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_android_create_instance 1 +#define XR_KHR_android_create_instance_SPEC_VERSION 3 +#define XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME "XR_KHR_android_create_instance" +// XrInstanceCreateInfoAndroidKHR extends XrInstanceCreateInfo +typedef struct XrInstanceCreateInfoAndroidKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + void* XR_MAY_ALIAS applicationVM; + void* XR_MAY_ALIAS applicationActivity; +} XrInstanceCreateInfoAndroidKHR; + +#endif /* XR_USE_PLATFORM_ANDROID */ + +#ifdef XR_USE_GRAPHICS_API_VULKAN + +// XR_KHR_vulkan_swapchain_format_list is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_vulkan_swapchain_format_list 1 +#define XR_KHR_vulkan_swapchain_format_list_SPEC_VERSION 5 +#define XR_KHR_VULKAN_SWAPCHAIN_FORMAT_LIST_EXTENSION_NAME "XR_KHR_vulkan_swapchain_format_list" +// XrVulkanSwapchainFormatListCreateInfoKHR extends XrSwapchainCreateInfo +typedef struct XrVulkanSwapchainFormatListCreateInfoKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + uint32_t viewFormatCount; + const VkFormat* viewFormats; +} XrVulkanSwapchainFormatListCreateInfoKHR; + +#endif /* XR_USE_GRAPHICS_API_VULKAN */ + +#ifdef XR_USE_GRAPHICS_API_OPENGL + +// XR_KHR_opengl_enable is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_opengl_enable 1 +#define XR_KHR_opengl_enable_SPEC_VERSION 12 +#define XR_KHR_OPENGL_ENABLE_EXTENSION_NAME "XR_KHR_opengl_enable" +#ifdef XR_USE_PLATFORM_WIN32 +// XrGraphicsBindingOpenGLWin32KHR extends XrSessionCreateInfo +typedef struct XrGraphicsBindingOpenGLWin32KHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + HDC hDC; + HGLRC hGLRC; +} XrGraphicsBindingOpenGLWin32KHR; +#endif // XR_USE_PLATFORM_WIN32 + +#ifdef XR_USE_PLATFORM_XLIB +// XrGraphicsBindingOpenGLXlibKHR extends XrSessionCreateInfo +typedef struct XrGraphicsBindingOpenGLXlibKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + Display* xDisplay; + uint32_t visualid; + GLXFBConfig glxFBConfig; + GLXDrawable glxDrawable; + GLXContext glxContext; +} XrGraphicsBindingOpenGLXlibKHR; +#endif // XR_USE_PLATFORM_XLIB + +#ifdef XR_USE_PLATFORM_XCB +// XrGraphicsBindingOpenGLXcbKHR extends XrSessionCreateInfo +typedef struct XrGraphicsBindingOpenGLXcbKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + xcb_connection_t* connection; + uint32_t screenNumber; + xcb_glx_fbconfig_t fbconfigid; + xcb_visualid_t visualid; + xcb_glx_drawable_t glxDrawable; + xcb_glx_context_t glxContext; +} XrGraphicsBindingOpenGLXcbKHR; +#endif // XR_USE_PLATFORM_XCB + +#ifdef XR_USE_PLATFORM_WAYLAND +// XrGraphicsBindingOpenGLWaylandKHR extends XrSessionCreateInfo +typedef struct XrGraphicsBindingOpenGLWaylandKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + struct wl_display* display; +} XrGraphicsBindingOpenGLWaylandKHR; +#endif // XR_USE_PLATFORM_WAYLAND + +typedef struct XrSwapchainImageOpenGLKHR { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t image; +} XrSwapchainImageOpenGLKHR; + +typedef struct XrGraphicsRequirementsOpenGLKHR { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrVersion minApiVersionSupported; + XrVersion maxApiVersionSupported; +} XrGraphicsRequirementsOpenGLKHR; + +typedef XrResult (XRAPI_PTR *PFN_xrGetOpenGLGraphicsRequirementsKHR)(XrInstance instance, XrSystemId systemId, XrGraphicsRequirementsOpenGLKHR* graphicsRequirements); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetOpenGLGraphicsRequirementsKHR( + XrInstance instance, + XrSystemId systemId, + XrGraphicsRequirementsOpenGLKHR* graphicsRequirements); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ +#endif /* XR_USE_GRAPHICS_API_OPENGL */ + +#ifdef XR_USE_GRAPHICS_API_OPENGL_ES + +// XR_KHR_opengl_es_enable is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_opengl_es_enable 1 +#define XR_KHR_opengl_es_enable_SPEC_VERSION 10 +#define XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME "XR_KHR_opengl_es_enable" +#ifdef XR_USE_PLATFORM_ANDROID +// XrGraphicsBindingOpenGLESAndroidKHR extends XrSessionCreateInfo +typedef struct XrGraphicsBindingOpenGLESAndroidKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + EGLDisplay display; + EGLConfig config; + EGLContext context; +} XrGraphicsBindingOpenGLESAndroidKHR; +#endif // XR_USE_PLATFORM_ANDROID + +typedef struct XrSwapchainImageOpenGLESKHR { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t image; +} XrSwapchainImageOpenGLESKHR; + +typedef struct XrGraphicsRequirementsOpenGLESKHR { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrVersion minApiVersionSupported; + XrVersion maxApiVersionSupported; +} XrGraphicsRequirementsOpenGLESKHR; + +typedef XrResult (XRAPI_PTR *PFN_xrGetOpenGLESGraphicsRequirementsKHR)(XrInstance instance, XrSystemId systemId, XrGraphicsRequirementsOpenGLESKHR* graphicsRequirements); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetOpenGLESGraphicsRequirementsKHR( + XrInstance instance, + XrSystemId systemId, + XrGraphicsRequirementsOpenGLESKHR* graphicsRequirements); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ +#endif /* XR_USE_GRAPHICS_API_OPENGL_ES */ + +#ifdef XR_USE_GRAPHICS_API_VULKAN + +// XR_KHR_vulkan_enable is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_vulkan_enable 1 +#define XR_KHR_vulkan_enable_SPEC_VERSION 10 +#define XR_KHR_VULKAN_ENABLE_EXTENSION_NAME "XR_KHR_vulkan_enable" +// XrGraphicsBindingVulkanKHR extends XrSessionCreateInfo +typedef struct XrGraphicsBindingVulkanKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + VkInstance instance; + VkPhysicalDevice physicalDevice; + VkDevice device; + uint32_t queueFamilyIndex; + uint32_t queueIndex; +} XrGraphicsBindingVulkanKHR; + +typedef struct XrSwapchainImageVulkanKHR { + XrStructureType type; + void* XR_MAY_ALIAS next; + VkImage image; +} XrSwapchainImageVulkanKHR; + +typedef struct XrGraphicsRequirementsVulkanKHR { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrVersion minApiVersionSupported; + XrVersion maxApiVersionSupported; +} XrGraphicsRequirementsVulkanKHR; + +typedef XrResult (XRAPI_PTR *PFN_xrGetVulkanInstanceExtensionsKHR)(XrInstance instance, XrSystemId systemId, uint32_t bufferCapacityInput, uint32_t* bufferCountOutput, char* buffer); +typedef XrResult (XRAPI_PTR *PFN_xrGetVulkanDeviceExtensionsKHR)(XrInstance instance, XrSystemId systemId, uint32_t bufferCapacityInput, uint32_t* bufferCountOutput, char* buffer); +typedef XrResult (XRAPI_PTR *PFN_xrGetVulkanGraphicsDeviceKHR)(XrInstance instance, XrSystemId systemId, VkInstance vkInstance, VkPhysicalDevice* vkPhysicalDevice); +typedef XrResult (XRAPI_PTR *PFN_xrGetVulkanGraphicsRequirementsKHR)(XrInstance instance, XrSystemId systemId, XrGraphicsRequirementsVulkanKHR* graphicsRequirements); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetVulkanInstanceExtensionsKHR( + XrInstance instance, + XrSystemId systemId, + uint32_t bufferCapacityInput, + uint32_t* bufferCountOutput, + char* buffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetVulkanDeviceExtensionsKHR( + XrInstance instance, + XrSystemId systemId, + uint32_t bufferCapacityInput, + uint32_t* bufferCountOutput, + char* buffer); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetVulkanGraphicsDeviceKHR( + XrInstance instance, + XrSystemId systemId, + VkInstance vkInstance, + VkPhysicalDevice* vkPhysicalDevice); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetVulkanGraphicsRequirementsKHR( + XrInstance instance, + XrSystemId systemId, + XrGraphicsRequirementsVulkanKHR* graphicsRequirements); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ +#endif /* XR_USE_GRAPHICS_API_VULKAN */ + +#ifdef XR_USE_GRAPHICS_API_D3D11 + +// XR_KHR_D3D11_enable is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_D3D11_enable 1 +#define XR_KHR_D3D11_enable_SPEC_VERSION 11 +#define XR_KHR_D3D11_ENABLE_EXTENSION_NAME "XR_KHR_D3D11_enable" +// XrGraphicsBindingD3D11KHR extends XrSessionCreateInfo +typedef struct XrGraphicsBindingD3D11KHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + ID3D11Device* device; +} XrGraphicsBindingD3D11KHR; + +typedef struct XrSwapchainImageD3D11KHR { + XrStructureType type; + void* XR_MAY_ALIAS next; + ID3D11Texture2D* texture; +} XrSwapchainImageD3D11KHR; + +typedef struct XrGraphicsRequirementsD3D11KHR { + XrStructureType type; + void* XR_MAY_ALIAS next; + LUID adapterLuid; + D3D_FEATURE_LEVEL minFeatureLevel; +} XrGraphicsRequirementsD3D11KHR; + +typedef XrResult (XRAPI_PTR *PFN_xrGetD3D11GraphicsRequirementsKHR)(XrInstance instance, XrSystemId systemId, XrGraphicsRequirementsD3D11KHR* graphicsRequirements); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetD3D11GraphicsRequirementsKHR( + XrInstance instance, + XrSystemId systemId, + XrGraphicsRequirementsD3D11KHR* graphicsRequirements); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ +#endif /* XR_USE_GRAPHICS_API_D3D11 */ + +#ifdef XR_USE_GRAPHICS_API_D3D12 + +// XR_KHR_D3D12_enable is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_D3D12_enable 1 +#define XR_KHR_D3D12_enable_SPEC_VERSION 11 +#define XR_KHR_D3D12_ENABLE_EXTENSION_NAME "XR_KHR_D3D12_enable" +// XrGraphicsBindingD3D12KHR extends XrSessionCreateInfo +typedef struct XrGraphicsBindingD3D12KHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + ID3D12Device* device; + ID3D12CommandQueue* queue; +} XrGraphicsBindingD3D12KHR; + +typedef struct XrSwapchainImageD3D12KHR { + XrStructureType type; + void* XR_MAY_ALIAS next; + ID3D12Resource* texture; +} XrSwapchainImageD3D12KHR; + +typedef struct XrGraphicsRequirementsD3D12KHR { + XrStructureType type; + void* XR_MAY_ALIAS next; + LUID adapterLuid; + D3D_FEATURE_LEVEL minFeatureLevel; +} XrGraphicsRequirementsD3D12KHR; + +typedef XrResult (XRAPI_PTR *PFN_xrGetD3D12GraphicsRequirementsKHR)(XrInstance instance, XrSystemId systemId, XrGraphicsRequirementsD3D12KHR* graphicsRequirements); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetD3D12GraphicsRequirementsKHR( + XrInstance instance, + XrSystemId systemId, + XrGraphicsRequirementsD3D12KHR* graphicsRequirements); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ +#endif /* XR_USE_GRAPHICS_API_D3D12 */ + +#ifdef XR_USE_GRAPHICS_API_METAL + +// XR_KHR_metal_enable is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_metal_enable 1 +#define XR_KHR_metal_enable_SPEC_VERSION 3 +#define XR_KHR_METAL_ENABLE_EXTENSION_NAME "XR_KHR_metal_enable" +// XrGraphicsBindingMetalKHR extends XrSessionCreateInfo +typedef struct XrGraphicsBindingMetalKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + void* XR_MAY_ALIAS commandQueue; +} XrGraphicsBindingMetalKHR; + +typedef struct XrSwapchainImageMetalKHR { + XrStructureType type; + void* XR_MAY_ALIAS next; + void* XR_MAY_ALIAS texture; +} XrSwapchainImageMetalKHR; + +typedef struct XrGraphicsRequirementsMetalKHR { + XrStructureType type; + void* XR_MAY_ALIAS next; + void* XR_MAY_ALIAS metalDevice; +} XrGraphicsRequirementsMetalKHR; + +typedef XrResult (XRAPI_PTR *PFN_xrGetMetalGraphicsRequirementsKHR)(XrInstance instance, XrSystemId systemId, XrGraphicsRequirementsMetalKHR* graphicsRequirements); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetMetalGraphicsRequirementsKHR( + XrInstance instance, + XrSystemId systemId, + XrGraphicsRequirementsMetalKHR* graphicsRequirements); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ +#endif /* XR_USE_GRAPHICS_API_METAL */ + +#ifdef XR_USE_PLATFORM_WIN32 + +// XR_KHR_win32_convert_performance_counter_time is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_win32_convert_performance_counter_time 1 +#define XR_KHR_win32_convert_performance_counter_time_SPEC_VERSION 1 +#define XR_KHR_WIN32_CONVERT_PERFORMANCE_COUNTER_TIME_EXTENSION_NAME "XR_KHR_win32_convert_performance_counter_time" +typedef XrResult (XRAPI_PTR *PFN_xrConvertWin32PerformanceCounterToTimeKHR)(XrInstance instance, const LARGE_INTEGER* performanceCounter, XrTime* time); +typedef XrResult (XRAPI_PTR *PFN_xrConvertTimeToWin32PerformanceCounterKHR)(XrInstance instance, XrTime time, LARGE_INTEGER* performanceCounter); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrConvertWin32PerformanceCounterToTimeKHR( + XrInstance instance, + const LARGE_INTEGER* performanceCounter, + XrTime* time); + +XRAPI_ATTR XrResult XRAPI_CALL xrConvertTimeToWin32PerformanceCounterKHR( + XrInstance instance, + XrTime time, + LARGE_INTEGER* performanceCounter); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ +#endif /* XR_USE_PLATFORM_WIN32 */ + +#ifdef XR_USE_TIMESPEC + +// XR_KHR_convert_timespec_time is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_convert_timespec_time 1 +#define XR_KHR_convert_timespec_time_SPEC_VERSION 1 +#define XR_KHR_CONVERT_TIMESPEC_TIME_EXTENSION_NAME "XR_KHR_convert_timespec_time" +typedef XrResult (XRAPI_PTR *PFN_xrConvertTimespecTimeToTimeKHR)(XrInstance instance, const struct timespec* timespecTime, XrTime* time); +typedef XrResult (XRAPI_PTR *PFN_xrConvertTimeToTimespecTimeKHR)(XrInstance instance, XrTime time, struct timespec* timespecTime); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrConvertTimespecTimeToTimeKHR( + XrInstance instance, + const struct timespec* timespecTime, + XrTime* time); + +XRAPI_ATTR XrResult XRAPI_CALL xrConvertTimeToTimespecTimeKHR( + XrInstance instance, + XrTime time, + struct timespec* timespecTime); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ +#endif /* XR_USE_TIMESPEC */ + +#ifdef XR_USE_PLATFORM_ANDROID + +// XR_KHR_loader_init_android is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_loader_init_android 1 +#define XR_KHR_loader_init_android_SPEC_VERSION 1 +#define XR_KHR_LOADER_INIT_ANDROID_EXTENSION_NAME "XR_KHR_loader_init_android" +typedef struct XrLoaderInitInfoAndroidKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + void* XR_MAY_ALIAS applicationVM; + void* XR_MAY_ALIAS applicationContext; +} XrLoaderInitInfoAndroidKHR; + +#endif /* XR_USE_PLATFORM_ANDROID */ + +#ifdef XR_USE_GRAPHICS_API_VULKAN + +// XR_KHR_vulkan_enable2 is a preprocessor guard. Do not pass it to API calls. +#define XR_KHR_vulkan_enable2 1 +#define XR_KHR_vulkan_enable2_SPEC_VERSION 4 +#define XR_KHR_VULKAN_ENABLE2_EXTENSION_NAME "XR_KHR_vulkan_enable2" +typedef XrFlags64 XrVulkanInstanceCreateFlagsKHR; + +// Flag bits for XrVulkanInstanceCreateFlagsKHR + +typedef XrFlags64 XrVulkanDeviceCreateFlagsKHR; + +// Flag bits for XrVulkanDeviceCreateFlagsKHR + +typedef struct XrVulkanInstanceCreateInfoKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSystemId systemId; + XrVulkanInstanceCreateFlagsKHR createFlags; + PFN_vkGetInstanceProcAddr pfnGetInstanceProcAddr; + const VkInstanceCreateInfo* vulkanCreateInfo; + const VkAllocationCallbacks* vulkanAllocator; +} XrVulkanInstanceCreateInfoKHR; + +typedef struct XrVulkanDeviceCreateInfoKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSystemId systemId; + XrVulkanDeviceCreateFlagsKHR createFlags; + PFN_vkGetInstanceProcAddr pfnGetInstanceProcAddr; + VkPhysicalDevice vulkanPhysicalDevice; + const VkDeviceCreateInfo* vulkanCreateInfo; + const VkAllocationCallbacks* vulkanAllocator; +} XrVulkanDeviceCreateInfoKHR; + +typedef XrGraphicsBindingVulkanKHR XrGraphicsBindingVulkan2KHR; + +typedef struct XrVulkanGraphicsDeviceGetInfoKHR { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSystemId systemId; + VkInstance vulkanInstance; +} XrVulkanGraphicsDeviceGetInfoKHR; + +typedef XrSwapchainImageVulkanKHR XrSwapchainImageVulkan2KHR; + +typedef XrGraphicsRequirementsVulkanKHR XrGraphicsRequirementsVulkan2KHR; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateVulkanInstanceKHR)(XrInstance instance, const XrVulkanInstanceCreateInfoKHR* createInfo, VkInstance* vulkanInstance, VkResult* vulkanResult); +typedef XrResult (XRAPI_PTR *PFN_xrCreateVulkanDeviceKHR)(XrInstance instance, const XrVulkanDeviceCreateInfoKHR* createInfo, VkDevice* vulkanDevice, VkResult* vulkanResult); +typedef XrResult (XRAPI_PTR *PFN_xrGetVulkanGraphicsDevice2KHR)(XrInstance instance, const XrVulkanGraphicsDeviceGetInfoKHR* getInfo, VkPhysicalDevice* vulkanPhysicalDevice); +typedef XrResult (XRAPI_PTR *PFN_xrGetVulkanGraphicsRequirements2KHR)(XrInstance instance, XrSystemId systemId, XrGraphicsRequirementsVulkanKHR* graphicsRequirements); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateVulkanInstanceKHR( + XrInstance instance, + const XrVulkanInstanceCreateInfoKHR* createInfo, + VkInstance* vulkanInstance, + VkResult* vulkanResult); + +XRAPI_ATTR XrResult XRAPI_CALL xrCreateVulkanDeviceKHR( + XrInstance instance, + const XrVulkanDeviceCreateInfoKHR* createInfo, + VkDevice* vulkanDevice, + VkResult* vulkanResult); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetVulkanGraphicsDevice2KHR( + XrInstance instance, + const XrVulkanGraphicsDeviceGetInfoKHR* getInfo, + VkPhysicalDevice* vulkanPhysicalDevice); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetVulkanGraphicsRequirements2KHR( + XrInstance instance, + XrSystemId systemId, + XrGraphicsRequirementsVulkanKHR* graphicsRequirements); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ +#endif /* XR_USE_GRAPHICS_API_VULKAN */ + +#ifdef XR_USE_PLATFORM_EGL + +// XR_MNDX_egl_enable is a preprocessor guard. Do not pass it to API calls. +#define XR_MNDX_egl_enable 1 +#define XR_MNDX_egl_enable_SPEC_VERSION 2 +#define XR_MNDX_EGL_ENABLE_EXTENSION_NAME "XR_MNDX_egl_enable" +typedef PFN_xrVoidFunction (*PFN_xrEglGetProcAddressMNDX)(const char *name); +// XrGraphicsBindingEGLMNDX extends XrSessionCreateInfo +typedef struct XrGraphicsBindingEGLMNDX { + XrStructureType type; + const void* XR_MAY_ALIAS next; + PFN_xrEglGetProcAddressMNDX getProcAddress; + EGLDisplay display; + EGLConfig config; + EGLContext context; +} XrGraphicsBindingEGLMNDX; + +#endif /* XR_USE_PLATFORM_EGL */ + +#ifdef XR_USE_PLATFORM_WIN32 + +// XR_MSFT_perception_anchor_interop is a preprocessor guard. Do not pass it to API calls. +#define XR_MSFT_perception_anchor_interop 1 +#define XR_MSFT_perception_anchor_interop_SPEC_VERSION 1 +#define XR_MSFT_PERCEPTION_ANCHOR_INTEROP_EXTENSION_NAME "XR_MSFT_perception_anchor_interop" +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorFromPerceptionAnchorMSFT)(XrSession session, IUnknown* perceptionAnchor, XrSpatialAnchorMSFT* anchor); +typedef XrResult (XRAPI_PTR *PFN_xrTryGetPerceptionAnchorFromSpatialAnchorMSFT)(XrSession session, XrSpatialAnchorMSFT anchor, IUnknown** perceptionAnchor); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorFromPerceptionAnchorMSFT( + XrSession session, + IUnknown* perceptionAnchor, + XrSpatialAnchorMSFT* anchor); + +XRAPI_ATTR XrResult XRAPI_CALL xrTryGetPerceptionAnchorFromSpatialAnchorMSFT( + XrSession session, + XrSpatialAnchorMSFT anchor, + IUnknown** perceptionAnchor); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ +#endif /* XR_USE_PLATFORM_WIN32 */ + +#ifdef XR_USE_PLATFORM_WIN32 + +// XR_MSFT_holographic_window_attachment is a preprocessor guard. Do not pass it to API calls. +#define XR_MSFT_holographic_window_attachment 1 +#define XR_MSFT_holographic_window_attachment_SPEC_VERSION 1 +#define XR_MSFT_HOLOGRAPHIC_WINDOW_ATTACHMENT_EXTENSION_NAME "XR_MSFT_holographic_window_attachment" +#ifdef XR_USE_PLATFORM_WIN32 +// XrHolographicWindowAttachmentMSFT extends XrSessionCreateInfo +typedef struct XrHolographicWindowAttachmentMSFT { + XrStructureType type; + const void* XR_MAY_ALIAS next; + IUnknown* holographicSpace; + IUnknown* coreWindow; +} XrHolographicWindowAttachmentMSFT; +#endif // XR_USE_PLATFORM_WIN32 + +#endif /* XR_USE_PLATFORM_WIN32 */ + +#ifdef XR_USE_PLATFORM_ANDROID + +// XR_FB_android_surface_swapchain_create is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_android_surface_swapchain_create 1 +#define XR_FB_android_surface_swapchain_create_SPEC_VERSION 1 +#define XR_FB_ANDROID_SURFACE_SWAPCHAIN_CREATE_EXTENSION_NAME "XR_FB_android_surface_swapchain_create" +typedef XrFlags64 XrAndroidSurfaceSwapchainFlagsFB; + +// Flag bits for XrAndroidSurfaceSwapchainFlagsFB +static const XrAndroidSurfaceSwapchainFlagsFB XR_ANDROID_SURFACE_SWAPCHAIN_SYNCHRONOUS_BIT_FB = 0x00000001; +static const XrAndroidSurfaceSwapchainFlagsFB XR_ANDROID_SURFACE_SWAPCHAIN_USE_TIMESTAMPS_BIT_FB = 0x00000002; + +#ifdef XR_USE_PLATFORM_ANDROID +// XrAndroidSurfaceSwapchainCreateInfoFB extends XrSwapchainCreateInfo +typedef struct XrAndroidSurfaceSwapchainCreateInfoFB { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrAndroidSurfaceSwapchainFlagsFB createFlags; +} XrAndroidSurfaceSwapchainCreateInfoFB; +#endif // XR_USE_PLATFORM_ANDROID + +#endif /* XR_USE_PLATFORM_ANDROID */ + +#ifdef XR_USE_PLATFORM_ML + +// XR_ML_compat is a preprocessor guard. Do not pass it to API calls. +#define XR_ML_compat 1 +#define XR_ML_compat_SPEC_VERSION 1 +#define XR_ML_COMPAT_EXTENSION_NAME "XR_ML_compat" +typedef struct XrCoordinateSpaceCreateInfoML { + XrStructureType type; + const void* XR_MAY_ALIAS next; + MLCoordinateFrameUID cfuid; + XrPosef poseInCoordinateSpace; +} XrCoordinateSpaceCreateInfoML; + +typedef XrResult (XRAPI_PTR *PFN_xrCreateSpaceFromCoordinateFrameUIDML)(XrSession session, const XrCoordinateSpaceCreateInfoML *createInfo, XrSpace* space); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpaceFromCoordinateFrameUIDML( + XrSession session, + const XrCoordinateSpaceCreateInfoML * createInfo, + XrSpace* space); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ +#endif /* XR_USE_PLATFORM_ML */ + +#ifdef XR_USE_PLATFORM_WIN32 + +// XR_OCULUS_audio_device_guid is a preprocessor guard. Do not pass it to API calls. +#define XR_OCULUS_audio_device_guid 1 +#define XR_OCULUS_audio_device_guid_SPEC_VERSION 1 +#define XR_OCULUS_AUDIO_DEVICE_GUID_EXTENSION_NAME "XR_OCULUS_audio_device_guid" +#define XR_MAX_AUDIO_DEVICE_STR_SIZE_OCULUS 128 +typedef XrResult (XRAPI_PTR *PFN_xrGetAudioOutputDeviceGuidOculus)(XrInstance instance, wchar_t buffer[XR_MAX_AUDIO_DEVICE_STR_SIZE_OCULUS]); +typedef XrResult (XRAPI_PTR *PFN_xrGetAudioInputDeviceGuidOculus)(XrInstance instance, wchar_t buffer[XR_MAX_AUDIO_DEVICE_STR_SIZE_OCULUS]); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrGetAudioOutputDeviceGuidOculus( + XrInstance instance, + wchar_t buffer[XR_MAX_AUDIO_DEVICE_STR_SIZE_OCULUS]); + +XRAPI_ATTR XrResult XRAPI_CALL xrGetAudioInputDeviceGuidOculus( + XrInstance instance, + wchar_t buffer[XR_MAX_AUDIO_DEVICE_STR_SIZE_OCULUS]); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ +#endif /* XR_USE_PLATFORM_WIN32 */ + +#ifdef XR_USE_GRAPHICS_API_VULKAN + +// XR_FB_foveation_vulkan is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_foveation_vulkan 1 +#define XR_FB_foveation_vulkan_SPEC_VERSION 1 +#define XR_FB_FOVEATION_VULKAN_EXTENSION_NAME "XR_FB_foveation_vulkan" +// XrSwapchainImageFoveationVulkanFB extends XrSwapchainImageVulkanKHR +typedef struct XrSwapchainImageFoveationVulkanFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + VkImage image; + uint32_t width; + uint32_t height; +} XrSwapchainImageFoveationVulkanFB; + +#endif /* XR_USE_GRAPHICS_API_VULKAN */ + +#ifdef XR_USE_PLATFORM_ANDROID + +// XR_FB_swapchain_update_state_android_surface is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_swapchain_update_state_android_surface 1 +#define XR_FB_swapchain_update_state_android_surface_SPEC_VERSION 1 +#define XR_FB_SWAPCHAIN_UPDATE_STATE_ANDROID_SURFACE_EXTENSION_NAME "XR_FB_swapchain_update_state_android_surface" +#ifdef XR_USE_PLATFORM_ANDROID +typedef struct XrSwapchainStateAndroidSurfaceDimensionsFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + uint32_t width; + uint32_t height; +} XrSwapchainStateAndroidSurfaceDimensionsFB; +#endif // XR_USE_PLATFORM_ANDROID + +#endif /* XR_USE_PLATFORM_ANDROID */ + +#ifdef XR_USE_GRAPHICS_API_OPENGL_ES + +// XR_FB_swapchain_update_state_opengl_es is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_swapchain_update_state_opengl_es 1 +#define XR_FB_swapchain_update_state_opengl_es_SPEC_VERSION 1 +#define XR_FB_SWAPCHAIN_UPDATE_STATE_OPENGL_ES_EXTENSION_NAME "XR_FB_swapchain_update_state_opengl_es" +#ifdef XR_USE_GRAPHICS_API_OPENGL_ES +typedef struct XrSwapchainStateSamplerOpenGLESFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + EGLenum minFilter; + EGLenum magFilter; + EGLenum wrapModeS; + EGLenum wrapModeT; + EGLenum swizzleRed; + EGLenum swizzleGreen; + EGLenum swizzleBlue; + EGLenum swizzleAlpha; + float maxAnisotropy; + XrColor4f borderColor; +} XrSwapchainStateSamplerOpenGLESFB; +#endif // XR_USE_GRAPHICS_API_OPENGL_ES + +#endif /* XR_USE_GRAPHICS_API_OPENGL_ES */ + +#ifdef XR_USE_GRAPHICS_API_VULKAN + +// XR_FB_swapchain_update_state_vulkan is a preprocessor guard. Do not pass it to API calls. +#define XR_FB_swapchain_update_state_vulkan 1 +#define XR_FB_swapchain_update_state_vulkan_SPEC_VERSION 1 +#define XR_FB_SWAPCHAIN_UPDATE_STATE_VULKAN_EXTENSION_NAME "XR_FB_swapchain_update_state_vulkan" +#ifdef XR_USE_GRAPHICS_API_VULKAN +typedef struct XrSwapchainStateSamplerVulkanFB { + XrStructureType type; + void* XR_MAY_ALIAS next; + VkFilter minFilter; + VkFilter magFilter; + VkSamplerMipmapMode mipmapMode; + VkSamplerAddressMode wrapModeS; + VkSamplerAddressMode wrapModeT; + VkComponentSwizzle swizzleRed; + VkComponentSwizzle swizzleGreen; + VkComponentSwizzle swizzleBlue; + VkComponentSwizzle swizzleAlpha; + float maxAnisotropy; + XrColor4f borderColor; +} XrSwapchainStateSamplerVulkanFB; +#endif // XR_USE_GRAPHICS_API_VULKAN + +#endif /* XR_USE_GRAPHICS_API_VULKAN */ + +#ifdef XR_USE_GRAPHICS_API_VULKAN + +// XR_META_vulkan_swapchain_create_info is a preprocessor guard. Do not pass it to API calls. +#define XR_META_vulkan_swapchain_create_info 1 +#define XR_META_vulkan_swapchain_create_info_SPEC_VERSION 1 +#define XR_META_VULKAN_SWAPCHAIN_CREATE_INFO_EXTENSION_NAME "XR_META_vulkan_swapchain_create_info" +// XrVulkanSwapchainCreateInfoMETA extends XrSwapchainCreateInfo +typedef struct XrVulkanSwapchainCreateInfoMETA { + XrStructureType type; + const void* XR_MAY_ALIAS next; + VkImageCreateFlags additionalCreateFlags; + VkImageUsageFlags additionalUsageFlags; +} XrVulkanSwapchainCreateInfoMETA; + +#endif /* XR_USE_GRAPHICS_API_VULKAN */ + +#ifdef XR_USE_PLATFORM_ANDROID + +// XR_ANDROID_anchor_sharing_export is a preprocessor guard. Do not pass it to API calls. +#define XR_ANDROID_anchor_sharing_export 1 +#define XR_ANDROID_anchor_sharing_export_SPEC_VERSION 1 +#define XR_ANDROID_ANCHOR_SHARING_EXPORT_EXTENSION_NAME "XR_ANDROID_anchor_sharing_export" +typedef struct XrAnchorSharingInfoANDROID { + XrStructureType type; + const void* XR_MAY_ALIAS next; + XrSpace anchor; +} XrAnchorSharingInfoANDROID; + +typedef struct XrAnchorSharingTokenANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + struct AIBinder* token; +} XrAnchorSharingTokenANDROID; + +// XrSystemAnchorSharingExportPropertiesANDROID extends XrSystemProperties +typedef struct XrSystemAnchorSharingExportPropertiesANDROID { + XrStructureType type; + void* XR_MAY_ALIAS next; + XrBool32 supportsAnchorSharingExport; +} XrSystemAnchorSharingExportPropertiesANDROID; + +typedef XrResult (XRAPI_PTR *PFN_xrShareAnchorANDROID)(XrSession session, const XrAnchorSharingInfoANDROID* sharingInfo, XrAnchorSharingTokenANDROID* anchorToken); +typedef XrResult (XRAPI_PTR *PFN_xrUnshareAnchorANDROID)(XrSession session, XrSpace anchor); + +#ifndef XR_NO_PROTOTYPES +#ifdef XR_EXTENSION_PROTOTYPES +XRAPI_ATTR XrResult XRAPI_CALL xrShareAnchorANDROID( + XrSession session, + const XrAnchorSharingInfoANDROID* sharingInfo, + XrAnchorSharingTokenANDROID* anchorToken); + +XRAPI_ATTR XrResult XRAPI_CALL xrUnshareAnchorANDROID( + XrSession session, + XrSpace anchor); +#endif /* XR_EXTENSION_PROTOTYPES */ +#endif /* !XR_NO_PROTOTYPES */ +#endif /* XR_USE_PLATFORM_ANDROID */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/frame/openxr-compat/vendor/openxr/openxr_platform_defines.h b/frame/openxr-compat/vendor/openxr/openxr_platform_defines.h new file mode 100644 index 0000000..79c5b2c --- /dev/null +++ b/frame/openxr-compat/vendor/openxr/openxr_platform_defines.h @@ -0,0 +1,114 @@ +/* +** Copyright (c) 2017-2026 The Khronos Group Inc. +** +** SPDX-License-Identifier: Apache-2.0 OR MIT +*/ + +#ifndef OPENXR_PLATFORM_DEFINES_H_ +#define OPENXR_PLATFORM_DEFINES_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* Platform-specific calling convention macros. + * + * Platforms should define these so that OpenXR clients call OpenXR functions + * with the same calling conventions that the OpenXR implementation expects. + * + * XRAPI_ATTR - Placed before the return type in function declarations. + * Useful for C++11 and GCC/Clang-style function attribute syntax. + * XRAPI_CALL - Placed after the return type in function declarations. + * Useful for MSVC-style calling convention syntax. + * XRAPI_PTR - Placed between the '(' and '*' in function pointer types. + * + * Function declaration: XRAPI_ATTR void XRAPI_CALL xrFunction(void); + * Function pointer type: typedef void (XRAPI_PTR *PFN_xrFunction)(void); + */ +#if defined(_WIN32) +#define XRAPI_ATTR +// On Windows, functions use the stdcall convention +#define XRAPI_CALL __stdcall +#define XRAPI_PTR XRAPI_CALL +#elif defined(__ANDROID__) && defined(__ARM_ARCH) && __ARM_ARCH < 7 +#error "API not supported for the 'armeabi' NDK ABI" +#elif defined(__ANDROID__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7 && defined(__ARM_32BIT_STATE) +// On Android 32-bit ARM targets, functions use the "hardfloat" +// calling convention, i.e. float parameters are passed in registers. This +// is true even if the rest of the application passes floats on the stack, +// as it does by default when compiling for the armeabi-v7a NDK ABI. +#define XRAPI_ATTR __attribute__((pcs("aapcs-vfp"))) +#define XRAPI_CALL +#define XRAPI_PTR XRAPI_ATTR +#else +// On other platforms, use the default calling convention +#define XRAPI_ATTR +#define XRAPI_CALL +#define XRAPI_PTR +#endif + +#include + +#if !defined(XR_NO_STDINT_H) +#if defined(_MSC_VER) && (_MSC_VER < 1600) +typedef signed __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef signed __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef signed __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; +#else +#include +#endif +#endif // !defined( XR_NO_STDINT_H ) + +// XR_PTR_SIZE (in bytes) +#if (defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)) +#define XR_PTR_SIZE 8 +#else +#define XR_PTR_SIZE 4 +#endif + +// Needed so we can use clang __has_feature portably. +#if !defined(XR_COMPILER_HAS_FEATURE) +#if defined(__clang__) +#define XR_COMPILER_HAS_FEATURE(x) __has_feature(x) +#else +#define XR_COMPILER_HAS_FEATURE(x) 0 +#endif +#endif + +// Identifies if the current compiler has C++11 support enabled. +// Does not by itself identify if any given C++11 feature is present. +#if !defined(XR_CPP11_ENABLED) && defined(__cplusplus) +#if defined(__GNUC__) && defined(__GXX_EXPERIMENTAL_CXX0X__) +#define XR_CPP11_ENABLED 1 +#elif defined(_MSC_VER) && (_MSC_VER >= 1600) +#define XR_CPP11_ENABLED 1 +#elif (__cplusplus >= 201103L) // 201103 is the first C++11 version. +#define XR_CPP11_ENABLED 1 +#endif +#endif + +// Identifies if the current compiler supports C++11 nullptr. +#if !defined(XR_CPP_NULLPTR_SUPPORTED) +#if defined(XR_CPP11_ENABLED) && \ + ((defined(__clang__) && XR_COMPILER_HAS_FEATURE(cxx_nullptr)) || \ + (defined(__GNUC__) && (((__GNUC__ * 1000) + __GNUC_MINOR__) >= 4006)) || \ + (defined(_MSC_VER) && (_MSC_VER >= 1600)) || \ + (defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 403))) +#define XR_CPP_NULLPTR_SUPPORTED 1 +#endif +#endif + +#if !defined(XR_CPP_NULLPTR_SUPPORTED) +#define XR_CPP_NULLPTR_SUPPORTED 0 +#endif // !defined(XR_CPP_NULLPTR_SUPPORTED) + +#ifdef __cplusplus +} +#endif + +#endif From 8d75ede0ab6404acf5a868c90d7709a23e3ec8a0 Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 16:29:06 +1000 Subject: [PATCH 05/36] OpenXR compat layer: keep the current refresh rate when SteamVR refuses a request Quest apps ask for 72/90/120/144 Hz; SteamVR offers only the current rate and Wolvic aborted on XR_ERROR_DISPLAY_REFRESH_RATE_UNSUPPORTED_FB. Verified on the Frame: Wolvic's Quest build now reaches XR_SESSION_STATE_SYNCHRONIZED. Co-Authored-By: Claude Opus 5.5 (1M context) --- frame/openxr-compat/README.md | 2 +- frame/openxr-compat/layer.cpp | 14 ++++++++++++++ .../arm64-v8a/libXrApiLayer_FRAME_compat.so | Bin 98248 -> 98936 bytes 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/frame/openxr-compat/README.md b/frame/openxr-compat/README.md index 6c366c9..4d5eb60 100644 --- a/frame/openxr-compat/README.md +++ b/frame/openxr-compat/README.md @@ -238,7 +238,7 @@ from the binary; runtime execution of those paths remains unverified. Prebuilt library SHA-256: ``` -57abbcec6fbc6df7c79e2b9a46db1a5d8613b4db3c0d7e49bf87cb3172280d10 +aace99b7bcdb3c79fa844d75a9483f8f4b16bd6e669fb3003515df8c673ce54b ``` APK SHA-256: diff --git a/frame/openxr-compat/layer.cpp b/frame/openxr-compat/layer.cpp index 3d6f9df..a5433a7 100644 --- a/frame/openxr-compat/layer.cpp +++ b/frame/openxr-compat/layer.cpp @@ -21,6 +21,7 @@ struct Dispatch { PFN_xrPathToString pathToString{}; PFN_xrSuggestInteractionProfileBindings suggest{}; PFN_xrSetAndroidApplicationThreadKHR thread{}; + PFN_xrRequestDisplayRefreshRateFB refresh{}; frame::Names enabled, stubbed; XrInstance instance{}; bool promote{}, palm{}; @@ -131,6 +132,17 @@ XrResult XRAPI_CALL threadSettings(XrSession s, XrAndroidThreadTypeKHR type, uin } return d->thread ? d->thread(s, type, tid) : XR_ERROR_FUNCTION_UNSUPPORTED; } GUARD_END +// SteamVR offers only the current refresh rate; Quest apps ask for 72/90/120 Hz +// and abort on the error, so keep the current rate and report success. +XrResult XRAPI_CALL requestRefreshRate(XrSession s, float hz) try { + auto d = get(s); if (!d) return XR_ERROR_HANDLE_INVALID; + auto r = d->refresh(s, hz); + if (r == XR_ERROR_DISPLAY_REFRESH_RATE_UNSUPPORTED_FB) { + LOG("xrRequestDisplayRefreshRateFB %.1f Hz unsupported; keeping the current rate", hz); + return XR_SUCCESS; + } + return r; +} GUARD_END XrResult XRAPI_CALL stringToPath(XrInstance i, const char* path, XrPath* out) try { auto d = get(i); if (!d) return XR_ERROR_HANDLE_INVALID; if (!path) return XR_ERROR_VALIDATION_FAILURE; @@ -224,6 +236,7 @@ XrResult XRAPI_CALL createLayer(const XrInstanceCreateInfo* info, const XrApiLay LOAD(suggest, xrSuggestInteractionProfileBindings); if (frame::has(d->enabled, "XR_KHR_locate_spaces")) LOAD(locate, xrLocateSpacesKHR); if (frame::has(d->enabled, frame::stubs[0])) LOAD(thread, xrSetAndroidApplicationThreadKHR); + if (frame::has(d->enabled, "XR_FB_display_refresh_rate")) LOAD(refresh, xrRequestDisplayRefreshRateFB); #undef LOAD if (!d->createSession || !d->destroySession || !d->stringToPath || !d->pathToString || !d->suggest) { d->destroy(*instance); *instance = XR_NULL_HANDLE; return XR_ERROR_INITIALIZATION_FAILED; @@ -249,6 +262,7 @@ XrResult XRAPI_CALL gipa(XrInstance i, const char* name, PFN_xrVoidFunction* out RETURN_PROC("xrSuggestInteractionProfileBindings", suggest) if (d->locate && d->promote) { RETURN_PROC("xrLocateSpaces", locateSpaces) } if (frame::has(d->stubbed, frame::stubs[0])) { RETURN_PROC("xrSetAndroidApplicationThreadKHR", threadSettings) } + if (d->refresh) { RETURN_PROC("xrRequestDisplayRefreshRateFB", requestRefreshRate) } #undef RETURN_PROC // Includes xrGetInstanceProperties: report the actual runtime's identity. return d->gipa(i, name, out); diff --git a/frame/openxr-compat/prebuilt/arm64-v8a/libXrApiLayer_FRAME_compat.so b/frame/openxr-compat/prebuilt/arm64-v8a/libXrApiLayer_FRAME_compat.so index e58d223fe68c2b5c12c44da3652ca3ab908612a9..a58721a065638dde82d73ca8f733cde852743df4 100755 GIT binary patch delta 24450 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�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 delta 23881 zcmc(Hdt6l2*Z$dO1{_9^0R|YZGh75jR8&M%#9=feGbOdcONzI|OG$-Ed6_}2Fs&Fj zIaXw7T7Z`@P_V4Pw5ZImOs%ZY>KmpOCK=;bo8Pm~MUSW6&+otYozG`_&U)6`Yp=cb zI(zSPhU{A7-Qw{oOX6FKoyiZ`+vXFFePraUg2*w}Tfw(gOLh{L=|wwBh=}TTg+-!$ zVdt$_AelFH7cOWh2?)$76m4>9Wu!w%!elw5|6kW)BaX}cxoD0SUhBt;eW=m5o5=gt z?I(%E*l*RRr{2D@__6&{2lX&+-r;wC-^NsWj6~KMG?TTYq;5gCl^VO;w}v^!N0!^A zG{}6>a8ci(;%!!u?^5x-M~ggHBStU#sTwBoI+3??vLbw>s4%D+Od2ZkCKbOdTjVV& z{x``!NySf)29SCSZ|AsfqeMp;w*N32`L9wb@~(&-*Q)rOKSjP# z#ZS8=@=Yo}U-HwU;)^7{mGP|pic_ivi=+pe_|(Ch?rq zj`b_l|Fx@P1KFV*kbK)16b+c=^HQo!D*n_iacV3oUiOou;;YAq{d1`*MXn@^b96I9 zvLma?{sSqX92GAIBKwmECf8ivPP-VftoT)ORG@NnN|Fmze3DztT(OGJk$8uSFP8W+ znYT;ZZj(f8k`)p!cTlO~D zGbE$hPn*hqu;|CmwW}0zDis$eq1-{6G-92K&yb4RpyCT8-Xid*f7y{-y-BJD zMN)%Q6@NtHGgQ2MH^~9Z{^gA3DDBz(cbk;bT%`iMoQZrD?~vpI6)$I^P{r&1D;24V zXPFT0f7zir(o$7}d!#_iRJ=UWN)_KE$yF*|4zzX`5&FL|1sPJLjj9InoXHN=fwrjZ zACQYw#mj-pQ?1d8{d4l%w5b{_l^nIJczJ5NRJ=*r%s2_$vt@$yJhRlMvcJ42<|EIG2Oc!#v@=BW54+Uy@{e_v{qr_##Z7N~gH zi@Yq!!>hO^m8#0VUg8}pew)bKxl)y4r_`WK#gDo!cCbmsm->ktL8XfC(20DNieE20 zKXP27z|#i;0_}22)njQsk{abJ#@wN5kl9V- zyHvc9h&(4vu{;2IKsptFMw)EHE&M=^%aI&gZc%Vtfh5aEH@Slf+CMPVZj`1ZL)A*o zhgHSP`N&c6az1iZyh$$(FHglgGy-qu@>Pl|DIap!a=3Cn3RU*rW2K^1@p3vGDqb%7 zQh~<`BzMq9a#X2maA1Hqf+`h%NaAZ%{1J(7RPly^qMs%u&sM-LNztNIfNx0?8?>tU z(-MD5#b1#4HWhzO;@efcDP4L3?NBN5p|(T>XZ}~RfBwo5D%B6;&UZFRmES65j)6G@m296Z@s}wJCODpu>;*(qJcazhqS{O zRJ^>0npAwAWN%UN^7$@F#V6UN^HZuyA@8*rDqil`s^T|E9p|X{%HPF-<*Io3plHui zDdfF1U&WiG)ulki%X@2~itmsDDpvE-!O|h}H`h&*)Sy(=piO#?DpT?DbJivmuai2c zRPkk<;?!2%!ejnRB}MHm3cMKlQ*6+v=A{mrRJ{DK(4yi?CHqqU`(cYorV#h821K)a+7w^ARju$+&N+TwC{t6X+3;)&Ci4J-2;+3d0D0tC>$eR?rM&ZYz z;KfHgv41W}q3}{PNLBFcb+!D-Q1H@gUqNM6@H*KTn4{o*6nw5-q0lQ7c?!O}g3nj* zz6!oT!TTxrLIvML!52%sou$-Yp>QY~7!-V|f)7yeWePq}!EaLVK?=T7!5gI~AVFQF zPy{O))GGK81>dOPO$xqA!G|gM76l)EgU9^0Dijen6dZR-!AC0iHU%H0;M)~^w1V$Y z@MZkf-2N6nwsd@1x)g>gmwkgkt22ACqCxuEYsCZO1sUsuHO17 zse8rtuD0&=+rKAutM7PNQ`a^20V8n_jtbS^mxt#3xPAvdpwRVk7qT$r!RUu z*DrfKPvN>Jt{;WI|F0g;383kM$Mfp%9?w!-bNFs@dpuWu@pvM^Ya!ou6>b0Uc<%kp z<5>pz^*=qHwa_m_KX%B^|LU=O3K4)D#CIV7cfaAUO18C=HQ|gb>9d~ znF1$h*a9p7PMZoxSRnH5M+cCrfTw`*`5b4!Hffo~abtn0zyjbXU=HldfomWi0`3HM z0gnJ{=b$Zi(XuG4z>_V6?!pCon+p8OsoOZ^&5IFLcL$QcEyu?9&H; zbD*eQ&2ft$C#}I~ftd`!r#*$yKpq7=1uO<~*vIpqM(4l+;3QyqDICK-^BGJPVQ>+y*<{CXV|FSOJXh zgYS#rq1U|xAHdv~5j@Td+kn%6MXw+L;Bw#rU?cDXuxm5sKh*%?Rg4^td@ErLIRm%} zavrb}a@7`e2y6s)0WDiGm(ZuZhBkdMnr#Ro0G$IffWF(|510a+1bsPh4dg?>THv>I}*D?RsaM1+9fRl;#4WtNY0Zsy@0oTAm5wHMqDX+i_vRMF-FUyap@>THZ$^q`DAj=m+B_WB`~7tb)E0SOi(u3}=w% z0JY%=1h^A?(LszFnD+r{1N;%7DHWMI3=Xmbn460EcYKHuf*5-QBLgl6wgKahqT>iu z@aOmfbB@^fjDDAp^iF;HYn504xR?qcIv_Ca?vV3v30>0dl9&3D5xC2DAVhfvLbFz*E3$ zz)9aBb7p+MM|OadfEB<3V67b&Rk%3K4B9Yi3sU+cIsh7g#Xu8qJ#ZSZ5;*E7j0kA_ z85sa30j~iwfQA@k1Q-vT^B;5q+zDI`Yy_484*|CUJAn2hxJWyPPGb=OFcLTxmlfM7VZfsgcBTNeP&X&DtWO4*w zJBdb5PUl-D(R%^D_tEDj5A`~d2j7%T3!-h4wb4zGa3>?(Jq)9KqYRt*SF~(w%XuVg zGTl5inp&plc*A6xJSCcspG?P0xz}sW6x0%Za9SumFvY|lpGr?m8Or-k0}Rlk__-Dh z{7s|drVfZcKI7m0o3xmOeE3iBDxQl15Mn+vHG*%POaGo4&ezVR;nTvsS|38;Ko?4L zrE$=E~Q6j$0kQYoCdKHCKgnRDxf|&dv!iL@Y#l$AT_*P$g%&z!*@tzIhRMtT zhg(|su-J3JWl}OLj&{t}^|C_CEZK))DbuQ2RDiQeEwte+kXS!*s}!_4%`7(3S#$c5 zf*XbzepuUK2%Yd@hGW6a*+{>D;d&pi?X>|uXaeHec4}?hD+f18!Lg32z~#7T-v^TUu`c@1+yv@)K+EU4Zj?rf zKWjwadLW(8+(dtUU}$pEONdPvl9+{hSX6++j0>Mk{I>;Mqs$2A1boY1@rY;`4DwN2x4Gb9Knukzm+qAYf^A&uulP(a-F_4 zKRx?^%0)3$1u&NDh-8Y>S{`X7Yz! zR|x8xJmYG?&Cef0`LG;~+6SF!&<$z%y*0cy_ zNr!K((NgA4!NtR_0-WhEjeR76k33ArJTf#n{-ax486(h{f@8fTfir$YcRwp4#dgmlmHl?p#mmk7Se}+GH-;2IWyNwd78{u^ZnsDH zjG~^RXpLSohmKzwrO(8KG-;teNgrD}Mt>?A>BdP(_bL5k=}>)p3{C-F5Z|FaipS`E z6S1nGdl=6u9-yy45!JI^Kc}0EZF+5Qlqoa*lKu)~LnFDGP__}_Jr27 z4SX5+6y|oKJiH$HwCwRv`T?9>fI*QB6Uhc>-c8Rxaa#{7uG>eW6V~Z3G;3LU*cwp9 zw`1TsP#^4)($u4t(2}LmbjPx&9;aYoL6~{W(Rq4e*=YY1j5PON41jh1&MuaAI%au@ zeu_QMX3Ky@R19}tw-nCDe#BEUjSbSUa5P>zftVpKAN(en|BFsvnc!azYHSf|pM@K-htie(dmIO6S%P6O?sxjd z%Ax*|NLTA)*d&-o^BzPHHJEuH3%;rtmB;vzbz(Z%L5%X7kw4)WRS26?uwfDC2G-HV z_9*`faHGnkX{L1;eF)uS5A|p7yz;QCX$ zJNmDE$!yVl^$g(*2R)t+TX~GrrXrUU+3IT(?~247f?f>j9)YTLuL| z7fpYAGMe03{Y zwl`;U_51K;H82a9#98;71GtAtHa96ri)*BEi&eOGn@e<;ve~t7wp(ZA>V1fH|A=7d zM{tvTyl@TMZ1LJXVvF|DnJjKn@LH>TZx*KIS*yDM*fKPGe*q4JEnBVb_Tikd{!9?( zW`*W?nsW~35^mpn6@ zKNnBGc_u-h6VL5!`eVhHf233Wx`cq~XuT0zXPYJjw|Zzz49#8_nF0rA5u)v;U+#Np>=ayNV^D|rv)3ZLy+4dvnJlRdUb8x*2*X=)A zhhR+%^qI#Gslxr!ZnnDH60L6Hwr@CY!L5g`S+|0ZMSYxS(XNRfae~?5#oEKPX}zC5 zlyeLB-64*?xjun6MFFDqaZ;<+Uep6N&!5Fhhd-+`R;3HEY*fUuh{ik@5n7HOml7^r z?p5&G>dP8N%0aL-070x!G@d9lUiNQ|3%t-cj4paEWqxH=cf(E-SAOOL9?L^eO%YOa zuA9}3VRx|gp>lY4gU>GBy_?yv1{aeb7J`JRcjJ$jnd|1))}V zwPr?_nfRQKpbAC1r_ThtXLNx~nYIXEnRR;NGC0m5sEwn7MMCYm7Lkv4g_R#R>7BN9tJo zQ>FOFOYz^uaGBLziRy1(jY_g}ZoI+#bT53d&EYHH6!c=(a}VdB&^fQt@UjJA>#q&hT(9zPsUMC|8adYh|Ny#C11> z?6SGP3#C0aWRYIRTY|(9vqOj5QtQ$GT-DQv(cr#YQK-Y~x?F zQP<0pgT5TVl_z2C{uAm0P*-SzsivZ5_XcdM`ymgB3#J1rme_HoD*p>M%$4aCo7=;i z&d=9!liL4Y@nw53x7UrOzD*NW^D_J~n=aULFWTIu7kM|bRL@3y;;7!x;*E9m(uyyS zMsnq+5b8eIo;Yj7;$y@A19IVZUedUk73(OkGsa3t4n-^5VDo_+H8r1S2Mgi z;I(D5P4Kn^-hSB3vt>&d;KI!V*Z^PM9B$lhbNBM%oIcydtmnV0H|(N!ZwVs7^vvc6 zGKF5>O#9L|jChUyI=P2hdepl5acO|K>{8wq2}_4wN09Hf~*-5Y_;Mb<0@_ z%T8ScSDryHZJ9xkj;#|(0^PpV999mmjkt?F3(_-4H`|c*)i}Y%an9ZJyRD%<j<)n4u37uD4qdoZMvb-f{*hueeksrAu%wn$3m^F?e8o!`lWCZOTT+qLQh7Ax)NgD?FCC>O* zoyGkg%S>B@xZfAinXgYI+vwicQ*I13FWC8`CZgsiO?XW){qyyyB!k}lMkpCT=f5$V zKi!WWeIq&G_EfGM4{!Btnn=`jSdE_gR4pF!TNoyL^|TC3csj7Fa`pXP>{QVXz7A=B z6zjlDX&uu7gH>brCZ6`F4)LCjvHaeTCReWv+X_eRv$Mrv z^usU)i^Hg(&DBADT>nG&PVkkhxnsSFIKOW~PNSsePMpQ>sxe8sK&`D}u#m`2bV*kDl8xlBClvxALU6LvlymC@|+w6ens4kxX)X~9yh z8k2v*k>b<7v}RWV*+h@+8bR)$0lOo}bF|Oyfn*DvxjTqfyqQFPrib4&ljypi-`v5I zAE@)K56Gyxd+PECpV6JZS)U%M&34;#+3we{6SI|;EfOTQ6T zF`LfbyPs^JQTy&B*XjIy6NB4Rm}w{~#aMF|C*%x`Y2CN<*uDZDtHQ8`zHBjD*bvTR z(R#Y!tthN%>@frD+A+4SDR(74lHS|6ia!&;?#rDvz=>E3{hu_~@$ZeUfImr1jPJAr4Y-@}0qxyHuymct;{mx|n zw_(L~=hB)m3)n*`eP=SMq!-`WM6~p&0|!I5!)GdNt}lDP`Z|h2uxt0HGu};)G$99( z+3uxm?~_tBNZ8hlHS6e}cO&>5{8IJqRFX-P-wW?snv9dDurY+zuuUqA3tdOxnH|Hh zAtmF@x$#epxyFreQx<*Xy^UUj(0LQ}dq34DxOq70q)gX`j($Hmc<6#|hU(SkE<9rI zx@Y0iE>=nz^vUN)rbA`pGc}zy#!T` zpxRC6el!w~-_;+P$qV%3j~>M@Rl{4pCHK&AV+pG5Fp`%%xM==CGl z_;oh=-_Hi{Pupm}qm#)}y7p)!Kif#(I2zS`3XWe`_c|(88|mjqS2EM_pNHKxB0*TN zYb;OO-04r>JQAead~O^G9>q!J1Qt7?`ABvoV2_IINDx}{0;XVR-LB7*NHjc*fCsky z=d2UA|46i)v5t4&i{37`_Dz6^&*MwG*7A4&HIl$$#i)$K%L&dg=Hua+LP@ioMCYOZe}DU+wUE7$tg*Mx7{Scl;YCwv*BH zu9JymH(hda80kazoHP?kj|u<1baID(E4KNm@pu5iPY^mhweL6;9^9Oa#gVJgah$sk zwi~ua;13ACIc4_Cw7Qw*I07!P(daMIY3A2HQCWD+FdBzLoA|&$a8f85V0EWyV3p3S z0z3S$FyhwD2VF1}rs=fi>%?w&U2w(^f7n&q9sVzTUBRa4n_=X8dia|k*<61+4~NTu z(=_xb4h)lV-jwUG67LCE9lmoqZ2rWR-Uco{TR5wp`ATOvi*Sd-7Vi!hvvWzw37x^% z@x}ypZRvkq(5G3r{T7bfHBe>8Y}(R98>VM=o#WXuf3PZv4*%{xEUhnmH&x5GjHQ2^ zS-{U9Pp6+v=Xcy*xAE*0Z>{C>)m+-yKE#}gr6Vod?bP2~I>zIgt+Lxu1GLa%r1|G` ze9}<5;M{1kgYG$(A2}Rvc(OSsOFMhfsKK)qTS&M$obx%_{A&c(;O~Boql}YPAFlZb zPf%7(@Vad55A3A;0S);5PMm1&|GjtccIfuPMZP8!E<)fUxTcc6^7}%vmil!3K<=XF zI?Mql@xX8fXKmI28`vE*?9x(_NS&8vAgoK5dJ`Ru`XiKFrGx$$i0x+nALc&S(FV8M zpzF(;tKN^(e8o0qHlxjtT}@5LkzoWlxq`0Hy?@+J+UfN_BFIG=cG;5Jqo=c!w+_(} zZqQcj7B-`gaH1p*$Alj>A<3OMT^>gVYz`ZFZvP%S|MD=BO1ED&la2K7n*WR_?u8NaH(`!o5LOVa+~w*K-r{^>__5qs5w(B!X`F za~ST0@7Sv#8gs==JT&{teSA_Aed)?puOyh%(d@q_>awqQ!(R`f2%0XMX#HRPBgdlR za>_eBJdvsiH5s6ZxbI^q@N#Tm411CB8Jl8Gz(m#!(fw zRT-B#%;^XD<=;Fmc_an2|J8}wrd1eE>DA$V@3Oh8Lac7~{zZ7Zf%g?cOYIQncZjKg z=bicZ_2(@1Q!XW&UcQ>Z-({l_|4p!){P6mZ9UX#d4#Kh&Rz&4(3!cdZZ%O@}%&rl3 zY?oF}9&1-EY=W6ipSyVXYLCaI)U)h6zUyXq8gEgQ#Xy_>Yasdb*ni{roEPc1Yq8XH zt*0SA#mOo~9&8J}>spTHT9lKfT#qv0rZj5t)&Msq9(<3nn-izEz$5gtYs*czss2s* zM%~QoiA38r75Q5KcZ_a9wtMwKtuvbL{d<^S5XMph$%xr5C*f3L1o+rfay3`%o;s!ro5=zK`hMs<;H&5PZxZ9r$CS zUUJuAg(anHgP1xSl+T&xJ`zTh8`MB;?675yJ|Fym*)4^YJ{Y+VU5 z57`Ep`RR0Fc{KNJ;<_1Lh6`rmgOQ!XhKk=2TgSkB1knpm0`f>wh-;{c*rN0vZ-Wtm z_agc`d#&In;?(fCQ0UjZ4J%wPn#e#>>iWz?O#H{Z>lYJwA*vsOW6Ml zB$>{W?;Bo-CVvsHa=g=|E~kYo<}WXH@iF8>65#qch9rBpw!MD){^2fn3~Jy(SFc!N zwtK-i0m;G7nyuwq#Zqndb}C^Nt0#m>yhqsyf2v#UC7rO zclz~LTd?S~a&6Jh>v#vW6xXb$K(uSR>e<^e8awO7s+_B)xE558myJ65-x^b?L zVo5;Q88l)qnAjnVHDv45CU4HIab1cf6G^!1wm8z8)VUsxBSB=J>&ZA0H{>(4IfCz} z_%9&6-q!uyTxa5lC8bOpDy$6nvmy2n zJ&3jLg(!SVqKEw5ExYS|X&c+=>Jv{=eYfAf|L^+_3Y*(@*Zg>-@MG7D@g(B@g8`h8 z_2l`09Q%79)+pXF;h5mlIvBT1?cW2f?hz&CA<#QzpR9PA>bea0HOR@LH*2|vKSjj2 zVQOPilbvngZCIr~as~7x#%NV*mMhtFQi<6$vM0&v*}r-CI<#JoZyCORA3yr#sy@xb z$*40A&5mjw9^bQhxOceg^`0crC#HFLYp>?vKl!*$^d!A~1~m_liQGPJR6v71fs7#h zpBmSli6oZ)bEIo=A_*Jbwi$t9Ly2R7;6s-!o3Q^uvMfwvBk8` z-bP$^_9ov4&$yv$wHZU1u6DMiAuEN<=e1?JlB|XueaRs{sTF@Z?i_`}9%Qw4rKfVv zd;a$9b-;uUi6 zFL{(@wp5d7nbY6(!w3>Rrdgr8*@H75o0oTY{JI3hUtE^&z`=GRHXl|_CGX#8qwLvr zyOqr3T}v8jti)gEo#*4!&ChndpF=_xGzB^PA`?k?UQ( zlB-a5risdyWLLj&@H$SRQ#71Cj#&6eA>iiB8>OTir0dmOl9VS{Y#FMsD2x_6NJ|q6 z^b4GQS%Dr0F2&dEgP+%NP8}$vqdDW&w!*zt*v)0Wo0#l^_n5)AS_oq^h6vvGN!}ZQ zQ}LC(mJGVJjo|gTpU|dSYO@14PSNJfz+2nQ_6)&nR*VoDRY;9C1G5#4vInRe32hpF zzniQiV|t28%u1`RDVF7>b#`N-;4oQom;(G98#y9x70M(2U)mf`bu~;RYx!@!ZLm!u zBfEwCbVXP#gy|ORt`&9XdU+~l^v8eKeGc82Gykd+0@CAj!6wQV_$FTe-w3GfMw?ct zv|49leH%QD#D;vY&>in5PJF7)wV3Hn|3lY0`*@1$4W|3%AG(g&mC>#rrXj|!|3#O= z2{C?%xLAz%-xSIhDq<`-sc0isu@K|!_ah%){kv}2{iH@-f_qOVPmz#$HuoJ1@lz3g zCw>htfTvO$!&-J4j!DAvM}D?@hpV}O z%pfCNG4shVAH8J#s%!3iV$n%9#ja=PlSuDX*=_^x>w0rO*5;>h9qa3~@a7X(mumx_ zfoxOA&q1E0kT*bHrI0s5-l~vaf_zXRZ-#tUA#a7u``zfz9`HJd*qcN^4P=`_-VJ${ zLVgSKDuuic@>YfXHspf}`8~*I74kvId=EJQdu_mnAYyM2wE>?%wkhPJkY_36FCec{ z$R{9gRmk5!KB$nthkRBcpM}i(O99xC^8bK{l?lkdLbfU7-yqLY$bUdyrI4>c-l~wV zLO!UF|Au^4A@eo_U=RbavpM$$5i1c))pk0LZHpaysO#qTG=E5ZOkkL8EmoT0wjp zavvg9TG~c@1?t)(ZQ(l^k8gB1J7SRUb;0zTj$|CF}t#g(LA zK-0aVxs0W*uUC@P0EfutN|^VvE8I@{>Z|68lUg;`HNj5O^*1NiwSmdqKTe~2#PtP}J0!W|5m#UdNe?Wn7yFn0 zY4Wn}t|=vmOaAA23X5DDV5e{E6zxtGxxOeNSv)Xs70J@SjKBM0pCsa%vVuhaKduQb ABLDyZ From 064cf95f6d6c12211c80e54eea8eb1bf13ea04d7 Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 16:29:11 +1000 Subject: [PATCH 06/36] VR APK notes: Lepton's missing clipboard also stops the Godot XR Tools demo Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/vr-apks.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/vr-apks.md b/docs/vr-apks.md index 45ecd56..aeced96 100644 --- a/docs/vr-apks.md +++ b/docs/vr-apks.md @@ -73,7 +73,11 @@ not being worn, so it did not reach `FOCUSED`). inside the APK that asks the runtime for 1.0 and maps the 1.1 core functions to the extensions the runtime does have (`XR_KHR_locate_spaces`, `XR_EXT_local_floor`, `XR_EXT_uuid`, `XR_EXT_palm_pose`). -3. **Not yet reached:** required Meta-only extensions (each app differs), +3. **Lepton's missing clipboard service** still applies to VR apps. The Godot + XR Tools demo's Quest build (itch.io) dies in `Godot.` casting the + null clipboard service to `ClipboardManager`, before any OpenXR call. See + the clipboard table in [apks.md](apks.md). +4. **Not yet reached:** required Meta-only extensions (each app differs), swapchain formats (the Lynx Wolvic build needed `GL_SRGB8_ALPHA8`), and Meta platform services. From 50d5e1453963565ff4b71ee0458d2bcdd7315e44 Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 16:33:14 +1000 Subject: [PATCH 07/36] Inject the OpenXR compatibility layer into VR APKs on install VR apps with an arm64 OpenXR loader get frame/openxr-compat's layer unless --no-xr-compat; the app bundle ships the layer. Verified on the Frame: Wolvic's Quest build gets through OpenXR start-up, Open Brush still reaches FOCUSED. Co-Authored-By: Claude Opus 5.5 (1M context) --- app/package.json | 8 ++++++++ docs/vr-apks.md | 24 ++++++++++++++++++++++++ tests/test_frame_apk_vr.py | 37 ++++++++++++++++++++++++++++++++++++- ui/frame_android.py | 38 +++++++++++++++++++++++++++++++------- 4 files changed, 99 insertions(+), 8 deletions(-) diff --git a/app/package.json b/app/package.json index bd5701a..c9b80b3 100644 --- a/app/package.json +++ b/app/package.json @@ -64,6 +64,14 @@ "*.py" ] }, + { + "from": "../frame/openxr-compat", + "to": "frame/openxr-compat", + "filter": [ + "XrApiLayer_FRAME_compat.json", + "prebuilt/**/*.so" + ] + }, { "from": "../frame/devkit-utils", "to": "frame/devkit-utils", diff --git a/docs/vr-apks.md b/docs/vr-apks.md index aeced96..8d31cc3 100644 --- a/docs/vr-apks.md +++ b/docs/vr-apks.md @@ -109,6 +109,30 @@ in Frame Control's per-user app-data directory as `apk-signing-key.json` updates. A re-signed APK cannot update an installation signed by its original publisher; Android also treats it as a different signer for signature checks. +VR apps with an arm64 OpenXR loader also get the OpenXR compatibility layer +([frame/openxr-compat](../frame/openxr-compat/README.md)): an implicit API +layer in the APK's `assets/openxr/1/api_layers/implicit.d/`, which the app's +own loader picks up next to Valve's layer. It asks SteamVR for OpenXR 1.0 when +the app wants 1.1 and enables the extensions that became 1.1 core; maps +`xrLocateSpaces` to `xrLocateSpacesKHR` and `grip_surface` to `palm_ext`; drops +1.1 controller profiles SteamVR doesn't know; stubs +`XR_KHR_android_thread_settings` and `XR_OCULUS_android_session_state_enable`; +and keeps the current refresh rate when SteamVR refuses a requested one. +`meta.json` records `"patched": ["openxr-compat"]`. Skip it with +`install … --no-xr-compat`. Its decisions go to logcat under `FrameXrCompat`. + +Verified on the headset (2026-09-28): + +- **Wolvic 1.9, Quest build**, installed as downloaded: Frame Control added + `LAUNCHER` and the layer. The layer turned OpenXR 1.1.48 into 1.0.63, the + instance and session were created, and a 144 Hz refresh request that SteamVR + refused was kept at the current rate. The session reached `SYNCHRONIZED`; + then Wolvic's Gecko engine crashed (null SIGSEGV on its Gecko thread, the + same crash its Lynx build has), which is Wolvic's, not OpenXR's. +- **Open Brush, Quest build**, with the layer: 1.1.54 → 1.0.63, the thread + settings stub in use, `bytedance/pico4_controller` bindings dropped, and the + session reached `FOCUSED`, the same as without the layer. + Inspect or prepare an APK without contacting the headset: ```sh diff --git a/tests/test_frame_apk_vr.py b/tests/test_frame_apk_vr.py index c13e856..186ae58 100644 --- a/tests/test_frame_apk_vr.py +++ b/tests/test_frame_apk_vr.py @@ -174,7 +174,10 @@ class VRTests(unittest.TestCase): def test_install_auto_and_override(self): base = {'package': 'org.test.vr', 'label': 'VR', 'abis': [], 'min_sdk': None, 'vr': True, 'vr_activity': True, 'launchable': False} - with patch.object(frame_android, 'apk_info', return_value=base), patch.object(frame_android, 'patch') as repair, patch.object(frame_android, '_install', return_value={}) as install: + with patch.object(frame_android, 'apk_info', return_value=base), \ + patch.object(frame_android, 'xr_compat_files', return_value={}), \ + patch.object(frame_android, 'patch', return_value={'patched': ['launcher']}) as repair, \ + patch.object(frame_android, '_install', return_value={}) as install: frame_android.install('original.apk') repair.assert_called_once() self.assertFalse(install.call_args.args[3]) @@ -182,6 +185,38 @@ class VRTests(unittest.TestCase): frame_android.install('original.apk', flatscreen=True) self.assertTrue(install.call_args.args[3]) + def test_xr_compat_layer(self): + with tempfile.TemporaryDirectory() as d: + apk = Path(d) / 'a.apk' + with zipfile.ZipFile(apk, 'w') as z: + z.writestr('lib/arm64-v8a/libopenxr_loader.so', b'') + add = frame_android.xr_compat_files(str(apk)) + self.assertEqual(set(add), set(frame_android.XR_COMPAT_FILES)) + self.assertIn(b'XR_APILAYER_FRAME_compat', add['assets/openxr/1/api_layers/implicit.d/XrApiLayer_FRAME_compat.json']) + self.assertTrue(add['lib/arm64-v8a/libXrApiLayer_FRAME_compat.so'].startswith(b'\x7fELF')) + with zipfile.ZipFile(apk, 'a') as z: # already injected: nothing more to add + z.writestr('lib/arm64-v8a/libXrApiLayer_FRAME_compat.so', b'') + self.assertEqual(frame_android.xr_compat_files(str(apk)), {}) + flat = Path(d) / 'flat.apk' + with zipfile.ZipFile(flat, 'w') as z: + z.writestr('classes.dex', b'') + self.assertEqual(frame_android.xr_compat_files(str(flat)), {}) + + def test_install_adds_layer_to_vr_apps(self): + base = {'package': 'org.test.vr', 'label': 'VR', 'abis': [], 'min_sdk': None, + 'vr': True, 'vr_activity': True, 'launchable': True} + layer = {'x': b''} + with patch.object(frame_android, 'apk_info', return_value=dict(base)), \ + patch.object(frame_android, 'xr_compat_files', return_value=layer), \ + patch.object(frame_android, 'patch', return_value={'patched': ['openxr-compat']}) as repair, \ + patch.object(frame_android, '_install', return_value={}) as install: + frame_android.install('game.apk') + self.assertIs(repair.call_args.args[2], layer) + self.assertEqual(install.call_args.args[1]['patched'], ['openxr-compat']) + repair.reset_mock() + frame_android.install('game.apk', xr_compat=False) # launchable, no layer: install as is + repair.assert_not_called() + if __name__ == '__main__': unittest.main() diff --git a/ui/frame_android.py b/ui/frame_android.py index 39e9161..cec2e07 100644 --- a/ui/frame_android.py +++ b/ui/frame_android.py @@ -7,7 +7,7 @@ in the Steam library and gets its own SteamVR panel. Nothing goes through Lepton Development, which wipes its apps on exit. See docs/apks.md. Python stdlib only. CLI: python3 ui/frame_android.py - install APK [--vr|--flat] | info APK | patch SRC DST [--add NAME=PATH ...] + install APK [--vr|--flat] [--no-xr-compat] | info APK | patch SRC DST [--add NAME=PATH ...] list | launch PKG | stop PKG | remove PKG | probe PKG """ import json, os, re, shlex, shutil, subprocess, sys, threading, time, zlib @@ -26,6 +26,13 @@ COMPAT = '.local/share/Steam/steamapps/compatdata' SHADERS = '.local/share/Steam/steamapps/shadercache' LAUNCHER = os.path.join(ROOT, 'frame', 'android', 'lepton-app.sh') SHORTCUTS = os.path.join(ROOT, 'frame', 'android', 'steam_shortcuts.py') +# OpenXR API layer that lets OpenXR 1.1 apps run on SteamVR's 1.0-only Android +# runtime (frame/openxr-compat, docs/vr-apks.md). Injected into VR APKs. +XR_COMPAT = os.path.join(ROOT, 'frame', 'openxr-compat') +XR_COMPAT_FILES = { + 'assets/openxr/1/api_layers/implicit.d/XrApiLayer_FRAME_compat.json': 'XrApiLayer_FRAME_compat.json', + 'lib/arm64-v8a/libXrApiLayer_FRAME_compat.so': 'prebuilt/arm64-v8a/libXrApiLayer_FRAME_compat.so', +} PKG_RE = re.compile(r'^[A-Za-z][\w]*(\.[A-Za-z_][\w]*)+$') SSH_OPTS = ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=8'] @@ -71,6 +78,19 @@ def apk_info(path): raise FrameError(f'{os.path.basename(path)}: {e}') +def xr_compat_files(apk_path): + """The layer's files to add, or {} if the APK has no OpenXR loader or already has the layer.""" + with zipfile.ZipFile(apk_path) as z: + names = set(z.namelist()) + if 'lib/arm64-v8a/libopenxr_loader.so' not in names or names & set(XR_COMPAT_FILES): + return {} + add = {} + for entry, rel in XR_COMPAT_FILES.items(): + with open(os.path.join(XR_COMPAT, rel), 'rb') as f: + add[entry] = f.read() + return add + + def check_installable(info): if info['min_sdk'] and info['min_sdk'] > 30: raise FrameError(f"{info['label']} needs Android API {info['min_sdk']}; Lepton is Android 11 (API 30)") @@ -112,7 +132,7 @@ def _write_meta(d, meta): ssh(f'cat > {d}/meta.json.tmp && mv {d}/meta.json.tmp {d}/meta.json', input=json.dumps(meta, indent=1)) -def install(apk_path, flatscreen=None, name=None, source=None, icon_png=None): +def install(apk_path, flatscreen=None, name=None, source=None, icon_png=None, xr_compat=None): info = apk_info(apk_path) if icon_png: info['icon_png'] = icon_png @@ -122,12 +142,14 @@ def install(apk_path, flatscreen=None, name=None, source=None, icon_png=None): raise FrameError(f'unexpected package name {pkg!r}') if flatscreen is None: flatscreen = not info['vr'] + # VR apps get the OpenXR compatibility layer unless told otherwise; it only + # changes calls SteamVR would otherwise reject. + add = xr_compat_files(apk_path) if (info['vr'] if xr_compat is None else xr_compat) else {} with _install_lock: - if not info['launchable'] and info['vr_activity']: + if add or (not info['launchable'] and info['vr_activity']): with tempfile.TemporaryDirectory(prefix='frame-vr-') as tmp: patched = os.path.join(tmp, 'app.apk') - patch(apk_path, patched) - info['patched'] = ['launcher'] + info['patched'] = patch(apk_path, patched, add)['patched'] info['launchable'] = True return _install(patched, info, pkg, flatscreen, name, source or os.path.basename(apk_path)) return _install(apk_path, info, pkg, flatscreen, name, source) @@ -301,7 +323,8 @@ def patch(src, dst, add=None): repack(src, dst, replace={'AndroidManifest.xml': manifest}, add=add) result = apk_info(dst) result.pop('icon_png', None) - result['patched'] = ['launcher'] if manifest != original else [] + result['patched'] = (['launcher'] if manifest != original else []) + \ + (['openxr-compat'] if add and set(XR_COMPAT_FILES) <= set(add) else []) return result except (OSError, ValueError, zipfile.BadZipFile, frame_apk.ApkError) as e: raise FrameError(str(e)) from e @@ -311,7 +334,8 @@ def main(): cmd, *args = sys.argv[1:] or ['help'] try: if cmd == 'install': - r = install(args[0], flatscreen=False if '--vr' in args else True if '--flat' in args else None) + r = install(args[0], flatscreen=False if '--vr' in args else True if '--flat' in args else None, + xr_compat=False if '--no-xr-compat' in args else None) elif cmd in ('info', 'describe'): r = apk_info(args[0]) r.pop('icon_png', None) From 45f720883abec30e77199fcbe1fa47d5c0ebf166 Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:22:55 +1000 Subject: [PATCH 08/36] Docs: house style for announcing features and fixes Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/announcements.md | 144 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 docs/announcements.md diff --git a/docs/announcements.md b/docs/announcements.md new file mode 100644 index 0000000..a62c0ab --- /dev/null +++ b/docs/announcements.md @@ -0,0 +1,144 @@ +# Announcing changes + +How we tell people about Frame Control features and fixes as they merge. The +same few sentences feed the X post, the release notes and the website, so they +are written once, in the pull request, while the change is fresh. + +## What gets announced + +| Kind | Announce? | Example | +|---|---|---| +| **New** — something you can now do | Yes, its own post | Stream Mac windows into the Frame as panels | +| **Better** — something existing got noticeably easier, faster or wider | Yes, its own post or a roundup | APKs install without the Android SDK | +| **Fixed** — something broken that users hit | Yes if people reported it or it blocked a flow; otherwise the next roundup | Mac mirror showed a zoomed-in corner | +| **Release** — a tagged build | Always, one post linking the release | Frame Control 0.3.1 | +| Tests, refactors, CI, docs-only, website polish | No | Fake Frame tests, screenshot crop | + +If a change isn't worth a sentence to someone who owns a Frame, it isn't +announced. + +## The voice + +Write it the way the README and release notes already read. + +- **Lead with what the person can now do**, in their words: "Install older + versions of an app when the newest won't run on the Frame", not "Add APK + version fallback resolver". +- **Plain and specific.** Name the thing, give the number: "about 30 fps", + "4,500 apps", "up to 8 older versions". No "blazing", "game-changing", + "excited to announce", "huge", or exclamation marks. +- **Say where it works.** Platforms and what it was tested on, briefly: + "Tested on a real Frame from macOS 27." Don't claim what wasn't tested. +- **Say the catch.** If it needs a setup step, an unsigned build, or only works + on one OS, say so in the same post. +- **Sentence case**, full sentences, British spelling to match the docs. + Contractions are fine. +- **No emoji in the text.** One image, GIF or short clip carries the tone + instead. The only symbol is the kind label below. +- **Unofficial, always.** Never imply Valve made or endorses it. Say "Steam + Frame" for the headset and "Frame Control" for the app. +- **Credit people.** If a user reported the bug or suggested the feature and is + happy to be named, thank them by handle. + +## The formats + +Every announceable PR ends with an `## Announcement` section holding these. +The reviewer checks it like code. + +### 1. The post (X, and any other social account) + +``` +: + + + + +``` + +- `` is `New`, `Better` or `Fixed`. +- 280 characters maximum including the link (X counts any link as 23). +- One link: the release if it has shipped, otherwise the PR. +- One visual when the change is visible: a screenshot from the app, a GIF, or a + short clip from the headset. Alt text describes what it shows. +- No hashtags, except `#SteamFrame` on releases and on posts about something + new, because people search for it. + +### 2. The release-note line + +One bullet under **New in x.y.z**, same as the current release notes: the +first half of the post's first sentence, no kind label, no link. + +### 3. Release post + +``` +Frame Control : + +. Also: . + +Windows, macOS and Linux: +#SteamFrame +``` + +The release title on GitHub uses the same `Frame Control : ` +line, as 0.3.0 and 0.3.1 already do. + +### Roundups + +Small fixes that don't earn their own post wait for a roundup, posted with the +next release or when three or more have piled up: + +``` +Fixed in Frame Control this week: +- +- +- + + +``` + +## Examples from what has already merged + +**#11, older APK versions** + +``` +New: when an Android app is too new for the Frame, Frame Control now offers +older versions that will install. + +It checks F-Droid, its archive and IzzyOnDroid, and verifies each download +before it goes on the headset. + +https://github.com/saphid/steam-frame/pull/11 +``` + +**#8, Mac mirror fixes** + +``` +Fixed: mirroring your Mac into the Steam Frame now fits the whole desktop in +the panel, asks for the right password, and shows the cursor. + +Tested end to end on a real Frame from macOS 27. + +https://github.com/saphid/steam-frame/pull/8 +``` + +**v0.3.1** + +``` +Frame Control 0.3.1: install APKs without the Android SDK + +Frame Control now reads APK files itself, so there's nothing extra to install. +Also: Linux and Windows game sideloading, and one-click install links. + +Windows, macOS and Linux: https://github.com/saphid/steam-frame/releases/tag/v0.3.1 +#SteamFrame +``` + +## Posting + +Nothing is posted without a person approving it. The flow is: + +1. The PR carries its `## Announcement` section. +2. On merge, the post is drafted from that section (manually for now). +3. Alex approves or edits it, then it's posted from the project account. +4. Replies and questions that turn out to be bugs become GitHub issues labelled + `feedback`, same as the website form. From b393e9085419bf44f5239b253b15581085d2e88f Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:23:06 +1000 Subject: [PATCH 09/36] Keep the Frame awake during agent work Steam's own idle timer (60 min on AC, 15 on battery) suspends the Frame, and SSH work doesn't count as activity. scripts/keep-awake.sh on sets both timers to Never through Steam's DevTools (reusing ui/frame_steam.py) and holds a logind sleep inhibitor as a user unit; off releases the inhibitor and restores the saved timers. Findings recorded in how-the-frame-works.md. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/how-the-frame-works.md | 1 + scripts/keep-awake.sh | 74 +++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100755 scripts/keep-awake.sh diff --git a/docs/how-the-frame-works.md b/docs/how-the-frame-works.md index 5c392d1..cdf2498 100644 --- a/docs/how-the-frame-works.md +++ b/docs/how-the-frame-works.md @@ -55,6 +55,7 @@ Lepton (Android 11, podman container "lepton-dev") ← its own panel, app 305600 | **Tools on the image:** Python 3.12.3, `ffmpeg`, `openssl`, `curl`, `rsync`, `zip`/`unzip`, `flatpak`, `wpctl`, `podman`. **No `adb`.** `steamos` is uid 1000, in `wheel`, and sudoers has `%wheel ALL=(ALL) ALL`, so `sudo -S` takes the Developer Mode password on stdin. **Verified 2026-09-27.** | Running Frame Control's server on the Frame (`FRAME_LOCAL=1`, [iphone.md](iphone.md)) | | **Each Lepton instance is a podman container** named `lepton-steamlaunch-`, labelled with its ADB port (`podman ps --format '{{.Names}} {{.Labels.adb_port}}'`). `podman exec /system/bin/sh -c '…'` runs Android's shell inside it with no adb at all (used for `pidof` and `logcat` by the app tester). Running `wm size`/`wm density` that way is untested. **Verified 2026-09-27.** | `ui/frame_android.py`, the iPhone app's display settings | | **Asleep means off the network.** In standby the Frame stops answering on its LAN address, `frame.local` and Tailscale alike (`Host is down`, `No route to host`, timeouts), and ping fails. It was unreachable for about 2.5 hours until woken. Nothing over SSH can wake it. **Verified 2026-09-27.** | Frame Control's offline banner and retries | +| **What puts it to sleep is Steam's idle timer**, not logind. The journal shows `steamui_system: Switching to power state: [ k_ESystemPowerState_Sleep ] reason: 'ComputeNextPowerState: active: 3600 < 3600 (k_EACState_Connected)'`, then Steam suspends. SSH work doesn't count as activity. The timers are the client settings `system_idle_suspend_ac_sec` (3600) and `system_idle_suspend_battery_sec` (900); 0 means Never (Settings → Power → Sleep after inactivity). They can be written over DevTools the way the settings page does. logind refuses a `systemd-inhibit --mode=block` sleep lock from an SSH session (`Interactive authentication required`) but accepts one started with `systemd-run --user`. `scripts/keep-awake.sh on|off|status` does both and restores the old timers on `off`. **Verified 2026-09-28**, BUILD_ID 20260925.6191901. Whether Steam's suspend honours the inhibitor on its own is **inferred** (polkit gives `steamos` no `suspend-ignore-inhibit`), not tested. | Keeping the Frame awake for agent work | | **Battery at full on a charger** can read `Discharging` at about 0 W (for example 99 %, 0.0 W, USB-C PD 18 W). Treat under 0.5 W on a charger as "not charging", not "draining". **Verified 2026-09-27.** | Frame Control's battery card | | **The OS image is downloadable.** Valve's recovery images for the Frame are at `https://steamdeck-images.steamos.cloud/recovery/`. The root filesystem inside is btrfs, and it runs as an SSH test target on ARM64 Linux without the headset (`tests/frame-container/frame-image.sh`). **Verified 2026-09-27.** | [recovery-and-images.md](recovery-and-images.md) | | **Boot / recovery menu.** Hold Power ~10 s until the LED goes off, then power on while holding the **AUX button on top of the Power button** (not the volume keys) until a text menu appears. Entries: `Current` (SteamOS-A/B + build), `Previous` (the other A/B slot), `Boot from USB`, `Repair Steam Installation`, `Erase User Data` (factory reset), `ADB mode`, `Battery Ship Mode`. It auto-boots `Current` after a ~15 s countdown. **Volume Up/Down (left side) move, AUX (right side) selects.** For a boot loop, Valve says pick `Previous` (keeps user data); then `Repair Steam Installation`; `Erase User Data` wipes `~` (SSH keys, Tailscale, Flatpaks, T3 setup). Last resort is a full re-image, two ways: (1) USB: write `steamframe-oobe-repair-.img.bz2` to an 8 GB+ USB-C stick (Balena Etcher on the Mac), pick `Boot from USB`, then use "Wipe Device & Install SteamOS" / "Repair SteamOS" (keeps games and personal content) from the recovery desktop; (2) cable/EDL: `steamframe-oobe-repair-qdl-.tar.gz`, run `flash.sh` (Linux) or `flash.cmd` (Windows), then with the Frame off for 10 s hold Power + Vol Up + Vol Down for 10 s and plug it in; it reflashes and reboots. Both images: `https://steamdeck-images.steamos.cloud/recovery/` (build 20260922.5153644, 0.3.0, 3.8 GiB each, no published checksums); local copies in `~/Downloads/steam-frame-recovery/`. File names, checksums and what's inside: [recovery-and-images.md](recovery-and-images.md). Source: Valve's [SteamOS Recovery FAQ](https://help.steampowered.com/en/faqs/view/1B71-EDF2-EB6D-2BB3) and [Installation and Repair FAQ](https://help.steampowered.com/en/faqs/view/65B4-2AA3-5F37-4227), plus a menu photo in [EloiStree/HelloSteamFrame#9](https://github.com/EloiStree/HelloSteamFrame/issues/9). **Inferred** (Valve docs, 2026-09-26); not yet tried on our Frame. | Recovering from a boot loop | diff --git a/scripts/keep-awake.sh b/scripts/keep-awake.sh new file mode 100755 index 0000000..c4a3301 --- /dev/null +++ b/scripts/keep-awake.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env zsh +# Mac-side: stop the Steam Frame from going to sleep while an agent works on it. +# +# The Frame sleeps when Steam's own idle timer runs out ("Sleep after +# inactivity": 60 min on AC, 15 min on battery by default). SSH activity +# doesn't count as input, and asleep the Frame is off the network. `on` sets +# both timers to Never through Steam's UI (DevTools on 127.0.0.1:8080, via +# ui/frame_steam.py) and holds a logind sleep inhibitor as a user unit. +# `off` drops the inhibitor and restores the timers `on` saved. +# +# Usage: +# scripts/keep-awake.sh on +# scripts/keep-awake.sh off +# scripts/keep-awake.sh status +set -euo pipefail + +FRAME_ALIAS=${FRAME_ALIAS:-frame} +HERE=${0:A:h} +cmd=${1:-status} +case $cmd in on|off|status) ;; *) echo "usage: keep-awake.sh on|off|status" >&2; exit 2 ;; esac + +ssh -o ConnectTimeout=8 "$FRAME_ALIAS" \ + 'mkdir -p ~/.cache/frame-control && cat > ~/.cache/frame-control/frame_steam.py' < "$HERE/../ui/frame_steam.py" + +# Runs on the Frame. Verified 2026-09-28 (BUILD_ID 20260925.6191901): the +# timers are client settings system_idle_suspend_{ac,battery}_sec (0 = Never), +# written the way Steam's settings page does (steamui module exporting the +# SetSetting wrapper). logind refuses an inhibitor from an SSH session +# ("Interactive authentication required") but allows one from a user unit. +ssh "$FRAME_ALIAS" python3 - "$cmd" <<'EOF' +import json, os, subprocess, sys +sys.path.insert(0, os.path.expanduser("~/.cache/frame-control")) +from frame_steam import Page + +cmd = sys.argv[1] +saved_path = os.path.expanduser("~/.cache/frame-control/keep-awake.json") +unit = "fc-keep-awake" +keys = ("system_idle_suspend_ac_sec", "system_idle_suspend_battery_sec") + +if cmd == "off": # release the lock first, even if Steam's UI is down + subprocess.run(["systemctl", "--user", "stop", unit], stderr=subprocess.DEVNULL) +page = Page() +def read(): + return {k: page.eval(f"settingsStore.clientSettings.{k}") for k in keys} +def write(values): + page.eval("""(async () => { let req; + webpackChunksteamui.push([[Symbol()], {}, r => { req = r }]); + const mod = Object.keys(req.m).map(id => req.m[id].toString().includes("Settings.SetSetting") ? req(id) : null).find(Boolean); + const set = Object.values(mod).find(f => typeof f == "function" && f.toString().includes("SetSetting(")); + for (const [k, v] of Object.entries(%s)) await set(k, v); + await new Promise(r => setTimeout(r, 1000)); })()""" % json.dumps(values)) +def inhibitor(): + return subprocess.run(["systemctl", "--user", "is-active", "-q", unit]).returncode == 0 + +if cmd == "on": + current = read() + if not os.path.exists(saved_path): + with open(saved_path, "w") as f: + json.dump(current, f) + write({k: 0 for k in keys}) + if not inhibitor(): + subprocess.run(["systemd-run", "--user", "-q", f"--unit={unit}", + "--description=Frame Control: keep the Frame awake", + "systemd-inhibit", "--what=sleep:idle:handle-suspend-key:handle-power-key", + "--who=Frame Control", "--why=Keep the Frame awake while an agent works on it", + "--mode=block", "sleep", "infinity"], check=True) +elif cmd == "off": + if os.path.exists(saved_path): # no backup: leave the timers as they are + with open(saved_path) as f: + write(json.load(f)) + os.remove(saved_path) + +print(json.dumps({"timers": read(), "inhibitor": inhibitor()})) +EOF From 9eeca79b5da23f5184be7818c42f2cdf58dcb9cb Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:34:21 +1000 Subject: [PATCH 10/36] Analytics, self-update and Report a problem - Anonymous PostHog analytics (ui/frame_telemetry.py): usage on by default after a first-run notice; compatibility results and error details opt-in, offered together by the notice's "Share more to help fix problems" button. Random id, no person profiles or GeoIP, scrubbed text, an offline outbox, and "Show what's been sent" in the new Privacy panel. Inert without a project key, from a source checkout, or with DO_NOT_TRACK=1. - APK installs now record install_failed when the APK itself won't install, and offer a 20-second test after installing. Opted-in reports reach the shared database through PostHog and `frame_compat_db.py sync`. - The desktop app updates itself from published releases (app/updater.js): update.json from releases/latest/download, SHA-256 checked, no downgrades; macOS bundle swap, Windows NSIS, Linux AppImage, otherwise the release page. scripts/publish-release.sh publishes a tested draft with its manifest. - Report a problem (header button, Privacy panel, Help menu) files a GitHub issue through the website's feedback API, with a previewed, scrubbed diagnostics snapshot; activity and logs only when asked for. Reviewed by GPT-6 Astra (xhigh, read-only) three times; all findings fixed. Docs: docs/privacy.md, docs/releasing.md. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/checks.yml | 4 +- README.md | 15 +- app/main.js | 100 ++++++- app/package-lock.json | 4 +- app/package.json | 6 +- app/preload.js | 16 ++ app/test/updater.test.js | 59 ++++ app/updater.js | 250 +++++++++++++++++ compat-db/README.md | 29 +- docs/frame-control.md | 13 +- docs/privacy.md | 133 +++++++++ docs/releasing.md | 59 ++++ scripts/publish-release.sh | 45 +++ tests/test_telemetry.py | 405 +++++++++++++++++++++++++++ ui/frame_android.py | 38 ++- ui/frame_compat_db.py | 150 +++++++++- ui/frame_report.py | 147 ++++++++++ ui/frame_telemetry.py | 522 +++++++++++++++++++++++++++++++++++ ui/index.html | 270 +++++++++++++++++- ui/server.py | 103 ++++++- ui/telemetry.json | 5 + 21 files changed, 2326 insertions(+), 47 deletions(-) create mode 100644 app/test/updater.test.js create mode 100644 app/updater.js create mode 100644 docs/privacy.md create mode 100644 docs/releasing.md create mode 100755 scripts/publish-release.sh create mode 100644 tests/test_telemetry.py create mode 100644 ui/frame_report.py create mode 100644 ui/frame_telemetry.py create mode 100644 ui/telemetry.json diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 9239b5d..b0d46ca 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -34,7 +34,9 @@ jobs: - name: Server tests run: python -m unittest discover -s tests -v - name: App syntax - run: node --check app/main.js && node --check app/build/make-icon.js && node --check app/build/fetch-deps.js && node --check app/preload.js && node --check app/install-link.js + run: node --check app/main.js && node --check app/build/make-icon.js && node --check app/build/fetch-deps.js && node --check app/preload.js && node --check app/install-link.js && node --check app/updater.js + - name: Updater tests + run: node --test app/test/updater.test.js - name: Website run: node --test site/test/*.test.mjs && node --check site/public/js/site.js && node --check site/public/js/feedback.js diff --git a/README.md b/README.md index 75167b0..2acc3df 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,10 @@ already ships (sideloading a game copies Valve's own devkit scripts to | **Linux** (arm64) | [AppImage](https://github.com/saphid/steam-frame/releases/latest/download/Frame-Control-linux-arm64.AppImage) · [.deb](https://github.com/saphid/steam-frame/releases/latest/download/Frame-Control-linux-arm64.deb) | `ssh`, and `adb` for Android apps (`sudo apt install adb`) | The app brings its own Python and `adb`; SSH is built into macOS and Windows. +From 0.4 it updates itself: when a new version is published, a banner offers +**Update and restart**. It sends anonymous usage statistics, which you can turn +off. Sharing compatibility results and error details is opt-in. See +[docs/privacy.md](docs/privacy.md). Google doesn't publish `adb` for arm64 Linux, so that build uses your distribution's. If you already have `adb`, the app uses yours. @@ -172,9 +176,11 @@ entry to `~/.ssh/config` and keys at `~/.ssh/id_ed25519_frame` and ## Feedback This is a first public test, so reports are really useful, especially from -Windows and Linux. The quickest way is the -[feedback form](https://frame-control.pages.dev/feedback/): no GitHub account -needed, and it opens an issue here. Please include: +Windows and Linux. The quickest way is **Report a problem** in the app (the +warning-sign button at the top, or **Help → Report a Problem…**). It adds +diagnostics with personal details removed, shows you exactly what's included, +and opens an issue here. You don't need a GitHub account. Without the app, +use the [feedback form](https://frame-control.pages.dev/feedback/). Please include: - what you tried and what happened - your computer's OS and your SteamOS build (Steam Settings → System) @@ -230,7 +236,8 @@ cd app && npm install && npm start # run the app from the checkout The server is Python stdlib only; the app is Electron. GitHub Actions runs the tests on macOS, Windows and Linux, and a `v*` tag builds all three installers -into the release. See [building](docs/frame-control.md#building). +into a draft release, which reaches users once published. See +[building](docs/frame-control.md#building) and [releasing](docs/releasing.md). ## License diff --git a/app/main.js b/app/main.js index ad64d8f..f3463b8 100644 --- a/app/main.js +++ b/app/main.js @@ -10,6 +10,7 @@ const net = require("net"); const os = require("os"); const path = require("path"); const { SCHEME, parseInstallLink, linkFromArgv } = require("./install-link"); +const updater = require("./updater"); const run = promisify(execFile); @@ -114,7 +115,11 @@ function ping(target) { } async function startServer() { + // The version and whether this is a built app go to ui/frame_telemetry.py, which + // sends nothing from a source checkout. const env = { ...process.env, PATH: await loginPath(), FRAME_CONTROL_APP: "1", + FRAME_CONTROL_VERSION: app.getVersion(), FRAME_CONTROL_LOG: LOG, + ...(app.isPackaged ? { FRAME_CONTROL_PACKAGED: "1" } : {}), ...(fs.existsSync(TOOLS) ? { FRAME_CONTROL_TOOLS: TOOLS } : {}) }; python = await findPython(env); if (!python) throw new Error(`Frame Control needs Python 3.8 or later. ${PYTHON_HELP}`); @@ -243,6 +248,9 @@ function fromUi(e) { } ipcMain.handle("clipboard:read", (e) => fromUi(e) ? clipboard.readText() : ""); +ipcMain.handle("update:get", (e) => fromUi(e) ? publicUpdate() : null); +ipcMain.handle("update:check", (e) => fromUi(e) ? checkForUpdate({ manual: true }).then(publicUpdate) : null); +ipcMain.handle("update:install", (e) => { if (fromUi(e)) installUpdate(); }); // frame-control://install links from websites (docs/web-install.md). They can // arrive before the window or server exists (macOS open-url on a cold launch), @@ -275,6 +283,81 @@ ipcMain.on("install-link:ready", (e) => { deliverLinks(); }); +// ---- updates (app/updater.js, docs/releasing.md) ---- +// Checked shortly after launch and every few hours; the page shows a banner and +// the Update button calls installUpdate. +const UPDATE_EVERY = 6 * 3600 * 1000; +const update = { status: "idle", current: app.getVersion(), latest: null, error: null, progress: 0, how: null }; + +function publicUpdate() { + const r = update.latest; + return { status: update.status, current: update.current, error: update.error, progress: update.progress, + latest: r && { version: r.version, notes: r.notes, page: r.page }, + canInstall: !!update.how && update.how.method !== "manual", why: update.how && update.how.why }; +} + +function setUpdate(fields) { + Object.assign(update, fields); + if (win && linkPage === win.webContents) win.webContents.send("update:state", publicUpdate()); +} + +async function checkForUpdate({ manual = false } = {}) { + if (["checking", "downloading", "ready"].includes(update.status)) return; + setUpdate({ status: "checking", error: null }); + try { + const latest = await updater.latestRelease(); + const how = updater.updateMethod({ platform: process.platform, isPackaged: app.isPackaged, + execPath: process.execPath, env: process.env, + exists: fs.existsSync, writable: updater.writable }); + if (updater.isNewer(latest.version, update.current)) { + setUpdate({ status: "available", latest, how }); + if (manual) offerUpdateDialog(); + } else { + setUpdate({ status: "none", latest, how }); + if (manual) dialog.showMessageBox(win, { type: "info", message: "Frame Control is up to date", + detail: `You have ${update.current}, the newest version.` }); + } + } catch (e) { + // A failed check: nothing to install, and never an older release kept from before. + setUpdate({ status: "check-failed", error: e.message, latest: null }); + if (manual) dialog.showMessageBox(win, { type: "warning", message: "Couldn't check for updates", detail: e.message }); + } +} + +async function offerUpdateDialog() { + const r = update.latest; + const { response } = await dialog.showMessageBox(win, { + type: "info", message: `Frame Control ${r.version} is available`, + detail: `You have ${update.current}.` + (update.how.method === "manual" ? ` Download it from the release page (${update.how.why}).` : ""), + buttons: [update.how.method === "manual" ? "Open Release Page" : "Update and Restart", "Later"], defaultId: 0, cancelId: 1, + }); + if (response === 0) installUpdate(); +} + +async function installUpdate() { + // "error" here only ever means an install failed, so trying again is safe. + if (update.status !== "available" && update.status !== "error") return; + if (!update.latest || !updater.isNewer(update.latest.version, update.current)) return; + if (!update.how || update.how.method === "manual") { shell.openExternal(update.latest.page); return; } + setUpdate({ status: "downloading", progress: 0, error: null }); + try { + const start = await updater.prepare(update.latest, update.how, + (done, total) => { if (total) setUpdate({ progress: done / total }); }, update.current); + setUpdate({ status: "ready", progress: 1 }); + start(); + quitting = true; + app.quit(); + } catch (e) { + setUpdate({ status: "error", error: e.message }); + } +} + +function scheduleUpdateChecks() { + if (process.env.FRAME_CONTROL_NO_UPDATE_CHECK === "1") return; + setTimeout(checkForUpdate, 8000); + setInterval(checkForUpdate, UPDATE_EVERY).unref(); +} + function registerScheme() { // A checkout runs as `electron .`, so the OS must be told the script too. // (macOS takes the scheme from Info.plist, which only the built app has.) @@ -336,7 +419,11 @@ async function setUpConnection() { function buildMenu() { const template = [ - ...(IS_MAC ? [{ role: "appMenu" }] : []), + ...(IS_MAC ? [{ label: app.name, submenu: [ + { role: "about" }, { label: "Check for Updates…", click: () => checkForUpdate({ manual: true }) }, + { type: "separator" }, { role: "services" }, { type: "separator" }, + { role: "hide" }, { role: "hideOthers" }, { role: "unhide" }, { type: "separator" }, { role: "quit" }, + ] }] : []), { role: "fileMenu" }, { role: "editMenu" }, { @@ -363,7 +450,15 @@ function buildMenu() { ...(IS_MAC ? [{ role: "windowMenu" }] : []), { role: "help", - submenu: [{ label: "Project on GitHub", click: () => shell.openExternal("https://github.com/saphid/steam-frame") }], + submenu: [ + ...(IS_MAC ? [] : [{ label: "Check for Updates…", click: () => checkForUpdate({ manual: true }) }]), + { label: "Report a Problem…", click: () => { + if (win && url && linkPage === win.webContents) win.webContents.send("report:open"); + else shell.openExternal("https://frame-control.pages.dev/feedback/"); // the page isn't up + } }, + { label: "Release Notes", click: () => shell.openExternal(updater.RELEASES) }, + { label: "Project on GitHub", click: () => shell.openExternal("https://github.com/saphid/steam-frame") }, + ], }, ]; Menu.setApplicationMenu(Menu.buildFromTemplate(template)); @@ -386,6 +481,7 @@ if (!app.requestSingleInstanceLock()) { registerScheme(); buildMenu(); createWindow(); + scheduleUpdateChecks(); }); app.on("activate", () => { if (!win) createWindow(); }); app.on("window-all-closed", () => app.quit()); diff --git a/app/package-lock.json b/app/package-lock.json index f8f2d77..264b1dd 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1,12 +1,12 @@ { "name": "frame-control", - "version": "0.3.1", + "version": "0.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "frame-control", - "version": "0.3.1", + "version": "0.4.0", "license": "MIT", "devDependencies": { "electron": "^44.4.5", diff --git a/app/package.json b/app/package.json index bd5701a..78f6989 100644 --- a/app/package.json +++ b/app/package.json @@ -1,7 +1,7 @@ { "name": "frame-control", "productName": "Frame Control", - "version": "0.3.1", + "version": "0.4.0", "description": "Desktop app for managing a Valve Steam Frame over SSH", "private": true, "main": "main.js", @@ -37,6 +37,7 @@ "main.js", "preload.js", "install-link.js", + "updater.js", "package.json", "build/icon.png" ], @@ -46,7 +47,8 @@ "to": "ui", "filter": [ "*.py", - "*.html" + "*.html", + "telemetry.json" ] }, { diff --git a/app/preload.js b/app/preload.js index f21551d..aaf5290 100644 --- a/app/preload.js +++ b/app/preload.js @@ -4,11 +4,27 @@ // as a title without zipping it (the local server reads it from there). // It also receives frame-control://install links (docs/web-install.md): only // what the link asked for, never an install; the page asks the user first. +// And it passes update state both ways: see app/updater.js. const { contextBridge, ipcRenderer, webUtils } = require("electron"); contextBridge.exposeInMainWorld("frameApp", { readClipboard: () => ipcRenderer.invoke("clipboard:read"), pathForFile: (file) => { try { return webUtils.getPathForFile(file) || ""; } catch { return ""; } }, + // Updates (app/updater.js): the page shows a banner and an Update button. + update: { + get: () => ipcRenderer.invoke("update:get"), + check: () => ipcRenderer.invoke("update:check"), + install: () => ipcRenderer.invoke("update:install"), + onState: (cb) => { + ipcRenderer.removeAllListeners("update:state"); + ipcRenderer.on("update:state", (_e, s) => cb(s)); + }, + }, + // Help → Report a Problem… opens the page's report dialog (ui/frame_report.py). + onReportProblem: (cb) => { + ipcRenderer.removeAllListeners("report:open"); + ipcRenderer.on("report:open", () => cb()); + }, onInstallLink: (cb) => { ipcRenderer.removeAllListeners("install-link"); ipcRenderer.on("install-link", (_e, req) => cb({ kind: req.kind, target: req.target })); diff --git a/app/test/updater.test.js b/app/test/updater.test.js new file mode 100644 index 0000000..4bf9594 --- /dev/null +++ b/app/test/updater.test.js @@ -0,0 +1,59 @@ +// Run: node --test app/test/ +const test = require("node:test"); +const assert = require("node:assert"); +const { isNewer, assetName, updateMethod, macBundle } = require("../updater"); + +test("versions compare numerically, and a release beats its pre-releases", () => { + assert.ok(isNewer("0.3.10", "0.3.9")); + assert.ok(isNewer("v1.0.0", "0.9.9")); + assert.ok(!isNewer("0.3.1", "0.3.1")); + assert.ok(!isNewer("0.3.0", "0.3.1")); + assert.ok(isNewer("1.0.0", "1.0.0-beta.1")); + assert.ok(!isNewer("1.0.0-beta.1", "1.0.0")); + assert.ok(!isNewer("garbage", "0.1.0")); +}); + +test("asset names match what electron-builder publishes", () => { + assert.strictEqual(assetName("darwin", "arm64", "mac-zip"), "Frame-Control-mac-arm64.zip"); + assert.strictEqual(assetName("win32", "x64", "nsis"), "Frame-Control-Setup-x64.exe"); + assert.strictEqual(assetName("linux", "x64", "appimage"), "Frame-Control-linux-x86_64.AppImage"); + assert.strictEqual(assetName("linux", "arm64", "appimage"), "Frame-Control-linux-arm64.AppImage"); +}); + +const base = { isPackaged: true, env: {}, exists: () => false, writable: () => true }; + +test("macOS updates in place only from a writable, non-translocated location", () => { + const exe = "/Applications/Frame Control.app/Contents/MacOS/Frame Control"; + assert.strictEqual(macBundle(exe), "/Applications/Frame Control.app"); + assert.deepStrictEqual(updateMethod({ ...base, platform: "darwin", execPath: exe }), + { method: "mac-zip", bundle: "/Applications/Frame Control.app" }); + const dmg = "/Volumes/Frame Control 0.3.1/Frame Control.app/Contents/MacOS/Frame Control"; + assert.strictEqual(updateMethod({ ...base, platform: "darwin", execPath: dmg }).method, "manual"); + const trans = "/private/var/folders/x/AppTranslocation/ABC/d/Frame Control.app/Contents/MacOS/Frame Control"; + assert.strictEqual(updateMethod({ ...base, platform: "darwin", execPath: trans }).method, "manual"); + assert.strictEqual(updateMethod({ ...base, platform: "darwin", execPath: exe, writable: () => false }).method, "manual"); +}); + +test("Windows needs the installer's copy; Linux needs an AppImage", () => { + const exe = "C:\\Users\\a\\AppData\\Local\\Programs\\Frame Control\\Frame Control.exe"; + assert.strictEqual(updateMethod({ ...base, platform: "win32", execPath: exe, exists: () => true }).method, "nsis"); + assert.strictEqual(updateMethod({ ...base, platform: "win32", execPath: exe }).method, "manual"); + assert.strictEqual(updateMethod({ ...base, platform: "linux", execPath: "/opt/x", env: { APPIMAGE: "/home/a/F.AppImage" } }).method, + "appimage"); + assert.strictEqual(updateMethod({ ...base, platform: "linux", execPath: "/opt/Frame Control/frame-control" }).method, "manual"); + assert.strictEqual(updateMethod({ ...base, isPackaged: false, platform: "darwin", execPath: "x" }).method, "manual"); +}); + +test("update.json assets always download from this repository's release", () => { + const r = require("../updater").fromManifest({ version: "0.4.0", notes: "n", assets: [ + { name: "Frame-Control-mac-arm64.zip", url: "https://evil.example/x.zip", digest: "sha256:" + "a".repeat(64) }] }); + assert.strictEqual(r.assets[0].url, "https://github.com/saphid/frame-control/releases/download/v0.4.0/Frame-Control-mac-arm64.zip"); + assert.throws(() => require("../updater").fromManifest({ version: "nope", assets: [] })); +}); + +test("prepare refuses a release that isn't newer (no downgrades)", async () => { + const { prepare } = require("../updater"); + const release = { version: "0.3.1", assets: [] }; + await assert.rejects(prepare(release, { method: "appimage", appImage: "/nonexistent/x" }, null, "0.4.0"), /isn't newer/); + await assert.rejects(prepare(release, { method: "appimage", appImage: "/nonexistent/x" }, null, "0.3.1"), /isn't newer/); +}); diff --git a/app/updater.js b/app/updater.js new file mode 100644 index 0000000..85caab5 --- /dev/null +++ b/app/updater.js @@ -0,0 +1,250 @@ +// Update checks and self-update for the desktop app (docs/releasing.md). +// +// The newest version is GitHub's "latest" release of saphid/frame-control. Drafts +// and pre-releases never count, so a build reaches people only when the +// maintainer publishes it after testing (scripts/publish-release.sh). That +// script attaches update.json (version, notes, each asset's SHA-256), read +// through github.com's latest/download link: the REST API allows only 60 +// unauthenticated requests an hour per IP address, shared by everyone behind +// the same router, so it's only the fallback. +// +// Every download is checked against the SHA-256 digest GitHub records for the +// asset before anything is replaced. How the update is applied: +// macOS the .zip: unpacked next to the running app, swapped in by a small +// script once the app has quit, then reopened. +// Windows the NSIS installer, run silently over the current install; it +// reopens the app. A copy unpacked from the .zip is updated by hand. +// Linux the AppImage replaces itself; .deb installs are updated by hand. +// When the app can't update itself it opens the release page instead. +const { execFile, spawn } = require("child_process"); +const crypto = require("crypto"); +const fs = require("fs"); +const https = require("https"); +const os = require("os"); +const path = require("path"); + +const REPO = "saphid/frame-control"; // renamed from saphid/steam-frame; GitHub redirects the old name +const LATEST = `https://api.github.com/repos/${REPO}/releases/latest`; +const MANIFEST = `https://github.com/${REPO}/releases/latest/download/update.json`; +const RELEASES = `https://github.com/${REPO}/releases`; + +// "0.3.1" or "v0.3.1" -> [0, 3, 1]; pre-release suffixes sort before the release. +function parseVersion(v) { + const m = String(v || "").trim().replace(/^v/i, "").match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/); + return m ? { nums: [+m[1], +m[2], +m[3]], pre: m[4] || null } : null; +} + +function isNewer(candidate, current) { + const a = parseVersion(candidate), b = parseVersion(current); + if (!a || !b) return false; + for (let i = 0; i < 3; i++) if (a.nums[i] !== b.nums[i]) return a.nums[i] > b.nums[i]; + if (a.pre === b.pre) return false; + if (!a.pre) return true; // 1.0.0 is newer than 1.0.0-beta + if (!b.pre) return false; + return a.pre > b.pre; +} + +// The asset this copy of the app updates from, by the names electron-builder gives them. +function assetName(platform, arch, method) { + if (method === "mac-zip") return `Frame-Control-mac-${arch}.zip`; + if (method === "nsis") return `Frame-Control-Setup-${arch}.exe`; + if (method === "appimage") return `Frame-Control-linux-${arch === "x64" ? "x86_64" : arch}.AppImage`; + return null; +} + +// How this copy can update itself: mac-zip, nsis, appimage, or manual (with why). +function updateMethod({ platform, isPackaged, execPath, env, exists, writable }) { + if (!isPackaged) return { method: "manual", why: "running from a source checkout" }; + if (platform === "darwin") { + const bundle = macBundle(execPath); + if (!bundle) return { method: "manual", why: "can't find the app bundle" }; + if (bundle.includes("/AppTranslocation/") || bundle.startsWith("/Volumes/")) { + return { method: "manual", why: "move Frame Control to Applications first" }; + } + if (!writable(path.dirname(bundle))) return { method: "manual", why: `${path.dirname(bundle)} isn't writable` }; + return { method: "mac-zip", bundle }; + } + if (platform === "win32") { + // electron-builder's NSIS install puts its uninstaller next to the app. + const dir = path.dirname(execPath); + if (exists(path.join(dir, "Uninstall Frame Control.exe"))) return { method: "nsis" }; + return { method: "manual", why: "not installed with the installer" }; + } + if (platform === "linux" && env.APPIMAGE) { + if (!writable(path.dirname(env.APPIMAGE))) return { method: "manual", why: "the AppImage's folder isn't writable" }; + return { method: "appimage", appImage: env.APPIMAGE }; + } + return { method: "manual", why: "installed from a package" }; +} + +function macBundle(execPath) { + const i = execPath.indexOf(".app/Contents/MacOS/"); + return i < 0 ? null : execPath.slice(0, i + 4); +} + +function get(url, { headers = {}, timeout = 20000, redirects = 5 } = {}) { + return new Promise((resolve, reject) => { + const req = https.get(url, { headers: { "user-agent": "FrameControl-updater", ...headers }, timeout }, (res) => { + if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location && redirects > 0) { + res.resume(); + const next = new URL(res.headers.location, url); + if (next.protocol !== "https:") return reject(new Error("refusing a non-HTTPS redirect")); + return resolve(get(next.href, { headers, timeout, redirects: redirects - 1 })); + } + if (res.statusCode !== 200) { res.resume(); return reject(new Error(`HTTP ${res.statusCode} from ${new URL(url).host}`)); } + resolve(res); + }); + req.on("timeout", () => req.destroy(new Error("timed out"))); + req.on("error", reject); + }); +} + +async function getJson(url, headers) { + const res = await get(url, { headers }); + let body = ""; + for await (const chunk of res) body += chunk; + return JSON.parse(body); +} + +// update.json and the API's release both become { version, notes, page, assets }. +function fromManifest(m) { + if (!parseVersion(m.version) || !Array.isArray(m.assets)) throw new Error("update.json is malformed"); + const base = `https://github.com/${REPO}/releases/download/v${String(m.version).replace(/^v/i, "")}/`; + return { version: String(m.version).replace(/^v/i, ""), notes: String(m.notes || "").slice(0, 4000), + page: m.page || RELEASES, + // Assets always come from this repository's release, whatever the manifest says. + assets: m.assets.map((a) => ({ name: String(a.name), url: base + encodeURIComponent(String(a.name)), + size: a.size, digest: a.digest || null })) }; +} + +function fromApi(r) { + if (r.draft || r.prerelease) throw new Error("GitHub returned an unpublished release"); + return { version: String(r.tag_name || "").replace(/^v/i, ""), notes: String(r.body || "").slice(0, 4000), + page: r.html_url || RELEASES, + assets: (r.assets || []).map((a) => ({ name: a.name, url: a.browser_download_url, size: a.size, + digest: a.digest || null })) }; +} + +async function latestRelease() { + try { + return fromManifest(await getJson(MANIFEST)); + } catch (e) { + if (!/HTTP 404/.test(e.message)) throw e; // releases before update.json existed + } + return fromApi(await getJson(LATEST, { accept: "application/vnd.github+json" })); +} + +async function download(asset, dest, onProgress) { + const m = /^sha256:([0-9a-f]{64})$/.exec(asset.digest || ""); + if (!m) throw new Error(`GitHub has no SHA-256 for ${asset.name}, so it can't be checked`); + const res = await get(asset.url, { timeout: 60000 }); + const total = +res.headers["content-length"] || asset.size || 0; + const hash = crypto.createHash("sha256"); + // "wx": a new file only, never through an existing file or symlink at that path. + const out = fs.createWriteStream(dest, { mode: 0o755, flags: "wx" }); + let done = 0; + await new Promise((resolve, reject) => { + res.on("data", (chunk) => { hash.update(chunk); done += chunk.length; onProgress && onProgress(done, total); }); + res.on("error", reject); + out.on("error", reject); + out.on("finish", resolve); + res.pipe(out); + }); + if (hash.digest("hex") !== m[1]) { + fs.rmSync(dest, { force: true }); + throw new Error(`${asset.name} didn't match its SHA-256; nothing was changed`); + } +} + +const run = (cmd, args) => new Promise((resolve, reject) => + execFile(cmd, args, { timeout: 120000 }, (err, stdout, stderr) => err ? reject(new Error((stderr || err.message).trim())) : resolve(stdout))); + +// Waits for this process to exit, swaps the new bundle in (putting the old one +// back if that fails), and reopens the app. +const MAC_SWAP = `set -u +pid="$1"; app="$2"; new="$3"; stage="$4" +while kill -0 "$pid" 2>/dev/null; do sleep 0.2; done +old="$stage/old.app" +if mv "$app" "$old"; then + if mv "$new" "$app"; then rm -rf "$old"; else mv "$old" "$app"; fi +fi +xattr -dr com.apple.quarantine "$app" 2>/dev/null +rm -rf "$stage" +open "$app" +`; + +async function applyMac(release, bundle, onProgress) { + const asset = release.assets.find((a) => a.name === assetName("darwin", process.arch, "mac-zip")); + if (!asset) throw new Error(`the release has no ${assetName("darwin", process.arch, "mac-zip")}`); + // Staged beside the app, so the final move stays on one volume. + const stage = fs.mkdtempSync(path.join(path.dirname(bundle), ".frame-control-update-")); + try { + const zip = path.join(stage, asset.name); + await download(asset, zip, onProgress); + await run("/usr/bin/ditto", ["-x", "-k", zip, stage]); + fs.rmSync(zip, { force: true }); + const name = fs.readdirSync(stage).find((n) => n.endsWith(".app")); + if (!name) throw new Error("the download has no app in it"); + const fresh = path.join(stage, name); + const version = (await run("/usr/bin/plutil", ["-extract", "CFBundleShortVersionString", "raw", + path.join(fresh, "Contents", "Info.plist")])).trim(); + if (version !== release.version) throw new Error(`the download is version ${version}, not ${release.version}`); + const script = path.join(stage, "swap.sh"); + fs.writeFileSync(script, MAC_SWAP); + return () => spawn("/bin/sh", [script, String(process.pid), bundle, fresh, stage], + { detached: true, stdio: "ignore" }).unref(); + } catch (e) { + fs.rmSync(stage, { recursive: true, force: true }); + throw e; + } +} + +async function applyNsis(release, onProgress) { + const asset = release.assets.find((a) => a.name === assetName("win32", process.arch, "nsis")); + if (!asset) throw new Error(`the release has no ${assetName("win32", process.arch, "nsis")}`); + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "frame-control-update-")); + const exe = path.join(dir, asset.name); + await download(asset, exe, onProgress); + // /S: silent, into the existing install. --force-run: open the app afterwards. + return () => spawn(exe, ["--updated", "/S", "--force-run"], { detached: true, stdio: "ignore" }).unref(); +} + +async function applyAppImage(release, appImage, onProgress) { + const asset = release.assets.find((a) => a.name === assetName("linux", process.arch, "appimage")); + if (!asset) throw new Error(`the release has no ${assetName("linux", process.arch, "appimage")}`); + // A private folder beside the AppImage, so the final rename stays on one filesystem. + const stage = fs.mkdtempSync(path.join(path.dirname(appImage), ".frame-control-update-")); + try { + const next = path.join(stage, asset.name); + await download(asset, next, onProgress); + fs.chmodSync(next, 0o755); + fs.renameSync(next, appImage); // the running copy keeps its open file + } finally { + fs.rmSync(stage, { recursive: true, force: true }); + } + // Without FUSE the AppImage runs extracted (--appimage-extract-and-run, which isn't passed + // on to the app); a FUSE mount lives under /tmp/.mount_*. Keep the same mode on restart. + const extracted = process.env.APPIMAGE_EXTRACT_AND_RUN === "1" || !process.execPath.includes("/.mount_"); + const env = { ...process.env, APPIMAGE: appImage, ...(extracted ? { APPIMAGE_EXTRACT_AND_RUN: "1" } : {}) }; + // Started only once this process has exited, or the new copy would lose the single-instance lock. + return () => spawn("/bin/sh", ["-c", 'while kill -0 "$1" 2>/dev/null; do sleep 0.2; done; exec "$2"', + "sh", String(process.pid), appImage], { detached: true, stdio: "ignore", env }).unref(); +} + +// Downloads and prepares the update; returns a function that starts the swap, +// to be called just before the app quits. +async function prepare(release, how, onProgress, current) { + if (!isNewer(release.version, current)) throw new Error(`${release.version} isn't newer than ${current}`); + if (how.method === "mac-zip") return applyMac(release, how.bundle, onProgress); + if (how.method === "nsis") return applyNsis(release, onProgress); + if (how.method === "appimage") return applyAppImage(release, how.appImage, onProgress); + throw new Error(how.why || "this copy can't update itself"); +} + +function writable(dir) { + try { fs.accessSync(dir, fs.constants.W_OK); return true; } catch { return false; } +} + +module.exports = { REPO, RELEASES, parseVersion, isNewer, assetName, updateMethod, macBundle, latestRelease, + fromManifest, fromApi, + download, prepare, writable }; diff --git a/compat-db/README.md b/compat-db/README.md index 5c81211..d851728 100644 --- a/compat-db/README.md +++ b/compat-db/README.md @@ -1,9 +1,32 @@ # compat-db: Frame Control's compatibility database A private [Lakebed](https://docs.lakebed.dev/) capsule holding compatibility -reports for Android apps on the Steam Frame. For now only the maintainer's -copy of Frame Control has the key to read or write it. Everyone else's reports -stay on their own Mac (see `shared()` in `ui/frame_compat_db.py`). +reports for Android apps on the Steam Frame. Only the maintainer's copy of +Frame Control has the key to read or write it (see `shared()` in +`ui/frame_compat_db.py`). Everyone else's reports stay on their computer +unless they turn on **Share compatibility results**. Then the reports also go +to PostHog as `compat_report` events, and the maintainer syncs them in (below). + +## Community reports + +```sh +python3 ui/frame_compat_db.py sync --dry-run # what would be added +python3 ui/frame_compat_db.py sync # add them +``` + +`sync` reads `compat_report` events through PostHog's query API and adds +them with `via` set to `community`, `community-probe` or `community-install`. +It skips invalid reports and anything over 30 per reporter per day. Each run +re-reads the last 30 days, because an offline copy sends its reports late, +with the time they were made. `posthog-sync.json`, next to the outbox, +remembers which reports it has handled and each reporter's daily count, so +nothing is added twice and the cap holds across runs. + +It needs: + +- the PostHog project id: `"project"` in `ui/telemetry.json` +- a personal API key with `query:read`: `POSTHOG_PERSONAL_API_KEY`, or in the + Keychain (service `frame-control-posthog`, account `personal-api-key`) - Live: `https://frame-compat.lakebed.app` (deploy `dep_dDmcsosVSiFirpW6`, claimed, so it doesn't expire). The browser page only says it's private. diff --git a/docs/frame-control.md b/docs/frame-control.md index 0dfb4ae..34796d9 100644 --- a/docs/frame-control.md +++ b/docs/frame-control.md @@ -47,9 +47,11 @@ python3 ui/server.py # anywhere: then open http://127.0.0.1:47810 Steam library), then launch, stop, test or remove it. **Report an APK** records whether any APK worked (F-Droid or not: pick a file, type a package, or use an installed app). Your reports are saved on your computer and change the verdicts - you see. They aren't uploaded anywhere: the shared database is maintainer-only - for now (see [compat-db/README.md](../compat-db/README.md)). Uses the app's bundled - `adb`, or yours if you have one. + you see. With **Share compatibility results** on (Privacy & updates), they also + go to the shared database ([privacy.md](privacy.md), + [compat-db/README.md](../compat-db/README.md)). A failed install records + itself when the APK was the problem, and after an install the app offers a + 20-second test. Uses the app's bundled `adb`, or yours if you have one. - **Android display**: pick a running Lepton instance (by the app in it) and set its resolution (Native 1920×1080, or Sharp 2560×1440 with density scaled to match), UI scale (Smaller / Default / Larger, or an exact dpi) and text size @@ -139,5 +141,6 @@ npm run dist:win # Windows: installer and .zip npm run dist:linux # Linux: AppImage and .deb, x64 and arm64 ``` -Pushing a `v*` tag builds all three in GitHub Actions and attaches them to the -release (`.github/workflows/release.yml`). +Pushing a `v*` tag builds all three in GitHub Actions and attaches them to a +draft release (`.github/workflows/release.yml`). Running copies are offered it +once you publish it: see [releasing.md](releasing.md). diff --git a/docs/privacy.md b/docs/privacy.md new file mode 100644 index 0000000..af44eae --- /dev/null +++ b/docs/privacy.md @@ -0,0 +1,133 @@ +# Privacy and analytics + +Frame Control sends anonymous analytics to [PostHog](https://posthog.com) +(EU cloud) so the maintainer can see how many people use it, which features +matter and where installs fail. You choose how much in **Privacy & updates**, +the last panel on the page. `ui/frame_telemetry.py` is the whole +implementation. + +## The three levels + +| Level | Default | What it sends | +|---|---|---| +| Anonymous usage statistics | On, after a notice on first run | The events in the table below | +| Share compatibility results | Off | Your Android compatibility reports and tests | +| Send error details | Off | Scrubbed error messages and tracebacks | + +Nothing is sent until the first-run notice has been shown. The notice's +**Share more to help fix problems** button turns on the second and third +levels together. Either can be turned off later. Turning a level off +deletes that level's events that haven't been sent yet. + +**Show what's been sent** in the panel lists the last 50 events that left your +computer, exactly as they were sent. + +## Anonymous + +- Events carry a random id, made when Frame Control first runs and kept in + its data folder (`telemetry/settings.json`). It isn't derived from your + computer, account or network. To get a new one, delete that file. +- Events are sent without person profiles (`$process_person_profile: false`) + and without location lookup (`$geoip_disable: true`). The PostHog project + is also set to discard client IP addresses. +- Every event includes the app version, OS name (macOS, Windows or Linux), + CPU architecture and Python version. + +## Usage events + +| Event | When | Properties besides the common ones | +|---|---|---| +| `app_installed` | First run | | +| `app_updated` | First run of a new version | `from_version` | +| `app_opened` | At most once a day | | +| `frame_connected` | The first time a SteamOS build is seen | `steamos_build`, `steamos_version` | +| `tab_viewed` | The first click on each tab in a session | `tab` | +| `install_finished` | Any install finishes, working or not | `kind` (apk, flatpak, steam, title, web), `ok`, `seconds`, `error_category`, `installer_code`, and see below | +| `update_offered`, `update_started`, `update_failed` | The update banner | `to_version`, `error_category` | + +`install_finished` never includes a file name, path or error message. An +error becomes one category from a fixed list (for example `apk_wrong_abi` or +`frame_unreachable`), plus Android's own `INSTALL_FAILED_…` code when there +is one. It names what was installed only when that's already public: + +- F-Droid catalogue apps: `package`. Never the version, since a local build can reuse a + catalogue app's package name +- Flathub apps: `flatpak_id` +- Steam games: `steam_appid` +- A sideloaded title: only its runtime (Proton or Linux) + +Any other APK is sent as `catalog: false`, with no name. + +## Compatibility results (opt-in) + +Each report becomes a `compat_report` event with the fields the Report dialog +shows: package, version, result or rating, your notes, how it was run, and the +SteamOS and Lepton builds. Before sending: + +- the notes, app name and version are scrubbed like error messages (see + below) +- the APK's source is kept only if it's `F-Droid` or the public host name of + a download link (`https://example.com/…`). File names, user names, + passwords, ports, paths, IP addresses and local host names are dropped + +When you turn this on, reports you made earlier on this computer are shared +too. + +The maintainer's `python3 ui/frame_compat_db.py sync` copies these events +into the compatibility database, marked `via=community…`. It takes at most +30 per reporter per day. + +## Error details (opt-in) + +`$exception` events carry an error message, the Frame Control file, line and +function it came from, and the request that failed (for example +`POST /api/android install`). Before anything is sent, the message is +scrubbed: + +- your home folder becomes `~`, and any user name becomes `` +- IP and MAC addresses, email addresses, `.local`, `.lan` and Tailscale host + names, Steam ids, SSH and PEM keys, API tokens and long hex strings are + replaced +- URLs are cut down to their scheme and a public host name, or ``. User + names, passwords, ports, paths and queries are dropped +- `token=`, `key=`, `password=` and similar values are replaced + +The same error is sent at most once every 10 minutes. + +## Report a problem + +**Report a problem** is the warning-sign button in the header, also in the +Privacy panel and under **Help → Report a Problem…**. It files a public issue +on [GitHub](https://github.com/saphid/frame-control/issues) through the +website's feedback service, so no GitHub account is needed. It works whatever +the analytics settings are, because the person sends it deliberately. + +With **Include diagnostics** ticked (the default), the report adds: + +- the app version and whether it's a built app +- the OS, its release and CPU, and the Python version +- the Frame's SteamOS build, if it has connected since the app started +- which analytics levels are on + +**Also include recent activity and the server log** is off by default, +because those lines can name files and apps. When ticked, it adds the newest +Activity lines and server log lines, without the request lines. + +Everything is scrubbed like error details and limited to what fits in the +issue. Environment details are kept first, then the newest lines. **Show +exactly what's included** shows the snapshot that will be sent, and later +activity isn't added to it. If the service can't be reached, the dialog offers +**Copy report** and **Open on GitHub instead**, a prefilled issue. + +## Turning it all off + +Untick the boxes, or set `DO_NOT_TRACK=1` or `FRAME_CONTROL_TELEMETRY=0` in +the environment that starts Frame Control. A copy run from a source checkout +never sends anything unless `FRAME_CONTROL_TELEMETRY=1` is set. + +## Update checks + +The desktop app asks GitHub for the latest release shortly after starting, +then every 6 hours (`api.github.com/repos/saphid/frame-control/releases/latest`). +That request carries no id. To stop it, set +`FRAME_CONTROL_NO_UPDATE_CHECK=1`. See [releasing.md](releasing.md). diff --git a/docs/releasing.md b/docs/releasing.md new file mode 100644 index 0000000..7db5e3a --- /dev/null +++ b/docs/releasing.md @@ -0,0 +1,59 @@ +# Releasing and updates + +Frame Control checks for updates itself. The desktop app offers a new version +only once it's GitHub's **latest release**, and drafts and pre-releases never +count. So a build reaches people only when you publish it, after testing it. + +## Steps + +1. Bump `version` in `app/package.json`, commit, and push a tag: + + ```sh + git tag v0.4.0 && git push origin v0.4.0 + ``` + + `.github/workflows/release.yml` builds macOS, Windows and Linux, and + attaches everything to a **draft** release for that tag. Nobody is + offered a draft. + +2. Download the draft's installers and test them. An installed copy of the + previous version won't offer the draft, so install it directly. + +3. Write the release notes on the draft. The update banner links to them. + +4. Publish: + + ```sh + scripts/publish-release.sh v0.4.0 + ``` + + The script checks that all eight installers are attached, each with the + SHA-256 digest GitHub records. It attaches `update.json` (the version, the + notes and each installer's digest), then publishes the release and marks it + latest. From then on, running copies see the update. They check about 8 + seconds after starting, then every 6 hours, and anyone can use **Check for + Updates…** (the app menu on macOS, the Help menu elsewhere). + +To pull a bad release, mark the previous one as latest +(`gh release edit v0.3.9 --latest`) or turn the bad one back into a draft. +Copies that already updated stay on it. Nothing downgrades them. + +## How a copy updates itself + +`app/updater.js` reads `update.json` from +`github.com/saphid/frame-control/releases/latest/download/`. It falls back to the +REST API only when a release has no manifest, because the API allows just 60 +unauthenticated requests an hour per IP address, shared by a whole household. +Then it downloads the installer for its platform and checks it +against the SHA-256 digest GitHub publishes for the asset. It refuses if the +digest is missing or doesn't match. Then: + +| Installed from | Update | +|---|---| +| macOS `.dmg`, app in a writable folder such as Applications | The `.zip` is unpacked next to the app and its version checked. After the app quits, a small script swaps the new app in, putting the old one back if that fails, and reopens it. Updates don't get the download quarantine, so there's no `xattr` step. | +| Windows installer | The new `Setup` runs silently over the install (`/S --force-run`) and reopens the app. | +| Linux AppImage | The new AppImage replaces the old file and is started. | +| macOS app still on the disk image or translocated, Windows `.zip`, Linux `.deb` | The banner opens the release page instead. | + +Version 0.3.1 and earlier have no updater, so people on them have to download +the new version once by hand. diff --git a/scripts/publish-release.sh b/scripts/publish-release.sh new file mode 100755 index 0000000..82b5c10 --- /dev/null +++ b/scripts/publish-release.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# Publish a tested draft release so running copies of Frame Control offer it +# (docs/releasing.md). Checks every installer is attached with a SHA-256 +# digest first, since the app's updater refuses assets without one, then +# attaches update.json, the manifest the updater reads. +# Usage: scripts/publish-release.sh v0.4.0 +set -eu +tag="${1:?usage: $0 vX.Y.Z}" +repo=saphid/frame-control +expected="Frame-Control-mac-arm64.dmg Frame-Control-mac-arm64.zip Frame-Control-Setup-x64.exe +Frame-Control-win-x64.zip Frame-Control-linux-x86_64.AppImage Frame-Control-linux-arm64.AppImage +Frame-Control-linux-amd64.deb Frame-Control-linux-arm64.deb" + +info=$(gh release view "$tag" -R "$repo" --json isDraft,isPrerelease,assets) +version=$(sed -n 's/.*"version": *"\([^"]*\)".*/\1/p' "$(dirname "$0")/../app/package.json") +[ "v$version" = "$tag" ] || echo "note: app/package.json here says $version (the release was built from the tag)" + +missing="" +for name in $expected; do + digest=$(printf '%s' "$info" | python3 -c 'import json,sys +d=json.load(sys.stdin); n=sys.argv[1] +print(next((a.get("digest") or "" for a in d["assets"] if a["name"]==n), "absent"))' "$name") + case "$digest" in + sha256:*) echo "ok $name" ;; + absent) echo "MISSING $name"; missing=1 ;; + *) echo "NO HASH $name"; missing=1 ;; + esac +done +[ -z "$missing" ] || { echo "not publishing: fix the assets above" >&2; exit 1; } + +# update.json: what running copies read (app/updater.js), from github.com's +# latest/download link rather than the rate-limited REST API. +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT +gh release view "$tag" -R "$repo" --json tagName,url,body,assets | python3 -c 'import json,sys +d=json.load(sys.stdin) +names=set(sys.argv[1].split()) +print(json.dumps({"version": d["tagName"].lstrip("v"), "page": d["url"], "notes": d["body"][:4000], + "assets": [{"name": a["name"], "size": a["size"], "digest": a["digest"]} + for a in d["assets"] if a["name"] in names]}, indent=1))' "$expected" > "$tmp/update.json" +gh release upload "$tag" -R "$repo" "$tmp/update.json" --clobber +echo "ok update.json" + +gh release edit "$tag" -R "$repo" --draft=false --prerelease=false --latest +echo "published $tag; running copies will offer it at their next check" diff --git a/tests/test_telemetry.py b/tests/test_telemetry.py new file mode 100644 index 0000000..19043bc --- /dev/null +++ b/tests/test_telemetry.py @@ -0,0 +1,405 @@ +"""Anonymous analytics (ui/frame_telemetry.py): what's collected at each level, +what's scrubbed, and that nothing is sent without a key, the notice, or consent. + +Run: python3 -m unittest discover -s tests +""" +import json +import os +import sys +import tempfile +import threading +import time +import unittest +from http.server import BaseHTTPRequestHandler, HTTPServer +from pathlib import Path +from unittest import mock + +ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(ROOT / "ui")) + +import frame_compat_db as db # noqa: E402 +import frame_report as fr # noqa: E402 +import frame_telemetry as tm # noqa: E402 + + +class Base(unittest.TestCase): + """A packaged build with a key, its state in a temp folder.""" + + def setUp(self): + tmp = tempfile.TemporaryDirectory() + self.addCleanup(tmp.cleanup) + state = Path(tmp.name) + for name, value in (("STATE", state), ("SETTINGS", state / "settings.json"), + ("OUTBOX", state / "outbox.jsonl"), ("SENT", state / "sent.jsonl")): + p = mock.patch.object(tm, name, value) + p.start() + self.addCleanup(p.stop) + env = mock.patch.dict(os.environ, {"FRAME_CONTROL_POSTHOG_KEY": "phc_test", "FRAME_CONTROL_PACKAGED": "1", + "FRAME_CONTROL_POSTHOG_HOST": "http://127.0.0.1:9", + "FRAME_CONTROL_VERSION": "9.9.9"}) + env.start() + self.addCleanup(env.stop) + for k in ("DO_NOT_TRACK", "FRAME_CONTROL_TELEMETRY"): + os.environ.pop(k, None) + tm._seen_errors.clear() + + def queued(self): + return tm._read_lines(tm.OUTBOX) + + +class Gates(Base): + def test_blocked_without_key_or_in_a_checkout_or_by_do_not_track(self): + self.assertIsNone(tm.blocked()) + with mock.patch.dict(os.environ, {"FRAME_CONTROL_POSTHOG_KEY": ""}), \ + mock.patch.object(tm, "HERE", Path(tempfile.gettempdir()) / "no-config-here"): + self.assertIn("key", tm.blocked()) + with mock.patch.dict(os.environ, {"FRAME_CONTROL_PACKAGED": ""}): + self.assertIn("source checkout", tm.blocked()) + with mock.patch.dict(os.environ, {"DO_NOT_TRACK": "1"}): + self.assertIn("DO_NOT_TRACK", tm.blocked()) + self.assertFalse(tm.capture("app_opened")) + self.assertFalse(tm.OUTBOX.exists()) + + def test_usage_is_on_by_default_the_others_are_opt_in(self): + self.assertTrue(tm.capture("app_opened")) + self.assertFalse(tm.capture("compat_report", {}, level="compat")) + self.assertFalse(tm.capture("$exception", {}, level="diagnostics")) + self.assertEqual([e["event"] for e in self.queued()], ["app_opened"]) + + def test_events_are_anonymous(self): + tm.capture("app_opened") + e = self.queued()[0] + self.assertEqual(e["distinct_id"], tm.settings()["id"]) + self.assertIs(e["properties"]["$process_person_profile"], False) + self.assertIs(e["properties"]["$geoip_disable"], True) + self.assertEqual(e["properties"]["app_version"], "9.9.9") + + def test_turning_a_level_off_drops_its_unsent_events(self): + tm.update_settings({"diagnostics": True}) + tm.capture("app_opened") + tm.diagnostic("somewhere", RuntimeError("boom")) + self.assertEqual(len(self.queued()), 2) + tm.update_settings({"diagnostics": False}) + self.assertEqual([e["event"] for e in self.queued()], ["app_opened"]) + tm.update_settings({"usage": False}) + self.assertEqual(self.queued(), []) + self.assertFalse(tm.capture("app_opened")) + + def test_the_same_error_is_sent_once_in_a_while(self): + tm.update_settings({"diagnostics": True}) + for _ in range(3): + tm.diagnostic("POST /api/android install", RuntimeError("boom")) + self.assertEqual(len(self.queued()), 1) + + def test_page_events_are_checked(self): + self.assertTrue(tm.page_event({"event": "tab_viewed", "properties": {"tab": "android", "extra": "x"}})["queued"]) + self.assertEqual(self.queued()[0]["properties"].get("extra"), None) + with self.assertRaises(ValueError): + tm.page_event({"event": "anything_else"}) + with self.assertRaises(ValueError): + tm.page_event({"event": "tab_viewed", "properties": {"tab": "/Users/me/secret"}}) + + +class Lifecycle(Base): + def test_install_update_and_one_open_a_day(self): + tm.app_started() + tm.app_started() + self.assertEqual([e["event"] for e in self.queued()], ["app_installed", "app_opened"]) + with mock.patch.dict(os.environ, {"FRAME_CONTROL_VERSION": "10.0.0"}): + tm.app_started() + e = self.queued()[-1] + self.assertEqual((e["event"], e["properties"]["from_version"]), ("app_updated", "9.9.9")) + + def test_frame_build_once(self): + tm.frame_seen("20260922.1", "3.8") + tm.frame_seen("20260922.1", "3.8") + self.assertEqual(len(self.queued()), 1) + + +class Sending(Base): + def serve(self, status=200): + got = [] + + class H(BaseHTTPRequestHandler): + def do_POST(self): + got.append((self.path, json.loads(self.rfile.read(int(self.headers["Content-Length"]))))) + self.send_response(status) + self.end_headers() + self.wfile.write(b'{"status": 1}') + + def log_message(self, *a): + pass + + httpd = HTTPServer(("127.0.0.1", 0), H) + threading.Thread(target=httpd.serve_forever, daemon=True).start() + self.addCleanup(httpd.server_close) + self.addCleanup(httpd.shutdown) + os.environ["FRAME_CONTROL_POSTHOG_HOST"] = f"http://127.0.0.1:{httpd.server_port}" + return got + + def test_nothing_is_sent_before_the_notice_was_shown(self): + got = self.serve() + tm.capture("app_opened") + self.assertEqual(tm.flush(), 0) + self.assertEqual(got, []) + tm.update_settings({"noticeShown": True}) + self.assertEqual(tm.flush(), 1) + path, body = got[0] + self.assertEqual((path, body["api_key"], body["batch"][0]["event"]), ("/batch/", "phc_test", "app_opened")) + self.assertEqual(self.queued(), []) + self.assertEqual([e["event"] for e in tm.state()["sent"]], ["app_opened"]) + + def test_a_failed_send_keeps_the_events(self): + self.serve(status=500) + tm.update_settings({"noticeShown": True}) + tm.capture("app_opened") + self.assertEqual(tm.flush(), 0) + self.assertEqual(len(self.queued()), 1) + + +class Scrub(unittest.TestCase): + def test_personal_details_are_removed(self): + home = str(Path.home()) + text = (f"open {home}/Downloads/My Game.apk failed; ssh alex@192.168.1.20 (frame.local) " + "key ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM steam 76561198000000000 mac 3c:22:fb:12:34:56 " + "url https://example.com/private/path?token=abc phc_abcdefghijklmnopqrstu C:\\Users\\Bob\\x " + "/home/carol/y") + out = tm.scrub(text) + for leaked in (home, "192.168.1.20", "frame.local", "AAAAC3Nza", "76561198000000000", "3c:22:fb", + "private/path", "phc_abcdefghijklmnopqrstu", "Bob", "carol", "alex@"): + self.assertNotIn(leaked, out) + self.assertIn("https://example.com/…", out) + self.assertIn("~/Downloads", out) + + def test_categories(self): + self.assertEqual(tm.categorize("adb: failed to install: INSTALL_FAILED_NO_MATCHING_ABIS: x"), + ("android_installer", "INSTALL_FAILED_NO_MATCHING_ABIS")) + self.assertEqual(tm.categorize("X has no arm64-v8a build (armeabi-v7a)")[0], "apk_wrong_abi") + self.assertEqual(tm.categorize("ssh: connect to host 10.0.0.2 port 22: Connection refused")[0], + "frame_unreachable") + self.assertEqual(tm.categorize("something new")[0], "other") + + +class Compat(Base): + def test_reports_are_shared_only_after_opting_in_without_file_names(self): + r = {"id": "r1", "package": "org.example", "version": "1.0", "rating": "works", "via": "user", + "notes": f"from {Path.home()}/x", "source": "MyPrivateBuild.apk", "date": "2026-09-28T10:00:00"} + self.assertFalse(tm.compat_report(r)) + tm.update_settings({"compat": True}) + self.assertTrue(tm.compat_report(r)) + p = self.queued()[-1]["properties"] + self.assertEqual((p["package"], p["rating"], p["id"]), ("org.example", "works", "r1")) + self.assertNotIn("source", p) + self.assertNotIn(str(Path.home()), p["notes"]) + r2 = dict(r, id="r2", source="https://f-droid.org/repo/org.example_1.apk") + tm.compat_report(r2) + self.assertEqual(self.queued()[-1]["properties"]["source"], "https://f-droid.org/…") + + def test_opting_in_shares_earlier_local_reports(self): + with mock.patch.object(db, "shared", return_value=False), \ + mock.patch.object(db, "_outbox", return_value=[{"id": "old1", "package": "org.a", "rating": "works", + "date": "2026-09-01T00:00:00"}]): + tm.update_settings({"compat": True}) + tm.update_settings({"compat": True}) # already sent: not again + self.assertEqual([e["properties"]["id"] for e in self.queued() if e["event"] == "compat_report"], ["old1"]) + + +class ApkInstallReports(unittest.TestCase): + """server.apk_installed: an APK that won't install is reported; connection trouble isn't.""" + + def setUp(self): + import server + self.server = server + for target, name in ((server.frame_catalog, "add_report"), (server.frame_telemetry, "install_finished")): + p = mock.patch.object(target, name) + setattr(self, name, p.start()) + self.addCleanup(p.stop) + + def test_wrong_abi_is_an_install_failed_report(self): + info = {"package": "org.x", "version": "2.0", "label": "X"} + self.server.apk_installed(info, None, self.server.frame_android.FrameError( + "X has no arm64-v8a build (armeabi-v7a); Lepton is 64-bit ARM only"), 3.0) + args, kw = self.add_report.call_args + self.assertEqual((args[0], args[1], kw["result"], kw["via"]), ("org.x", "2.0", "install_failed", "install")) + self.assertIs(self.install_finished.call_args[0][1], False) + + def test_connection_trouble_is_not_reported(self): + self.server.apk_installed({"package": "org.x", "version": "2.0"}, None, + self.server.frame_android.FrameError("timed out talking to frame"), 3.0) + self.add_report.assert_not_called() + + def test_private_package_names_stay_here(self): + with mock.patch.dict(self.server.frame_catalog._cache, {"by_pkg": {"org.public": {}}}): + self.server.apk_installed({"package": "com.private.thing", "version": "1"}, {"package": "com.private.thing"}, + None, 2.0) + self.assertIsNone(self.install_finished.call_args[1]["package"]) + self.server.apk_installed({"package": "org.public", "version": "1"}, {"package": "org.public"}, None, 2.0) + self.assertEqual(self.install_finished.call_args[1]["package"], "org.public") + + +class CommunitySync(unittest.TestCase): + def ev(self, i, who="a", day="2026-09-28", **kw): + return ({"id": f"id{i}", "package": "org.x", "rating": "works", "date": f"{day}T00:00:00", + "via": "probe", **kw}, who, f"{day} 10:00:00") + + def test_rows_are_validated_marked_and_capped_per_reporter(self): + events = [self.ev(i) for i in range(5)] + [self.ev(9, who="b", rating="nonsense"), self.ev(10, who="b")] + rows, skipped = db.community_rows(events, {}, cap=3) + self.assertEqual([r["id"] for r in rows], ["id0", "id1", "id2", "id10"]) + self.assertTrue(all(r["via"] == "community-probe" for r in rows)) + self.assertEqual(len(skipped), 3) + + def test_the_cap_and_duplicates_hold_across_syncs(self): + state = {} + rows, _ = db.community_rows([self.ev(i) for i in range(3)], state, cap=3) + self.assertEqual(len(rows), 3) + rows, skipped = db.community_rows([self.ev(i) for i in range(6)], state, cap=3) # overlapping re-read + self.assertEqual(rows, []) + self.assertEqual([why for _, why in skipped], ["over the daily limit for one reporter"] * 3) + + def test_a_malformed_event_is_skipped_not_fatal(self): + rows, skipped = db.community_rows([self.ev(1, via=["probe"]), ("not json", "a", "2026-09-28"), self.ev(2)], {}) + self.assertEqual([r["id"] for r in rows], ["id2"]) + self.assertEqual(len(skipped), 2) + + +class Regressions(Base): + """Findings from the cross-provider review.""" + + def test_urls_lose_credentials_paths_and_private_hosts(self): + for text, leaked in (("https://alice:secret@example.com/private.apk?token=credential", ("alice", "secret", "private", "credential")), + ("https://alice:secret@192.168.1.4/private.apk", ("alice", "192.168", "private")), + ("fe80::1234 and 2001:db8::5", ("fe80", "2001:db8")), + ("sk-proj-abcdefghijklmnopqrstuv", ("abcdefghijk",)), + ("http://frame.local:8080/x", ("frame.local", "8080"))): + out = tm.scrub(text) + for s in leaked: + self.assertNotIn(s, out, (text, out)) + + def test_compat_labels_versions_and_sources_are_scrubbed(self): + tm.update_settings({"compat": True}) + tm.compat_report({"id": "r9", "package": "org.x", "rating": "works", "date": "2026-09-28T00:00:00", + "label": "alice@example.com build", "version": "1.0-alice@example.com", + "source": "https://alice:secret@192.168.1.4/private.apk"}) + p = self.queued()[-1]["properties"] + self.assertNotIn("alice", json.dumps(p)) + self.assertNotIn("source", p) + + def test_an_unsent_report_is_shared_again_after_opting_out_and_in(self): + with mock.patch.object(db, "shared", return_value=False), \ + mock.patch.object(db, "_outbox", return_value=[{"id": "q1", "package": "org.a", "rating": "works", + "date": "2026-09-01T00:00:00"}]): + tm.update_settings({"compat": True}) + tm.update_settings({"compat": False}) + self.assertEqual(self.queued(), []) + tm.update_settings({"compat": True}) + self.assertEqual([e["properties"]["id"] for e in self.queued() if e["event"] == "compat_report"], ["q1"]) + + def test_opting_out_waits_for_a_send_in_progress(self): + tm.update_settings({"noticeShown": True}) + tm.capture("app_opened") + order = [] + started = threading.Event() + + def slow_open(req, timeout): + started.set() + time.sleep(0.3) + order.append("sent") + return mock.MagicMock(__enter__=lambda s: s, __exit__=lambda *a: False, read=lambda: b"{}") + + with mock.patch.object(tm.urllib.request, "urlopen", side_effect=slow_open): + th = threading.Thread(target=tm.flush) + th.start() + started.wait(2) + tm.update_settings({"usage": False}) + order.append("opted out") + th.join() + self.assertEqual(order, ["sent", "opted out"]) + + def test_project_id_comes_from_the_config(self): + with mock.patch.dict(os.environ, {"FRAME_CONTROL_POSTHOG_PROJECT": "12345"}): + self.assertEqual(tm.config()["project"], "12345") + + +class ReportProblem(Base): + """Report a problem: diagnostics are scrubbed and fit the feedback API; sending goes to it.""" + + def serve(self, status=201, reply=None): + got = [] + + class H(BaseHTTPRequestHandler): + def do_POST(self): + got.append(json.loads(self.rfile.read(int(self.headers["Content-Length"])))) + self.send_response(status) + self.end_headers() + self.wfile.write(json.dumps(reply or {"ok": True, "number": 42, + "url": "https://github.com/saphid/frame-control/issues/42"}).encode()) + + def log_message(self, *a): + pass + + httpd = HTTPServer(("127.0.0.1", 0), H) + threading.Thread(target=httpd.serve_forever, daemon=True).start() + self.addCleanup(httpd.server_close) + self.addCleanup(httpd.shutdown) + p = mock.patch.object(fr, "FEEDBACK_URL", f"http://127.0.0.1:{httpd.server_port}/api/feedback") + p.start() + self.addCleanup(p.stop) + return got + + def test_diagnostics_are_scrubbed_and_include_the_log(self): + log = tm.STATE / "server.log" + log.write_text("GET /api/status 200\nTraceback: ssh alice@192.168.1.9 failed in %s/x\n" % Path.home()) + with mock.patch.dict(os.environ, {"FRAME_CONTROL_LOG": str(log)}): + text = fr.diagnostics(["16:00 Install failed: https://bob:pw@example.com/a.apk"], include_logs=True, limit=5000) + self.assertIn("Frame Control 9.9.9", text) + self.assertIn("Traceback", text) + self.assertNotIn("GET /api/status", text) + for leaked in ("alice", "192.168.1.9", str(Path.home()), "bob", "pw@"): + self.assertNotIn(leaked, text) + + def test_a_report_fits_the_api_limit_in_utf16_units(self): + body = {"title": "Live view stops", "message": "It stops 😀 " * 600, "diagnostics": "log 😀 line\n" * 2000} + title, message = fr.compose(body) + self.assertLessEqual(fr.u16(message), fr.MESSAGE_MAX) + self.assertTrue(message.startswith("It stops")) + with self.assertRaises(ValueError): + fr.compose({"title": "hi", "message": "It stops after a minute."}) + + def test_logs_only_when_asked_and_environment_is_kept_first(self): + log = tm.STATE / "server.log" + log.write_text("".join(f"old line {i}\n" for i in range(200)) + "newest line\n") + with mock.patch.dict(os.environ, {"FRAME_CONTROL_LOG": str(log)}): + plain = fr.diagnostics(["Copy Jane Doe tax return.pdf to ~/Downloads"]) + full = fr.diagnostics(["Install failed"], include_logs=True, limit=400) + self.assertNotIn("Jane Doe", plain) + self.assertNotIn("line", plain) + self.assertTrue(full.startswith("Frame Control 9.9.9")) + self.assertIn("Install failed", full) + self.assertIn("newest line", full) + self.assertLessEqual(fr.u16(full), 400) + + def test_the_previewed_diagnostics_are_what_is_sent(self): + got = self.serve() + fr.send({"title": "Live view stops", "message": "It stops after a minute.", "elapsed": 9000, + "diagnostics": "Frame Control 9.9.9\nssh janes-mac.tail12345.ts.net failed"}) + self.assertIn("Frame Control 9.9.9", got[0]["message"]) + self.assertNotIn("janes-mac", got[0]["message"]) + + def test_send_files_an_issue_and_reports_its_number(self): + got = self.serve() + res = fr.send({"kind": "bug", "title": "Live view stops", "message": "It stops after a minute.", + "elapsed": 9000, "github": "@someone"}) + self.assertEqual((res["number"], res["url"]), (42, "https://github.com/saphid/frame-control/issues/42")) + sent = got[0] + self.assertEqual((sent["kind"], sent["github"], sent["website"], sent["elapsed"]), ("bug", "someone", "", 9000)) + self.assertEqual(sent["message"], "It stops after a minute.") + + def test_the_services_error_is_passed_on(self): + self.serve(status=429, reply={"error": "That's a lot of feedback in one hour."}) + with self.assertRaisesRegex(fr.ReportError, "a lot of feedback"): + fr.send({"title": "Live view stops", "message": "It stops after a minute.", "elapsed": 9000}) + + +if __name__ == "__main__": + unittest.main() diff --git a/ui/frame_android.py b/ui/frame_android.py index 3bff853..9f6f789 100644 --- a/ui/frame_android.py +++ b/ui/frame_android.py @@ -106,16 +106,36 @@ def _write_meta(d, meta): ssh(f'cat > {d}/meta.json.tmp && mv {d}/meta.json.tmp {d}/meta.json', input=json.dumps(meta, indent=1)) +# Called after every install, worked or not, as fn(info, meta, error, seconds): +# info is None if the APK couldn't be read, meta None and error set if it failed. +install_hooks = [] + + def install(apk_path, flatscreen=True, name=None, source=None, icon_png=None): - info = apk_info(apk_path) - if icon_png: - info['icon_png'] = icon_png - check_installable(info) - pkg = info['package'] - if not PKG_RE.match(pkg): - raise FrameError(f'unexpected package name {pkg!r}') - with _install_lock: - return _install(apk_path, info, pkg, flatscreen, name, source) + start, info = time.time(), None + try: + info = apk_info(apk_path) + if icon_png: + info['icon_png'] = icon_png + check_installable(info) + pkg = info['package'] + if not PKG_RE.match(pkg): + raise FrameError(f'unexpected package name {pkg!r}') + with _install_lock: + meta = _install(apk_path, info, pkg, flatscreen, name, source) + except FrameError as e: + _after_install(info, None, e, start) + raise + _after_install(info, meta, None, start) + return meta + + +def _after_install(info, meta, error, start): + for hook in install_hooks: + try: + hook(info, meta, error, time.time() - start) + except Exception: + pass # reporting must never change an install's outcome def _install(apk_path, info, pkg, flatscreen, name, source): diff --git a/ui/frame_compat_db.py b/ui/frame_compat_db.py index 98c67c4..1576818 100644 --- a/ui/frame_compat_db.py +++ b/ui/frame_compat_db.py @@ -8,12 +8,18 @@ New reports go to a local outbox first and are sent from there, so nothing is lost offline. A mirror of every report is kept for offline reads. Both live in frame_host.data_dir('compat-db'). Python stdlib only. -CLI: python3 ui/frame_compat_db.py {count|export FILE|import FILE|flush} +Everyone else can opt in to sharing (the Privacy panel): their reports then +also go to PostHog as compat_report events (frame_telemetry.py), and the +maintainer's `sync` pulls them into the database, at most SYNC_DAILY_CAP per +reporter per day, marked via=community[-probe|-install]. + +CLI: python3 ui/frame_compat_db.py {count|export FILE|import FILE|flush|sync} (import restores a backup; reports already in the database are skipped.) """ import json, os, subprocess, sys, threading, time, urllib.error, urllib.parse, urllib.request, uuid import frame_host +import frame_telemetry URL = os.environ.get('FRAME_COMPAT_DB_URL', 'https://frame-compat.lakebed.app') KEYCHAIN = ('frame-control-compat-db', 'app-key') @@ -216,11 +222,147 @@ def add(report): if shared(): flush() _mem['at'] = 0 # refetch on next load + else: + frame_telemetry.compat_report(r) # only if this person opted in to sharing except Exception: pass # stays queued; load() shows it and a later call sends it return r +# ---- community reports: PostHog -> the database (maintainer only) --------------- + +POSTHOG_KEYCHAIN = ('frame-control-posthog', 'personal-api-key') +SYNC_STATE = os.path.join(STATE, 'posthog-sync.json') +SYNC_DAILY_CAP = 30 +COMMUNITY_VIA = {'user': 'community', 'probe': 'community-probe', 'install': 'community-install'} + + +def posthog_personal_key(): + k = os.environ.get('POSTHOG_PERSONAL_API_KEY') + if k: + return k + if frame_host.MAC: + p = subprocess.run(['security', 'find-generic-password', '-s', POSTHOG_KEYCHAIN[0], '-a', + POSTHOG_KEYCHAIN[1], '-w'], capture_output=True, text=True) + if p.returncode == 0 and p.stdout.strip(): + return p.stdout.strip() + raise DBError('No PostHog personal API key (set POSTHOG_PERSONAL_API_KEY, or on macOS the Keychain ' + f'item service {POSTHOG_KEYCHAIN[0]}, account {POSTHOG_KEYCHAIN[1]})') + + +def _posthog_query(sql): + cfg = frame_telemetry.config() + project = os.environ.get('FRAME_CONTROL_POSTHOG_PROJECT') or cfg.get('project') + if not project: + raise DBError('No PostHog project id (ui/telemetry.json "project", or FRAME_CONTROL_POSTHOG_PROJECT)') + # The query API lives on the app host (eu.posthog.com), not the ingestion host (eu.i.posthog.com). + host = cfg['host'].replace('.i.posthog.com', '.posthog.com') + req = urllib.request.Request(f'{host}/api/projects/{urllib.parse.quote(str(project))}/query/', method='POST', + data=json.dumps({'query': {'kind': 'HogQLQuery', 'query': sql}}).encode(), + headers={'authorization': 'Bearer ' + posthog_personal_key(), + 'content-type': 'application/json'}) + try: + with _opener.open(req, timeout=60) as r: + return json.loads(r.read()) + except urllib.error.HTTPError as e: + raise DBError(f'PostHog said HTTP {e.code}: {e.read()[:300]!r}') + except (urllib.error.URLError, TimeoutError, OSError, ValueError) as e: + raise DBError(f"can't reach PostHog: {e}") + + +SYNC_OVERLAP_DAYS = 30 # re-read this far back: offline copies send late, with their original time +SYNC_PAGE = 5000 + + +def community_rows(events, state, cap=SYNC_DAILY_CAP): + """(reports, skipped): compat_report events as database rows. `state` ({"seen": {id: day}, + "counts": {"reporter|day": n}}) persists between syncs, so an event read twice is handled + once and each reporter gets at most `cap` reports a day in total.""" + seen, counts = state.setdefault('seen', {}), state.setdefault('counts', {}) + out, skipped = [], [] + for props, reporter, ts in events: + if isinstance(props, str): + try: + props = json.loads(props) + except ValueError: + props = None + if not isinstance(props, dict): + skipped.append((None, 'unreadable properties')) + continue + bad = [k for k in (*FIELDS, 'id') if props.get(k) is not None and not isinstance(props[k], (str, int, float))] + if bad: + skipped.append((str(props.get('id'))[:60], f'bad field {bad[0]}')) + continue + r = {k: (str(props[k]) if props.get(k) is not None else None) for k in FIELDS} + r['id'] = str(props['id']) if props.get('id') is not None else None + if r['id'] in seen: + continue # handled in an earlier sync (or earlier in this one) + r['via'] = COMMUNITY_VIA.get(r.get('via') or 'user', 'community') + why = problem(r) + if why: + skipped.append((r.get('id'), why)) + continue + day = str(ts)[:10] + seen[r['id']] = day + key_ = f'{reporter}|{day}' + if counts.get(key_, 0) >= cap: + skipped.append((r['id'], 'over the daily limit for one reporter')) + continue + counts[key_] = counts.get(key_, 0) + 1 + out.append(r) + return out, skipped + + +def _sync_state(): + try: + with open(SYNC_STATE) as f: + s = json.load(f) + return s if isinstance(s, dict) else {} + except (OSError, ValueError): + return {} + + +def _save_sync_state(s): + """Forget ids and counts older than the overlap window (plus a margin).""" + cutoff = time.strftime('%Y-%m-%d', time.gmtime(time.time() - (SYNC_OVERLAP_DAYS + 15) * 86400)) + s['seen'] = {k: d for k, d in s.get('seen', {}).items() if d >= cutoff} + s['counts'] = {k: n for k, n in s.get('counts', {}).items() if k.rsplit('|', 1)[-1] >= cutoff} + os.makedirs(STATE, exist_ok=True) + with open(SYNC_STATE + '.tmp', 'w') as f: + json.dump(s, f) + os.replace(SYNC_STATE + '.tmp', SYNC_STATE) + + +def sync(dry_run=False): + """Pull community reports from PostHog into the database. Returns (added, skipped). + Reads the last SYNC_OVERLAP_DAYS each time, since events carry the time they were + made, not when they arrived; the saved state keeps that from adding anything twice.""" + key() # the maintainer's copy only + state = _sync_state() + since = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(time.time() - SYNC_OVERLAP_DAYS * 86400)) + events = [] + for page in range(40): + res = _posthog_query("SELECT properties, distinct_id, timestamp FROM events " + f"WHERE event = 'compat_report' AND timestamp >= toDateTime('{since}') " + f"ORDER BY timestamp, uuid LIMIT {SYNC_PAGE} OFFSET {page * SYNC_PAGE}") + rows = res.get('results') or [] + events += rows + if len(rows) < SYNC_PAGE: + break + rows, skipped = community_rows(events, state) + if dry_run: + return rows, skipped + if rows: + os.makedirs(STATE, exist_ok=True) + with _lock, open(OUTBOX, 'a') as f: + f.writelines(json.dumps(r, ensure_ascii=False) + '\n' for r in rows) + # Saved before sending: the rows are in the outbox now, and flush retries them if sending fails. + _save_sync_state(state) + flush() # also retries rows a failed earlier sync left in the outbox + _mem['at'] = 0 + return rows, skipped + + def main(): cmd, *args = sys.argv[1:] or ['count'] try: @@ -248,6 +390,12 @@ def main(): 'reports already in the database were not duplicated') elif cmd == 'flush': print(f'{flush()} still queued') + elif cmd == 'sync': + rows, skipped = sync(dry_run='--dry-run' in args) + for rid, why in skipped: + print(f'skipped {rid!r}: {why}', file=sys.stderr) + print(f"{len(rows)} community reports {'found' if '--dry-run' in args else 'added'}, " + f'{len(skipped)} skipped') else: sys.exit(__doc__) except DBError as e: diff --git a/ui/frame_report.py b/ui/frame_report.py new file mode 100644 index 0000000..f898ea5 --- /dev/null +++ b/ui/frame_report.py @@ -0,0 +1,147 @@ +"""Report a problem from inside Frame Control. Python stdlib only. + +The page's Report a problem dialog shows the diagnostics below before anything +is sent, then this sends the report to the website's feedback API +(site/functions/api/feedback.js), which files it as a GitHub issue. The user +needs no GitHub account. Everything collected is scrubbed first +(frame_telemetry.scrub), since the issue is public. +""" +import json +import os +import platform +import sys +import time +import urllib.error +import urllib.request + +import frame_host +import frame_telemetry + +FEEDBACK_URL = os.environ.get('FRAME_CONTROL_FEEDBACK_URL', 'https://frame-control.pages.dev/api/feedback') +ISSUES_URL = 'https://github.com/saphid/frame-control/issues/new' +KINDS = ('bug', 'idea', 'question', 'other') +MESSAGE_MAX = 5000 # the feedback API's limit, in JavaScript (UTF-16) units +TEXT_MAX = 3500 # the person's own text +DIAG_MAX = 1300 # the diagnostics block, so text + diagnostics always fit MESSAGE_MAX +LOG_LINES = 40 +ACTIVITY_LINES = 25 +HEAD = '\n\n---\nDiagnostics from Frame Control (personal details removed):\n```\n' +TAIL = '\n```' + +frame = {} # the Frame's last known SteamOS build, set by server.status() + + +def u16(s): + """Length as the website's validator counts it (JavaScript strings are UTF-16).""" + return len(s.encode('utf-16-le')) // 2 + + +def cut(s, n): + """s shortened to at most n UTF-16 units, never splitting a character.""" + while u16(s) > n: + s = s[:max(0, len(s) - max(1, (u16(s) - n) // 2))] + return s + + +def _log_tail(): + """The last lines of the server log the app writes (FRAME_CONTROL_LOG), newest first.""" + path = os.environ.get('FRAME_CONTROL_LOG') + if not path: + return [] + try: + with open(path, 'rb') as f: + f.seek(0, os.SEEK_END) + f.seek(max(0, f.tell() - 64 * 1024)) + lines = f.read().decode('utf-8', 'replace').splitlines() + except OSError: + return [] + # Request lines ("GET /api/status ...") are noise; keep what went wrong. + keep = [ln for ln in lines if ln.strip() and not ln.startswith(('GET ', 'POST '))] + return list(reversed(keep[-LOG_LINES:])) + + +def diagnostics(activity=(), include_logs=False, limit=DIAG_MAX): + """What a report includes, scrubbed and at most `limit` UTF-16 units. Always the versions + and builds; recent activity and the server log only when asked for, since they can name + files. Sections are filled in order of use, newest lines first, so trimming drops the oldest.""" + t = frame_telemetry.state() + levels = ', '.join(f"{name} {'on' if on else 'off'}" for name, on in + (('usage', t['usage']), ('compat', t['compat']), ('error details', t['diagnostics']))) + env = [ + f"Frame Control {frame_telemetry.app_version()}" + f"{' (built app)' if os.environ.get('FRAME_CONTROL_PACKAGED') else ' (source checkout)'}", + f"Computer: {frame_host.NAME} {platform.release()} {platform.machine()}, Python {'%d.%d.%d' % sys.version_info[:3]}", + f"SteamOS: {frame.get('build') or 'unknown'} ({frame.get('version') or 'not connected since start'})", + f"Analytics: {levels}", + f"Report time: {time.strftime('%Y-%m-%d %H:%M %Z')}", + ] + out = frame_telemetry.scrub('\n'.join(env), limit=limit) + if not include_logs: + return cut(out, limit) + sections = [('Recent activity (newest first):', [str(a)[:300] for a in list(activity)[:ACTIVITY_LINES] if isinstance(a, str)]), + ('Server log (newest first):', _log_tail())] + for title, lines in sections: + if not lines: + continue + block = '\n\n' + title + if u16(out + block) > limit: + break + out += block + for line in lines: + line = '\n' + frame_telemetry.scrub(line, 300) + if u16(out + line) > limit: + break + out += line + return out + + +def compose(body): + """(title, message) for the feedback API: the person's text, then the diagnostics exactly as + the dialog previewed them (passed back, scrubbed again and bounded here).""" + title = ' '.join(str(body.get('title') or '').split()) + text = str(body.get('message') or '').strip() + if len(title) < 5: + raise ValueError('give it a short title (at least 5 characters)') + if len(text) < 10: + raise ValueError('say a little more about what happened (at least 10 characters)') + title, text = cut(title, 120), cut(text, TEXT_MAX) + diag = body.get('diagnostics') + if not isinstance(diag, str) or not diag.strip(): + return title, text + diag = cut(frame_telemetry.scrub(diag, 20000), DIAG_MAX) + return title, f'{text}{HEAD}{diag}{TAIL}' + + +def send(body): + """File the report. Returns {"number", "url"} of the new issue; raises ReportError.""" + kind = body.get('kind') if body.get('kind') in KINDS else 'bug' + title, message = compose(body) + payload = {'kind': kind, 'title': title, 'message': message, 'website': '', + 'github': str(body.get('github') or '').strip().lstrip('@')[:40], + 'version': frame_telemetry.app_version(), 'os': f'{frame_host.NAME} {platform.machine()}', + 'steamos': str(frame.get('build') or '')[:120], + # How long the dialog was open; the API treats anything under 3 s as a script. + 'elapsed': max(0, int(body.get('elapsed') or 0))} + req = urllib.request.Request(FEEDBACK_URL, data=json.dumps(payload).encode(), method='POST', + headers={'content-type': 'application/json', + 'user-agent': f'FrameControl/{frame_telemetry.app_version()}'}) + try: + with urllib.request.urlopen(req, timeout=30) as r: + res = json.loads(r.read() or b'{}') + except urllib.error.HTTPError as e: + try: + why = json.loads(e.read() or b'{}').get('error') + except ValueError: + why = None + e.close() + raise ReportError(why or f'the feedback service said HTTP {e.code}') + except (urllib.error.URLError, OSError, ValueError) as e: + raise ReportError(f"couldn't reach the feedback service: {e}") + if not res.get('url'): + raise ReportError("the feedback service didn't file it; try again in a moment") + frame_telemetry.capture('problem_reported', {'kind': kind, 'with_diagnostics': bool(body.get('diagnostics'))}) + return {'number': res.get('number'), 'url': res['url'], 'message': f"Sent. It's issue #{res.get('number')} on GitHub."} + + +class ReportError(RuntimeError): + pass diff --git a/ui/frame_telemetry.py b/ui/frame_telemetry.py new file mode 100644 index 0000000..6dca188 --- /dev/null +++ b/ui/frame_telemetry.py @@ -0,0 +1,522 @@ +"""Anonymous analytics for Frame Control, sent to PostHog. Python stdlib only. + +Three levels, each chosen in the page's Privacy panel (docs/privacy.md lists +every event and property): + +- usage (on by default, after the first-run notice has been shown): installs of + Frame Control, daily opens, updates, which tabs are used, and whether installs + on the Frame worked, with an error category from a fixed list. Never file + names, paths, hostnames, IP addresses, window titles or account data. +- compat (opt-in): Android compatibility reports, the same fields the Report + dialog shows, so they reach the shared database (frame_compat_db.py). The + maintainer's sync (python3 ui/frame_compat_db.py sync) moves them there. +- diagnostics (opt-in): error messages and Python tracebacks, scrubbed of + home folders, user names, addresses and keys. + +The first-run notice offers compat and diagnostics together, and the page's +Report a problem dialog (frame_report.py) files bug reports whatever is chosen +here. + +Events are identified by a random id made on first run, not by the person or +computer, and sent without person profiles or GeoIP. Nothing is sent without a +project key (ui/telemetry.json or $FRAME_CONTROL_POSTHOG_KEY), from a source +checkout unless $FRAME_CONTROL_TELEMETRY=1, or when $DO_NOT_TRACK=1 or +$FRAME_CONTROL_TELEMETRY=0. + +Events wait in an outbox file and are sent in batches from a background thread, +so going offline loses nothing. The last SENT_KEEP sent events are kept on this +computer so the page can show exactly what left it. +""" +import ipaddress +import json +import os +import platform +import re +import sys +import threading +import time +import traceback +import urllib.error +import urllib.request +import uuid +from pathlib import Path +from urllib.parse import urlsplit + +import frame_host + +HERE = Path(__file__).resolve().parent +STATE = frame_host.data_dir('telemetry') +SETTINGS = STATE / 'settings.json' +OUTBOX = STATE / 'outbox.jsonl' +SENT = STATE / 'sent.jsonl' +SENT_KEEP = 200 +OUTBOX_MAX = 2000 # events kept while offline; the oldest go first +FLUSH_EVERY = 60 +REPEAT_WINDOW = 600 # the same diagnostic error is sent at most once in this many seconds +DEFAULT_HOST = 'https://eu.i.posthog.com' + +LEVELS = ('usage', 'compat', 'diagnostics') +# Events the page may send through /api/telemetry, and the properties each may carry. +PAGE_EVENTS = {'tab_viewed': {'tab'}, 'update_offered': {'to_version'}, + 'update_started': {'to_version'}, 'update_failed': {'to_version', 'error_category'}} +TABS = {'view', 'shots', 'library', 'getgames', 'android', 'transfer', 'apps', 'display', 'power', 'privacy'} + +_lock = threading.RLock() +_send_lock = threading.Lock() # held while sending; consent changes wait for it +_seen_errors = {} +_flusher = None +_wake = threading.Event() + + +# ---- configuration and settings ------------------------------------------------- + +def config(): + """PostHog host and project key: the environment, else ui/telemetry.json.""" + try: + with open(HERE / 'telemetry.json') as f: + c = json.load(f) + except (OSError, ValueError): + c = {} + host = os.environ.get('FRAME_CONTROL_POSTHOG_HOST') or c.get('host') or DEFAULT_HOST + key = os.environ.get('FRAME_CONTROL_POSTHOG_KEY') or c.get('key') or '' + project = os.environ.get('FRAME_CONTROL_POSTHOG_PROJECT') or c.get('project') or '' + return {'host': host.rstrip('/'), 'key': key, 'project': str(project)} + + +def blocked(): + """Why nothing may be sent at all, whatever the settings say, or None.""" + if os.environ.get('DO_NOT_TRACK') == '1' or os.environ.get('FRAME_CONTROL_TELEMETRY') == '0': + return 'turned off by DO_NOT_TRACK or FRAME_CONTROL_TELEMETRY=0' + if not config()['key']: + return 'no PostHog project key in this build' + if not os.environ.get('FRAME_CONTROL_PACKAGED') and os.environ.get('FRAME_CONTROL_TELEMETRY') != '1': + return 'running from a source checkout (set FRAME_CONTROL_TELEMETRY=1 to send)' + return None + + +def _defaults(): + return {'id': str(uuid.uuid4()), 'usage': True, 'compat': False, 'diagnostics': False, + 'notice_shown': False, 'installed_sent': False, 'last_version': None, 'last_open_day': None, + 'frames_seen': [], 'compat_sent': []} + + +def settings(): + with _lock: + s = _defaults() + try: + with open(SETTINGS) as f: + saved = json.load(f) + if isinstance(saved, dict): + s.update({k: v for k, v in saved.items() if k in s}) + except (OSError, ValueError): + pass + if not SETTINGS.exists(): + _save(s) # keep the id stable from the first call + return s + + +def _save(s): + try: + STATE.mkdir(parents=True, exist_ok=True) + tmp = SETTINGS.with_suffix('.tmp') + tmp.write_text(json.dumps(s, indent=1)) + os.replace(tmp, SETTINGS) + except OSError: + pass + + +def enabled(level): + """Whether events of this level are collected: never when sending is blocked, so a + source checkout or a test run leaves nothing behind.""" + if blocked(): + return False + return bool(settings().get(level)) + + +def update_settings(changes): + """Apply the page's choices. Turning a level off drops its unsent events; a send already + under way finishes first, so nothing leaves after this returns.""" + with _send_lock, _lock: + s = settings() + if 'noticeShown' in changes: + s['notice_shown'] = bool(changes['noticeShown']) or s['notice_shown'] + for level in LEVELS: + if level in changes: + s[level] = bool(changes[level]) + s['notice_shown'] = True + _save(s) + _drop_unwanted(s) + if changes.get('compat'): + backfill_compat() + _wake.set() + return state() + + +def state(): + """What the page shows: the choices, why sending is blocked, and what was sent.""" + s = settings() + return {'usage': s['usage'], 'compat': s['compat'], 'noticeShown': s['notice_shown'], + 'diagnostics': s['diagnostics'], + 'blocked': blocked(), 'id': s['id'], 'queued': len(_read_lines(OUTBOX)), + 'sent': list(reversed(_read_lines(SENT)))[:50]} + + +# ---- scrubbing and error categories --------------------------------------------- + +def _user_names(): + names = set() + for v in (os.environ.get('USER'), os.environ.get('USERNAME'), Path.home().name): + if v and len(v) > 2: + names.add(v) + return names + + +URL_RE = re.compile(r'[A-Za-z][A-Za-z0-9+.-]*://[^\s\'"<>]+') +SCRUBS = [ + (re.compile(r'ssh-(?:rsa|ed25519|dss)\s+\S+'), ''), + (re.compile(r'-----BEGIN [^-]+-----.*?-----END [^-]+-----', re.S), ''), + (re.compile(r'\b(?:phc|phx|ghp|gho|ghu|ghs|github_pat|sk|pk|rk|xox[abpr])[_-][A-Za-z0-9_-]{12,}'), ''), + (re.compile(r'(?i)\b(token|key|secret|password|passwd|pwd|auth|signature|sig)=[^\s&]+'), r'\1='), + (re.compile(r'[\w.+-]+@[\w-]+(?:\.[\w-]+)+'), ''), + (re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b'), ''), + (re.compile(r'\b(?:[0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}\b'), ''), + (re.compile(r'\b7656119\d{10}\b'), ''), + (re.compile(r'\b(?:[\w-]+\.)+(?:local|lan|home|internal|localdomain|ts\.net)\b'), ''), + (re.compile(r'\b[0-9a-fA-F]{32,}\b'), ''), +] +IPV6_RE = re.compile(r'(?' + except ValueError: + return m.group(0) + + +def public_host(host): + """A host name that's safe to send: not an address, not a private or single-label name.""" + host = (host or '').lower().rstrip('.') + if not host or '.' not in host: + return None + try: + ipaddress.ip_address(host.strip('[]')) + return None + except ValueError: + pass + if re.search(r'\.(?:local|lan|home|internal|localdomain|ts\.net|arpa)$', host) or not re.fullmatch(r'[a-z0-9.-]+', host): + return None + return host + + +def _scrub_url(u): + """Only the scheme and a public host name of a URL; never user names, passwords, ports, + paths or queries.""" + try: + parts = urlsplit(u) + host = public_host(parts.hostname) + except ValueError: + host = None + return f'{parts.scheme}://{host}/…' if host else '' + + +def scrub(text, limit=2000): + """Text with URLs, home folders, user names, addresses, hosts, ids and keys replaced.""" + if text is None: + return None + t = URL_RE.sub(lambda m: _scrub_url(m.group(0)), str(text)) # first, before anything splits a URL + home = str(Path.home()) + if len(home) > 3: + t = t.replace(home, '~') + t = re.sub(r'(/Users/|/home/|[A-Za-z]:\\Users\\)[^/\\\s]+', r'\1', t) + for pattern, repl in SCRUBS: + t = pattern.sub(repl, t) + t = IPV6_RE.sub(_ipv6, t) + for name in _user_names(): + t = re.sub(r'\b%s\b' % re.escape(name), '', t) + return t[:limit] + + +# From the most to the least specific; the first match wins. +CATEGORIES = [ + ('android_installer', re.compile(r'INSTALL_(?:FAILED|PARSE_FAILED)_[A-Z_]+')), + ('apk_needs_newer_android', re.compile(r'needs Android API')), + ('apk_wrong_abi', re.compile(r'no arm64-v8a build')), + ('apk_unreadable', re.compile(r'(?i)not a zip|bad apk|AndroidManifest|ApkError|unexpected package name')), + ('cant_run_on_frame', re.compile(r"can't run on the Frame")), + ('steam_shortcut', re.compile(r'(?i)steam did not return a shortcut|shortcut list|no Steam shortcut')), + ('frame_not_set_up', re.compile(r'(?i)Could not resolve hostname|no "?frame"? (?:SSH )?alias')), + ('frame_auth', re.compile(r'(?i)Permission denied|Host key verification failed')), + ('frame_unreachable', re.compile(r'(?i)timed out|Connection (?:refused|reset|closed)|No route to host|' + r'Network is unreachable|Operation timed out|asleep|kex_exchange')), + ('frame_disk_full', re.compile(r'(?i)No space left|disk full|ENOSPC')), + ('download_failed', re.compile(r'(?i)HTTP (?:Error )?\d{3}|URLError|download|certificate verify failed')), + ('flatpak', re.compile(r'(?i)flatpak|flathub')), + ('cancelled', re.compile(r'(?i)cancel')), + ('lepton', re.compile(r'(?i)lepton|podman|instance')), +] + + +def categorize(message): + """(category, detail): a fixed category name, plus an Android installer code when there is one.""" + text = str(message or '') + for name, pattern in CATEGORIES: + m = pattern.search(text) + if m: + return name, (m.group(0) if name == 'android_installer' else None) + return 'other', None + + +# ---- capturing ------------------------------------------------------------------ + +def _common(): + return {'app_version': app_version(), 'os': frame_host.NAME, 'arch': platform.machine().lower(), + 'python': '%d.%d' % sys.version_info[:2], '$lib': 'frame-control', + # Anonymous events: no person profile, no location lookup. + '$process_person_profile': False, '$geoip_disable': True} + + +def app_version(): + v = os.environ.get('FRAME_CONTROL_VERSION') + if v: + return v + try: + with open(HERE.parent / 'app' / 'package.json') as f: + return json.load(f).get('version') or 'dev' + except (OSError, ValueError): + return 'dev' + + +def capture(event, props=None, level='usage'): + """Queue an event if its level is on. Never raises.""" + try: + if level not in LEVELS or not enabled(level): + return False + s = settings() + e = {'event': event, 'distinct_id': s['id'], 'uuid': str(uuid.uuid4()), + 'timestamp': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), + 'properties': {**_common(), **(props or {}), 'level': level}} + with _lock: + lines = _read_lines(OUTBOX) + [e] + _write_lines(OUTBOX, lines[-OUTBOX_MAX:]) + return True + except Exception: + return False + + +def page_event(body): + """An event from the page, checked against PAGE_EVENTS.""" + name = body.get('event') + allowed = PAGE_EVENTS.get(name) + if allowed is None: + raise ValueError('unknown event') + props = {k: str(v)[:40] for k, v in (body.get('properties') or {}).items() if k in allowed} + if name == 'tab_viewed' and props.get('tab') not in TABS: + raise ValueError('unknown tab') + return {'queued': capture(name, props)} + + +def app_started(): + """Once per server start: first install, an update, and one open a day.""" + if blocked(): + return + with _lock: + s = settings() + version, today = app_version(), time.strftime('%Y-%m-%d') + if not s['installed_sent']: + capture('app_installed') + s['installed_sent'] = True + elif s['last_version'] and s['last_version'] != version: + capture('app_updated', {'from_version': s['last_version']}) + if s['last_open_day'] != today: + capture('app_opened') + s['last_open_day'] = today + s['last_version'] = version + _save(s) + + +def frame_seen(build, version): + """The Frame's SteamOS build, once per build (public build numbers).""" + key = f'{build}/{version}' + with _lock: + s = settings() + if not build or key in s['frames_seen']: + return + s['frames_seen'] = (s['frames_seen'] + [key])[-20:] + _save(s) + capture('frame_connected', {'steamos_build': str(build)[:40], 'steamos_version': str(version or '')[:40]}) + + +def install_finished(kind, ok, seconds=None, error=None, **props): + """kind: apk, flatpak, steam, title or web. props must already be public (no file names).""" + p = {'kind': kind, 'ok': bool(ok), **{k: v for k, v in props.items() if v is not None}} + if seconds is not None: + p['seconds'] = round(seconds, 1) + if error is not None: + p['error_category'], code = categorize(error) + if code: + p['installer_code'] = code + capture('install_finished', p) + if error is not None and not ok: + diagnostic(f'{kind} install failed', error) + + +def diagnostic(where, error, tb=None): + """An error for the opt-in diagnostics level: scrubbed text, and a traceback if there is one.""" + if not enabled('diagnostics'): + return + message = scrub(error) + fingerprint = f'{where}|{message[:120]}' + now = time.time() + with _lock: + if now - _seen_errors.get(fingerprint, 0) < REPEAT_WINDOW: + return + _seen_errors[fingerprint] = now + exc_type = type(error).__name__ if isinstance(error, BaseException) else 'Error' + frames = [] + if tb is None and isinstance(error, BaseException): + tb = error.__traceback__ + for fs in traceback.extract_tb(tb) if tb else []: + frames.append({'filename': os.path.basename(fs.filename), 'lineno': fs.lineno, 'function': fs.name, + 'in_app': True, 'platform': 'python'}) + capture('$exception', {'$exception_list': [{'type': exc_type, 'value': message, + 'mechanism': {'handled': True, 'type': 'generic'}, + 'stacktrace': {'type': 'raw', 'frames': frames[-30:]}}], + '$exception_type': exc_type, '$exception_message': message, + 'where': scrub(where, 200), 'error_category': categorize(error)[0]}, + level='diagnostics') + + +COMPAT_FIELDS = ('package', 'version', 'result', 'rating', 'notes', 'via', 'date', 'steamos', 'lepton', + 'runtime', 'label', 'source', 'id') + + +def compat_report(report): + """A compatibility report for the shared database (compat level only). Free text is + scrubbed; the source is kept only as F-Droid or a public download host.""" + if not report.get('id') or not enabled('compat'): + return False + p = {k: report.get(k) for k in COMPAT_FIELDS if report.get(k) not in (None, '')} + for k, n in (('notes', 1000), ('label', 120), ('version', 80)): + if k in p: + p[k] = scrub(p[k], n) + src = str(p.pop('source', '') or '') + if src == 'F-Droid': + p['source'] = src + elif src.startswith(('http://', 'https://')) and _scrub_url(src) != '': + p['source'] = _scrub_url(src) + return capture('compat_report', p, level='compat') + + +def backfill_compat(): + """On opting in, share the reports this computer kept before (not ones already sent or queued).""" + try: + import frame_compat_db + if frame_compat_db.shared(): + return 0 # the maintainer's copy writes to the database directly + done = set(settings()['compat_sent']) + done |= {e['properties'].get('id') for e in _read_lines(OUTBOX) if e.get('event') == 'compat_report'} + n = 0 + for r in frame_compat_db._outbox(): + if r.get('id') not in done and compat_report(r): + n += 1 + return n + except Exception: + return 0 + + +# ---- the outbox ----------------------------------------------------------------- + +def _read_lines(path): + try: + with open(path) as f: + out = [] + for line in f: + try: + out.append(json.loads(line)) + except ValueError: + pass + return out + except OSError: + return [] + + +def _write_lines(path, rows): + STATE.mkdir(parents=True, exist_ok=True) + tmp = Path(str(path) + '.tmp') + with open(tmp, 'w') as f: + f.writelines(json.dumps(r, ensure_ascii=False) + '\n' for r in rows) + os.replace(tmp, path) + + +def _drop_unwanted(s): + """Unsent events whose level is now off never leave the computer.""" + keep = {level: s[level] for level in LEVELS} + rows = _read_lines(OUTBOX) + kept = [e for e in rows if keep.get(e.get('properties', {}).get('level'), False)] + if len(kept) != len(rows): + _write_lines(OUTBOX, kept) + + +def flush(timeout=20): + """Send what's queued. Returns how many were sent; on failure they stay queued.""" + with _send_lock: + if blocked() or not settings()['notice_shown']: + return 0 + cfg = config() + with _lock: + _drop_unwanted(settings()) + batch = _read_lines(OUTBOX)[:100] + if not batch: + return 0 + body = json.dumps({'api_key': cfg['key'], 'batch': batch}).encode() + req = urllib.request.Request(cfg['host'] + '/batch/', data=body, method='POST', + headers={'content-type': 'application/json', + 'user-agent': f'FrameControl/{app_version()}'}) + try: + with urllib.request.urlopen(req, timeout=timeout) as r: + r.read() + except urllib.error.HTTPError as e: + e.close() + return 0 + except (urllib.error.URLError, OSError, ValueError): + return 0 + sent_ids = {e['uuid'] for e in batch} + with _lock: + _write_lines(OUTBOX, [e for e in _read_lines(OUTBOX) if e.get('uuid') not in sent_ids]) + _write_lines(SENT, (_read_lines(SENT) + batch)[-SENT_KEEP:]) + compat = [e['properties'].get('id') for e in batch if e.get('event') == 'compat_report'] + if compat: # remembered only once PostHog has them, so an opt-out before sending can't lose them + s = settings() + s['compat_sent'] = (s['compat_sent'] + compat)[-5000:] + _save(s) + return len(batch) + + +def start(): + """Record this start and send in the background from now on.""" + global _flusher + try: + app_started() + except Exception: + pass + if _flusher: + return + + def loop(): + while True: + try: + while flush() == 100: # a full batch: there may be more + pass + except Exception: + pass + _wake.wait(FLUSH_EVERY) + _wake.clear() + + _flusher = threading.Thread(target=loop, name='telemetry', daemon=True) + _flusher.start() + + +def wake(): + _wake.set() diff --git a/ui/index.html b/ui/index.html index 475ea72..43d58f9 100644 --- a/ui/index.html +++ b/ui/index.html @@ -44,7 +44,12 @@ .brand { display: flex; align-items: center; gap: 11px; color: var(--bright); text-decoration: none; } .brand svg { width: 30px; height: 30px; } .brand b { font-weight: 700; font-size: 15px; letter-spacing: 2.5px; } - nav { display: flex; gap: 4px; height: 100%; } + /* The tabs shrink and scroll sideways rather than push the header wider than the window. */ + nav { display: flex; gap: 4px; height: 100%; min-width: 0; flex-shrink: 1; overflow-x: auto; scrollbar-width: none; + mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent); } + nav::-webkit-scrollbar { display: none; } + nav a { flex: none; } + @media (max-width: 1600px) { header { gap: 18px; } nav a { padding: 0 9px !important; } } nav a { display: flex; align-items: center; padding: 0 13px; height: 100%; color: var(--text); text-decoration: none; font-weight: 500; font-size: 15px; letter-spacing: 1.2px; text-transform: uppercase; border-bottom: 3px solid transparent; transition: color .15s; } @@ -195,6 +200,17 @@ .actions { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; } .actions button { justify-content: flex-start; height: 40px; } .actions svg { width: 16px; height: 16px; flex: none; opacity: .85; } + .notice { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; padding: 12px 16px; border-radius: 4px; + background: rgba(26,159,255,.12); border-left: 3px solid var(--blue); font-size: 13.5px; line-height: 1.5; } + .notice .grow { flex: 1; min-width: 260px; } + .notice .progress { width: 160px; margin-top: 0; display: block; } + .popt { display: grid; grid-template-columns: auto 1fr; gap: 4px 10px; align-items: start; margin: 0 0 14px; cursor: pointer; } + .popt input { margin: 3px 0 0; width: 16px; height: 16px; accent-color: var(--blue); } + .popt b { font-weight: 600; color: var(--text); } + .popt .sub { grid-column: 2; line-height: 1.45; } + .sentlog { max-height: 260px; overflow: auto; background: rgba(0,0,0,.3); border-radius: 3px; padding: 10px; + font: 11.5px ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; word-break: break-all; margin: 8px 0 0; } + details summary { cursor: pointer; color: var(--link); font-size: 13px; margin-top: 12px; } .links { margin-top: 16px; padding-top: 14px; border-top: 1px solid rgba(255,255,255,.06); font-size: 13px; color: var(--muted); } .links a { color: var(--link); text-decoration: none; } .links a:hover { color: #fff; } .links div { margin: 5px 0; } @@ -221,10 +237,18 @@ .and-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: 22px; align-items: start; } .and-col { display: grid; gap: 22px; align-content: start; } .rep-item .s { white-space: normal; } - #repDlg, #titleDlg, #wiDlg { background: #1e2329; color: var(--text); border: 1px solid rgba(255,255,255,.1); border-radius: 4px; + #bugDlg, #repDlg, #titleDlg, #wiDlg { background: #1e2329; color: var(--text); border: 1px solid rgba(255,255,255,.1); border-radius: 4px; padding: 22px; width: min(560px, 92vw); box-shadow: 0 20px 60px rgba(0,0,0,.6); } - #repDlg::backdrop, #titleDlg::backdrop, #wiDlg::backdrop { background: rgba(0,0,0,.55); } - #repDlg h2, #titleDlg h2, #wiDlg h2 { margin: 0 0 14px; font-size: 15px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--bright); } + #bugDlg::backdrop, #repDlg::backdrop, #titleDlg::backdrop, #wiDlg::backdrop { background: rgba(0,0,0,.55); } + #bugDlg { width: min(640px, calc(100vw - 40px)); } + #bugForm label.field { display: block; font-size: 12.5px; color: var(--muted); margin-top: 10px; } + #bugForm label.field input, #bugForm label.field textarea, #bugForm select { margin-top: 5px; } + #bugForm select { width: 100%; background: rgba(0,0,0,.28); color: var(--text); border: 1px solid transparent; + border-radius: 3px; padding: 8px 10px; font: inherit; } + #bugForm .popt { margin: 14px 0 0; } + #bugForm .sentlog { max-height: 200px; } + #bugWarn { color: var(--muted); font-size: 12.5px; line-height: 1.45; margin: 12px 0 0; } + #bugDlg h2, #repDlg h2, #titleDlg h2, #wiDlg h2 { margin: 0 0 14px; font-size: 15px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--bright); } #repForm label, #titleForm label { display: block; font-size: 12.5px; color: var(--muted); margin-top: 10px; } #repForm label input[type=text], #repForm textarea, #titleForm label input, #titleForm label select { margin-top: 5px; } #titleForm select { width: 100%; background: rgba(0,0,0,.28); color: var(--text); border: 1px solid transparent; @@ -309,16 +333,36 @@ Apps Display Power + Privacy
Connecting… — +
+ +
@@ -543,6 +587,24 @@
FrameDrop: sideloader (Windows only for now)
+ +
+

Privacy & updates

+ + + +
+
Show what's been sent
+
+ + What's collected, exactly
+
@@ -581,6 +643,30 @@ + +
+

Report a problem

+ + + + + + +
Show exactly what's included
Loading…
+

This becomes a public issue on GitHub (saphid/frame-control). No GitHub account is needed.

+
+ + + +
+
+

Add to the Steam library

@@ -1162,7 +1248,8 @@ async function sendFiles(files, dirs = new Set()) { if (!f.size) { toast(`${f.name}: empty files aren't supported`, true); continue; } if (TITLE_EXT.test(f.name)) { await sideload(f, path); continue; } const apk = f.name.toLowerCase().endsWith(".apk"); - await act(apk ? `Install ${f.name}` : `Copy ${f.name} to ~/Downloads`, () => upload(f, apk ? "apk" : "push")); + const res = await act(apk ? `Install ${f.name}` : `Copy ${f.name} to ~/Downloads`, () => upload(f, apk ? "apk" : "push")); + if (apk && res && res.app) await offerTest(res.app); } $("fileInput").value = ""; refresh(); @@ -1544,7 +1631,8 @@ document.body.addEventListener("click", async e => { const pkg = b.dataset.pkg, action = b.dataset.and; if (action === "install") { toast(`Installing ${b.dataset.name}. The first time takes a minute…`); - await act(`Install ${b.dataset.name}`, () => api("/api/android", { action, package: pkg }), b); + const res = await act(`Install ${b.dataset.name}`, () => api("/api/android", { action, package: pkg }), b); + if (res && res.app) await offerTest(res.app); } else if (action === "remove") { if (!confirm(`Remove ${b.dataset.name} and its data from the Frame?`)) return; await act(`Remove ${b.dataset.name}`, () => api("/api/android", { action, package: pkg }), b); @@ -1661,13 +1749,19 @@ loadDisplays(); const RLABEL = { works: "Works", issues: "Problems", broken: "Doesn't work", runs: "Runs (test)", crashes: "Crashed (test)", install_failed: "Won't install", instance_failed: "Didn't start (test)" }; const RCLASS = { works: "works", runs: "works", issues: "maybe" }; +let repShared = false; +function setRepHint() { + $("repHint").textContent = repShared + ? "Reports go to Frame Control's shared compatibility database and change the verdicts in the catalogue. Any APK can be reported, including ones not on F-Droid." + : telemetry.compat && !telemetry.blocked + ? "Reports change the verdicts you see and are shared with Frame Control's compatibility database (Privacy settings). Any APK can be reported, including ones not on F-Droid." + : "Reports are saved on this computer and change the verdicts you see. Turn on Share compatibility results in Privacy settings to add them to the shared database. Any APK can be reported, including ones not on F-Droid."; +} async function loadReports() { let reps, shared; try { ({ reports: reps, shared } = await api("/api/android/reports")); } catch (e) { $("repList").innerHTML = `
${esc(e.message)}
`; return; } - $("repHint").textContent = shared - ? "Reports go to Frame Control's shared compatibility database and change the verdicts in the catalogue. Any APK can be reported, including ones not on F-Droid." - : "Reports are saved on this computer and change the verdicts you see. They aren't uploaded: the shared database is maintainer-only for now. Any APK can be reported, including ones not on F-Droid."; + repShared = shared; setRepHint(); $("repCount").textContent = reps.length ? `${reps.length} newest` : ""; $("repList").innerHTML = reps.length ? reps.slice(0, 40).map(r => { const k = r.rating || r.result; @@ -1820,7 +1914,163 @@ const spy = new IntersectionObserver(entries => { const top = entries.filter(e => e.isIntersecting).sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)[0]; if (top) document.querySelectorAll("nav a").forEach(a => a.classList.toggle("on", a.getAttribute("href") === "#" + top.target.id)); }, { rootMargin: "-80px 0px -55% 0px" }); -["view", "shots", "library", "getgames", "android", "transfer", "apps", "display", "power"].forEach(id => spy.observe($(id))); +["view", "shots", "library", "getgames", "android", "transfer", "apps", "display", "power", "privacy"].forEach(id => spy.observe($(id))); + +// ---- after an APK install: test it, so the compatibility database learns whether it runs ---- +async function offerTest(m) { + if (!m.package || !confirm(`${m.label || m.package} is installed. Test it now?\n\nIt opens in the headset for about 20 seconds ` + + "and records whether it stays up.")) return; + toast(`Testing ${m.label || m.package}: launching it and watching for 20 s…`); + await act(`Test ${m.label || m.package}`, () => api("/api/android", { action: "probe", package: m.package })); + loadReports(); +} + +// ---- privacy: anonymous analytics levels (ui/frame_telemetry.py, docs/privacy.md) ---- +const telemetry = { usage: false, compat: false, blocked: "not loaded" }; +function renderTelemetry(s) { + Object.assign(telemetry, s); + setRepHint(); + $("tUsage").checked = s.usage; $("tCompat").checked = s.compat; $("tDiag").checked = s.diagnostics; + $("tStatus").textContent = s.blocked ? `Nothing is being sent: ${s.blocked}.` + : `${s.queued ? s.queued + " waiting to send. " : ""}Your anonymous id is ${s.id.slice(0, 8)}…; it isn't linked to you or this computer.`; + $("tSent").textContent = s.sent.length ? s.sent.map(e => JSON.stringify({ event: e.event, time: e.timestamp, ...e.properties })).join("\n\n") + : "Nothing sent yet."; + const showNotice = !s.blocked && !s.noticeShown && s.usage; + $("privacyNotice").hidden = !showNotice; + if (showNotice) api("/api/telemetry", { noticeShown: true }).catch(() => {}); +} +async function loadTelemetry() { + try { renderTelemetry(await api("/api/telemetry")); } catch {} +} +async function setTelemetry(change) { + try { renderTelemetry(await api("/api/telemetry", change)); } + catch (e) { toast(`Couldn't save: ${e.message}`, true); loadTelemetry(); } +} +$("tUsage").onchange = e => setTelemetry({ usage: e.target.checked }); +$("tCompat").onchange = e => setTelemetry({ compat: e.target.checked }); +$("tDiag").onchange = e => setTelemetry({ diagnostics: e.target.checked }); +$("tSentBox").ontoggle = () => { if ($("tSentBox").open) loadTelemetry(); }; +$("noticeOk").onclick = () => { $("privacyNotice").hidden = true; }; +$("noticeMore").onclick = async () => { + $("privacyNotice").hidden = true; + await setTelemetry({ compat: true, diagnostics: true }); + toast("Thanks! Compatibility results and error details will be shared too. Change it any time in Privacy."); +}; +$("noticeSettings").onclick = () => { $("privacyNotice").hidden = true; location.hash = "#privacy"; $("privacy").scrollIntoView(); }; +loadTelemetry(); +function pageEvent(event, properties) { + if (telemetry.usage && !telemetry.blocked) api("/api/telemetry/event", { event, properties }).catch(() => {}); +} +// Which tabs get used: once per tab per session. +const tabsSeen = new Set(); +document.querySelectorAll("nav a").forEach(a => a.addEventListener("click", () => { + const tab = a.getAttribute("href").slice(1); + if (!tabsSeen.has(tab)) { tabsSeen.add(tab); pageEvent("tab_viewed", { tab }); } +})); + +// ---- report a problem (ui/frame_report.py): a GitHub issue via the website, with diagnostics ---- +const bug = { opened: 0, preview: "" }; +const activityLines = () => [...$("log").children].slice(0, 25).map(el => el.textContent.trim()); +function bugReportText() { + const body = `${$("bugText").value.trim()}${bug.preview ? "\n\n---\nDiagnostics:\n```\n" + bug.preview + "\n```" : ""}`; + return { title: $("bugTitleIn").value.trim(), body }; +} +function bugGithubUrl() { + const { title, body } = bugReportText(); + return "https://github.com/saphid/frame-control/issues/new?labels=feedback&title=" + encodeURIComponent(title) + + "&body=" + encodeURIComponent(body.slice(0, 6000)); +} +// The preview is a snapshot: exactly this text is sent, even if more activity happens meanwhile. +async function loadBugPreview() { + if (!$("bugDiag").checked) { bug.preview = ""; $("bugDiagText").textContent = "Nothing: diagnostics are off."; return; } + $("bugDiagText").textContent = "Loading…"; + try { bug.preview = (await api("/api/report/preview", { activity: activityLines(), includeLogs: $("bugLogs").checked })).text; } + catch (e) { bug.preview = ""; $("bugDiagText").textContent = `Couldn't collect diagnostics: ${e.message}`; return; } + $("bugDiagText").textContent = bug.preview; +} +function openBugReport() { + $("bugForm").reset(); + $("bugMsg").textContent = ""; $("bugGithubLink").hidden = true; $("bugSend").disabled = false; + $("bugCancel").textContent = "Cancel"; + $("bugDiagBox").open = false; $("bugLogs").disabled = false; + bug.opened = performance.now(); + $("bugDlg").showModal(); + loadBugPreview(); +} +document.body.addEventListener("click", e => { if (e.target.closest("[data-report]")) openBugReport(); }); +$("bugDiag").onchange = () => { $("bugLogs").disabled = !$("bugDiag").checked; loadBugPreview(); }; +$("bugLogs").onchange = loadBugPreview; +$("bugCancel").onclick = () => $("bugDlg").close(); +$("bugCopy").onclick = async () => { + const { title, body } = bugReportText(); + try { await navigator.clipboard.writeText(`${title}\n\n${body}`); $("bugMsg").textContent = "Copied."; } + catch { $("bugMsg").textContent = "Couldn't copy; select the text under Show exactly what's included."; } +}; +$("bugForm").onsubmit = async e => { + e.preventDefault(); + if (!$("bugForm").reportValidity()) return; + $("bugSend").disabled = true; $("bugMsg").textContent = "Sending…"; + try { + const res = await api("/api/report", { + kind: $("bugKind").value, title: $("bugTitleIn").value, message: $("bugText").value, + github: $("bugGithub").value, diagnostics: $("bugDiag").checked ? bug.preview : "", + elapsed: Math.round(performance.now() - bug.opened) }); + $("bugMsg").innerHTML = `Sent. Issue #${esc(String(res.number))} on GitHub.`; + $("bugCancel").textContent = "Close"; + log(res.message, "ok"); + } catch (err) { + $("bugMsg").textContent = `Couldn't send it: ${err.message}.`; + $("bugGithubLink").href = bugGithubUrl(); $("bugGithubLink").hidden = false; + $("bugSend").disabled = false; + } +}; +if (window.frameApp && window.frameApp.onReportProblem) window.frameApp.onReportProblem(openBugReport); + +// ---- updates (the desktop app only: app/updater.js) ---- +const upd = { dismissed: false, offered: null }; +function renderUpdate(s) { + if (!s) return; + $("appVersion").textContent = `Version ${s.current}`; + const r = s.latest; + const show = r && ["available", "downloading", "ready", "error"].includes(s.status) && !upd.dismissed + && !(s.status === "error" && !r); + $("updateBar").hidden = !show; + if (!show) return; + if (s.status === "available" && upd.offered !== r.version) { upd.offered = r.version; pageEvent("update_offered", { to_version: r.version }); } + const busy = s.status === "downloading" || s.status === "ready"; + $("updateText").innerHTML = s.status === "error" + ? `Updating to ${esc(r.version)} didn't work: ${esc(s.error || "unknown error")}` + : busy ? `Downloading Frame Control ${esc(r.version)}… It restarts when it's ready.` + : `Frame Control ${esc(r.version)} is available. You have ${esc(s.current)}.` + + (s.canInstall ? "" : ` ${esc(s.why ? "It can't update itself here (" + s.why + ")," : "")} download it from the release page.`); + $("updateProg").hidden = !busy; + $("updateProg").firstElementChild.style.width = Math.round((s.progress || 0) * 100) + "%"; + $("updateGo").textContent = s.canInstall ? (s.status === "error" ? "Try again" : "Update and restart") : "Open release page"; + $("updateGo").disabled = busy; $("updateLater").hidden = busy; +} +if (window.frameApp && window.frameApp.update) { + window.frameApp.update.onState(renderUpdate); + window.frameApp.update.get().then(renderUpdate); + $("updateCheck").hidden = false; + $("updateCheck").onclick = async () => { + const btn = $("updateCheck"); + btn.disabled = true; + let s; + try { s = await window.frameApp.update.check(); } finally { btn.disabled = false; } + upd.dismissed = false; renderUpdate(s); + if (s && s.status === "none") toast(`You have the newest version (${s.current})`); + if (s && s.status === "check-failed") toast(`Couldn't check for updates: ${s.error}`, true); + }; + $("updateGo").onclick = () => { + pageEvent("update_started", { to_version: upd.offered || "" }); + window.frameApp.update.install(); + }; + $("updateLater").onclick = () => { upd.dismissed = true; $("updateBar").hidden = true; }; + $("updateNotes").onclick = async () => { + const s = await window.frameApp.update.get(); + if (s && s.latest) window.open(s.latest.page, "_blank"); + }; +} // ---- install links from websites (frame-control://install, docs/web-install.md) ---- // The app passes each link here. The server checks it and reads the manifest; diff --git a/ui/server.py b/ui/server.py index 05661e3..58e8d57 100755 --- a/ui/server.py +++ b/ui/server.py @@ -36,7 +36,9 @@ sys.path.insert(0, str(Path(__file__).resolve().parent)) import frame_android # noqa: E402 import frame_catalog # noqa: E402 import frame_host # noqa: E402 +import frame_report # noqa: E402 import frame_store # noqa: E402 +import frame_telemetry # noqa: E402 import frame_titles # noqa: E402 import frame_webinstall # noqa: E402 @@ -153,7 +155,12 @@ def terminal(argv): # ---- actions --------------------------------------------------------------- def status(_body): - return json.loads(ssh("python3 -", stdin=(HERE / "frame_status.py").read_text(), timeout=20)) + s = json.loads(ssh("python3 -", stdin=(HERE / "frame_status.py").read_text(), timeout=20)) + osr = s.get("os") if isinstance(s, dict) else None + if isinstance(osr, dict): + frame_telemetry.frame_seen(osr.get("build"), osr.get("version")) + frame_report.frame.update(build=osr.get("build"), version=osr.get("version")) + return s def headset_view(): @@ -335,7 +342,16 @@ def steam(body): raise Failure("bad appid", 400) if action not in ("install", "store"): raise Failure("action must be install or store", 400) - return steam_frame(action, appid) + if action == "store": + return steam_frame(action, appid) + # Starts Steam's download; Steam reports the rest in the headset. + try: + res = steam_frame(action, appid) + except Failure as e: + frame_telemetry.install_finished("steam", False, error=e, steam_appid=appid) + raise + frame_telemetry.install_finished("steam", True, steam_appid=appid) + return res def steam_search(query): @@ -408,10 +424,16 @@ def flatpak(body): if not FLATPAK_ID.match(app): raise Failure("bad Flatpak app ID", 400) if action == "install": - # Per-user, so it survives SteamOS updates and needs no sudo (as install-apps.sh). - ssh("flatpak remote-add --user --if-not-exists flathub " - "https://dl.flathub.org/repo/flathub.flatpakrepo && " - f"flatpak install --user -y --noninteractive flathub {shlex.quote(app)}", timeout=900) + start = time.time() + try: + # Per-user, so it survives SteamOS updates and needs no sudo (as install-apps.sh). + ssh("flatpak remote-add --user --if-not-exists flathub " + "https://dl.flathub.org/repo/flathub.flatpakrepo && " + f"flatpak install --user -y --noninteractive flathub {shlex.quote(app)}", timeout=900) + except Failure as e: + frame_telemetry.install_finished("flatpak", False, time.time() - start, e, flatpak_id=app) + raise + frame_telemetry.install_finished("flatpak", True, time.time() - start, flatpak_id=app) return {"message": f"Installed {app}"} if action == "uninstall": out = ssh(f"flatpak uninstall --user -y -- {shlex.quote(app)}", timeout=300) @@ -468,13 +490,37 @@ def android(body): runtime=body.get("runtime") or "instance", label=body.get("label"), source=body.get("source")) name = r.get("label") or pkg - where = "" if frame_catalog.compat_db.shared() else " on this computer" + where = ("" if frame_catalog.compat_db.shared() else + " and shared it" if frame_telemetry.enabled("compat") else " on this computer") return {"message": f"Saved your report for {name}{where}", "report": r} except frame_android.FrameError as e: raise Failure(str(e)) raise Failure("unknown action", 400) +# Errors that are the APK's own fault, so they belong in the compatibility +# database as install_failed. Connection trouble and the like don't. +APK_FAULTS = {"android_installer", "apk_needs_newer_android", "apk_wrong_abi"} + + +def apk_installed(info, meta, error, seconds): + """Every APK install (catalogue, dropped file, web link): usage analytics, and an + install_failed report when the APK itself wouldn't install.""" + pkg = (info or {}).get("package") + by_pkg = frame_catalog._cache.get("by_pkg") or {} + in_catalog = bool(pkg) and pkg in by_pkg + # Package names only for catalogue apps, which are public; a private APK's name stays here. + # No version: a local rebuild can share a catalogue app's package name but carry anything in its version. + frame_telemetry.install_finished("apk", error is None, seconds, error, catalog=in_catalog, + package=pkg if in_catalog else None) + if error is not None and pkg and frame_telemetry.categorize(error)[0] in APK_FAULTS: + frame_catalog.add_report(pkg, info.get("version"), result="install_failed", notes=str(error)[:300], + via="install", label=info.get("label")) + + +frame_android.install_hooks.append(apk_installed) + + # ---- Sideloaded titles (Linux/Windows builds as Steam Devkit Games) -------- # # Installing is two steps: inspect (a dropped file is uploaded and a zip @@ -528,14 +574,18 @@ def _run_title_install(token, entry, name, exe, runtime): with _titles_lock: _title_jobs[token].update(fields) + start = time.time() try: m = frame_titles.install_plan(entry["plan"], name=name, exe=exe, runtime=runtime, progress=lambda stage, fraction: update(stage=stage, fraction=fraction)) update(title=m, message=f"Installed {m['id']} in the Steam library ({m['runtime_label']})") + frame_telemetry.install_finished("title", True, time.time() - start, runtime=m.get("runtime")) except frame_android.FrameError as e: update(error=str(e)) + frame_telemetry.install_finished("title", False, time.time() - start, e) except Exception as e: update(error=f"{type(e).__name__}: {e}") + frame_telemetry.install_finished("title", False, time.time() - start, e) finally: _drop_staged(entry) update(done=True, time=time.time()) @@ -964,10 +1014,16 @@ def _webinstall_run(plan, job): ensure_master() res = frame_webinstall.dispatch(path, name=plan["name"], exe=plan["exe"], progress=detail, source=plan["url"]) job["message"], job["phase"] = res["message"], "done" + if res.get("kind") != "apk": # APKs are counted by apk_installed + frame_telemetry.install_finished("web", True, kind_detail=res.get("kind")) except Exception as e: + stage = job.get("phase") # download or install, before it becomes "error" known = (frame_webinstall.WebInstallError, Failure, frame_android.FrameError) job["error"] = str(e) if isinstance(e, known) else f"{type(e).__name__}: {e}" job["phase"] = "error" + # An APK that failed to install was counted by apk_installed. + if not isinstance(e, frame_webinstall.Cancelled) and not (stage == "install" and plan.get("kind") == "apk"): + frame_telemetry.install_finished("web", False, error=e, stage=stage, kind_detail=plan.get("kind")) finally: with _web_lock: job.pop("_conn", None) @@ -1066,14 +1122,36 @@ def _sweep_one(prefix, d): pass +# ---- Report a problem (frame_report.py) -------------------------------------- + +def report_preview(body): + """Exactly the diagnostics a report would include, for the dialog to show first.""" + return {"text": frame_report.diagnostics(body.get("activity") or (), include_logs=bool(body.get("includeLogs")))} + + +def report_send(body): + try: + return frame_report.send(body) + except frame_report.ReportError as e: + raise Failure(str(e)) + + POST = {"/api/android/display": android_display, "/api/android": android, "/api/titles": titles, "/api/launch": launch, "/api/steam": steam, "/api/volume": set_volume, "/api/clipboard": clipboard, "/api/flatpak": flatpak, "/api/open": open_thing, "/api/shots/save": save_shots, "/api/webinstall/check": webinstall_check, "/api/webinstall/start": webinstall_start, - "/api/webinstall/cancel": webinstall_cancel} + "/api/webinstall/cancel": webinstall_cancel, + "/api/telemetry": frame_telemetry.update_settings, "/api/telemetry/event": frame_telemetry.page_event, + "/api/report/preview": report_preview, "/api/report": report_send} # ---- HTTP ------------------------------------------------------------------ +def action_of(body): + """The action a request asked for, for diagnostics: a short word, never user data.""" + a = body.get("action") if isinstance(body, dict) else None + return a if isinstance(a, str) and re.fullmatch(r"[a-z]{1,20}", a) else "" + + def _pipe_reader(pipe): """Chunks from a pipe via a thread; select() can't wait on pipes on Windows.""" chunks = queue.Queue() # unbounded: the pump never blocks, so it ends at EOF @@ -1182,6 +1260,8 @@ class Handler(BaseHTTPRequestHandler): "shared": frame_catalog.compat_db.shared()}) elif path == "/api/android/catalog": self.send_json({"apps": frame_catalog.catalog()}) + elif path == "/api/telemetry": + self.send_json(frame_telemetry.state()) elif path == "/api/status": self.send_json(status({})) elif path == "/api/steam/owned": @@ -1208,12 +1288,14 @@ class Handler(BaseHTTPRequestHandler): except frame_android.FrameError as e: self.send_json({"error": str(e)}, 502) except Exception as e: + frame_telemetry.diagnostic(f"GET {path}", e) self.send_json({"error": f"{type(e).__name__}: {e}"}, 500) def do_POST(self): if not self.local_request(): return path = urlparse(self.path).path + body = None try: if path == "/api/upload": self.send_json(self.upload()) @@ -1230,12 +1312,16 @@ class Handler(BaseHTTPRequestHandler): raise Failure("request body must be a JSON object", 400) self.send_json(handler(body)) except Failure as e: + if e.status >= 500: + frame_telemetry.diagnostic(f"POST {path} {action_of(body)}", e) self.send_json({"error": str(e)}, e.status) except (ValueError, TypeError) as e: self.send_json({"error": f"bad request: {e}"}, 400) except frame_android.FrameError as e: + frame_telemetry.diagnostic(f"POST {path} {action_of(body)}", e) self.send_json({"error": str(e)}, 502) except Exception as e: + frame_telemetry.diagnostic(f"POST {path} {action_of(body)}", e) self.send_json({"error": f"{type(e).__name__}: {e}"}, 500) def stream_video(self, query): @@ -1356,6 +1442,7 @@ def main(): args = ap.parse_args() httpd = ThreadingHTTPServer(("127.0.0.1", args.port), Handler) sweep_tmp() + frame_telemetry.start() if not frame_host.WINDOWS: signal.signal(signal.SIGTERM, lambda *_: (_ for _ in ()).throw(KeyboardInterrupt)) if args.exit_on_eof: diff --git a/ui/telemetry.json b/ui/telemetry.json new file mode 100644 index 0000000..3c89053 --- /dev/null +++ b/ui/telemetry.json @@ -0,0 +1,5 @@ +{ + "host": "https://eu.i.posthog.com", + "key": "", + "project": "" +} From f10282294d45934d99e834d55de35db718c867c8 Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:47:35 +1000 Subject: [PATCH 11/36] Fix the cross-provider review's findings on VR APK support patch() turns corrupt-manifest struct/index errors into FrameError; a missing layer build says so; the signing key falls back to a rename where hard links aren't supported and explains how to recover from a bad cached key; the layer nulls an instance it can't destroy and logs xrLocateSpaces once. Not changed: the 1.1 Meta profile names match xr.xml's promoted names (meta/touch_pro_controller, meta/touch_plus_controller), and grip_surface is palm_ext renamed, so the rewrite stays (now commented). Co-Authored-By: Claude Opus 5.5 (1M context) --- frame/openxr-compat/README.md | 2 +- frame/openxr-compat/compat_logic.h | 1 + frame/openxr-compat/layer.cpp | 7 +++++-- .../arm64-v8a/libXrApiLayer_FRAME_compat.so | Bin 98936 -> 99864 bytes tests/test_frame_apk_vr.py | 14 ++++++++++++++ ui/frame_android.py | 11 +++++++---- ui/frame_apk_sign.py | 13 +++++++++---- 7 files changed, 37 insertions(+), 11 deletions(-) diff --git a/frame/openxr-compat/README.md b/frame/openxr-compat/README.md index 4d5eb60..5978979 100644 --- a/frame/openxr-compat/README.md +++ b/frame/openxr-compat/README.md @@ -238,7 +238,7 @@ from the binary; runtime execution of those paths remains unverified. Prebuilt library SHA-256: ``` -aace99b7bcdb3c79fa844d75a9483f8f4b16bd6e669fb3003515df8c673ce54b +479d31c374f137906e03f73209b581d65d7e6a41b8476e6425fa55a6aa40bd68 ``` APK SHA-256: diff --git a/frame/openxr-compat/compat_logic.h b/frame/openxr-compat/compat_logic.h index a5c6e57..9d94fbc 100644 --- a/frame/openxr-compat/compat_logic.h +++ b/frame/openxr-compat/compat_logic.h @@ -35,6 +35,7 @@ inline ExtensionPlan extensions(const Names& requested, const Names& available, } return p; } +// 1.1's grip_surface is XR_EXT_palm_pose's palm_ext renamed (same pose), not the grip pose. inline std::string rewritePath(std::string p, bool palm) { const std::string suffix = "/input/grip_surface/pose"; if (palm && (p == "/user/hand/left" + suffix || p == "/user/hand/right" + suffix)) diff --git a/frame/openxr-compat/layer.cpp b/frame/openxr-compat/layer.cpp index a5433a7..79b858c 100644 --- a/frame/openxr-compat/layer.cpp +++ b/frame/openxr-compat/layer.cpp @@ -121,7 +121,8 @@ XrResult XRAPI_CALL destroySession(XrSession s) try { XrResult XRAPI_CALL locateSpaces(XrSession s, const XrSpacesLocateInfo* in, XrSpaceLocations* out) try { auto d = get(s); if (!d) return XR_ERROR_HANDLE_INVALID; if (!d->locate) return XR_ERROR_FUNCTION_UNSUPPORTED; - LOG("xrLocateSpaces -> xrLocateSpacesKHR (core/KHR type aliases)"); + static std::once_flag logged; // called every frame + std::call_once(logged, [] { LOG("xrLocateSpaces -> xrLocateSpacesKHR (core/KHR type aliases)"); }); return frame::locate(d->locate, s, in, out); } GUARD_END XrResult XRAPI_CALL threadSettings(XrSession s, XrAndroidThreadTypeKHR type, uint32_t tid) try { @@ -226,7 +227,9 @@ XrResult XRAPI_CALL createLayer(const XrInstanceCreateInfo* info, const XrApiLay d->gipa = next; d->instance = *instance; d->destroy = proc(next, *instance, "xrDestroyInstance"); // A successful runtime instance must expose xrDestroyInstance. - if (!d->destroy) { LOG("invalid downstream: missing xrDestroyInstance"); return XR_ERROR_INITIALIZATION_FAILED; } + if (!d->destroy) { // nothing could ever destroy it, so don't hand it out + LOG("invalid downstream: missing xrDestroyInstance"); *instance = XR_NULL_HANDLE; return XR_ERROR_INITIALIZATION_FAILED; + } try { d->enabled = std::move(plan.downstream); d->stubbed = std::move(plan.stubbed); d->palm = frame::has(d->enabled, "XR_EXT_palm_pose"); diff --git a/frame/openxr-compat/prebuilt/arm64-v8a/libXrApiLayer_FRAME_compat.so b/frame/openxr-compat/prebuilt/arm64-v8a/libXrApiLayer_FRAME_compat.so index a58721a065638dde82d73ca8f733cde852743df4..608cdd13bf350b8be3133f782a78d0342c888f3d 100755 GIT binary patch delta 27945 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 Zl7W2EGMn!zGR*DL3aOdcCafS4{|{UVG422W delta 27385 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 Date: Mon, 28 Sep 2026 19:44:44 +1000 Subject: [PATCH 12/36] Report APKs refused before install; offer a compatibility test after installing an alternative Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/test_frame_apk_versions.py | 23 +++++++++++++++++++++++ ui/index.html | 1 + ui/server.py | 5 +++++ 3 files changed, 29 insertions(+) diff --git a/tests/test_frame_apk_versions.py b/tests/test_frame_apk_versions.py index 8cf7701..53f1511 100644 --- a/tests/test_frame_apk_versions.py +++ b/tests/test_frame_apk_versions.py @@ -256,5 +256,28 @@ class UploadVersionsTest(unittest.TestCase): ssh.assert_not_called() + def test_blocked_uploads_are_reported_like_failed_installs(self): + import server + info = {'package': 'org.example.app', 'label': 'Example', 'version': '5', + 'version_code': 5, 'min_sdk': 33, 'abis': [], 'icon_png': None} + for apk_info, expected_info in ((dict(info), 'org.example.app'), + (frame_android.FrameError('not an APK'), None)): + handler = object.__new__(server.Handler) + handler.headers = {'X-Filename': 'app.apk', 'X-Mode': 'apk', 'Content-Length': '1'} + handler.rfile = io.BytesIO(b'x') + calls = [] + patch_info = (patch.object(frame_android, 'apk_info', side_effect=apk_info) + if isinstance(apk_info, Exception) else + patch.object(frame_android, 'apk_info', return_value=apk_info)) + with patch_info, patch.object(frame_android, 'install_hooks', [lambda *a: calls.append(a)]), \ + patch.object(server, 'ensure_master'): + with self.assertRaises(server.Failure): + handler.upload() + self.assertEqual(len(calls), 1) + got_info, meta, error, _ = calls[0] + self.assertEqual((got_info or {}).get('package'), expected_info) + self.assertIsNone(meta) + self.assertIsInstance(error, frame_android.FrameError) + if __name__ == '__main__': unittest.main() diff --git a/ui/index.html b/ui/index.html index 82e6437..92ec81a 100644 --- a/ui/index.html +++ b/ui/index.html @@ -1545,6 +1545,7 @@ function showApkAlternatives(reason, result) { if (res) $("apkAltDlg").close(); else { b.disabled = false; b.textContent = "Install"; } await loadAndroid(); if (cat.apps) { const y = window.scrollY; filterCatalog(); window.scrollTo(0, y); } + if (res && res.app) await offerTest(res.app); }; if (!$("apkAltDlg").open) $("apkAltDlg").showModal(); } diff --git a/ui/server.py b/ui/server.py index 8a04aa0..23aa600 100755 --- a/ui/server.py +++ b/ui/server.py @@ -1595,13 +1595,18 @@ class Handler(BaseHTTPRequestHandler): keep = True # stage_title owns tmp now, and removes it on failure return stage_title(str(dest), temp_dir=str(tmp)) if mode == "apk": + # Checked here, before install(), to hand the page a blocker it can offer + # alternatives for; report these failures the way install() would have. + start = time.time() try: info = frame_android.apk_info(str(dest)) except frame_android.FrameError as e: + frame_android._after_install(None, None, e, start) raise Failure(str(e), 400) try: frame_android.check_installable(info) except frame_android.FrameError as e: + frame_android._after_install(info, None, e, start) raise Failure(str(e), 400, {"package": info["package"], "version_code": info.get("version_code"), "blocker": str(e)}) ensure_master() try: From e67802f15d973940545d84fdca0e3849f4c8d4b8 Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:11:48 +1000 Subject: [PATCH 13/36] Tests: keep the blocked-upload test from reaching real install reporting Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/test_frame_apk_versions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_frame_apk_versions.py b/tests/test_frame_apk_versions.py index 53f1511..508ddcf 100644 --- a/tests/test_frame_apk_versions.py +++ b/tests/test_frame_apk_versions.py @@ -242,6 +242,7 @@ class UploadVersionsTest(unittest.TestCase): handler.rfile = io.BytesIO(b'x') with patch.object(frame_android, 'apk_info', return_value=dict(info)), \ patch.object(versions, 'alternatives', side_effect=AssertionError('lookup during upload')) as lookup, \ + patch.object(frame_android, 'install_hooks', []), \ patch.object(server, 'ensure_master') as ssh: if mode == 'apkinfo': reply = handler.upload() From f0765277220219382a6302de9c73b8ac5efb4f72 Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:14:36 +1000 Subject: [PATCH 14/36] Send analytics to the existing PostHog project; make bug reports private - Analytics go to the maintainer's PostHog US project 343535, tagged $lib = frame-control. Every event carries $ip 0.0.0.0, since PostHog stores the sender's address otherwise (checked live), including events queued by earlier versions. - Report a problem sends a private problem_report event to PostHog instead of a public GitHub issue, with its own random id so a contact address can't be linked to analytics. The dialog asks how to reach the person and shows a reference. Maintainers read reports on the PostHog dashboard or with `python3 ui/frame_report.py inbox`. - Community sync pages by timestamp in UTC: PostHog refuses OFFSET for personal API keys. Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 2 +- docs/privacy.md | 34 ++++++++----- tests/test_telemetry.py | 92 ++++++++++++++++++++++++--------- ui/frame_compat_db.py | 20 +++++--- ui/frame_report.py | 110 ++++++++++++++++++++++------------------ ui/frame_telemetry.py | 59 ++++++++++++++------- ui/index.html | 30 ++++------- ui/telemetry.json | 6 +-- 8 files changed, 220 insertions(+), 133 deletions(-) diff --git a/README.md b/README.md index c4574f7..a87d650 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ This is a first public test, so reports are really useful, especially from Windows and Linux. The quickest way is **Report a problem** in the app (the warning-sign button at the top, or **Help → Report a Problem…**). It adds diagnostics with personal details removed, shows you exactly what's included, -and opens an issue here. You don't need a GitHub account. Without the app, +and sends it privately to the maintainer; nothing is published. Without the app, use the [feedback form](https://frame-control.pages.dev/feedback/). Please include: - what you tried and what happened diff --git a/docs/privacy.md b/docs/privacy.md index af44eae..db7004f 100644 --- a/docs/privacy.md +++ b/docs/privacy.md @@ -1,7 +1,7 @@ # Privacy and analytics Frame Control sends anonymous analytics to [PostHog](https://posthog.com) -(EU cloud) so the maintainer can see how many people use it, which features +(US cloud) so the maintainer can see how many people use it, which features matter and where installs fail. You choose how much in **Privacy & updates**, the last panel on the page. `ui/frame_telemetry.py` is the whole implementation. @@ -28,8 +28,9 @@ computer, exactly as they were sent. its data folder (`telemetry/settings.json`). It isn't derived from your computer, account or network. To get a new one, delete that file. - Events are sent without person profiles (`$process_person_profile: false`) - and without location lookup (`$geoip_disable: true`). The PostHog project - is also set to discard client IP addresses. + and without location lookup (`$geoip_disable: true`). Each carries a + placeholder address (`$ip: 0.0.0.0`), so PostHog stores that instead of + yours. - Every event includes the app version, OS name (macOS, Windows or Linux), CPU architecture and Python version. @@ -97,10 +98,14 @@ The same error is sent at most once every 10 minutes. ## Report a problem **Report a problem** is the warning-sign button in the header, also in the -Privacy panel and under **Help → Report a Problem…**. It files a public issue -on [GitHub](https://github.com/saphid/frame-control/issues) through the -website's feedback service, so no GitHub account is needed. It works whatever -the analytics settings are, because the person sends it deliberately. +Privacy panel and under **Help → Report a Problem…**. It sends the report +privately to Frame Control's PostHog project as a `problem_report` event, the +same way as the analytics above, so only the maintainer can read it and +nothing is published. It works whatever the analytics settings are, because +the person sends it deliberately. The report has the kind, title and text you +wrote, how to reach you if you gave it, a short reference shown after sending, +and the diagnostics below. It has its own random id, so it isn't linked to +your analytics events. With **Include diagnostics** ticked (the default), the report adds: @@ -114,10 +119,14 @@ because those lines can name files and apps. When ticked, it adds the newest Activity lines and server log lines, without the request lines. Everything is scrubbed like error details and limited to what fits in the -issue. Environment details are kept first, then the newest lines. **Show +report. Environment details are kept first, then the newest lines. **Show exactly what's included** shows the snapshot that will be sent, and later -activity isn't added to it. If the service can't be reached, the dialog offers -**Copy report** and **Open on GitHub instead**, a prefilled issue. +activity isn't added to it. If PostHog can't be reached, **Copy report** puts +the whole report on the clipboard. + +The maintainer reads reports on the Frame Control dashboard in PostHog, or +with `python3 ui/frame_report.py inbox [days]`, which uses the same personal +API key as `frame_compat_db.py sync`. ## Turning it all off @@ -128,6 +137,7 @@ never sends anything unless `FRAME_CONTROL_TELEMETRY=1` is set. ## Update checks The desktop app asks GitHub for the latest release shortly after starting, -then every 6 hours (`api.github.com/repos/saphid/frame-control/releases/latest`). -That request carries no id. To stop it, set +then every 6 hours: the latest release's `update.json` on GitHub, or +`api.github.com/repos/saphid/frame-control/releases/latest` if that fails. +Those requests carry no id. To stop it, set `FRAME_CONTROL_NO_UPDATE_CHECK=1`. See [releasing.md](releasing.md). diff --git a/tests/test_telemetry.py b/tests/test_telemetry.py index 19043bc..6a033c7 100644 --- a/tests/test_telemetry.py +++ b/tests/test_telemetry.py @@ -322,18 +322,17 @@ class Regressions(Base): class ReportProblem(Base): - """Report a problem: diagnostics are scrubbed and fit the feedback API; sending goes to it.""" + """Report a problem: diagnostics are scrubbed and bounded; the report goes privately to PostHog.""" - def serve(self, status=201, reply=None): + def serve(self, status=200): got = [] class H(BaseHTTPRequestHandler): def do_POST(self): - got.append(json.loads(self.rfile.read(int(self.headers["Content-Length"])))) + got.append((self.path, json.loads(self.rfile.read(int(self.headers["Content-Length"]))))) self.send_response(status) self.end_headers() - self.wfile.write(json.dumps(reply or {"ok": True, "number": 42, - "url": "https://github.com/saphid/frame-control/issues/42"}).encode()) + self.wfile.write(b'{"status":"Ok"}') def log_message(self, *a): pass @@ -342,7 +341,7 @@ class ReportProblem(Base): threading.Thread(target=httpd.serve_forever, daemon=True).start() self.addCleanup(httpd.server_close) self.addCleanup(httpd.shutdown) - p = mock.patch.object(fr, "FEEDBACK_URL", f"http://127.0.0.1:{httpd.server_port}/api/feedback") + p = mock.patch.dict(os.environ, {"FRAME_CONTROL_POSTHOG_HOST": f"http://127.0.0.1:{httpd.server_port}"}) p.start() self.addCleanup(p.stop) return got @@ -358,11 +357,12 @@ class ReportProblem(Base): for leaked in ("alice", "192.168.1.9", str(Path.home()), "bob", "pw@"): self.assertNotIn(leaked, text) - def test_a_report_fits_the_api_limit_in_utf16_units(self): - body = {"title": "Live view stops", "message": "It stops 😀 " * 600, "diagnostics": "log 😀 line\n" * 2000} - title, message = fr.compose(body) - self.assertLessEqual(fr.u16(message), fr.MESSAGE_MAX) - self.assertTrue(message.startswith("It stops")) + def test_a_report_is_bounded_in_utf16_units(self): + body = {"title": "Live view stops", "message": "It stops 😀 " * 800, "diagnostics": "log 😀 line\n" * 2000} + title, text, diag = fr.compose(body) + self.assertLessEqual(fr.u16(text), fr.TEXT_MAX) + self.assertLessEqual(fr.u16(diag), fr.DIAG_MAX) + self.assertTrue(text.startswith("It stops")) with self.assertRaises(ValueError): fr.compose({"title": "hi", "message": "It stops after a minute."}) @@ -381,24 +381,66 @@ class ReportProblem(Base): def test_the_previewed_diagnostics_are_what_is_sent(self): got = self.serve() - fr.send({"title": "Live view stops", "message": "It stops after a minute.", "elapsed": 9000, + fr.send({"title": "Live view stops", "message": "It stops after a minute.", "diagnostics": "Frame Control 9.9.9\nssh janes-mac.tail12345.ts.net failed"}) - self.assertIn("Frame Control 9.9.9", got[0]["message"]) - self.assertNotIn("janes-mac", got[0]["message"]) + diag = got[0][1]["batch"][0]["properties"]["diagnostics"] + self.assertIn("Frame Control 9.9.9", diag) + self.assertNotIn("janes-mac", diag) - def test_send_files_an_issue_and_reports_its_number(self): + def test_send_is_a_private_posthog_event_whatever_the_settings(self): got = self.serve() - res = fr.send({"kind": "bug", "title": "Live view stops", "message": "It stops after a minute.", - "elapsed": 9000, "github": "@someone"}) - self.assertEqual((res["number"], res["url"]), (42, "https://github.com/saphid/frame-control/issues/42")) - sent = got[0] - self.assertEqual((sent["kind"], sent["github"], sent["website"], sent["elapsed"]), ("bug", "someone", "", 9000)) - self.assertEqual(sent["message"], "It stops after a minute.") + tm.update_settings({"usage": False}) # analytics off: a deliberate report still goes + res = fr.send({"kind": "idea", "title": "Live view stops", "message": "It stops after a minute.", + "contact": "me@example.com"}) + path, body = got[0] + event = body["batch"][0] + self.assertEqual((path, body["api_key"], event["event"]), ("/batch/", "phc_test", "problem_report")) + props = event["properties"] + self.assertEqual((props["kind"], props["title"], props["message"], props["contact"], props["report_id"]), + ("idea", "Live view stops", "It stops after a minute.", "me@example.com", res["id"])) + self.assertEqual((props["$process_person_profile"], props["$geoip_disable"]), (False, True)) + self.assertNotEqual(event["distinct_id"], tm.settings()["id"]) # not linked to the analytics + self.assertIn(res["id"], res["message"]) + self.assertEqual([e["event"] for e in tm._read_lines(tm.SENT)], ["problem_report"]) - def test_the_services_error_is_passed_on(self): - self.serve(status=429, reply={"error": "That's a lot of feedback in one hour."}) - with self.assertRaisesRegex(fr.ReportError, "a lot of feedback"): - fr.send({"title": "Live view stops", "message": "It stops after a minute.", "elapsed": 9000}) + def test_a_sent_report_is_not_an_error_if_the_local_log_fails(self): + self.serve() + with mock.patch.object(tm, "record_sent", side_effect=OSError("disk full")): + res = fr.send({"title": "Live view stops", "message": "It stops after a minute."}) + self.assertTrue(res["id"]) + + def test_events_queued_by_older_versions_get_the_placeholder_address(self): + got = self.serve() + tm.update_settings({"noticeShown": True}) + tm._write_lines(tm.OUTBOX, [{"event": "app_opened", "distinct_id": "x", "uuid": "u1", + "properties": {"level": "usage"}}]) + self.assertEqual(tm.flush(), 1) + self.assertEqual(got[0][1]["batch"][0]["properties"]["$ip"], "0.0.0.0") + self.assertEqual(tm._read_lines(tm.SENT)[0]["properties"]["$ip"], "0.0.0.0") + + def test_the_inbox_skips_malformed_reports(self): + good = ["2026-09-28T09:50:00Z", "AB12CD34", "bug", "Live view stops", "It stops.", None, + "0.4.0", "macOS", "", ""] + rows = [["2026-09-28T10:00:00Z", "X", "bug", "Hand-made", None, None, None, None, None, None], ["short"], good] + with mock.patch.object(db, "_posthog_query", return_value={"results": rows}), \ + mock.patch.object(sys, "argv", ["frame_report.py", "inbox"]), \ + mock.patch("builtins.print") as out: + fr.main() + printed = " ".join(str(c.args[0]) for c in out.call_args_list if c.args) + self.assertIn("AB12CD34", printed) + self.assertIn("Hand-made", printed) + + def test_a_refused_report_is_an_error(self): + self.serve(status=401) + with self.assertRaisesRegex(fr.ReportError, "HTTP 401"): + fr.send({"title": "Live view stops", "message": "It stops after a minute."}) + self.assertEqual(tm._read_lines(tm.SENT), []) + + def test_no_key_means_no_report(self): + with mock.patch.dict(os.environ, {"FRAME_CONTROL_POSTHOG_KEY": ""}), \ + mock.patch.object(tm, "HERE", tm.STATE): + with self.assertRaisesRegex(fr.ReportError, "no PostHog project key"): + fr.send({"title": "Live view stops", "message": "It stops after a minute."}) if __name__ == "__main__": diff --git a/ui/frame_compat_db.py b/ui/frame_compat_db.py index bc28dd4..6371435 100644 --- a/ui/frame_compat_db.py +++ b/ui/frame_compat_db.py @@ -267,7 +267,7 @@ def _posthog_query(sql): project = os.environ.get('FRAME_CONTROL_POSTHOG_PROJECT') or cfg.get('project') if not project: raise DBError('No PostHog project id (ui/telemetry.json "project", or FRAME_CONTROL_POSTHOG_PROJECT)') - # The query API lives on the app host (eu.posthog.com), not the ingestion host (eu.i.posthog.com). + # The query API lives on the app host (us.posthog.com), not the ingestion host (us.i.posthog.com). host = cfg['host'].replace('.i.posthog.com', '.posthog.com') req = urllib.request.Request(f'{host}/api/projects/{urllib.parse.quote(str(project))}/query/', method='POST', data=json.dumps({'query': {'kind': 'HogQLQuery', 'query': sql}}).encode(), @@ -352,15 +352,21 @@ def sync(dry_run=False): key() # the maintainer's copy only state = _sync_state() since = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(time.time() - SYNC_OVERLAP_DAYS * 86400)) - events = [] - for page in range(40): - res = _posthog_query("SELECT properties, distinct_id, timestamp FROM events " - f"WHERE event = 'compat_report' AND timestamp >= toDateTime('{since}') " - f"ORDER BY timestamp, uuid LIMIT {SYNC_PAGE} OFFSET {page * SYNC_PAGE}") + events, after = [], f"timestamp >= toDateTime('{since}', 'UTC')" + for _ in range(40): + # Keyset paging: PostHog refuses OFFSET with a personal API key. The cursor is in UTC, + # since a local time is ambiguous in the hour clocks go back. + res = _posthog_query("SELECT properties, distinct_id, timestamp, toString(uuid), " + "formatDateTime(timestamp, '%Y-%m-%d %H:%i:%S.%f', 'UTC') FROM events " + f"WHERE event = 'compat_report' AND {after} " + f"ORDER BY timestamp, toString(uuid) LIMIT {SYNC_PAGE}") rows = res.get('results') or [] - events += rows + events += [row[:3] for row in rows] if len(rows) < SYNC_PAGE: break + last_uuid, last_ts = rows[-1][3], rows[-1][4] + after = (f"(timestamp > toDateTime64('{last_ts}', 6, 'UTC') OR " + f"(timestamp = toDateTime64('{last_ts}', 6, 'UTC') AND toString(uuid) > '{last_uuid}'))") rows, skipped = community_rows(events, state) if dry_run: return rows, skipped diff --git a/ui/frame_report.py b/ui/frame_report.py index f898ea5..ffcfa9d 100644 --- a/ui/frame_report.py +++ b/ui/frame_report.py @@ -1,32 +1,26 @@ """Report a problem from inside Frame Control. Python stdlib only. The page's Report a problem dialog shows the diagnostics below before anything -is sent, then this sends the report to the website's feedback API -(site/functions/api/feedback.js), which files it as a GitHub issue. The user -needs no GitHub account. Everything collected is scrubbed first -(frame_telemetry.scrub), since the issue is public. +is sent, then this sends the report privately to Frame Control's PostHog +project as a `problem_report` event: only the maintainer can read it, and +nothing is published. It is sent whatever the analytics settings are, because +the person sends it deliberately. Diagnostics are scrubbed first +(frame_telemetry.scrub); the person's own words are sent as written. """ -import json import os import platform import sys import time -import urllib.error -import urllib.request +import uuid import frame_host import frame_telemetry -FEEDBACK_URL = os.environ.get('FRAME_CONTROL_FEEDBACK_URL', 'https://frame-control.pages.dev/api/feedback') -ISSUES_URL = 'https://github.com/saphid/frame-control/issues/new' KINDS = ('bug', 'idea', 'question', 'other') -MESSAGE_MAX = 5000 # the feedback API's limit, in JavaScript (UTF-16) units -TEXT_MAX = 3500 # the person's own text -DIAG_MAX = 1300 # the diagnostics block, so text + diagnostics always fit MESSAGE_MAX -LOG_LINES = 40 +TEXT_MAX = 5000 # the person's own text, in JavaScript (UTF-16) units like the page's maxlength +DIAG_MAX = 8000 # the diagnostics block +LOG_LINES = 60 ACTIVITY_LINES = 25 -HEAD = '\n\n---\nDiagnostics from Frame Control (personal details removed):\n```\n' -TAIL = '\n```' frame = {} # the Frame's last known SteamOS build, set by server.status() @@ -96,52 +90,72 @@ def diagnostics(activity=(), include_logs=False, limit=DIAG_MAX): def compose(body): - """(title, message) for the feedback API: the person's text, then the diagnostics exactly as - the dialog previewed them (passed back, scrubbed again and bounded here).""" + """(title, text, diagnostics): the diagnostics exactly as the dialog previewed them (passed + back, scrubbed again and bounded here).""" title = ' '.join(str(body.get('title') or '').split()) text = str(body.get('message') or '').strip() if len(title) < 5: raise ValueError('give it a short title (at least 5 characters)') if len(text) < 10: raise ValueError('say a little more about what happened (at least 10 characters)') - title, text = cut(title, 120), cut(text, TEXT_MAX) diag = body.get('diagnostics') - if not isinstance(diag, str) or not diag.strip(): - return title, text - diag = cut(frame_telemetry.scrub(diag, 20000), DIAG_MAX) - return title, f'{text}{HEAD}{diag}{TAIL}' + diag = cut(frame_telemetry.scrub(diag, 40000), DIAG_MAX) if isinstance(diag, str) and diag.strip() else '' + return cut(title, 120), cut(text, TEXT_MAX), diag def send(body): - """File the report. Returns {"number", "url"} of the new issue; raises ReportError.""" + """Send the report to PostHog. Returns {"id", "message"}; raises ReportError.""" kind = body.get('kind') if body.get('kind') in KINDS else 'bug' - title, message = compose(body) - payload = {'kind': kind, 'title': title, 'message': message, 'website': '', - 'github': str(body.get('github') or '').strip().lstrip('@')[:40], - 'version': frame_telemetry.app_version(), 'os': f'{frame_host.NAME} {platform.machine()}', - 'steamos': str(frame.get('build') or '')[:120], - # How long the dialog was open; the API treats anything under 3 s as a script. - 'elapsed': max(0, int(body.get('elapsed') or 0))} - req = urllib.request.Request(FEEDBACK_URL, data=json.dumps(payload).encode(), method='POST', - headers={'content-type': 'application/json', - 'user-agent': f'FrameControl/{frame_telemetry.app_version()}'}) + title, text, diag = compose(body) + ref = uuid.uuid4().hex[:8].upper() + props = {**frame_telemetry.common(), 'kind': kind, 'title': title, 'message': text, + 'contact': str(body.get('contact') or '').strip()[:120], 'diagnostics': diag, + 'report_id': ref, 'steamos': str(frame.get('build') or '')[:120], 'level': 'report'} + # Its own random id: a report can carry contact details, so it isn't linked to this copy's analytics. + event = {'event': 'problem_report', 'distinct_id': str(uuid.uuid4()), 'uuid': str(uuid.uuid4()), + 'timestamp': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), 'properties': props} try: - with urllib.request.urlopen(req, timeout=30) as r: - res = json.loads(r.read() or b'{}') - except urllib.error.HTTPError as e: - try: - why = json.loads(e.read() or b'{}').get('error') - except ValueError: - why = None - e.close() - raise ReportError(why or f'the feedback service said HTTP {e.code}') - except (urllib.error.URLError, OSError, ValueError) as e: - raise ReportError(f"couldn't reach the feedback service: {e}") - if not res.get('url'): - raise ReportError("the feedback service didn't file it; try again in a moment") - frame_telemetry.capture('problem_reported', {'kind': kind, 'with_diagnostics': bool(body.get('diagnostics'))}) - return {'number': res.get('number'), 'url': res['url'], 'message': f"Sent. It's issue #{res.get('number')} on GitHub."} + frame_telemetry.post([event], timeout=30) + except frame_telemetry.SendError as e: + raise ReportError(str(e)) + try: + frame_telemetry.record_sent([event]) + except OSError: + pass # it was sent; failing to log it here mustn't make the person send it again + return {'id': ref, 'message': f'Sent privately to the Frame Control developer (report {ref}).'} class ReportError(RuntimeError): pass + + +def inbox(days=30): + """The maintainer's recent reports from PostHog, newest first (needs the personal API key + frame_compat_db.sync uses).""" + import frame_compat_db + res = frame_compat_db._posthog_query( + "SELECT timestamp, properties.report_id, properties.kind, properties.title, properties.message, " + "properties.contact, properties.app_version, properties.os, properties.steamos, properties.diagnostics " + f"FROM events WHERE event = 'problem_report' AND timestamp > now() - INTERVAL {int(days)} DAY " + "ORDER BY timestamp DESC LIMIT 200") + return res.get('results') or [] + + +def main(): + cmd, *args = sys.argv[1:] or ['inbox'] + if cmd != 'inbox': + sys.exit('usage: frame_report.py inbox [days]') + for row in inbox(*(args[:1] or [30])): + if not isinstance(row, list) or len(row) != 10: + continue + ts, ref, kind, title, text, contact, version, osname, steamos, diag = (str(v or '') for v in row) + print(f"== {ts[:16].replace('T', ' ')} {ref} [{kind}] {title}") + print(f" {version} on {osname}, SteamOS {steamos or 'unknown'}{', reply to ' + contact if contact else ''}") + print(' ' + text.replace('\n', '\n ')) + if diag: + print(' --- diagnostics\n ' + diag.replace('\n', '\n ')) + print() + + +if __name__ == '__main__': + main() diff --git a/ui/frame_telemetry.py b/ui/frame_telemetry.py index 818bf4b..5771ac7 100644 --- a/ui/frame_telemetry.py +++ b/ui/frame_telemetry.py @@ -14,8 +14,8 @@ every event and property): home folders, user names, addresses and keys. The first-run notice offers compat and diagnostics together, and the page's -Report a problem dialog (frame_report.py) files bug reports whatever is chosen -here. +Report a problem dialog (frame_report.py) sends bug reports privately to the +same project whatever is chosen here. Events are identified by a random id made on first run, not by the person or computer, and sent without person profiles or GeoIP. Nothing is sent without a @@ -53,7 +53,7 @@ SENT_KEEP = 200 OUTBOX_MAX = 2000 # events kept while offline; the oldest go first FLUSH_EVERY = 60 REPEAT_WINDOW = 600 # the same diagnostic error is sent at most once in this many seconds -DEFAULT_HOST = 'https://eu.i.posthog.com' +DEFAULT_HOST = 'https://us.i.posthog.com' LEVELS = ('usage', 'compat', 'diagnostics') # Events the page may send through /api/telemetry, and the properties each may carry. @@ -270,11 +270,12 @@ def categorize(message): # ---- capturing ------------------------------------------------------------------ -def _common(): +def common(): return {'app_version': app_version(), 'os': frame_host.NAME, 'arch': platform.machine().lower(), 'python': '%d.%d' % sys.version_info[:2], '$lib': 'frame-control', - # Anonymous events: no person profile, no location lookup. - '$process_person_profile': False, '$geoip_disable': True} + # Anonymous events: no person profile, no location lookup, and a placeholder address, + # since PostHog stores the sender's IP unless an event gives one. + '$process_person_profile': False, '$geoip_disable': True, '$ip': '0.0.0.0'} def app_version(): @@ -296,7 +297,7 @@ def capture(event, props=None, level='usage'): s = settings() e = {'event': event, 'distinct_id': s['id'], 'uuid': str(uuid.uuid4()), 'timestamp': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), - 'properties': {**_common(), **(props or {}), 'level': level}} + 'properties': {**common(), **(props or {}), 'level': level}} with _lock: lines = _read_lines(OUTBOX) + [e] _write_lines(OUTBOX, lines[-OUTBOX_MAX:]) @@ -459,28 +460,50 @@ def _drop_unwanted(s): _write_lines(OUTBOX, kept) +def post(batch, timeout=20): + """Send events to PostHog now. Raises SendError if they weren't accepted.""" + cfg = config() + if not cfg['key']: + raise SendError('no PostHog project key in this build') + for e in batch: # also events queued by versions that didn't add the placeholder address + e.setdefault('properties', {})['$ip'] = '0.0.0.0' + body = json.dumps({'api_key': cfg['key'], 'batch': batch}).encode() + req = urllib.request.Request(cfg['host'] + '/batch/', data=body, method='POST', + headers={'content-type': 'application/json', + 'user-agent': f'FrameControl/{app_version()}'}) + try: + with urllib.request.urlopen(req, timeout=timeout) as r: + r.read() + except urllib.error.HTTPError as e: + e.close() + raise SendError(f'PostHog said HTTP {e.code}') + except (urllib.error.URLError, OSError, ValueError) as e: + raise SendError(f"couldn't reach PostHog: {e}") + + +def record_sent(events): + """Add events sent outside the outbox to the log the page shows.""" + with _lock: + _write_lines(SENT, (_read_lines(SENT) + list(events))[-SENT_KEEP:]) + + +class SendError(RuntimeError): + pass + + def flush(timeout=20): """Send what's queued. Returns how many were sent; on failure they stay queued.""" with _send_lock: if blocked() or not settings()['notice_shown']: return 0 - cfg = config() with _lock: _drop_unwanted(settings()) batch = _read_lines(OUTBOX)[:100] if not batch: return 0 - body = json.dumps({'api_key': cfg['key'], 'batch': batch}).encode() - req = urllib.request.Request(cfg['host'] + '/batch/', data=body, method='POST', - headers={'content-type': 'application/json', - 'user-agent': f'FrameControl/{app_version()}'}) try: - with urllib.request.urlopen(req, timeout=timeout) as r: - r.read() - except urllib.error.HTTPError as e: - e.close() - return 0 - except (urllib.error.URLError, OSError, ValueError): + post(batch, timeout) + except SendError: return 0 sent_ids = {e['uuid'] for e in batch} with _lock: diff --git a/ui/index.html b/ui/index.html index 92ec81a..6006c4d 100644 --- a/ui/index.html +++ b/ui/index.html @@ -759,19 +759,18 @@ - - +
Show exactly what's included
Loading…
-

This becomes a public issue on GitHub (saphid/frame-control). No GitHub account is needed.

+

Sent privately to the Frame Control developer. Nothing is published.

-
@@ -2331,18 +2330,14 @@ document.querySelectorAll("nav a").forEach(a => a.addEventListener("click", () = if (!tabsSeen.has(tab)) { tabsSeen.add(tab); pageEvent("tab_viewed", { tab }); } })); -// ---- report a problem (ui/frame_report.py): a GitHub issue via the website, with diagnostics ---- -const bug = { opened: 0, preview: "" }; +// ---- report a problem (ui/frame_report.py): sent privately to PostHog, with diagnostics ---- +const bug = { preview: "" }; const activityLines = () => [...$("log").children].slice(0, 25).map(el => el.textContent.trim()); function bugReportText() { - const body = `${$("bugText").value.trim()}${bug.preview ? "\n\n---\nDiagnostics:\n```\n" + bug.preview + "\n```" : ""}`; + const contact = $("bugContact").value.trim(); + const body = `Kind: ${$("bugKind").value}${contact ? `\nContact: ${contact}` : ""}\n\n${$("bugText").value.trim()}${bug.preview ? "\n\n---\nDiagnostics:\n```\n" + bug.preview + "\n```" : ""}`; return { title: $("bugTitleIn").value.trim(), body }; } -function bugGithubUrl() { - const { title, body } = bugReportText(); - return "https://github.com/saphid/frame-control/issues/new?labels=feedback&title=" + encodeURIComponent(title) - + "&body=" + encodeURIComponent(body.slice(0, 6000)); -} // The preview is a snapshot: exactly this text is sent, even if more activity happens meanwhile. async function loadBugPreview() { if (!$("bugDiag").checked) { bug.preview = ""; $("bugDiagText").textContent = "Nothing: diagnostics are off."; return; } @@ -2353,10 +2348,9 @@ async function loadBugPreview() { } function openBugReport() { $("bugForm").reset(); - $("bugMsg").textContent = ""; $("bugGithubLink").hidden = true; $("bugSend").disabled = false; + $("bugMsg").textContent = ""; $("bugSend").disabled = false; $("bugCancel").textContent = "Cancel"; $("bugDiagBox").open = false; $("bugLogs").disabled = false; - bug.opened = performance.now(); $("bugDlg").showModal(); loadBugPreview(); } @@ -2376,14 +2370,12 @@ $("bugForm").onsubmit = async e => { try { const res = await api("/api/report", { kind: $("bugKind").value, title: $("bugTitleIn").value, message: $("bugText").value, - github: $("bugGithub").value, diagnostics: $("bugDiag").checked ? bug.preview : "", - elapsed: Math.round(performance.now() - bug.opened) }); - $("bugMsg").innerHTML = `Sent. Issue #${esc(String(res.number))} on GitHub.`; + contact: $("bugContact").value, diagnostics: $("bugDiag").checked ? bug.preview : "" }); + $("bugMsg").textContent = `Sent, thank you. Your reference is ${res.id}.`; $("bugCancel").textContent = "Close"; log(res.message, "ok"); } catch (err) { - $("bugMsg").textContent = `Couldn't send it: ${err.message}.`; - $("bugGithubLink").href = bugGithubUrl(); $("bugGithubLink").hidden = false; + $("bugMsg").textContent = `Couldn't send it: ${err.message}. Try again later, or use Copy report.`; $("bugSend").disabled = false; } }; diff --git a/ui/telemetry.json b/ui/telemetry.json index 3c89053..357c5ca 100644 --- a/ui/telemetry.json +++ b/ui/telemetry.json @@ -1,5 +1,5 @@ { - "host": "https://eu.i.posthog.com", - "key": "", - "project": "" + "host": "https://us.i.posthog.com", + "key": "phc_qkmbgQBvl2oBXGUVzfV6gG52EpmJdeaQyaRIxHRoQoL", + "project": "343535" } From 077eab2b79384d084fd5c0eb8c39119b4e97e9d8 Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:19:38 +1000 Subject: [PATCH 15/36] Add LAUNCHER to the real VR activity when only an activity-alias has it Lepton's apk-info-extractor ignores , and Godot 4 exports put LAUNCHER only on an alias (com.godot.game.GodotAppLauncher). Open Saber Plus 0.7.67 installed but Lepton exited with 'APP_ACTIVITY is empty'. Count only real activities as launchable and patch the activity's VR intent filter. Verified on the Frame: Open Saber Plus launches and renders. Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/test_frame_apk_vr.py | 22 +++++++++++++++++++--- ui/frame_apk_vr.py | 8 +++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/tests/test_frame_apk_vr.py b/tests/test_frame_apk_vr.py index 8b801bc..aa0d984 100644 --- a/tests/test_frame_apk_vr.py +++ b/tests/test_frame_apk_vr.py @@ -18,11 +18,11 @@ import frame_android from test_frame_apk import pool -def manifest(utf8=False, launcher=False, category=None, samsung=False, split=False): +def manifest(utf8=False, launcher=False, category=None, samsung=False, split=False, alias=False): strings = ['manifest', 'package', 'org.test.vr', 'application', 'activity', 'intent-filter', 'action', 'category', 'name', vr.MAIN, category or next(iter(sorted(vr.VR))), vr.LAUNCHER, 'http://schemas.android.com/apk/res/android', 'meta-data', 'value', - 'com.samsung.android.vr.application.mode', 'vr_only'] + 'com.samsung.android.vr.application.mode', 'vr_only', 'activity-alias'] def start(tag, attrs=()): body = struct.pack('; Godot 4 puts LAUNCHER only there. + return {'launchable': any(LAUNCHER in f['categories'] for f in mains if not f['alias']), + 'vr_activity': bool(vr_filters), 'vr': bool(vr_filters) or samsung}, \ + [f for f in vr_filters if not f['alias']] def _append_string(chunk, text): From 33a92a2e1d09d5abbc265e6be997e03206429cbd Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:20:27 +1000 Subject: [PATCH 16/36] Tests: run in a sandbox that can't touch real app data, telemetry or the shared database On a maintainer's Mac the compatibility-database key is in the Keychain, so a test that reached install reporting published fake reports. Every test module now imports tests/sandbox.py first, which points app data at a throwaway directory (new FRAME_CONTROL_DATA_DIR), turns telemetry off and sends the database nowhere. Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/sandbox.py | 16 ++++++++++++++++ tests/test_compat_db.py | 1 + tests/test_connect.py | 1 + tests/test_frame_apk.py | 1 + tests/test_frame_apk_versions.py | 1 + tests/test_frame_titles.py | 1 + tests/test_server.py | 1 + tests/test_steam.py | 1 + tests/test_telemetry.py | 1 + tests/test_webinstall.py | 1 + ui/frame_host.py | 7 +++++-- 11 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 tests/sandbox.py diff --git a/tests/sandbox.py b/tests/sandbox.py new file mode 100644 index 0000000..5d2a3f0 --- /dev/null +++ b/tests/sandbox.py @@ -0,0 +1,16 @@ +"""Imported first by every test module: nothing a test does reaches this person's +app data, their telemetry, or the shared compatibility database. + +Must run before any ui module is imported, since those read these at import time. +""" +import atexit +import os +import shutil +import tempfile + +_dir = tempfile.mkdtemp(prefix="frame-control-tests-") +atexit.register(shutil.rmtree, _dir, ignore_errors=True) +os.environ["FRAME_CONTROL_DATA_DIR"] = _dir +os.environ["FRAME_CONTROL_TELEMETRY"] = "0" +# A maintainer's machine holds the database key; send anything that slips through nowhere. +os.environ["FRAME_COMPAT_DB_URL"] = "http://127.0.0.1:9" diff --git a/tests/test_compat_db.py b/tests/test_compat_db.py index 7081d79..37208d5 100644 --- a/tests/test_compat_db.py +++ b/tests/test_compat_db.py @@ -2,6 +2,7 @@ Run: python3 -m unittest discover -s tests """ +import sandbox # noqa: F401 (first: keeps tests off real data and services) import os import sys import tempfile diff --git a/tests/test_connect.py b/tests/test_connect.py index b3366bd..0e8972a 100644 --- a/tests/test_connect.py +++ b/tests/test_connect.py @@ -3,6 +3,7 @@ steamos-devkit-service, the ~/.ssh/config block, and the mDNS output parsers. Run: python3 -m unittest discover -s tests """ +import sandbox # noqa: F401 (first: keeps tests off real data and services) import json import socket import sys diff --git a/tests/test_frame_apk.py b/tests/test_frame_apk.py index 1af14f9..ebe7b0b 100644 --- a/tests/test_frame_apk.py +++ b/tests/test_frame_apk.py @@ -1,4 +1,5 @@ """frame_apk against a small APK built here: binary manifest plus resource table.""" +import sandbox # noqa: F401 (first: keeps tests off real data and services) import io import os import struct diff --git a/tests/test_frame_apk_versions.py b/tests/test_frame_apk_versions.py index 508ddcf..ea70373 100644 --- a/tests/test_frame_apk_versions.py +++ b/tests/test_frame_apk_versions.py @@ -1,4 +1,5 @@ """Offline version lookup with small index-v2 fixtures.""" +import sandbox # noqa: F401 (first: keeps tests off real data and services) import io import json import os diff --git a/tests/test_frame_titles.py b/tests/test_frame_titles.py index 55d080d..acb9454 100644 --- a/tests/test_frame_titles.py +++ b/tests/test_frame_titles.py @@ -1,4 +1,5 @@ """frame_titles without a headset: executable headers, launch targets, zips, runtimes.""" +import sandbox # noqa: F401 (first: keeps tests off real data and services) import json import os import shutil diff --git a/tests/test_server.py b/tests/test_server.py index ec389bd..1bb565d 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -5,6 +5,7 @@ request guards and input validation, which all run before any SSH call. Run: python3 -m unittest discover -s tests """ +import sandbox # noqa: F401 (first: keeps tests off real data and services) import http.client import io import json diff --git a/tests/test_steam.py b/tests/test_steam.py index d29d975..615bf1e 100644 --- a/tests/test_steam.py +++ b/tests/test_steam.py @@ -2,6 +2,7 @@ Run: python3 -m unittest discover -s tests """ +import sandbox # noqa: F401 (first: keeps tests off real data and services) import json import subprocess import sys diff --git a/tests/test_telemetry.py b/tests/test_telemetry.py index 6a033c7..4b400c6 100644 --- a/tests/test_telemetry.py +++ b/tests/test_telemetry.py @@ -3,6 +3,7 @@ what's scrubbed, and that nothing is sent without a key, the notice, or consent. Run: python3 -m unittest discover -s tests """ +import sandbox # noqa: F401 (first: keeps tests off real data and services) import json import os import sys diff --git a/tests/test_webinstall.py b/tests/test_webinstall.py index 1858297..e197b49 100644 --- a/tests/test_webinstall.py +++ b/tests/test_webinstall.py @@ -4,6 +4,7 @@ the localhost-testing rule allows. Run: python3 -m unittest discover -s tests """ +import sandbox # noqa: F401 (first: keeps tests off real data and services) import hashlib import json import os diff --git a/ui/frame_host.py b/ui/frame_host.py index a60b82c..a3842f3 100644 --- a/ui/frame_host.py +++ b/ui/frame_host.py @@ -33,8 +33,11 @@ class HostError(RuntimeError): def data_dir(*parts): - """Per-user app data: ~/Library/Application Support, %APPDATA% or $XDG_DATA_HOME.""" - if MAC: + """Per-user app data: ~/Library/Application Support, %APPDATA% or $XDG_DATA_HOME + (or $FRAME_CONTROL_DATA_DIR, which the tests point at a throwaway directory).""" + if os.environ.get("FRAME_CONTROL_DATA_DIR"): + base = Path(os.environ["FRAME_CONTROL_DATA_DIR"]) + elif MAC: base = Path.home() / "Library" / "Application Support" / "Frame Control" elif WINDOWS: base = Path(os.environ.get("APPDATA") or Path.home() / "AppData" / "Roaming") / "Frame Control" From e1c0ac983c52acd2eb82ed04f65b9b15d0bb19be Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:40:16 +1000 Subject: [PATCH 17/36] Repair alias-only launchers for flat apps too; never fail on a VR alias Review follow-up. inspect() now returns 'repairable' and the filters it can patch: the real activity's VR MAIN filter, or, when LAUNCHER/VR sits only on an , any real MAIN filter with a category to copy. install() and patch() repair on 'repairable' instead of 'vr_activity', so a flat Godot 4 export is fixed and a VR category only on the alias no longer aborts install. Tests cover both shapes, an already-launchable activity with an alias, and an end-to-end patch. Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/test_frame_apk_vr.py | 55 +++++++++++++++++++++++++++++--------- ui/frame_android.py | 11 ++++---- ui/frame_apk_vr.py | 14 ++++++---- 3 files changed, 57 insertions(+), 23 deletions(-) diff --git a/tests/test_frame_apk_vr.py b/tests/test_frame_apk_vr.py index aa0d984..da87d15 100644 --- a/tests/test_frame_apk_vr.py +++ b/tests/test_frame_apk_vr.py @@ -18,11 +18,14 @@ import frame_android from test_frame_apk import pool +DEFAULT = 'android.intent.category.DEFAULT' + + def manifest(utf8=False, launcher=False, category=None, samsung=False, split=False, alias=False): strings = ['manifest', 'package', 'org.test.vr', 'application', 'activity', 'intent-filter', 'action', 'category', 'name', vr.MAIN, category or next(iter(sorted(vr.VR))), vr.LAUNCHER, 'http://schemas.android.com/apk/res/android', 'meta-data', 'value', - 'com.samsung.android.vr.application.mode', 'vr_only', 'activity-alias'] + 'com.samsung.android.vr.application.mode', 'vr_only', 'activity-alias', DEFAULT, next(iter(sorted(vr.VR)))] def start(tag, attrs=()): body = struct.pack('; Godot 4 puts LAUNCHER only there. - return {'launchable': any(LAUNCHER in f['categories'] for f in mains if not f['alias']), - 'vr_activity': bool(vr_filters), 'vr': bool(vr_filters) or samsung}, \ - [f for f in vr_filters if not f['alias']] + real = [f for f in mains if not f['alias']] + targets = [f for f in real if VR & f['categories']] + if not targets and any(LAUNCHER in f['categories'] or VR & f['categories'] for f in mains if f['alias']): + targets = [f for f in real if f['templates']] # the patch copies an existing + launchable = any(LAUNCHER in f['categories'] for f in real) + return {'launchable': launchable, 'repairable': not launchable and bool(targets), + 'vr_activity': bool(vr_filters), 'vr': bool(vr_filters) or samsung}, targets def _append_string(chunk, text): @@ -64,7 +68,7 @@ def add_launcher_category(axml_bytes): if info['launchable']: return axml_bytes if not filters: - raise frame_apk.ApkError('no VR activity with a MAIN intent filter to patch') + raise frame_apk.ApkError('no activity with a MAIN intent filter that Frame Control can patch') target = filters[0] chunks = list(frame_apk._chunks(axml_bytes, 8, len(axml_bytes))) pool = next(c for c in chunks if c[0] == 1) @@ -87,7 +91,7 @@ def add_launcher_category(axml_bytes): struct.pack_into(' Date: Mon, 28 Sep 2026 20:52:30 +1000 Subject: [PATCH 18/36] APK sources: shared interface for source modules Co-Authored-By: Claude Opus 5.5 (1M context) --- ui/apk_sources/__init__.py | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 ui/apk_sources/__init__.py diff --git a/ui/apk_sources/__init__.py b/ui/apk_sources/__init__.py new file mode 100644 index 0000000..88df1f4 --- /dev/null +++ b/ui/apk_sources/__init__.py @@ -0,0 +1,45 @@ +"""APK sources: every place Frame Control can find and download APKs. + +One module per source kind in this package. Each module exposes the same small +interface so ``search.py`` can query them all and show where every result came +from. Python stdlib only; must run on Python 3.9. + +Module interface +---------------- +KIND = 'sidequest' # stable id of the source kind +def sources() -> list[dict] # configured sources of this kind (a kind can have + # several, e.g. one per F-Droid-format repo) +def search(source, query, limit=50) -> list[dict] # Entry dicts, best first +def details(source, entry_id) -> dict # Entry with 'versions' +def download(source, entry_id, version_code=None) -> dict + # {'apk': local path, 'obb': [paths], 'sha256': hex or None, 'verified': bool} + # Raise SourceError with a user-readable message on failure. + +Source dict +----------- +{'id': 'sidequest', 'kind': KIND, 'name': 'SideQuest', 'url': 'https://...', + 'builtin': True, 'enabled': True, 'trust': 'official' | 'community' | 'user'} + +Entry dict (missing facts are None, never guessed) +---------- +{'source': source id, 'id': source-local id, 'package': 'org.example.app' or None, + 'name': str, 'summary': str, 'icon': url or None, 'page': url or None, + 'version': '1.2', 'version_code': 12, 'min_sdk': 24, 'abis': ['arm64-v8a'], + 'vr': True/False/None, 'size': bytes, 'free': True, 'license': 'GPL-3.0' or None, + 'updated': 'YYYY-MM-DD', 'downloadable': bool, # False = open page only + 'versions': [ {version, version_code, min_sdk, size, updated}, ... ]} # details() only + +Rules +----- +- Only sources that distribute APKs with the developer's consent: free listings, + never paid apps re-hosted, no licence or entitlement workarounds. +- Honour each site's terms and robots rules; if automated download isn't allowed, + return entries with 'downloadable': False and a 'page' link instead. +- Cache indexes under frame_host.cache_dir('apk-sources'); send a clear + User-Agent ('FrameControl/'); keep requests modest. +- Tests use recorded fixtures, never the network. +""" + + +class SourceError(Exception): + """User-readable failure from a source (network, format, verification).""" From 9e9e5950d0635c5e3bec178bbf7047492c208a9e Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:55:56 +1000 Subject: [PATCH 19/36] Patch the activity the launcher alias targets, not the first MAIN filter Second review follow-up: with several activities (e.g. a splash activity ahead of the game), the alias fallback now prefers the real activity named by the alias's android:targetActivity. Reads targetActivity by resource id, updates the error text and docs/vr-apks.md. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/vr-apks.md | 7 +++++-- tests/test_frame_apk_vr.py | 24 ++++++++++++++++++++---- ui/frame_android.py | 2 +- ui/frame_apk.py | 3 ++- ui/frame_apk_vr.py | 11 +++++++++-- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/docs/vr-apks.md b/docs/vr-apks.md index 8d31cc3..e57325c 100644 --- a/docs/vr-apks.md +++ b/docs/vr-apks.md @@ -100,8 +100,11 @@ VR apps default to immersive mode without the flatscreen marker. The upload selector or CLI `--flat` / `--vr` overrides that choice. Compatibility notes identify legacy VrApi, Meta platform SDK and OpenXR libraries. -If a VR MAIN intent filter lacks LAUNCHER, Frame Control inserts it, repacks -and v2-signs the APK locally before copying it; `meta.json` records +Lepton only starts an `` whose MAIN intent filter has LAUNCHER; it +ignores ``, which is where Godot 4 exports put LAUNCHER. When no +real activity qualifies, Frame Control adds LAUNCHER to the VR activity's MAIN +filter, or to the activity the launcher alias targets, then repacks and v2-signs +the APK locally before copying it; `meta.json` records `"patched": ["launcher"]`. Unchanged ZIP members retain their compressed bytes; stored libraries are aligned to 16 KiB. The RSA signing identity lives in Frame Control's per-user app-data directory as `apk-signing-key.json` diff --git a/tests/test_frame_apk_vr.py b/tests/test_frame_apk_vr.py index da87d15..49a56ca 100644 --- a/tests/test_frame_apk_vr.py +++ b/tests/test_frame_apk_vr.py @@ -21,11 +21,11 @@ from test_frame_apk import pool DEFAULT = 'android.intent.category.DEFAULT' -def manifest(utf8=False, launcher=False, category=None, samsung=False, split=False, alias=False): +def manifest(utf8=False, launcher=False, category=None, samsung=False, split=False, alias=False, splash=False): strings = ['manifest', 'package', 'org.test.vr', 'application', 'activity', 'intent-filter', 'action', 'category', 'name', vr.MAIN, category or next(iter(sorted(vr.VR))), vr.LAUNCHER, 'http://schemas.android.com/apk/res/android', 'meta-data', 'value', - 'com.samsung.android.vr.application.mode', 'vr_only', 'activity-alias', DEFAULT, next(iter(sorted(vr.VR)))] + 'com.samsung.android.vr.application.mode', 'vr_only', 'activity-alias', DEFAULT, next(iter(sorted(vr.VR))), 'targetActivity', '.Splash', '.Game'] def start(tag, attrs=()): body = struct.pack(' + targets = [f for f in targets if f['activity'] in aimed] or targets launchable = any(LAUNCHER in f['categories'] for f in real) return {'launchable': launchable, 'repairable': not launchable and bool(targets), 'vr_activity': bool(vr_filters), 'vr': bool(vr_filters) or samsung}, targets From 9e4dcdd147edf95a02cccdf2f1dbb076ab3f1e7b Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:56:14 +1000 Subject: [PATCH 20/36] Mac in the headset: measure every frame, adapt to the network, separate windows - Per-frame timing on the Mac's clock (capture, encode, network, decode, draw), viewer clock sync and reports, input echo, /stats and a HUD. - scripts/macview-bench.py: repeatable runs on the real Frame, a shaping relay (no sudo), interleaved A/B between agent settings; results in bench/results/. - Adaptive controller: ack-based send gate with jitter-aware slack, AIMD bitrate that knows when a stream is app-limited, fps then size tiers. On a 50->3->50 Mbit/s step, scroll p95 went from 4.7 s to 72 ms; no cost on a clean link. - Separate mode: real AppKit event loop (HiDPI and NSScreen now work), cropped capture for fixed-size windows, windows kept on their display, graceful quit restores windows; stop/start races fixed. - Encoder timeline clamp (no oversized frame after a pause). - Frame Control shows each live stream's fps, delay, bitrate and tier. Reviewed by GPT-6 Astra xhigh (read-only), 7 rounds; findings fixed. Co-Authored-By: Claude Opus 5.5 (1M context) --- bench/pages/present.html | 24 + bench/pages/scroll.html | 32 + bench/pages/type.html | 9 + ...26-09-28-a3c6e5c-dirty-ab-adapt-clean.json | 4211 +++++++++++++++++ ...6-09-28-a3c6e5c-dirty-ab-adapt-clean2.json | 4150 ++++++++++++++++ ...26-09-28-a3c6e5c-dirty-ab-adapt-step3.json | 3743 +++++++++++++++ .../2026-09-28-a3c6e5c-dirty-ab-keepwarm.json | 2172 +++++++++ .../2026-09-28-a3c6e5c-dirty-adapt-clean.json | 984 ++++ ...2026-09-28-a3c6e5c-dirty-adapt-clean2.json | 1116 +++++ ...26-09-28-a3c6e5c-dirty-baseline-fixed.json | 1124 +++++ .../2026-09-28-a3c6e5c-dirty-baseline.json | 1124 +++++ docs/how-the-frame-works.md | 1 + docs/mac-in-headset.md | 161 +- docs/mac-window-separation.md | 200 + mac/frame-mac-view/Sources/CGVirtualDisplay.h | 28 + mac/frame-mac-view/Sources/Capture.swift | 103 +- mac/frame-mac-view/Sources/Controller.swift | 234 + mac/frame-mac-view/Sources/Encoder.swift | 73 +- mac/frame-mac-view/Sources/Separate.swift | 295 ++ mac/frame-mac-view/Sources/Server.swift | 6 +- mac/frame-mac-view/Sources/Stats.swift | 180 + mac/frame-mac-view/Sources/main.swift | 321 +- mac/frame-mac-view/build.sh | 1 + mac/frame-mac-view/lab.sh | 50 + scripts/macview-bench.py | 815 ++++ tests/test_macview.py | 80 +- ui/frame_macview.py | 25 +- ui/index.html | 39 +- ui/mac-view.html | 208 +- 29 files changed, 21405 insertions(+), 104 deletions(-) create mode 100644 bench/pages/present.html create mode 100644 bench/pages/scroll.html create mode 100644 bench/pages/type.html create mode 100644 bench/results/2026-09-28-a3c6e5c-dirty-ab-adapt-clean.json create mode 100644 bench/results/2026-09-28-a3c6e5c-dirty-ab-adapt-clean2.json create mode 100644 bench/results/2026-09-28-a3c6e5c-dirty-ab-adapt-step3.json create mode 100644 bench/results/2026-09-28-a3c6e5c-dirty-ab-keepwarm.json create mode 100644 bench/results/2026-09-28-a3c6e5c-dirty-adapt-clean.json create mode 100644 bench/results/2026-09-28-a3c6e5c-dirty-adapt-clean2.json create mode 100644 bench/results/2026-09-28-a3c6e5c-dirty-baseline-fixed.json create mode 100644 bench/results/2026-09-28-a3c6e5c-dirty-baseline.json create mode 100644 docs/mac-window-separation.md create mode 100644 mac/frame-mac-view/Sources/CGVirtualDisplay.h create mode 100644 mac/frame-mac-view/Sources/Controller.swift create mode 100644 mac/frame-mac-view/Sources/Separate.swift create mode 100644 mac/frame-mac-view/Sources/Stats.swift create mode 100755 mac/frame-mac-view/lab.sh create mode 100755 scripts/macview-bench.py diff --git a/bench/pages/present.html b/bench/pages/present.html new file mode 100644 index 0000000..2e783fd --- /dev/null +++ b/bench/pages/present.html @@ -0,0 +1,24 @@ + + +fmv-present + + diff --git a/bench/pages/scroll.html b/bench/pages/scroll.html new file mode 100644 index 0000000..602ced9 --- /dev/null +++ b/bench/pages/scroll.html @@ -0,0 +1,32 @@ + + +fmv-bench scroll +
+ diff --git a/bench/pages/type.html b/bench/pages/type.html new file mode 100644 index 0000000..288904c --- /dev/null +++ b/bench/pages/type.html @@ -0,0 +1,9 @@ + + +fmv-bench type + + diff --git a/bench/results/2026-09-28-a3c6e5c-dirty-ab-adapt-clean.json b/bench/results/2026-09-28-a3c6e5c-dirty-ab-adapt-clean.json new file mode 100644 index 0000000..0b1f835 --- /dev/null +++ b/bench/results/2026-09-28-a3c6e5c-dirty-ab-adapt-clean.json @@ -0,0 +1,4211 @@ +{ + "label": "ab-adapt-clean", + "date": "2026-09-28T17:50:22", + "commit": "a3c6e5c+dirty", + "config": { + "quality": "balanced", + "mode": "separate", + "net": "none", + "delay_ms": 0, + "buffer_ms": 250, + "host": "frame", + "ssh_opts": [], + "encoder": "", + "duration_s": 15.0, + "browser_flags": [] + }, + "frame_build": "20260925.6191901", + "mac": "26.5.2", + "headset": ":35.258683 [Info] - 0 - entering standby", + "scenarios": [], + "arms": [ + "off:FRAME_MAC_VIEW_ADAPT=0", + "on:" + ], + "runs": { + "off": [ + [ + { + "scenario": "test", + "frames_sent": 944, + "frames_drawn": 944, + "frames_shown": 648, + "duration_s": 15.7, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 944 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 944 + }, + "encode": { + "p50": 4.3, + "p95": 5.0, + "p99": 5.2, + "n": 944 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 944 + }, + "network": { + "p50": 5.6, + "p95": 9.6, + "p99": 12.9, + "n": 944 + }, + "decode": { + "p50": 1.0, + "p95": 1.7, + "p99": 4.2, + "n": 944 + }, + "draw": { + "p50": 0.3, + "p95": 0.5, + "p99": 0.9, + "n": 944 + }, + "present": { + "p50": 4.5, + "p95": 15.8, + "p99": 18.4, + "n": 648 + }, + "content": { + "p50": 11.5, + "p95": 15.8, + "p99": 19.3, + "n": 944 + }, + "content_shown": { + "p50": 17.0, + "p95": 28.4, + "p99": 31.5, + "n": 648 + } + }, + "fps": 60.1, + "fps_shown": 41.2, + "late_pct": 0.95, + "stall_max": 68.8, + "stalls_over_100ms": 0, + "mbps": 0.48, + "keyframes": 1, + "size": "1280x720", + "captured": 1034, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 28.8, + "p95": 38.1, + "p99": 45.1, + "n": 30 + }, + "input_shown_ms": { + "p50": 30.2, + "p95": 45.6, + "p99": 50.1, + "n": 19 + }, + "input_parts_ms": { + "uplink": { + "p50": 7.1, + "p95": 21.5, + "p99": 23.6, + "n": 30 + }, + "mac": { + "p50": 4.2, + "p95": 15.1, + "p99": 15.8, + "n": 30 + }, + "back": { + "p50": 12.2, + "p95": 16.3, + "p99": 16.9, + "n": 30 + } + }, + "inputs": { + "sent": 30, + "seen": 30 + }, + "timeline": [ + { + "t": 0, + "fps": 60, + "mbps": 0.51, + "content_p50": 10.9, + "content_p95": 16.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 1, + "fps": 60, + "mbps": 0.49, + "content_p50": 10.2, + "content_p95": 15.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 2, + "fps": 60, + "mbps": 0.5, + "content_p50": 10.2, + "content_p95": 13.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 3, + "fps": 60, + "mbps": 0.51, + "content_p50": 10.3, + "content_p95": 15.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 4, + "fps": 60, + "mbps": 0.5, + "content_p50": 10.4, + "content_p95": 14.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 5, + "fps": 60, + "mbps": 0.49, + "content_p50": 10.7, + "content_p95": 14.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 6, + "fps": 60, + "mbps": 0.59, + "content_p50": 10.7, + "content_p95": 13.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 7, + "fps": 60, + "mbps": 0.48, + "content_p50": 10.1, + "content_p95": 11.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 8, + "fps": 60, + "mbps": 0.46, + "content_p50": 10.4, + "content_p95": 12.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 9, + "fps": 60, + "mbps": 0.45, + "content_p50": 10.6, + "content_p95": 14.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 10, + "fps": 60, + "mbps": 0.44, + "content_p50": 13.8, + "content_p95": 16.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 11, + "fps": 60, + "mbps": 0.44, + "content_p50": 14.3, + "content_p95": 18.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 12, + "fps": 60, + "mbps": 0.45, + "content_p50": 13.7, + "content_p95": 15.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 13, + "fps": 60, + "mbps": 0.45, + "content_p50": 14.1, + "content_p95": 17.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 14, + "fps": 60, + "mbps": 0.45, + "content_p50": 12.1, + "content_p95": 18.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 15, + "fps": 44, + "mbps": 0.32, + "content_p50": 12.1, + "content_p95": 13.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + } + ], + "adapt": [], + "content_p50": 11.5, + "content_p95": 15.8, + "input_p50": 28.8, + "input_p95": 38.1, + "grades": { + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "local", + "fps": "local", + "late_pct": "local", + "stall_max": "local" + }, + "controller": { + "adapt": false, + "baseRtt": 5.833, + "ceiling": 5529600, + "fps": 60, + "inFlight": 2, + "scale": 1, + "target": 5529600, + "tier": 0 + }, + "events": [], + "source_fps": 65.9, + "cpu": { + "mac_agent_pct": 16.1, + "frame_viewer_pct": 66.4, + "frame_tailscaled_pct": 5.8, + "frame_sshd_pct": 0.6, + "frame_gamescope_pct": 28.1, + "frame_total_pct": 31.6 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 142 Hz", + "show_s": 1.52, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + }, + { + "scenario": "scroll", + "frames_sent": 742, + "frames_drawn": 742, + "frames_shown": 603, + "duration_s": 15.8, + "stages_ms": { + "capture": { + "p50": -3.8, + "p95": 12.1, + "p99": 22.3, + "n": 742 + }, + "queue": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.1, + "n": 742 + }, + "encode": { + "p50": 6.7, + "p95": 9.0, + "p99": 16.4, + "n": 742 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.5, + "n": 742 + }, + "network": { + "p50": 8.1, + "p95": 24.9, + "p99": 61.3, + "n": 742 + }, + "decode": { + "p50": 6.0, + "p95": 12.4, + "p99": 26.2, + "n": 742 + }, + "draw": { + "p50": 0.6, + "p95": 1.2, + "p99": 1.9, + "n": 742 + }, + "present": { + "p50": 2.1, + "p95": 19.6, + "p99": 24.7, + "n": 603 + }, + "content": { + "p50": 19.5, + "p95": 44.4, + "p99": 118.6, + "n": 742 + }, + "content_shown": { + "p50": 25.0, + "p95": 51.3, + "p99": 125.2, + "n": 603 + } + }, + "fps": 47.1, + "fps_shown": 38.3, + "late_pct": 20.92, + "stall_max": 128.0, + "stalls_over_100ms": 2, + "mbps": 9.15, + "keyframes": 1, + "size": "1920x1290", + "captured": 815, + "viewer_never_drawn": 0, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 40, + "mbps": 9.15, + "content_p50": 28.7, + "content_p95": 139.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 36, + "mbps": 7.35, + "content_p50": 23.6, + "content_p95": 37.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 44, + "mbps": 9.92, + "content_p50": 21.9, + "content_p95": 39.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 53, + "mbps": 9.98, + "content_p50": 20.4, + "content_p95": 37.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 45, + "mbps": 9.58, + "content_p50": 20.2, + "content_p95": 41.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 53, + "mbps": 10.27, + "content_p50": 19.2, + "content_p95": 37.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 48, + "mbps": 11.11, + "content_p50": 29.9, + "content_p95": 107.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 44, + "mbps": 8.39, + "content_p50": 24.1, + "content_p95": 34.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 49, + "mbps": 8.68, + "content_p50": 14.5, + "content_p95": 39.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 43, + "mbps": 8.08, + "content_p50": 16.6, + "content_p95": 36.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 53, + "mbps": 8.85, + "content_p50": 15.1, + "content_p95": 25.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 50, + "mbps": 8.82, + "content_p50": 20.1, + "content_p95": 35.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 54, + "mbps": 8.42, + "content_p50": 18.9, + "content_p95": 26.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 52, + "mbps": 9.27, + "content_p50": 12.8, + "content_p95": 28.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 47, + "mbps": 9.89, + "content_p50": 21.5, + "content_p95": 42.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 31, + "mbps": 6.44, + "content_p50": 21.6, + "content_p95": 39.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 19.5, + "content_p95": 44.4, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "local", + "content_p95": "acceptable", + "fps": "acceptable", + "late_pct": "bad", + "stall_max": "acceptable" + }, + "controller": { + "adapt": false, + "baseRtt": 7.541, + "ceiling": 14860800, + "fps": 60, + "inFlight": 1, + "scale": 1, + "target": 14860800, + "tier": 0 + }, + "events": [], + "source_fps": 51.6, + "cpu": { + "mac_agent_pct": 7.1, + "frame_viewer_pct": 80.9, + "frame_tailscaled_pct": 7.9, + "frame_sshd_pct": 0.9, + "frame_gamescope_pct": 25.3, + "frame_total_pct": 31.1 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 142 Hz", + "show_s": 1.68, + "panel": "valve.steam.desktopgame.2001300498", + "src": "separate:13051" + } + ], + [ + { + "scenario": "test", + "frames_sent": 944, + "frames_drawn": 944, + "frames_shown": 608, + "duration_s": 15.7, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 944 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 944 + }, + "encode": { + "p50": 4.0, + "p95": 4.7, + "p99": 5.2, + "n": 944 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 944 + }, + "network": { + "p50": 4.8, + "p95": 40.4, + "p99": 74.2, + "n": 944 + }, + "decode": { + "p50": 1.1, + "p95": 5.4, + "p99": 10.8, + "n": 944 + }, + "draw": { + "p50": 0.3, + "p95": 0.5, + "p99": 0.9, + "n": 944 + }, + "present": { + "p50": 4.9, + "p95": 17.7, + "p99": 22.2, + "n": 608 + }, + "content": { + "p50": 10.5, + "p95": 52.3, + "p99": 82.1, + "n": 944 + }, + "content_shown": { + "p50": 16.0, + "p95": 33.4, + "p99": 82.8, + "n": 608 + } + }, + "fps": 60.1, + "fps_shown": 38.7, + "late_pct": 3.61, + "stall_max": 120.1, + "stalls_over_100ms": 2, + "mbps": 0.47, + "keyframes": 1, + "size": "1280x720", + "captured": 1034, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 26.7, + "p95": 89.3, + "p99": 106.5, + "n": 30 + }, + "input_shown_ms": { + "p50": 29.4, + "p95": 44.4, + "p99": 106.9, + "n": 18 + }, + "input_parts_ms": { + "uplink": { + "p50": 9.2, + "p95": 38.3, + "p99": 70.5, + "n": 30 + }, + "mac": { + "p50": 5.2, + "p95": 14.1, + "p99": 18.3, + "n": 30 + }, + "back": { + "p50": 10.4, + "p95": 16.2, + "p99": 86.2, + "n": 30 + } + }, + "inputs": { + "sent": 30, + "seen": 30 + }, + "timeline": [ + { + "t": 0, + "fps": 61, + "mbps": 0.48, + "content_p50": 10.3, + "content_p95": 14.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 1, + "fps": 59, + "mbps": 0.46, + "content_p50": 10.4, + "content_p95": 13.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 2, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.8, + "content_p95": 11.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 3, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.7, + "content_p95": 11.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 4, + "fps": 61, + "mbps": 0.48, + "content_p50": 9.6, + "content_p95": 11.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 5, + "fps": 59, + "mbps": 0.46, + "content_p50": 9.6, + "content_p95": 11.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 6, + "fps": 61, + "mbps": 0.57, + "content_p50": 9.9, + "content_p95": 34.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 7, + "fps": 59, + "mbps": 0.45, + "content_p50": 9.7, + "content_p95": 11.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 8, + "fps": 61, + "mbps": 0.47, + "content_p50": 9.8, + "content_p95": 13.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 9, + "fps": 59, + "mbps": 0.46, + "content_p50": 10.5, + "content_p95": 40.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 10, + "fps": 60, + "mbps": 0.44, + "content_p50": 11.8, + "content_p95": 82.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 11, + "fps": 60, + "mbps": 0.45, + "content_p50": 12.4, + "content_p95": 68.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 12, + "fps": 61, + "mbps": 0.46, + "content_p50": 12.4, + "content_p95": 71.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 13, + "fps": 59, + "mbps": 0.44, + "content_p50": 11.8, + "content_p95": 70.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 14, + "fps": 61, + "mbps": 0.47, + "content_p50": 11.8, + "content_p95": 70.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 15, + "fps": 43, + "mbps": 0.32, + "content_p50": 12.1, + "content_p95": 69.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + } + ], + "adapt": [], + "content_p50": 10.5, + "content_p95": 52.3, + "input_p50": 26.7, + "input_p95": 89.3, + "grades": { + "content_p50": "local", + "content_p95": "acceptable", + "input_p50": "local", + "input_p95": "acceptable", + "fps": "local", + "late_pct": "acceptable", + "stall_max": "acceptable" + }, + "controller": { + "adapt": false, + "baseRtt": 5.458, + "ceiling": 5529600, + "fps": 60, + "inFlight": 5, + "scale": 1, + "target": 5529600, + "tier": 0 + }, + "events": [], + "source_fps": 65.9, + "cpu": { + "mac_agent_pct": 14.0, + "frame_viewer_pct": 66.0, + "frame_tailscaled_pct": 5.1, + "frame_sshd_pct": 0.6, + "frame_gamescope_pct": 28.5, + "frame_total_pct": 32.3 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 135 Hz", + "show_s": 1.73, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + }, + { + "scenario": "scroll", + "frames_sent": 823, + "frames_drawn": 823, + "frames_shown": 523, + "duration_s": 15.6, + "stages_ms": { + "capture": { + "p50": -4.4, + "p95": 5.1, + "p99": 12.6, + "n": 823 + }, + "queue": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.1, + "n": 823 + }, + "encode": { + "p50": 6.6, + "p95": 7.8, + "p99": 10.5, + "n": 823 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 823 + }, + "network": { + "p50": 16.2, + "p95": 90.9, + "p99": 145.2, + "n": 823 + }, + "decode": { + "p50": 8.7, + "p95": 25.2, + "p99": 35.5, + "n": 823 + }, + "draw": { + "p50": 0.6, + "p95": 0.9, + "p99": 1.7, + "n": 823 + }, + "present": { + "p50": 1.9, + "p95": 20.9, + "p99": 26.5, + "n": 523 + }, + "content": { + "p50": 33.3, + "p95": 108.6, + "p99": 167.3, + "n": 823 + }, + "content_shown": { + "p50": 35.0, + "p95": 106.8, + "p99": 141.4, + "n": 523 + } + }, + "fps": 52.6, + "fps_shown": 33.4, + "late_pct": 14.84, + "stall_max": 232.0, + "stalls_over_100ms": 32, + "mbps": 9.35, + "keyframes": 1, + "size": "1920x1290", + "captured": 895, + "viewer_never_drawn": 0, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 52, + "mbps": 8.9, + "content_p50": 30.2, + "content_p95": 85.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 53, + "mbps": 9.82, + "content_p50": 32.6, + "content_p95": 106.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 53, + "mbps": 9.45, + "content_p50": 31.9, + "content_p95": 96.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 56, + "mbps": 9.42, + "content_p50": 37.6, + "content_p95": 99.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 57, + "mbps": 9.26, + "content_p50": 38.2, + "content_p95": 97.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 57, + "mbps": 11.43, + "content_p50": 56.3, + "content_p95": 144.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 50, + "mbps": 8.55, + "content_p50": 27.0, + "content_p95": 104.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 52, + "mbps": 9.63, + "content_p50": 37.1, + "content_p95": 99.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 46, + "mbps": 8.79, + "content_p50": 31.2, + "content_p95": 115.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 55, + "mbps": 9.43, + "content_p50": 29.4, + "content_p95": 97.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 50, + "mbps": 9.29, + "content_p50": 29.8, + "content_p95": 93.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 55, + "mbps": 9.78, + "content_p50": 27.0, + "content_p95": 98.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 52, + "mbps": 9.19, + "content_p50": 53.0, + "content_p95": 116.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 55, + "mbps": 9.33, + "content_p50": 45.5, + "content_p95": 102.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 45, + "mbps": 7.78, + "content_p50": 28.6, + "content_p95": 102.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 35, + "mbps": 6.17, + "content_p50": 62.5, + "content_p95": 211.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 33.3, + "content_p95": 108.6, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "acceptable", + "content_p95": "bad", + "fps": "acceptable", + "late_pct": "bad", + "stall_max": "acceptable" + }, + "controller": { + "adapt": false, + "baseRtt": 7.791, + "ceiling": 14860800, + "fps": 60, + "inFlight": 0, + "scale": 1, + "target": 14860800, + "tier": 0 + }, + "events": [], + "source_fps": 57.4, + "cpu": { + "mac_agent_pct": 7.6, + "frame_viewer_pct": 83.9, + "frame_tailscaled_pct": 7.8, + "frame_sshd_pct": 0.9, + "frame_gamescope_pct": 26.7, + "frame_total_pct": 32.6 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 135 Hz", + "show_s": 1.66, + "panel": "valve.steam.desktopgame.2001667691", + "src": "separate:13436" + } + ], + [ + { + "scenario": "test", + "frames_sent": 943, + "frames_drawn": 943, + "frames_shown": 631, + "duration_s": 15.7, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 943 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 943 + }, + "encode": { + "p50": 4.2, + "p95": 4.8, + "p99": 5.1, + "n": 943 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.3, + "n": 943 + }, + "network": { + "p50": 6.4, + "p95": 23.1, + "p99": 64.7, + "n": 943 + }, + "decode": { + "p50": 1.0, + "p95": 2.8, + "p99": 6.3, + "n": 943 + }, + "draw": { + "p50": 0.3, + "p95": 0.5, + "p99": 0.9, + "n": 943 + }, + "present": { + "p50": 5.2, + "p95": 15.7, + "p99": 21.4, + "n": 631 + }, + "content": { + "p50": 12.2, + "p95": 29.7, + "p99": 72.2, + "n": 943 + }, + "content_shown": { + "p50": 18.4, + "p95": 32.7, + "p99": 75.5, + "n": 631 + } + }, + "fps": 60.1, + "fps_shown": 40.2, + "late_pct": 5.73, + "stall_max": 123.4, + "stalls_over_100ms": 1, + "mbps": 0.48, + "keyframes": 1, + "size": "1280x720", + "captured": 1034, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 31.4, + "p95": 78.1, + "p99": 187.4, + "n": 30 + }, + "input_shown_ms": { + "p50": 38.8, + "p95": 56.9, + "p99": 62.9, + "n": 20 + }, + "input_parts_ms": { + "uplink": { + "p50": 11.4, + "p95": 40.8, + "p99": 170.5, + "n": 30 + }, + "mac": { + "p50": 7.0, + "p95": 15.0, + "p99": 16.1, + "n": 30 + }, + "back": { + "p50": 11.6, + "p95": 18.8, + "p99": 29.4, + "n": 30 + } + }, + "inputs": { + "sent": 30, + "seen": 30 + }, + "timeline": [ + { + "t": 0, + "fps": 60, + "mbps": 0.51, + "content_p50": 9.3, + "content_p95": 53.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 1, + "fps": 60, + "mbps": 0.49, + "content_p50": 9.3, + "content_p95": 51.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 2, + "fps": 60, + "mbps": 0.51, + "content_p50": 22.3, + "content_p95": 86.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 3, + "fps": 60, + "mbps": 0.51, + "content_p50": 16.9, + "content_p95": 65.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 4, + "fps": 60, + "mbps": 0.47, + "content_p50": 11.6, + "content_p95": 37.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 5, + "fps": 60, + "mbps": 0.59, + "content_p50": 12.3, + "content_p95": 16.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 6, + "fps": 60, + "mbps": 0.49, + "content_p50": 12.6, + "content_p95": 16.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 7, + "fps": 60, + "mbps": 0.48, + "content_p50": 10.9, + "content_p95": 15.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 8, + "fps": 60, + "mbps": 0.49, + "content_p50": 11.8, + "content_p95": 17.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 9, + "fps": 60, + "mbps": 0.46, + "content_p50": 11.8, + "content_p95": 19.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 10, + "fps": 60, + "mbps": 0.46, + "content_p50": 12.1, + "content_p95": 15.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 11, + "fps": 60, + "mbps": 0.45, + "content_p50": 12.6, + "content_p95": 18.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 12, + "fps": 60, + "mbps": 0.46, + "content_p50": 12.5, + "content_p95": 17.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 13, + "fps": 60, + "mbps": 0.46, + "content_p50": 13.0, + "content_p95": 18.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 14, + "fps": 60, + "mbps": 0.46, + "content_p50": 14.0, + "content_p95": 22.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 15, + "fps": 43, + "mbps": 0.3, + "content_p50": 13.6, + "content_p95": 22.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + } + ], + "adapt": [], + "content_p50": 12.2, + "content_p95": 29.7, + "input_p50": 31.4, + "input_p95": 78.1, + "grades": { + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "acceptable", + "fps": "local", + "late_pct": "bad", + "stall_max": "acceptable" + }, + "controller": { + "adapt": false, + "baseRtt": 5.125, + "ceiling": 5529600, + "fps": 60, + "inFlight": 2, + "scale": 1, + "target": 5529600, + "tier": 0 + }, + "events": [], + "source_fps": 65.9, + "cpu": { + "mac_agent_pct": 15.5, + "frame_viewer_pct": 67.2, + "frame_tailscaled_pct": 22.5, + "frame_sshd_pct": 2.6, + "frame_gamescope_pct": 28.6, + "frame_total_pct": 38.9 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 132 Hz", + "show_s": 1.52, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + }, + { + "scenario": "scroll", + "frames_sent": 624, + "frames_drawn": 622, + "frames_shown": 522, + "duration_s": 15.7, + "stages_ms": { + "capture": { + "p50": -2.0, + "p95": 13.8, + "p99": 34.2, + "n": 624 + }, + "queue": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.2, + "n": 624 + }, + "encode": { + "p50": 6.5, + "p95": 8.1, + "p99": 13.7, + "n": 624 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.5, + "n": 624 + }, + "network": { + "p50": 7.5, + "p95": 29.1, + "p99": 83.6, + "n": 624 + }, + "decode": { + "p50": 7.6, + "p95": 83.1, + "p99": 242.3, + "n": 622 + }, + "draw": { + "p50": 0.8, + "p95": 2.2, + "p99": 5.1, + "n": 622 + }, + "present": { + "p50": 1.5, + "p95": 19.9, + "p99": 25.1, + "n": 522 + }, + "content": { + "p50": 25.3, + "p95": 110.6, + "p99": 326.3, + "n": 622 + }, + "content_shown": { + "p50": 30.3, + "p95": 91.4, + "p99": 143.8, + "n": 522 + } + }, + "fps": 39.5, + "fps_shown": 33.2, + "late_pct": 33.66, + "stall_max": 362.0, + "stalls_over_100ms": 5, + "mbps": 8.59, + "keyframes": 2, + "size": "1920x1290", + "captured": 678, + "viewer_never_drawn": 2, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 42, + "mbps": 9.07, + "content_p50": 24.3, + "content_p95": 39.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 42, + "mbps": 9.11, + "content_p50": 27.3, + "content_p95": 76.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 43, + "mbps": 8.81, + "content_p50": 25.6, + "content_p95": 96.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 44, + "mbps": 8.11, + "content_p50": 46.5, + "content_p95": 89.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 37, + "mbps": 8.52, + "content_p50": 19.4, + "content_p95": 49.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 43, + "mbps": 9.51, + "content_p50": 28.1, + "content_p95": 92.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 35, + "mbps": 10.84, + "content_p50": 24.8, + "content_p95": 61.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 25, + "mbps": 6.13, + "content_p50": 21.8, + "content_p95": 62.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 52, + "mbps": 9.53, + "content_p50": 17.0, + "content_p95": 29.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 37, + "mbps": 8.18, + "content_p50": 35.9, + "content_p95": 78.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 21, + "mbps": 5.48, + "content_p50": 44.5, + "content_p95": 350.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 34, + "mbps": 7.4, + "content_p50": 27.0, + "content_p95": 54.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 54, + "mbps": 8.87, + "content_p50": 15.4, + "content_p95": 25.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 43, + "mbps": 10.59, + "content_p50": 131.1, + "content_p95": 322.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 43, + "mbps": 9.22, + "content_p50": 23.9, + "content_p95": 42.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 27, + "mbps": 5.81, + "content_p50": 27.6, + "content_p95": 53.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 25.3, + "content_p95": 110.6, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "acceptable", + "content_p95": "bad", + "fps": "bad", + "late_pct": "bad", + "stall_max": "bad" + }, + "controller": { + "adapt": false, + "baseRtt": 7.583, + "ceiling": 14860800, + "fps": 60, + "inFlight": 0, + "scale": 1, + "target": 14860800, + "tier": 0 + }, + "events": [], + "source_fps": 43.2, + "cpu": { + "mac_agent_pct": 5.8, + "frame_viewer_pct": 86.7, + "frame_tailscaled_pct": 9.9, + "frame_sshd_pct": 1.0, + "frame_gamescope_pct": 25.0, + "frame_total_pct": 77.7 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 141 Hz", + "show_s": 1.54, + "panel": "valve.steam.desktopgame.2001796574", + "src": "separate:13568" + } + ] + ], + "on": [ + [ + { + "scenario": "test", + "frames_sent": 843, + "frames_drawn": 843, + "frames_shown": 580, + "duration_s": 15.7, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 843 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 12.6, + "n": 843 + }, + "encode": { + "p50": 4.2, + "p95": 4.8, + "p99": 5.1, + "n": 843 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.3, + "n": 843 + }, + "network": { + "p50": 4.8, + "p95": 66.0, + "p99": 82.6, + "n": 843 + }, + "decode": { + "p50": 1.1, + "p95": 4.1, + "p99": 6.3, + "n": 843 + }, + "draw": { + "p50": 0.3, + "p95": 0.6, + "p99": 1.0, + "n": 843 + }, + "present": { + "p50": 4.0, + "p95": 17.6, + "p99": 23.5, + "n": 580 + }, + "content": { + "p50": 10.9, + "p95": 75.0, + "p99": 91.6, + "n": 843 + }, + "content_shown": { + "p50": 16.5, + "p95": 81.2, + "p99": 93.9, + "n": 580 + } + }, + "fps": 53.6, + "fps_shown": 36.9, + "late_pct": 6.06, + "stall_max": 127.2, + "stalls_over_100ms": 2, + "mbps": 0.43, + "keyframes": 1, + "size": "1280x720", + "captured": 1037, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 27.2, + "p95": 95.5, + "p99": 107.8, + "n": 30 + }, + "input_shown_ms": { + "p50": 32.1, + "p95": 95.9, + "p99": 108.5, + "n": 19 + }, + "input_parts_ms": { + "uplink": { + "p50": 8.0, + "p95": 44.4, + "p99": 44.8, + "n": 30 + }, + "mac": { + "p50": 5.8, + "p95": 13.7, + "p99": 17.9, + "n": 30 + }, + "back": { + "p50": 11.2, + "p95": 84.3, + "p99": 85.8, + "n": 30 + } + }, + "inputs": { + "sent": 30, + "seen": 30 + }, + "timeline": [ + { + "t": 0, + "fps": 60, + "mbps": 0.48, + "content_p50": 10.1, + "content_p95": 13.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 1, + "fps": 60, + "mbps": 0.47, + "content_p50": 10.1, + "content_p95": 11.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 2, + "fps": 60, + "mbps": 0.48, + "content_p50": 9.6, + "content_p95": 12.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 3, + "fps": 60, + "mbps": 0.48, + "content_p50": 10.0, + "content_p95": 12.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 4, + "fps": 59, + "mbps": 0.46, + "content_p50": 9.9, + "content_p95": 35.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 5, + "fps": 54, + "mbps": 0.43, + "content_p50": 9.8, + "content_p95": 51.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 6, + "fps": 52, + "mbps": 0.52, + "content_p50": 10.6, + "content_p95": 68.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 7, + "fps": 50, + "mbps": 0.39, + "content_p50": 10.6, + "content_p95": 73.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 8, + "fps": 47, + "mbps": 0.39, + "content_p50": 11.2, + "content_p95": 80.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 9, + "fps": 52, + "mbps": 0.41, + "content_p50": 11.2, + "content_p95": 79.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 10, + "fps": 52, + "mbps": 0.4, + "content_p50": 10.5, + "content_p95": 75.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 11, + "fps": 51, + "mbps": 0.4, + "content_p50": 13.4, + "content_p95": 75.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 12, + "fps": 50, + "mbps": 0.4, + "content_p50": 14.7, + "content_p95": 77.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 13, + "fps": 45, + "mbps": 0.37, + "content_p50": 15.3, + "content_p95": 102.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 14, + "fps": 52, + "mbps": 0.4, + "content_p50": 15.6, + "content_p95": 80.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 15, + "fps": 39, + "mbps": 0.3, + "content_p50": 14.7, + "content_p95": 80.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + } + ], + "adapt": [], + "content_p50": 10.9, + "content_p95": 75.0, + "input_p50": 27.2, + "input_p95": 95.5, + "grades": { + "content_p50": "local", + "content_p95": "acceptable", + "input_p50": "local", + "input_p95": "acceptable", + "fps": "acceptable", + "late_pct": "bad", + "stall_max": "acceptable" + }, + "controller": { + "adapt": true, + "baseRtt": 5.875, + "ceiling": 5529600, + "fps": 60, + "inFlight": 0, + "scale": 1, + "target": 5529600, + "tier": 0 + }, + "events": [], + "source_fps": 66.1, + "cpu": { + "mac_agent_pct": 14.9, + "frame_viewer_pct": 58.7, + "frame_tailscaled_pct": 4.9, + "frame_sshd_pct": 0.6, + "frame_gamescope_pct": 25.8, + "frame_total_pct": 28.7 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 139 Hz", + "show_s": 1.32, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + }, + { + "scenario": "scroll", + "frames_sent": 756, + "frames_drawn": 756, + "frames_shown": 606, + "duration_s": 15.6, + "stages_ms": { + "capture": { + "p50": -7.9, + "p95": 7.1, + "p99": 14.9, + "n": 756 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 7.8, + "n": 756 + }, + "encode": { + "p50": 6.8, + "p95": 9.0, + "p99": 12.8, + "n": 756 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 756 + }, + "network": { + "p50": 7.1, + "p95": 27.0, + "p99": 45.8, + "n": 756 + }, + "decode": { + "p50": 5.9, + "p95": 11.9, + "p99": 20.9, + "n": 756 + }, + "draw": { + "p50": 0.6, + "p95": 0.8, + "p99": 1.6, + "n": 756 + }, + "present": { + "p50": 3.3, + "p95": 20.5, + "p99": 25.9, + "n": 606 + }, + "content": { + "p50": 16.6, + "p95": 45.7, + "p99": 64.8, + "n": 756 + }, + "content_shown": { + "p50": 22.8, + "p95": 50.9, + "p99": 68.4, + "n": 606 + } + }, + "fps": 48.4, + "fps_shown": 38.8, + "late_pct": 21.06, + "stall_max": 88.2, + "stalls_over_100ms": 0, + "mbps": 9.16, + "keyframes": 1, + "size": "1920x1290", + "captured": 841, + "viewer_never_drawn": 0, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 50, + "mbps": 8.74, + "content_p50": 14.0, + "content_p95": 30.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 45, + "mbps": 8.64, + "content_p50": 22.9, + "content_p95": 49.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 47, + "mbps": 8.91, + "content_p50": 20.8, + "content_p95": 45.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 46, + "mbps": 9.41, + "content_p50": 18.6, + "content_p95": 53.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 49, + "mbps": 8.61, + "content_p50": 13.0, + "content_p95": 25.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 53, + "mbps": 11.17, + "content_p50": 11.7, + "content_p95": 51.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 51, + "mbps": 9.1, + "content_p50": 12.4, + "content_p95": 27.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 48, + "mbps": 9.45, + "content_p50": 19.5, + "content_p95": 56.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 55, + "mbps": 9.21, + "content_p50": 11.9, + "content_p95": 30.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 50, + "mbps": 8.55, + "content_p50": 23.1, + "content_p95": 63.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 49, + "mbps": 8.94, + "content_p50": 19.5, + "content_p95": 36.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 54, + "mbps": 9.51, + "content_p50": 16.0, + "content_p95": 31.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 41, + "mbps": 8.36, + "content_p50": 21.5, + "content_p95": 45.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 37, + "mbps": 8.55, + "content_p50": 22.4, + "content_p95": 51.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 48, + "mbps": 10.2, + "content_p50": 19.6, + "content_p95": 34.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 33, + "mbps": 5.73, + "content_p50": 10.7, + "content_p95": 54.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 16.6, + "content_p95": 45.7, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "local", + "content_p95": "acceptable", + "fps": "acceptable", + "late_pct": "bad", + "stall_max": "local" + }, + "controller": { + "adapt": true, + "baseRtt": 7.041, + "ceiling": 14860800, + "fps": 60, + "inFlight": 0, + "scale": 1, + "target": 14860800, + "tier": 0 + }, + "events": [ + { + "t": 0.19, + "e": "tier 0" + } + ], + "source_fps": 53.9, + "cpu": { + "mac_agent_pct": 7.7, + "frame_viewer_pct": 83.2, + "frame_tailscaled_pct": 9.0, + "frame_sshd_pct": 1.0, + "frame_gamescope_pct": 25.4, + "frame_total_pct": 32.3 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 142 Hz", + "show_s": 1.54, + "panel": "valve.steam.desktopgame.2001527998", + "src": "separate:13175" + } + ], + [ + { + "scenario": "test", + "frames_sent": 932, + "frames_drawn": 932, + "frames_shown": 637, + "duration_s": 15.7, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 932 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.2, + "n": 932 + }, + "encode": { + "p50": 4.2, + "p95": 4.9, + "p99": 5.2, + "n": 932 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 932 + }, + "network": { + "p50": 4.6, + "p95": 9.1, + "p99": 12.6, + "n": 932 + }, + "decode": { + "p50": 1.1, + "p95": 1.6, + "p99": 4.1, + "n": 932 + }, + "draw": { + "p50": 0.3, + "p95": 0.5, + "p99": 0.9, + "n": 932 + }, + "present": { + "p50": 5.0, + "p95": 15.8, + "p99": 19.3, + "n": 637 + }, + "content": { + "p50": 10.5, + "p95": 15.4, + "p99": 19.0, + "n": 932 + }, + "content_shown": { + "p50": 16.1, + "p95": 28.2, + "p99": 33.7, + "n": 637 + } + }, + "fps": 59.4, + "fps_shown": 40.6, + "late_pct": 0.75, + "stall_max": 126.0, + "stalls_over_100ms": 1, + "mbps": 0.48, + "keyframes": 1, + "size": "1280x720", + "captured": 1031, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 24.6, + "p95": 39.8, + "p99": 45.9, + "n": 30 + }, + "input_shown_ms": { + "p50": 34.5, + "p95": 41.8, + "p99": 58.5, + "n": 24 + }, + "input_parts_ms": { + "uplink": { + "p50": 5.1, + "p95": 19.2, + "p99": 21.0, + "n": 30 + }, + "mac": { + "p50": 5.2, + "p95": 14.8, + "p99": 16.1, + "n": 30 + }, + "back": { + "p50": 10.7, + "p95": 14.8, + "p99": 14.9, + "n": 30 + } + }, + "inputs": { + "sent": 30, + "seen": 30 + }, + "timeline": [ + { + "t": 0, + "fps": 60, + "mbps": 0.49, + "content_p50": 9.2, + "content_p95": 10.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 1, + "fps": 59, + "mbps": 0.45, + "content_p50": 9.5, + "content_p95": 12.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 2, + "fps": 61, + "mbps": 0.47, + "content_p50": 9.8, + "content_p95": 11.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 3, + "fps": 53, + "mbps": 0.45, + "content_p50": 10.0, + "content_p95": 13.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 4, + "fps": 60, + "mbps": 0.48, + "content_p50": 9.8, + "content_p95": 12.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 5, + "fps": 57, + "mbps": 0.47, + "content_p50": 10.7, + "content_p95": 14.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 6, + "fps": 60, + "mbps": 0.58, + "content_p50": 10.3, + "content_p95": 12.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 7, + "fps": 60, + "mbps": 0.49, + "content_p50": 9.8, + "content_p95": 12.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 8, + "fps": 60, + "mbps": 0.49, + "content_p50": 9.9, + "content_p95": 12.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 9, + "fps": 59, + "mbps": 0.45, + "content_p50": 10.1, + "content_p95": 12.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 10, + "fps": 61, + "mbps": 0.48, + "content_p50": 10.2, + "content_p95": 12.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 11, + "fps": 59, + "mbps": 0.45, + "content_p50": 10.6, + "content_p95": 14.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 12, + "fps": 61, + "mbps": 0.47, + "content_p50": 14.3, + "content_p95": 17.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 13, + "fps": 60, + "mbps": 0.47, + "content_p50": 14.1, + "content_p95": 16.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 14, + "fps": 60, + "mbps": 0.45, + "content_p50": 13.8, + "content_p95": 15.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 15, + "fps": 42, + "mbps": 0.32, + "content_p50": 13.8, + "content_p95": 18.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + } + ], + "adapt": [], + "content_p50": 10.5, + "content_p95": 15.4, + "input_p50": 24.6, + "input_p95": 39.8, + "grades": { + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "local", + "fps": "local", + "late_pct": "local", + "stall_max": "acceptable" + }, + "controller": { + "adapt": true, + "baseRtt": 5.459, + "ceiling": 5529600, + "fps": 60, + "inFlight": 2, + "scale": 1, + "target": 5529600, + "tier": 0 + }, + "events": [], + "source_fps": 65.7, + "cpu": { + "mac_agent_pct": 15.9, + "frame_viewer_pct": 66.0, + "frame_tailscaled_pct": 5.4, + "frame_sshd_pct": 0.8, + "frame_gamescope_pct": 28.8, + "frame_total_pct": 32.1 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 141 Hz", + "show_s": 1.49, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + }, + { + "scenario": "scroll", + "frames_sent": 604, + "frames_drawn": 604, + "frames_shown": 496, + "duration_s": 15.8, + "stages_ms": { + "capture": { + "p50": -3.6, + "p95": 11.2, + "p99": 19.8, + "n": 604 + }, + "queue": { + "p50": 0.0, + "p95": 15.4, + "p99": 23.5, + "n": 604 + }, + "encode": { + "p50": 6.8, + "p95": 9.3, + "p99": 19.2, + "n": 604 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.7, + "n": 604 + }, + "network": { + "p50": 7.9, + "p95": 76.6, + "p99": 94.2, + "n": 604 + }, + "decode": { + "p50": 6.3, + "p95": 19.1, + "p99": 25.5, + "n": 604 + }, + "draw": { + "p50": 0.6, + "p95": 1.0, + "p99": 1.6, + "n": 604 + }, + "present": { + "p50": 0.9, + "p95": 19.0, + "p99": 22.0, + "n": 496 + }, + "content": { + "p50": 23.4, + "p95": 100.3, + "p99": 145.3, + "n": 604 + }, + "content_shown": { + "p50": 28.9, + "p95": 104.7, + "p99": 158.2, + "n": 496 + } + }, + "fps": 38.2, + "fps_shown": 31.3, + "late_pct": 25.87, + "stall_max": 180.7, + "stalls_over_100ms": 22, + "mbps": 6.88, + "keyframes": 1, + "size": "1920x1290", + "captured": 843, + "viewer_never_drawn": 0, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 28, + "mbps": 6.12, + "content_p50": 30.5, + "content_p95": 161.7, + "bitrate": 7430400, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 24, + "mbps": 4.2, + "content_p50": 36.9, + "content_p95": 120.7, + "bitrate": 8223440, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 41, + "mbps": 8.28, + "content_p50": 20.3, + "content_p95": 62.3, + "bitrate": 11110898, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 42, + "mbps": 9.01, + "content_p50": 22.2, + "content_p95": 91.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 47, + "mbps": 8.61, + "content_p50": 15.8, + "content_p95": 81.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 43, + "mbps": 8.02, + "content_p50": 17.4, + "content_p95": 56.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 40, + "mbps": 7.97, + "content_p50": 29.0, + "content_p95": 82.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 36, + "mbps": 7.83, + "content_p50": 33.3, + "content_p95": 100.5, + "bitrate": 6992996, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 40, + "mbps": 5.6, + "content_p50": 21.7, + "content_p95": 97.9, + "bitrate": 7742295, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 43, + "mbps": 7.07, + "content_p50": 19.5, + "content_p95": 90.7, + "bitrate": 10470493, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 44, + "mbps": 7.09, + "content_p50": 19.9, + "content_p95": 97.5, + "bitrate": 7246454, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 35, + "mbps": 5.19, + "content_p50": 22.4, + "content_p95": 166.6, + "bitrate": 5307220, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 39, + "mbps": 4.5, + "content_p50": 22.9, + "content_p95": 84.3, + "bitrate": 5887942, + "tier": 1, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 36, + "mbps": 5.81, + "content_p50": 28.4, + "content_p95": 94.8, + "bitrate": 8002349, + "tier": 1, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 36, + "mbps": 7.68, + "content_p50": 27.4, + "content_p95": 107.5, + "bitrate": 11948287, + "tier": 1, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 30, + "mbps": 5.87, + "content_p50": 33.0, + "content_p95": 104.9, + "bitrate": 14562426, + "tier": 1, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 23.4, + "content_p95": 100.3, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "local", + "content_p95": "bad", + "fps": "bad", + "late_pct": "bad", + "stall_max": "acceptable" + }, + "controller": { + "adapt": true, + "baseRtt": 6.959, + "ceiling": 14860800, + "fps": 60, + "inFlight": 2, + "scale": 1, + "target": 7430400, + "tier": 0 + }, + "events": [ + { + "t": 0.87, + "e": "down to 7430 kbit/s: queue 191 ms, held 3, oldest 218 ms, base 7 ms, sent 4207 got 3329" + }, + { + "t": 7.54, + "e": "down to 8741 kbit/s: queue 52 ms, held 3, oldest 35 ms, base 7 ms, sent 10505 got 9712" + }, + { + "t": 7.84, + "e": "down to 6992 kbit/s: queue 17 ms, held 3, oldest 3 ms, base 7 ms, sent 8895 got 9452" + }, + { + "t": 10.54, + "e": "down to 7246 kbit/s: queue 19 ms, held 3, oldest 80 ms, base 7 ms, sent 8009 got 8051" + }, + { + "t": 11.74, + "e": "down to 5307 kbit/s: queue 26 ms, held 3, oldest 9 ms, base 6 ms, sent 5808 got 5896" + }, + { + "t": 12.34, + "e": "tier 1" + }, + { + "t": 16.14, + "e": "tier 0" + }, + { + "t": 17.34, + "e": "down to 7430 kbit/s: queue 94 ms, held 2, oldest 94 ms, base 6 ms, sent 7608 got 6892" + } + ], + "source_fps": 53.4, + "cpu": { + "mac_agent_pct": 6.5, + "frame_viewer_pct": 69.3, + "frame_tailscaled_pct": 6.4, + "frame_sshd_pct": 0.6, + "frame_gamescope_pct": 23.2, + "frame_total_pct": 27.8 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 139 Hz", + "show_s": 1.35, + "panel": "valve.steam.desktopgame.2001652781", + "src": "separate:13306" + } + ], + [ + { + "scenario": "test", + "frames_sent": 482, + "frames_drawn": 482, + "frames_shown": 479, + "duration_s": 15.7, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 482 + }, + "queue": { + "p50": 8.8, + "p95": 16.5, + "p99": 18.6, + "n": 482 + }, + "encode": { + "p50": 3.7, + "p95": 4.5, + "p99": 5.0, + "n": 482 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 482 + }, + "network": { + "p50": 6.0, + "p95": 36.8, + "p99": 84.9, + "n": 482 + }, + "decode": { + "p50": 1.0, + "p95": 2.7, + "p99": 4.8, + "n": 482 + }, + "draw": { + "p50": 0.3, + "p95": 0.7, + "p99": 0.9, + "n": 482 + }, + "present": { + "p50": 0.6, + "p95": 10.8, + "p99": 13.0, + "n": 479 + }, + "content": { + "p50": 22.9, + "p95": 54.1, + "p99": 104.8, + "n": 482 + }, + "content_shown": { + "p50": 24.9, + "p95": 54.9, + "p99": 77.6, + "n": 479 + } + }, + "fps": 30.6, + "fps_shown": 30.4, + "late_pct": 68.81, + "stall_max": 218.0, + "stalls_over_100ms": 10, + "mbps": 0.19, + "keyframes": 2, + "size": "960x540", + "captured": 1037, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 53.9, + "p95": 117.8, + "p99": 222.5, + "n": 29 + }, + "input_shown_ms": { + "p50": 54.4, + "p95": 123.9, + "p99": 223.5, + "n": 29 + }, + "input_parts_ms": { + "uplink": { + "p50": 17.7, + "p95": 77.5, + "p99": 127.7, + "n": 29 + }, + "mac": { + "p50": 8.2, + "p95": 30.4, + "p99": 63.3, + "n": 29 + }, + "back": { + "p50": 19.5, + "p95": 38.3, + "p99": 38.6, + "n": 29 + } + }, + "inputs": { + "sent": 30, + "seen": 29 + }, + "timeline": [ + { + "t": 0, + "fps": 55, + "mbps": 0.46, + "content_p50": 9.9, + "content_p95": 20.9, + "bitrate": 1545640, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 1, + "fps": 33, + "mbps": 0.28, + "content_p50": 27.7, + "content_p95": 40.2, + "bitrate": 772820, + "tier": 3, + "w": 1280, + "net": null + }, + { + "t": 2, + "fps": 32, + "mbps": 0.28, + "content_p50": 22.9, + "content_p95": 29.0, + "bitrate": 772820, + "tier": 3, + "w": 1280, + "net": null + }, + { + "t": 3, + "fps": 25, + "mbps": 0.23, + "content_p50": 26.5, + "content_p95": 106.6, + "bitrate": 386410, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 4, + "fps": 21, + "mbps": 0.14, + "content_p50": 37.0, + "content_p95": 54.1, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 5, + "fps": 24, + "mbps": 0.1, + "content_p50": 32.6, + "content_p95": 96.2, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 6, + "fps": 31, + "mbps": 0.12, + "content_p50": 29.6, + "content_p95": 46.7, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 7, + "fps": 30, + "mbps": 0.12, + "content_p50": 25.7, + "content_p95": 80.1, + "bitrate": 380000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 8, + "fps": 32, + "mbps": 0.2, + "content_p50": 29.0, + "content_p95": 56.4, + "bitrate": 671280, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 9, + "fps": 26, + "mbps": 0.13, + "content_p50": 31.8, + "content_p95": 47.8, + "bitrate": 458624, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 10, + "fps": 30, + "mbps": 0.14, + "content_p50": 36.1, + "content_p95": 64.2, + "bitrate": 458624, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 11, + "fps": 29, + "mbps": 0.14, + "content_p50": 28.1, + "content_p95": 56.7, + "bitrate": 458624, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 12, + "fps": 26, + "mbps": 0.11, + "content_p50": 18.7, + "content_p95": 27.8, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 13, + "fps": 32, + "mbps": 0.24, + "content_p50": 20.1, + "content_p95": 24.8, + "bitrate": 468000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 14, + "fps": 32, + "mbps": 0.18, + "content_p50": 19.4, + "content_p95": 32.8, + "bitrate": 468000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 15, + "fps": 24, + "mbps": 0.12, + "content_p50": 21.9, + "content_p95": 35.2, + "bitrate": 671280, + "tier": 4, + "w": 960, + "net": null + } + ], + "adapt": [], + "content_p50": 22.9, + "content_p95": 54.1, + "input_p50": 53.9, + "input_p95": 117.8, + "grades": { + "content_p50": "local", + "content_p95": "acceptable", + "input_p50": "acceptable", + "input_p95": "acceptable", + "fps": "bad", + "late_pct": "bad", + "stall_max": "acceptable" + }, + "controller": { + "adapt": true, + "baseRtt": 6.417, + "ceiling": 5529600, + "fps": 30, + "inFlight": 2, + "scale": 0.5, + "target": 1386355, + "tier": 4 + }, + "events": [ + { + "t": 0.87, + "e": "down to 1545 kbit/s: queue 18 ms, held 3, oldest 11 ms, base 5 ms, sent 424 got 420" + }, + { + "t": 1.27, + "e": "down to 772 kbit/s: queue 40 ms, held 3, oldest 55 ms, base 5 ms, sent 374 got 368" + }, + { + "t": 1.48, + "e": "tier 3" + }, + { + "t": 3.37, + "e": "down to 386 kbit/s: queue 119 ms, held 5, oldest 20 ms, base 5 ms, sent 210 got 208" + }, + { + "t": 3.88, + "e": "tier 4" + }, + { + "t": 4.07, + "e": "down to 300 kbit/s: queue 55 ms, held 0, oldest 0 ms, base 5 ms, sent 254 got 254" + }, + { + "t": 4.97, + "e": "down to 300 kbit/s: queue 113 ms, held 1, oldest 91 ms, base 5 ms, sent 154 got 161" + }, + { + "t": 9.78, + "e": "down to 458 kbit/s: queue 206 ms, held 6, oldest 0 ms, base 6 ms, sent 109 got 127" + }, + { + "t": 12.17, + "e": "down to 300 kbit/s: queue 4 ms, held 6, oldest 215 ms, base 6 ms, sent 116 got 103" + } + ], + "source_fps": 66.1, + "cpu": { + "mac_agent_pct": 10.9, + "frame_viewer_pct": 42.3, + "frame_tailscaled_pct": 3.8, + "frame_sshd_pct": 0.4, + "frame_gamescope_pct": 15.4, + "frame_total_pct": 23.3 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 70 Hz", + "show_s": 1.46, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + }, + { + "scenario": "scroll", + "frames_sent": 523, + "frames_drawn": 523, + "frames_shown": 515, + "duration_s": 15.7, + "stages_ms": { + "capture": { + "p50": -4.7, + "p95": 10.3, + "p99": 17.5, + "n": 523 + }, + "queue": { + "p50": 0.0, + "p95": 18.2, + "p99": 33.6, + "n": 523 + }, + "encode": { + "p50": 6.8, + "p95": 8.4, + "p99": 17.2, + "n": 523 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.4, + "n": 523 + }, + "network": { + "p50": 7.7, + "p95": 34.2, + "p99": 205.8, + "n": 523 + }, + "decode": { + "p50": 6.2, + "p95": 15.1, + "p99": 23.5, + "n": 523 + }, + "draw": { + "p50": 0.6, + "p95": 0.8, + "p99": 1.7, + "n": 523 + }, + "present": { + "p50": 0.5, + "p95": 11.4, + "p99": 14.3, + "n": 515 + }, + "content": { + "p50": 23.9, + "p95": 65.6, + "p99": 214.3, + "n": 523 + }, + "content_shown": { + "p50": 26.4, + "p95": 66.0, + "p99": 215.2, + "n": 515 + } + }, + "fps": 33.3, + "fps_shown": 32.7, + "late_pct": 56.13, + "stall_max": 321.6, + "stalls_over_100ms": 10, + "mbps": 6.29, + "keyframes": 2, + "size": "1920x1290", + "captured": 789, + "viewer_never_drawn": 0, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 29, + "mbps": 6.01, + "content_p50": 17.9, + "content_p95": 139.0, + "bitrate": 7430400, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 26, + "mbps": 2.1, + "content_p50": 33.3, + "content_p95": 86.5, + "bitrate": 2202984, + "tier": 3, + "w": 1440, + "net": null + }, + { + "t": 2, + "fps": 30, + "mbps": 2.11, + "content_p50": 23.4, + "content_p95": 40.3, + "bitrate": 2770610, + "tier": 3, + "w": 1440, + "net": null + }, + { + "t": 3, + "fps": 30, + "mbps": 3.24, + "content_p50": 19.3, + "content_p95": 47.2, + "bitrate": 3853181, + "tier": 3, + "w": 1440, + "net": null + }, + { + "t": 4, + "fps": 26, + "mbps": 3.85, + "content_p50": 20.9, + "content_p95": 120.6, + "bitrate": 5294082, + "tier": 3, + "w": 1440, + "net": null + }, + { + "t": 5, + "fps": 26, + "mbps": 2.95, + "content_p50": 23.3, + "content_p95": 46.7, + "bitrate": 3187180, + "tier": 2, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 29, + "mbps": 3.51, + "content_p50": 27.5, + "content_p95": 48.7, + "bitrate": 4898398, + "tier": 2, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 28, + "mbps": 5.29, + "content_p50": 28.2, + "content_p95": 48.8, + "bitrate": 6685266, + "tier": 2, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 29, + "mbps": 7.09, + "content_p50": 23.8, + "content_p95": 50.0, + "bitrate": 9063588, + "tier": 2, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 35, + "mbps": 9.72, + "content_p50": 31.0, + "content_p95": 59.7, + "bitrate": 13502047, + "tier": 1, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 42, + "mbps": 12.62, + "content_p50": 23.9, + "content_p95": 34.4, + "bitrate": 14860800, + "tier": 1, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 47, + "mbps": 11.31, + "content_p50": 20.5, + "content_p95": 41.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 35, + "mbps": 8.1, + "content_p50": 20.7, + "content_p95": 120.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 35, + "mbps": 6.11, + "content_p50": 21.1, + "content_p95": 36.8, + "bitrate": 7430400, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 42, + "mbps": 8.02, + "content_p50": 24.8, + "content_p95": 38.7, + "bitrate": 10055362, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 34, + "mbps": 6.95, + "content_p50": 19.2, + "content_p95": 309.3, + "bitrate": 12271987, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 23.9, + "content_p95": 65.6, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "local", + "content_p95": "acceptable", + "fps": "bad", + "late_pct": "bad", + "stall_max": "bad" + }, + "controller": { + "adapt": true, + "baseRtt": 7.209, + "ceiling": 14860800, + "fps": 60, + "inFlight": 1, + "scale": 1, + "target": 7502051, + "tier": 0 + }, + "events": [ + { + "t": 0.78, + "e": "down to 7430 kbit/s: queue 95 ms, held 3, oldest 0 ms, base 7 ms, sent 4459 got 5191" + }, + { + "t": 1.08, + "e": "down to 3715 kbit/s: queue 130 ms, held 2, oldest 127 ms, base 7 ms, sent 2434 got 2754" + }, + { + "t": 1.48, + "e": "down to 2202 kbit/s: queue 44 ms, held 1, oldest 37 ms, base 7 ms, sent 1917 got 2447" + }, + { + "t": 1.68, + "e": "tier 3" + }, + { + "t": 4.98, + "e": "down to 3187 kbit/s: queue 102 ms, held 0, oldest 31 ms, base 7 ms, sent 3802 got 3541" + }, + { + "t": 5.18, + "e": "tier 2" + }, + { + "t": 9.38, + "e": "tier 1" + }, + { + "t": 11.48, + "e": "tier 0" + }, + { + "t": 13.28, + "e": "down to 7430 kbit/s: queue 257 ms, held 5, oldest 240 ms, base 6 ms, sent 4758 got 6534" + }, + { + "t": 15.99, + "e": "down to 6774 kbit/s: queue 1 ms, held 6, oldest 284 ms, base 7 ms, sent 5665 got 4867" + } + ], + "source_fps": 50.3, + "cpu": { + "mac_agent_pct": 5.8, + "frame_viewer_pct": 64.7, + "frame_tailscaled_pct": 5.9, + "frame_sshd_pct": 0.6, + "frame_gamescope_pct": 16.2, + "frame_total_pct": 26.5 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 71 Hz", + "show_s": 1.37, + "panel": "valve.steam.desktopgame.2001605080", + "src": "separate:13687" + } + ] + ] + }, + "table": { + "test": { + "content_p50": { + "off": [ + 11.5, + 10.5, + 12.2 + ], + "on": [ + 10.9, + 10.5, + 22.9 + ] + }, + "content_p95": { + "off": [ + 15.8, + 52.3, + 29.7 + ], + "on": [ + 75.0, + 15.4, + 54.1 + ] + }, + "input_p50": { + "off": [ + 28.8, + 26.7, + 31.4 + ], + "on": [ + 27.2, + 24.6, + 53.9 + ] + }, + "input_p95": { + "off": [ + 38.1, + 89.3, + 78.1 + ], + "on": [ + 95.5, + 39.8, + 117.8 + ] + }, + "fps": { + "off": [ + 60.1, + 60.1, + 60.1 + ], + "on": [ + 53.6, + 59.4, + 30.6 + ] + }, + "late_pct": { + "off": [ + 0.95, + 3.61, + 5.73 + ], + "on": [ + 6.06, + 0.75, + 68.81 + ] + }, + "stall_max": { + "off": [ + 68.8, + 120.1, + 123.4 + ], + "on": [ + 127.2, + 126.0, + 218.0 + ] + }, + "mbps": { + "off": [ + 0.48, + 0.47, + 0.48 + ], + "on": [ + 0.43, + 0.48, + 0.19 + ] + } + }, + "scroll": { + "content_p50": { + "off": [ + 19.5, + 33.3, + 25.3 + ], + "on": [ + 16.6, + 23.4, + 23.9 + ] + }, + "content_p95": { + "off": [ + 44.4, + 108.6, + 110.6 + ], + "on": [ + 45.7, + 100.3, + 65.6 + ] + }, + "input_p50": { + "off": [], + "on": [] + }, + "input_p95": { + "off": [], + "on": [] + }, + "fps": { + "off": [ + 47.1, + 52.6, + 39.5 + ], + "on": [ + 48.4, + 38.2, + 33.3 + ] + }, + "late_pct": { + "off": [ + 20.92, + 14.84, + 33.66 + ], + "on": [ + 21.06, + 25.87, + 56.13 + ] + }, + "stall_max": { + "off": [ + 128.0, + 232.0, + 362.0 + ], + "on": [ + 88.2, + 180.7, + 321.6 + ] + }, + "mbps": { + "off": [ + 9.15, + 9.35, + 8.59 + ], + "on": [ + 9.16, + 6.88, + 6.29 + ] + } + } + } +} \ No newline at end of file diff --git a/bench/results/2026-09-28-a3c6e5c-dirty-ab-adapt-clean2.json b/bench/results/2026-09-28-a3c6e5c-dirty-ab-adapt-clean2.json new file mode 100644 index 0000000..d88f64a --- /dev/null +++ b/bench/results/2026-09-28-a3c6e5c-dirty-ab-adapt-clean2.json @@ -0,0 +1,4150 @@ +{ + "label": "ab-adapt-clean2", + "date": "2026-09-28T20:18:23", + "commit": "a3c6e5c+dirty", + "config": { + "quality": "balanced", + "mode": "separate", + "net": "none", + "delay_ms": 0, + "buffer_ms": 250, + "host": "frame", + "ssh_opts": [], + "encoder": "", + "duration_s": 15.0, + "browser_flags": [] + }, + "frame_build": "20260925.6191901", + "mac": "26.5.2", + "headset": "8:23.135290 [Info] - 0 - leaving standby", + "scenarios": [], + "arms": [ + "off:FRAME_MAC_VIEW_ADAPT=0", + "on:" + ], + "runs": { + "off": [ + [ + { + "scenario": "test", + "frames_sent": 925, + "frames_drawn": 925, + "frames_shown": 649, + "duration_s": 15.4, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 925 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 925 + }, + "encode": { + "p50": 3.6, + "p95": 4.5, + "p99": 4.8, + "n": 925 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.4, + "n": 925 + }, + "network": { + "p50": 4.0, + "p95": 10.6, + "p99": 57.5, + "n": 925 + }, + "decode": { + "p50": 1.0, + "p95": 2.9, + "p99": 6.5, + "n": 925 + }, + "draw": { + "p50": 0.3, + "p95": 0.8, + "p99": 1.0, + "n": 925 + }, + "present": { + "p50": 4.2, + "p95": 14.9, + "p99": 19.7, + "n": 649 + }, + "content": { + "p50": 9.5, + "p95": 16.2, + "p99": 68.6, + "n": 925 + }, + "content_shown": { + "p50": 14.8, + "p95": 26.6, + "p99": 33.5, + "n": 649 + } + }, + "fps": 59.9, + "fps_shown": 42.1, + "late_pct": 2.05, + "stall_max": 150.2, + "stalls_over_100ms": 2, + "mbps": 0.47, + "keyframes": 1, + "size": "1280x720", + "captured": 1047, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 28.3, + "p95": 38.3, + "p99": 41.2, + "n": 30 + }, + "input_shown_ms": { + "p50": 34.8, + "p95": 43.5, + "p99": 47.2, + "n": 21 + }, + "input_parts_ms": { + "uplink": { + "p50": 11.3, + "p95": 19.6, + "p99": 23.2, + "n": 30 + }, + "mac": { + "p50": 5.5, + "p95": 14.1, + "p99": 14.8, + "n": 30 + }, + "back": { + "p50": 9.9, + "p95": 13.8, + "p99": 15.2, + "n": 30 + } + }, + "inputs": { + "sent": 30, + "seen": 30 + }, + "timeline": [ + { + "t": 0, + "fps": 60, + "mbps": 0.48, + "content_p50": 9.0, + "content_p95": 12.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 1, + "fps": 60, + "mbps": 0.47, + "content_p50": 8.8, + "content_p95": 11.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 2, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.0, + "content_p95": 15.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 3, + "fps": 59, + "mbps": 0.46, + "content_p50": 9.2, + "content_p95": 12.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 4, + "fps": 61, + "mbps": 0.46, + "content_p50": 9.1, + "content_p95": 13.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 5, + "fps": 60, + "mbps": 0.57, + "content_p50": 9.0, + "content_p95": 13.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 6, + "fps": 59, + "mbps": 0.48, + "content_p50": 9.5, + "content_p95": 20.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 7, + "fps": 61, + "mbps": 0.47, + "content_p50": 9.2, + "content_p95": 20.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 8, + "fps": 60, + "mbps": 0.47, + "content_p50": 9.4, + "content_p95": 12.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 9, + "fps": 60, + "mbps": 0.45, + "content_p50": 9.8, + "content_p95": 12.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 10, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.7, + "content_p95": 13.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 11, + "fps": 60, + "mbps": 0.46, + "content_p50": 12.8, + "content_p95": 14.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 12, + "fps": 59, + "mbps": 0.46, + "content_p50": 13.6, + "content_p95": 67.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 13, + "fps": 61, + "mbps": 0.48, + "content_p50": 13.1, + "content_p95": 102.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 14, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.1, + "content_p95": 11.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 15, + "fps": 25, + "mbps": 0.19, + "content_p50": 9.2, + "content_p95": 13.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + } + ], + "adapt": [], + "content_p50": 9.5, + "content_p95": 16.2, + "input_p50": 28.3, + "input_p95": 38.3, + "grades": { + "input_replies": "local", + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "local", + "fps": "local", + "late_pct": "acceptable", + "stall_max": "acceptable" + }, + "controller": { + "adapt": false, + "baseRtt": 5.375, + "ceiling": 5529600, + "fps": 60, + "inFlight": 1, + "scale": 1, + "slack": 40, + "target": 5529600, + "tier": 0 + }, + "events": [], + "source_fps": 68.0, + "cpu": { + "mac_agent_pct": 11.5, + "frame_viewer_pct": 39.7, + "frame_tailscaled_pct": 4.8, + "frame_sshd_pct": 0.6, + "frame_gamescope_pct": 6.2, + "frame_total_pct": 23.9 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 69 Hz", + "show_s": 2.03, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + }, + { + "scenario": "scroll", + "frames_sent": 835, + "frames_drawn": 835, + "frames_shown": 628, + "duration_s": 15.2, + "stages_ms": { + "capture": { + "p50": -3.6, + "p95": 1.6, + "p99": 8.2, + "n": 835 + }, + "queue": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.1, + "n": 835 + }, + "encode": { + "p50": 6.4, + "p95": 8.6, + "p99": 14.2, + "n": 835 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.4, + "n": 835 + }, + "network": { + "p50": 6.1, + "p95": 12.0, + "p99": 24.3, + "n": 835 + }, + "decode": { + "p50": 5.8, + "p95": 10.9, + "p99": 14.7, + "n": 835 + }, + "draw": { + "p50": 0.6, + "p95": 1.4, + "p99": 1.8, + "n": 835 + }, + "present": { + "p50": 4.2, + "p95": 17.9, + "p99": 22.4, + "n": 628 + }, + "content": { + "p50": 20.0, + "p95": 32.3, + "p99": 45.5, + "n": 835 + }, + "content_shown": { + "p50": 25.2, + "p95": 42.2, + "p99": 53.7, + "n": 628 + } + }, + "fps": 54.9, + "fps_shown": 41.3, + "late_pct": 6.59, + "stall_max": 84.3, + "stalls_over_100ms": 0, + "mbps": 9.44, + "keyframes": 1, + "size": "1920x1290", + "captured": 946, + "viewer_never_drawn": 0, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 57, + "mbps": 10.21, + "content_p50": 19.2, + "content_p95": 24.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 55, + "mbps": 9.18, + "content_p50": 18.6, + "content_p95": 45.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 57, + "mbps": 8.93, + "content_p50": 19.1, + "content_p95": 24.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 57, + "mbps": 9.42, + "content_p50": 19.5, + "content_p95": 25.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 56, + "mbps": 9.88, + "content_p50": 19.9, + "content_p95": 25.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 52, + "mbps": 9.25, + "content_p50": 19.7, + "content_p95": 27.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 53, + "mbps": 12.28, + "content_p50": 20.6, + "content_p95": 38.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 53, + "mbps": 9.14, + "content_p50": 19.2, + "content_p95": 24.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 55, + "mbps": 9.31, + "content_p50": 20.4, + "content_p95": 28.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 55, + "mbps": 9.22, + "content_p50": 21.8, + "content_p95": 34.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 56, + "mbps": 8.46, + "content_p50": 22.7, + "content_p95": 41.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 56, + "mbps": 9.18, + "content_p50": 20.0, + "content_p95": 23.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 54, + "mbps": 9.08, + "content_p50": 21.4, + "content_p95": 27.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 55, + "mbps": 8.78, + "content_p50": 19.6, + "content_p95": 28.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 55, + "mbps": 9.45, + "content_p50": 18.8, + "content_p95": 26.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 9, + "mbps": 1.68, + "content_p50": 25.8, + "content_p95": 44.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 20.0, + "content_p95": 32.3, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "local", + "content_p95": "local", + "fps": "acceptable", + "late_pct": "bad", + "stall_max": "local" + }, + "controller": { + "adapt": false, + "baseRtt": 6.875, + "ceiling": 14860800, + "fps": 60, + "inFlight": 1, + "scale": 1, + "slack": 40, + "target": 14860800, + "tier": 0 + }, + "events": [], + "source_fps": 62.2, + "cpu": { + "mac_agent_pct": 7.4, + "frame_viewer_pct": 70.3, + "frame_tailscaled_pct": 7.7, + "frame_sshd_pct": 0.8, + "frame_gamescope_pct": 6.7, + "frame_total_pct": 28.8 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 139 Hz", + "show_s": 1.27, + "panel": "valve.steam.desktopgame.2001924380", + "src": "separate:5316" + } + ], + [ + { + "scenario": "test", + "frames_sent": 912, + "frames_drawn": 912, + "frames_shown": 618, + "duration_s": 15.2, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 912 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.2, + "n": 912 + }, + "encode": { + "p50": 4.2, + "p95": 4.9, + "p99": 5.2, + "n": 912 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 912 + }, + "network": { + "p50": 3.8, + "p95": 8.0, + "p99": 11.1, + "n": 912 + }, + "decode": { + "p50": 1.0, + "p95": 2.3, + "p99": 4.2, + "n": 912 + }, + "draw": { + "p50": 0.3, + "p95": 0.7, + "p99": 1.0, + "n": 912 + }, + "present": { + "p50": 5.1, + "p95": 16.1, + "p99": 17.8, + "n": 618 + }, + "content": { + "p50": 9.7, + "p95": 14.3, + "p99": 17.6, + "n": 912 + }, + "content_shown": { + "p50": 15.3, + "p95": 27.8, + "p99": 29.4, + "n": 618 + } + }, + "fps": 60.0, + "fps_shown": 40.7, + "late_pct": 0.33, + "stall_max": 120.5, + "stalls_over_100ms": 1, + "mbps": 0.48, + "keyframes": 1, + "size": "1280x720", + "captured": 1032, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 22.6, + "p95": 32.7, + "p99": 46.2, + "n": 30 + }, + "input_shown_ms": { + "p50": 28.6, + "p95": 39.0, + "p99": 47.0, + "n": 23 + }, + "input_parts_ms": { + "uplink": { + "p50": 7.3, + "p95": 18.0, + "p99": 22.1, + "n": 30 + }, + "mac": { + "p50": 5.2, + "p95": 14.0, + "p99": 17.2, + "n": 30 + }, + "back": { + "p50": 9.9, + "p95": 13.6, + "p99": 14.3, + "n": 30 + } + }, + "inputs": { + "sent": 30, + "seen": 30 + }, + "timeline": [ + { + "t": 0, + "fps": 60, + "mbps": 0.5, + "content_p50": 8.9, + "content_p95": 71.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 1, + "fps": 60, + "mbps": 0.48, + "content_p50": 8.7, + "content_p95": 10.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 2, + "fps": 60, + "mbps": 0.48, + "content_p50": 8.7, + "content_p95": 10.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 3, + "fps": 60, + "mbps": 0.48, + "content_p50": 9.1, + "content_p95": 10.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 4, + "fps": 60, + "mbps": 0.47, + "content_p50": 8.7, + "content_p95": 10.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 5, + "fps": 60, + "mbps": 0.58, + "content_p50": 8.7, + "content_p95": 11.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 6, + "fps": 60, + "mbps": 0.48, + "content_p50": 8.9, + "content_p95": 11.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 7, + "fps": 60, + "mbps": 0.48, + "content_p50": 8.7, + "content_p95": 10.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 8, + "fps": 60, + "mbps": 0.48, + "content_p50": 10.2, + "content_p95": 13.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 9, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.6, + "content_p95": 11.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 10, + "fps": 60, + "mbps": 0.47, + "content_p50": 10.6, + "content_p95": 14.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 11, + "fps": 60, + "mbps": 0.47, + "content_p50": 12.3, + "content_p95": 15.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 12, + "fps": 60, + "mbps": 0.47, + "content_p50": 12.8, + "content_p95": 15.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 13, + "fps": 60, + "mbps": 0.48, + "content_p50": 12.7, + "content_p95": 14.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 14, + "fps": 60, + "mbps": 0.46, + "content_p50": 12.9, + "content_p95": 17.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 15, + "fps": 12, + "mbps": 0.09, + "content_p50": 12.6, + "content_p95": 14.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + } + ], + "adapt": [], + "content_p50": 9.7, + "content_p95": 14.3, + "input_p50": 22.6, + "input_p95": 32.7, + "grades": { + "input_replies": "local", + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "local", + "fps": "local", + "late_pct": "local", + "stall_max": "acceptable" + }, + "controller": { + "adapt": false, + "baseRtt": 5.334, + "ceiling": 5529600, + "fps": 60, + "inFlight": 2, + "scale": 1, + "slack": 40, + "target": 5529600, + "tier": 0 + }, + "events": [], + "source_fps": 67.9, + "cpu": { + "mac_agent_pct": 16.1, + "frame_viewer_pct": 46.3, + "frame_tailscaled_pct": 5.1, + "frame_sshd_pct": 0.6, + "frame_gamescope_pct": 8.9, + "frame_total_pct": 28.6 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 140 Hz", + "show_s": 1.27, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + }, + { + "scenario": "scroll", + "frames_sent": 864, + "frames_drawn": 864, + "frames_shown": 626, + "duration_s": 15.2, + "stages_ms": { + "capture": { + "p50": -4.6, + "p95": 0.4, + "p99": 0.8, + "n": 864 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 864 + }, + "encode": { + "p50": 6.7, + "p95": 8.5, + "p99": 11.1, + "n": 864 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.3, + "n": 864 + }, + "network": { + "p50": 6.0, + "p95": 9.2, + "p99": 14.7, + "n": 864 + }, + "decode": { + "p50": 5.7, + "p95": 9.9, + "p99": 12.9, + "n": 864 + }, + "draw": { + "p50": 0.6, + "p95": 1.3, + "p99": 1.9, + "n": 864 + }, + "present": { + "p50": 4.3, + "p95": 16.8, + "p99": 21.2, + "n": 626 + }, + "content": { + "p50": 19.5, + "p95": 25.2, + "p99": 32.2, + "n": 864 + }, + "content_shown": { + "p50": 24.4, + "p95": 36.6, + "p99": 41.8, + "n": 626 + } + }, + "fps": 57.0, + "fps_shown": 41.3, + "late_pct": 3.36, + "stall_max": 105.4, + "stalls_over_100ms": 1, + "mbps": 9.44, + "keyframes": 1, + "size": "1920x1290", + "captured": 978, + "viewer_never_drawn": 0, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 56, + "mbps": 9.38, + "content_p50": 19.1, + "content_p95": 24.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 56, + "mbps": 9.47, + "content_p50": 19.2, + "content_p95": 24.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 56, + "mbps": 8.94, + "content_p50": 19.3, + "content_p95": 25.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 57, + "mbps": 9.51, + "content_p50": 19.7, + "content_p95": 26.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 57, + "mbps": 9.65, + "content_p50": 18.6, + "content_p95": 22.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 57, + "mbps": 9.0, + "content_p50": 18.7, + "content_p95": 24.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 55, + "mbps": 11.82, + "content_p50": 20.2, + "content_p95": 33.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 57, + "mbps": 9.24, + "content_p50": 18.9, + "content_p95": 21.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 58, + "mbps": 9.37, + "content_p50": 19.3, + "content_p95": 25.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 57, + "mbps": 9.2, + "content_p50": 20.5, + "content_p95": 27.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 58, + "mbps": 9.12, + "content_p50": 20.1, + "content_p95": 25.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 57, + "mbps": 9.29, + "content_p50": 19.4, + "content_p95": 25.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 57, + "mbps": 9.29, + "content_p50": 19.9, + "content_p95": 23.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 58, + "mbps": 8.8, + "content_p50": 19.8, + "content_p95": 23.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 58, + "mbps": 9.34, + "content_p50": 20.3, + "content_p95": 28.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 10, + "mbps": 1.68, + "content_p50": 19.6, + "content_p95": 22.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 19.5, + "content_p95": 25.2, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "local", + "content_p95": "local", + "fps": "acceptable", + "late_pct": "acceptable", + "stall_max": "acceptable" + }, + "controller": { + "adapt": false, + "baseRtt": 6.75, + "ceiling": 14860800, + "fps": 60, + "inFlight": 0, + "scale": 1, + "slack": 40, + "target": 14860800, + "tier": 0 + }, + "events": [], + "source_fps": 64.3, + "cpu": { + "mac_agent_pct": 8.6, + "frame_viewer_pct": 73.0, + "frame_tailscaled_pct": 8.0, + "frame_sshd_pct": 0.9, + "frame_gamescope_pct": 7.8, + "frame_total_pct": 30.0 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 142 Hz", + "show_s": 1.26, + "panel": "valve.steam.desktopgame.2001204762", + "src": "separate:5722" + } + ], + [ + { + "scenario": "test", + "frames_sent": 912, + "frames_drawn": 912, + "frames_shown": 618, + "duration_s": 15.2, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 912 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 912 + }, + "encode": { + "p50": 4.2, + "p95": 4.8, + "p99": 5.1, + "n": 912 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 912 + }, + "network": { + "p50": 4.6, + "p95": 8.9, + "p99": 13.7, + "n": 912 + }, + "decode": { + "p50": 1.2, + "p95": 2.2, + "p99": 3.9, + "n": 912 + }, + "draw": { + "p50": 0.3, + "p95": 0.7, + "p99": 1.0, + "n": 912 + }, + "present": { + "p50": 5.4, + "p95": 17.0, + "p99": 19.3, + "n": 618 + }, + "content": { + "p50": 10.6, + "p95": 15.0, + "p99": 19.9, + "n": 912 + }, + "content_shown": { + "p50": 16.0, + "p95": 27.7, + "p99": 30.8, + "n": 618 + } + }, + "fps": 60.0, + "fps_shown": 40.6, + "late_pct": 0.99, + "stall_max": 37.7, + "stalls_over_100ms": 0, + "mbps": 0.47, + "keyframes": 1, + "size": "1280x720", + "captured": 1034, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 30.9, + "p95": 103.7, + "p99": 196.7, + "n": 30 + }, + "input_shown_ms": { + "p50": 36.3, + "p95": 106.8, + "p99": 200.8, + "n": 19 + }, + "input_parts_ms": { + "uplink": { + "p50": 13.9, + "p95": 74.2, + "p99": 183.2, + "n": 30 + }, + "mac": { + "p50": 5.5, + "p95": 16.4, + "p99": 18.7, + "n": 30 + }, + "back": { + "p50": 11.2, + "p95": 15.5, + "p99": 20.2, + "n": 30 + } + }, + "inputs": { + "sent": 30, + "seen": 30 + }, + "timeline": [ + { + "t": 0, + "fps": 60, + "mbps": 0.49, + "content_p50": 9.4, + "content_p95": 13.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 1, + "fps": 60, + "mbps": 0.47, + "content_p50": 9.5, + "content_p95": 11.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 2, + "fps": 60, + "mbps": 0.47, + "content_p50": 9.4, + "content_p95": 11.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 3, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.6, + "content_p95": 11.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 4, + "fps": 60, + "mbps": 0.47, + "content_p50": 10.1, + "content_p95": 12.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 5, + "fps": 60, + "mbps": 0.58, + "content_p50": 10.5, + "content_p95": 12.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 6, + "fps": 60, + "mbps": 0.47, + "content_p50": 10.3, + "content_p95": 13.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 7, + "fps": 60, + "mbps": 0.47, + "content_p50": 10.3, + "content_p95": 12.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 8, + "fps": 60, + "mbps": 0.46, + "content_p50": 10.1, + "content_p95": 11.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 9, + "fps": 60, + "mbps": 0.45, + "content_p50": 10.9, + "content_p95": 14.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 10, + "fps": 60, + "mbps": 0.46, + "content_p50": 11.4, + "content_p95": 15.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 11, + "fps": 60, + "mbps": 0.48, + "content_p50": 11.5, + "content_p95": 16.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 12, + "fps": 60, + "mbps": 0.48, + "content_p50": 13.3, + "content_p95": 17.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 13, + "fps": 60, + "mbps": 0.46, + "content_p50": 12.1, + "content_p95": 16.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 14, + "fps": 60, + "mbps": 0.45, + "content_p50": 12.5, + "content_p95": 16.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 15, + "fps": 12, + "mbps": 0.09, + "content_p50": 12.9, + "content_p95": 15.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + } + ], + "adapt": [], + "content_p50": 10.6, + "content_p95": 15.0, + "input_p50": 30.9, + "input_p95": 103.7, + "grades": { + "input_replies": "local", + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "acceptable", + "fps": "local", + "late_pct": "local", + "stall_max": "local" + }, + "controller": { + "adapt": false, + "baseRtt": 5.625, + "ceiling": 5529600, + "fps": 60, + "inFlight": 2, + "scale": 1, + "slack": 40, + "target": 5529600, + "tier": 0 + }, + "events": [], + "source_fps": 68.0, + "cpu": { + "mac_agent_pct": 15.7, + "frame_viewer_pct": 52.1, + "frame_tailscaled_pct": 8.9, + "frame_sshd_pct": 1.3, + "frame_gamescope_pct": 9.1, + "frame_total_pct": 23.1 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 139 Hz", + "show_s": 1.26, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + }, + { + "scenario": "scroll", + "frames_sent": 870, + "frames_drawn": 870, + "frames_shown": 627, + "duration_s": 15.2, + "stages_ms": { + "capture": { + "p50": -4.4, + "p95": 0.4, + "p99": 0.8, + "n": 870 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 870 + }, + "encode": { + "p50": 6.7, + "p95": 8.5, + "p99": 13.9, + "n": 870 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.5, + "n": 870 + }, + "network": { + "p50": 6.3, + "p95": 27.2, + "p99": 133.0, + "n": 870 + }, + "decode": { + "p50": 5.9, + "p95": 15.5, + "p99": 21.7, + "n": 870 + }, + "draw": { + "p50": 0.6, + "p95": 1.5, + "p99": 1.9, + "n": 870 + }, + "present": { + "p50": 3.6, + "p95": 16.4, + "p99": 21.1, + "n": 627 + }, + "content": { + "p50": 20.5, + "p95": 45.9, + "p99": 170.0, + "n": 870 + }, + "content_shown": { + "p50": 27.1, + "p95": 55.8, + "p99": 200.4, + "n": 627 + } + }, + "fps": 57.3, + "fps_shown": 41.3, + "late_pct": 4.6, + "stall_max": 321.6, + "stalls_over_100ms": 2, + "mbps": 9.4, + "keyframes": 1, + "size": "1920x1290", + "captured": 986, + "viewer_never_drawn": 0, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 56, + "mbps": 9.16, + "content_p50": 27.2, + "content_p95": 49.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 55, + "mbps": 9.46, + "content_p50": 23.6, + "content_p95": 237.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 58, + "mbps": 8.98, + "content_p50": 21.5, + "content_p95": 26.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 58, + "mbps": 9.81, + "content_p50": 22.4, + "content_p95": 55.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 57, + "mbps": 9.66, + "content_p50": 21.1, + "content_p95": 31.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 58, + "mbps": 8.89, + "content_p50": 19.9, + "content_p95": 24.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 58, + "mbps": 11.75, + "content_p50": 20.5, + "content_p95": 74.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 57, + "mbps": 9.0, + "content_p50": 20.1, + "content_p95": 27.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 57, + "mbps": 9.35, + "content_p50": 20.5, + "content_p95": 24.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 57, + "mbps": 9.17, + "content_p50": 19.8, + "content_p95": 24.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 57, + "mbps": 8.89, + "content_p50": 19.3, + "content_p95": 25.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 58, + "mbps": 9.09, + "content_p50": 18.9, + "content_p95": 22.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 58, + "mbps": 9.74, + "content_p50": 19.7, + "content_p95": 31.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 57, + "mbps": 8.62, + "content_p50": 18.5, + "content_p95": 21.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 58, + "mbps": 9.36, + "content_p50": 25.9, + "content_p95": 37.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 11, + "mbps": 1.84, + "content_p50": 21.5, + "content_p95": 123.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 20.5, + "content_p95": 45.9, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "local", + "content_p95": "acceptable", + "fps": "acceptable", + "late_pct": "acceptable", + "stall_max": "bad" + }, + "controller": { + "adapt": false, + "baseRtt": 7.125, + "ceiling": 14860800, + "fps": 60, + "inFlight": 1, + "scale": 1, + "slack": 40, + "target": 14860800, + "tier": 0 + }, + "events": [], + "source_fps": 64.9, + "cpu": { + "mac_agent_pct": 8.8, + "frame_viewer_pct": 84.1, + "frame_tailscaled_pct": 13.1, + "frame_sshd_pct": -0.9, + "frame_gamescope_pct": 9.9, + "frame_total_pct": 35.6 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 143 Hz", + "show_s": 1.44, + "panel": "valve.steam.desktopgame.2001042553", + "src": "separate:5856" + } + ] + ], + "on": [ + [ + { + "scenario": "test", + "frames_sent": 942, + "frames_drawn": 942, + "frames_shown": 641, + "duration_s": 15.7, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 942 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 942 + }, + "encode": { + "p50": 3.7, + "p95": 4.5, + "p99": 5.5, + "n": 942 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.5, + "n": 942 + }, + "network": { + "p50": 4.0, + "p95": 7.1, + "p99": 14.6, + "n": 942 + }, + "decode": { + "p50": 1.0, + "p95": 2.3, + "p99": 3.7, + "n": 942 + }, + "draw": { + "p50": 0.3, + "p95": 0.7, + "p99": 1.0, + "n": 942 + }, + "present": { + "p50": 4.9, + "p95": 17.5, + "p99": 19.9, + "n": 641 + }, + "content": { + "p50": 9.5, + "p95": 13.2, + "p99": 23.3, + "n": 942 + }, + "content_shown": { + "p50": 15.0, + "p95": 27.3, + "p99": 29.9, + "n": 641 + } + }, + "fps": 60.0, + "fps_shown": 40.9, + "late_pct": 1.17, + "stall_max": 75.6, + "stalls_over_100ms": 0, + "mbps": 0.47, + "keyframes": 1, + "size": "1280x720", + "captured": 1064, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 27.2, + "p95": 51.1, + "p99": 60.4, + "n": 30 + }, + "input_shown_ms": { + "p50": 33.7, + "p95": 53.0, + "p99": 72.9, + "n": 20 + }, + "input_parts_ms": { + "uplink": { + "p50": 11.8, + "p95": 39.5, + "p99": 41.9, + "n": 30 + }, + "mac": { + "p50": 4.6, + "p95": 14.7, + "p99": 19.7, + "n": 30 + }, + "back": { + "p50": 9.5, + "p95": 14.0, + "p99": 15.3, + "n": 30 + } + }, + "inputs": { + "sent": 30, + "seen": 30 + }, + "timeline": [ + { + "t": 0, + "fps": 60, + "mbps": 0.48, + "content_p50": 9.2, + "content_p95": 12.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 1, + "fps": 60, + "mbps": 0.47, + "content_p50": 8.9, + "content_p95": 24.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 2, + "fps": 60, + "mbps": 0.48, + "content_p50": 8.7, + "content_p95": 11.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 3, + "fps": 60, + "mbps": 0.47, + "content_p50": 8.6, + "content_p95": 11.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 4, + "fps": 60, + "mbps": 0.45, + "content_p50": 9.0, + "content_p95": 10.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 5, + "fps": 60, + "mbps": 0.59, + "content_p50": 8.6, + "content_p95": 10.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 6, + "fps": 60, + "mbps": 0.45, + "content_p50": 8.5, + "content_p95": 11.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 7, + "fps": 60, + "mbps": 0.47, + "content_p50": 8.6, + "content_p95": 13.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 8, + "fps": 60, + "mbps": 0.45, + "content_p50": 8.0, + "content_p95": 11.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 9, + "fps": 60, + "mbps": 0.45, + "content_p50": 9.1, + "content_p95": 14.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 10, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.2, + "content_p95": 11.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 11, + "fps": 60, + "mbps": 0.46, + "content_p50": 10.8, + "content_p95": 14.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 12, + "fps": 60, + "mbps": 0.46, + "content_p50": 10.6, + "content_p95": 12.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 13, + "fps": 60, + "mbps": 0.46, + "content_p50": 11.4, + "content_p95": 15.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 14, + "fps": 60, + "mbps": 0.44, + "content_p50": 11.0, + "content_p95": 12.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 15, + "fps": 42, + "mbps": 0.31, + "content_p50": 11.2, + "content_p95": 14.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + } + ], + "adapt": [], + "content_p50": 9.5, + "content_p95": 13.2, + "input_p50": 27.2, + "input_p95": 51.1, + "grades": { + "input_replies": "local", + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "local", + "fps": "local", + "late_pct": "acceptable", + "stall_max": "local" + }, + "controller": { + "adapt": true, + "baseRtt": 5.541, + "ceiling": 5529600, + "fps": 60, + "inFlight": 1, + "scale": 1, + "slack": 52.292, + "target": 5529600, + "tier": 0 + }, + "events": [], + "source_fps": 67.8, + "cpu": { + "mac_agent_pct": 12.1, + "frame_viewer_pct": 45.2, + "frame_tailscaled_pct": 5.1, + "frame_sshd_pct": 0.7, + "frame_gamescope_pct": 7.9, + "frame_total_pct": 27.2 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 141 Hz", + "show_s": 1.3, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + }, + { + "scenario": "scroll", + "frames_sent": 857, + "frames_drawn": 857, + "frames_shown": 618, + "duration_s": 15.2, + "stages_ms": { + "capture": { + "p50": -2.9, + "p95": 4.5, + "p99": 6.3, + "n": 857 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 857 + }, + "encode": { + "p50": 6.6, + "p95": 9.0, + "p99": 15.0, + "n": 857 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.4, + "n": 857 + }, + "network": { + "p50": 5.2, + "p95": 12.8, + "p99": 25.4, + "n": 857 + }, + "decode": { + "p50": 5.8, + "p95": 10.4, + "p99": 13.5, + "n": 857 + }, + "draw": { + "p50": 0.6, + "p95": 1.4, + "p99": 1.8, + "n": 857 + }, + "present": { + "p50": 4.7, + "p95": 16.8, + "p99": 20.3, + "n": 618 + }, + "content": { + "p50": 19.5, + "p95": 31.7, + "p99": 45.5, + "n": 857 + }, + "content_shown": { + "p50": 25.7, + "p95": 38.5, + "p99": 47.8, + "n": 618 + } + }, + "fps": 56.4, + "fps_shown": 40.7, + "late_pct": 5.6, + "stall_max": 120.5, + "stalls_over_100ms": 1, + "mbps": 9.4, + "keyframes": 1, + "size": "1920x1290", + "captured": 971, + "viewer_never_drawn": 0, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 53, + "mbps": 9.14, + "content_p50": 20.2, + "content_p95": 40.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 58, + "mbps": 9.36, + "content_p50": 20.2, + "content_p95": 43.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 54, + "mbps": 9.42, + "content_p50": 18.9, + "content_p95": 28.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 57, + "mbps": 9.46, + "content_p50": 18.1, + "content_p95": 25.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 56, + "mbps": 8.81, + "content_p50": 18.9, + "content_p95": 25.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 57, + "mbps": 11.11, + "content_p50": 18.6, + "content_p95": 34.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 57, + "mbps": 9.96, + "content_p50": 18.1, + "content_p95": 24.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 57, + "mbps": 8.79, + "content_p50": 18.4, + "content_p95": 27.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 58, + "mbps": 9.1, + "content_p50": 20.3, + "content_p95": 27.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 56, + "mbps": 9.65, + "content_p50": 19.3, + "content_p95": 28.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 56, + "mbps": 9.15, + "content_p50": 20.4, + "content_p95": 34.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 57, + "mbps": 9.46, + "content_p50": 20.4, + "content_p95": 31.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 57, + "mbps": 9.53, + "content_p50": 20.4, + "content_p95": 31.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 57, + "mbps": 8.94, + "content_p50": 20.2, + "content_p95": 25.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 57, + "mbps": 9.2, + "content_p50": 19.4, + "content_p95": 27.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 10, + "mbps": 1.69, + "content_p50": 18.1, + "content_p95": 23.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 19.5, + "content_p95": 31.7, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "local", + "content_p95": "local", + "fps": "acceptable", + "late_pct": "bad", + "stall_max": "acceptable" + }, + "controller": { + "adapt": true, + "baseRtt": 6.666, + "ceiling": 14860800, + "fps": 60, + "inFlight": 1, + "scale": 1, + "slack": 47.293, + "target": 14860800, + "tier": 0 + }, + "events": [], + "source_fps": 63.9, + "cpu": { + "mac_agent_pct": 8.5, + "frame_viewer_pct": 72.5, + "frame_tailscaled_pct": 8.3, + "frame_sshd_pct": 0.9, + "frame_gamescope_pct": 7.1, + "frame_total_pct": 30.1 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 139 Hz", + "show_s": 1.27, + "panel": "valve.steam.desktopgame.2001321860", + "src": "separate:5452" + } + ], + [ + { + "scenario": "test", + "frames_sent": 740, + "frames_drawn": 740, + "frames_shown": 520, + "duration_s": 15.2, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 740 + }, + "queue": { + "p50": 0.0, + "p95": 13.2, + "p99": 19.4, + "n": 740 + }, + "encode": { + "p50": 4.1, + "p95": 4.8, + "p99": 5.2, + "n": 740 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 740 + }, + "network": { + "p50": 3.6, + "p95": 125.3, + "p99": 165.0, + "n": 740 + }, + "decode": { + "p50": 1.0, + "p95": 6.1, + "p99": 10.2, + "n": 740 + }, + "draw": { + "p50": 0.3, + "p95": 0.6, + "p99": 1.1, + "n": 740 + }, + "present": { + "p50": 0.9, + "p95": 15.9, + "p99": 20.1, + "n": 520 + }, + "content": { + "p50": 9.5, + "p95": 137.4, + "p99": 173.0, + "n": 740 + }, + "content_shown": { + "p50": 14.9, + "p95": 66.7, + "p99": 174.2, + "n": 520 + } + }, + "fps": 48.7, + "fps_shown": 34.2, + "late_pct": 16.35, + "stall_max": 267.4, + "stalls_over_100ms": 16, + "mbps": 0.36, + "keyframes": 2, + "size": "960x540", + "captured": 1032, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 32.1, + "p95": 63.7, + "p99": 188.2, + "n": 29 + }, + "input_shown_ms": { + "p50": 36.2, + "p95": 64.1, + "p99": 188.9, + "n": 20 + }, + "input_parts_ms": { + "uplink": { + "p50": 10.5, + "p95": 31.9, + "p99": 47.7, + "n": 29 + }, + "mac": { + "p50": 5.4, + "p95": 19.2, + "p99": 30.2, + "n": 29 + }, + "back": { + "p50": 9.7, + "p95": 29.3, + "p99": 168.5, + "n": 29 + } + }, + "inputs": { + "sent": 30, + "seen": 29 + }, + "timeline": [ + { + "t": 0, + "fps": 60, + "mbps": 0.47, + "content_p50": 8.6, + "content_p95": 10.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 1, + "fps": 60, + "mbps": 0.47, + "content_p50": 8.8, + "content_p95": 11.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 2, + "fps": 60, + "mbps": 0.47, + "content_p50": 8.6, + "content_p95": 10.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 3, + "fps": 60, + "mbps": 0.47, + "content_p50": 9.3, + "content_p95": 12.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 4, + "fps": 60, + "mbps": 0.47, + "content_p50": 8.6, + "content_p95": 10.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 5, + "fps": 60, + "mbps": 0.56, + "content_p50": 9.1, + "content_p95": 11.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 6, + "fps": 59, + "mbps": 0.45, + "content_p50": 9.1, + "content_p95": 25.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 7, + "fps": 60, + "mbps": 0.46, + "content_p50": 12.2, + "content_p95": 57.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 8, + "fps": 52, + "mbps": 0.4, + "content_p50": 22.0, + "content_p95": 159.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 9, + "fps": 47, + "mbps": 0.35, + "content_p50": 90.3, + "content_p95": 164.7, + "bitrate": 2764800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 10, + "fps": 45, + "mbps": 0.34, + "content_p50": 93.8, + "content_p95": 166.0, + "bitrate": 345600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 11, + "fps": 21, + "mbps": 0.18, + "content_p50": 81.0, + "content_p95": 177.2, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 12, + "fps": 27, + "mbps": 0.13, + "content_p50": 29.0, + "content_p95": 176.3, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 13, + "fps": 31, + "mbps": 0.11, + "content_p50": 21.7, + "content_p95": 29.4, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 14, + "fps": 31, + "mbps": 0.11, + "content_p50": 19.6, + "content_p95": 29.3, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 15, + "fps": 7, + "mbps": 0.02, + "content_p50": 21.9, + "content_p95": 29.3, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + } + ], + "adapt": [], + "content_p50": 9.5, + "content_p95": 137.4, + "input_p50": 32.1, + "input_p95": 63.7, + "grades": { + "input_replies": "local", + "content_p50": "local", + "content_p95": "bad", + "input_p50": "local", + "input_p95": "local", + "fps": "acceptable", + "late_pct": "bad", + "stall_max": "bad" + }, + "controller": { + "adapt": true, + "baseRtt": 5.625, + "ceiling": 5529600, + "fps": 30, + "inFlight": 3, + "scale": 0.5, + "slack": 86.958, + "target": 917248, + "tier": 4 + }, + "events": [ + { + "t": 9.22, + "e": "down to 2764 kbit/s: queue 25 ms, held 4, oldest 0 ms, base 5 ms, sent 354 got 379" + }, + { + "t": 10.02, + "e": "down to 1382 kbit/s: queue 99 ms, held 5, oldest 0 ms, base 5 ms, sent 346 got 346" + }, + { + "t": 10.32, + "e": "down to 691 kbit/s: queue 109 ms, held 4, oldest 15 ms, base 5 ms, sent 316 got 309" + }, + { + "t": 10.82, + "e": "down to 345 kbit/s: queue 105 ms, held 5, oldest 177 ms, base 5 ms, sent 304 got 243" + }, + { + "t": 11.22, + "e": "down to 300 kbit/s: queue 118 ms, held 0, oldest 42 ms, base 5 ms, sent 302 got 332" + }, + { + "t": 11.42, + "e": "tier 4" + }, + { + "t": 11.62, + "e": "down to 300 kbit/s: queue 140 ms, held 3, oldest 0 ms, base 5 ms, sent 291 got 291" + }, + { + "t": 12.22, + "e": "down to 300 kbit/s: queue 61 ms, held 2, oldest 1 ms, base 5 ms, sent 152 got 137" + }, + { + "t": 12.52, + "e": "down to 300 kbit/s: queue 102 ms, held 0, oldest 4 ms, base 5 ms, sent 156 got 146" + } + ], + "source_fps": 67.9, + "cpu": { + "mac_agent_pct": 13.4, + "frame_viewer_pct": 35.6, + "frame_tailscaled_pct": 4.1, + "frame_sshd_pct": 0.5, + "frame_gamescope_pct": 7.0, + "frame_total_pct": 24.6 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 138 Hz", + "show_s": 1.29, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + }, + { + "scenario": "scroll", + "frames_sent": 852, + "frames_drawn": 852, + "frames_shown": 607, + "duration_s": 15.2, + "stages_ms": { + "capture": { + "p50": -4.5, + "p95": 0.5, + "p99": 6.7, + "n": 852 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.5, + "n": 852 + }, + "encode": { + "p50": 6.6, + "p95": 9.3, + "p99": 16.5, + "n": 852 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.4, + "n": 852 + }, + "network": { + "p50": 5.9, + "p95": 15.8, + "p99": 58.8, + "n": 852 + }, + "decode": { + "p50": 5.8, + "p95": 11.6, + "p99": 23.2, + "n": 852 + }, + "draw": { + "p50": 0.6, + "p95": 1.3, + "p99": 1.8, + "n": 852 + }, + "present": { + "p50": 4.2, + "p95": 16.4, + "p99": 20.1, + "n": 607 + }, + "content": { + "p50": 19.6, + "p95": 37.9, + "p99": 106.2, + "n": 852 + }, + "content_shown": { + "p50": 25.7, + "p95": 45.1, + "p99": 110.7, + "n": 607 + } + }, + "fps": 56.2, + "fps_shown": 40.0, + "late_pct": 5.52, + "stall_max": 147.0, + "stalls_over_100ms": 3, + "mbps": 9.36, + "keyframes": 1, + "size": "1920x1290", + "captured": 982, + "viewer_never_drawn": 0, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 44, + "mbps": 8.61, + "content_p50": 29.3, + "content_p95": 126.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 55, + "mbps": 9.63, + "content_p50": 19.5, + "content_p95": 29.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 57, + "mbps": 9.2, + "content_p50": 20.4, + "content_p95": 71.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 53, + "mbps": 9.04, + "content_p50": 19.5, + "content_p95": 36.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 58, + "mbps": 9.08, + "content_p50": 18.9, + "content_p95": 22.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 57, + "mbps": 9.22, + "content_p50": 19.3, + "content_p95": 25.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 57, + "mbps": 11.78, + "content_p50": 20.5, + "content_p95": 37.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 57, + "mbps": 8.73, + "content_p50": 19.2, + "content_p95": 24.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 58, + "mbps": 9.29, + "content_p50": 19.1, + "content_p95": 23.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 58, + "mbps": 9.5, + "content_p50": 18.9, + "content_p95": 24.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 58, + "mbps": 8.58, + "content_p50": 19.2, + "content_p95": 23.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 57, + "mbps": 9.44, + "content_p50": 20.2, + "content_p95": 28.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 58, + "mbps": 9.7, + "content_p50": 20.1, + "content_p95": 37.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 58, + "mbps": 9.15, + "content_p50": 20.8, + "content_p95": 41.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 58, + "mbps": 9.29, + "content_p50": 19.7, + "content_p95": 23.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 9, + "mbps": 1.64, + "content_p50": 20.9, + "content_p95": 38.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 19.6, + "content_p95": 37.9, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "local", + "content_p95": "local", + "fps": "acceptable", + "late_pct": "bad", + "stall_max": "acceptable" + }, + "controller": { + "adapt": true, + "baseRtt": 6.625, + "ceiling": 14860800, + "fps": 60, + "inFlight": 1, + "scale": 1, + "slack": 48.604, + "target": 14860800, + "tier": 0 + }, + "events": [], + "source_fps": 64.6, + "cpu": { + "mac_agent_pct": 8.3, + "frame_viewer_pct": 71.7, + "frame_tailscaled_pct": 8.0, + "frame_sshd_pct": 0.9, + "frame_gamescope_pct": 7.2, + "frame_total_pct": 29.1 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 139 Hz", + "show_s": 1.27, + "panel": "valve.steam.desktopgame.2001299121", + "src": "separate:5587" + } + ], + [ + { + "scenario": "test", + "frames_sent": 908, + "frames_drawn": 908, + "frames_shown": 382, + "duration_s": 15.2, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 908 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.2, + "n": 908 + }, + "encode": { + "p50": 4.2, + "p95": 4.8, + "p99": 5.1, + "n": 908 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 908 + }, + "network": { + "p50": 4.2, + "p95": 7.5, + "p99": 10.1, + "n": 908 + }, + "decode": { + "p50": 1.1, + "p95": 4.0, + "p99": 4.6, + "n": 908 + }, + "draw": { + "p50": 0.4, + "p95": 1.1, + "p99": 1.3, + "n": 908 + }, + "present": { + "p50": 6.2, + "p95": 17.0, + "p99": 18.3, + "n": 382 + }, + "content": { + "p50": 10.5, + "p95": 14.2, + "p99": 16.6, + "n": 908 + }, + "content_shown": { + "p50": 17.1, + "p95": 27.2, + "p99": 29.4, + "n": 382 + } + }, + "fps": 59.7, + "fps_shown": 25.1, + "late_pct": 0.33, + "stall_max": 122.8, + "stalls_over_100ms": 1, + "mbps": 0.47, + "keyframes": 1, + "size": "1280x720", + "captured": 1033, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 25.2, + "p95": 33.5, + "p99": 34.1, + "n": 30 + }, + "input_shown_ms": { + "p50": 32.4, + "p95": 43.4, + "p99": 43.4, + "n": 9 + }, + "input_parts_ms": { + "uplink": { + "p50": 5.9, + "p95": 17.6, + "p99": 18.3, + "n": 30 + }, + "mac": { + "p50": 6.2, + "p95": 14.5, + "p99": 20.8, + "n": 30 + }, + "back": { + "p50": 10.7, + "p95": 13.5, + "p99": 14.8, + "n": 30 + } + }, + "inputs": { + "sent": 30, + "seen": 30 + }, + "timeline": [ + { + "t": 0, + "fps": 60, + "mbps": 0.48, + "content_p50": 10.7, + "content_p95": 15.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 1, + "fps": 60, + "mbps": 0.47, + "content_p50": 12.1, + "content_p95": 14.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 2, + "fps": 60, + "mbps": 0.47, + "content_p50": 11.0, + "content_p95": 14.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 3, + "fps": 60, + "mbps": 0.47, + "content_p50": 9.9, + "content_p95": 12.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 4, + "fps": 60, + "mbps": 0.46, + "content_p50": 10.3, + "content_p95": 13.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 5, + "fps": 60, + "mbps": 0.56, + "content_p50": 10.3, + "content_p95": 13.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 6, + "fps": 60, + "mbps": 0.45, + "content_p50": 9.3, + "content_p95": 11.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 7, + "fps": 56, + "mbps": 0.44, + "content_p50": 8.9, + "content_p95": 10.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 8, + "fps": 60, + "mbps": 0.47, + "content_p50": 9.4, + "content_p95": 11.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 9, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.6, + "content_p95": 11.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 10, + "fps": 60, + "mbps": 0.44, + "content_p50": 9.1, + "content_p95": 11.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 11, + "fps": 60, + "mbps": 0.46, + "content_p50": 11.1, + "content_p95": 13.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 12, + "fps": 60, + "mbps": 0.45, + "content_p50": 12.3, + "content_p95": 14.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 13, + "fps": 60, + "mbps": 0.44, + "content_p50": 12.3, + "content_p95": 14.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 14, + "fps": 60, + "mbps": 0.45, + "content_p50": 12.4, + "content_p95": 14.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 15, + "fps": 12, + "mbps": 0.09, + "content_p50": 12.5, + "content_p95": 13.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + } + ], + "adapt": [], + "content_p50": 10.5, + "content_p95": 14.2, + "input_p50": 25.2, + "input_p95": 33.5, + "grades": { + "input_replies": "local", + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "local", + "fps": "local", + "late_pct": "local", + "stall_max": "acceptable" + }, + "controller": { + "adapt": true, + "baseRtt": 5.167, + "ceiling": 5529600, + "fps": 60, + "inFlight": 0, + "scale": 1, + "slack": 49.102, + "target": 5529600, + "tier": 0 + }, + "events": [], + "source_fps": 68.0, + "cpu": { + "mac_agent_pct": 15.5, + "frame_viewer_pct": 51.6, + "frame_tailscaled_pct": 5.2, + "frame_sshd_pct": 0.8, + "frame_gamescope_pct": 9.5, + "frame_total_pct": 33.4 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 135 Hz", + "show_s": 1.37, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + }, + { + "scenario": "scroll", + "frames_sent": 843, + "frames_drawn": 843, + "frames_shown": 414, + "duration_s": 15.2, + "stages_ms": { + "capture": { + "p50": -4.7, + "p95": 0.5, + "p99": 1.3, + "n": 843 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 843 + }, + "encode": { + "p50": 6.6, + "p95": 9.2, + "p99": 17.1, + "n": 843 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.4, + "n": 843 + }, + "network": { + "p50": 6.4, + "p95": 14.7, + "p99": 68.7, + "n": 843 + }, + "decode": { + "p50": 6.0, + "p95": 11.7, + "p99": 16.1, + "n": 843 + }, + "draw": { + "p50": 0.7, + "p95": 1.4, + "p99": 1.7, + "n": 843 + }, + "present": { + "p50": 4.0, + "p95": 18.1, + "p99": 25.9, + "n": 414 + }, + "content": { + "p50": 20.5, + "p95": 33.6, + "p99": 103.0, + "n": 843 + }, + "content_shown": { + "p50": 26.1, + "p95": 41.0, + "p99": 122.7, + "n": 414 + } + }, + "fps": 55.6, + "fps_shown": 27.3, + "late_pct": 6.29, + "stall_max": 153.6, + "stalls_over_100ms": 3, + "mbps": 9.25, + "keyframes": 1, + "size": "1920x1290", + "captured": 970, + "viewer_never_drawn": 0, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 53, + "mbps": 9.13, + "content_p50": 22.1, + "content_p95": 110.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 53, + "mbps": 9.46, + "content_p50": 20.7, + "content_p95": 27.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 56, + "mbps": 9.19, + "content_p50": 20.2, + "content_p95": 26.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 57, + "mbps": 9.36, + "content_p50": 20.1, + "content_p95": 27.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 57, + "mbps": 9.66, + "content_p50": 19.9, + "content_p95": 24.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 57, + "mbps": 8.68, + "content_p50": 20.5, + "content_p95": 25.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 46, + "mbps": 10.48, + "content_p50": 25.4, + "content_p95": 126.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 56, + "mbps": 9.16, + "content_p50": 20.1, + "content_p95": 35.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 57, + "mbps": 8.76, + "content_p50": 21.1, + "content_p95": 28.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 57, + "mbps": 9.29, + "content_p50": 20.5, + "content_p95": 24.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 57, + "mbps": 8.87, + "content_p50": 20.4, + "content_p95": 40.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 58, + "mbps": 9.17, + "content_p50": 20.3, + "content_p95": 23.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 56, + "mbps": 9.05, + "content_p50": 19.5, + "content_p95": 23.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 56, + "mbps": 8.85, + "content_p50": 20.8, + "content_p95": 31.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 57, + "mbps": 9.48, + "content_p50": 20.6, + "content_p95": 34.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 10, + "mbps": 1.65, + "content_p50": 24.8, + "content_p95": 39.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 20.5, + "content_p95": 33.6, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "local", + "content_p95": "local", + "fps": "acceptable", + "late_pct": "bad", + "stall_max": "acceptable" + }, + "controller": { + "adapt": true, + "baseRtt": 7.167, + "ceiling": 14860800, + "fps": 60, + "inFlight": 1, + "scale": 1, + "slack": 48.229, + "target": 14860800, + "tier": 0 + }, + "events": [], + "source_fps": 63.8, + "cpu": { + "mac_agent_pct": 8.5, + "frame_viewer_pct": 78.1, + "frame_tailscaled_pct": 8.2, + "frame_sshd_pct": 0.9, + "frame_gamescope_pct": 9.0, + "frame_total_pct": 29.8 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 71 Hz", + "show_s": 1.26, + "panel": "valve.steam.desktopgame.2001733678", + "src": "separate:5994" + } + ] + ] + }, + "table": { + "test": { + "content_p50": { + "off": [ + 9.5, + 9.7, + 10.6 + ], + "on": [ + 9.5, + 9.5, + 10.5 + ] + }, + "content_p95": { + "off": [ + 16.2, + 14.3, + 15.0 + ], + "on": [ + 13.2, + 137.4, + 14.2 + ] + }, + "input_p50": { + "off": [ + 28.3, + 22.6, + 30.9 + ], + "on": [ + 27.2, + 32.1, + 25.2 + ] + }, + "input_p95": { + "off": [ + 38.3, + 32.7, + 103.7 + ], + "on": [ + 51.1, + 63.7, + 33.5 + ] + }, + "fps": { + "off": [ + 59.9, + 60.0, + 60.0 + ], + "on": [ + 60.0, + 48.7, + 59.7 + ] + }, + "late_pct": { + "off": [ + 2.05, + 0.33, + 0.99 + ], + "on": [ + 1.17, + 16.35, + 0.33 + ] + }, + "stall_max": { + "off": [ + 150.2, + 120.5, + 37.7 + ], + "on": [ + 75.6, + 267.4, + 122.8 + ] + }, + "mbps": { + "off": [ + 0.47, + 0.48, + 0.47 + ], + "on": [ + 0.47, + 0.36, + 0.47 + ] + } + }, + "scroll": { + "content_p50": { + "off": [ + 20.0, + 19.5, + 20.5 + ], + "on": [ + 19.5, + 19.6, + 20.5 + ] + }, + "content_p95": { + "off": [ + 32.3, + 25.2, + 45.9 + ], + "on": [ + 31.7, + 37.9, + 33.6 + ] + }, + "input_p50": { + "off": [], + "on": [] + }, + "input_p95": { + "off": [], + "on": [] + }, + "fps": { + "off": [ + 54.9, + 57.0, + 57.3 + ], + "on": [ + 56.4, + 56.2, + 55.6 + ] + }, + "late_pct": { + "off": [ + 6.59, + 3.36, + 4.6 + ], + "on": [ + 5.6, + 5.52, + 6.29 + ] + }, + "stall_max": { + "off": [ + 84.3, + 105.4, + 321.6 + ], + "on": [ + 120.5, + 147.0, + 153.6 + ] + }, + "mbps": { + "off": [ + 9.44, + 9.44, + 9.4 + ], + "on": [ + 9.4, + 9.36, + 9.25 + ] + } + } + } +} \ No newline at end of file diff --git a/bench/results/2026-09-28-a3c6e5c-dirty-ab-adapt-step3.json b/bench/results/2026-09-28-a3c6e5c-dirty-ab-adapt-step3.json new file mode 100644 index 0000000..332db76 --- /dev/null +++ b/bench/results/2026-09-28-a3c6e5c-dirty-ab-adapt-step3.json @@ -0,0 +1,3743 @@ +{ + "label": "ab-adapt-step3", + "date": "2026-09-28T20:24:10", + "commit": "a3c6e5c+dirty", + "config": { + "quality": "balanced", + "mode": "separate", + "net": "50@0,3@8,50@16", + "delay_ms": 0, + "buffer_ms": 250, + "host": "frame", + "ssh_opts": [], + "encoder": "", + "duration_s": 24.0, + "browser_flags": [] + }, + "frame_build": "20260925.6191901", + "mac": "26.5.2", + "headset": "8:23.135290 [Info] - 0 - leaving standby", + "scenarios": [], + "arms": [ + "off:FRAME_MAC_VIEW_ADAPT=0", + "on:" + ], + "runs": { + "off": [ + [ + { + "scenario": "scroll", + "frames_sent": 1179, + "frames_drawn": 1110, + "frames_shown": 576, + "duration_s": 24.2, + "stages_ms": { + "capture": { + "p50": -4.7, + "p95": 0.5, + "p99": 4.3, + "n": 1179 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.3, + "n": 1179 + }, + "encode": { + "p50": 6.6, + "p95": 8.7, + "p99": 13.8, + "n": 1179 + }, + "socket": { + "p50": 0.1, + "p95": 1690.5, + "p99": 2066.6, + "n": 1179 + }, + "network": { + "p50": 13.6, + "p95": 4872.0, + "p99": 5491.7, + "n": 1179 + }, + "decode": { + "p50": 6.0, + "p95": 46.0, + "p99": 75.8, + "n": 1110 + }, + "draw": { + "p50": 0.7, + "p95": 1.3, + "p99": 2.0, + "n": 1110 + }, + "present": { + "p50": 5.1, + "p95": 34.3, + "p99": 44.3, + "n": 576 + }, + "content": { + "p50": 27.5, + "p95": 4986.4, + "p99": 5514.6, + "n": 1110 + }, + "content_shown": { + "p50": 36.0, + "p95": 4759.3, + "p99": 5454.2, + "n": 576 + } + }, + "fps": 45.8, + "fps_shown": 23.8, + "late_pct": 19.19, + "stall_max": 232.1, + "stalls_over_100ms": 5, + "mbps": 8.62, + "keyframes": 3, + "size": "1920x1290", + "captured": 1500, + "viewer_never_drawn": 69, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 55, + "mbps": 9.44, + "content_p50": 22.8, + "content_p95": 36.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 1, + "fps": 57, + "mbps": 9.75, + "content_p50": 25.3, + "content_p95": 139.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 2, + "fps": 58, + "mbps": 9.8, + "content_p50": 24.2, + "content_p95": 110.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 3, + "fps": 58, + "mbps": 9.52, + "content_p50": 22.5, + "content_p95": 37.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 4, + "fps": 56, + "mbps": 8.91, + "content_p50": 23.4, + "content_p95": 30.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 5, + "fps": 53, + "mbps": 8.91, + "content_p50": 23.4, + "content_p95": 77.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 6, + "fps": 58, + "mbps": 12.0, + "content_p50": 22.9, + "content_p95": 54.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 7, + "fps": 58, + "mbps": 8.82, + "content_p50": 21.8, + "content_p95": 28.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 8, + "fps": 58, + "mbps": 9.48, + "content_p50": 1079.9, + "content_p95": 2016.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 9, + "fps": 57, + "mbps": 9.19, + "content_p50": 3208.0, + "content_p95": 4125.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 10, + "fps": 58, + "mbps": 8.91, + "content_p50": 5139.1, + "content_p95": 5585.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 11, + "fps": 29, + "mbps": 6.33, + "content_p50": 5245.6, + "content_p95": 5430.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 12, + "fps": 10, + "mbps": 2.92, + "content_p50": 4133.7, + "content_p95": 4182.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 13, + "fps": 9, + "mbps": 4.39, + "content_p50": 3552.8, + "content_p95": 3589.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 14, + "fps": 25, + "mbps": 5.77, + "content_p50": 2820.8, + "content_p95": 2926.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 15, + "fps": 10, + "mbps": 2.52, + "content_p50": 1747.5, + "content_p95": 1791.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 16, + "fps": 13, + "mbps": 10.51, + "content_p50": 1139.0, + "content_p95": 1179.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 17, + "fps": 31, + "mbps": 13.74, + "content_p50": 164.3, + "content_p95": 295.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 18, + "fps": 57, + "mbps": 9.05, + "content_p50": 24.4, + "content_p95": 30.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 19, + "fps": 57, + "mbps": 9.45, + "content_p50": 29.5, + "content_p95": 43.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 20, + "fps": 58, + "mbps": 9.16, + "content_p50": 27.3, + "content_p95": 63.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 21, + "fps": 58, + "mbps": 9.75, + "content_p50": 26.9, + "content_p95": 40.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 22, + "fps": 57, + "mbps": 9.45, + "content_p50": 28.5, + "content_p95": 34.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 23, + "fps": 58, + "mbps": 9.21, + "content_p50": 25.9, + "content_p95": 30.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 24, + "fps": 12, + "mbps": 1.9, + "content_p50": 25.9, + "content_p95": 28.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + } + ], + "adapt": [ + { + "at": 8.0, + "to_mbit": 3.0, + "p95_bound": 63.8, + "worst_p95": 5585.2, + "settled_s": 10.0 + } + ], + "content_p50": 27.5, + "content_p95": 4986.4, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "acceptable", + "content_p95": "bad", + "fps": "acceptable", + "late_pct": "bad", + "stall_max": "acceptable", + "adapt@8s": "bad" + }, + "controller": { + "adapt": false, + "baseRtt": 10.625, + "ceiling": 14860800, + "fps": 60, + "inFlight": 1, + "scale": 1, + "slack": 40, + "target": 14860800, + "tier": 0 + }, + "events": [], + "source_fps": 62.0, + "cpu": { + "mac_agent_pct": 7.7, + "frame_viewer_pct": 62.6, + "frame_tailscaled_pct": 20.6, + "frame_sshd_pct": 2.4, + "frame_gamescope_pct": 9.7, + "frame_total_pct": 44.8 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 71 Hz", + "show_s": 1.28, + "panel": "valve.steam.desktopgame.2001525774", + "src": "separate:6129" + }, + { + "scenario": "test", + "frames_sent": 1464, + "frames_drawn": 1464, + "frames_shown": 1198, + "duration_s": 24.4, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 1464 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 1464 + }, + "encode": { + "p50": 4.2, + "p95": 4.9, + "p99": 5.2, + "n": 1464 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.3, + "n": 1464 + }, + "network": { + "p50": 6.8, + "p95": 102.3, + "p99": 162.7, + "n": 1464 + }, + "decode": { + "p50": 1.1, + "p95": 10.7, + "p99": 16.6, + "n": 1464 + }, + "draw": { + "p50": 0.3, + "p95": 0.8, + "p99": 1.2, + "n": 1464 + }, + "present": { + "p50": 0.6, + "p95": 12.6, + "p99": 15.1, + "n": 1198 + }, + "content": { + "p50": 12.8, + "p95": 116.8, + "p99": 168.7, + "n": 1464 + }, + "content_shown": { + "p50": 14.7, + "p95": 30.1, + "p99": 173.8, + "n": 1198 + } + }, + "fps": 60.0, + "fps_shown": 49.1, + "late_pct": 3.69, + "stall_max": 185.4, + "stalls_over_100ms": 16, + "mbps": 0.49, + "keyframes": 2, + "size": "1280x720", + "captured": 1584, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 34.4, + "p95": 86.0, + "p99": 207.9, + "n": 48 + }, + "input_shown_ms": { + "p50": 34.9, + "p95": 86.7, + "p99": 208.5, + "n": 46 + }, + "input_parts_ms": { + "uplink": { + "p50": 13.9, + "p95": 29.5, + "p99": 55.9, + "n": 48 + }, + "mac": { + "p50": 6.5, + "p95": 16.2, + "p99": 18.2, + "n": 48 + }, + "back": { + "p50": 12.8, + "p95": 48.6, + "p99": 178.9, + "n": 48 + } + }, + "inputs": { + "sent": 48, + "seen": 48 + }, + "timeline": [ + { + "t": 0, + "fps": 60, + "mbps": 0.52, + "content_p50": 11.7, + "content_p95": 15.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 1, + "fps": 60, + "mbps": 0.48, + "content_p50": 11.8, + "content_p95": 16.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 2, + "fps": 60, + "mbps": 0.48, + "content_p50": 11.2, + "content_p95": 16.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 3, + "fps": 60, + "mbps": 0.47, + "content_p50": 10.3, + "content_p95": 14.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 4, + "fps": 60, + "mbps": 0.48, + "content_p50": 11.1, + "content_p95": 15.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 5, + "fps": 60, + "mbps": 0.5, + "content_p50": 10.7, + "content_p95": 13.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 6, + "fps": 60, + "mbps": 0.59, + "content_p50": 11.6, + "content_p95": 15.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 7, + "fps": 60, + "mbps": 0.5, + "content_p50": 11.9, + "content_p95": 14.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 8, + "fps": 60, + "mbps": 0.49, + "content_p50": 15.7, + "content_p95": 19.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 9, + "fps": 60, + "mbps": 0.47, + "content_p50": 15.3, + "content_p95": 19.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 10, + "fps": 60, + "mbps": 0.47, + "content_p50": 16.1, + "content_p95": 55.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 11, + "fps": 60, + "mbps": 0.48, + "content_p50": 23.0, + "content_p95": 71.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 12, + "fps": 60, + "mbps": 0.49, + "content_p50": 72.3, + "content_p95": 174.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 13, + "fps": 60, + "mbps": 0.5, + "content_p50": 79.0, + "content_p95": 173.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 14, + "fps": 60, + "mbps": 0.48, + "content_p50": 48.3, + "content_p95": 163.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 15, + "fps": 60, + "mbps": 0.48, + "content_p50": 68.1, + "content_p95": 168.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 16, + "fps": 60, + "mbps": 0.57, + "content_p50": 13.8, + "content_p95": 125.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 17, + "fps": 60, + "mbps": 0.48, + "content_p50": 13.7, + "content_p95": 19.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 18, + "fps": 60, + "mbps": 0.46, + "content_p50": 11.6, + "content_p95": 14.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 19, + "fps": 60, + "mbps": 0.48, + "content_p50": 12.1, + "content_p95": 16.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 20, + "fps": 60, + "mbps": 0.47, + "content_p50": 12.1, + "content_p95": 21.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 21, + "fps": 60, + "mbps": 0.47, + "content_p50": 10.0, + "content_p95": 14.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 22, + "fps": 60, + "mbps": 0.47, + "content_p50": 12.5, + "content_p95": 15.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 23, + "fps": 60, + "mbps": 0.47, + "content_p50": 9.8, + "content_p95": 13.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 24, + "fps": 24, + "mbps": 0.17, + "content_p50": 10.2, + "content_p95": 14.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + } + ], + "adapt": [ + { + "at": 8.0, + "to_mbit": 3.0, + "p95_bound": 40.0, + "worst_p95": 174.5, + "settled_s": 9.0 + } + ], + "content_p50": 12.8, + "content_p95": 116.8, + "input_p50": 34.4, + "input_p95": 86.0, + "grades": { + "input_replies": "local", + "content_p50": "local", + "content_p95": "bad", + "input_p50": "local", + "input_p95": "acceptable", + "fps": "local", + "late_pct": "acceptable", + "stall_max": "acceptable", + "adapt@8s": "bad" + }, + "controller": { + "adapt": false, + "baseRtt": 6.042, + "ceiling": 5529600, + "fps": 60, + "inFlight": 2, + "scale": 1, + "slack": 40, + "target": 5529600, + "tier": 0 + }, + "events": [], + "source_fps": 64.9, + "cpu": { + "mac_agent_pct": 14.9, + "frame_viewer_pct": 43.7, + "frame_tailscaled_pct": 21.3, + "frame_sshd_pct": -1.7, + "frame_gamescope_pct": 9.8, + "frame_total_pct": 42.4 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 72 Hz", + "show_s": 1.42, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + } + ], + [ + { + "scenario": "scroll", + "frames_sent": 1168, + "frames_drawn": 1081, + "frames_shown": 565, + "duration_s": 24.3, + "stages_ms": { + "capture": { + "p50": -7.8, + "p95": 0.4, + "p99": 3.8, + "n": 1168 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 1168 + }, + "encode": { + "p50": 6.7, + "p95": 9.3, + "p99": 15.8, + "n": 1168 + }, + "socket": { + "p50": 0.1, + "p95": 1496.6, + "p99": 2773.0, + "n": 1168 + }, + "network": { + "p50": 9.8, + "p95": 4895.9, + "p99": 5418.1, + "n": 1168 + }, + "decode": { + "p50": 5.9, + "p95": 17.7, + "p99": 65.2, + "n": 1081 + }, + "draw": { + "p50": 0.6, + "p95": 1.3, + "p99": 1.8, + "n": 1081 + }, + "present": { + "p50": 5.3, + "p95": 30.8, + "p99": 41.9, + "n": 565 + }, + "content": { + "p50": 23.8, + "p95": 4355.1, + "p99": 5463.9, + "n": 1081 + }, + "content_shown": { + "p50": 32.9, + "p95": 4606.2, + "p99": 5408.3, + "n": 565 + } + }, + "fps": 44.5, + "fps_shown": 23.3, + "late_pct": 17.67, + "stall_max": 398.2, + "stalls_over_100ms": 2, + "mbps": 8.39, + "keyframes": 3, + "size": "1920x1290", + "captured": 1509, + "viewer_never_drawn": 87, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 55, + "mbps": 9.32, + "content_p50": 23.8, + "content_p95": 73.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 1, + "fps": 58, + "mbps": 9.52, + "content_p50": 22.8, + "content_p95": 28.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 2, + "fps": 58, + "mbps": 9.4, + "content_p50": 22.9, + "content_p95": 26.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 3, + "fps": 57, + "mbps": 9.62, + "content_p50": 23.6, + "content_p95": 33.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 4, + "fps": 58, + "mbps": 8.86, + "content_p50": 22.4, + "content_p95": 25.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 5, + "fps": 57, + "mbps": 9.67, + "content_p50": 23.2, + "content_p95": 29.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 6, + "fps": 58, + "mbps": 12.04, + "content_p50": 23.7, + "content_p95": 46.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 7, + "fps": 58, + "mbps": 8.85, + "content_p50": 22.5, + "content_p95": 27.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 8, + "fps": 57, + "mbps": 9.27, + "content_p50": 979.8, + "content_p95": 1991.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 9, + "fps": 58, + "mbps": 8.89, + "content_p50": 3131.5, + "content_p95": 3947.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 10, + "fps": 58, + "mbps": 9.25, + "content_p50": 5057.4, + "content_p95": 5532.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 11, + "fps": 5, + "mbps": 6.6, + "content_p50": 5369.2, + "content_p95": 5392.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 12, + "fps": 0, + "mbps": 3.01, + "content_p50": null, + "content_p95": null, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 13, + "fps": 0, + "mbps": 5.04, + "content_p50": null, + "content_p95": null, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 14, + "fps": 0, + "mbps": 1.64, + "content_p50": null, + "content_p95": null, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 15, + "fps": 0, + "mbps": 2.19, + "content_p50": null, + "content_p95": null, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 16, + "fps": 27, + "mbps": 13.34, + "content_p50": 332.4, + "content_p95": 451.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 17, + "fps": 57, + "mbps": 9.49, + "content_p50": 24.1, + "content_p95": 32.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 18, + "fps": 58, + "mbps": 9.06, + "content_p50": 23.0, + "content_p95": 28.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 19, + "fps": 58, + "mbps": 9.39, + "content_p50": 23.3, + "content_p95": 26.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 20, + "fps": 57, + "mbps": 8.89, + "content_p50": 23.4, + "content_p95": 32.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 21, + "fps": 58, + "mbps": 9.87, + "content_p50": 23.7, + "content_p95": 28.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 22, + "fps": 57, + "mbps": 9.49, + "content_p50": 22.2, + "content_p95": 29.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 23, + "fps": 57, + "mbps": 8.75, + "content_p50": 22.3, + "content_p95": 27.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 24, + "fps": 15, + "mbps": 2.32, + "content_p50": 22.0, + "content_p95": 25.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + } + ], + "adapt": [ + { + "at": 8.0, + "to_mbit": 3.0, + "p95_bound": 42.7, + "worst_p95": 5532.6, + "settled_s": 9.0 + } + ], + "content_p50": 23.8, + "content_p95": 4355.1, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "local", + "content_p95": "bad", + "fps": "bad", + "late_pct": "bad", + "stall_max": "bad", + "adapt@8s": "bad" + }, + "controller": { + "adapt": false, + "baseRtt": 10.292, + "ceiling": 14860800, + "fps": 60, + "inFlight": 1, + "scale": 1, + "slack": 40, + "target": 14860800, + "tier": 0 + }, + "events": [], + "source_fps": 62.1, + "cpu": { + "mac_agent_pct": 7.4, + "frame_viewer_pct": 65.4, + "frame_tailscaled_pct": 8.1, + "frame_sshd_pct": 0.9, + "frame_gamescope_pct": 11.5, + "frame_total_pct": 38.7 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 71 Hz", + "show_s": 1.29, + "panel": "valve.steam.desktopgame.2001170343", + "src": "separate:6551" + }, + { + "scenario": "test", + "frames_sent": 1459, + "frames_drawn": 1459, + "frames_shown": 572, + "duration_s": 24.3, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 1459 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 1459 + }, + "encode": { + "p50": 4.1, + "p95": 4.8, + "p99": 5.1, + "n": 1459 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.3, + "n": 1459 + }, + "network": { + "p50": 4.6, + "p95": 9.0, + "p99": 17.9, + "n": 1459 + }, + "decode": { + "p50": 1.1, + "p95": 2.6, + "p99": 4.2, + "n": 1459 + }, + "draw": { + "p50": 0.4, + "p95": 0.8, + "p99": 1.2, + "n": 1459 + }, + "present": { + "p50": 5.1, + "p95": 15.6, + "p99": 18.8, + "n": 572 + }, + "content": { + "p50": 10.7, + "p95": 15.6, + "p99": 24.6, + "n": 1459 + }, + "content_shown": { + "p50": 16.1, + "p95": 27.1, + "p99": 31.8, + "n": 572 + } + }, + "fps": 60.0, + "fps_shown": 23.5, + "late_pct": 1.92, + "stall_max": 157.3, + "stalls_over_100ms": 1, + "mbps": 0.46, + "keyframes": 2, + "size": "1280x720", + "captured": 1580, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 29.6, + "p95": 50.1, + "p99": 163.0, + "n": 48 + }, + "input_shown_ms": { + "p50": 36.5, + "p95": 52.4, + "p99": 163.6, + "n": 21 + }, + "input_parts_ms": { + "uplink": { + "p50": 12.0, + "p95": 21.1, + "p99": 34.4, + "n": 48 + }, + "mac": { + "p50": 5.5, + "p95": 15.1, + "p99": 16.3, + "n": 48 + }, + "back": { + "p50": 11.0, + "p95": 15.7, + "p99": 150.9, + "n": 48 + } + }, + "inputs": { + "sent": 48, + "seen": 48 + }, + "timeline": [ + { + "t": 0, + "fps": 60, + "mbps": 0.47, + "content_p50": 10.3, + "content_p95": 14.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 1, + "fps": 60, + "mbps": 0.45, + "content_p50": 9.8, + "content_p95": 11.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 2, + "fps": 60, + "mbps": 0.45, + "content_p50": 10.1, + "content_p95": 12.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 3, + "fps": 60, + "mbps": 0.46, + "content_p50": 10.0, + "content_p95": 12.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 4, + "fps": 60, + "mbps": 0.45, + "content_p50": 10.6, + "content_p95": 13.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 5, + "fps": 60, + "mbps": 0.45, + "content_p50": 9.9, + "content_p95": 11.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 6, + "fps": 60, + "mbps": 0.56, + "content_p50": 10.1, + "content_p95": 13.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 7, + "fps": 60, + "mbps": 0.44, + "content_p50": 9.8, + "content_p95": 13.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 8, + "fps": 60, + "mbps": 0.45, + "content_p50": 12.8, + "content_p95": 16.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 9, + "fps": 60, + "mbps": 0.45, + "content_p50": 12.4, + "content_p95": 14.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 10, + "fps": 60, + "mbps": 0.45, + "content_p50": 12.8, + "content_p95": 14.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 11, + "fps": 60, + "mbps": 0.45, + "content_p50": 12.7, + "content_p95": 15.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 12, + "fps": 60, + "mbps": 0.45, + "content_p50": 12.9, + "content_p95": 17.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 13, + "fps": 60, + "mbps": 0.44, + "content_p50": 13.3, + "content_p95": 16.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 14, + "fps": 60, + "mbps": 0.44, + "content_p50": 13.4, + "content_p95": 107.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 15, + "fps": 60, + "mbps": 0.44, + "content_p50": 13.4, + "content_p95": 18.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 16, + "fps": 60, + "mbps": 0.55, + "content_p50": 9.5, + "content_p95": 13.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 17, + "fps": 60, + "mbps": 0.43, + "content_p50": 9.3, + "content_p95": 12.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 18, + "fps": 60, + "mbps": 0.44, + "content_p50": 9.8, + "content_p95": 13.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 19, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.5, + "content_p95": 13.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 20, + "fps": 60, + "mbps": 0.44, + "content_p50": 9.7, + "content_p95": 13.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 21, + "fps": 60, + "mbps": 0.44, + "content_p50": 9.3, + "content_p95": 13.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 22, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.7, + "content_p95": 12.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 23, + "fps": 60, + "mbps": 0.45, + "content_p50": 9.5, + "content_p95": 16.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 24, + "fps": 19, + "mbps": 0.13, + "content_p50": 10.2, + "content_p95": 13.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + } + ], + "adapt": [ + { + "at": 8.0, + "to_mbit": 3.0, + "p95_bound": 40.0, + "worst_p95": 107.3, + "settled_s": 0.0 + } + ], + "content_p50": 10.7, + "content_p95": 15.6, + "input_p50": 29.6, + "input_p95": 50.1, + "grades": { + "input_replies": "local", + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "local", + "fps": "local", + "late_pct": "acceptable", + "stall_max": "acceptable", + "adapt@8s": "local" + }, + "controller": { + "adapt": false, + "baseRtt": 5.916, + "ceiling": 5529600, + "fps": 60, + "inFlight": 1, + "scale": 1, + "slack": 40, + "target": 5529600, + "tier": 0 + }, + "events": [], + "source_fps": 65.0, + "cpu": { + "mac_agent_pct": 14.7, + "frame_viewer_pct": 54.2, + "frame_tailscaled_pct": 6.0, + "frame_sshd_pct": 0.8, + "frame_gamescope_pct": 14.5, + "frame_total_pct": 39.3 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 70 Hz", + "show_s": 1.37, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + } + ] + ], + "on": [ + [ + { + "scenario": "scroll", + "frames_sent": 1012, + "frames_drawn": 1012, + "frames_shown": 975, + "duration_s": 24.2, + "stages_ms": { + "capture": { + "p50": -4.2, + "p95": 3.0, + "p99": 4.5, + "n": 1012 + }, + "queue": { + "p50": 0.0, + "p95": 15.9, + "p99": 17.4, + "n": 1012 + }, + "encode": { + "p50": 6.4, + "p95": 7.5, + "p99": 8.5, + "n": 1012 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.4, + "n": 1012 + }, + "network": { + "p50": 9.9, + "p95": 51.0, + "p99": 167.2, + "n": 1012 + }, + "decode": { + "p50": 5.2, + "p95": 8.7, + "p99": 13.2, + "n": 1012 + }, + "draw": { + "p50": 0.6, + "p95": 1.4, + "p99": 1.7, + "n": 1012 + }, + "present": { + "p50": 0.6, + "p95": 11.6, + "p99": 14.3, + "n": 975 + }, + "content": { + "p50": 26.6, + "p95": 69.2, + "p99": 189.9, + "n": 1012 + }, + "content_shown": { + "p50": 31.6, + "p95": 71.1, + "p99": 190.4, + "n": 975 + } + }, + "fps": 41.8, + "fps_shown": 40.3, + "late_pct": 31.82, + "stall_max": 245.3, + "stalls_over_100ms": 5, + "mbps": 6.13, + "keyframes": 3, + "size": "1920x1290", + "captured": 1507, + "viewer_never_drawn": 0, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 56, + "mbps": 9.54, + "content_p50": 21.9, + "content_p95": 42.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 1, + "fps": 58, + "mbps": 9.38, + "content_p50": 21.4, + "content_p95": 26.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 2, + "fps": 57, + "mbps": 9.41, + "content_p50": 22.3, + "content_p95": 27.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 3, + "fps": 58, + "mbps": 9.67, + "content_p50": 23.0, + "content_p95": 25.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 4, + "fps": 57, + "mbps": 8.99, + "content_p50": 22.9, + "content_p95": 28.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 5, + "fps": 58, + "mbps": 9.25, + "content_p50": 23.3, + "content_p95": 32.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 6, + "fps": 57, + "mbps": 11.74, + "content_p50": 22.2, + "content_p95": 46.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 7, + "fps": 58, + "mbps": 8.89, + "content_p50": 21.1, + "content_p95": 25.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 8, + "fps": 25, + "mbps": 3.02, + "content_p50": 75.9, + "content_p95": 218.9, + "bitrate": 3715200, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 9, + "fps": 50, + "mbps": 3.15, + "content_p50": 56.5, + "content_p95": 84.0, + "bitrate": 2112376, + "tier": 3, + "w": 1440, + "net": 3.0 + }, + { + "t": 10, + "fps": 24, + "mbps": 0.94, + "content_p50": 37.9, + "content_p95": 54.8, + "bitrate": 1689900, + "tier": 3, + "w": 1440, + "net": 3.0 + }, + { + "t": 11, + "fps": 30, + "mbps": 1.93, + "content_p50": 48.0, + "content_p95": 57.0, + "bitrate": 2414756, + "tier": 3, + "w": 1440, + "net": 3.0 + }, + { + "t": 12, + "fps": 30, + "mbps": 2.74, + "content_p50": 58.5, + "content_p95": 92.4, + "bitrate": 2611641, + "tier": 3, + "w": 1440, + "net": 3.0 + }, + { + "t": 13, + "fps": 30, + "mbps": 2.12, + "content_p50": 45.3, + "content_p95": 79.0, + "bitrate": 2089312, + "tier": 3, + "w": 1440, + "net": 3.0 + }, + { + "t": 14, + "fps": 30, + "mbps": 2.24, + "content_p50": 52.0, + "content_p95": 71.4, + "bitrate": 2633067, + "tier": 3, + "w": 1440, + "net": 3.0 + }, + { + "t": 15, + "fps": 30, + "mbps": 2.47, + "content_p50": 53.7, + "content_p95": 93.6, + "bitrate": 2357098, + "tier": 3, + "w": 1440, + "net": 3.0 + }, + { + "t": 16, + "fps": 30, + "mbps": 2.61, + "content_p50": 24.7, + "content_p95": 39.8, + "bitrate": 2957087, + "tier": 3, + "w": 1440, + "net": 50.0 + }, + { + "t": 17, + "fps": 30, + "mbps": 3.74, + "content_p50": 29.5, + "content_p95": 38.0, + "bitrate": 4561519, + "tier": 3, + "w": 1440, + "net": 50.0 + }, + { + "t": 18, + "fps": 30, + "mbps": 5.32, + "content_p50": 29.2, + "content_p95": 41.6, + "bitrate": 6236880, + "tier": 3, + "w": 1440, + "net": 50.0 + }, + { + "t": 19, + "fps": 30, + "mbps": 6.62, + "content_p50": 35.0, + "content_p95": 68.5, + "bitrate": 8466786, + "tier": 2, + "w": 1920, + "net": 50.0 + }, + { + "t": 20, + "fps": 30, + "mbps": 7.48, + "content_p50": 36.1, + "content_p95": 41.1, + "bitrate": 12628270, + "tier": 2, + "w": 1920, + "net": 50.0 + }, + { + "t": 21, + "fps": 42, + "mbps": 7.71, + "content_p50": 31.7, + "content_p95": 39.0, + "bitrate": 14860800, + "tier": 1, + "w": 1920, + "net": 50.0 + }, + { + "t": 22, + "fps": 46, + "mbps": 8.79, + "content_p50": 33.6, + "content_p95": 40.6, + "bitrate": 14860800, + "tier": 1, + "w": 1920, + "net": 50.0 + }, + { + "t": 23, + "fps": 53, + "mbps": 8.78, + "content_p50": 28.4, + "content_p95": 42.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 24, + "fps": 13, + "mbps": 1.89, + "content_p50": 24.7, + "content_p95": 28.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + } + ], + "adapt": [ + { + "at": 8.0, + "to_mbit": 3.0, + "p95_bound": 45.2, + "worst_p95": 218.9, + "settled_s": 8.0 + } + ], + "content_p50": 26.6, + "content_p95": 69.2, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "acceptable", + "content_p95": "acceptable", + "fps": "bad", + "late_pct": "bad", + "stall_max": "acceptable", + "adapt@8s": "bad" + }, + "controller": { + "adapt": true, + "baseRtt": 6.333, + "ceiling": 14860800, + "fps": 60, + "inFlight": 1, + "scale": 1, + "slack": 51.355, + "target": 14860800, + "tier": 0 + }, + "events": [ + { + "t": 8.31, + "e": "down to 7430 kbit/s: queue 100 ms, held 6, oldest 197 ms, base 10 ms, sent 5528 got 5268 wants 8906" + }, + { + "t": 8.71, + "e": "down to 3715 kbit/s: queue 209 ms, held 4, oldest 115 ms, base 10 ms, sent 2861 got 2421 wants 10373" + }, + { + "t": 9.41, + "e": "down to 2640 kbit/s: queue 48 ms, held 0, oldest 59 ms, base 10 ms, sent 3222 got 2933 wants 3166" + }, + { + "t": 9.71, + "e": "down to 2112 kbit/s: queue 48 ms, held 0, oldest 31 ms, base 10 ms, sent 2907 got 3032 wants 2906" + }, + { + "t": 9.81, + "e": "tier 3" + }, + { + "t": 10.21, + "e": "down to 1689 kbit/s: queue 186 ms, held 4, oldest 0 ms, base 10 ms, sent 2091 got 2288 wants 3137" + }, + { + "t": 12.91, + "e": "down to 2611 kbit/s: queue 44 ms, held 0, oldest 27 ms, base 10 ms, sent 3243 got 2901 wants 3243" + }, + { + "t": 13.31, + "e": "down to 2089 kbit/s: queue 41 ms, held 0, oldest 60 ms, base 10 ms, sent 2775 got 2764 wants 2775" + }, + { + "t": 15.31, + "e": "down to 2357 kbit/s: queue 44 ms, held 0, oldest 28 ms, base 10 ms, sent 2539 got 2729 wants 2539" + }, + { + "t": 19.11, + "e": "tier 2" + }, + { + "t": 21.31, + "e": "tier 1" + }, + { + "t": 23.51, + "e": "tier 0" + } + ], + "source_fps": 62.3, + "cpu": { + "mac_agent_pct": 7.3, + "frame_viewer_pct": 57.5, + "frame_tailscaled_pct": 7.4, + "frame_sshd_pct": 0.8, + "frame_gamescope_pct": 10.6, + "frame_total_pct": 47.8 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 71 Hz", + "show_s": 1.27, + "panel": "valve.steam.desktopgame.2001853011", + "src": "separate:6269" + }, + { + "scenario": "test", + "frames_sent": 1457, + "frames_drawn": 1457, + "frames_shown": 578, + "duration_s": 24.3, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 1457 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 1457 + }, + "encode": { + "p50": 4.2, + "p95": 4.9, + "p99": 5.2, + "n": 1457 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 1457 + }, + "network": { + "p50": 5.0, + "p95": 10.1, + "p99": 13.0, + "n": 1457 + }, + "decode": { + "p50": 1.0, + "p95": 2.2, + "p99": 3.6, + "n": 1457 + }, + "draw": { + "p50": 0.3, + "p95": 0.7, + "p99": 1.1, + "n": 1457 + }, + "present": { + "p50": 5.6, + "p95": 16.1, + "p99": 18.0, + "n": 578 + }, + "content": { + "p50": 11.1, + "p95": 16.2, + "p99": 19.2, + "n": 1457 + }, + "content_shown": { + "p50": 18.9, + "p95": 27.0, + "p99": 31.0, + "n": 578 + } + }, + "fps": 60.0, + "fps_shown": 23.8, + "late_pct": 0.82, + "stall_max": 32.9, + "stalls_over_100ms": 0, + "mbps": 0.47, + "keyframes": 2, + "size": "1280x720", + "captured": 1578, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 30.0, + "p95": 47.7, + "p99": 76.0, + "n": 48 + }, + "input_shown_ms": { + "p50": 33.7, + "p95": 47.5, + "p99": 81.8, + "n": 17 + }, + "input_parts_ms": { + "uplink": { + "p50": 10.2, + "p95": 27.4, + "p99": 49.5, + "n": 48 + }, + "mac": { + "p50": 5.3, + "p95": 15.1, + "p99": 16.3, + "n": 48 + }, + "back": { + "p50": 12.5, + "p95": 18.7, + "p99": 19.2, + "n": 48 + } + }, + "inputs": { + "sent": 48, + "seen": 48 + }, + "timeline": [ + { + "t": 0, + "fps": 60, + "mbps": 0.49, + "content_p50": 10.0, + "content_p95": 11.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 1, + "fps": 60, + "mbps": 0.46, + "content_p50": 10.5, + "content_p95": 15.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 2, + "fps": 60, + "mbps": 0.48, + "content_p50": 10.1, + "content_p95": 12.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 3, + "fps": 60, + "mbps": 0.46, + "content_p50": 10.6, + "content_p95": 13.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 4, + "fps": 60, + "mbps": 0.47, + "content_p50": 10.7, + "content_p95": 13.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 5, + "fps": 60, + "mbps": 0.46, + "content_p50": 10.8, + "content_p95": 12.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 6, + "fps": 60, + "mbps": 0.56, + "content_p50": 10.8, + "content_p95": 13.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 7, + "fps": 60, + "mbps": 0.46, + "content_p50": 10.9, + "content_p95": 12.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 8, + "fps": 60, + "mbps": 0.45, + "content_p50": 13.4, + "content_p95": 15.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 9, + "fps": 60, + "mbps": 0.46, + "content_p50": 13.8, + "content_p95": 16.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 10, + "fps": 60, + "mbps": 0.45, + "content_p50": 14.1, + "content_p95": 16.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 11, + "fps": 60, + "mbps": 0.45, + "content_p50": 13.8, + "content_p95": 22.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 12, + "fps": 60, + "mbps": 0.45, + "content_p50": 14.3, + "content_p95": 18.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 13, + "fps": 60, + "mbps": 0.45, + "content_p50": 14.2, + "content_p95": 18.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 14, + "fps": 60, + "mbps": 0.45, + "content_p50": 14.1, + "content_p95": 18.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 15, + "fps": 60, + "mbps": 0.47, + "content_p50": 14.2, + "content_p95": 17.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 16, + "fps": 60, + "mbps": 0.57, + "content_p50": 9.9, + "content_p95": 18.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 17, + "fps": 60, + "mbps": 0.45, + "content_p50": 9.9, + "content_p95": 11.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 18, + "fps": 60, + "mbps": 0.45, + "content_p50": 10.0, + "content_p95": 12.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 19, + "fps": 60, + "mbps": 0.45, + "content_p50": 10.0, + "content_p95": 11.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 20, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.7, + "content_p95": 11.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 21, + "fps": 60, + "mbps": 0.45, + "content_p50": 9.8, + "content_p95": 11.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 22, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.9, + "content_p95": 12.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 23, + "fps": 60, + "mbps": 0.46, + "content_p50": 10.9, + "content_p95": 12.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 24, + "fps": 17, + "mbps": 0.11, + "content_p50": 10.9, + "content_p95": 12.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + } + ], + "adapt": [ + { + "at": 8.0, + "to_mbit": 3.0, + "p95_bound": 40.0, + "worst_p95": 22.7, + "settled_s": 0.0 + } + ], + "content_p50": 11.1, + "content_p95": 16.2, + "input_p50": 30.0, + "input_p95": 47.7, + "grades": { + "input_replies": "local", + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "local", + "fps": "local", + "late_pct": "local", + "stall_max": "local", + "adapt@8s": "local" + }, + "controller": { + "adapt": true, + "baseRtt": 6.083, + "ceiling": 5529600, + "fps": 60, + "inFlight": 1, + "scale": 1, + "slack": 47.104, + "target": 5529600, + "tier": 0 + }, + "events": [], + "source_fps": 64.9, + "cpu": { + "mac_agent_pct": 15.6, + "frame_viewer_pct": 49.6, + "frame_tailscaled_pct": 5.8, + "frame_sshd_pct": 0.7, + "frame_gamescope_pct": 11.3, + "frame_total_pct": 37.4 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 70 Hz", + "show_s": 1.29, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + } + ], + [ + { + "scenario": "scroll", + "frames_sent": 1044, + "frames_drawn": 1044, + "frames_shown": 786, + "duration_s": 24.3, + "stages_ms": { + "capture": { + "p50": -4.6, + "p95": 0.4, + "p99": 1.8, + "n": 1044 + }, + "queue": { + "p50": 0.0, + "p95": 14.9, + "p99": 17.0, + "n": 1044 + }, + "encode": { + "p50": 6.5, + "p95": 8.8, + "p99": 17.0, + "n": 1044 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.5, + "n": 1044 + }, + "network": { + "p50": 10.1, + "p95": 52.0, + "p99": 225.2, + "n": 1044 + }, + "decode": { + "p50": 5.4, + "p95": 9.5, + "p99": 17.6, + "n": 1044 + }, + "draw": { + "p50": 0.6, + "p95": 1.3, + "p99": 1.7, + "n": 1044 + }, + "present": { + "p50": 0.7, + "p95": 14.4, + "p99": 22.3, + "n": 786 + }, + "content": { + "p50": 25.5, + "p95": 74.6, + "p99": 236.2, + "n": 1044 + }, + "content_shown": { + "p50": 33.0, + "p95": 74.9, + "p99": 234.6, + "n": 786 + } + }, + "fps": 43.0, + "fps_shown": 32.4, + "late_pct": 27.59, + "stall_max": 430.7, + "stalls_over_100ms": 7, + "mbps": 6.22, + "keyframes": 3, + "size": "1920x1290", + "captured": 1499, + "viewer_never_drawn": 0, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 54, + "mbps": 9.98, + "content_p50": 23.9, + "content_p95": 33.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 1, + "fps": 57, + "mbps": 9.24, + "content_p50": 23.4, + "content_p95": 32.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 2, + "fps": 57, + "mbps": 9.09, + "content_p50": 23.0, + "content_p95": 32.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 3, + "fps": 54, + "mbps": 9.77, + "content_p50": 24.1, + "content_p95": 36.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 4, + "fps": 58, + "mbps": 9.09, + "content_p50": 23.2, + "content_p95": 27.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 5, + "fps": 52, + "mbps": 8.72, + "content_p50": 24.5, + "content_p95": 63.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 6, + "fps": 58, + "mbps": 11.69, + "content_p50": 23.1, + "content_p95": 50.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 7, + "fps": 57, + "mbps": 8.99, + "content_p50": 22.4, + "content_p95": 32.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 8, + "fps": 20, + "mbps": 3.23, + "content_p50": 205.5, + "content_p95": 270.8, + "bitrate": 7430400, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 9, + "fps": 34, + "mbps": 1.66, + "content_p50": 18.4, + "content_p95": 428.1, + "bitrate": 1765116, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 10, + "fps": 57, + "mbps": 1.27, + "content_p50": 23.3, + "content_p95": 101.0, + "bitrate": 2240789, + "tier": 0, + "w": 1920, + "net": 3.0 + }, + { + "t": 11, + "fps": 35, + "mbps": 1.73, + "content_p50": 31.8, + "content_p95": 148.1, + "bitrate": 1405577, + "tier": 3, + "w": 1440, + "net": 3.0 + }, + { + "t": 12, + "fps": 30, + "mbps": 1.26, + "content_p50": 38.8, + "content_p95": 64.3, + "bitrate": 1596134, + "tier": 3, + "w": 1440, + "net": 3.0 + }, + { + "t": 13, + "fps": 31, + "mbps": 1.84, + "content_p50": 43.3, + "content_p95": 59.6, + "bitrate": 2289953, + "tier": 3, + "w": 1440, + "net": 3.0 + }, + { + "t": 14, + "fps": 30, + "mbps": 2.61, + "content_p50": 59.7, + "content_p95": 85.4, + "bitrate": 2373537, + "tier": 3, + "w": 1440, + "net": 3.0 + }, + { + "t": 15, + "fps": 30, + "mbps": 2.39, + "content_p50": 53.7, + "content_p95": 87.6, + "bitrate": 2660890, + "tier": 3, + "w": 1440, + "net": 3.0 + }, + { + "t": 16, + "fps": 30, + "mbps": 3.04, + "content_p50": 25.4, + "content_p95": 37.6, + "bitrate": 3707143, + "tier": 3, + "w": 1440, + "net": 50.0 + }, + { + "t": 17, + "fps": 30, + "mbps": 4.45, + "content_p50": 31.8, + "content_p95": 41.7, + "bitrate": 5099706, + "tier": 3, + "w": 1440, + "net": 50.0 + }, + { + "t": 18, + "fps": 28, + "mbps": 5.76, + "content_p50": 34.2, + "content_p95": 51.9, + "bitrate": 7698527, + "tier": 2, + "w": 1920, + "net": 50.0 + }, + { + "t": 19, + "fps": 30, + "mbps": 7.38, + "content_p50": 37.7, + "content_p95": 45.2, + "bitrate": 10412237, + "tier": 2, + "w": 1920, + "net": 50.0 + }, + { + "t": 20, + "fps": 41, + "mbps": 8.63, + "content_p50": 34.5, + "content_p95": 64.1, + "bitrate": 14024186, + "tier": 1, + "w": 1920, + "net": 50.0 + }, + { + "t": 21, + "fps": 46, + "mbps": 7.87, + "content_p50": 35.1, + "content_p95": 45.4, + "bitrate": 14860800, + "tier": 1, + "w": 1920, + "net": 50.0 + }, + { + "t": 22, + "fps": 53, + "mbps": 9.21, + "content_p50": 24.4, + "content_p95": 37.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 23, + "fps": 57, + "mbps": 9.68, + "content_p50": 23.2, + "content_p95": 31.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + }, + { + "t": 24, + "fps": 15, + "mbps": 2.34, + "content_p50": 25.1, + "content_p95": 34.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": 50.0 + } + ], + "adapt": [ + { + "at": 8.0, + "to_mbit": 3.0, + "p95_bound": 62.6, + "worst_p95": 428.1, + "settled_s": 8.0 + } + ], + "content_p50": 25.5, + "content_p95": 74.6, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "acceptable", + "content_p95": "acceptable", + "fps": "bad", + "late_pct": "bad", + "stall_max": "bad", + "adapt@8s": "bad" + }, + "controller": { + "adapt": true, + "baseRtt": 7.875, + "ceiling": 14860800, + "fps": 60, + "inFlight": 4, + "scale": 1, + "slack": 48.916, + "target": 14860800, + "tier": 0 + }, + "events": [ + { + "t": 8.32, + "e": "down to 7430 kbit/s: queue 98 ms, held 6, oldest 191 ms, base 9 ms, sent 6068 got 6112 wants 9608" + }, + { + "t": 8.92, + "e": "down to 3715 kbit/s: queue 197 ms, held 5, oldest 239 ms, base 9 ms, sent 2451 got 2882 wants 8733" + }, + { + "t": 9.22, + "e": "down to 2206 kbit/s: queue 237 ms, held 4, oldest 161 ms, base 9 ms, sent 3261 got 2451 wants 10510" + }, + { + "t": 9.52, + "e": "down to 1765 kbit/s: queue 392 ms, held 6, oldest 456 ms, base 9 ms, sent 2553 got 2705 wants 10396" + }, + { + "t": 11.22, + "e": "down to 1756 kbit/s: queue 48 ms, held 0, oldest 3 ms, base 8 ms, sent 1996 got 1952 wants 2107" + }, + { + "t": 11.52, + "e": "tier 3" + }, + { + "t": 11.92, + "e": "down to 1405 kbit/s: queue 158 ms, held 2, oldest 0 ms, base 8 ms, sent 1907 got 1907 wants 2384" + }, + { + "t": 14.92, + "e": "down to 2373 kbit/s: queue 47 ms, held 0, oldest 58 ms, base 8 ms, sent 2973 got 2637 wants 2972" + }, + { + "t": 18.22, + "e": "tier 2" + }, + { + "t": 20.32, + "e": "tier 1" + }, + { + "t": 22.42, + "e": "tier 0" + } + ], + "source_fps": 61.7, + "cpu": { + "mac_agent_pct": 7.1, + "frame_viewer_pct": 59.9, + "frame_tailscaled_pct": 7.4, + "frame_sshd_pct": 0.7, + "frame_gamescope_pct": 9.8, + "frame_total_pct": 37.2 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 70 Hz", + "show_s": 1.32, + "panel": "valve.steam.desktopgame.2001220345", + "src": "separate:6403" + }, + { + "scenario": "test", + "frames_sent": 1455, + "frames_drawn": 1455, + "frames_shown": 569, + "duration_s": 24.3, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 1455 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 1455 + }, + "encode": { + "p50": 4.0, + "p95": 4.7, + "p99": 5.0, + "n": 1455 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 1455 + }, + "network": { + "p50": 5.7, + "p95": 9.8, + "p99": 13.3, + "n": 1455 + }, + "decode": { + "p50": 1.1, + "p95": 2.6, + "p99": 3.9, + "n": 1455 + }, + "draw": { + "p50": 0.4, + "p95": 0.8, + "p99": 1.2, + "n": 1455 + }, + "present": { + "p50": 5.7, + "p95": 17.2, + "p99": 19.5, + "n": 569 + }, + "content": { + "p50": 11.5, + "p95": 15.9, + "p99": 19.4, + "n": 1455 + }, + "content_shown": { + "p50": 17.6, + "p95": 29.4, + "p99": 31.2, + "n": 569 + } + }, + "fps": 59.8, + "fps_shown": 23.4, + "late_pct": 1.1, + "stall_max": 115.1, + "stalls_over_100ms": 1, + "mbps": 0.47, + "keyframes": 2, + "size": "1280x720", + "captured": 1580, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 29.1, + "p95": 50.1, + "p99": 55.5, + "n": 48 + }, + "input_shown_ms": { + "p50": 44.2, + "p95": 51.9, + "p99": 55.9, + "n": 15 + }, + "input_parts_ms": { + "uplink": { + "p50": 10.1, + "p95": 23.2, + "p99": 27.8, + "n": 48 + }, + "mac": { + "p50": 6.0, + "p95": 17.1, + "p99": 20.1, + "n": 48 + }, + "back": { + "p50": 11.6, + "p95": 17.2, + "p99": 18.3, + "n": 48 + } + }, + "inputs": { + "sent": 48, + "seen": 48 + }, + "timeline": [ + { + "t": 0, + "fps": 60, + "mbps": 0.48, + "content_p50": 10.9, + "content_p95": 13.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 1, + "fps": 60, + "mbps": 0.47, + "content_p50": 11.0, + "content_p95": 13.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 2, + "fps": 60, + "mbps": 0.47, + "content_p50": 11.0, + "content_p95": 14.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 3, + "fps": 60, + "mbps": 0.48, + "content_p50": 10.9, + "content_p95": 15.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 4, + "fps": 61, + "mbps": 0.48, + "content_p50": 11.4, + "content_p95": 18.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 5, + "fps": 59, + "mbps": 0.47, + "content_p50": 10.8, + "content_p95": 13.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 6, + "fps": 61, + "mbps": 0.58, + "content_p50": 11.3, + "content_p95": 25.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 7, + "fps": 59, + "mbps": 0.45, + "content_p50": 11.0, + "content_p95": 13.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 8, + "fps": 61, + "mbps": 0.48, + "content_p50": 13.9, + "content_p95": 17.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 9, + "fps": 59, + "mbps": 0.45, + "content_p50": 13.8, + "content_p95": 18.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 10, + "fps": 61, + "mbps": 0.45, + "content_p50": 13.9, + "content_p95": 16.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 11, + "fps": 59, + "mbps": 0.45, + "content_p50": 11.8, + "content_p95": 15.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 12, + "fps": 61, + "mbps": 0.46, + "content_p50": 13.4, + "content_p95": 16.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 13, + "fps": 59, + "mbps": 0.45, + "content_p50": 13.4, + "content_p95": 16.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 14, + "fps": 57, + "mbps": 0.43, + "content_p50": 13.2, + "content_p95": 18.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 15, + "fps": 59, + "mbps": 0.45, + "content_p50": 13.4, + "content_p95": 16.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 3.0 + }, + { + "t": 16, + "fps": 61, + "mbps": 0.56, + "content_p50": 10.5, + "content_p95": 15.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 17, + "fps": 59, + "mbps": 0.43, + "content_p50": 10.8, + "content_p95": 14.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 18, + "fps": 61, + "mbps": 0.46, + "content_p50": 10.7, + "content_p95": 13.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 19, + "fps": 59, + "mbps": 0.45, + "content_p50": 10.7, + "content_p95": 13.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 20, + "fps": 61, + "mbps": 0.46, + "content_p50": 10.5, + "content_p95": 15.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 21, + "fps": 59, + "mbps": 0.44, + "content_p50": 10.7, + "content_p95": 13.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 22, + "fps": 60, + "mbps": 0.45, + "content_p50": 10.8, + "content_p95": 12.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 23, + "fps": 60, + "mbps": 0.45, + "content_p50": 10.6, + "content_p95": 12.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + }, + { + "t": 24, + "fps": 19, + "mbps": 0.13, + "content_p50": 10.6, + "content_p95": 16.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": 50.0 + } + ], + "adapt": [ + { + "at": 8.0, + "to_mbit": 3.0, + "p95_bound": 40.0, + "worst_p95": 18.3, + "settled_s": 0.0 + } + ], + "content_p50": 11.5, + "content_p95": 15.9, + "input_p50": 29.1, + "input_p95": 50.1, + "grades": { + "input_replies": "local", + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "local", + "fps": "local", + "late_pct": "acceptable", + "stall_max": "acceptable", + "adapt@8s": "local" + }, + "controller": { + "adapt": true, + "baseRtt": 6, + "ceiling": 5529600, + "fps": 60, + "inFlight": 2, + "scale": 1, + "slack": 49.792, + "target": 5529600, + "tier": 0 + }, + "events": [], + "source_fps": 65.0, + "cpu": { + "mac_agent_pct": 14.2, + "frame_viewer_pct": 52.2, + "frame_tailscaled_pct": 5.8, + "frame_sshd_pct": 0.8, + "frame_gamescope_pct": 12.2, + "frame_total_pct": 38.8 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 72 Hz", + "show_s": 1.38, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + } + ] + ] + }, + "table": { + "scroll": { + "content_p50": { + "off": [ + 27.5, + 23.8 + ], + "on": [ + 26.6, + 25.5 + ] + }, + "content_p95": { + "off": [ + 4986.4, + 4355.1 + ], + "on": [ + 69.2, + 74.6 + ] + }, + "input_p50": { + "off": [], + "on": [] + }, + "input_p95": { + "off": [], + "on": [] + }, + "fps": { + "off": [ + 45.8, + 44.5 + ], + "on": [ + 41.8, + 43.0 + ] + }, + "late_pct": { + "off": [ + 19.19, + 17.67 + ], + "on": [ + 31.82, + 27.59 + ] + }, + "stall_max": { + "off": [ + 232.1, + 398.2 + ], + "on": [ + 245.3, + 430.7 + ] + }, + "mbps": { + "off": [ + 8.62, + 8.39 + ], + "on": [ + 6.13, + 6.22 + ] + } + }, + "test": { + "content_p50": { + "off": [ + 12.8, + 10.7 + ], + "on": [ + 11.1, + 11.5 + ] + }, + "content_p95": { + "off": [ + 116.8, + 15.6 + ], + "on": [ + 16.2, + 15.9 + ] + }, + "input_p50": { + "off": [ + 34.4, + 29.6 + ], + "on": [ + 30.0, + 29.1 + ] + }, + "input_p95": { + "off": [ + 86.0, + 50.1 + ], + "on": [ + 47.7, + 50.1 + ] + }, + "fps": { + "off": [ + 60.0, + 60.0 + ], + "on": [ + 60.0, + 59.8 + ] + }, + "late_pct": { + "off": [ + 3.69, + 1.92 + ], + "on": [ + 0.82, + 1.1 + ] + }, + "stall_max": { + "off": [ + 185.4, + 157.3 + ], + "on": [ + 32.9, + 115.1 + ] + }, + "mbps": { + "off": [ + 0.49, + 0.46 + ], + "on": [ + 0.47, + 0.47 + ] + } + } + } +} \ No newline at end of file diff --git a/bench/results/2026-09-28-a3c6e5c-dirty-ab-keepwarm.json b/bench/results/2026-09-28-a3c6e5c-dirty-ab-keepwarm.json new file mode 100644 index 0000000..21c9e37 --- /dev/null +++ b/bench/results/2026-09-28-a3c6e5c-dirty-ab-keepwarm.json @@ -0,0 +1,2172 @@ +{ + "label": "ab-keepwarm", + "date": "2026-09-28T20:28:57", + "commit": "a3c6e5c+dirty", + "config": { + "quality": "balanced", + "mode": "separate", + "net": "none", + "delay_ms": 0, + "buffer_ms": 250, + "host": "frame", + "ssh_opts": [], + "encoder": "", + "duration_s": 20.0, + "browser_flags": [] + }, + "frame_build": "20260925.6191901", + "mac": "26.5.2", + "headset": "8:23.135290 [Info] - 0 - leaving standby", + "scenarios": [], + "arms": [ + "cold:FRAME_MAC_VIEW_WARM=0", + "warm:FRAME_MAC_VIEW_WARM=40" + ], + "runs": { + "cold": [ + [ + { + "scenario": "type", + "frames_sent": 145, + "frames_drawn": 145, + "frames_shown": 142, + "duration_s": 21.1, + "stages_ms": { + "capture": { + "p50": -2.5, + "p95": 2.7, + "p99": 19.4, + "n": 145 + }, + "queue": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.3, + "n": 145 + }, + "encode": { + "p50": 6.5, + "p95": 9.6, + "p99": 18.2, + "n": 145 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 1.0, + "n": 145 + }, + "network": { + "p50": 3.7, + "p95": 7.5, + "p99": 22.8, + "n": 145 + }, + "decode": { + "p50": 2.1, + "p95": 4.2, + "p99": 5.1, + "n": 145 + }, + "draw": { + "p50": 0.6, + "p95": 1.3, + "p99": 2.0, + "n": 145 + }, + "present": { + "p50": 0.5, + "p95": 9.3, + "p99": 12.6, + "n": 142 + }, + "content": { + "p50": 13.9, + "p95": 25.6, + "p99": 38.0, + "n": 145 + }, + "content_shown": { + "p50": 14.9, + "p95": 26.0, + "p99": 36.9, + "n": 142 + } + }, + "fps": 6.9, + "fps_shown": 6.7, + "late_pct": 51.37, + "stall_max": 1007.5, + "stalls_over_100ms": 66, + "mbps": 0.03, + "keyframes": 0, + "size": "1920x1290", + "captured": 145, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 44.5, + "p95": 68.2, + "p99": 72.7, + "n": 66 + }, + "input_shown_ms": { + "p50": 44.9, + "p95": 68.9, + "p99": 73.1, + "n": 66 + }, + "input_parts_ms": { + "uplink": { + "p50": 4.2, + "p95": 17.4, + "p99": 41.0, + "n": 66 + }, + "mac": { + "p50": 22.8, + "p95": 36.8, + "p99": 42.9, + "n": 66 + }, + "back": { + "p50": 14.5, + "p95": 20.7, + "p99": 25.4, + "n": 66 + } + }, + "inputs": { + "asked": 65, + "sent": 66, + "seen": 66 + }, + "timeline": [ + { + "t": 0, + "fps": 37, + "mbps": 0.26, + "content_p50": 14.9, + "content_p95": 36.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 42, + "mbps": 0.15, + "content_p50": 12.7, + "content_p95": 15.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 6, + "mbps": 0.02, + "content_p50": 15.1, + "content_p95": 20.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 5, + "mbps": 0.02, + "content_p50": 13.6, + "content_p95": 14.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 5, + "mbps": 0.02, + "content_p50": 16.5, + "content_p95": 18.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 5, + "mbps": 0.02, + "content_p50": 14.0, + "content_p95": 20.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 5, + "mbps": 0.02, + "content_p50": 14.5, + "content_p95": 15.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 5, + "mbps": 0.02, + "content_p50": 15.5, + "content_p95": 18.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 5, + "mbps": 0.02, + "content_p50": 13.4, + "content_p95": 16.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 5, + "mbps": 0.02, + "content_p50": 14.3, + "content_p95": 16.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 5, + "mbps": 0.02, + "content_p50": 14.9, + "content_p95": 16.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 5, + "mbps": 0.02, + "content_p50": 14.0, + "content_p95": 18.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 2, + "mbps": 0.01, + "content_p50": 13.2, + "content_p95": 17.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 3, + "mbps": 0.01, + "content_p50": 12.9, + "content_p95": 13.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 3, + "mbps": 0.01, + "content_p50": 14.2, + "content_p95": 25.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 1, + "mbps": 0.0, + "content_p50": 13.9, + "content_p95": 13.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 16, + "fps": 1, + "mbps": 0.0, + "content_p50": 15.8, + "content_p95": 15.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 17, + "fps": 2, + "mbps": 0.01, + "content_p50": 13.6, + "content_p95": 25.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 18, + "fps": 1, + "mbps": 0.0, + "content_p50": 15.7, + "content_p95": 15.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 19, + "fps": 1, + "mbps": 0.0, + "content_p50": 17.6, + "content_p95": 17.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 20, + "fps": 1, + "mbps": 0.0, + "content_p50": 16.0, + "content_p95": 16.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 13.9, + "content_p95": 25.6, + "input_p50": 44.5, + "input_p95": 68.2, + "grades": { + "input_replies": "local", + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "local" + }, + "controller": { + "adapt": true, + "baseRtt": 5.75, + "ceiling": 14860800, + "fps": 60, + "inFlight": 0, + "scale": 1, + "slack": 41.666, + "target": 14860800, + "tier": 0 + }, + "events": [], + "source_fps": 6.9, + "cpu": { + "mac_agent_pct": 1.7, + "frame_viewer_pct": 10.2, + "frame_tailscaled_pct": 1.6, + "frame_sshd_pct": 0.2, + "frame_gamescope_pct": 5.4, + "frame_total_pct": 32.0 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 71 Hz", + "show_s": 1.57, + "panel": "valve.steam.desktopgame.2001615880", + "src": "separate:6692" + } + ], + [ + { + "scenario": "type", + "frames_sent": 190, + "frames_drawn": 190, + "frames_shown": 190, + "duration_s": 21.1, + "stages_ms": { + "capture": { + "p50": -4.7, + "p95": 4.9, + "p99": 7.4, + "n": 190 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 190 + }, + "encode": { + "p50": 6.4, + "p95": 9.0, + "p99": 12.6, + "n": 190 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.3, + "n": 190 + }, + "network": { + "p50": 2.8, + "p95": 5.1, + "p99": 7.8, + "n": 190 + }, + "decode": { + "p50": 2.0, + "p95": 4.0, + "p99": 5.9, + "n": 190 + }, + "draw": { + "p50": 0.6, + "p95": 1.5, + "p99": 2.0, + "n": 190 + }, + "present": { + "p50": 0.6, + "p95": 8.5, + "p99": 13.4, + "n": 190 + }, + "content": { + "p50": 12.8, + "p95": 19.4, + "p99": 21.3, + "n": 190 + }, + "content_shown": { + "p50": 13.8, + "p95": 22.9, + "p99": 25.0, + "n": 190 + } + }, + "fps": 9.0, + "fps_shown": 9.0, + "late_pct": 41.88, + "stall_max": 2314.7, + "stalls_over_100ms": 67, + "mbps": 0.03, + "keyframes": 0, + "size": "1920x1290", + "captured": 190, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 35.9, + "p95": 91.9, + "p99": 180.6, + "n": 65 + }, + "input_shown_ms": { + "p50": 36.3, + "p95": 92.4, + "p99": 181.0, + "n": 65 + }, + "input_parts_ms": { + "uplink": { + "p50": 5.3, + "p95": 19.9, + "p99": 57.7, + "n": 65 + }, + "mac": { + "p50": 17.6, + "p95": 38.0, + "p99": 89.2, + "n": 65 + }, + "back": { + "p50": 13.2, + "p95": 20.9, + "p99": 21.3, + "n": 65 + } + }, + "inputs": { + "asked": 65, + "sent": 66, + "seen": 65 + }, + "timeline": [ + { + "t": 0, + "fps": 14, + "mbps": 0.03, + "content_p50": 12.3, + "content_p95": 17.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 6, + "mbps": 0.02, + "content_p50": 12.0, + "content_p95": 20.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 5, + "mbps": 0.02, + "content_p50": 15.5, + "content_p95": 19.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 5, + "mbps": 0.02, + "content_p50": 12.5, + "content_p95": 18.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 11, + "mbps": 0.03, + "content_p50": 13.2, + "content_p95": 19.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 12, + "mbps": 0.03, + "content_p50": 14.7, + "content_p95": 18.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 6, + "mbps": 0.02, + "content_p50": 12.6, + "content_p95": 14.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 13, + "mbps": 0.03, + "content_p50": 13.5, + "content_p95": 22.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 19, + "mbps": 0.05, + "content_p50": 12.4, + "content_p95": 16.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 5, + "mbps": 0.02, + "content_p50": 13.0, + "content_p95": 14.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 5, + "mbps": 0.02, + "content_p50": 12.9, + "content_p95": 13.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 5, + "mbps": 0.02, + "content_p50": 10.9, + "content_p95": 21.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 3, + "mbps": 0.01, + "content_p50": 14.2, + "content_p95": 14.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 1, + "mbps": 0.0, + "content_p50": 13.7, + "content_p95": 13.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 2, + "mbps": 0.01, + "content_p50": 12.7, + "content_p95": 15.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 42, + "mbps": 0.14, + "content_p50": 12.4, + "content_p95": 16.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 16, + "fps": 25, + "mbps": 0.1, + "content_p50": 12.2, + "content_p95": 14.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 17, + "fps": 7, + "mbps": 0.11, + "content_p50": 15.2, + "content_p95": 20.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 18, + "fps": 4, + "mbps": 0.01, + "content_p50": 14.2, + "content_p95": 14.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 12.8, + "content_p95": 19.4, + "input_p50": 35.9, + "input_p95": 91.9, + "grades": { + "input_replies": "local", + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "acceptable" + }, + "controller": { + "adapt": true, + "baseRtt": 5.709, + "ceiling": 14860800, + "fps": 60, + "inFlight": 0, + "scale": 1, + "slack": 41.666, + "target": 14860800, + "tier": 0 + }, + "events": [], + "source_fps": 9.0, + "cpu": { + "mac_agent_pct": 2.0, + "frame_viewer_pct": 12.8, + "frame_tailscaled_pct": 2.0, + "frame_sshd_pct": 0.2, + "frame_gamescope_pct": 6.0, + "frame_total_pct": 26.5 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 71 Hz", + "show_s": 1.29, + "panel": "valve.steam.desktopgame.2001147903", + "src": "separate:7108" + } + ], + [ + { + "scenario": "type", + "frames_sent": 83, + "frames_drawn": 83, + "frames_shown": 83, + "duration_s": 21.1, + "stages_ms": { + "capture": { + "p50": 0.2, + "p95": 19.9, + "p99": 50.0, + "n": 83 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 21.0, + "n": 83 + }, + "encode": { + "p50": 6.6, + "p95": 8.9, + "p99": 19.9, + "n": 83 + }, + "socket": { + "p50": 0.1, + "p95": 0.3, + "p99": 9.8, + "n": 83 + }, + "network": { + "p50": 3.4, + "p95": 8.8, + "p99": 43.6, + "n": 83 + }, + "decode": { + "p50": 2.6, + "p95": 8.7, + "p99": 11.1, + "n": 83 + }, + "draw": { + "p50": 0.7, + "p95": 1.1, + "p99": 1.7, + "n": 83 + }, + "present": { + "p50": 0.6, + "p95": 5.7, + "p99": 11.5, + "n": 83 + }, + "content": { + "p50": 14.6, + "p95": 60.4, + "p99": 124.2, + "n": 83 + }, + "content_shown": { + "p50": 15.9, + "p95": 61.1, + "p99": 124.8, + "n": 83 + } + }, + "fps": 3.9, + "fps_shown": 3.9, + "late_pct": 61.45, + "stall_max": 9230.5, + "stalls_over_100ms": 39, + "mbps": 0.15, + "keyframes": 0, + "size": "1920x1290", + "captured": 114, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 44.3, + "p95": 236.4, + "p99": 1853.7, + "n": 39 + }, + "input_shown_ms": { + "p50": 45.2, + "p95": 236.9, + "p99": 1854.2, + "n": 39 + }, + "input_parts_ms": { + "uplink": { + "p50": 5.3, + "p95": 18.0, + "p99": 45.1, + "n": 39 + }, + "mac": { + "p50": 21.2, + "p95": 150.9, + "p99": 1809.2, + "n": 39 + }, + "back": { + "p50": 13.9, + "p95": 23.3, + "p99": 179.5, + "n": 39 + } + }, + "inputs": { + "asked": 65, + "sent": 66, + "seen": 39 + }, + "timeline": [ + { + "t": 0, + "fps": 7, + "mbps": 0.04, + "content_p50": 16.2, + "content_p95": 179.5, + "bitrate": 469728, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 10, + "mbps": 0.04, + "content_p50": 16.5, + "content_p95": 30.9, + "bitrate": 469728, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 5, + "mbps": 0.02, + "content_p50": 13.8, + "content_p95": 14.6, + "bitrate": 469728, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 6, + "mbps": 0.02, + "content_p50": 13.5, + "content_p95": 18.5, + "bitrate": 673370, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 7, + "mbps": 0.02, + "content_p50": 12.7, + "content_p95": 13.4, + "bitrate": 1217929, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 15, + "mbps": 0.06, + "content_p50": 13.3, + "content_p95": 19.4, + "bitrate": 1786562, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 6, + "mbps": 0.02, + "content_p50": 13.9, + "content_p95": 20.1, + "bitrate": 2543412, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 5, + "mbps": 0.11, + "content_p50": 14.2, + "content_p95": 23.2, + "bitrate": 3182528, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 3, + "mbps": 0.01, + "content_p50": 13.5, + "content_p95": 13.5, + "bitrate": 4891587, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 17, + "fps": 5, + "mbps": 0.97, + "content_p50": 41.2, + "content_p95": 89.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 18, + "fps": 5, + "mbps": 0.65, + "content_p50": 36.5, + "content_p95": 44.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 20, + "fps": 5, + "mbps": 0.72, + "content_p50": 26.1, + "content_p95": 40.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 21, + "fps": 4, + "mbps": 0.5, + "content_p50": 17.6, + "content_p95": 24.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 14.6, + "content_p95": 60.4, + "input_p50": 44.3, + "input_p95": 236.4, + "grades": { + "input_replies": "bad", + "content_p50": "local", + "content_p95": "acceptable", + "input_p50": "local", + "input_p95": "bad" + }, + "controller": { + "adapt": true, + "baseRtt": 6.375, + "ceiling": 14860800, + "fps": 60, + "inFlight": 0, + "scale": 1, + "slack": 44.666, + "target": 14860800, + "tier": 0 + }, + "events": [ + { + "t": 0.39, + "e": "down to 469 kbit/s: queue 165 ms, held 4, oldest 213 ms, base 5 ms, sent 44 got 28 wants 234" + } + ], + "source_fps": 5.4, + "cpu": { + "mac_agent_pct": 1.5, + "frame_viewer_pct": 8.1, + "frame_tailscaled_pct": 1.8, + "frame_sshd_pct": 0.1, + "frame_gamescope_pct": 5.5, + "frame_total_pct": 25.1 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 72 Hz", + "show_s": 1.27, + "panel": "valve.steam.desktopgame.2001547827", + "src": "separate:7247" + } + ] + ], + "warm": [ + [ + { + "scenario": "type", + "frames_sent": 114, + "frames_drawn": 114, + "frames_shown": 112, + "duration_s": 21.1, + "stages_ms": { + "capture": { + "p50": 0.2, + "p95": 5.8, + "p99": 10.8, + "n": 114 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 114 + }, + "encode": { + "p50": 6.6, + "p95": 12.2, + "p99": 14.3, + "n": 114 + }, + "socket": { + "p50": 0.1, + "p95": 0.4, + "p99": 2.9, + "n": 114 + }, + "network": { + "p50": 3.9, + "p95": 12.5, + "p99": 44.8, + "n": 114 + }, + "decode": { + "p50": 2.2, + "p95": 5.4, + "p99": 9.2, + "n": 114 + }, + "draw": { + "p50": 0.6, + "p95": 1.5, + "p99": 1.9, + "n": 114 + }, + "present": { + "p50": 0.5, + "p95": 0.9, + "p99": 1.2, + "n": 112 + }, + "content": { + "p50": 14.5, + "p95": 29.9, + "p99": 65.7, + "n": 114 + }, + "content_shown": { + "p50": 15.1, + "p95": 28.9, + "p99": 52.5, + "n": 112 + } + }, + "fps": 5.4, + "fps_shown": 5.3, + "late_pct": 72.17, + "stall_max": 1001.0, + "stalls_over_100ms": 67, + "mbps": 0.03, + "keyframes": 0, + "size": "1920x1290", + "captured": 162, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 46.6, + "p95": 71.3, + "p99": 84.0, + "n": 66 + }, + "input_shown_ms": { + "p50": 47.1, + "p95": 71.8, + "p99": 84.7, + "n": 66 + }, + "input_parts_ms": { + "uplink": { + "p50": 4.3, + "p95": 9.7, + "p99": 34.2, + "n": 66 + }, + "mac": { + "p50": 25.8, + "p95": 42.8, + "p99": 44.3, + "n": 66 + }, + "back": { + "p50": 15.2, + "p95": 29.3, + "p99": 29.9, + "n": 66 + } + }, + "inputs": { + "asked": 65, + "sent": 66, + "seen": 66 + }, + "timeline": [ + { + "t": 0, + "fps": 29, + "mbps": 0.26, + "content_p50": 14.3, + "content_p95": 21.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 7, + "mbps": 0.03, + "content_p50": 13.4, + "content_p95": 15.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 5, + "mbps": 0.02, + "content_p50": 16.9, + "content_p95": 18.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 9, + "mbps": 0.03, + "content_p50": 16.1, + "content_p95": 20.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 5, + "mbps": 0.02, + "content_p50": 14.4, + "content_p95": 19.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 10, + "mbps": 0.04, + "content_p50": 13.8, + "content_p95": 29.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 6, + "mbps": 0.02, + "content_p50": 14.2, + "content_p95": 28.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 4, + "mbps": 0.02, + "content_p50": 16.8, + "content_p95": 28.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 6, + "mbps": 0.03, + "content_p50": 13.8, + "content_p95": 16.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 5, + "mbps": 0.02, + "content_p50": 13.5, + "content_p95": 20.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 9, + "mbps": 0.05, + "content_p50": 29.9, + "content_p95": 95.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 7, + "mbps": 0.02, + "content_p50": 13.8, + "content_p95": 30.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 2, + "mbps": 0.01, + "content_p50": 13.3, + "content_p95": 17.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 1, + "mbps": 0.0, + "content_p50": 16.6, + "content_p95": 16.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 2, + "mbps": 0.01, + "content_p50": 15.2, + "content_p95": 17.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 1, + "mbps": 0.0, + "content_p50": 14.2, + "content_p95": 14.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 16, + "fps": 1, + "mbps": 0.0, + "content_p50": 19.1, + "content_p95": 19.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 17, + "fps": 2, + "mbps": 0.01, + "content_p50": 14.2, + "content_p95": 14.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 18, + "fps": 1, + "mbps": 0.0, + "content_p50": 13.9, + "content_p95": 13.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 19, + "fps": 1, + "mbps": 0.0, + "content_p50": 14.4, + "content_p95": 14.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 20, + "fps": 1, + "mbps": 0.0, + "content_p50": 15.1, + "content_p95": 15.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 14.5, + "content_p95": 29.9, + "input_p50": 46.6, + "input_p95": 71.3, + "grades": { + "input_replies": "local", + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "acceptable" + }, + "controller": { + "adapt": true, + "baseRtt": 5.541, + "ceiling": 14860800, + "fps": 60, + "inFlight": 1, + "scale": 1, + "slack": 48.292, + "target": 14860800, + "tier": 0 + }, + "events": [], + "source_fps": 7.7, + "cpu": { + "mac_agent_pct": 2.0, + "frame_viewer_pct": 10.9, + "frame_tailscaled_pct": 2.8, + "frame_sshd_pct": 0.4, + "frame_gamescope_pct": 5.4, + "frame_total_pct": 36.0 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 70 Hz", + "show_s": 1.28, + "panel": "valve.steam.desktopgame.2001621121", + "src": "separate:6829" + } + ], + [ + { + "scenario": "type", + "frames_sent": 220, + "frames_drawn": 220, + "frames_shown": 217, + "duration_s": 21.1, + "stages_ms": { + "capture": { + "p50": -5.5, + "p95": 3.8, + "p99": 14.3, + "n": 220 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.3, + "n": 220 + }, + "encode": { + "p50": 6.5, + "p95": 9.0, + "p99": 18.8, + "n": 220 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 1.7, + "n": 220 + }, + "network": { + "p50": 3.1, + "p95": 8.9, + "p99": 46.7, + "n": 220 + }, + "decode": { + "p50": 2.0, + "p95": 4.1, + "p99": 7.6, + "n": 220 + }, + "draw": { + "p50": 0.7, + "p95": 1.4, + "p99": 1.8, + "n": 220 + }, + "present": { + "p50": 0.6, + "p95": 10.4, + "p99": 13.9, + "n": 217 + }, + "content": { + "p50": 13.0, + "p95": 25.6, + "p99": 122.5, + "n": 220 + }, + "content_shown": { + "p50": 14.1, + "p95": 27.2, + "p99": 122.9, + "n": 217 + } + }, + "fps": 10.4, + "fps_shown": 10.3, + "late_pct": 38.46, + "stall_max": 4500.7, + "stalls_over_100ms": 64, + "mbps": 0.05, + "keyframes": 0, + "size": "1920x1290", + "captured": 220, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 51.4, + "p95": 63.8, + "p99": 80.7, + "n": 64 + }, + "input_shown_ms": { + "p50": 51.8, + "p95": 64.3, + "p99": 81.3, + "n": 64 + }, + "input_parts_ms": { + "uplink": { + "p50": 4.1, + "p95": 17.8, + "p99": 29.2, + "n": 64 + }, + "mac": { + "p50": 31.3, + "p95": 35.3, + "p99": 39.4, + "n": 64 + }, + "back": { + "p50": 13.6, + "p95": 19.9, + "p99": 26.1, + "n": 64 + } + }, + "inputs": { + "asked": 65, + "sent": 66, + "seen": 64 + }, + "timeline": [ + { + "t": 0, + "fps": 28, + "mbps": 0.27, + "content_p50": 16.7, + "content_p95": 61.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 8, + "mbps": 0.03, + "content_p50": 13.7, + "content_p95": 19.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 5, + "mbps": 0.02, + "content_p50": 13.3, + "content_p95": 14.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 5, + "mbps": 0.02, + "content_p50": 13.2, + "content_p95": 13.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 5, + "mbps": 0.02, + "content_p50": 12.9, + "content_p95": 14.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 5, + "mbps": 0.02, + "content_p50": 16.9, + "content_p95": 24.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 5, + "mbps": 0.02, + "content_p50": 13.6, + "content_p95": 17.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 5, + "mbps": 0.02, + "content_p50": 13.1, + "content_p95": 15.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 6, + "mbps": 0.02, + "content_p50": 13.8, + "content_p95": 15.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 6, + "mbps": 0.02, + "content_p50": 14.5, + "content_p95": 16.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 6, + "mbps": 0.02, + "content_p50": 12.6, + "content_p95": 19.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 48, + "mbps": 0.15, + "content_p50": 12.8, + "content_p95": 16.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 47, + "mbps": 0.15, + "content_p50": 12.6, + "content_p95": 16.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 30, + "mbps": 0.09, + "content_p50": 12.8, + "content_p95": 15.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 2, + "mbps": 0.01, + "content_p50": 11.6, + "content_p95": 13.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 6, + "mbps": 0.11, + "content_p50": 42.9, + "content_p95": 143.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 16, + "fps": 3, + "mbps": 0.01, + "content_p50": 12.8, + "content_p95": 13.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 13.0, + "content_p95": 25.6, + "input_p50": 51.4, + "input_p95": 63.8, + "grades": { + "input_replies": "local", + "content_p50": "local", + "content_p95": "local", + "input_p50": "acceptable", + "input_p95": "local" + }, + "controller": { + "adapt": true, + "baseRtt": 5.792, + "ceiling": 14860800, + "fps": 60, + "inFlight": 0, + "scale": 1, + "slack": 41.666, + "target": 14860800, + "tier": 0 + }, + "events": [], + "source_fps": 10.4, + "cpu": { + "mac_agent_pct": 2.5, + "frame_viewer_pct": 14.7, + "frame_tailscaled_pct": 2.9, + "frame_sshd_pct": 0.4, + "frame_gamescope_pct": 6.1, + "frame_total_pct": 33.5 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 71 Hz", + "show_s": 1.27, + "panel": "valve.steam.desktopgame.2001606834", + "src": "separate:6969" + } + ], + [ + { + "scenario": "type", + "frames_sent": 128, + "frames_drawn": 128, + "frames_shown": 126, + "duration_s": 21.1, + "stages_ms": { + "capture": { + "p50": -3.5, + "p95": 1.4, + "p99": 26.9, + "n": 128 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.7, + "n": 128 + }, + "encode": { + "p50": 6.7, + "p95": 9.9, + "p99": 23.0, + "n": 128 + }, + "socket": { + "p50": 0.1, + "p95": 0.3, + "p99": 2.8, + "n": 128 + }, + "network": { + "p50": 3.3, + "p95": 5.1, + "p99": 205.9, + "n": 128 + }, + "decode": { + "p50": 2.1, + "p95": 4.2, + "p99": 6.4, + "n": 128 + }, + "draw": { + "p50": 0.7, + "p95": 1.7, + "p99": 1.9, + "n": 128 + }, + "present": { + "p50": 0.6, + "p95": 9.2, + "p99": 13.7, + "n": 126 + }, + "content": { + "p50": 13.4, + "p95": 21.6, + "p99": 264.8, + "n": 128 + }, + "content_shown": { + "p50": 14.5, + "p95": 26.1, + "p99": 265.4, + "n": 126 + } + }, + "fps": 6.1, + "fps_shown": 6.0, + "late_pct": 17.83, + "stall_max": 9246.3, + "stalls_over_100ms": 17, + "mbps": 0.02, + "keyframes": 0, + "size": "1920x1290", + "captured": 129, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 42.5, + "p95": 194.0, + "p99": 404.5, + "n": 25 + }, + "input_shown_ms": { + "p50": 43.2, + "p95": 194.6, + "p99": 405.1, + "n": 25 + }, + "input_parts_ms": { + "uplink": { + "p50": 9.4, + "p95": 26.2, + "p99": 35.0, + "n": 25 + }, + "mac": { + "p50": 23.3, + "p95": 135.9, + "p99": 176.2, + "n": 25 + }, + "back": { + "p50": 13.8, + "p95": 21.6, + "p99": 264.8, + "n": 25 + } + }, + "inputs": { + "asked": 65, + "sent": 66, + "seen": 25 + }, + "timeline": [ + { + "t": 0, + "fps": 24, + "mbps": 0.06, + "content_p50": 13.0, + "content_p95": 14.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 58, + "mbps": 0.19, + "content_p50": 13.6, + "content_p95": 18.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 26, + "mbps": 0.08, + "content_p50": 12.9, + "content_p95": 15.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 6, + "mbps": 0.08, + "content_p50": 13.5, + "content_p95": 17.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 4, + "mbps": 0.05, + "content_p50": 18.8, + "content_p95": 25.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 3, + "mbps": 0.01, + "content_p50": 13.3, + "content_p95": 13.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 4, + "mbps": 0.03, + "content_p50": 264.8, + "content_p95": 354.5, + "bitrate": 300000, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 3, + "mbps": 0.01, + "content_p50": 13.8, + "content_p95": 15.1, + "bitrate": 300000, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 13.4, + "content_p95": 21.6, + "input_p50": 42.5, + "input_p95": 194.0, + "grades": { + "input_replies": "bad", + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "bad" + }, + "controller": { + "adapt": true, + "baseRtt": 0, + "ceiling": 14860800, + "fps": 60, + "inFlight": 0, + "scale": 1, + "slack": 41.666, + "target": 14855423, + "tier": 0 + }, + "events": [ + { + "t": 10.93, + "e": "down to 300 kbit/s: queue 0 ms, held 0, oldest 201 ms, base 5 ms, sent 61 got 0 wants 51" + } + ], + "source_fps": 6.1, + "cpu": { + "mac_agent_pct": 2.1, + "frame_viewer_pct": 11.1, + "frame_tailscaled_pct": 2.6, + "frame_sshd_pct": 0.4, + "frame_gamescope_pct": 6.0, + "frame_total_pct": 25.6 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 72 Hz", + "show_s": 1.32, + "panel": "valve.steam.desktopgame.2001763888", + "src": "separate:7401" + } + ] + ] + }, + "table": { + "type": { + "content_p50": { + "cold": [ + 13.9, + 12.8, + 14.6 + ], + "warm": [ + 14.5, + 13.0, + 13.4 + ] + }, + "content_p95": { + "cold": [ + 25.6, + 19.4, + 60.4 + ], + "warm": [ + 29.9, + 25.6, + 21.6 + ] + }, + "input_p50": { + "cold": [ + 44.5, + 35.9, + 44.3 + ], + "warm": [ + 46.6, + 51.4, + 42.5 + ] + }, + "input_p95": { + "cold": [ + 68.2, + 91.9, + 236.4 + ], + "warm": [ + 71.3, + 63.8, + 194.0 + ] + }, + "fps": { + "cold": [ + 6.9, + 9.0, + 3.9 + ], + "warm": [ + 5.4, + 10.4, + 6.1 + ] + }, + "late_pct": { + "cold": [ + 51.37, + 41.88, + 61.45 + ], + "warm": [ + 72.17, + 38.46, + 17.83 + ] + }, + "stall_max": { + "cold": [ + 1007.5, + 2314.7, + 9230.5 + ], + "warm": [ + 1001.0, + 4500.7, + 9246.3 + ] + }, + "mbps": { + "cold": [ + 0.03, + 0.03, + 0.15 + ], + "warm": [ + 0.03, + 0.05, + 0.02 + ] + } + } + } +} \ No newline at end of file diff --git a/bench/results/2026-09-28-a3c6e5c-dirty-adapt-clean.json b/bench/results/2026-09-28-a3c6e5c-dirty-adapt-clean.json new file mode 100644 index 0000000..7f449e1 --- /dev/null +++ b/bench/results/2026-09-28-a3c6e5c-dirty-adapt-clean.json @@ -0,0 +1,984 @@ +{ + "label": "adapt-clean", + "date": "2026-09-28T17:13:59", + "commit": "a3c6e5c+dirty", + "config": { + "quality": "balanced", + "mode": "separate", + "net": "none", + "delay_ms": 0, + "buffer_ms": 250, + "host": "frame", + "ssh_opts": [], + "encoder": "", + "duration_s": 20.0, + "browser_flags": [] + }, + "frame_build": "20260925.6191901", + "mac": "26.5.2", + "headset": ":39.639829 [Info] - 0 - entering standby", + "scenarios": [ + { + "scenario": "test", + "frames_sent": 653, + "frames_drawn": 653, + "frames_shown": 649, + "duration_s": 20.7, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 653 + }, + "queue": { + "p50": 11.2, + "p95": 16.1, + "p99": 17.1, + "n": 653 + }, + "encode": { + "p50": 3.5, + "p95": 4.2, + "p99": 4.4, + "n": 653 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 653 + }, + "network": { + "p50": 2.9, + "p95": 7.0, + "p99": 12.0, + "n": 653 + }, + "decode": { + "p50": 1.0, + "p95": 2.4, + "p99": 2.8, + "n": 653 + }, + "draw": { + "p50": 0.3, + "p95": 0.6, + "p99": 0.9, + "n": 653 + }, + "present": { + "p50": 0.5, + "p95": 0.9, + "p99": 5.9, + "n": 649 + }, + "content": { + "p50": 19.8, + "p95": 26.2, + "p99": 30.1, + "n": 653 + }, + "content_shown": { + "p50": 20.3, + "p95": 26.9, + "p99": 31.1, + "n": 649 + } + }, + "fps": 31.5, + "fps_shown": 31.3, + "late_pct": 98.01, + "stall_max": 144.0, + "stalls_over_100ms": 2, + "mbps": 0.13, + "keyframes": 2, + "size": "960x540", + "captured": 1336, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 43.5, + "p95": 61.8, + "p99": 63.4, + "n": 40 + }, + "input_shown_ms": { + "p50": 44.3, + "p95": 62.3, + "p99": 63.9, + "n": 40 + }, + "input_parts_ms": { + "uplink": { + "p50": 11.1, + "p95": 29.2, + "p99": 36.1, + "n": 40 + }, + "mac": { + "p50": 10.4, + "p95": 28.2, + "p99": 32.4, + "n": 40 + }, + "back": { + "p50": 17.5, + "p95": 27.9, + "p99": 28.8, + "n": 40 + } + }, + "inputs": { + "sent": 40, + "seen": 40 + }, + "timeline": [ + { + "t": 0, + "fps": 31, + "mbps": 0.13, + "content_p50": 21.5, + "content_p95": 27.1, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 1, + "fps": 31, + "mbps": 0.12, + "content_p50": 22.4, + "content_p95": 26.3, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 2, + "fps": 32, + "mbps": 0.13, + "content_p50": 18.9, + "content_p95": 24.8, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 3, + "fps": 31, + "mbps": 0.12, + "content_p50": 18.2, + "content_p95": 24.6, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 4, + "fps": 32, + "mbps": 0.13, + "content_p50": 21.0, + "content_p95": 25.6, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 5, + "fps": 32, + "mbps": 0.12, + "content_p50": 19.5, + "content_p95": 24.3, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 6, + "fps": 31, + "mbps": 0.12, + "content_p50": 22.2, + "content_p95": 25.6, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 7, + "fps": 32, + "mbps": 0.13, + "content_p50": 19.9, + "content_p95": 24.0, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 8, + "fps": 31, + "mbps": 0.21, + "content_p50": 22.7, + "content_p95": 26.9, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 9, + "fps": 31, + "mbps": 0.12, + "content_p50": 20.2, + "content_p95": 26.1, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 10, + "fps": 32, + "mbps": 0.12, + "content_p50": 18.2, + "content_p95": 23.2, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 11, + "fps": 31, + "mbps": 0.12, + "content_p50": 19.8, + "content_p95": 24.2, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 12, + "fps": 32, + "mbps": 0.13, + "content_p50": 19.2, + "content_p95": 24.8, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 13, + "fps": 32, + "mbps": 0.12, + "content_p50": 21.0, + "content_p95": 24.3, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 14, + "fps": 30, + "mbps": 0.13, + "content_p50": 20.4, + "content_p95": 96.7, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 15, + "fps": 32, + "mbps": 0.13, + "content_p50": 18.2, + "content_p95": 24.4, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 16, + "fps": 31, + "mbps": 0.12, + "content_p50": 18.4, + "content_p95": 25.1, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 17, + "fps": 32, + "mbps": 0.13, + "content_p50": 17.2, + "content_p95": 23.5, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 18, + "fps": 32, + "mbps": 0.19, + "content_p50": 19.4, + "content_p95": 25.2, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 19, + "fps": 31, + "mbps": 0.14, + "content_p50": 21.1, + "content_p95": 84.3, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 20, + "fps": 24, + "mbps": 0.08, + "content_p50": 20.8, + "content_p95": 25.1, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + } + ], + "adapt": [], + "content_p50": 19.8, + "content_p95": 26.2, + "input_p50": 43.5, + "input_p95": 61.8, + "grades": { + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "local", + "fps": "bad", + "late_pct": "bad", + "stall_max": "acceptable" + }, + "source_fps": 64.5, + "cpu": { + "mac_agent_pct": 11.3, + "frame_viewer_pct": 35.9, + "frame_tailscaled_pct": 3.5, + "frame_sshd_pct": 0.6, + "frame_gamescope_pct": 7.5, + "frame_total_pct": 16.1 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 141 Hz", + "show_s": 4.68, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + }, + { + "scenario": "scroll", + "frames_sent": 937, + "frames_drawn": 937, + "frames_shown": 720, + "duration_s": 20.7, + "stages_ms": { + "capture": { + "p50": -3.9, + "p95": 0.9, + "p99": 8.2, + "n": 937 + }, + "queue": { + "p50": 0.0, + "p95": 16.3, + "p99": 20.1, + "n": 937 + }, + "encode": { + "p50": 6.4, + "p95": 7.2, + "p99": 9.3, + "n": 937 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 937 + }, + "network": { + "p50": 7.3, + "p95": 13.0, + "p99": 22.8, + "n": 937 + }, + "decode": { + "p50": 6.2, + "p95": 12.7, + "p99": 18.6, + "n": 937 + }, + "draw": { + "p50": 0.6, + "p95": 1.1, + "p99": 1.9, + "n": 937 + }, + "present": { + "p50": 2.9, + "p95": 18.5, + "p99": 24.5, + "n": 720 + }, + "content": { + "p50": 19.9, + "p95": 37.2, + "p99": 50.2, + "n": 937 + }, + "content_shown": { + "p50": 27.0, + "p95": 45.7, + "p99": 55.0, + "n": 720 + } + }, + "fps": 45.3, + "fps_shown": 34.8, + "late_pct": 28.63, + "stall_max": 99.3, + "stalls_over_100ms": 0, + "mbps": 7.59, + "keyframes": 2, + "size": "1920x1290", + "captured": 1215, + "viewer_never_drawn": 0, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 30, + "mbps": 4.01, + "content_p50": 31.9, + "content_p95": 52.3, + "bitrate": 4867140, + "tier": 2, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 30, + "mbps": 4.12, + "content_p50": 29.9, + "content_p95": 45.3, + "bitrate": 3807054, + "tier": 2, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 30, + "mbps": 3.26, + "content_p50": 31.8, + "content_p95": 52.5, + "bitrate": 3235995, + "tier": 2, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 30, + "mbps": 3.72, + "content_p50": 24.5, + "content_p95": 37.7, + "bitrate": 4238740, + "tier": 2, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 30, + "mbps": 5.09, + "content_p50": 25.2, + "content_p95": 39.6, + "bitrate": 5992063, + "tier": 2, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 31, + "mbps": 7.29, + "content_p50": 29.1, + "content_p95": 53.5, + "bitrate": 5526067, + "tier": 2, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 35, + "mbps": 4.58, + "content_p50": 25.3, + "content_p95": 32.4, + "bitrate": 6018152, + "tier": 1, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 46, + "mbps": 7.12, + "content_p50": 25.1, + "content_p95": 34.3, + "bitrate": 8412933, + "tier": 1, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 46, + "mbps": 8.25, + "content_p50": 27.1, + "content_p95": 40.3, + "bitrate": 10760191, + "tier": 1, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 47, + "mbps": 9.3, + "content_p50": 27.8, + "content_p95": 41.2, + "bitrate": 13717060, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 55, + "mbps": 8.98, + "content_p50": 16.5, + "content_p95": 21.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 51, + "mbps": 9.32, + "content_p50": 18.6, + "content_p95": 35.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 56, + "mbps": 9.53, + "content_p50": 15.4, + "content_p95": 26.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 55, + "mbps": 8.46, + "content_p50": 18.6, + "content_p95": 23.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 54, + "mbps": 9.82, + "content_p50": 15.9, + "content_p95": 28.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 54, + "mbps": 11.11, + "content_p50": 15.5, + "content_p95": 26.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 16, + "fps": 54, + "mbps": 9.79, + "content_p50": 17.4, + "content_p95": 25.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 17, + "fps": 57, + "mbps": 9.3, + "content_p50": 16.3, + "content_p95": 23.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 18, + "fps": 55, + "mbps": 8.3, + "content_p50": 19.5, + "content_p95": 31.0, + "bitrate": 7984791, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 19, + "fps": 51, + "mbps": 8.56, + "content_p50": 16.4, + "content_p95": 29.1, + "bitrate": 10220855, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 20, + "fps": 40, + "mbps": 7.13, + "content_p50": 18.3, + "content_p95": 23.9, + "bitrate": 12025604, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 19.9, + "content_p95": 37.2, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "local", + "content_p95": "local", + "fps": "acceptable", + "late_pct": "bad", + "stall_max": "local" + }, + "source_fps": 58.7, + "cpu": { + "mac_agent_pct": 6.6, + "frame_viewer_pct": 72.4, + "frame_tailscaled_pct": 8.1, + "frame_sshd_pct": 0.9, + "frame_gamescope_pct": 10.0, + "frame_total_pct": 24.2 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 141 Hz", + "show_s": 6.43, + "panel": "valve.steam.desktopgame.2001414593", + "src": "separate:9384" + }, + { + "scenario": "type", + "frames_sent": 25, + "frames_drawn": 25, + "frames_shown": 25, + "duration_s": 14.7, + "stages_ms": { + "capture": { + "p50": -0.3, + "p95": 0.9, + "p99": 1.1, + "n": 25 + }, + "queue": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 25 + }, + "encode": { + "p50": 5.0, + "p95": 33.4, + "p99": 33.6, + "n": 25 + }, + "socket": { + "p50": 0.1, + "p95": 0.1, + "p99": 0.2, + "n": 25 + }, + "network": { + "p50": 6.1, + "p95": 14.9, + "p99": 24.4, + "n": 25 + }, + "decode": { + "p50": 3.0, + "p95": 10.5, + "p99": 11.4, + "n": 25 + }, + "draw": { + "p50": 0.6, + "p95": 1.0, + "p99": 1.1, + "n": 25 + }, + "present": { + "p50": 0.8, + "p95": 1.4, + "p99": 1.7, + "n": 25 + }, + "content": { + "p50": 12.6, + "p95": 41.0, + "p99": 46.3, + "n": 25 + }, + "content_shown": { + "p50": 13.9, + "p95": 41.8, + "p99": 46.6, + "n": 25 + } + }, + "fps": 1.7, + "fps_shown": 1.7, + "late_pct": 100.0, + "stall_max": 3099.2, + "stalls_over_100ms": 21, + "mbps": 0.04, + "keyframes": 4, + "size": "960x646", + "captured": 79, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 41.3, + "p95": 60.3, + "p99": 68.6, + "n": 18 + }, + "input_shown_ms": { + "p50": 43.0, + "p95": 61.1, + "p99": 69.6, + "n": 18 + }, + "input_parts_ms": { + "uplink": { + "p50": 5.3, + "p95": 11.6, + "p99": 14.2, + "n": 18 + }, + "mac": { + "p50": 23.1, + "p95": 35.0, + "p99": 42.6, + "n": 18 + }, + "back": { + "p50": 11.8, + "p95": 19.2, + "p99": 22.7, + "n": 18 + } + }, + "inputs": { + "sent": 66, + "seen": 18 + }, + "timeline": [ + { + "t": 3, + "fps": 1, + "mbps": 0.01, + "content_p50": 11.8, + "content_p95": 11.8, + "bitrate": 1224075, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 4, + "fps": 1, + "mbps": 0.02, + "content_p50": 14.7, + "content_p95": 14.7, + "bitrate": 2091896, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 5, + "fps": 4, + "mbps": 0.1, + "content_p50": 18.6, + "content_p95": 37.4, + "bitrate": 3071304, + "tier": 3, + "w": 1440, + "net": null + }, + { + "t": 6, + "fps": 5, + "mbps": 0.13, + "content_p50": 4.2, + "content_p95": 19.2, + "bitrate": 4031277, + "tier": 3, + "w": 1440, + "net": null + }, + { + "t": 7, + "fps": 6, + "mbps": 0.04, + "content_p50": 12.0, + "content_p95": 46.3, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 10, + "fps": 1, + "mbps": 0.01, + "content_p50": 12.2, + "content_p95": 12.2, + "bitrate": 300000, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 12, + "fps": 1, + "mbps": 0.02, + "content_p50": -1.2, + "content_p95": -1.2, + "bitrate": 842857, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 14, + "fps": 1, + "mbps": 0.02, + "content_p50": 14.4, + "content_p95": 14.4, + "bitrate": 2091896, + "tier": 4, + "w": 960, + "net": null + } + ], + "adapt": [], + "content_p50": 12.6, + "content_p95": 41.0, + "input_p50": 41.3, + "input_p95": 60.3, + "grades": { + "content_p50": "local", + "content_p95": "acceptable", + "input_p50": "local", + "input_p95": "local" + }, + "source_fps": 5.4, + "cpu": { + "mac_agent_pct": 1.2, + "frame_viewer_pct": 5.0, + "frame_tailscaled_pct": 1.5, + "frame_sshd_pct": 0.2, + "frame_gamescope_pct": 4.7, + "frame_total_pct": 8.5 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 140 Hz", + "show_s": 5.12, + "panel": "valve.steam.desktopgame.2001910179", + "src": "separate:9510" + } + ] +} \ No newline at end of file diff --git a/bench/results/2026-09-28-a3c6e5c-dirty-adapt-clean2.json b/bench/results/2026-09-28-a3c6e5c-dirty-adapt-clean2.json new file mode 100644 index 0000000..79fd46c --- /dev/null +++ b/bench/results/2026-09-28-a3c6e5c-dirty-adapt-clean2.json @@ -0,0 +1,1116 @@ +{ + "label": "adapt-clean2", + "date": "2026-09-28T17:16:27", + "commit": "a3c6e5c+dirty", + "config": { + "quality": "balanced", + "mode": "separate", + "net": "none", + "delay_ms": 0, + "buffer_ms": 250, + "host": "frame", + "ssh_opts": [], + "encoder": "", + "duration_s": 20.0, + "browser_flags": [] + }, + "frame_build": "20260925.6191901", + "mac": "26.5.2", + "headset": ":39.639829 [Info] - 0 - entering standby", + "scenarios": [ + { + "scenario": "test", + "frames_sent": 1229, + "frames_drawn": 1229, + "frames_shown": 814, + "duration_s": 20.7, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 1229 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 1229 + }, + "encode": { + "p50": 4.2, + "p95": 4.8, + "p99": 5.0, + "n": 1229 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 1229 + }, + "network": { + "p50": 6.1, + "p95": 10.9, + "p99": 28.8, + "n": 1229 + }, + "decode": { + "p50": 1.2, + "p95": 3.4, + "p99": 4.4, + "n": 1229 + }, + "draw": { + "p50": 0.3, + "p95": 0.7, + "p99": 1.1, + "n": 1229 + }, + "present": { + "p50": 5.5, + "p95": 16.5, + "p99": 19.2, + "n": 814 + }, + "content": { + "p50": 12.2, + "p95": 17.6, + "p99": 34.3, + "n": 1229 + }, + "content_shown": { + "p50": 18.0, + "p95": 29.6, + "p99": 38.7, + "n": 814 + } + }, + "fps": 59.3, + "fps_shown": 39.3, + "late_pct": 1.55, + "stall_max": 168.0, + "stalls_over_100ms": 2, + "mbps": 0.47, + "keyframes": 2, + "size": "1280x720", + "captured": 1337, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 27.8, + "p95": 41.1, + "p99": 46.2, + "n": 40 + }, + "input_shown_ms": { + "p50": 32.3, + "p95": 47.0, + "p99": 53.4, + "n": 25 + }, + "input_parts_ms": { + "uplink": { + "p50": 8.0, + "p95": 19.4, + "p99": 31.7, + "n": 40 + }, + "mac": { + "p50": 5.3, + "p95": 14.2, + "p99": 18.3, + "n": 40 + }, + "back": { + "p50": 12.7, + "p95": 16.6, + "p99": 17.7, + "n": 40 + } + }, + "inputs": { + "sent": 40, + "seen": 40 + }, + "timeline": [ + { + "t": 0, + "fps": 60, + "mbps": 0.48, + "content_p50": 10.2, + "content_p95": 13.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 1, + "fps": 60, + "mbps": 0.48, + "content_p50": 10.0, + "content_p95": 13.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 2, + "fps": 60, + "mbps": 0.47, + "content_p50": 10.6, + "content_p95": 17.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 3, + "fps": 60, + "mbps": 0.48, + "content_p50": 10.9, + "content_p95": 15.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 4, + "fps": 60, + "mbps": 0.49, + "content_p50": 11.0, + "content_p95": 17.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 5, + "fps": 60, + "mbps": 0.49, + "content_p50": 10.6, + "content_p95": 13.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 6, + "fps": 60, + "mbps": 0.59, + "content_p50": 10.4, + "content_p95": 15.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 7, + "fps": 60, + "mbps": 0.49, + "content_p50": 10.3, + "content_p95": 15.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 8, + "fps": 60, + "mbps": 0.48, + "content_p50": 11.8, + "content_p95": 15.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 9, + "fps": 60, + "mbps": 0.47, + "content_p50": 11.9, + "content_p95": 14.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 10, + "fps": 60, + "mbps": 0.45, + "content_p50": 12.3, + "content_p95": 23.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 11, + "fps": 58, + "mbps": 0.44, + "content_p50": 12.5, + "content_p95": 17.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 12, + "fps": 60, + "mbps": 0.46, + "content_p50": 14.2, + "content_p95": 17.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 13, + "fps": 58, + "mbps": 0.44, + "content_p50": 14.2, + "content_p95": 27.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 14, + "fps": 60, + "mbps": 0.47, + "content_p50": 14.3, + "content_p95": 17.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 15, + "fps": 60, + "mbps": 0.46, + "content_p50": 13.3, + "content_p95": 17.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 16, + "fps": 60, + "mbps": 0.57, + "content_p50": 11.6, + "content_p95": 14.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 17, + "fps": 60, + "mbps": 0.46, + "content_p50": 12.3, + "content_p95": 18.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 18, + "fps": 60, + "mbps": 0.44, + "content_p50": 12.4, + "content_p95": 14.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 19, + "fps": 60, + "mbps": 0.46, + "content_p50": 13.7, + "content_p95": 16.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 20, + "fps": 33, + "mbps": 0.24, + "content_p50": 13.3, + "content_p95": 135.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + } + ], + "adapt": [], + "content_p50": 12.2, + "content_p95": 17.6, + "input_p50": 27.8, + "input_p95": 41.1, + "grades": { + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "local", + "fps": "local", + "late_pct": "acceptable", + "stall_max": "acceptable" + }, + "source_fps": 64.6, + "cpu": { + "mac_agent_pct": 15.4, + "frame_viewer_pct": 57.6, + "frame_tailscaled_pct": 5.8, + "frame_sshd_pct": 0.8, + "frame_gamescope_pct": 11.1, + "frame_total_pct": 22.2 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 139 Hz", + "show_s": 5.83, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + }, + { + "scenario": "scroll", + "frames_sent": 734, + "frames_drawn": 734, + "frames_shown": 707, + "duration_s": 20.6, + "stages_ms": { + "capture": { + "p50": -4.8, + "p95": 6.5, + "p99": 13.1, + "n": 734 + }, + "queue": { + "p50": 8.4, + "p95": 19.8, + "p99": 28.9, + "n": 734 + }, + "encode": { + "p50": 6.3, + "p95": 7.1, + "p99": 10.1, + "n": 734 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 734 + }, + "network": { + "p50": 7.4, + "p95": 20.9, + "p99": 48.9, + "n": 734 + }, + "decode": { + "p50": 5.4, + "p95": 13.5, + "p99": 17.2, + "n": 734 + }, + "draw": { + "p50": 0.6, + "p95": 1.2, + "p99": 1.8, + "n": 734 + }, + "present": { + "p50": 0.5, + "p95": 13.6, + "p99": 21.3, + "n": 707 + }, + "content": { + "p50": 24.8, + "p95": 48.1, + "p99": 69.4, + "n": 734 + }, + "content_shown": { + "p50": 26.9, + "p95": 50.5, + "p99": 68.5, + "n": 707 + } + }, + "fps": 35.6, + "fps_shown": 34.3, + "late_pct": 58.12, + "stall_max": 168.7, + "stalls_over_100ms": 2, + "mbps": 4.09, + "keyframes": 3, + "size": "960x646", + "captured": 1122, + "viewer_never_drawn": 0, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 45, + "mbps": 7.92, + "content_p50": 23.2, + "content_p95": 38.8, + "bitrate": 8869129, + "tier": 1, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 46, + "mbps": 8.52, + "content_p50": 31.8, + "content_p95": 45.2, + "bitrate": 11334867, + "tier": 1, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 45, + "mbps": 8.17, + "content_p50": 29.2, + "content_p95": 65.7, + "bitrate": 6587726, + "tier": 1, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 45, + "mbps": 6.56, + "content_p50": 26.5, + "content_p95": 43.5, + "bitrate": 7164744, + "tier": 1, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 46, + "mbps": 6.64, + "content_p50": 21.5, + "content_p95": 30.9, + "bitrate": 6256828, + "tier": 1, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 44, + "mbps": 7.24, + "content_p50": 22.0, + "content_p95": 47.5, + "bitrate": 6291668, + "tier": 1, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 44, + "mbps": 5.19, + "content_p50": 28.2, + "content_p95": 50.0, + "bitrate": 5347917, + "tier": 1, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 44, + "mbps": 4.54, + "content_p50": 23.7, + "content_p95": 43.0, + "bitrate": 3863869, + "tier": 1, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 35, + "mbps": 3.83, + "content_p50": 28.2, + "content_p95": 41.2, + "bitrate": 4222978, + "tier": 2, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 29, + "mbps": 4.65, + "content_p50": 30.0, + "content_p95": 53.8, + "bitrate": 5482055, + "tier": 2, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 28, + "mbps": 4.05, + "content_p50": 26.4, + "content_p95": 121.7, + "bitrate": 3981384, + "tier": 2, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 30, + "mbps": 3.12, + "content_p50": 24.5, + "content_p95": 42.4, + "bitrate": 2876549, + "tier": 2, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 31, + "mbps": 2.72, + "content_p50": 22.6, + "content_p95": 36.5, + "bitrate": 2445066, + "tier": 2, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 29, + "mbps": 2.4, + "content_p50": 26.4, + "content_p95": 46.1, + "bitrate": 2690671, + "tier": 2, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 26, + "mbps": 2.21, + "content_p50": 24.8, + "content_p95": 55.1, + "bitrate": 2168791, + "tier": 3, + "w": 1440, + "net": null + }, + { + "t": 15, + "fps": 31, + "mbps": 1.65, + "content_p50": 24.1, + "content_p95": 33.2, + "bitrate": 1566951, + "tier": 3, + "w": 1440, + "net": null + }, + { + "t": 16, + "fps": 30, + "mbps": 1.43, + "content_p50": 19.8, + "content_p95": 38.0, + "bitrate": 1317132, + "tier": 3, + "w": 1440, + "net": null + }, + { + "t": 17, + "fps": 31, + "mbps": 1.33, + "content_p50": 23.2, + "content_p95": 41.1, + "bitrate": 1190325, + "tier": 3, + "w": 1440, + "net": null + }, + { + "t": 18, + "fps": 25, + "mbps": 1.11, + "content_p50": 23.6, + "content_p95": 99.7, + "bitrate": 860009, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 19, + "fps": 29, + "mbps": 0.64, + "content_p50": 22.1, + "content_p95": 41.5, + "bitrate": 731007, + "tier": 4, + "w": 960, + "net": null + }, + { + "t": 20, + "fps": 21, + "mbps": 0.47, + "content_p50": 26.1, + "content_p95": 31.1, + "bitrate": 619308, + "tier": 4, + "w": 960, + "net": null + } + ], + "adapt": [], + "content_p50": 24.8, + "content_p95": 48.1, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "local", + "content_p95": "acceptable", + "fps": "bad", + "late_pct": "bad", + "stall_max": "acceptable" + }, + "source_fps": 54.5, + "cpu": { + "mac_agent_pct": 5.6, + "frame_viewer_pct": 57.2, + "frame_tailscaled_pct": 5.3, + "frame_sshd_pct": 0.7, + "frame_gamescope_pct": 8.9, + "frame_total_pct": 21.0 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 142 Hz", + "show_s": 6.59, + "panel": "valve.steam.desktopgame.2001551415", + "src": "separate:9635" + }, + { + "scenario": "type", + "frames_sent": 75, + "frames_drawn": 75, + "frames_shown": 75, + "duration_s": 20.0, + "stages_ms": { + "capture": { + "p50": 0.3, + "p95": 4.2, + "p99": 5.1, + "n": 75 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 75 + }, + "encode": { + "p50": 6.8, + "p95": 9.4, + "p99": 11.7, + "n": 75 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 75 + }, + "network": { + "p50": 4.0, + "p95": 12.4, + "p99": 26.6, + "n": 75 + }, + "decode": { + "p50": 2.3, + "p95": 7.2, + "p99": 14.4, + "n": 75 + }, + "draw": { + "p50": 0.7, + "p95": 1.5, + "p99": 1.6, + "n": 75 + }, + "present": { + "p50": 0.5, + "p95": 1.1, + "p99": 1.2, + "n": 75 + }, + "content": { + "p50": 14.4, + "p95": 25.1, + "p99": 37.7, + "n": 75 + }, + "content_shown": { + "p50": 14.9, + "p95": 25.6, + "p99": 38.9, + "n": 75 + } + }, + "fps": 3.8, + "fps_shown": 3.8, + "late_pct": 97.3, + "stall_max": 1109.7, + "stalls_over_100ms": 70, + "mbps": 0.03, + "keyframes": 2, + "size": "1920x1290", + "captured": 75, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 45.2, + "p95": 77.2, + "p99": 83.1, + "n": 66 + }, + "input_shown_ms": { + "p50": 45.9, + "p95": 77.9, + "p99": 84.3, + "n": 66 + }, + "input_parts_ms": { + "uplink": { + "p50": 5.8, + "p95": 16.5, + "p99": 29.9, + "n": 66 + }, + "mac": { + "p50": 24.4, + "p95": 41.2, + "p99": 41.6, + "n": 66 + }, + "back": { + "p50": 14.5, + "p95": 25.1, + "p99": 31.6, + "n": 66 + } + }, + "inputs": { + "sent": 66, + "seen": 66 + }, + "timeline": [ + { + "t": 0, + "fps": 4, + "mbps": 0.01, + "content_p50": 2.0, + "content_p95": 4.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 5, + "mbps": 0.02, + "content_p50": 15.7, + "content_p95": 37.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 5, + "mbps": 0.02, + "content_p50": 17.1, + "content_p95": 18.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 5, + "mbps": 0.02, + "content_p50": 15.7, + "content_p95": 26.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 5, + "mbps": 0.02, + "content_p50": 15.9, + "content_p95": 19.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 5, + "mbps": 0.02, + "content_p50": 17.8, + "content_p95": 21.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 5, + "mbps": 0.22, + "content_p50": 18.7, + "content_p95": 31.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 5, + "mbps": 0.02, + "content_p50": 18.1, + "content_p95": 25.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 5, + "mbps": 0.02, + "content_p50": 12.5, + "content_p95": 13.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 6, + "mbps": 0.02, + "content_p50": 6.4, + "content_p95": 16.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 6, + "mbps": 0.02, + "content_p50": 14.4, + "content_p95": 18.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 5, + "mbps": 0.02, + "content_p50": 11.9, + "content_p95": 14.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 3, + "mbps": 0.01, + "content_p50": 12.9, + "content_p95": 22.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 1, + "mbps": 0.0, + "content_p50": 13.5, + "content_p95": 13.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 3, + "mbps": 0.01, + "content_p50": 2.2, + "content_p95": 2.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 1, + "mbps": 0.0, + "content_p50": 110.2, + "content_p95": 110.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 16, + "fps": 1, + "mbps": 0.0, + "content_p50": 14.4, + "content_p95": 14.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 17, + "fps": 2, + "mbps": 0.25, + "content_p50": -0.0, + "content_p95": 17.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 18, + "fps": 1, + "mbps": 0.0, + "content_p50": 14.1, + "content_p95": 14.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 19, + "fps": 1, + "mbps": 0.0, + "content_p50": 17.5, + "content_p95": 17.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 14.4, + "content_p95": 25.1, + "input_p50": 45.2, + "input_p95": 77.2, + "grades": { + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "acceptable" + }, + "source_fps": 3.8, + "cpu": { + "mac_agent_pct": 1.2, + "frame_viewer_pct": 7.1, + "frame_tailscaled_pct": 2.2, + "frame_sshd_pct": 0.3, + "frame_gamescope_pct": 4.7, + "frame_total_pct": 15.3 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 141 Hz", + "show_s": 6.64, + "panel": "valve.steam.desktopgame.2001684656", + "src": "separate:9763" + } + ] +} \ No newline at end of file diff --git a/bench/results/2026-09-28-a3c6e5c-dirty-baseline-fixed.json b/bench/results/2026-09-28-a3c6e5c-dirty-baseline-fixed.json new file mode 100644 index 0000000..d12e58c --- /dev/null +++ b/bench/results/2026-09-28-a3c6e5c-dirty-baseline-fixed.json @@ -0,0 +1,1124 @@ +{ + "label": "baseline-fixed", + "date": "2026-09-28T17:02:53", + "commit": "a3c6e5c+dirty", + "config": { + "quality": "balanced", + "mode": "separate", + "net": "none", + "delay_ms": 0, + "buffer_ms": 250, + "host": "frame", + "ssh_opts": [], + "encoder": "", + "duration_s": 20.0, + "browser_flags": [] + }, + "frame_build": "20260925.6191901", + "mac": "26.5.2", + "headset": ":51.824645 [Info] - 0 - entering standby", + "scenarios": [ + { + "scenario": "test", + "frames_sent": 1246, + "frames_drawn": 1223, + "frames_shown": 804, + "duration_s": 20.7, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 1246 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 1246 + }, + "encode": { + "p50": 4.0, + "p95": 4.7, + "p99": 4.9, + "n": 1246 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 1246 + }, + "network": { + "p50": 4.0, + "p95": 6.8, + "p99": 221.1, + "n": 1246 + }, + "decode": { + "p50": 1.3, + "p95": 2.5, + "p99": 4.2, + "n": 1223 + }, + "draw": { + "p50": 0.4, + "p95": 0.5, + "p99": 0.9, + "n": 1223 + }, + "present": { + "p50": 6.2, + "p95": 15.6, + "p99": 16.9, + "n": 804 + }, + "content": { + "p50": 9.9, + "p95": 12.9, + "p99": 16.4, + "n": 1223 + }, + "content_shown": { + "p50": 16.1, + "p95": 25.5, + "p99": 27.9, + "n": 804 + } + }, + "fps": 59.0, + "fps_shown": 38.8, + "late_pct": 0.33, + "stall_max": 433.3, + "stalls_over_100ms": 1, + "mbps": 0.48, + "keyframes": 2, + "size": "1280x720", + "captured": 1598, + "viewer_never_drawn": 23, + "input_ms": { + "p50": 28.8, + "p95": 39.0, + "p99": 45.2, + "n": 40 + }, + "input_shown_ms": { + "p50": 33.7, + "p95": 55.8, + "p99": 58.6, + "n": 29 + }, + "input_parts_ms": { + "uplink": { + "p50": 9.8, + "p95": 21.7, + "p99": 22.5, + "n": 40 + }, + "mac": { + "p50": 4.5, + "p95": 15.7, + "p99": 18.4, + "n": 40 + }, + "back": { + "p50": 10.6, + "p95": 13.2, + "p99": 15.7, + "n": 40 + } + }, + "inputs": { + "sent": 40, + "seen": 40 + }, + "timeline": [ + { + "t": 0, + "fps": 59, + "mbps": 0.49, + "content_p50": 9.3, + "content_p95": 11.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 1, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.6, + "content_p95": 12.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 2, + "fps": 60, + "mbps": 0.48, + "content_p50": 9.2, + "content_p95": 11.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 3, + "fps": 60, + "mbps": 0.48, + "content_p50": 9.8, + "content_p95": 11.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 4, + "fps": 38, + "mbps": 0.58, + "content_p50": 9.3, + "content_p95": 395.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 5, + "fps": 60, + "mbps": 0.48, + "content_p50": 9.3, + "content_p95": 11.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 6, + "fps": 60, + "mbps": 0.47, + "content_p50": 9.4, + "content_p95": 12.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 7, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.5, + "content_p95": 10.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 8, + "fps": 60, + "mbps": 0.46, + "content_p50": 8.9, + "content_p95": 11.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 9, + "fps": 60, + "mbps": 0.45, + "content_p50": 8.8, + "content_p95": 12.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 10, + "fps": 60, + "mbps": 0.45, + "content_p50": 11.5, + "content_p95": 14.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 11, + "fps": 60, + "mbps": 0.46, + "content_p50": 11.2, + "content_p95": 14.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 12, + "fps": 61, + "mbps": 0.47, + "content_p50": 10.7, + "content_p95": 12.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 13, + "fps": 59, + "mbps": 0.44, + "content_p50": 10.3, + "content_p95": 13.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 14, + "fps": 61, + "mbps": 0.57, + "content_p50": 10.1, + "content_p95": 14.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 15, + "fps": 59, + "mbps": 0.45, + "content_p50": 10.2, + "content_p95": 12.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 16, + "fps": 60, + "mbps": 0.48, + "content_p50": 10.1, + "content_p95": 11.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 17, + "fps": 60, + "mbps": 0.47, + "content_p50": 10.0, + "content_p95": 11.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 18, + "fps": 60, + "mbps": 0.48, + "content_p50": 9.9, + "content_p95": 11.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 19, + "fps": 60, + "mbps": 0.47, + "content_p50": 9.5, + "content_p95": 13.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 20, + "fps": 46, + "mbps": 0.34, + "content_p50": 9.2, + "content_p95": 13.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + } + ], + "adapt": [], + "content_p50": 9.9, + "content_p95": 12.9, + "input_p50": 28.8, + "input_p95": 39.0, + "grades": { + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "local", + "fps": "local", + "late_pct": "local", + "stall_max": "bad" + }, + "cpu": { + "mac_agent_pct": 14.3, + "frame_viewer_pct": 49.7, + "frame_tailscaled_pct": 5.7, + "frame_sshd_pct": 0.7, + "frame_gamescope_pct": 6.7, + "frame_total_pct": 16.5 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 142 Hz", + "show_s": 3.62, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + }, + { + "scenario": "scroll", + "frames_sent": 1047, + "frames_drawn": 1042, + "frames_shown": 798, + "duration_s": 20.7, + "stages_ms": { + "capture": { + "p50": -5.5, + "p95": 5.8, + "p99": 14.2, + "n": 1047 + }, + "queue": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.1, + "n": 1047 + }, + "encode": { + "p50": 6.7, + "p95": 9.0, + "p99": 14.1, + "n": 1047 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.3, + "n": 1047 + }, + "network": { + "p50": 4.9, + "p95": 9.3, + "p99": 47.3, + "n": 1047 + }, + "decode": { + "p50": 6.4, + "p95": 15.2, + "p99": 22.6, + "n": 1042 + }, + "draw": { + "p50": 0.6, + "p95": 0.8, + "p99": 1.6, + "n": 1042 + }, + "present": { + "p50": 4.7, + "p95": 19.6, + "p99": 24.9, + "n": 798 + }, + "content": { + "p50": 14.7, + "p95": 32.2, + "p99": 73.2, + "n": 1042 + }, + "content_shown": { + "p50": 21.1, + "p95": 41.7, + "p99": 66.3, + "n": 798 + } + }, + "fps": 50.4, + "fps_shown": 38.6, + "late_pct": 16.04, + "stall_max": 152.4, + "stalls_over_100ms": 3, + "mbps": 9.37, + "keyframes": 3, + "size": "1920x1290", + "captured": 1312, + "viewer_never_drawn": 5, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 51, + "mbps": 8.99, + "content_p50": 14.9, + "content_p95": 32.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 55, + "mbps": 10.09, + "content_p50": 17.4, + "content_p95": 26.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 51, + "mbps": 8.51, + "content_p50": 15.1, + "content_p95": 25.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 44, + "mbps": 7.9, + "content_p50": 16.7, + "content_p95": 73.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 46, + "mbps": 11.34, + "content_p50": 17.4, + "content_p95": 120.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 45, + "mbps": 8.55, + "content_p50": 15.1, + "content_p95": 25.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 51, + "mbps": 9.61, + "content_p50": 17.4, + "content_p95": 24.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 54, + "mbps": 9.52, + "content_p50": 16.9, + "content_p95": 27.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 50, + "mbps": 8.51, + "content_p50": 15.8, + "content_p95": 33.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 54, + "mbps": 9.22, + "content_p50": 12.4, + "content_p95": 27.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 51, + "mbps": 9.96, + "content_p50": 9.9, + "content_p95": 28.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 51, + "mbps": 9.27, + "content_p50": 14.1, + "content_p95": 22.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 52, + "mbps": 9.58, + "content_p50": 14.4, + "content_p95": 25.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 54, + "mbps": 8.61, + "content_p50": 7.8, + "content_p95": 22.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 43, + "mbps": 10.54, + "content_p50": 19.0, + "content_p95": 37.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 49, + "mbps": 8.51, + "content_p50": 16.1, + "content_p95": 27.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 16, + "fps": 51, + "mbps": 11.23, + "content_p50": 11.1, + "content_p95": 110.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 17, + "fps": 52, + "mbps": 8.99, + "content_p50": 13.9, + "content_p95": 26.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 18, + "fps": 52, + "mbps": 9.05, + "content_p50": 11.6, + "content_p95": 26.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 19, + "fps": 49, + "mbps": 9.3, + "content_p50": 12.4, + "content_p95": 28.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 20, + "fps": 37, + "mbps": 6.58, + "content_p50": 15.7, + "content_p95": 31.0, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 14.7, + "content_p95": 32.2, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "local", + "content_p95": "local", + "fps": "acceptable", + "late_pct": "bad", + "stall_max": "acceptable" + }, + "cpu": { + "mac_agent_pct": 7.6, + "frame_viewer_pct": 73.9, + "frame_tailscaled_pct": 8.0, + "frame_sshd_pct": 0.9, + "frame_gamescope_pct": 6.5, + "frame_total_pct": 19.4 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 138 Hz", + "show_s": 3.38, + "panel": "valve.steam.desktopgame.2001739297", + "src": "separate:8529" + }, + { + "scenario": "type", + "frames_sent": 77, + "frames_drawn": 77, + "frames_shown": 77, + "duration_s": 20.0, + "stages_ms": { + "capture": { + "p50": 0.2, + "p95": 4.7, + "p99": 4.8, + "n": 77 + }, + "queue": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.1, + "n": 77 + }, + "encode": { + "p50": 6.8, + "p95": 11.7, + "p99": 14.7, + "n": 77 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 77 + }, + "network": { + "p50": 4.9, + "p95": 8.7, + "p99": 75.8, + "n": 77 + }, + "decode": { + "p50": 4.2, + "p95": 9.9, + "p99": 14.5, + "n": 77 + }, + "draw": { + "p50": 1.0, + "p95": 1.6, + "p99": 2.1, + "n": 77 + }, + "present": { + "p50": 0.9, + "p95": 1.3, + "p99": 1.7, + "n": 77 + }, + "content": { + "p50": 16.7, + "p95": 34.2, + "p99": 88.1, + "n": 77 + }, + "content_shown": { + "p50": 17.9, + "p95": 34.6, + "p99": 88.9, + "n": 77 + } + }, + "fps": 3.8, + "fps_shown": 3.8, + "late_pct": 94.74, + "stall_max": 1156.0, + "stalls_over_100ms": 71, + "mbps": 0.03, + "keyframes": 2, + "size": "1920x1290", + "captured": 151, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 51.2, + "p95": 73.2, + "p99": 79.9, + "n": 66 + }, + "input_shown_ms": { + "p50": 52.1, + "p95": 73.9, + "p99": 80.5, + "n": 66 + }, + "input_parts_ms": { + "uplink": { + "p50": 5.6, + "p95": 13.2, + "p99": 17.5, + "n": 66 + }, + "mac": { + "p50": 34.1, + "p95": 43.6, + "p99": 48.7, + "n": 66 + }, + "back": { + "p50": 16.4, + "p95": 26.5, + "p99": 26.8, + "n": 66 + } + }, + "inputs": { + "sent": 66, + "seen": 66 + }, + "timeline": [ + { + "t": 0, + "fps": 5, + "mbps": 0.01, + "content_p50": 7.7, + "content_p95": 15.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 1, + "fps": 5, + "mbps": 0.02, + "content_p50": 7.4, + "content_p95": 16.7, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 2, + "fps": 5, + "mbps": 0.02, + "content_p50": 16.2, + "content_p95": 21.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 3, + "fps": 6, + "mbps": 0.02, + "content_p50": 19.1, + "content_p95": 26.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 4, + "fps": 5, + "mbps": 0.02, + "content_p50": 18.9, + "content_p95": 20.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 5, + "fps": 5, + "mbps": 0.21, + "content_p50": 13.7, + "content_p95": 43.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 6, + "fps": 5, + "mbps": 0.02, + "content_p50": 20.2, + "content_p95": 24.5, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 7, + "fps": 4, + "mbps": 0.01, + "content_p50": 16.4, + "content_p95": 16.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 8, + "fps": 5, + "mbps": 0.02, + "content_p50": 15.2, + "content_p95": 24.6, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 9, + "fps": 5, + "mbps": 0.02, + "content_p50": 7.7, + "content_p95": 17.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 10, + "fps": 5, + "mbps": 0.02, + "content_p50": 6.0, + "content_p95": 26.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 11, + "fps": 7, + "mbps": 0.02, + "content_p50": 19.2, + "content_p95": 46.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 12, + "fps": 4, + "mbps": 0.01, + "content_p50": 21.7, + "content_p95": 22.3, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 13, + "fps": 1, + "mbps": 0.0, + "content_p50": 13.4, + "content_p95": 13.4, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 14, + "fps": 3, + "mbps": 0.01, + "content_p50": 19.0, + "content_p95": 20.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 15, + "fps": 1, + "mbps": 0.0, + "content_p50": 88.1, + "content_p95": 88.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 16, + "fps": 1, + "mbps": 0.25, + "content_p50": 26.8, + "content_p95": 26.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 17, + "fps": 2, + "mbps": 0.01, + "content_p50": 19.1, + "content_p95": 19.2, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 18, + "fps": 1, + "mbps": 0.0, + "content_p50": 175.1, + "content_p95": 175.1, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 19, + "fps": 1, + "mbps": 0.0, + "content_p50": 16.9, + "content_p95": 16.9, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + }, + { + "t": 20, + "fps": 1, + "mbps": 0.0, + "content_p50": 14.8, + "content_p95": 14.8, + "bitrate": 14860800, + "tier": 0, + "w": 1920, + "net": null + } + ], + "adapt": [], + "content_p50": 16.7, + "content_p95": 34.2, + "input_p50": 51.2, + "input_p95": 73.2, + "grades": { + "content_p50": "local", + "content_p95": "local", + "input_p50": "acceptable", + "input_p95": "acceptable" + }, + "cpu": { + "mac_agent_pct": 1.1, + "frame_viewer_pct": 8.0, + "frame_tailscaled_pct": 1.8, + "frame_sshd_pct": 0.5, + "frame_gamescope_pct": 3.7, + "frame_total_pct": 9.1 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 143 Hz", + "show_s": 3.79, + "panel": "valve.steam.desktopgame.2001132791", + "src": "separate:8652" + } + ] +} \ No newline at end of file diff --git a/bench/results/2026-09-28-a3c6e5c-dirty-baseline.json b/bench/results/2026-09-28-a3c6e5c-dirty-baseline.json new file mode 100644 index 0000000..25b9f92 --- /dev/null +++ b/bench/results/2026-09-28-a3c6e5c-dirty-baseline.json @@ -0,0 +1,1124 @@ +{ + "label": "baseline", + "date": "2026-09-28T16:53:43", + "commit": "a3c6e5c+dirty", + "config": { + "quality": "balanced", + "mode": "separate", + "net": "none", + "delay_ms": 0, + "buffer_ms": 250, + "host": "frame", + "ssh_opts": [], + "encoder": "", + "duration_s": 20.0, + "browser_flags": [] + }, + "frame_build": "20260925.6191901", + "mac": "26.5.2", + "headset": ":51.824645 [Info] - 0 - entering standby", + "scenarios": [ + { + "scenario": "test", + "frames_sent": 1248, + "frames_drawn": 1245, + "frames_shown": 809, + "duration_s": 20.8, + "stages_ms": { + "capture": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.0, + "n": 1248 + }, + "queue": { + "p50": 0.0, + "p95": 0.1, + "p99": 0.1, + "n": 1248 + }, + "encode": { + "p50": 4.2, + "p95": 4.8, + "p99": 5.1, + "n": 1248 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 1248 + }, + "network": { + "p50": 4.2, + "p95": 6.9, + "p99": 13.9, + "n": 1248 + }, + "decode": { + "p50": 1.3, + "p95": 2.5, + "p99": 4.6, + "n": 1245 + }, + "draw": { + "p50": 0.4, + "p95": 0.6, + "p99": 1.0, + "n": 1245 + }, + "present": { + "p50": 6.9, + "p95": 15.4, + "p99": 19.5, + "n": 809 + }, + "content": { + "p50": 10.3, + "p95": 13.5, + "p99": 19.6, + "n": 1245 + }, + "content_shown": { + "p50": 17.4, + "p95": 25.6, + "p99": 30.7, + "n": 809 + } + }, + "fps": 60.0, + "fps_shown": 39.0, + "late_pct": 0.96, + "stall_max": 110.3, + "stalls_over_100ms": 1, + "mbps": 0.47, + "keyframes": 2, + "size": "1280x720", + "captured": 1588, + "viewer_never_drawn": 3, + "input_ms": { + "p50": 28.0, + "p95": 34.4, + "p99": 37.6, + "n": 40 + }, + "input_shown_ms": { + "p50": 35.1, + "p95": 49.5, + "p99": 50.5, + "n": 27 + }, + "input_parts_ms": { + "uplink": { + "p50": 11.5, + "p95": 19.7, + "p99": 22.1, + "n": 40 + }, + "mac": { + "p50": 4.7, + "p95": 12.3, + "p99": 16.1, + "n": 40 + }, + "back": { + "p50": 11.2, + "p95": 12.9, + "p99": 16.5, + "n": 40 + } + }, + "inputs": { + "sent": 40, + "seen": 40 + }, + "timeline": [ + { + "t": 0, + "fps": 60, + "mbps": 0.49, + "content_p50": 10.2, + "content_p95": 13.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 1, + "fps": 60, + "mbps": 0.46, + "content_p50": 10.4, + "content_p95": 12.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 2, + "fps": 60, + "mbps": 0.46, + "content_p50": 10.5, + "content_p95": 12.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 3, + "fps": 60, + "mbps": 0.48, + "content_p50": 10.7, + "content_p95": 12.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 4, + "fps": 60, + "mbps": 0.47, + "content_p50": 10.6, + "content_p95": 13.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 5, + "fps": 60, + "mbps": 0.58, + "content_p50": 10.6, + "content_p95": 12.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 6, + "fps": 60, + "mbps": 0.45, + "content_p50": 10.8, + "content_p95": 13.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 7, + "fps": 60, + "mbps": 0.47, + "content_p50": 10.5, + "content_p95": 13.0, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 8, + "fps": 60, + "mbps": 0.47, + "content_p50": 10.5, + "content_p95": 12.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 9, + "fps": 60, + "mbps": 0.46, + "content_p50": 10.3, + "content_p95": 13.6, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 10, + "fps": 60, + "mbps": 0.46, + "content_p50": 10.8, + "content_p95": 14.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 11, + "fps": 58, + "mbps": 0.56, + "content_p50": 10.4, + "content_p95": 61.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 12, + "fps": 60, + "mbps": 0.45, + "content_p50": 10.8, + "content_p95": 12.7, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 13, + "fps": 60, + "mbps": 0.46, + "content_p50": 12.4, + "content_p95": 16.1, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 14, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.8, + "content_p95": 12.2, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 15, + "fps": 60, + "mbps": 0.47, + "content_p50": 9.7, + "content_p95": 12.3, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 16, + "fps": 60, + "mbps": 0.45, + "content_p50": 9.7, + "content_p95": 12.5, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 17, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.7, + "content_p95": 10.9, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 18, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.7, + "content_p95": 11.4, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 19, + "fps": 60, + "mbps": 0.46, + "content_p50": 9.9, + "content_p95": 18.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 20, + "fps": 47, + "mbps": 0.36, + "content_p50": 9.9, + "content_p95": 11.8, + "bitrate": 5529600, + "tier": 0, + "w": 1280, + "net": null + } + ], + "adapt": [], + "content_p50": 10.3, + "content_p95": 13.5, + "input_p50": 28.0, + "input_p95": 34.4, + "grades": { + "content_p50": "local", + "content_p95": "local", + "input_p50": "local", + "input_p95": "local", + "fps": "local", + "late_pct": "local", + "stall_max": "acceptable" + }, + "cpu": { + "mac_agent_pct": 15.4, + "frame_viewer_pct": 51.1, + "frame_tailscaled_pct": 5.3, + "frame_sshd_pct": 0.8, + "frame_gamescope_pct": 5.7, + "frame_total_pct": 15.3 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 140 Hz", + "show_s": 1.87, + "panel": "valve.steam.desktopgame.2001639889", + "src": "test" + }, + { + "scenario": "scroll", + "frames_sent": 690, + "frames_drawn": 683, + "frames_shown": 645, + "duration_s": 20.7, + "stages_ms": { + "capture": { + "p50": -1.2, + "p95": 16.9, + "p99": 24.5, + "n": 690 + }, + "queue": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.1, + "n": 690 + }, + "encode": { + "p50": 4.1, + "p95": 7.0, + "p99": 13.2, + "n": 690 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.3, + "n": 690 + }, + "network": { + "p50": 6.2, + "p95": 14.6, + "p99": 119.2, + "n": 690 + }, + "decode": { + "p50": 3.7, + "p95": 9.4, + "p99": 23.3, + "n": 683 + }, + "draw": { + "p50": 0.4, + "p95": 0.8, + "p99": 1.2, + "n": 683 + }, + "present": { + "p50": 0.8, + "p95": 18.6, + "p99": 23.6, + "n": 645 + }, + "content": { + "p50": 15.0, + "p95": 40.3, + "p99": 112.7, + "n": 683 + }, + "content_shown": { + "p50": 20.2, + "p95": 42.7, + "p99": 106.9, + "n": 645 + } + }, + "fps": 33.0, + "fps_shown": 31.1, + "late_pct": 42.82, + "stall_max": 492.6, + "stalls_over_100ms": 6, + "mbps": 3.11, + "keyframes": 3, + "size": "1280x860", + "captured": 905, + "viewer_never_drawn": 7, + "input_ms": { + "n": 0 + }, + "input_shown_ms": { + "n": 0 + }, + "input_parts_ms": { + "uplink": { + "n": 0 + }, + "mac": { + "n": 0 + }, + "back": { + "n": 0 + } + }, + "inputs": { + "sent": 0, + "seen": 0 + }, + "timeline": [ + { + "t": 0, + "fps": 36, + "mbps": 3.42, + "content_p50": 17.7, + "content_p95": 34.3, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 1, + "fps": 35, + "mbps": 2.8, + "content_p50": 13.6, + "content_p95": 27.7, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 2, + "fps": 31, + "mbps": 2.15, + "content_p50": 12.1, + "content_p95": 34.1, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 3, + "fps": 37, + "mbps": 2.29, + "content_p50": 14.6, + "content_p95": 28.5, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 4, + "fps": 35, + "mbps": 2.11, + "content_p50": 16.1, + "content_p95": 29.3, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 5, + "fps": 33, + "mbps": 2.05, + "content_p50": 10.9, + "content_p95": 27.9, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 6, + "fps": 33, + "mbps": 5.71, + "content_p50": 17.1, + "content_p95": 41.7, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 7, + "fps": 31, + "mbps": 3.3, + "content_p50": 12.1, + "content_p95": 31.3, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 8, + "fps": 24, + "mbps": 4.37, + "content_p50": 26.6, + "content_p95": 236.8, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 9, + "fps": 36, + "mbps": 3.19, + "content_p50": 11.6, + "content_p95": 26.5, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 10, + "fps": 36, + "mbps": 3.53, + "content_p50": 15.2, + "content_p95": 29.6, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 11, + "fps": 29, + "mbps": 3.56, + "content_p50": 11.5, + "content_p95": 36.8, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 12, + "fps": 29, + "mbps": 1.99, + "content_p50": 13.2, + "content_p95": 41.2, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 13, + "fps": 31, + "mbps": 3.4, + "content_p50": 23.9, + "content_p95": 44.5, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 14, + "fps": 32, + "mbps": 2.85, + "content_p50": 14.3, + "content_p95": 37.6, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 15, + "fps": 31, + "mbps": 3.39, + "content_p50": 19.8, + "content_p95": 35.9, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 16, + "fps": 22, + "mbps": 2.74, + "content_p50": 22.5, + "content_p95": 112.7, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 17, + "fps": 44, + "mbps": 3.2, + "content_p50": 14.0, + "content_p95": 31.0, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 18, + "fps": 35, + "mbps": 4.12, + "content_p50": 16.8, + "content_p95": 32.9, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 19, + "fps": 36, + "mbps": 2.63, + "content_p50": 13.9, + "content_p95": 25.2, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 20, + "fps": 27, + "mbps": 1.71, + "content_p50": 17.2, + "content_p95": 40.3, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + } + ], + "adapt": [], + "content_p50": 15.0, + "content_p95": 40.3, + "input_p50": null, + "input_p95": null, + "grades": { + "content_p50": "local", + "content_p95": "acceptable", + "fps": "bad", + "late_pct": "bad", + "stall_max": "bad" + }, + "cpu": { + "mac_agent_pct": 5.1, + "frame_viewer_pct": 44.2, + "frame_tailscaled_pct": 4.8, + "frame_sshd_pct": 0.6, + "frame_gamescope_pct": 4.2, + "frame_total_pct": 13.7 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 139 Hz", + "show_s": 1.82, + "panel": "valve.steam.desktopgame.2001631563", + "src": "separate:6889" + }, + { + "scenario": "type", + "frames_sent": 76, + "frames_drawn": 76, + "frames_shown": 76, + "duration_s": 20.2, + "stages_ms": { + "capture": { + "p50": 0.5, + "p95": 7.2, + "p99": 10.1, + "n": 76 + }, + "queue": { + "p50": 0.0, + "p95": 0.0, + "p99": 0.1, + "n": 76 + }, + "encode": { + "p50": 4.4, + "p95": 9.7, + "p99": 16.7, + "n": 76 + }, + "socket": { + "p50": 0.1, + "p95": 0.2, + "p99": 0.2, + "n": 76 + }, + "network": { + "p50": 5.2, + "p95": 43.2, + "p99": 263.5, + "n": 76 + }, + "decode": { + "p50": 2.1, + "p95": 4.8, + "p99": 13.4, + "n": 76 + }, + "draw": { + "p50": 0.7, + "p95": 1.0, + "p99": 1.1, + "n": 76 + }, + "present": { + "p50": 0.9, + "p95": 1.5, + "p99": 1.8, + "n": 76 + }, + "content": { + "p50": 12.9, + "p95": 54.2, + "p99": 264.6, + "n": 76 + }, + "content_shown": { + "p50": 14.1, + "p95": 55.3, + "p99": 265.9, + "n": 76 + } + }, + "fps": 3.8, + "fps_shown": 3.8, + "late_pct": 96.0, + "stall_max": 1327.9, + "stalls_over_100ms": 68, + "mbps": 0.02, + "keyframes": 2, + "size": "1280x860", + "captured": 125, + "viewer_never_drawn": 0, + "input_ms": { + "p50": 50.1, + "p95": 110.6, + "p99": 165.7, + "n": 65 + }, + "input_shown_ms": { + "p50": 50.8, + "p95": 111.5, + "p99": 166.2, + "n": 65 + }, + "input_parts_ms": { + "uplink": { + "p50": 7.6, + "p95": 18.3, + "p99": 23.2, + "n": 65 + }, + "mac": { + "p50": 30.6, + "p95": 72.4, + "p99": 119.2, + "n": 65 + }, + "back": { + "p50": 12.3, + "p95": 28.1, + "p99": 33.9, + "n": 65 + } + }, + "inputs": { + "sent": 66, + "seen": 65 + }, + "timeline": [ + { + "t": 0, + "fps": 5, + "mbps": 0.01, + "content_p50": 12.3, + "content_p95": 28.1, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 1, + "fps": 4, + "mbps": 0.01, + "content_p50": 16.8, + "content_p95": 33.9, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 2, + "fps": 6, + "mbps": 0.01, + "content_p50": 9.0, + "content_p95": 22.2, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 3, + "fps": 5, + "mbps": 0.01, + "content_p50": 11.5, + "content_p95": 20.6, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 4, + "fps": 5, + "mbps": 0.01, + "content_p50": 9.2, + "content_p95": 13.1, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 5, + "fps": 5, + "mbps": 0.01, + "content_p50": 3.0, + "content_p95": 15.1, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 6, + "fps": 5, + "mbps": 0.14, + "content_p50": 14.0, + "content_p95": 21.1, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 7, + "fps": 5, + "mbps": 0.01, + "content_p50": 9.5, + "content_p95": 16.2, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 8, + "fps": 5, + "mbps": 0.01, + "content_p50": 13.8, + "content_p95": 16.2, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 9, + "fps": 5, + "mbps": 0.01, + "content_p50": 14.0, + "content_p95": 28.1, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 10, + "fps": 5, + "mbps": 0.01, + "content_p50": 12.8, + "content_p95": 31.7, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 11, + "fps": 6, + "mbps": 0.01, + "content_p50": 12.3, + "content_p95": 14.4, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 12, + "fps": 4, + "mbps": 0.01, + "content_p50": 15.2, + "content_p95": 17.2, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 13, + "fps": 1, + "mbps": 0.0, + "content_p50": 18.9, + "content_p95": 18.9, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 14, + "fps": 2, + "mbps": 0.0, + "content_p50": 203.5, + "content_p95": 215.0, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 15, + "fps": 1, + "mbps": 0.0, + "content_p50": 16.1, + "content_p95": 16.1, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 16, + "fps": 2, + "mbps": 0.16, + "content_p50": 15.5, + "content_p95": 35.1, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 17, + "fps": 1, + "mbps": 0.0, + "content_p50": 264.6, + "content_p95": 264.6, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 18, + "fps": 1, + "mbps": 0.0, + "content_p50": 12.9, + "content_p95": 12.9, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 19, + "fps": 2, + "mbps": 0.0, + "content_p50": 11.7, + "content_p95": 54.2, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + }, + { + "t": 20, + "fps": 1, + "mbps": 0.0, + "content_p50": 339.6, + "content_p95": 339.6, + "bitrate": 6604800, + "tier": 0, + "w": 1280, + "net": null + } + ], + "adapt": [], + "content_p50": 12.9, + "content_p95": 54.2, + "input_p50": 50.1, + "input_p95": 110.6, + "grades": { + "content_p50": "local", + "content_p95": "acceptable", + "input_p50": "acceptable", + "input_p95": "acceptable" + }, + "cpu": { + "mac_agent_pct": 1.2, + "frame_viewer_pct": 6.5, + "frame_tailscaled_pct": 1.6, + "frame_sshd_pct": 0.3, + "frame_gamescope_pct": 2.4, + "frame_total_pct": 8.0 + }, + "viewer": "h264 software; ANGLE (Mesa, zink Vulkan 1.4(Turnip Adreno (TM) 750 (MESA_TURNIP)), OpenGL 4.6); raf 141 Hz", + "show_s": 1.43, + "panel": "valve.steam.desktopgame.2001427053", + "src": "separate:7011" + } + ] +} \ No newline at end of file diff --git a/docs/how-the-frame-works.md b/docs/how-the-frame-works.md index 9681da8..bcac736 100644 --- a/docs/how-the-frame-works.md +++ b/docs/how-the-frame-works.md @@ -52,6 +52,7 @@ Lepton (Android 11, podman container "lepton-dev") ← its own panel, app 305600 | Power actions need `sudo`, which asks for the Developer Mode password over SSH. | Frame Control's power buttons | | **SSH server:** OpenSSH 9.7p1. It offers `publickey,password` (keyboard-interactive is off, PAM on) and also asks `userdbctl ssh-authorized-keys` for keys. OpenSSH ≥ 8.8 rejects SHA-1 `ssh-rsa` signatures by default, so a client whose RSA support is SHA-1 only (the Swift library Citadel, for one) can't log in with the RSA key that devkit pairing installs; use ed25519 (**inferred** from OpenSSH defaults). **Verified 2026-09-27**, BUILD_ID 20260922.6101926. | [iphone.md](iphone.md), `ui/frame_connect.py` | | **A Chromium app window on gamescope's `:0` with its own `STEAM_GAME` becomes a panel, even when started over SSH.** `chromium-xr/chrome --ozone-platform=x11 --app=URL --window-size=1280,720` got a 1920×1080 window, and tagging it produced `[Overlays] Created: valve.steam.desktopgame.` in `~/.local/share/Steam/logs/vrwebhelper_systemui.txt`. Chromium XR decoded a 1080p H.264 WebCodecs stream from the Mac at about 60 fps. XTest events sent to `:0` (libXtst through Python ctypes) did not reach the page. The Frame has `libXtst`, `xprop`, `xwininfo`, `curl` and the `C.utf8` locale. **Verified 2026-09-28**, BUILD_ID 20260925.6191901. | [mac-in-headset.md](mac-in-headset.md) | +| **Streaming video into a panel: what the Frame adds.** Chromium XR decodes H.264 in **software** (1920×1290 at about 9 Mbit/s took 4–6 ms per frame); its GL is ANGLE → zink → Turnip on the Adreno 750, and it logs `GetVSyncParametersIfAvailable() failed`. An idle page's `requestAnimationFrame` runs at about 140 Hz; when the headset is worn or woken, SteamVR picks the 4320×2160 @ 144 Hz mode. **An unworn headset throttles panel apps** whatever they draw: a local canvas page ran at 58 fps for about 6 s, then 28, then about 15 once in standby (vrserver logs `entering standby`, with `power.pauseCompositorOnStandby 1`). `vrcmd --handlewakeup` gave 137–144 fps for about 2 s, then 36. So a panel's frame rate and compositor delay can only be measured while it's worn. `tailscaled` runs with `--tun=userspace-networking` and cost about 8% of a core at 9 Mbit/s (0.5% over the LAN address). Wi-Fi power saving is on (`iw … get power_save`). **Verified 2026-09-28**, BUILD_ID 20260925.6191901. | [mac-in-headset.md](mac-in-headset.md#measuring) | | **Tools on the image:** Python 3.12.3, `ffmpeg`, `openssl`, `curl`, `rsync`, `zip`/`unzip`, `flatpak`, `wpctl`, `podman`. **No `adb`.** `steamos` is uid 1000, in `wheel`, and sudoers has `%wheel ALL=(ALL) ALL`, so `sudo -S` takes the Developer Mode password on stdin. **Verified 2026-09-27.** | Running Frame Control's server on the Frame (`FRAME_LOCAL=1`, [iphone.md](iphone.md)) | | **Each Lepton instance is a podman container** named `lepton-steamlaunch-`, labelled with its ADB port (`podman ps --format '{{.Names}} {{.Labels.adb_port}}'`). `podman exec /system/bin/sh -c '…'` runs Android's shell inside it with no adb at all (used for `pidof` and `logcat` by the app tester). Running `wm size`/`wm density` that way is untested. **Verified 2026-09-27.** | `ui/frame_android.py`, the iPhone app's display settings | | **Asleep means off the network.** In standby the Frame stops answering on its LAN address, `frame.local` and Tailscale alike (`Host is down`, `No route to host`, timeouts), and ping fails. It was unreachable for about 2.5 hours until woken. Nothing over SSH can wake it. **Verified 2026-09-27.** | Frame Control's offline banner and retries | diff --git a/docs/mac-in-headset.md b/docs/mac-in-headset.md index 55728bc..142c9e3 100644 --- a/docs/mac-in-headset.md +++ b/docs/mac-in-headset.md @@ -99,9 +99,10 @@ ScreenCaptureKit (one window or display) keyframe instead of showing old frames late. - It falls back to JPEG stills (**Compatible** quality) where H.264 isn't available. -- **Flow control.** When the link backs up, the agent skips capture frames - *before* encoding, so no reference frame goes missing. Once the link drains, - it sends the newest picture. +- **Flow control.** The agent never lets frames queue up anywhere on the + way. It skips capture frames *before* encoding, so no reference frame goes + missing, and it lowers the bitrate, then the frame rate, then the size, to + fit the link (see [Adapting to the network](#adapting-to-the-network)). - **Input.** - A click on a window's panel brings that Mac window to the front (Accessibility API), then clicks at the same point. Double clicks, right @@ -133,6 +134,157 @@ may ask again after an update. | Light | 1280 | 30 | H.264 | Weak Wi-Fi or Tailscale off the LAN | | Compatible | 1280 | 20 | JPEG | A Frame browser without H.264 | +## Measuring + +Every frame carries a sequence number, and the agent records its journey on +the Mac's clock (`Sources/Stats.swift`): + +| Stage | From → to | +|---|---| +| capture | the Mac composited it (ScreenCaptureKit's display time) → the agent got it | +| queue, encode | → encoding started → the encoder finished | +| network | → the viewer received it | +| decode, draw | → WebCodecs decoded it → it was drawn on the page's canvas | +| present | → the page's next animation frame | + +- **Clock sync.** The viewer syncs its clock to the Mac's the way NTP does: + it pings over the stream's own WebSocket and keeps the sample with the + shortest round trip. It then reports, in Mac time, when each frame arrived + (right away, so the agent can pace itself) and when it was decoded and + drawn (in batches every 250 ms). +- **Input.** The first frame captured after a click or key carries that + event's id. So input latency is the viewer's event → injected on the Mac → + the first frame after it → drawn in the headset. +- **Where to see it.** + - `GET /stats` (key required) returns every frame and input record. + - `/status` includes a two-second summary, which Frame Control's card + shows next to each live stream. + - In the headset, add `?stats=1` to the viewer or press + Ctrl+Alt+Shift+S for an overlay. +- **The benchmark.** `scripts/macview-bench.py` runs fixed scenarios on the + real Frame, from the Mac, with nobody wearing the headset: + - **test** is the moving test pattern. + - **scroll** is a Chrome page on its own display scrolling at 240 pt/s, + which gives about 9 Mbit/s of real 1920×1290 video. + - **type** types into a Chrome text box, first fast and then with pauses. + + It writes `bench/results/--