Skip to content

Commit ebb125f

Browse files
committed
one device, one context
1 parent abe8b25 commit ebb125f

File tree

8 files changed

+130
-131
lines changed

8 files changed

+130
-131
lines changed

app/favicon.ico

-14.7 KB
Binary file not shown.

components/editor/editor.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import dynamic from 'next/dynamic';
3030
import { useCallback } from 'react';
3131
import { ItemWithTransitionSignal } from 'theme/itemwithtransition';
3232
import { MonacoTheme } from 'theme/monacotheme';
33-
import { Frame } from 'theme/theme';
3433
import ConfigurationPicker from './configurationpicker';
3534
import Explainer from './explainer';
3635

@@ -94,6 +93,15 @@ export default function Editor(props: EditorProps) {
9493
);
9594
}
9695

96+
const ordinaryStyle = {
97+
width: '100%',
98+
height: '100%',
99+
margin: 0,
100+
padding: 0,
101+
aspectRatio: '1.77',
102+
background: 'rgba(0,0,0,0)',
103+
borderRadius: '4px'
104+
};
97105
let embedStyle = {};
98106
if (props.embed) {
99107
embedStyle = {
@@ -144,20 +152,15 @@ export default function Editor(props: EditorProps) {
144152
);
145153

146154
const leftPanel = (
147-
<div ref={renderParentNodeRef}>
155+
<div>
148156
<ItemWithTransitionSignal transitionAtom={saveColorTransitionSignalAtom}>
149-
<Frame elevation={12}>
157+
<div style={ordinaryStyle} ref={renderParentNodeRef}>
150158
<WgpuToyWrapper
151159
bindID={'editor-canvas'}
152-
style={{
153-
display: 'inline-block',
154-
borderRadius: '4px',
155-
backgroundColor: 'black',
156-
...embedStyle
157-
}}
160+
style={{ ...ordinaryStyle, ...embedStyle }}
158161
embed={props.embed}
159162
/>
160-
</Frame>
163+
</div>
161164
<Grid
162165
container
163166
sx={{

components/wgputoy.tsx

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,65 +3,64 @@ import WarningIcon from '@mui/icons-material/Warning';
33
import Stack from '@mui/material/Stack';
44
import Typography from '@mui/material/Typography';
55
import { useAtom, useSetAtom } from 'jotai';
6-
import { canvasElAtom, wgpuAvailabilityAtom } from 'lib/atoms/wgputoyatoms';
7-
import dynamic from 'next/dynamic';
8-
import { Suspense, useCallback, useState } from 'react';
6+
import {
7+
canvasElAtom,
8+
wgpuAvailabilityAtom,
9+
wgpuContextAtom,
10+
wgpuDeviceAtom
11+
} from 'lib/atoms/wgputoyatoms';
12+
import { useEffect, useRef } from 'react';
913
import { theme } from 'theme/theme';
14+
import WgpuToyController from './wgputoycontroller';
1015

1116
export const WgpuToyWrapper = props => {
1217
const setCanvasEl = useSetAtom(canvasElAtom);
18+
const setWgpuContext = useSetAtom(wgpuContextAtom);
19+
const setWgpuDevice = useSetAtom(wgpuDeviceAtom);
1320
const [wgpuAvailability, setWgpuAvailability] = useAtom(wgpuAvailabilityAtom);
14-
const [loaded, setLoaded] = useState(false);
1521

16-
const canvasRef = useCallback(canvas => {
22+
const canvasRef = useRef<HTMLCanvasElement>(null);
23+
24+
useEffect(() => {
1725
(async () => {
1826
// there may be a case where we don't have the canvas *yet*
19-
if (canvas && canvas.getContext('webgpu') && 'gpu' in navigator) {
20-
const adapter = await navigator.gpu.requestAdapter();
21-
if (adapter) {
22-
const device = await adapter.requestDevice();
23-
if (device) {
24-
setWgpuAvailability('available');
25-
setCanvasEl(canvas);
26-
setLoaded(true);
27-
} else {
28-
setWgpuAvailability('unavailable');
29-
}
27+
if (canvasRef.current) {
28+
const context = canvasRef.current.getContext('webgpu');
29+
const adapter = await navigator.gpu?.requestAdapter({
30+
powerPreference: 'high-performance'
31+
});
32+
const device = await adapter?.requestDevice({
33+
label: 'compute.toys device with all features',
34+
requiredFeatures: [...adapter.features] as GPUFeatureName[]
35+
});
36+
if (device && context) {
37+
setCanvasEl(canvasRef.current);
38+
setWgpuContext(context);
39+
setWgpuDevice(device);
40+
setWgpuAvailability('available');
3041
} else {
3142
setWgpuAvailability('unavailable');
3243
}
33-
} else {
34-
setWgpuAvailability('unavailable');
3544
}
3645
})();
3746
}, []);
3847

39-
const onLoad = useCallback(() => {
40-
setLoaded(true);
41-
}, []);
42-
43-
const Controller = dynamic(() => import('./wgputoycontroller'), {
44-
ssr: false
45-
});
46-
4748
return (
4849
<div style={props.style}>
4950
<canvas
5051
ref={canvasRef}
5152
id={props.bindID}
5253
style={
53-
loaded
54-
? { ...props.style, ...{ outline: 'none' } }
55-
: { position: 'fixed', display: 'hidden' }
54+
wgpuAvailability !== 'unavailable'
55+
? { ...props.style, backgroundColor: 'black', borderRadius: '4px' }
56+
: { position: 'fixed', display: 'none' }
5657
}
5758
tabIndex={1}
5859
/>
59-
{loaded ? (
60-
<Suspense>
61-
<Controller onLoad={onLoad} embed={props.embed} />
62-
</Suspense>
63-
) : wgpuAvailability === 'unknown' ? null : (
64-
<Stack color={theme.palette.primary.contrastText} spacing={2} padding={4}>
60+
{wgpuAvailability === 'unknown' ? null : wgpuAvailability === 'available' ? (
61+
<WgpuToyController embed={props.embed} />
62+
) : (
63+
<Stack color={theme.palette.primary.contrastText} spacing={2} padding={7}>
6564
<Typography>
6665
<WarningIcon />
6766
</Typography>

components/wgputoycontroller.tsx

Lines changed: 68 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import {
3333
canvasElAtom,
3434
canvasParentElAtom,
3535
wgpuAvailabilityAtom,
36+
wgpuContextAtom,
37+
wgpuDeviceAtom,
3638
wgputoyPreludeAtom
3739
} from 'lib/atoms/wgputoyatoms';
3840
import { ComputeEngine } from 'lib/engine';
@@ -94,11 +96,12 @@ const WgpuToyController = props => {
9496
const setEntryPoints = useSetAtom(entryPointsAtom);
9597
const setEntryTimers = useSetAtom(entryTimersAtom);
9698
const setSaveColorTransitionSignal = useSetAtom(saveColorTransitionSignalAtom);
99+
const setPrelude = useSetAtom(wgputoyPreludeAtom);
97100

98101
const canvas = useAtomValue(canvasElAtom);
99-
const setPrelude = useSetAtom(wgputoyPreludeAtom);
100102
const wgpuAvailability = useAtomValue(wgpuAvailabilityAtom);
101-
103+
const wgpuContext = useAtomValue(wgpuContextAtom);
104+
const wgpuDevice = useAtomValue(wgpuDeviceAtom);
102105
const parentRef = useAtomValue<HTMLElement | null>(canvasParentElAtom);
103106

104107
const [width, setWidth] = useTransientAtom(widthAtom);
@@ -170,51 +173,69 @@ const WgpuToyController = props => {
170173
where manualReload gets set before the controller is loaded, which
171174
results in the effect hook for manualReload never getting called.
172175
*/
173-
useAnimationFrame(async e => {
176+
useAnimationFrame(e => {
174177
if (sliderUpdateSignal() && !needsInitialReset()) {
175-
await updateUniforms();
178+
(async () => {
179+
await updateUniforms();
180+
})();
176181
}
177182
if (performingInitialReset()) {
178183
// wait for initial reset to complete
179-
} else if (needsInitialReset() && dbLoaded()) {
180-
console.log('Initialising engine...');
181-
setPerformingInitialReset(true);
182-
await ComputeEngine.create();
183-
const engine = ComputeEngine.getInstance();
184-
if (!canvas) {
185-
console.error('Canvas not found');
186-
return;
187-
}
188-
engine.setSurface(canvas);
189-
engine.onSuccess(handleSuccess);
190-
engine.onUpdate(handleUpdate);
191-
engine.onError(handleError);
192-
setTimer(0);
193-
engine.setPassF32(float32Enabled);
194-
setProfilerEnabled(false);
195-
updateResolution();
196-
engine.resize(width(), height());
197-
engine.reset();
198-
await loadTexture(0, loadedTextures[0].img);
199-
await loadTexture(1, loadedTextures[1].img);
200-
await updateUniforms();
201-
console.log('Compiling shader...');
202-
const source = await processShaderCode(engine);
203-
if (!source) {
204-
console.error('Initialisation aborted: shader compilation failed');
205-
return;
206-
}
207-
await engine.compile(source);
208-
setPrelude(engine.getPrelude());
209-
engine.render();
210-
setManualReload(false);
211-
setNeedsInitialReset(false);
212-
setPerformingInitialReset(false);
213-
console.log('Initialisation complete');
184+
} else if (needsInitialReset() && dbLoaded() && wgpuContext && wgpuDevice) {
185+
(async () => {
186+
console.log('Initialising engine...');
187+
188+
try {
189+
const engineCreationPromise = ComputeEngine.create(wgpuContext, wgpuDevice);
190+
const textureLoadingPromises = [
191+
loadTexture(0, loadedTextures[0].img),
192+
loadTexture(1, loadedTextures[1].img)
193+
];
194+
195+
await engineCreationPromise;
196+
setPerformingInitialReset(true);
197+
198+
const engine = ComputeEngine.getInstance();
199+
engine.onSuccess(handleSuccess);
200+
engine.onUpdate(handleUpdate);
201+
engine.onError(handleError);
202+
203+
setTimer(0);
204+
engine.setPassF32(float32Enabled);
205+
setProfilerEnabled(false);
206+
updateResolution();
207+
engine.resize(width(), height());
208+
engine.reset();
209+
210+
console.log('Waiting for texture loading...');
211+
await Promise.all(textureLoadingPromises);
212+
213+
await updateUniforms();
214+
console.log('Compiling shader...');
215+
const source = await processShaderCode(engine);
216+
217+
if (!source) {
218+
console.error('Initialisation aborted: shader compilation failed');
219+
return;
220+
}
221+
222+
await engine.compile(source);
223+
setPrelude(engine.getPrelude());
224+
engine.render();
225+
226+
setManualReload(false);
227+
setNeedsInitialReset(false);
228+
setPerformingInitialReset(false);
229+
console.log('Initialisation complete');
230+
} catch (error) {
231+
console.error('Initialization failed:', error);
232+
setPerformingInitialReset(false);
233+
}
234+
})();
214235
} else if (dbLoaded() && manualReload()) {
215236
console.log('Manual reload triggered');
216237
setManualReload(false);
217-
await recompile();
238+
recompile();
218239
}
219240
if (needsInitialReset()) {
220241
return;
@@ -323,14 +344,15 @@ const WgpuToyController = props => {
323344
}, []);
324345

325346
const requestFullscreen = useCallback(() => {
326-
if (canvas && !document.fullscreenElement) {
327-
canvas.requestFullscreen({ navigationUI: 'hide' });
347+
try {
348+
if (canvas && !document.fullscreenElement) {
349+
canvas.requestFullscreen({ navigationUI: 'hide' });
350+
}
351+
} catch {
352+
console.error('Error requesting fullscreen');
328353
}
329354
}, []);
330355

331-
// init effect
332-
useEffect(props.onLoad, []);
333-
334356
useEffect(() => {
335357
const handleKeyDown = e => {
336358
// console.log(`Key down: ${e.keyCode}`);
@@ -648,8 +670,7 @@ const WgpuToyController = props => {
648670
} else if (props.embed) {
649671
dimensions = getDimensions(window.innerWidth * dpr);
650672
} else {
651-
const padding = 16;
652-
dimensions = getDimensions((parentRef!.offsetWidth - padding) * dpr);
673+
dimensions = getDimensions(parentRef!.offsetWidth * dpr);
653674
}
654675
if (canvas) {
655676
canvas.width = dimensions.x;

lib/atoms/wgputoyatoms.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { atom } from 'jotai';
33

44
// Using 'false' here to satisfy type checker for Jotai's function overloads
55
export const canvasElAtom = atom<HTMLCanvasElement | false>(false);
6+
export const wgpuContextAtom = atom<GPUCanvasContext | false>(false);
7+
export const wgpuDeviceAtom = atom<GPUDevice | false>(false);
68

79
const canvasParentElBaseAtom = atom<HTMLElement | false>(false);
810

0 commit comments

Comments
 (0)