Crystal OG native XR app — frames submitted successfully but display stays dark

We’re building a native Android VR application on Crystal OG using the Pimax XR SDK (com.pimax.pxrapi.PxrApi).

What’s working:

  • sxrInitialize() succeeds
  • sxrBeginXr() succeeds
  • sxrSubmitFrame() is called every ~16ms with valid predicted display times (~29.8s)
  • No GL errors, no Java exceptions
  • firstPresentationFrameComplete() succeeds
  • enablePresentation(true) is called
  • Our NativeActivity holds foreground (mResumedActivity = our app)
  • Window shows HAS_DRAWN state
  • Display power stays interactive (wake lock held)

The problem:
The display stays completely dark. No errors in logcat — the compositor just doesn’t warp to our submitted frames.

Our call sequence:

  1. PiHalUtils.setVrWorkMode(1) — via reflection
  2. Set window flags (KEEP_SCREEN_ON | TURN_SCREEN_ON | SHOW_WHEN_LOCKED | FULLSCREEN)
  3. Acquire PowerManager wake lock
  4. PxrApi.sxrSetTrackingMode() + PxrServiceApi.SetTrackingMode()
  5. PvrServiceClient.Connect() + GetInterface() + ResumeVRMode()
  6. HideSystemUI(“vr_first_frame_ready”) + HideSystemUI(“6dofWarning_low_quality”)
  7. Sleep 250ms
  8. PxrApi.sxrInitialize(Context)
  9. Capture NativeActivity surface (4120x2060)
  10. Create EGL window context (GLES 3.2, Qualcomm Adreno 650)
  11. sxrBeginXr(Context, sxrBeginParams) — colorSpace=kColorSpaceSRGB, perf=kPerfMaximum, mainThreadId=0, optionFlags=1
  12. PxrApi.startVsync()
  13. PxrApi.getVsyncOffsetNanos() → 1,000,000
  14. PvrServiceClient.SetDisplayInterruptCapture(VSYNC, 1)
  15. PxrApi.enablePresentation(true, Context)
  16. nativeVsync(timestamp) pumped every frame
  17. Frame loop: sxrGetPredictedDisplayTimePipelined(1) → sxrGetPredictedHeadPose(time) → glFinish() → sxrSubmitFrame(Context, sxrFrameParams)

Frame params:

  • frameIndex: incrementing
  • minVsyncs: 1
  • fieldOfView: max(fovX, fovY) = ~1.57 rad
  • warpType: kSimple
  • frameOptions: 0
  • renderLayers[0]: layerFlags=10, imageType=kTypeTexture, eyeMask=kEyeMaskLeft, imageHandle=texture_id (GL texture from AHardwareBuffer)
  • renderLayers[1]: same for right eye

Textures:

  • AHardwareBuffer-backed (R8G8B8A8_UNORM)
  • GPU_FRAMEBUFFER | GPU_SAMPLED_IMAGE | COMPOSER_OVERLAY usage
  • EGLImage created via eglCreateImageKHR(display, null, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, preserved=1)
  • GL texture via glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage)
  • Framebuffer attached to texture
  • Cleared with glClearColor() each frame (alternating orange/cyan test colors)
  • Pixel readback confirms correct colors are in the textures

Display info from sxrGetDeviceInfo():

  • display: 4120x2060
  • eye: 2060x2060
  • refresh: 72Hz
  • FOV: 1.56 x 1.57 rad
  • warpMeshType: kMeshTypeColumsLtoR

What we’ve tried:

  • UV map companion textures via vulkanInfo.memSize — no change
  • Explicit sxrBeginEye/sxrEndEye per-eye calls — no change
  • Various layerFlags values (0, 2, 10) — no change
  • Different texture types (kTypeTexture, kTypeImage) — no change
  • Submitting via TextureId vs EGLImage handle vs AHardwareBuffer ptr — no change

Question:
Is there a missing call or configuration required on Crystal OG to make the display active? Is there a required call to a PxrPresentationManager or a specific sxrBeginParams.optionFlags value? Are we missing a PvrServiceClient call (like EnableDisplay or similar)?

Any guidance would be appreciated.