Skip to content

iOS: SIGSEGV entering WebXR/AR session — GetActiveEncoder() null in NativeXr::BeginUpdate after threading rework (#1652) #1767

Description

@julapy

Summary

On iOS, entering a WebXR/AR session crashes with EXC_BAD_ACCESS (SIGSEGV) inside NativeXr::Impl::BeginUpdate. The framebuffer-creation continuation dereferences GraphicsContext.GetActiveEncoder(), which returns nullptr because it runs on the runtime thread with no frame in flight. This reproduces in the stock iOS Playground and appears to be a regression from the threading rework (#1652).

Environment

  • Babylon Native master @ 64f545de (also present on every commit since Reworked threading model. #1652)
  • iOS 26.5, iPhone 15 Pro Max (device — ARKit), Xcode 26
  • Repro app: Apps/Playground (iOS), built via cmake -B build -G Xcode -D IOS=ON

Repro

  1. In Apps/Playground/Scripts/experience.js, set const ar = true;
  2. Build & run the Playground target on a physical iOS device.
  3. On session entry (the enterXRAsync("immersive-ar", ...) path, ~5s after load) the app crashes immediately.

Crash

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x00000000000002d9

Thread (crashed):
  bgfx::EncoderImpl::discard(unsigned char)
  bgfx::Encoder::touch(unsigned short)
  Babylon::Graphics::FrameBuffer::Clear(bgfx::Encoder&, unsigned short, unsigned int, float, unsigned char)
  Babylon::Plugins::NativeXr::Impl::BeginUpdate()::$_0::operator()() const
  ... (arcana task machinery, on Babylon::JsRuntimeScheduler / runtime thread)

Root cause

In Plugins/NativeXr/Source/NativeXrImpl.cpp, BeginUpdate() creates the per-view framebuffers inside a .then(m_runtimeScheduler, ...) continuation and then does the WebXR implicit clear:

// NativeXrImpl.cpp (~line 310)
frameBuffer.Clear(*m_sessionState->GraphicsContext.GetActiveEncoder(),
                  BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH | BGFX_CLEAR_STENCIL, 0, 1.0f, 0);

DeviceContext::GetActiveEncoder() returns DeviceImpl::m_frameEncoder, which is only non-null between frame start/end on the render thread. This continuation runs on the runtime thread with no frame in flight, so GetActiveEncoder() returns nullptr and the dereference crashes.

Before #1652 this used an explicitly-acquired encoder that was always valid:

// pre-#1652
frameBuffer.Clear(*m_sessionState->Update.GetUpdateToken().GetEncoder(), ...);

#1652 (6bb80141, "Reworked threading model") replaced the UpdateToken encoder with GraphicsContext.GetActiveEncoder() and removed the Update/UpdateToken API.

Regression range

  • Works: ≤ ff9d4dbc (2026-06-08, the commit before the threading rework)
  • Crashes: 6bb80141 (Reworked threading model. #1652, 2026-06-09) through master tip (64f545de)

Suggested fix

The creation-time clear needs a valid encoder. Options:

  1. Guard the clear on a non-null active encoder. On ARKit/ARCore the camera feed pre-composites (requiresAppClear == false), so skipping this one-time implicit clear when no encoder is active is visually safe there:
    if (auto* encoder = m_sessionState->GraphicsContext.GetActiveEncoder())
    {
        frameBuffer.Clear(*encoder, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH | BGFX_CLEAR_STENCIL, 0, 1.0f, 0);
    }
    (This unblocks ARKit, but likely leaves the initial clear missing for backends that don't pre-composite, e.g. OpenXR.)
  2. A more complete fix would acquire/attach a valid encoder for this creation-time clear (or defer the implicit clear to the first frame on the render thread) so it works across all backends.

Happy to open a PR for option 1 if that's an acceptable stopgap.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions