diff --git a/apps/typegpu-docs/astro.config.mjs b/apps/typegpu-docs/astro.config.mjs index a6d7473434..ade4ec82f3 100644 --- a/apps/typegpu-docs/astro.config.mjs +++ b/apps/typegpu-docs/astro.config.mjs @@ -39,6 +39,10 @@ export default defineConfig({ markdown: { remarkPlugins: [remarkMath], rehypePlugins: [rehypeMathJax], + shikiConfig: { + theme: 'one-dark-pro', + wrap: true, + }, }, vite: { define: { diff --git a/apps/typegpu-docs/src/assets/Group.svg b/apps/typegpu-docs/src/assets/Group.svg new file mode 100644 index 0000000000..94412e98af --- /dev/null +++ b/apps/typegpu-docs/src/assets/Group.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/typegpu-docs/src/assets/externalopen.svg b/apps/typegpu-docs/src/assets/externalopen.svg new file mode 100644 index 0000000000..f7589a04f8 --- /dev/null +++ b/apps/typegpu-docs/src/assets/externalopen.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/typegpu-docs/src/assets/hero/as_a_foundation.svg b/apps/typegpu-docs/src/assets/hero/as_a_foundation.svg new file mode 100644 index 0000000000..4d652e9892 --- /dev/null +++ b/apps/typegpu-docs/src/assets/hero/as_a_foundation.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/typegpu-docs/src/assets/hero/as_a_puzzle_piece.svg b/apps/typegpu-docs/src/assets/hero/as_a_puzzle_piece.svg new file mode 100644 index 0000000000..eef4d648ac --- /dev/null +++ b/apps/typegpu-docs/src/assets/hero/as_a_puzzle_piece.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/typegpu-docs/src/assets/hero/caustics_bg.gif b/apps/typegpu-docs/src/assets/hero/caustics_bg.gif new file mode 100644 index 0000000000..f771b692c9 Binary files /dev/null and b/apps/typegpu-docs/src/assets/hero/caustics_bg.gif differ diff --git a/apps/typegpu-docs/src/assets/hero/fish_bg.gif b/apps/typegpu-docs/src/assets/hero/fish_bg.gif new file mode 100644 index 0000000000..17613378eb Binary files /dev/null and b/apps/typegpu-docs/src/assets/hero/fish_bg.gif differ diff --git a/apps/typegpu-docs/src/assets/hero/for_libraries.svg b/apps/typegpu-docs/src/assets/hero/for_libraries.svg new file mode 100644 index 0000000000..e7868ca23c --- /dev/null +++ b/apps/typegpu-docs/src/assets/hero/for_libraries.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/typegpu-docs/src/assets/hero/jelly_slider_bg.png b/apps/typegpu-docs/src/assets/hero/jelly_slider_bg.png new file mode 100644 index 0000000000..1f714da4ed Binary files /dev/null and b/apps/typegpu-docs/src/assets/hero/jelly_slider_bg.png differ diff --git a/apps/typegpu-docs/src/assets/hero/perlin_noise_bg.gif b/apps/typegpu-docs/src/assets/hero/perlin_noise_bg.gif new file mode 100644 index 0000000000..6c17bf3a1d Binary files /dev/null and b/apps/typegpu-docs/src/assets/hero/perlin_noise_bg.gif differ diff --git a/apps/typegpu-docs/src/assets/hero/ray_march_bg.gif b/apps/typegpu-docs/src/assets/hero/ray_march_bg.gif new file mode 100644 index 0000000000..56c9e972a8 Binary files /dev/null and b/apps/typegpu-docs/src/assets/hero/ray_march_bg.gif differ diff --git a/apps/typegpu-docs/src/assets/hero/reflection_bg.png b/apps/typegpu-docs/src/assets/hero/reflection_bg.png new file mode 100644 index 0000000000..63f551bb56 Binary files /dev/null and b/apps/typegpu-docs/src/assets/hero/reflection_bg.png differ diff --git a/apps/typegpu-docs/src/assets/moon.svg b/apps/typegpu-docs/src/assets/moon.svg new file mode 100644 index 0000000000..a091833730 --- /dev/null +++ b/apps/typegpu-docs/src/assets/moon.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/typegpu-docs/src/assets/sun.svg b/apps/typegpu-docs/src/assets/sun.svg new file mode 100644 index 0000000000..ebdbb6e9a8 --- /dev/null +++ b/apps/typegpu-docs/src/assets/sun.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/apps/typegpu-docs/src/assets/youtube.svg b/apps/typegpu-docs/src/assets/youtube.svg new file mode 100644 index 0000000000..f4e653d625 --- /dev/null +++ b/apps/typegpu-docs/src/assets/youtube.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/typegpu-docs/src/components/Backlog.astro b/apps/typegpu-docs/src/components/Backlog.astro new file mode 100644 index 0000000000..35aa5dd0ec --- /dev/null +++ b/apps/typegpu-docs/src/components/Backlog.astro @@ -0,0 +1,148 @@ +--- +import { Image } from "astro:assets"; +import as_a_foundation from "../assets/hero/as_a_foundation.svg"; +import as_a_puzzle_piece from "../assets/hero/as_a_puzzle_piece.svg"; +import for_libraries from "../assets/hero/for_libraries.svg"; +--- + +
+ +
+
+

Backlog

+
+ For libraries icon +
+
+ +
+
+

Imperative code

+

+ Perform multiple background tasks at the same time and improve the + every... +

+
+
+

Need something?

+

+ Let us know about your improvement ideas, maybe they will land here! +

+
+
+
+ + +
+
+

In progress

+
+ As a puzzle piece icon +
+
+ +
+
+

Pipelines

+

+ Perform multiple background tasks at the same time and improve the + every... +

+
+ Planned for late 2025 +
+
+
+
+ + +
+
+

Shipped

+
+ As a foundation icon +
+
+
+
+

Data structures & buffers

+

+ Perform multiple background tasks at the same time and improve the + every... +

+
+ May 2024 + v0.1 +
+
+ +
+

Bind groups

+

+ Perform multiple background tasks at the same time and improve the + every... +

+
+ Oct 2024 + v0.2 +
+
+ +
+

Linker

+

+ Perform multiple background tasks at the same time and improve the + every... +

+
+ Feb 2025 + v0.3 +
+
+ +
+

Functions

+

+ Perform multiple background tasks at the same time and improve the + every... +

+
+ Feb 2025 + v0.3 +
+
+
+
+
diff --git a/apps/typegpu-docs/src/components/CodeButton.astro b/apps/typegpu-docs/src/components/CodeButton.astro new file mode 100644 index 0000000000..380c1fd961 --- /dev/null +++ b/apps/typegpu-docs/src/components/CodeButton.astro @@ -0,0 +1,29 @@ +--- +interface Props { + title: string; + message: string; + icon: ((_props: astroHTML.JSX.SVGAttributes) => any) & ImageMetadata; +} + +const { title, message, icon } = Astro.props as Props; +--- + +
+
+
+ {icon && Icon} +
+ +

+ {title} +

+ +

+ {message} +

+
+
diff --git a/apps/typegpu-docs/src/components/CodePen.astro b/apps/typegpu-docs/src/components/CodePen.astro new file mode 100644 index 0000000000..de25c41187 --- /dev/null +++ b/apps/typegpu-docs/src/components/CodePen.astro @@ -0,0 +1,56 @@ +--- +import { Code } from "astro/components"; + +type SupportedLanguages = + | "typescript" + | "javascript" + | "jsx" + | "tsx" + | "html" + | "css" + | "json" + | "yaml" + | "markdown" + | "bash" + | "shell"; + +interface Props { + code: string; + language?: SupportedLanguages; +} + +const { code, language = "typescript" } = Astro.props as Props; + +const formattedCode = code.trim(); +--- + +
+
+ +
+
+ + diff --git a/apps/typegpu-docs/src/components/CodeShowcase.astro b/apps/typegpu-docs/src/components/CodeShowcase.astro new file mode 100644 index 0000000000..ff25c51ba5 --- /dev/null +++ b/apps/typegpu-docs/src/components/CodeShowcase.astro @@ -0,0 +1,183 @@ +--- +import CodeButton from "./CodeButton.astro"; +import CodePen from "./CodePen.astro"; +import as_a_foundation from "../assets/hero/as_a_foundation.svg"; +import as_a_puzzle_piece from "../assets/hero/as_a_puzzle_piece.svg"; +import for_libraries from "../assets/hero/for_libraries.svg"; + +const sectionId = "interactive-code-section"; +--- + +
+
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+ , unknown][], + entryFn: TgpuComputeFn, +) { + return new TgpuComputePipelineImpl( + new ComputePipelineCore(branch, slotBindings, entryFn), + {}, + ); +}`} + language="typescript" + /> + , unknown][], + entryFn: TgpuComputeFn, + ) { + return new TgpuComputePipelineImpl( + new ComputePipelineCore(branch, slotBindings, entryFn), + {}, + ); + export interface TgpuComputePipeline + extends TgpuNamable, SelfResolvable, Timeable { + readonly [$internal]: ComputePipelineInternals; + readonly resourceType: 'compute-pipeline'; + + + with( + bindGroupLayout: TgpuBindGroupLayout, + bindGroup: TgpuBindGroup, + ): TgpuComputePipeline; + + dispatchWorkgroups( + x: number, + y?: number | undefined, + z?: number | undefined, + ): void; +} + +}`} + language="typescript" + /> + , unknown][], + entryFn: TgpuComputeFn, + ) { + return new TgpuComputePipelineImpl( + new ComputePipelineCore(branch, slotBindings, entryFn), + {}, + ); + with( + bindGroupLayout: TgpuBindGroupLayout, + bindGroup: TgpuBindGroup, + ): TgpuComputePipeline; + + dispatchWorkgroups( + x: number, + y?: number | undefined, + z?: number | undefined, + ): void; + export interface TgpuComputePipeline + extends TgpuNamable, SelfResolvable, Timeable { + readonly [$internal]: ComputePipelineInternals; + readonly resourceType: 'compute-pipeline'; + + +} + + }`} + language="typescript" + /> +
+
+ + + + diff --git a/apps/typegpu-docs/src/components/GetStartedButton.astro b/apps/typegpu-docs/src/components/GetStartedButton.astro index ac722ec1d0..66feb3e52f 100644 --- a/apps/typegpu-docs/src/components/GetStartedButton.astro +++ b/apps/typegpu-docs/src/components/GetStartedButton.astro @@ -1,16 +1,33 @@ +--- +interface Props { + title?: string; + href?: string; + class?: string; +} + +const { + title = "Get started", + href = "/TypeGPU/getting-started", + class: className, +} = Astro.props; +--- + - Get started + {href} +> + {title} + fill="none" + > + {title} + + + diff --git a/apps/typegpu-docs/src/components/HoverExampleIsland.tsx b/apps/typegpu-docs/src/components/HoverExampleIsland.tsx new file mode 100644 index 0000000000..3de1414f50 --- /dev/null +++ b/apps/typegpu-docs/src/components/HoverExampleIsland.tsx @@ -0,0 +1,207 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import type { + PointerEvent as ReactPointerEvent, + TouchEvent as ReactTouchEvent, +} from 'react'; +import { executeExample } from '../utils/examples/exampleRunner.ts'; +import { isGPUSupported } from '../utils/isGPUSupported.ts'; +import type { Example } from '../utils/examples/types.ts'; + +type Props = { + exampleKey: string; +}; + +type CleanupFn = () => void; + +async function loadExample(exampleKey: string): Promise { + const exampleContent = await import('../examples/exampleContent.ts'); + const examples = exampleContent.examples as Record; + + const example = examples[exampleKey] as Example | undefined; + if (!example) { + throw new Error(`Example "${exampleKey}" not found.`); + } + + return example; +} + +function resizeCanvases(container: HTMLElement) { + for (const canvas of container.querySelectorAll('canvas')) { + const dpr = window.devicePixelRatio || 1; + const rect = canvas.getBoundingClientRect(); + canvas.width = rect.width * dpr; + canvas.height = rect.height * dpr * 2; + canvas.style.width = '100%'; + canvas.style.height = '100%'; + } +} + +export default function HoverExampleIsland({ exampleKey }: Props) { + const rootRef = useRef(null); + const containerRef = useRef(null); + const cleanupRef = useRef(undefined); + const twoFingerActiveRef = useRef(false); + const [isHovered, setIsHovered] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(); + + const reset = useCallback(() => { + cleanupRef.current?.(); + cleanupRef.current = undefined; + if (containerRef.current) { + containerRef.current.innerHTML = ''; + } + }, []); + + const handlePointerEnter = (event: ReactPointerEvent) => { + if (event.pointerType !== 'touch') { + setIsHovered(true); + } + }; + + const handlePointerLeave = (event: ReactPointerEvent) => { + if (event.pointerType !== 'touch') { + setIsHovered(false); + } + }; + + const handleTouchStart = (event: ReactTouchEvent) => { + if (event.touches.length >= 2) { + event.preventDefault(); + twoFingerActiveRef.current = true; + } + }; + + const handleTouchMove = (event: ReactTouchEvent) => { + if (twoFingerActiveRef.current) { + event.preventDefault(); + } + }; + + const handleTouchEnd = (event: ReactTouchEvent) => { + if (event.touches.length === 0) { + if (!twoFingerActiveRef.current) { + return; + } + + twoFingerActiveRef.current = false; + setIsHovered((prev) => !prev); + } + }; + + const handleTouchCancel = () => { + twoFingerActiveRef.current = false; + }; + + useEffect(() => { // intersection observer + const element = rootRef.current; + if (!element) { + return; + } + + const observer = new IntersectionObserver(([entry]) => { + if (!entry.isIntersecting) { + twoFingerActiveRef.current = false; + setIsHovered(false); + } + }); + + observer.observe(element); + return () => observer.disconnect(); + }, []); + + useEffect(() => { // data hover overlay + const overlay = containerRef.current?.closest('[data-hover-overlay]'); + if (!overlay) { + return; + } + + if (isHovered) { + overlay.setAttribute('data-active', 'true'); + } else { + overlay.removeAttribute('data-active'); + } + + return () => overlay.removeAttribute('data-active'); + }, [isHovered]); + + useEffect(() => { + if (!isHovered) { + reset(); + setIsLoading(false); + return; + } + + let cancelled = false; + (async () => { + try { + setIsLoading(true); + setError(undefined); + if (!isGPUSupported) { + throw new Error('WebGPU is not enabled/supported in this browser.'); + } + + const example = await loadExample(exampleKey); + if (!containerRef.current) { + return; + } + + containerRef.current.innerHTML = example.htmlFile.content; + resizeCanvases(containerRef.current); + + const { dispose } = await executeExample(example.tsImport); + if (cancelled) { + dispose(); + return; + } + + cleanupRef.current = dispose; + } catch (err) { + console.error(err); + setError( + err instanceof Error ? err.message : 'Failed to load example.', + ); + reset(); + } finally { + if (!cancelled) { + setIsLoading(false); + } + } + })(); + + return () => { + cancelled = true; + reset(); + }; + }, [exampleKey, isHovered, reset]); + + return ( +
+ {error + ? ( +

+ {error} +

+ ) + : ( +
+ {isLoading && ( + + Loading… + + )} +
+
+ )} +
+ ); +} diff --git a/apps/typegpu-docs/src/components/TgpuExamples.astro b/apps/typegpu-docs/src/components/TgpuExamples.astro new file mode 100644 index 0000000000..a20910444c --- /dev/null +++ b/apps/typegpu-docs/src/components/TgpuExamples.astro @@ -0,0 +1,94 @@ +--- +import { Image } from "astro:assets"; +import ExternalOpenSvg from "../assets/externalopen.svg"; +import CausticsGif from "../assets/hero/caustics_bg.gif"; +import RayMarchingGif from "../assets/hero/ray_march_bg.gif"; +import JellySliderGif from "../assets/hero/jelly_slider_bg.png"; +import FishGif from "../assets/hero/fish_bg.gif"; +import ReflectionGif from "../assets/hero/reflection_bg.png"; + +import HoverExampleIsland from "./HoverExampleIsland"; + +const galleryItems = [ + { + asset: ReflectionGif, + title: "Reflection", + exampleKey: "rendering--cubemap-reflection", + }, + { + asset: FishGif, + title: "VaporRave", + exampleKey: "simple--vaporrave", + }, + { + asset: RayMarchingGif, + title: "Ray Marching", + exampleKey: "rendering--ray-marching", + }, + { + asset: FishGif, + title: "3D Fish", + exampleKey: "rendering--3d-fish", + }, + { + asset: JellySliderGif, + title: "Jelly Slider", + exampleKey: "rendering--jelly-slider", + }, + { + asset: CausticsGif, + title: "Caustics", + exampleKey: "rendering--caustics", + }, +]; +--- + +{/* Interactive gallery */} +
+ Hint: tap the example with two fingers to preview +
+
diff --git a/apps/typegpu-docs/src/components/ThemeSwitcher.astro b/apps/typegpu-docs/src/components/ThemeSwitcher.astro new file mode 100644 index 0000000000..51f7618716 --- /dev/null +++ b/apps/typegpu-docs/src/components/ThemeSwitcher.astro @@ -0,0 +1,64 @@ +--- +import { Image } from "astro:assets"; +import LightModeIconSvg from "../assets/sun.svg"; +import DarkModeIconSvg from "../assets/moon.svg"; +--- + +
+ + + +
+ + diff --git a/apps/typegpu-docs/src/components/Videos.astro b/apps/typegpu-docs/src/components/Videos.astro new file mode 100644 index 0000000000..5d4cd6d2b2 --- /dev/null +++ b/apps/typegpu-docs/src/components/Videos.astro @@ -0,0 +1,65 @@ +--- +import YoutubeSvg from "../assets/youtube.svg"; + +const videos = [ + { + title: "Iwo Plaza – Your GPU is a JavaScript runtime* (TypeGPU deep-dive)", + src: "https://www.youtube.com/embed/pBRLqJaG4kk?si=-xaSG_MET9-Q1ZfK", + className: "md:col-span-2", + }, + { + title: "TypeGPU: enhance your WebGPU project with type-safe API", + src: "https://www.youtube.com/embed/71TO3OIgStM?si=y96EGOPGKv0lL1D7", + }, + { + title: "Simplifying Compute Shaders with TypeGPU: GPU Worklets & More", + src: "https://www.youtube.com/embed/QrHZXiXxu9A?si=CSaZznbx0hf3PlQj", + }, + { + title: "Iwo Plaza – The Road to Type-Safety on the GPU | RNCK #15", + src: "https://www.youtube.com/embed/DysmnC1D2gQ?si=0pR77V9xatZdX2Co", + }, + { + title: "Live-Coding a Liquid Glass Effect with TypeGPU", + src: "https://www.youtube.com/embed/5PREvbHirEY?si=N2Cs3xn9IG8_Wh9g", + }, +]; +--- + +
+ { + videos.map((video) => ( +
+ + Watch on YouTube + {video.title} + +