-
Notifications
You must be signed in to change notification settings - Fork 60
Expand file tree
/
Copy pathhooks.tsx
More file actions
125 lines (113 loc) · 3.4 KB
/
hooks.tsx
File metadata and controls
125 lines (113 loc) · 3.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import type { ReactNode } from "react";
import { createContext, useContext, useEffect, useRef, useState } from "react";
import type { RNCanvasContext, CanvasRef, NativeCanvas } from "./Canvas";
type Unsubscribe = () => void;
export const warnIfNotHardwareAccelerated = (adapter: GPUAdapter) => {
if (adapter.isFallbackAdapter) {
console.warn(
"GPUAdapter is not hardware accelerated. This is common on Android emulators. Rendering will be slow. Some features may be unavailable.",
);
}
};
interface DeviceContext {
device: GPUDevice | null;
adapter: GPUAdapter | null;
}
const DeviceContext = createContext<DeviceContext | null>(null);
interface DeviceProviderProps {
children?: ReactNode | ReactNode[];
adapterOptions?: GPURequestAdapterOptions;
deviceDescriptor?: GPUDeviceDescriptor;
}
export const GPUDeviceProvider = ({
children,
adapterOptions,
deviceDescriptor,
}: DeviceProviderProps) => {
const state = useDevice(adapterOptions, deviceDescriptor);
if (!state.device) {
return null;
}
return (
<DeviceContext.Provider value={state}>{children}</DeviceContext.Provider>
);
};
export const useSurface = () => {
const [surface, setSurface] = useState<NativeCanvas | null>(null);
const ref = useCanvasEffect(() => {
const sur = ref.current!.getNativeSurface();
setSurface(sur);
});
return { ref, surface };
};
export const useMainDevice = () => {
const ctx = useContext(DeviceContext);
if (!ctx) {
throw new Error("No DeviceContext found.");
}
return ctx;
};
export const useDevice = (
adapterOptions?: GPURequestAdapterOptions,
deviceDescriptor?: GPUDeviceDescriptor,
) => {
const [state, setState] = useState<DeviceContext | null>(null);
useEffect(() => {
(async () => {
if (!state) {
const adapter = await navigator.gpu.requestAdapter(adapterOptions);
if (!adapter) {
throw new Error("No appropriate GPUAdapter found.");
}
warnIfNotHardwareAccelerated(adapter);
const device = await adapter.requestDevice(deviceDescriptor);
if (!device) {
throw new Error("No appropriate GPUDevice found.");
}
setState({ adapter, device });
return;
}
})();
}, [adapterOptions, deviceDescriptor, state]);
return { adapter: state?.adapter ?? null, device: state?.device ?? null };
};
export const useGPUContext = () => {
const [context, setContext] = useState<RNCanvasContext | null>(null);
const ref = useCanvasEffect(() => {
const ctx = ref.current!.getContext("webgpu")!;
setContext(ctx);
});
return { ref, context };
};
export const useCanvasEffect = (
effect: () =>
| void
| Unsubscribe
| Promise<Unsubscribe | void>
| Promise<void>,
) => {
const unsub = useRef<Unsubscribe | null | Promise<Unsubscribe | void>>(null);
const ref = useRef<CanvasRef>(null);
useEffect(() => {
if (!ref.current || !ref.current.whenReady) {
throw new Error("The reference is not assigned to a WebGPU Canvas");
}
ref.current.whenReady(async () => {
const sub = effect();
if (sub) {
unsub.current = sub;
}
});
return () => {
if (unsub.current) {
if (unsub.current instanceof Promise) {
unsub.current.then((sub) => sub && sub());
} else {
unsub.current();
}
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return ref;
};