From 25b9822b45ff86e15e08372a5652e67f6fc809bc Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Wed, 22 Apr 2026 13:14:39 +0200 Subject: [PATCH 1/2] fix(android): correct color output and unblock recording throughput The encoder-input EGL surface was declared as sRGB, causing Tensor/Mali drivers to gamma-linearize every framebuffer write and the encoder to emit color_transfer=iec61966-2-1 on the MP4. The result: mis-tagged pixels unrepairable by remux, and GPU throughput throttled to ~10 fps on Mali-G710. Exynos devices ignored the flag and worked by accident. Drop the EGL_GL_COLORSPACE_SRGB_KHR attribute; the encoder now emits BT.709 SDR consistently across SoCs and per-write gamma is gone. Under VROColorRenderingMode::Linear (HDR) the scene framebuffer holds linear RGB values that used to rely on the driver's implicit gamma from the dropped flag. Run the existing gamma post-process explicitly on the recording path for that mode; non-linear mode blits through. Fix a dormant bug in the gamma post-process while there: the samplers list named "hdr_texture" but the shader sampled from "srgb_texture", leaving the sampler unbound and producing black frames. The recording path is the first caller to exercise it in HDR. --- .../src/main/cpp/VROAVRecorderAndroid.cpp | 22 +++++++++++++++---- .../core/internal/MediaRecorderSurface.java | 12 +++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/android/sharedCode/src/main/cpp/VROAVRecorderAndroid.cpp b/android/sharedCode/src/main/cpp/VROAVRecorderAndroid.cpp index 55565309e..a53378f6e 100644 --- a/android/sharedCode/src/main/cpp/VROAVRecorderAndroid.cpp +++ b/android/sharedCode/src/main/cpp/VROAVRecorderAndroid.cpp @@ -86,7 +86,16 @@ bool VROAVRecorderAndroid::onRenderedFrameTexture(std::shared_ptrsetViewport({0, 0, input->getWidth(), input->getHeight()}); driver->bindRenderTarget(_recorderDisplay, VRORenderTargetUnbindOp::Invalidate); - _recordingPostProcess->blit({ input->getTexture(0) }, driver); + + // In linear color mode (HDR enabled) the scene framebuffer holds linear RGB values; + // gamma-encode them before the encoder reads the surface, otherwise recorded video + // is noticeably darker than the live view. In non-linear mode the framebuffer is + // already sRGB-encoded and can be blit straight through. + if (driver->getColorRenderingMode() == VROColorRenderingMode::Linear) { + getGammaPostProcess(driver)->blit({ input->getTexture(0) }, driver); + } else { + _recordingPostProcess->blit({ input->getTexture(0) }, driver); + } } if (_scheduledScreenShot) { @@ -156,11 +165,16 @@ std::shared_ptr VROAVRecorderAndroid::bindScreenshotLDRTarget(i std::shared_ptr VROAVRecorderAndroid::getGammaPostProcess(std::shared_ptr driver) { if (!_gammaPostProcess) { - std::vector samplers = { "hdr_texture", "tone_mapping_mask" }; + // The sampler name in `samplers` must match the uniform name in the shader + // code below — VROImagePostProcess uses `samplers` to locate and bind the + // input texture(s) to the corresponding shader uniform(s). A name mismatch + // leaves the sampler unbound and the shader reads from texture unit 0 with + // no texture attached, producing a black frame. + std::vector samplers = { "hdr_texture" }; std::vector code = { "const highp float gamma = 2.2;", - "uniform sampler2D srgb_texture;", - "highp vec4 srgb_color = texture(srgb_texture, v_texcoord);", + "uniform sampler2D hdr_texture;", + "highp vec4 srgb_color = texture(hdr_texture, v_texcoord);", "highp vec3 gamma_color = pow(srgb_color.xyz, vec3(1.0 / gamma));", "frag_color = vec4(gamma_color, srgb_color.a);", }; diff --git a/android/sharedCode/src/main/java/com/viro/core/internal/MediaRecorderSurface.java b/android/sharedCode/src/main/java/com/viro/core/internal/MediaRecorderSurface.java index e25f30071..6eda0a5b5 100644 --- a/android/sharedCode/src/main/java/com/viro/core/internal/MediaRecorderSurface.java +++ b/android/sharedCode/src/main/java/com/viro/core/internal/MediaRecorderSurface.java @@ -105,12 +105,14 @@ public boolean eglSetup() { return false; } - // Finally, create a window egl surface with the shared eglContext and mRecorderSurface. - final int EGL_GL_COLORSPACE_KHR = 0x309D; - final int EGL_GL_COLORSPACE_SRGB_KHR = 0x3089; + // Create a window egl surface with the shared eglContext and mRecorderSurface. + // Only EGL_NONE is passed — no EGL_GL_COLORSPACE_* attribute. On Tensor/Mali drivers, + // tagging the encoder-input surface as sRGB causes the driver to gamma-linearize + // every framebuffer write and the encoder to emit color_transfer=iec61966-2-1, + // producing mis-tagged pixels that no post-hoc remux can repair and throttling GPU + // throughput. With no colorspace attribute the encoder emits plain BT.709 SDR + // consistently across SoCs. final int[] surfaceAttribs = { - EGL_GL_COLORSPACE_KHR, - EGL_GL_COLORSPACE_SRGB_KHR, EGL14.EGL_NONE }; mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], mRecorderSurface, From af5c2ebe8645628368e98cb555e62bc203697f03 Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Wed, 22 Apr 2026 13:15:01 +0200 Subject: [PATCH 2/2] fix(android/arcore): guard createLocalGPSAnchor behind RVCCA_AVAILABLE The function takes a ReactVisionCCA::RVCCAGeospatialProvider* and dereferences it to call computeArPosition, but RVCCAGeospatialProvider is only forward-declared when RVCCA_AVAILABLE=0, so the definition fails to compile in any build without the proprietary libreactvisioncca headers present. All call sites are already inside #if RVCCA_AVAILABLE blocks; wrap the definition to match. --- android/sharedCode/src/main/cpp/arcore/VROARSessionARCore.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/android/sharedCode/src/main/cpp/arcore/VROARSessionARCore.cpp b/android/sharedCode/src/main/cpp/arcore/VROARSessionARCore.cpp index 522851679..d706eee4d 100644 --- a/android/sharedCode/src/main/cpp/arcore/VROARSessionARCore.cpp +++ b/android/sharedCode/src/main/cpp/arcore/VROARSessionARCore.cpp @@ -1637,6 +1637,7 @@ void VROARSessionARCore::checkVPSAvailability(double latitude, double longitude, // acquireNewAnchor. No VPS required — placed by GPS + compass + VIO. // AR placement math is delegated to RVCCAGeospatialProvider::computeArPosition() // (proprietary algorithm inside libreactvisioncca — not exposed in open-source virocore). +#if RVCCA_AVAILABLE static std::shared_ptr createLocalGPSAnchor( arcore::Session *session, const VROGeospatialPose &devicePose, @@ -1682,6 +1683,7 @@ static std::shared_ptr createLocalGPSAnchor( arSession->addAnchor(vAnchor); return geoAnchor; } +#endif // RVCCA_AVAILABLE void VROARSessionARCore::createGeospatialAnchor(double latitude, double longitude, double altitude, VROQuaternion quaternion,