Skip to content

Commit

Permalink
Organize Sample List (#358)
Browse files Browse the repository at this point in the history
* Encapsulated sample page link logic into its own component, which will be necessary to prevent duplication of the sample initialization code for each sub category of samples

* Test

* New sidebar layout

* First draft

* Sketch dropdown before merging main into this branch

* Dropdowns complete. Note that dropdowns can easily be removed if they are deemed unecessary for the functionality of this webpage

* fix build errors

* Implemented most suggested changes, will have further discussions about categorization

* Moved stuffed around, added comments justifying each category

* Capitalize GPGPU

* Added more specific comments to gpgpu section

* Cambios pequenos
  • Loading branch information
cmhhelgeson authored Feb 29, 2024
1 parent 41f3e5c commit 5424f6f
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 60 deletions.
8 changes: 8 additions & 0 deletions src/components/SampleCategory.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.sampleCategory {
display: flex;
}


li.selected a {
color: #ff0000;
}
84 changes: 84 additions & 0 deletions src/components/SampleCategory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import styles from './SampleCategory.module.css';

import { NextRouter } from 'next/router';
import Link from 'next/link';
import { PageCategory } from '../pages/samples/[slug]';

type PageType = {
[key: string]: React.ComponentType & { render: { preload: () => void } };
};

type PageComponentType = {
[key: string]: React.ComponentType;
};

interface SampleCategoryProps {
category: PageCategory;
router: NextRouter;
onClickPageLink: () => void;
}

export const SampleCategory = ({
category,
onClickPageLink,
router,
}: SampleCategoryProps) => {
const { title, pages, sampleNames } = category;
return (
<div>
<div className={styles.sampleCategory}>
<h3
style={{
marginTop: '5px',
}}
>
{title}
</h3>
</div>
{sampleNames.map((slug) => {
return (
<SampleLink
key={`samples/${slug}`}
slug={slug}
router={router}
pages={pages}
onClick={() => onClickPageLink()}
/>
);
})}
</div>
);
};

interface SampleLinkProps {
router: NextRouter;
slug: string;
pages: PageComponentType;
onClick: () => void;
}

export const SampleLink = ({
router,
slug,
pages,
onClick,
}: SampleLinkProps) => {
const className =
router.pathname === `/samples/[slug]` && router.query['slug'] === slug
? styles.selected
: undefined;

return (
<li
key={slug}
className={className}
onMouseOver={() => {
(pages as PageType)[slug].render.preload();
}}
>
<Link href={`/samples/${slug}`} onClick={() => onClick()}>
{slug}
</Link>
</li>
);
};
11 changes: 5 additions & 6 deletions src/pages/MainLayout.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@
margin-block-end: 16px;
}

.exampleList h3 {
color: rgb(43, 126, 171);
}

.exampleList li {
list-style: none;
padding: 0.3em 0;
}

.exampleList li.selected a {
color: #ff0000;
}

.expand {
display: none;
float: right;
Expand All @@ -45,7 +45,6 @@
.panel .panelContents {
display: block;
transition: max-height 0s;
overflow: none;
max-height: 100vh;
}

Expand All @@ -67,12 +66,12 @@
.panel .panelContents {
display: block;
transition: max-height 0.3s ease-out;
overflow: hidden;
max-height: 0px;
}

.panel[data-expanded='false'] .panelContents {
max-height: 0vh;
overflow: hidden;
}

.panel[data-expanded='true'] .panelContents {
Expand Down
56 changes: 21 additions & 35 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,16 @@ import { useMemo, memo, useState } from 'react';
import './styles.css';
import styles from './MainLayout.module.css';

import { pages } from './samples/[slug]';
import { pageCategories } from './samples/[slug]';
import { SampleCategory } from '../components/SampleCategory';

const title = 'WebGPU Samples';

type PageType = {
[key: string]: React.ComponentType & { render: { preload: () => void } };
};

const MainLayout: React.FunctionComponent<AppProps> = ({
Component,
pageProps,
}) => {
const router = useRouter();
const samplesNames = Object.keys(pages);
const [listExpanded, setListExpanded] = useState<boolean>(false);

const ComponentMemo = useMemo(() => {
Expand Down Expand Up @@ -66,36 +62,26 @@ const MainLayout: React.FunctionComponent<AppProps> = ({
Github
</a>
<hr />
<ul className={styles.exampleList}>
{samplesNames.map((slug) => {
const className =
router.pathname === `/samples/[slug]` &&
router.query['slug'] === slug
? styles.selected
: undefined;
return (
<li
key={slug}
className={className}
onMouseOver={() => {
(pages as PageType)[slug].render.preload();
}}
>
<Link
href={`/samples/${slug}`}
onClick={() => {
setListExpanded(false);
}}
>
{slug}
</Link>
</li>
);
})}
</ul>
{pageCategories.map((category) => {
return (
<ul
className={styles.exampleList}
key={`/categories/${category.title}`}
>
<SampleCategory
category={category}
router={router}
onClickPageLink={() => setListExpanded(false)}
/>
</ul>
);
})}
<hr />
<h3>Other Pages</h3>
<ul className={styles.exampleList}>
<h3 style={{ marginBottom: '5px' }}>Other Pages</h3>
<ul
style={{ margin: '0px', paddingBottom: '20px' }}
className={styles.exampleList}
>
<li>
<a
rel="noreferrer"
Expand Down
105 changes: 86 additions & 19 deletions src/pages/samples/[slug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,55 +13,122 @@ type PageComponentType = {
[key: string]: React.ComponentType;
};

export const pages: PageComponentType = {
// Samples that implement basic rendering functionality using the WebGPU API.
const graphicsBasicsPages: PageComponentType = {
helloTriangle: dynamic(() => import('../../sample/helloTriangle/main')),
helloTriangleMSAA: dynamic(
() => import('../../sample/helloTriangleMSAA/main')
),
resizeCanvas: dynamic(() => import('../../sample/resizeCanvas/main')),
rotatingCube: dynamic(() => import('../../sample/rotatingCube/main')),
twoCubes: dynamic(() => import('../../sample/twoCubes/main')),
texturedCube: dynamic(() => import('../../sample/texturedCube/main')),
instancedCube: dynamic(() => import('../../sample/instancedCube/main')),
fractalCube: dynamic(() => import('../../sample/fractalCube/main')),
cameras: dynamic(() => import('../../sample/cameras/main')),
cubemap: dynamic(() => import('../../sample/cubemap/main')),
computeBoids: dynamic(() => import('../../sample/computeBoids/main')),
animometer: dynamic(() => import('../../sample/animometer/main')),
videoUploading: dynamic(() => import('../../sample/videoUploading/main')),
videoUploadingWebCodecs: dynamic(
() => import('../../sample/videoUploadingWebCodecs/main')
),
};

// Samples that demonstrate functionality specific to WebGPU, or demonstrate the particularities
// of how WebGPU implements a particular feature within its api. For instance, while many of the
// sampler parameters in the 'samplerParameters' sample have direct analogues in other graphics api,
// the primary purpose of 'sampleParameters' is to demonstrate their specific nomenclature and
// functionality within the context of the WebGPU API.
const webGPUFeaturesPages: PageComponentType = {
samplerParameters: dynamic(
() => import('../../sample/samplerParameters/main')
),
imageBlur: dynamic(() => import('../../sample/imageBlur/main')),
shadowMapping: dynamic(() => import('../../sample/shadowMapping/main')),
reversedZ: dynamic(() => import('../../sample/reversedZ/main')),
renderBundles: dynamic(() => import('../../sample/renderBundles/main')),
};

// A selection of samples demonstrating various graphics techniques, utilizing various features
// of the WebGPU API, and often executing render and compute pipelines in tandem to achieve their
// visual results. The techniques demonstrated may even be independent of WebGPU (e.g. 'cameras')
const graphicsDemoPages: PageComponentType = {
cameras: dynamic(() => import('../../sample/cameras/main')),
normalMap: dynamic(() => import('../../sample/normalMap/main')),
shadowMapping: dynamic(() => import('../../sample/shadowMapping/main')),
deferredRendering: dynamic(
() => import('../../sample/deferredRendering/main')
),
particles: dynamic(() => import('../../sample/particles/main')),
imageBlur: dynamic(() => import('../../sample/imageBlur/main')),
cornell: dynamic(() => import('../../sample/cornell/main')),
gameOfLife: dynamic(() => import('../../sample/gameOfLife/main')),
renderBundles: dynamic(() => import('../../sample/renderBundles/main')),
worker: dynamic(() => import('../../sample/worker/main')),
'A-buffer': dynamic(() => import('../../sample/a-buffer/main')),
bitonicSort: dynamic(() => import('../../sample/bitonicSort/main')),
normalMap: dynamic(() => import('../../sample/normalMap/main')),
skinnedMesh: dynamic(() => import('../../sample/skinnedMesh/main')),
};

// Samples that demonstrate the GPGPU functionality of WebGPU. These samples generally provide some
// user-facing representation (e.g. image, text, or audio) of the result of compute operations.
// Any rendering code is primarily for visualization, not key to the unique part of the sample;
// rendering could also be done using canvas2D without detracting from the sample's usefulness.
const gpuComputeDemoPages: PageComponentType = {
computeBoids: dynamic(() => import('../../sample/computeBoids/main')),
gameOfLife: dynamic(() => import('../../sample/gameOfLife/main')),
bitonicSort: dynamic(() => import('../../sample/bitonicSort/main')),
};

// Samples that demonstrate how to integrate WebGPU and/or WebGPU render operations with other
// functionalities provided by the web platform.
const webPlatformPages: PageComponentType = {
resizeCanvas: dynamic(() => import('../../sample/resizeCanvas/main')),
videoUploading: dynamic(() => import('../../sample/videoUploading/main')),
videoUploadingWebCodecs: dynamic(
() => import('../../sample/videoUploadingWebCodecs/main')
),
worker: dynamic(() => import('../../sample/worker/main')),
};

// Samples whose primary purpose is to benchmark WebGPU performance.
const benchmarkPages: PageComponentType = {
animometer: dynamic(() => import('../../sample/animometer/main')),
};

const pages: PageComponentType = {
...graphicsBasicsPages,
...webGPUFeaturesPages,
...graphicsDemoPages,
...gpuComputeDemoPages,
...webPlatformPages,
...benchmarkPages,
};

export interface PageCategory {
title: string;
pages: PageComponentType;
sampleNames: string[];
}

const createPageCategory = (
title: string,
pages: PageComponentType
): PageCategory => {
return {
title,
pages,
sampleNames: Object.keys(pages),
};
};

export const pageCategories: PageCategory[] = [
createPageCategory('Basic Graphics', graphicsBasicsPages),
createPageCategory('WebGPU Features', webGPUFeaturesPages),
createPageCategory('GPGPU Demos', gpuComputeDemoPages),
createPageCategory('Graphics Techniques', graphicsDemoPages),
createPageCategory('Web Platform Integration', webPlatformPages),
createPageCategory('Benchmarks', benchmarkPages),
];

function Page({ slug }: Props): JSX.Element {
const PageComponent = pages[slug];
return <PageComponent />;
}

export const getStaticPaths: GetStaticPaths<PathParams> = async () => {
const paths = Object.keys(pages).map((p) => ({
params: { slug: p },
}));
return {
paths: Object.keys(pages).map((p) => {
return { params: { slug: p } };
}),
paths,
fallback: false,
};
};
Expand Down
5 changes: 5 additions & 0 deletions src/pages/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ a:hover {
text-decoration: underline;
}

h3 {
margin-bottom: 5px;
margin-top: 5px;
}

main {
position: relative;
flex: 1;
Expand Down

0 comments on commit 5424f6f

Please sign in to comment.