Skip to content
Draft
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
23 changes: 13 additions & 10 deletions components/editor/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import dynamic from 'next/dynamic';
import { useCallback } from 'react';
import { ItemWithTransitionSignal } from 'theme/itemwithtransition';
import { MonacoTheme } from 'theme/monacotheme';
import { Frame } from 'theme/theme';
import ConfigurationPicker from './configurationpicker';
import Explainer from './explainer';

Expand Down Expand Up @@ -94,6 +93,15 @@ export default function Editor(props: EditorProps) {
);
}

const ordinaryStyle = {
width: '100%',
height: '100%',
margin: 0,
padding: 0,
aspectRatio: '1.77',
background: 'rgba(0,0,0,0)',
borderRadius: '4px'
};
let embedStyle = {};
if (props.embed) {
embedStyle = {
Expand Down Expand Up @@ -144,20 +152,15 @@ export default function Editor(props: EditorProps) {
);

const leftPanel = (
<div ref={renderParentNodeRef}>
<div>
<ItemWithTransitionSignal transitionAtom={saveColorTransitionSignalAtom}>
<Frame elevation={12}>
<div style={ordinaryStyle} ref={renderParentNodeRef}>
<WgpuToyWrapper
bindID={'editor-canvas'}
style={{
display: 'inline-block',
borderRadius: '4px',
backgroundColor: 'black',
...embedStyle
}}
style={{ ...ordinaryStyle, ...embedStyle }}
embed={props.embed}
/>
</Frame>
</div>
<Grid
container
sx={{
Expand Down
69 changes: 34 additions & 35 deletions components/wgputoy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,65 +3,64 @@ import WarningIcon from '@mui/icons-material/Warning';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { useAtom, useSetAtom } from 'jotai';
import { canvasElAtom, wgpuAvailabilityAtom } from 'lib/atoms/wgputoyatoms';
import dynamic from 'next/dynamic';
import { Suspense, useCallback, useState } from 'react';
import {
canvasElAtom,
wgpuAvailabilityAtom,
wgpuContextAtom,
wgpuDeviceAtom
} from 'lib/atoms/wgputoyatoms';
import { useEffect, useRef } from 'react';
import { theme } from 'theme/theme';
import WgpuToyController from './wgputoycontroller';

export const WgpuToyWrapper = props => {
const setCanvasEl = useSetAtom(canvasElAtom);
const setWgpuContext = useSetAtom(wgpuContextAtom);
const setWgpuDevice = useSetAtom(wgpuDeviceAtom);
const [wgpuAvailability, setWgpuAvailability] = useAtom(wgpuAvailabilityAtom);
const [loaded, setLoaded] = useState(false);

const canvasRef = useCallback(canvas => {
const canvasRef = useRef<HTMLCanvasElement>(null);

useEffect(() => {
(async () => {
// there may be a case where we don't have the canvas *yet*
if (canvas && canvas.getContext('webgpu') && 'gpu' in navigator) {
const adapter = await navigator.gpu.requestAdapter();
if (adapter) {
const device = await adapter.requestDevice();
if (device) {
setWgpuAvailability('available');
setCanvasEl(canvas);
setLoaded(true);
} else {
setWgpuAvailability('unavailable');
}
if (canvasRef.current) {
const context = canvasRef.current.getContext('webgpu');
const adapter = await navigator.gpu?.requestAdapter({
powerPreference: 'high-performance'
});
const device = await adapter?.requestDevice({
label: 'compute.toys device with all features',
requiredFeatures: [...adapter.features] as GPUFeatureName[]
});
if (device && context) {
setCanvasEl(canvasRef.current);
setWgpuContext(context);
setWgpuDevice(device);
setWgpuAvailability('available');
} else {
setWgpuAvailability('unavailable');
}
} else {
setWgpuAvailability('unavailable');
}
})();
}, []);

const onLoad = useCallback(() => {
setLoaded(true);
}, []);

const Controller = dynamic(() => import('./wgputoycontroller'), {
ssr: false
});

return (
<div style={props.style}>
<canvas
ref={canvasRef}
id={props.bindID}
style={
loaded
? { ...props.style, ...{ outline: 'none' } }
: { position: 'fixed', display: 'hidden' }
wgpuAvailability !== 'unavailable'
? { ...props.style, backgroundColor: 'black', borderRadius: '4px' }
: { position: 'fixed', display: 'none' }
}
tabIndex={1}
/>
{loaded ? (
<Suspense>
<Controller onLoad={onLoad} embed={props.embed} />
</Suspense>
) : wgpuAvailability === 'unknown' ? null : (
<Stack color={theme.palette.primary.contrastText} spacing={2} padding={4}>
{wgpuAvailability === 'unknown' ? null : wgpuAvailability === 'available' ? (
<WgpuToyController embed={props.embed} />
) : (
<Stack color={theme.palette.primary.contrastText} spacing={2} padding={7}>
<Typography>
<WarningIcon />
</Typography>
Expand Down
115 changes: 68 additions & 47 deletions components/wgputoycontroller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import {
canvasElAtom,
canvasParentElAtom,
wgpuAvailabilityAtom,
wgpuContextAtom,
wgpuDeviceAtom,
wgputoyPreludeAtom
} from 'lib/atoms/wgputoyatoms';
import { ComputeEngine } from 'lib/engine';
Expand Down Expand Up @@ -94,11 +96,12 @@ const WgpuToyController = props => {
const setEntryPoints = useSetAtom(entryPointsAtom);
const setEntryTimers = useSetAtom(entryTimersAtom);
const setSaveColorTransitionSignal = useSetAtom(saveColorTransitionSignalAtom);
const setPrelude = useSetAtom(wgputoyPreludeAtom);

const canvas = useAtomValue(canvasElAtom);
const setPrelude = useSetAtom(wgputoyPreludeAtom);
const wgpuAvailability = useAtomValue(wgpuAvailabilityAtom);

const wgpuContext = useAtomValue(wgpuContextAtom);
const wgpuDevice = useAtomValue(wgpuDeviceAtom);
const parentRef = useAtomValue<HTMLElement | null>(canvasParentElAtom);

const [width, setWidth] = useTransientAtom(widthAtom);
Expand Down Expand Up @@ -170,51 +173,69 @@ const WgpuToyController = props => {
where manualReload gets set before the controller is loaded, which
results in the effect hook for manualReload never getting called.
*/
useAnimationFrame(async e => {
useAnimationFrame(e => {
if (sliderUpdateSignal() && !needsInitialReset()) {
await updateUniforms();
(async () => {
await updateUniforms();
})();
}
if (performingInitialReset()) {
// wait for initial reset to complete
} else if (needsInitialReset() && dbLoaded()) {
console.log('Initialising engine...');
setPerformingInitialReset(true);
await ComputeEngine.create();
const engine = ComputeEngine.getInstance();
if (!canvas) {
console.error('Canvas not found');
return;
}
engine.setSurface(canvas);
engine.onSuccess(handleSuccess);
engine.onUpdate(handleUpdate);
engine.onError(handleError);
setTimer(0);
engine.setPassF32(float32Enabled);
setProfilerEnabled(false);
updateResolution();
engine.resize(width(), height());
engine.reset();
await loadTexture(0, loadedTextures[0].img);
await loadTexture(1, loadedTextures[1].img);
await updateUniforms();
console.log('Compiling shader...');
const source = await processShaderCode(engine);
if (!source) {
console.error('Initialisation aborted: shader compilation failed');
return;
}
await engine.compile(source);
setPrelude(engine.getPrelude());
engine.render();
setManualReload(false);
setNeedsInitialReset(false);
setPerformingInitialReset(false);
console.log('Initialisation complete');
} else if (needsInitialReset() && dbLoaded() && wgpuContext && wgpuDevice) {
(async () => {
console.log('Initialising engine...');

try {
const engineCreationPromise = ComputeEngine.create(wgpuContext, wgpuDevice);
const textureLoadingPromises = [
loadTexture(0, loadedTextures[0].img),
loadTexture(1, loadedTextures[1].img)
];

await engineCreationPromise;
setPerformingInitialReset(true);

const engine = ComputeEngine.getInstance();
engine.onSuccess(handleSuccess);
engine.onUpdate(handleUpdate);
engine.onError(handleError);

setTimer(0);
engine.setPassF32(float32Enabled);
setProfilerEnabled(false);
updateResolution();
engine.resize(width(), height());
engine.reset();

console.log('Waiting for texture loading...');
await Promise.all(textureLoadingPromises);

await updateUniforms();
console.log('Compiling shader...');
const source = await processShaderCode(engine);

if (!source) {
console.error('Initialisation aborted: shader compilation failed');
return;
}

await engine.compile(source);
setPrelude(engine.getPrelude());
engine.render();

setManualReload(false);
setNeedsInitialReset(false);
setPerformingInitialReset(false);
console.log('Initialisation complete');
} catch (error) {
console.error('Initialization failed:', error);
setPerformingInitialReset(false);
}
})();
} else if (dbLoaded() && manualReload()) {
console.log('Manual reload triggered');
setManualReload(false);
await recompile();
recompile();
}
if (needsInitialReset()) {
return;
Expand Down Expand Up @@ -323,14 +344,15 @@ const WgpuToyController = props => {
}, []);

const requestFullscreen = useCallback(() => {
if (canvas && !document.fullscreenElement) {
canvas.requestFullscreen({ navigationUI: 'hide' });
try {
if (canvas && !document.fullscreenElement) {
canvas.requestFullscreen({ navigationUI: 'hide' });
}
} catch {
console.error('Error requesting fullscreen');
}
}, []);

// init effect
useEffect(props.onLoad, []);

useEffect(() => {
const handleKeyDown = e => {
// console.log(`Key down: ${e.keyCode}`);
Expand Down Expand Up @@ -648,8 +670,7 @@ const WgpuToyController = props => {
} else if (props.embed) {
dimensions = getDimensions(window.innerWidth * dpr);
} else {
const padding = 16;
dimensions = getDimensions((parentRef!.offsetWidth - padding) * dpr);
dimensions = getDimensions(parentRef!.offsetWidth * dpr);
}
if (canvas) {
canvas.width = dimensions.x;
Expand Down
2 changes: 2 additions & 0 deletions lib/atoms/wgputoyatoms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { atom } from 'jotai';

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

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

Expand Down
Loading