wcandillon / wcandillon/react-native-webgpu

iOS Layer Modifications Off Main Thread

Open
#244 8 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
1.2k
Forks
70
Avg merge
1d 6h
Merged PRs (30d)
23

Description

iOS Layer Modifications Off Main Thread

Summary

react-native-wgpu causes continuous memory leaks on iOS due to WebGPU operations (layer modifications) happening off the main thread. Memory usage climbs indefinitely at ~6-10MB per minute during rendering.

Environment

  • react-native-wgpu: 0.2.1
  • Platform: iOS (Physical device - iPhone 15 Pro)
  • React Native: 0.74.6 (Expo SDK 53)
  • iOS Version: 18.x

Issue Description

WebGPU rendering operations trigger iOS warnings about modifying view layers off the main thread, causing memory to accumulate without being garbage collected. The leak occurs even with minimal rendering (empty scene with no compute shaders).

Error Message

Modifying properties of a view's layer off the main thread is not allowed: 
view <MetalView: 0x11c558000> with nearest ancestor view controller <RNSScreen: 0x1073ab200>

Stack trace points to:
dawn::native::metal::SwapChain::Initialize
dawn::native::metal::Surface::Configure

Reproduction Steps

  1. Create a basic WebGPU render loop:
const ref = useCanvasEffect(async () => {
  const adapter = await navigator.gpu.requestAdapter();
  const device = await adapter.requestDevice();
  const context = ref.current!.getContext('webgpu')!;
  
  context.configure({
    device,
    format: navigator.gpu.getPreferredCanvasFormat(),
    alphaMode: 'premultiplied',
  });

  const frame = () => {
    const texture = context.getCurrentTexture();
    const commandEncoder = device.createCommandEncoder();
    
    const renderPass = commandEncoder.beginRenderPass({
      colorAttachments: [{
        view: texture.createView(),
        clearValue: { r: 0, g: 0, b: 0, a: 1 },
        loadOp: 'clear',
        storeOp: 'store',
      }],
    });
    
    renderPass.end();
    device.queue.submit([commandEncoder.finish()]);
    
    requestAnimationFrame(frame);
  };
  
  requestAnimationFrame(frame);
});
  1. Run the app on a physical iOS device
  2. Monitor memory usage in Xcode
  3. Observe continuous memory growth (~6-10MB/minute)

Root Cause

The WebGPU operations are executing on the React Native JavaScript thread instead of the main UI thread. iOS requires all layer/view modifications to happen on the main thread. Key problematic operations:

  1. context.configure() - Configures the Metal swap chain
  2. context.getCurrentTexture() - Gets texture from swap chain
  3. device.queue.submit() - Submits commands to GPU
  4. Any buffer mapping operations (buffer.mapAsync())

Impact

  • Memory Growth: 6-10MB per minute during active rendering
  • Performance Degradation: GPU frame time doubles after prolonged use
  • Battery Drain: Device heats up due to memory pressure
  • App Crashes: Eventually crashes due to memory exhaustion

Attempted Workarounds (Unsuccessful)

  1. ✅ Wrapping context.configure() in requestAnimationFrame() - Partial fix, doesn't solve render loop
  2. ❌ Periodic buffer recreation/destruction - Doesn't address core issue
  3. ❌ Reducing frame rate - Slows leak but doesn't eliminate it
  4. ❌ Manual garbage collection hints - Native memory not accessible from JS

Expected Behavior

WebGPU operations should be dispatched to the main thread on iOS to comply with UIKit requirements and prevent memory leaks.

Suggested Fix

The library needs to dispatch WebGPU operations to the main thread on iOS:

// In native iOS code
dispatch_async(dispatch_get_main_queue(), ^{
    // WebGPU/Metal operations here
});

Specifically for:

  • SwapChain configuration
  • Texture acquisition
  • Command submission
  • Buffer mapping operations

Additional Context

  • The issue does not occur in simulators (different threading model)
  • The issue is specific to iOS (UIKit's main thread requirements)
  • Memory leak occurs even without any compute shaders or complex rendering
  • The leak accumulates in native memory (not visible in JS heap snapshots)

Profiler Evidence

Instruments trace showing the issue originates in Dawn's Metal implementation when configuring the swap chain from a background thread. The memory accumulates in Core Animation layers that aren't properly released.

Related Issues

This may be related to iOS's strict main thread requirements for UI operations, similar to issues seen in other graphics libraries when not properly dispatching to the main thread.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the issue on a physical iOS device with the minimal render loop, then inspect the native path around Dawn's Metal SwapChain::Initialize and Surface::Configure. Verify which operations modify layers off the main thread and confirm completion when rendering no longer shows continuous memory growth, warnings, or eventual crashes.

Written by the indexing model from the issue text.

Assessment

Tech stack
ios, react-native, typescript
Domain
computer-graphics, mobile, performance
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.