diff --git a/src/components/SampleLayout.tsx b/src/components/SampleLayout.tsx index eca8a61a..6f93893e 100644 --- a/src/components/SampleLayout.tsx +++ b/src/components/SampleLayout.tsx @@ -136,6 +136,9 @@ const SampleLayout: React.FunctionComponent< }; try { const canvas = canvasRef.current; + if (!canvas) { + throw new Error('The canvas is not available'); + } const p = props.init({ canvas, pageState, @@ -190,7 +193,9 @@ const SampleLayout: React.FunctionComponent<

{props.description}

{error ? ( <> -

Is WebGPU Enabled?

+

+ Something went wrong. Do your browser and device support WebGPU? +

{`${error}`}

) : null} @@ -253,3 +258,9 @@ export const makeSample: ( ) => JSX.Element = (props) => { return ; }; + +export function assert(condition: unknown, msg?: string): asserts condition { + if (!condition) { + throw new Error(msg); + } +} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 705fe378..86fbea7b 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -11,6 +11,10 @@ import { pages } from './samples/[slug]'; const title = 'WebGPU Samples'; +type PageType = { + [key: string]: React.ComponentType & { render: { preload: () => void } }; +}; + const MainLayout: React.FunctionComponent = ({ Component, pageProps, @@ -71,7 +75,7 @@ const MainLayout: React.FunctionComponent = ({ key={slug} className={className} onMouseOver={() => { - pages[slug].render.preload(); + (pages as PageType)[slug].render.preload(); }} > import('../../sample/helloTriangle/main')), helloTriangleMSAA: dynamic( () => import('../../sample/helloTriangleMSAA/main') @@ -59,9 +63,13 @@ export const getStaticPaths: GetStaticPaths = async () => { export const getStaticProps: GetStaticProps = async ({ params, }) => { + if (!params) { + return { notFound: true }; + } + return { props: { - ...params, + slug: params.slug, }, }; }; diff --git a/src/sample/animometer/main.ts b/src/sample/animometer/main.ts index 5318c38e..8b6074af 100644 --- a/src/sample/animometer/main.ts +++ b/src/sample/animometer/main.ts @@ -1,9 +1,10 @@ -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import animometerWGSL from './animometer.wgsl'; const init: SampleInit = async ({ canvas, pageState, gui }) => { const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'requestAdapter returned null'); const device = await adapter.requestDevice(); if (!pageState.active) return; @@ -17,7 +18,11 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { const perfDisplay = document.createElement('pre'); perfDisplayContainer.appendChild(perfDisplay); - canvas.parentNode.appendChild(perfDisplayContainer); + if (canvas.parentNode) { + canvas.parentNode.appendChild(perfDisplayContainer); + } else { + console.error('canvas.parentNode is null'); + } const params = new URLSearchParams(window.location.search); const settings = { @@ -272,16 +277,16 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { } } - let startTime = undefined; + let startTime: number | undefined = undefined; const uniformTime = new Float32Array([0]); - const renderPassDescriptor: GPURenderPassDescriptor = { + const renderPassDescriptor = { colorAttachments: [ { - view: undefined, // Assigned later + view: undefined as any, // Assigned later clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, - loadOp: 'clear', - storeOp: 'store', + loadOp: 'clear' as const, + storeOp: 'store' as const, }, ], }; @@ -292,7 +297,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { recordRenderPass(renderBundleEncoder); const renderBundle = renderBundleEncoder.finish(); - return function doDraw(timestamp) { + return function doDraw(timestamp: number) { if (startTime === undefined) { startTime = timestamp; } @@ -322,19 +327,23 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { const updateSettings = () => { doDraw = configure(); }; - gui - .add(settings, 'numTriangles', 0, 200000) - .step(1) - .onFinishChange(updateSettings); - gui.add(settings, 'renderBundles'); - gui.add(settings, 'dynamicOffsets'); - - let previousFrameTimestamp = undefined; - let jsTimeAvg = undefined; - let frameTimeAvg = undefined; + if (gui === undefined) { + console.error('GUI not initialized'); + } else { + gui + .add(settings, 'numTriangles', 0, 200000) + .step(1) + .onFinishChange(updateSettings); + gui.add(settings, 'renderBundles'); + gui.add(settings, 'dynamicOffsets'); + } + + let previousFrameTimestamp: number | undefined = undefined; + let jsTimeAvg: number | undefined = undefined; + let frameTimeAvg: number | undefined = undefined; let updateDisplay = true; - function frame(timestamp) { + function frame(timestamp: number) { // Sample is no longer the active page. if (!pageState.active) return; diff --git a/src/sample/computeBoids/main.ts b/src/sample/computeBoids/main.ts index e6505bb1..ae8db740 100644 --- a/src/sample/computeBoids/main.ts +++ b/src/sample/computeBoids/main.ts @@ -1,10 +1,11 @@ -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import spriteWGSL from './sprite.wgsl'; import updateSpritesWGSL from './updateSprites.wgsl'; const init: SampleInit = async ({ canvas, pageState, gui }) => { const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'requestAdapter returned null'); const device = await adapter.requestDevice(); if (!pageState.active) return; @@ -85,13 +86,13 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { }, }); - const renderPassDescriptor: GPURenderPassDescriptor = { + const renderPassDescriptor = { colorAttachments: [ { - view: undefined, // Assigned later + view: undefined as any, // Assigned later clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, - loadOp: 'clear', - storeOp: 'store', + loadOp: 'clear' as const, + storeOp: 'store' as const, }, ], }; @@ -144,7 +145,12 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => { updateSimParams(); Object.keys(simParams).forEach((k) => { - gui.add(simParams, k).onFinishChange(updateSimParams); + const key = k as keyof typeof simParams; + if (gui === undefined) { + console.error('GUI not initialized'); + } else { + gui.add(simParams, key).onFinishChange(updateSimParams); + } }); const numParticles = 1500; diff --git a/src/sample/resizeCanvas/main.ts b/src/sample/resizeCanvas/main.ts index 58eea106..51be312f 100644 --- a/src/sample/resizeCanvas/main.ts +++ b/src/sample/resizeCanvas/main.ts @@ -1,4 +1,4 @@ -import { makeSample, SampleInit } from '../../components/SampleLayout'; +import { assert, makeSample, SampleInit } from '../../components/SampleLayout'; import triangleVertWGSL from '../../shaders/triangle.vert.wgsl'; import redFragWGSL from '../../shaders/red.frag.wgsl'; @@ -7,6 +7,7 @@ import styles from './animatedCanvasSize.module.css'; const init: SampleInit = async ({ canvas, pageState }) => { const adapter = await navigator.gpu.requestAdapter(); + assert(adapter, 'requestAdapter returned null'); const device = await adapter.requestDevice(); if (!pageState.active) return; diff --git a/src/types.d.ts b/src/types.d.ts index b12b0998..d32080f6 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -25,3 +25,6 @@ declare module '*.wgsl' { const shader: string; export default shader; } + +declare module 'stats-js'; +declare module 'stanford-dragon/4';