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:
- PiHalUtils.setVrWorkMode(1) — via reflection
- Set window flags (KEEP_SCREEN_ON | TURN_SCREEN_ON | SHOW_WHEN_LOCKED | FULLSCREEN)
- Acquire PowerManager wake lock
- PxrApi.sxrSetTrackingMode() + PxrServiceApi.SetTrackingMode()
- PvrServiceClient.Connect() + GetInterface() + ResumeVRMode()
- HideSystemUI(“vr_first_frame_ready”) + HideSystemUI(“6dofWarning_low_quality”)
- Sleep 250ms
- PxrApi.sxrInitialize(Context)
- Capture NativeActivity surface (4120x2060)
- Create EGL window context (GLES 3.2, Qualcomm Adreno 650)
- sxrBeginXr(Context, sxrBeginParams) — colorSpace=kColorSpaceSRGB, perf=kPerfMaximum, mainThreadId=0, optionFlags=1
- PxrApi.startVsync()
- PxrApi.getVsyncOffsetNanos() → 1,000,000
- PvrServiceClient.SetDisplayInterruptCapture(VSYNC, 1)
- PxrApi.enablePresentation(true, Context)
- nativeVsync(timestamp) pumped every frame
- 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.