# kanaicasual/vr-gaze-mirror architecture

> Architecture diagram of the GitHub repository kanaicasual/vr-gaze-mirror, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-09-29). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/kanaicasual/vr-gaze-mirror
- Repository: https://github.com/kanaicasual/vr-gaze-mirror

## Overview

VR Gaze Mirror produces a cropped, stabilized headset-eye image, optionally with a gaze indicator, for OBS or a capturable mirror window. OpenXR and SteamVR have separate capture producers that feed a shared frame pipeline and publication protocol; readers consume the finished image. The settings app configures live behavior and provides capture-based crop, edge-finding, profiles, status, and ring-preview tools. Edges reflect the excerpted source and README; some settings-app and runtime internals are unsampled, so documented capabilities are represented without asserting their internal call paths.

Main entry points: [`v2/core/pipeline.cpp`](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/core/pipeline.cpp) (Frame pipeline), [`GazeOverlayApp/MainWindow.xaml.cs`](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/GazeOverlayApp/MainWindow.xaml.cs) (Settings app), [`v2/openvr-helper/main.cpp`](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/openvr-helper/main.cpp) (SteamVR helper), [`v2/protocol/gaze_mirror_protocol.h`](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/protocol/gaze_mirror_protocol.h) (Shared frame protocol)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_producers["Game producers"]
  node_openxr["OpenXR layer<br/>[layer.cpp]"]
  node_steamvr["SteamVR helper<br/>[main.cpp]"]
end

subgraph group_frame["Image pipeline"]
  node_pipeline["Frame pipeline<br/>[pipeline.cpp]"]
  node_renderer["Crop and render<br/>[renderer.cpp]"]
  node_framing["Crop framing<br/>[framing.cpp]"]
  node_ring["Gaze ring<br/>[ring.cpp]"]
  node_settings_core["Runtime settings<br/>[settings.cpp]"]
  node_publisher["Frame publisher<br/>[publisher.cpp]"]
  node_protocol["Shared frame protocol"]
end

subgraph group_readers["Picture readers"]
  node_obs["OBS source"]
  node_window["Mirror window<br/>[main.cpp]"]
  node_viewer["Developer viewer<br/>[main.cpp]"]
end

subgraph group_settings["Settings and framing"]
  node_app["Settings app<br/>[MainWindow.xaml.cs]"]
  node_config["Settings storage<br/>[ConfigFile.cs]"]
  node_crop_tools["Capture and edge tools"]
  node_crop_ui["Crop controls<br/>[MainWindow.Crop.cs]"]
  node_profiles["Game crop profiles"]
  node_mirror_control["Mirror controls"]
  node_status["Layer status<br/>[LayerStatus.cs]"]
end

subgraph group_gaze["Gaze sources"]
  node_gaze_input["OpenXR gaze input<br/>[gaze_input.cpp]"]
  node_runtime_gaze["SteamVR gaze<br/>[runtime_gaze.cpp]"]
  node_raw_gaze["Raw gaze feed<br/>[raw_gaze.cpp]"]
  node_vrchat["VRChat eye fallback<br/>[vrchat_osc.cpp]"]
  node_calibration["Gaze calibration<br/>[calibration.cpp]"]
end

node_user(("Streamer"))
node_game(("VR game"))
node_obs_app(("OBS"))
node_capture_app(("Window capture app"))

node_game -->|"loads layer"| node_openxr
node_openxr -->|"submits frames"| node_pipeline
node_game -->|"SteamVR session"| node_steamvr
node_steamvr -->|"submits frames"| node_pipeline
node_pipeline -.->|"renders image"| node_renderer
node_pipeline -.->|"applies framing"| node_framing
node_pipeline -.->|"draws gaze ring"| node_ring
node_pipeline -->|"reads settings"| node_settings_core
node_pipeline -->|"publishes frame"| node_publisher
node_publisher -->|"writes shared frames"| node_protocol
node_obs -->|"reads shared frames"| node_protocol
node_window -->|"reads shared frames"| node_protocol
node_viewer -->|"reads shared frames"| node_protocol
node_obs_app -->|"captures source"| node_obs
node_capture_app -->|"captures window"| node_window
node_user -->|"configures mirror"| node_app
node_app -->|"loads and saves"| node_config
node_app -.->|"offers capture tools"| node_crop_tools
node_app -->|"offers crop controls"| node_crop_ui
node_app -.->|"offers profiles"| node_profiles
node_app -.->|"controls window"| node_mirror_control
node_app -->|"checks layer status"| node_status
node_app -.->|"previews ring"| node_ring
node_openxr -->|"gets eye gaze"| node_gaze_input
node_steamvr -->|"starts gaze source"| node_runtime_gaze
node_steamvr -->|"starts gaze feed"| node_raw_gaze
node_steamvr -->|"starts OSC fallback"| node_vrchat
node_steamvr -->|"uses calibration"| node_calibration
node_runtime_gaze -.->|"supplies gaze"| node_pipeline
node_raw_gaze -.->|"supplies gaze"| node_pipeline
node_vrchat -.->|"supplies gaze"| node_pipeline

click node_openxr "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/layer/layer.cpp"
click node_steamvr "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/openvr-helper/main.cpp"
click node_pipeline "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/core/pipeline.cpp"
click node_renderer "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/core/renderer.cpp"
click node_framing "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/core/framing.cpp"
click node_ring "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/core/ring.cpp"
click node_settings_core "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/core/settings.cpp"
click node_publisher "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/core/publisher.cpp"
click node_protocol "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/protocol/gaze_mirror_protocol.h"
click node_obs "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/obs-plugin/gaze-mirror-capture.cpp"
click node_window "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/mirror-window/main.cpp"
click node_viewer "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/viewer/main.cpp"
click node_app "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/GazeOverlayApp/MainWindow.xaml.cs"
click node_config "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/GazeOverlayApp/ConfigFile.cs"
click node_crop_tools "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/GazeOverlayApp/MainWindow.Capture.cs"
click node_crop_ui "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/GazeOverlayApp/MainWindow.Crop.cs"
click node_profiles "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/GazeOverlayApp/MainWindow.Profiles.cs"
click node_mirror_control "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/GazeOverlayApp/MirrorWindowControl.cs"
click node_status "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/GazeOverlayApp/LayerStatus.cs"
click node_gaze_input "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/layer/gaze_input.cpp"
click node_runtime_gaze "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/openvr-helper/runtime_gaze.cpp"
click node_raw_gaze "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/openvr-helper/raw_gaze.cpp"
click node_vrchat "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/openvr-helper/vrchat_osc.cpp"
click node_calibration "https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/openvr-helper/calibration.cpp"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_openxr,node_steamvr toneBlue
class node_pipeline,node_renderer,node_framing,node_ring,node_settings_core,node_publisher,node_protocol toneAmber
class node_obs,node_window,node_viewer toneMint
class node_app,node_config,node_crop_tools,node_crop_ui,node_profiles,node_mirror_control,node_status toneRose
class node_gaze_input,node_runtime_gaze,node_raw_gaze,node_vrchat,node_calibration,node_user,node_game,node_obs_app,node_capture_app toneIndigo
```

## Components

### Game producers

- [OpenXR layer](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/layer/layer.cpp): `v2/layer/layer.cpp`
- [SteamVR helper](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/openvr-helper/main.cpp): `v2/openvr-helper/main.cpp`

### Image pipeline

- [Frame pipeline](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/core/pipeline.cpp): `v2/core/pipeline.cpp`
- [Crop and render](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/core/renderer.cpp): `v2/core/renderer.cpp`
- [Crop framing](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/core/framing.cpp): `v2/core/framing.cpp`
- [Gaze ring](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/core/ring.cpp): `v2/core/ring.cpp`
- [Runtime settings](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/core/settings.cpp): `v2/core/settings.cpp`
- [Frame publisher](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/core/publisher.cpp): `v2/core/publisher.cpp`
- [Shared frame protocol](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/protocol/gaze_mirror_protocol.h): `v2/protocol/gaze_mirror_protocol.h`

### Picture readers

- [OBS source](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/obs-plugin/gaze-mirror-capture.cpp): `v2/obs-plugin/gaze-mirror-capture.cpp`
- [Mirror window](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/mirror-window/main.cpp): `v2/mirror-window/main.cpp`
- [Developer viewer](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/viewer/main.cpp): `v2/viewer/main.cpp`

### Settings and framing

- [Settings app](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/GazeOverlayApp/MainWindow.xaml.cs): `GazeOverlayApp/MainWindow.xaml.cs`
- [Settings storage](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/GazeOverlayApp/ConfigFile.cs): `GazeOverlayApp/ConfigFile.cs`
- [Capture and edge tools](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/GazeOverlayApp/MainWindow.Capture.cs): `GazeOverlayApp/MainWindow.Capture.cs`
- [Crop controls](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/GazeOverlayApp/MainWindow.Crop.cs): `GazeOverlayApp/MainWindow.Crop.cs`
- [Game crop profiles](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/GazeOverlayApp/MainWindow.Profiles.cs): `GazeOverlayApp/MainWindow.Profiles.cs`
- [Mirror controls](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/GazeOverlayApp/MirrorWindowControl.cs): `GazeOverlayApp/MirrorWindowControl.cs`
- [Layer status](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/GazeOverlayApp/LayerStatus.cs): `GazeOverlayApp/LayerStatus.cs`

### Gaze sources

- [OpenXR gaze input](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/layer/gaze_input.cpp): `v2/layer/gaze_input.cpp`
- [SteamVR gaze](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/openvr-helper/runtime_gaze.cpp): `v2/openvr-helper/runtime_gaze.cpp`
- [Raw gaze feed](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/openvr-helper/raw_gaze.cpp): `v2/openvr-helper/raw_gaze.cpp`
- [VRChat eye fallback](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/openvr-helper/vrchat_osc.cpp): `v2/openvr-helper/vrchat_osc.cpp`
- [Gaze calibration](https://github.com/kanaicasual/vr-gaze-mirror/blob/main/v2/openvr-helper/calibration.cpp): `v2/openvr-helper/calibration.cpp`

### Other components

- **Streamer**
- **VR game**
- **OBS**
- **Window capture app**

## Connections

- VR game → OpenXR layer: loads layer
- OpenXR layer → Frame pipeline: submits frames (evidence: `v2/layer/layer.cpp`)
- VR game → SteamVR helper: SteamVR session
- SteamVR helper → Frame pipeline: submits frames (evidence: `v2/openvr-helper/main.cpp`)
- Frame pipeline → Crop and render: renders image
- Frame pipeline → Crop framing: applies framing
- Frame pipeline → Gaze ring: draws gaze ring
- Frame pipeline → Runtime settings: reads settings (evidence: `v2/core/pipeline.cpp`)
- Frame pipeline → Frame publisher: publishes frame (evidence: `v2/core/pipeline.cpp`)
- Frame publisher → Shared frame protocol: writes shared frames (evidence: `v2/protocol/gaze_mirror_protocol.h`)
- OBS source → Shared frame protocol: reads shared frames (evidence: `v2/protocol/gaze_mirror_protocol.h`)
- Mirror window → Shared frame protocol: reads shared frames (evidence: `v2/mirror-window/main.cpp`)
- Developer viewer → Shared frame protocol: reads shared frames (evidence: `v2/viewer/main.cpp`)
- OBS → OBS source: captures source
- Window capture app → Mirror window: captures window
- Streamer → Settings app: configures mirror
- Settings app → Settings storage: loads and saves (evidence: `GazeOverlayApp/ConfigFile.cs`)
- Settings app → Capture and edge tools: offers capture tools
- Settings app → Crop controls: offers crop controls (evidence: `GazeOverlayApp/MainWindow.xaml.cs`)
- Settings app → Game crop profiles: offers profiles
- Settings app → Mirror controls: controls window (evidence: `GazeOverlayApp/MainWindow.xaml.cs`)
- Settings app → Layer status: checks layer status (evidence: `GazeOverlayApp/MainWindow.xaml.cs`)
- Settings app → Gaze ring: previews ring
- OpenXR layer → OpenXR gaze input: gets eye gaze (evidence: `v2/layer/layer.cpp`)
- SteamVR helper → SteamVR gaze: starts gaze source (evidence: `v2/openvr-helper/main.cpp`)
- SteamVR helper → Raw gaze feed: starts gaze feed (evidence: `v2/openvr-helper/main.cpp`)
- SteamVR helper → VRChat eye fallback: starts OSC fallback (evidence: `v2/openvr-helper/main.cpp`)
- SteamVR helper → Gaze calibration: uses calibration (evidence: `v2/openvr-helper/main.cpp`)
- SteamVR gaze → Frame pipeline: supplies gaze
- Raw gaze feed → Frame pipeline: supplies gaze
- VRChat eye fallback → Frame pipeline: supplies gaze
