diff --git a/index.html b/index.html index 833d737c..c653d30d 100644 --- a/index.html +++ b/index.html @@ -67,12 +67,12 @@ <h3 style="margin-bottom: 5px">Other Pages</h3> </p> </div> <div id="sample" style="display: none;"> - <div> + <div class="sampleInfo"> <h1 id="title"></h1> <a id="src" target="_blank" rel="noreferrer" href="">See it on Github!</a> <p id="description"></p> - <div class="sampleContainer"></div> </div> + <div class="sampleContainer"></div> </div> <nav id="code" class="sourceFileNav"> <div> diff --git a/package-lock.json b/package-lock.json index 0a91320a..3faa59c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@uiw/codemirror-theme-monokai": "^4.21.24", "codemirror": "^6.0.1", "dat.gui": "^0.7.6", + "showdown": "^2.1.0", "stats.js": "github:mrdoob/stats.js#b235d9c", "teapot": "^1.0.0", "wgpu-matrix": "^2.5.0" @@ -25,6 +26,7 @@ "@rollup/plugin-typescript": "^11.1.6", "@tsconfig/recommended": "^1.0.3", "@types/dat.gui": "^0.7.12", + "@types/showdown": "^2.0.6", "@types/stats.js": "^0.17.3", "@typescript-eslint/eslint-plugin": "^7.1.1", "@webgpu/types": "^0.1.40", @@ -762,6 +764,12 @@ "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, + "node_modules/@types/showdown": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.6.tgz", + "integrity": "sha512-pTvD/0CIeqe4x23+YJWlX2gArHa8G0J0Oh6GKaVXV7TAeickpkkZiNOgFcFcmLQ5lB/K0qBJL1FtRYltBfbGCQ==", + "dev": true + }, "node_modules/@types/stats.js": { "version": "0.17.3", "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz", @@ -3782,6 +3790,29 @@ "node": ">=8" } }, + "node_modules/showdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz", + "integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==", + "dependencies": { + "commander": "^9.0.0" + }, + "bin": { + "showdown": "bin/showdown.js" + }, + "funding": { + "type": "individual", + "url": "https://www.paypal.me/tiviesantos" + } + }, + "node_modules/showdown/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", diff --git a/package.json b/package.json index d1d50e62..eadc0286 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@uiw/codemirror-theme-monokai": "^4.21.24", "codemirror": "^6.0.1", "dat.gui": "^0.7.6", + "showdown": "^2.1.0", "stats.js": "github:mrdoob/stats.js#b235d9c", "teapot": "^1.0.0", "wgpu-matrix": "^2.5.0" @@ -36,6 +37,7 @@ "@rollup/plugin-typescript": "^11.1.6", "@tsconfig/recommended": "^1.0.3", "@types/dat.gui": "^0.7.12", + "@types/showdown": "^2.0.6", "@types/stats.js": "^0.17.3", "@typescript-eslint/eslint-plugin": "^7.1.1", "@webgpu/types": "^0.1.40", diff --git a/public/css/SampleLayout.css b/public/css/SampleLayout.css index e8fd6d94..fe186a40 100644 --- a/public/css/SampleLayout.css +++ b/public/css/SampleLayout.css @@ -1,3 +1,9 @@ +#sample { + flex: 1 1 auto; + display: flex; + flex-direction: column; +} + .sampleContainer { text-align: center; width: 100%; diff --git a/public/css/styles.css b/public/css/styles.css index 57618326..c96b0b41 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -30,6 +30,8 @@ a:hover { } main { + display: flex; + flex-direction: column; position: relative; flex: 1; background: black; diff --git a/rollup.config.js b/rollup.config.js index 33d1fa91..d58c8181 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -112,6 +112,7 @@ export default [ ], plugins: [ nodeResolve(), + commonjs(), filenamePlugin(), typescript({ tsconfig: './src/tsconfig.json' }), ], diff --git a/sample/bundleCulling/meta.ts b/sample/bundleCulling/meta.ts new file mode 100644 index 00000000..4aa14adb --- /dev/null +++ b/sample/bundleCulling/meta.ts @@ -0,0 +1,10 @@ +export default { + name: 'Bundle Culling', + description: `A demonstration of using frustum culling with render bundles through indirect instanced draw calls. + +Source at https://github.com/toji/webgpu-bundle-culling/ +`, + filename: __DIRNAME__, + url: 'https://toji.github.io/webgpu-bundle-culling/', + sources: [], +}; diff --git a/sample/clusteredShading/meta.ts b/sample/clusteredShading/meta.ts new file mode 100644 index 00000000..ff79e373 --- /dev/null +++ b/sample/clusteredShading/meta.ts @@ -0,0 +1,10 @@ +export default { + name: 'Clustered Shading', + description: `Shows a simple clustered forward shading renderer. + +Source at https://github.com/toji/webgpu-clustered-shading/ +`, + filename: __DIRNAME__, + url: 'https://toji.github.io/webgpu-clustered-shading/', + sources: [], +}; diff --git a/sample/metaballs/meta.ts b/sample/metaballs/meta.ts new file mode 100644 index 00000000..e0b01b20 --- /dev/null +++ b/sample/metaballs/meta.ts @@ -0,0 +1,10 @@ +export default { + name: 'Metaballs', + description: `This example shows an implementation of metaballs with WebGPU. + +Source at https://github.com/toji/webgpu-metaballs/ +`, + filename: __DIRNAME__, + url: 'https://toji.github.io/webgpu-metaballs/', + sources: [], +}; diff --git a/sample/pristineGrid/meta.ts b/sample/pristineGrid/meta.ts new file mode 100644 index 00000000..55784fb9 --- /dev/null +++ b/sample/pristineGrid/meta.ts @@ -0,0 +1,10 @@ +export default { + name: 'Pristine Grid', + description: `A simple WebGPU implementation of the "Pristine Grid" technique described in this wonderful little blog post: https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8. + +Source at https://github.com/toji/pristine-grid-webgpu/ +`, + filename: __DIRNAME__, + url: 'https://toji.github.io/pristine-grid-webgpu/', + sources: [], +}; diff --git a/sample/spookyball/meta.ts b/sample/spookyball/meta.ts new file mode 100644 index 00000000..b1145bcb --- /dev/null +++ b/sample/spookyball/meta.ts @@ -0,0 +1,10 @@ +export default { + name: 'Spookyball', + description: `This example shows a simple game made with WebGPU. + +Source at https://github.com/toji/spookyball +`, + filename: __DIRNAME__, + url: 'https://spookyball.com', + sources: [], +}; diff --git a/src/main.ts b/src/main.ts index c29f90a6..0eda53c2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,6 +5,11 @@ import { EditorView } from '@codemirror/view'; import { EditorState } from '@codemirror/state'; import { javascript } from '@codemirror/lang-javascript'; import { basicSetup } from 'codemirror'; +import { Converter } from 'showdown'; + +const markdownConverter = new Converter({ + simplifiedAutoLink: true, +}); /** * Gets an element unconditionally so TS doesn't complain. @@ -84,6 +89,11 @@ function setSourceTabHash(event: PointerEvent, sourceInfo: SourceInfo) { setSourceTab(sourceInfo); } +// Non authoritative test that url is for same domain +function isSameDomain(url: string) { + return new URL(url, window.location.href).origin === window.location.origin; +} + // That current sample so we don't reload an iframe if the user picks the same sample. let currentSampleInfo: SampleInfo | undefined; @@ -100,10 +110,10 @@ function setSampleIFrame( return; } sampleContainerElem.innerHTML = ''; - descriptionElem.textContent = ''; + descriptionElem.innerHTML = ''; currentSampleInfo = sampleInfo; - const { name, description, filename, sources } = sampleInfo || { + const { name, description, filename, url, sources } = sampleInfo || { name: '', description: '', filename: '', @@ -111,14 +121,15 @@ function setSampleIFrame( }; titleElem.textContent = name; - descriptionElem.textContent = description; + descriptionElem.innerHTML = markdownConverter.makeHtml(description); // Replace the iframe because changing src adds to the user's history. sampleContainerElem.innerHTML = ''; if (filename) { - sampleContainerElem.appendChild( - el('iframe', { src: `${filename}${search}`, style: { height: '600px' } }) - ); + const src = url || `${filename}${search}`; + sampleContainerElem.appendChild(el('iframe', { src })); + sampleContainerElem.style.height = sources.length > 0 ? '600px' : '100%'; + // hide intro and show sample introElem.style.display = 'none'; sampleElem.style.display = ''; @@ -131,13 +142,15 @@ function setSampleIFrame( // create source tabs codeTabsElem.innerHTML = ''; sourcesElem.innerHTML = ''; + sourcesElem.style.display = sources.length > 0 ? '' : 'none'; sources.forEach((source, i) => { + const { path } = source; const active = (i === 0).toString(); const name = basename(source.path); codeTabsElem.appendChild( el('li', {}, [ el('a', { - href: `#${source.path}`, + href: `#${path}`, textContent: name, dataset: { active, @@ -157,7 +170,8 @@ function setSampleIFrame( }, }); sourcesElem.appendChild(elem); - makeCodeMirrorEditor(elem, `${filename}/${source.path}`); + const url = isSameDomain(path) ? `${filename}/${path}` : source.path; + makeCodeMirrorEditor(elem, url); }); } diff --git a/src/samples.ts b/src/samples.ts index f7facdd0..212e2585 100644 --- a/src/samples.ts +++ b/src/samples.ts @@ -1,7 +1,9 @@ import aBuffer from '../sample/a-buffer/meta'; import animometer from '../sample/animometer/meta'; import bitonicSort from '../sample/bitonicSort/meta'; +import bundleCulling from '../sample/bundleCulling/meta'; import cameras from '../sample/cameras/meta'; +import clusteredShading from '../sample/clusteredShading/meta'; import cornell from '../sample/cornell/meta'; import computeBoids from '../sample/computeBoids/meta'; import cubemap from '../sample/cubemap/meta'; @@ -12,8 +14,10 @@ import helloTriangle from '../sample/helloTriangle/meta'; import helloTriangleMSAA from '../sample/helloTriangleMSAA/meta'; import imageBlur from '../sample/imageBlur/meta'; import instancedCube from '../sample/instancedCube/meta'; +import metaballs from '../sample/metaballs/meta'; import normalMap from '../sample/normalMap/meta'; import particles from '../sample/particles/meta'; +import pristineGrid from '../sample/pristineGrid/meta'; import renderBundles from '../sample/renderBundles/meta'; import resizeCanvas from '../sample/resizeCanvas/meta'; import reversedZ from '../sample/reversedZ/meta'; @@ -21,6 +25,7 @@ import rotatingCube from '../sample/rotatingCube/meta'; import samplerParameters from '../sample/samplerParameters/meta'; import shadowMapping from '../sample/shadowMapping/meta'; import skinnedMesh from '../sample/skinnedMesh/meta'; +import spookyball from '../sample/spookyball/meta'; import texturedCube from '../sample/texturedCube/meta'; import twoCubes from '../sample/twoCubes/meta'; import videoUploading from '../sample/videoUploading/meta'; @@ -34,7 +39,8 @@ export type SampleInfo = { name: string; tocName?: string; description: string; - filename: string; + filename: string; // used if sample is local + url?: string; // used if sample is remote sources: SourceInfo[]; }; @@ -74,6 +80,7 @@ export const pageCategories: PageCategory[] = [ samplerParameters, reversedZ, renderBundles, + spookyball, }, }, @@ -123,6 +130,19 @@ export const pageCategories: PageCategory[] = [ }, }, + // External examples + { + title: 'External Samples', + description: `Samples from around the net.`, + samples: { + bundleCulling, + metaballs, + pristineGrid, + clusteredShading, + spookyball, + }, + }, + // Samples whose primary purpose is to benchmark WebGPU performance. { title: 'Benchmarks',