Skip to content

Add per-container OpenComposite support - #1853

Open
TheReal-Flo wants to merge 5 commits into
utkarshdalal:vr-support-perffrom
TheReal-Flo:vr-support-perf
Open

TheReal-Flo wants to merge 5 commits into
utkarshdalal:vr-support-perffrom
TheReal-Flo:vr-support-perf

Conversation

@TheReal-Flo

@TheReal-Flo TheReal-Flo commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor
  • Persist the OpenComposite setting in container configuration
  • Add UI controls and update Windows VR payload setup

Description

Further improve OpenComposite support, not working yet

Recording

Type of Change

  • Bug fix
  • Performance / stability improvement
  • Compatibility improvements
  • Other (requires prior approval)

Checklist

  • If I have access to #code-changes, I have discussed this change there and it has been green-lighted. If I do not have access, I have still provided clear context in this PR. If I skip both, I accept that this change may face delays in review, may not be reviewed at all, or may be closed.
  • This change aligns with the current project scope (core functionality, stability, or performance). If not, it has been explicitly approved beforehand.
  • I have attached a recording of the change.
  • I have read and agree to the contribution guidelines in CONTRIBUTING.md.

Summary by cubic

Adds a per-container OpenComposite toggle so VR compatibility can be enabled per game and persisted in container config. On Windows, background OpenVR clients are rejected to avoid competing sessions, and changes apply on next launch.

  • Persists the toggle in ContainerData via ContainerUtils; WindowsVrRuntimeConfig owns the canonical keys/accessors, covered by a Robolectric test.
  • Exposes the toggle in GraphicsTab for non-default containers with new strings.
  • ImmersiveXrActivity reads/writes VR flags through WindowsVrRuntimeConfig; controller buttons are remapped from the shared Xbox layout to per-hand OpenVR semantics, and gamepad events only go out when a valid X server handler exists; quick-menu and pointer-mode handling unchanged.
  • .gitattributes keeps patch whitespace from breaking, and /app/.container snapshots are git-ignored.
  • Safer OpenComposite INI defaults in WindowsVrPayloadManager: writes initUsingVulkan=false and logAllOpenVRCalls=false since unknown keys are fatal in the pinned payload.
  • Replaces the downloaded OpenComposite binary with a reproducible one: Gradle stageOpenComposite tracks toolchain inputs, and tools/stage-opencomposite.ps1 builds from a pinned commit, applies patches, and exports Vulkan symbols via tools/opencomposite-vulkan-x64.def.
  • Patch highlights: ignore VRApplication_Background on Windows (return VRInitError_Init_NoServerForBackgroundApp), robust exe-name parsing, and removal of ATL dependencies.
  • Adds an androidXr build flavor and a native OpenVR backend (DrvGameNative) so OpenVR titles bypass the OpenXR compositor and submit straight to the native XR transport; a design doc and dev scripts accompany them.
  • ImmersiveSessionOwnership and ImmersiveRuntimeViewModel keep the Wine environment and runtime service alive across the Android XR Home Space handoff, fixing a Quest launch where the flat launcher tore them down mid-start; covered by unit and Robolectric tests.

To enable: toggle OpenComposite in the container's Graphics tab and relaunch the game.

For devs: run tools/stage-opencomposite.ps1 on Windows; needs git, CMake, Visual Studio C++ x64 tools, and an Android NDK. The Gradle task fingerprints the NDK/VS toolchain, so toolchain changes invalidate the cache and rebuild.

Written for commit e894b26. Summary will update on new commits.

Review in cubic

- Persist the OpenComposite setting in container configuration
- Add UI controls and update Windows VR payload setup
@coderabbitai

coderabbitai Bot commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5cd4bc8d-0ac2-4c35-9632-a5cc67f8ca6a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 12 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/src/main/java/app/gamenative/ui/screen/xr/windows/WindowsVrPayloadManager.kt">

<violation number="1" location="app/src/main/java/app/gamenative/ui/screen/xr/windows/WindowsVrPayloadManager.kt:112">
P3: These new OpenComposite options are written identically for every container, and the previous `enableAudio=false` is silently dropped. The per-container config (`WindowsVrRuntimeConfig`) only stores an `openCompositeEnabled` boolean, so the ini flags can't be varied per container/game. Dropping `enableAudio=false` silently re-enables audio redirection to its default for any game where audio was muted by the prior payload. Either drive these options from the container config or keep `enableAudio=false` if disabling redirect was intentional.</violation>
</file>

<file name="tools/stage-opencomposite.ps1">

<violation number="1" location="tools/stage-opencomposite.ps1:51">
P2: Each Windows modern-XR asset preparation now performs a full OpenComposite configure/build, requiring Git, CMake, Visual Studio, and network access on every invocation. Cache the pinned source/build or declare task inputs and outputs so rebuilding occurs only when the commit, patch, or toolchain changes.

(Based on your team's feedback about fragile build dependencies.)</violation>
</file>

Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.

Re-trigger cubic

Comment thread app/.container Outdated
Comment thread docs/xr/gamenative-main-rebuild-agent-prompt.md Outdated
Invoke-Checked { & $libTool.FullName /nologo "/def:$(Join-Path $vulkan 'vulkan-1.def')" /machine:x64 "/out:$(Join-Path $vulkan 'Lib\vulkan-1.lib')" } "Vulkan import library"

Invoke-Checked { cmake -S $source -B $build -G "Visual Studio 17 2022" -A x64 -DERROR_ON_WARNING=OFF "-DOC_VERSION=$commit-gamenative-wine-background" } "OpenComposite configure"
Invoke-Checked { cmake --build $build --config Release --target OCOVR --parallel } "OpenComposite build"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Each Windows modern-XR asset preparation now performs a full OpenComposite configure/build, requiring Git, CMake, Visual Studio, and network access on every invocation. Cache the pinned source/build or declare task inputs and outputs so rebuilding occurs only when the commit, patch, or toolchain changes.

(Based on your team's feedback about fragile build dependencies.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/stage-opencomposite.ps1, line 51:

<comment>Each Windows modern-XR asset preparation now performs a full OpenComposite configure/build, requiring Git, CMake, Visual Studio, and network access on every invocation. Cache the pinned source/build or declare task inputs and outputs so rebuilding occurs only when the commit, patch, or toolchain changes.

(Based on your team's feedback about fragile build dependencies.) </comment>

<file context>
@@ -2,14 +2,56 @@ $ErrorActionPreference = "Stop"
+Invoke-Checked { & $libTool.FullName /nologo "/def:$(Join-Path $vulkan 'vulkan-1.def')" /machine:x64 "/out:$(Join-Path $vulkan 'Lib\vulkan-1.lib')" } "Vulkan import library"
+
+Invoke-Checked { cmake -S $source -B $build -G "Visual Studio 17 2022" -A x64 -DERROR_ON_WARNING=OFF "-DOC_VERSION=$commit-gamenative-wine-background" } "OpenComposite configure"
+Invoke-Checked { cmake --build $build --config Release --target OCOVR --parallel } "OpenComposite build"
+$built = Join-Path $build "bin\Release\vrclient_x64.dll"
+if (!(Test-Path -LiteralPath $built -PathType Leaf)) { throw "OpenComposite build output is missing" }
</file context>

Comment thread app/.container Outdated
writeIfChanged(owner, "2\n".toByteArray())
writeIfChanged(target, adapter)
writeIfChanged(ini, "enableAudio=false\nlogEnabled=true\n".toByteArray())
writeIfChanged(ini, "initUsingVulkan=false\nlogAllOpenVRCalls=false\n".toByteArray())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: These new OpenComposite options are written identically for every container, and the previous enableAudio=false is silently dropped. The per-container config (WindowsVrRuntimeConfig) only stores an openCompositeEnabled boolean, so the ini flags can't be varied per container/game. Dropping enableAudio=false silently re-enables audio redirection to its default for any game where audio was muted by the prior payload. Either drive these options from the container config or keep enableAudio=false if disabling redirect was intentional.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/ui/screen/xr/windows/WindowsVrPayloadManager.kt, line 112:

<comment>These new OpenComposite options are written identically for every container, and the previous `enableAudio=false` is silently dropped. The per-container config (`WindowsVrRuntimeConfig`) only stores an `openCompositeEnabled` boolean, so the ini flags can't be varied per container/game. Dropping `enableAudio=false` silently re-enables audio redirection to its default for any game where audio was muted by the prior payload. Either drive these options from the container config or keep `enableAudio=false` if disabling redirect was intentional.</comment>

<file context>
@@ -109,7 +109,7 @@ class WindowsVrPayloadManager(
             writeIfChanged(owner, "2\n".toByteArray())
             writeIfChanged(target, adapter)
-            writeIfChanged(ini, "enableAudio=false\nlogEnabled=true\n".toByteArray())
+            writeIfChanged(ini, "initUsingVulkan=false\nlogAllOpenVRCalls=false\n".toByteArray())
             openCompositeDirectories += directory
             diagnostics.record("opencomposite", "installed path=${target.path}")
</file context>
Suggested change
writeIfChanged(ini, "initUsingVulkan=false\nlogAllOpenVRCalls=false\n".toByteArray())
writeIfChanged(ini, "enableAudio=false\ninitUsingVulkan=false\nlogAllOpenVRCalls=false\n".toByteArray())

Comment thread app/src/main/java/app/gamenative/utils/ContainerUtils.kt Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 12 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread app/build.gradle.kts Outdated
Comment thread app/build.gradle.kts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 existing issue remains and no new issues found across 2 files (changes from recent commits).

Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.
Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread tools/stage-opencomposite.ps1 Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10 issues found across 25 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/src/main/java/app/gamenative/ui/screen/xr/ImmersiveSessionOwnership.kt">

<violation number="1" location="app/src/main/java/app/gamenative/ui/screen/xr/ImmersiveSessionOwnership.kt:46">
P2: When an XR activity instance has no `app_id`, `release(null)` clears the current owner's state because `null` is treated as a wildcard. A malformed or externally launched instance can therefore make `MainActivity` tear down another game's environment during its handoff; require a non-null matching app ID before releasing ownership.</violation>
</file>

<file name="tools/patches/opencomposite-gamenative-native-backend.patch">

<violation number="1" location="tools/patches/opencomposite-gamenative-native-backend.patch:102">
P1: When `GAMENATIVE_BACKEND` is enabled, the new backend still creates and owns a Windows OpenXR session before using the GameNative transport. Remove the `DrvOpenXR::CreateOpenXRBackend` compatibility-session dependency and provide input/session handling that does not create an OpenXR session.</violation>
</file>

<file name="app/src/main/java/app/gamenative/MainActivity.kt">

<violation number="1" location="app/src/main/java/app/gamenative/MainActivity.kt:384">
P2: If the launcher is destroyed before `ImmersiveXrActivity.onCreate` runs, this condition suppresses `ActivityDestroyed` cleanup while ownership remains `LAUNCHING`. Clear abandoned launch ownership on task removal or with a launch timeout.</violation>
</file>

<file name="tools/prepare-native-openvr-dev.ps1">

<violation number="1" location="tools/prepare-native-openvr-dev.ps1:37">
P2: When an existing checkout’s `origin` or `upstream` points to a fork, this script fetches the pinned commit from that fork instead of the declared upstream repository. Set `upstream`’s fetch URL to `$upstream` before disabling push.</violation>
</file>

<file name="app/build.gradle.kts">

<violation number="1" location="app/build.gradle.kts:299">
P1: On non-Windows builds, the `androidXr` APK omits `libxrimmersive.so`, so launching `ImmersiveXrActivity` fails with `UnsatisfiedLinkError`. Include `src/modernXr/jniLibs` in this source set or otherwise package the prebuilt library.</violation>
</file>

<file name="app/src/main/windows/openxr_runtime/gamenative_openxr_runtime.c">

<violation number="1" location="app/src/main/windows/openxr_runtime/gamenative_openxr_runtime.c:2188">
P2: When aim tracking is valid independently of grip tracking, `xrLocateSpace` returns the aim pose with velocity flags derived from the grip pose. Track velocity per pose, or clear the velocity flags when the selected pose has no corresponding velocity.</violation>
</file>

<file name="app/src/main/windows/openvr_client/DrvGameNative.cpp">

<violation number="1" location="app/src/main/windows/openvr_client/DrvGameNative.cpp:125">
P2: When an OpenVR submission uses inverted V bounds, `store()` normalizes the range and copies it without flipping the pixels, so the eye image is submitted upside down. Apply a vertical transform during the copy or preserve the inversion through the submission path instead of treating it as informational.</violation>

<violation number="2" location="app/src/main/windows/openvr_client/DrvGameNative.cpp:485">
P2: After one successful view lookup, a later lookup failure leaves the previous validity flag and poses active. Clear the current-frame validity on failure and submit only when this frame’s view snapshot succeeded.</violation>
</file>

<file name="app/src/test/java/app/gamenative/AndroidXrLaunchTest.kt">

<violation number="1" location="app/src/test/java/app/gamenative/AndroidXrLaunchTest.kt:32">
P3: This test only covers the true path for the new openxr feature branch. Add a companion negative case that stubs both system features to false and asserts `isHeadset` returns false, so an always-true regression or a broken short-circuit would be caught.</violation>
</file>

<file name="tools/build-native-openvr-dev.ps1">

<violation number="1" location="tools/build-native-openvr-dev.ps1:11">
P3: Developer builds into app\build\native-openvr-dev are never cleaned between runs, so a stale CMake cache and incremental object files persist across NDK/compiler/commit changes. The managed (CI) path in stage-opencomposite.ps1 explicitly wipes its build directory before each build, but this wrapper always passes a non-empty -SourceDirectory, which forces stage into its developer branch and bypasses that cleanup. Clear the directory (or have stage do it for the same app/build path) before configuring so repeat builds don't silently produce stale OpenComposite payloads.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic


// TODO seperate this from the rest of dllmain
+ #ifdef GAMENATIVE_BACKEND
+ IBackend* backend = DrvGameNative::CreateGameNativeBackend(pStartupInfo);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When GAMENATIVE_BACKEND is enabled, the new backend still creates and owns a Windows OpenXR session before using the GameNative transport. Remove the DrvOpenXR::CreateOpenXRBackend compatibility-session dependency and provide input/session handling that does not create an OpenXR session.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/patches/opencomposite-gamenative-native-backend.patch, line 102:

<comment>When `GAMENATIVE_BACKEND` is enabled, the new backend still creates and owns a Windows OpenXR session before using the GameNative transport. Remove the `DrvOpenXR::CreateOpenXRBackend` compatibility-session dependency and provide input/session handling that does not create an OpenXR session.</comment>

<file context>
@@ -0,0 +1,108 @@
+ 
+ 	// TODO seperate this from the rest of dllmain
++	#ifdef GAMENATIVE_BACKEND
++	IBackend* backend = DrvGameNative::CreateGameNativeBackend(pStartupInfo);
++	#else
+ 	IBackend* backend = DrvOpenXR::CreateOpenXRBackend(pStartupInfo);
</file context>

Comment thread app/build.gradle.kts
srcDirs("src/modern/assets", "src/main/assets", "build/generated/xrPayload/modernXr")
}
jniLibs {
setSrcDirs(listOf("src/modern/jniLibs", "build/generated/xrNative/modernXr"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: On non-Windows builds, the androidXr APK omits libxrimmersive.so, so launching ImmersiveXrActivity fails with UnsatisfiedLinkError. Include src/modernXr/jniLibs in this source set or otherwise package the prebuilt library.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/build.gradle.kts, line 299:

<comment>On non-Windows builds, the `androidXr` APK omits `libxrimmersive.so`, so launching `ImmersiveXrActivity` fails with `UnsatisfiedLinkError`. Include `src/modernXr/jniLibs` in this source set or otherwise package the prebuilt library.</comment>

<file context>
@@ -274,6 +288,17 @@ android {
+                srcDirs("src/modern/assets", "src/main/assets", "build/generated/xrPayload/modernXr")
+            }
+            jniLibs {
+                setSrcDirs(listOf("src/modern/jniLibs", "build/generated/xrNative/modernXr"))
+            }
+        }
</file context>
Suggested change
setSrcDirs(listOf("src/modern/jniLibs", "build/generated/xrNative/modernXr"))
setSrcDirs(listOf("src/modern/jniLibs", "src/modernXr/jniLibs", "build/generated/xrNative/modernXr"))

fun release(appId: String?) {
var released = false
synchronized(lock) {
if (appId == null || state.appId == appId) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When an XR activity instance has no app_id, release(null) clears the current owner's state because null is treated as a wildcard. A malformed or externally launched instance can therefore make MainActivity tear down another game's environment during its handoff; require a non-null matching app ID before releasing ownership.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/ui/screen/xr/ImmersiveSessionOwnership.kt, line 46:

<comment>When an XR activity instance has no `app_id`, `release(null)` clears the current owner's state because `null` is treated as a wildcard. A malformed or externally launched instance can therefore make `MainActivity` tear down another game's environment during its handoff; require a non-null matching app ID before releasing ownership.</comment>

<file context>
@@ -0,0 +1,70 @@
+    fun release(appId: String?) {
+        var released = false
+        synchronized(lock) {
+            if (appId == null || state.appId == appId) {
+                state = Snapshot()
+                released = true
</file context>
Suggested change
if (appId == null || state.appId == appId) {
if (appId != null && state.appId == appId) {

// emit before super so Compose DisposableEffects (which unregister
// listeners during super.onDestroy's lifecycle transition) still fire
if (!isChangingConfigurations) {
if (ImmersiveSessionOwnership.shouldLauncherHandleDestruction(isChangingConfigurations)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: If the launcher is destroyed before ImmersiveXrActivity.onCreate runs, this condition suppresses ActivityDestroyed cleanup while ownership remains LAUNCHING. Clear abandoned launch ownership on task removal or with a launch timeout.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/MainActivity.kt, line 384:

<comment>If the launcher is destroyed before `ImmersiveXrActivity.onCreate` runs, this condition suppresses `ActivityDestroyed` cleanup while ownership remains `LAUNCHING`. Clear abandoned launch ownership on task removal or with a launch timeout.</comment>

<file context>
@@ -372,7 +381,7 @@ class MainActivity : ComponentActivity() {
         // emit before super so Compose DisposableEffects (which unregister
         // listeners during super.onDestroy's lifecycle transition) still fire
-        if (!isChangingConfigurations) {
+        if (ImmersiveSessionOwnership.shouldLauncherHandleDestruction(isChangingConfigurations)) {
             PluviaApp.events.emit(AndroidEvent.ActivityDestroyed)
 
</file context>

if (!(@(& git -c "safe.directory=$source" -C $source remote) -contains "upstream")) {
Invoke-Checked { git -c "safe.directory=$source" -C $source remote add upstream $upstream } "OpenComposite upstream remote"
}
Invoke-Checked { git -c "safe.directory=$source" -C $source remote set-url --push upstream DISABLED } "OpenComposite upstream push protection"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When an existing checkout’s origin or upstream points to a fork, this script fetches the pinned commit from that fork instead of the declared upstream repository. Set upstream’s fetch URL to $upstream before disabling push.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/prepare-native-openvr-dev.ps1, line 37:

<comment>When an existing checkout’s `origin` or `upstream` points to a fork, this script fetches the pinned commit from that fork instead of the declared upstream repository. Set `upstream`’s fetch URL to `$upstream` before disabling push.</comment>

<file context>
@@ -0,0 +1,71 @@
+if (!(@(& git -c "safe.directory=$source" -C $source remote) -contains "upstream")) {
+    Invoke-Checked { git -c "safe.directory=$source" -C $source remote add upstream $upstream } "OpenComposite upstream remote"
+}
+Invoke-Checked { git -c "safe.directory=$source" -C $source remote set-url --push upstream DISABLED } "OpenComposite upstream push protection"
+
+& git -c "safe.directory=$source" -C $source cat-file -e "$commit^{commit}" 2>$null
</file context>

* legacy actions were synced, which placed both controllers at the stage origin. */
if (!gn_hands[hand].active) return 0;
if (comp == GN_COMP_AIM_POSE) {
if (!gn_hands[hand].aim_active) return 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When aim tracking is valid independently of grip tracking, xrLocateSpace returns the aim pose with velocity flags derived from the grip pose. Track velocity per pose, or clear the velocity flags when the selected pose has no corresponding velocity.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/windows/openxr_runtime/gamenative_openxr_runtime.c, line 2188:

<comment>When aim tracking is valid independently of grip tracking, `xrLocateSpace` returns the aim pose with velocity flags derived from the grip pose. Track velocity per pose, or clear the velocity flags when the selected pose has no corresponding velocity.</comment>

<file context>
@@ -2176,8 +2180,19 @@ static int gn_space_absolute_pose(
+         * legacy actions were synced, which placed both controllers at the stage origin. */
+        if (!gn_hands[hand].active) return 0;
+        if (comp == GN_COMP_AIM_POSE) {
+            if (!gn_hands[hand].aim_active) return 0;
+            gn_write_pose(&tracked, gn_hands[hand].aim);
+        } else if (comp == GN_COMP_GRIP_POSE) {
</file context>

if (!transport_.locateViews(views_.data(), &flags)) {
OOVR_LOG("DrvGameNative: view snapshot unavailable after frame wait");
} else {
viewFlags_ = flags;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: After one successful view lookup, a later lookup failure leaves the previous validity flag and poses active. Clear the current-frame validity on failure and submit only when this frame’s view snapshot succeeded.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/windows/openvr_client/DrvGameNative.cpp, line 485:

<comment>After one successful view lookup, a later lookup failure leaves the previous validity flag and poses active. Clear the current-frame validity on failure and submit only when this frame’s view snapshot succeeded.</comment>

<file context>
@@ -0,0 +1,548 @@
+        if (!transport_.locateViews(views_.data(), &flags)) {
+            OOVR_LOG("DrvGameNative: view snapshot unavailable after frame wait");
+        } else {
+            viewFlags_ = flags;
+        }
+    }
</file context>

sourceBox.right = static_cast<UINT>(std::ceil(uMax * sourceDesc.Width));
sourceBox.top = static_cast<UINT>(std::floor(vMin * sourceDesc.Height));
sourceBox.bottom = static_cast<UINT>(std::ceil(vMax * sourceDesc.Height));
verticallyInverted = bounds->vMin > bounds->vMax;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When an OpenVR submission uses inverted V bounds, store() normalizes the range and copies it without flipping the pixels, so the eye image is submitted upside down. Apply a vertical transform during the copy or preserve the inversion through the submission path instead of treating it as informational.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/windows/openvr_client/DrvGameNative.cpp, line 125:

<comment>When an OpenVR submission uses inverted V bounds, `store()` normalizes the range and copies it without flipping the pixels, so the eye image is submitted upside down. Apply a vertical transform during the copy or preserve the inversion through the submission path instead of treating it as informational.</comment>

<file context>
@@ -0,0 +1,548 @@
+            sourceBox.right = static_cast<UINT>(std::ceil(uMax * sourceDesc.Width));
+            sourceBox.top = static_cast<UINT>(std::floor(vMin * sourceDesc.Height));
+            sourceBox.bottom = static_cast<UINT>(std::ceil(vMax * sourceDesc.Height));
+            verticallyInverted = bounds->vMin > bounds->vMax;
+        }
+        if (sourceBox.right <= sourceBox.left || sourceBox.bottom <= sourceBox.top) return false;
</file context>

whenever(packageManager.hasSystemFeature("android.hardware.vr.headtracking")).thenReturn(false)
whenever(packageManager.hasSystemFeature("android.software.xr.api.openxr")).thenReturn(true)

assertTrue(MainActivity.isHeadset(context))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This test only covers the true path for the new openxr feature branch. Add a companion negative case that stubs both system features to false and asserts isHeadset returns false, so an always-true regression or a broken short-circuit would be caught.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/test/java/app/gamenative/AndroidXrLaunchTest.kt, line 32:

<comment>This test only covers the true path for the new openxr feature branch. Add a companion negative case that stubs both system features to false and asserts `isHeadset` returns false, so an always-true regression or a broken short-circuit would be caught.</comment>

<file context>
@@ -0,0 +1,53 @@
+        whenever(packageManager.hasSystemFeature("android.hardware.vr.headtracking")).thenReturn(false)
+        whenever(packageManager.hasSystemFeature("android.software.xr.api.openxr")).thenReturn(true)
+
+        assertTrue(MainActivity.isHeadset(context))
+    }
+
</file context>

$repository = Split-Path -Parent $PSScriptRoot
$workspace = Split-Path -Parent $repository
$source = if ($SourceDirectory) { [IO.Path]::GetFullPath($SourceDirectory) } else { Join-Path $workspace "OpenComposite-gamenative" }
$build = if ($BuildDirectory) { [IO.Path]::GetFullPath($BuildDirectory) } else { Join-Path $repository "app\build\native-openvr-dev" }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Developer builds into app\build\native-openvr-dev are never cleaned between runs, so a stale CMake cache and incremental object files persist across NDK/compiler/commit changes. The managed (CI) path in stage-opencomposite.ps1 explicitly wipes its build directory before each build, but this wrapper always passes a non-empty -SourceDirectory, which forces stage into its developer branch and bypasses that cleanup. Clear the directory (or have stage do it for the same app/build path) before configuring so repeat builds don't silently produce stale OpenComposite payloads.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/build-native-openvr-dev.ps1, line 11:

<comment>Developer builds into app\build\native-openvr-dev are never cleaned between runs, so a stale CMake cache and incremental object files persist across NDK/compiler/commit changes. The managed (CI) path in stage-opencomposite.ps1 explicitly wipes its build directory before each build, but this wrapper always passes a non-empty -SourceDirectory, which forces stage into its developer branch and bypasses that cleanup. Clear the directory (or have stage do it for the same app/build path) before configuring so repeat builds don't silently produce stale OpenComposite payloads.</comment>

<file context>
@@ -0,0 +1,19 @@
+$repository = Split-Path -Parent $PSScriptRoot
+$workspace = Split-Path -Parent $repository
+$source = if ($SourceDirectory) { [IO.Path]::GetFullPath($SourceDirectory) } else { Join-Path $workspace "OpenComposite-gamenative" }
+$build = if ($BuildDirectory) { [IO.Path]::GetFullPath($BuildDirectory) } else { Join-Path $repository "app\build\native-openvr-dev" }
+$output = if ($Destination) { [IO.Path]::GetFullPath($Destination) } else { Join-Path $build "opencomposite_x64.dll" }
+$stage = Join-Path $PSScriptRoot "stage-opencomposite.ps1"
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant