Skip to content

Commit 6aecbe1

Browse files
committed
🔧
1 parent 893b766 commit 6aecbe1

6 files changed

Lines changed: 171 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ ctx.canvas.height = ctx.canvas.clientHeight * PixelRatio.get();
172172

173173
### Frame Scheduling
174174

175-
Frame presentation is automatic, matching the behavior of `GPUCanvasContext` on the Web. A native display link (CADisplayLink on iOS, Choreographer on Android) ticks once per vsync and presents any surface whose texture was acquired during a previous vsync interval, which gives your render code the full frame between two vsyncs to encode and submit. Short `await`s between `getCurrentTexture()` and `device.queue.submit(...)` are safe (microtasks drain before the next vsync); however, if your render code takes longer than one frame to submit, the present will fire before submit and you will see a stale frame.
175+
Frame presentation is automatic, matching the behavior of `GPUCanvasContext` on the Web. A native display link (CADisplayLink on iOS, Choreographer on Android) ticks once per vsync and presents any surface whose texture was acquired during a previous vsync interval, which gives your render code the full frame between two vsyncs to encode and submit.
176176

177177
### Canvas Transparency
178178

apps/example/src/App.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { Reanimated } from "./Reanimated";
3636
import { AsyncStarvation } from "./Diagnostics/AsyncStarvation";
3737
import { DeviceLostHang } from "./Diagnostics/DeviceLostHang";
3838
import { PresentRace } from "./Diagnostics/PresentRace";
39+
import { MultiCanvasSubmit } from "./Diagnostics/MultiCanvasSubmit";
3940
import { StorageBufferVertices } from "./StorageBufferVertices";
4041

4142
// The two lines below are needed by three.js
@@ -95,6 +96,10 @@ function App() {
9596
<Stack.Screen name="AsyncStarvation" component={AsyncStarvation} />
9697
<Stack.Screen name="DeviceLostHang" component={DeviceLostHang} />
9798
<Stack.Screen name="PresentRace" component={PresentRace} />
99+
<Stack.Screen
100+
name="MultiCanvasSubmit"
101+
component={MultiCanvasSubmit}
102+
/>
98103
<Stack.Screen
99104
name="StorageBufferVertices"
100105
component={StorageBufferVertices}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import React, { useEffect, useRef } from "react";
2+
import { StyleSheet, Text, View } from "react-native";
3+
import type { CanvasRef } from "react-native-wgpu";
4+
import { Canvas } from "react-native-wgpu";
5+
6+
type Mode = "combined" | "split";
7+
8+
const runPair = (
9+
device: GPUDevice,
10+
contextA: GPUCanvasContext,
11+
contextB: GPUCanvasContext,
12+
format: GPUTextureFormat,
13+
mode: Mode,
14+
shouldStop: () => boolean,
15+
) => {
16+
contextA.configure({ device, format, alphaMode: "premultiplied" });
17+
contextB.configure({ device, format, alphaMode: "premultiplied" });
18+
19+
const frame = () => {
20+
if (shouldStop()) {
21+
return;
22+
}
23+
24+
const textureA = contextA.getCurrentTexture();
25+
const textureB = contextB.getCurrentTexture();
26+
27+
const time = Date.now() / 1000;
28+
const r = (Math.sin(time * 2.0) + 1) / 2;
29+
const g = (Math.sin(time * 1.5 + Math.PI / 3) + 1) / 2;
30+
const b = (Math.sin(time * 1.0 + Math.PI / 2) + 1) / 2;
31+
32+
const drawClear = (
33+
encoder: GPUCommandEncoder,
34+
view: GPUTextureView,
35+
color: GPUColor,
36+
) => {
37+
const pass = encoder.beginRenderPass({
38+
colorAttachments: [
39+
{
40+
view,
41+
clearValue: color,
42+
loadOp: "clear",
43+
storeOp: "store",
44+
},
45+
],
46+
});
47+
pass.end();
48+
};
49+
50+
if (mode === "combined") {
51+
// One encoder, two passes targeting two different surfaces, one
52+
// command buffer, one submit. Tracks that beginRenderPass accumulates
53+
// every color-attachment surface into the encoder's presentable set.
54+
const encoder = device.createCommandEncoder();
55+
drawClear(encoder, textureA.createView(), [r, g, b, 1]);
56+
drawClear(encoder, textureB.createView(), [1 - r, 1 - g, 1 - b, 1]);
57+
device.queue.submit([encoder.finish()]);
58+
} else {
59+
// Two encoders, two command buffers, one submit. Tracks that
60+
// queue.submit aggregates presentable surfaces across every command
61+
// buffer in the array.
62+
const encoderA = device.createCommandEncoder();
63+
drawClear(encoderA, textureA.createView(), [r, g, b, 1]);
64+
const encoderB = device.createCommandEncoder();
65+
drawClear(encoderB, textureB.createView(), [1 - r, 1 - g, 1 - b, 1]);
66+
device.queue.submit([encoderA.finish(), encoderB.finish()]);
67+
}
68+
69+
requestAnimationFrame(frame);
70+
};
71+
72+
frame();
73+
};
74+
75+
const Pair = ({ mode, label }: { mode: Mode; label: string }) => {
76+
const refA = useRef<CanvasRef>(null);
77+
const refB = useRef<CanvasRef>(null);
78+
useEffect(() => {
79+
let stopped = false;
80+
(async () => {
81+
const adapter = await navigator.gpu.requestAdapter();
82+
if (!adapter) {
83+
return;
84+
}
85+
const device = await adapter.requestDevice();
86+
const contextA = refA.current?.getContext("webgpu");
87+
const contextB = refB.current?.getContext("webgpu");
88+
if (!contextA || !contextB) {
89+
return;
90+
}
91+
const format = navigator.gpu.getPreferredCanvasFormat();
92+
runPair(device, contextA, contextB, format, mode, () => stopped);
93+
})();
94+
return () => {
95+
stopped = true;
96+
};
97+
}, [mode]);
98+
return (
99+
<View style={styles.pair}>
100+
<Text style={styles.label}>{label}</Text>
101+
<View style={styles.row}>
102+
<Canvas ref={refA} style={styles.canvas} />
103+
<Canvas ref={refB} style={styles.canvas} />
104+
</View>
105+
</View>
106+
);
107+
};
108+
109+
export const MultiCanvasSubmit = () => {
110+
return (
111+
<View style={styles.container}>
112+
<Text style={styles.intro}>
113+
Each row drives two canvases that render inverted hues from a single
114+
submit. If the presentable-surface tracking is broken, one of the two
115+
canvases will stop updating (no display-link tick will present it).
116+
</Text>
117+
<Pair
118+
mode="combined"
119+
label="One encoder, two passes, one command buffer"
120+
/>
121+
<Pair
122+
mode="split"
123+
label="Two encoders, two command buffers, one submit"
124+
/>
125+
</View>
126+
);
127+
};
128+
129+
const styles = StyleSheet.create({
130+
container: {
131+
flex: 1,
132+
backgroundColor: "#111",
133+
padding: 12,
134+
},
135+
intro: {
136+
color: "#f5f5f5",
137+
fontSize: 13,
138+
lineHeight: 18,
139+
marginBottom: 12,
140+
},
141+
pair: {
142+
flex: 1,
143+
marginBottom: 12,
144+
},
145+
label: {
146+
color: "#f5f5f5",
147+
fontSize: 13,
148+
marginBottom: 6,
149+
},
150+
row: {
151+
flex: 1,
152+
flexDirection: "row",
153+
},
154+
canvas: {
155+
flex: 1,
156+
marginRight: 6,
157+
},
158+
});

apps/example/src/Home.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ export const examples = [
127127
screen: "PresentRace",
128128
title: "⚠️ Present Race (await before submit)",
129129
},
130+
{
131+
screen: "MultiCanvasSubmit",
132+
title: "🖼️ Multi-Canvas Submit Tracking",
133+
},
130134
{
131135
screen: "StorageBufferVertices",
132136
title: "💾 Storage Buffer Vertices",

apps/example/src/Route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@ export type Routes = {
2929
AsyncStarvation: undefined;
3030
DeviceLostHang: undefined;
3131
PresentRace: undefined;
32+
MultiCanvasSubmit: undefined;
3233
StorageBufferVertices: undefined;
3334
};

packages/webgpu/cpp/rnwgpu/SurfaceRegistry.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,15 @@ class SurfaceInfo {
4141

4242
void reconfigure(int newWidth, int newHeight) {
4343
std::unique_lock<std::shared_mutex> lock(_mutex);
44+
_resetPresentationStateLocked();
4445
config.width = newWidth;
4546
config.height = newHeight;
4647
_configure();
4748
}
4849

4950
void configure(wgpu::SurfaceConfiguration &newConfig) {
5051
std::unique_lock<std::shared_mutex> lock(_mutex);
52+
_resetPresentationStateLocked();
5153
config = newConfig;
5254
config.width = width;
5355
config.height = height;

0 commit comments

Comments
 (0)