Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 60 additions & 2 deletions apps/example/src/Diagnostics/ContextEdgeCases.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useRef } from "react";
import React, { useRef, useState } from "react";
import { Button, ScrollView, Text, View } from "react-native";
import type { CanvasRef } from "react-native-webgpu";
import { Canvas } from "react-native-webgpu";
Expand Down Expand Up @@ -29,11 +29,57 @@ import {
// the context keeps handing out textures as if still configured, where the
// spec says the canvas should behave as if it was never configured.
//
// 4. device.destroy() on the device a mounted Canvas is configured with.
// Dawn's Vulkan backend cannot detach a swapchain after its device was
// destroyed, so the Canvas unmount crashed on Android. Per spec the canvas
// stays configured (getCurrentTexture() must not throw) and configure()
// with a replacement device must render on screen again; the unmount
// toggle then checks the swapchain teardown. "unconfigure() first" covers
// the idiomatic cleanup order, where Dawn parks the old swapchain in the
// surface for reuse instead of detaching it.
//
// Each button is an independent repro; on a broken build the first two
// terminate the app, so relaunch between attempts.
export const ContextEdgeCases = () => {
const ref = useRef<CanvasRef>(null);
const { log, append } = useDiagnosticLog();
const [mounted, setMounted] = useState(true);

const destroyDevice = async (unconfigureFirst: boolean) => {
try {
const { device, format } = await initGPU(append);
const ctx = ref.current!.getContext("webgpu")!;
ctx.configure({ device, format, alphaMode: "opaque" });
drawClearFrame(device, ctx, 0);
if (unconfigureFirst) {
append("rendered one frame, calling unconfigure()...");
ctx.unconfigure();
} else {
append("rendered one frame");
}
append("calling device.destroy()...");
device.destroy();
if (!unconfigureFirst) {
const texture = ctx.getCurrentTexture();
append(
`getCurrentTexture() after destroy() -> ${texture.width}x${texture.height} (spec: invalid texture, no throw)`,
);
ctx.present();
}
const replacement = await initGPU(append);
ctx.configure({
device: replacement.device,
format: replacement.format,
alphaMode: "opaque",
});
drawClearFrame(replacement.device, ctx, 30);
append(
"reconfigured with a new device and rendered: the canvas should show a new color. Now unmount the canvas.",
);
} catch (e) {
append(`threw: ${e}`);
}
};

const getCurrentTextureUnconfigured = () => {
try {
Expand Down Expand Up @@ -99,8 +145,20 @@ export const ContextEdgeCases = () => {
title="unconfigure() then getCurrentTexture()"
onPress={unconfigureStub}
/>
<Button
title="device.destroy() then reconfigure"
onPress={() => destroyDevice(false)}
/>
<Button
title="unconfigure(), device.destroy(), reconfigure"
onPress={() => destroyDevice(true)}
/>
<Button
title={mounted ? "unmount canvas" : "mount canvas"}
onPress={() => setMounted((m) => !m)}
/>
</View>
<Canvas ref={ref} style={diagnosticStyles.canvas} />
{mounted && <Canvas ref={ref} style={diagnosticStyles.canvas} />}
<ScrollView style={diagnosticStyles.log}>
{log.map((line, i) => (
<Text key={i} style={diagnosticStyles.logLine}>
Expand Down
17 changes: 12 additions & 5 deletions packages/webgpu/android/cpp/cpp-adapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUModule_initializeNative(
auto jsCallInvoker{
facebook::jni::alias_ref<facebook::react::CallInvokerHolder::javaobject>{
reinterpret_cast<facebook::react::CallInvokerHolder::javaobject>(
jsCallInvokerHolder)} -> cthis()->getCallInvoker()};
jsCallInvokerHolder)} -> cthis()
->getCallInvoker()};
auto platformContext =
std::make_shared<rnwgpu::AndroidPlatformContext>(globalBlobModule);
manager = std::make_shared<rnwgpu::RNWebGPUManager>(runtime, jsCallInvoker,
Expand Down Expand Up @@ -55,13 +56,19 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceCreate(
}
auto &registry = rnwgpu::SurfaceRegistry::getInstance();
auto gpu = manager->_gpu;
auto surface = manager->_platformContext->makeSurface(
gpu, window, static_cast<int>(width), static_cast<int>(height));
// SurfaceInfo creates the Dawn surface now and keeps the factory so it can
// rebuild one for the same window later (device destroyed, then the canvas
// reconfigured with a replacement device).
auto platformContext = manager->_platformContext;
int w = static_cast<int>(width);
int h = static_cast<int>(height);
auto makeSurface = [platformContext, gpu, w, h](void *nativeWindow) {
return platformContext->makeSurface(gpu, nativeWindow, w, h);
};
// Find-or-create + attach runs atomically under the registry lock so a
// concurrent destroyContext cannot orphan this surface.
auto info = registry.attachSurface(
contextId, gpu, static_cast<int>(width), static_cast<int>(height), window,
surface, [](void *nativeSurface) {
contextId, gpu, w, h, window, makeSurface, [](void *nativeSurface) {
ANativeWindow_release(static_cast<ANativeWindow *>(nativeSurface));
});
// The attach is adopted at the next frame boundary by the rendering thread;
Expand Down
13 changes: 10 additions & 3 deletions packages/webgpu/apple/MetalView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,20 @@ - (void)configure {
void *nativeSurface = (void *)CFBridgingRetain(self.layer);
auto &registry = rnwgpu::SurfaceRegistry::getInstance();
auto gpu = manager->_gpu;
auto surface = manager->_platformContext->makeSurface(
gpu, nativeSurface, size.width, size.height);
// SurfaceInfo creates the Dawn surface now and keeps the factory so it can
// rebuild one for the same layer later (device destroyed, then the canvas
// reconfigured with a replacement device).
auto platformContext = manager->_platformContext;
int width = size.width;
int height = size.height;
auto makeSurface = [platformContext, gpu, width, height](void *layer) {
return platformContext->makeSurface(gpu, layer, width, height);
};
// Find-or-create + attach runs atomically under the registry lock so a
// concurrent destroyContext cannot orphan this surface.
auto info = registry.attachSurface(
[_contextId intValue], gpu, size.width, size.height, nativeSurface,
surface, [](void *layer) {
makeSurface, [](void *layer) {
// The releaser can run on the rendering thread; CALayer teardown
// belongs on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
Expand Down
Loading