-
Notifications
You must be signed in to change notification settings - Fork 37
Description
Memory Leak: 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
- 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);
});
- Run the app on a physical iOS device
- Monitor memory usage in Xcode
- 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:
context.configure()
- Configures the Metal swap chaincontext.getCurrentTexture()
- Gets texture from swap chaindevice.queue.submit()
- Submits commands to GPU- 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)
- ✅ Wrapping
context.configure()
inrequestAnimationFrame()
- Partial fix, doesn't solve render loop - ❌ Periodic buffer recreation/destruction - Doesn't address core issue
- ❌ Reducing frame rate - Slows leak but doesn't eliminate it
- ❌ 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.