From 9e9e3902d65d2a97f6132ce9aeb9cd01e5b54da1 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Fri, 2 Oct 2026 13:44:52 +0200 Subject: [PATCH 01/12] feat: common.attachAutoResizer --- .../src/components/ExampleView.tsx | 90 +--------- .../landing-examples/HoverExampleLive.tsx | 19 ++- .../components/stackblitz/stackBlitzIndex.ts | 46 ------ .../algorithms/genetic-racing/index.html | 2 +- .../algorithms/genetic-racing/index.ts | 6 + .../algorithms/jump-flood-distance/index.html | 2 +- .../algorithms/jump-flood-distance/index.ts | 20 +-- .../algorithms/jump-flood-voronoi/index.html | 2 +- .../algorithms/jump-flood-voronoi/index.ts | 18 +- .../algorithms/mnist-inference/index.html | 6 +- .../algorithms/mnist-inference/index.ts | 5 +- .../src/examples/algorithms/sort/index.html | 2 +- .../src/examples/algorithms/sort/index.ts | 19 ++- .../src/examples/geometry/circles/index.html | 2 +- .../src/examples/geometry/circles/index.ts | 24 ++- .../geometry/lines-combinations/index.html | 2 +- .../geometry/lines-combinations/index.ts | 19 ++- .../image-processing/ascii-filter/index.html | 2 +- .../image-processing/ascii-filter/index.ts | 47 ++++-- .../background-segmentation/index.html | 2 +- .../background-segmentation/index.ts | 51 ++++-- .../examples/image-processing/blur/index.html | 2 +- .../examples/image-processing/blur/index.ts | 13 +- .../camera-thresholding/index.html | 2 +- .../camera-thresholding/index.ts | 41 +++-- .../image-processing/chroma-keying/index.html | 2 +- .../image-processing/chroma-keying/index.ts | 42 +++-- .../image-processing/image-tuning/index.html | 2 +- .../image-processing/image-tuning/index.ts | 15 +- .../selfie-segmentation/index.html | 22 +-- .../selfie-segmentation/index.ts | 13 +- .../react/shifting-gradient/index.tsx | 2 +- .../react/spinning-triangle/index.tsx | 2 +- .../src/examples/rendering/3d-fish/index.html | 2 +- .../src/examples/rendering/3d-fish/index.ts | 11 +- .../src/examples/rendering/3d-fish/scene.ts | 75 +++++---- .../rendering/box-raytracing/index.html | 2 +- .../rendering/box-raytracing/index.ts | 21 ++- .../examples/rendering/caustics/index.html | 2 +- .../src/examples/rendering/caustics/index.ts | 15 +- .../src/examples/rendering/caustics/scene.ts | 16 +- .../src/examples/rendering/clouds/index.html | 2 +- .../src/examples/rendering/clouds/index.ts | 51 +++--- .../rendering/cubemap-reflection/index.html | 2 +- .../rendering/cubemap-reflection/index.ts | 11 +- .../rendering/cubemap-reflection/scene.ts | 31 ++-- .../src/examples/rendering/disco/index.html | 2 +- .../src/examples/rendering/disco/index.ts | 30 +++- .../rendering/function-visualizer/index.html | 2 +- .../rendering/function-visualizer/index.ts | 32 ++-- .../rendering/jelly-slider/index.html | 2 +- .../examples/rendering/jelly-slider/index.ts | 15 +- .../examples/rendering/jelly-slider/scene.ts | 45 ++--- .../rendering/jelly-switch/index.html | 2 +- .../examples/rendering/jelly-switch/index.ts | 46 ++++-- .../examples/rendering/os-awards/index.html | 2 +- .../src/examples/rendering/os-awards/index.ts | 18 +- .../rendering/os-awards/mesh/renderer.ts | 12 +- .../rendering/perlin-noise/index.html | 2 +- .../examples/rendering/perlin-noise/index.ts | 26 ++- .../rendering/phong-reflection/index.html | 2 +- .../rendering/phong-reflection/index.ts | 37 +++-- .../rendering/point-light-shadow/index.html | 2 +- .../rendering/point-light-shadow/index.ts | 67 ++++---- .../src/examples/rendering/pom/index.html | 2 +- .../src/examples/rendering/pom/index.ts | 24 ++- .../drawInteraction.ts | 25 ++- .../radiance-cascades-drawing/index.html | 2 +- .../radiance-cascades-drawing/index.ts | 24 ++- .../rendering/radiance-cascades/index.html | 2 +- .../rendering/radiance-cascades/index.ts | 18 +- .../rendering/ray-marching/index.html | 2 +- .../examples/rendering/ray-marching/index.ts | 11 +- .../examples/rendering/ray-marching/scene.ts | 13 +- .../rendering/render-bundles-with/index.html | 2 +- .../rendering/render-bundles-with/index.ts | 25 ++- .../rendering/render-bundles/index.html | 2 +- .../rendering/render-bundles/index.ts | 21 ++- .../rendering/simple-shadow/index.html | 2 +- .../examples/rendering/simple-shadow/index.ts | 49 +++--- .../rendering/smoky-triangle/index.html | 2 +- .../rendering/smoky-triangle/index.ts | 21 ++- .../examples/rendering/suika-sdf/index.html | 2 +- .../src/examples/rendering/suika-sdf/index.ts | 62 ++++--- .../examples/rendering/two-boxes/index.html | 2 +- .../src/examples/rendering/two-boxes/index.ts | 20 ++- .../rendering/xor-dev-centrifuge-2/index.html | 2 +- .../rendering/xor-dev-centrifuge-2/index.ts | 5 +- .../rendering/xor-dev-runner/index.html | 2 +- .../rendering/xor-dev-runner/index.ts | 8 +- .../examples/simple/gradient-tiles/index.html | 2 +- .../examples/simple/gradient-tiles/index.ts | 13 +- .../examples/simple/liquid-glass/index.html | 2 +- .../src/examples/simple/liquid-glass/index.ts | 23 ++- .../examples/simple/mesh-skinning/index.html | 2 +- .../examples/simple/mesh-skinning/index.ts | 17 +- .../src/examples/simple/oklab/index.html | 6 +- .../src/examples/simple/oklab/index.ts | 38 ++++- .../examples/simple/ripple-cube/index.html | 2 +- .../src/examples/simple/ripple-cube/index.ts | 55 ++++-- .../simple/ripple-cube/post-processing.ts | 156 ++++++++++++------ .../src/examples/simple/square/index.html | 2 +- .../src/examples/simple/square/index.ts | 16 +- .../src/examples/simple/stencil/index.html | 2 +- .../src/examples/simple/stencil/index.ts | 49 +++--- .../src/examples/simple/triangle/index.html | 2 +- .../src/examples/simple/triangle/index.ts | 30 +++- .../src/examples/simple/vaporrave/index.html | 2 +- .../src/examples/simple/vaporrave/index.ts | 11 +- .../src/examples/simple/vaporrave/scene.ts | 17 +- .../src/examples/simulation/boids/index.html | 2 +- .../src/examples/simulation/boids/index.ts | 32 +++- .../examples/simulation/confetti/index.html | 2 +- .../src/examples/simulation/confetti/index.ts | 5 +- .../fluid-double-buffering/index.html | 2 +- .../fluid-double-buffering/index.ts | 15 +- .../simulation/fluid-with-atomics/index.html | 2 +- .../simulation/fluid-with-atomics/index.ts | 13 ++ .../simulation/game-of-life/index.html | 2 +- .../examples/simulation/game-of-life/index.ts | 31 +++- .../examples/simulation/game-of-life/input.ts | 15 +- .../examples/simulation/gravity/index.html | 2 +- .../src/examples/simulation/gravity/index.ts | 23 +-- .../simulation/slime-mold-3d/index.html | 2 +- .../simulation/slime-mold-3d/index.ts | 29 +++- .../examples/simulation/slime-mold/index.html | 2 +- .../examples/simulation/slime-mold/index.ts | 31 +++- .../simulation/stable-fluid/index.html | 2 +- .../examples/simulation/stable-fluid/index.ts | 108 +++++++----- .../examples/simulation/wind-map/index.html | 2 +- .../src/examples/simulation/wind-map/index.ts | 15 +- .../tests/sdf-docs-examples/index.html | 2 +- .../examples/tests/sdf-docs-examples/index.ts | 3 + .../src/examples/tests/uniformity/index.html | 2 +- .../src/examples/tests/uniformity/index.ts | 10 ++ .../examples/threejs/attractors/index.html | 2 +- .../examples/threejs/compute-cloth/index.html | 2 +- .../threejs/compute-geometry/index.html | 2 +- .../threejs/compute-particles-snow/index.html | 2 +- .../threejs/compute-particles/index.html | 2 +- .../src/examples/threejs/simple/index.html | 2 +- .../threejs/test-reused-functions/index.html | 2 +- .../src/examples/threejs/varyings/index.html | 2 +- .../templates/template-vite-bare/src/main.ts | 27 +-- .../template-vite-complex/src/main.ts | 28 +--- .../typegpu/src/common/attachAutoResizer.ts | 48 ++++++ packages/typegpu/src/common/index.ts | 5 + 147 files changed, 1522 insertions(+), 882 deletions(-) create mode 100644 packages/typegpu/src/common/attachAutoResizer.ts diff --git a/apps/typegpu-docs/src/components/ExampleView.tsx b/apps/typegpu-docs/src/components/ExampleView.tsx index 6c1264169a..bbc1843a06 100644 --- a/apps/typegpu-docs/src/components/ExampleView.tsx +++ b/apps/typegpu-docs/src/components/ExampleView.tsx @@ -1,14 +1,6 @@ import cs from 'classnames'; import { useAtom, useAtomValue, useSetAtom } from 'jotai'; -import { - type RefObject, - Suspense, - useEffect, - useLayoutEffect, - useRef, - useState, - lazy, -} from 'react'; +import { Suspense, useEffect, useLayoutEffect, useRef, useState, lazy } from 'react'; import { currentSnackbarAtom } from '../utils/examples/currentSnackbarAtom.ts'; import { tsoverUsedAtom, exampleFullscreenAtom } from '../utils/examples/exampleViewStateAtoms.ts'; import { ExecutionCancelledError } from '../utils/examples/errors.ts'; @@ -131,7 +123,6 @@ export function ExampleView({ example, common }: Props) { }, [fullscreen, setFullscreen]); const isLoading = useExample(tsImport, setSnackbarText); - useResizableCanvas(exampleHtmlRef); return ( <> @@ -348,85 +339,6 @@ function GPUUnsupportedPanel() { ); } -function useResizableCanvas(exampleHtmlRef: RefObject) { - useEffect(() => { - const canvases = exampleHtmlRef.current?.querySelectorAll('canvas') as - | HTMLCanvasElement[] - | undefined; - const observers: ResizeObserver[] = []; - - for (const canvas of canvases ?? []) { - if ('width' in canvas.attributes || 'height' in canvas.attributes) { - continue; - } - - const newCanvas = document.createElement('canvas'); - const container = document.createElement('div'); - const frame = document.createElement('div'); - - frame.appendChild(newCanvas); - container.appendChild(frame); - - container.className = 'flex flex-1 justify-center items-center w-full h-full md:w-auto'; - container.style.containerType = 'size'; - - frame.className = 'relative'; - - if (canvas.dataset.fitToContainer !== undefined) { - frame.style.width = '100%'; - frame.style.height = '100%'; - } else { - const aspectRatio = canvas.dataset.aspectRatio ?? '1'; - frame.style.aspectRatio = aspectRatio; - frame.style.height = `min(100cqh, calc(100cqw/(${aspectRatio})))`; - } - - for (const prop of canvas.style) { - // @ts-expect-error - newCanvas.style[prop] = canvas.style[prop]; - } - for (const attribute of canvas.attributes) { - // @ts-expect-error - newCanvas[attribute.name] = attribute.value; - } - newCanvas.className = 'absolute w-full h-full'; - - canvas.parentElement?.replaceChild(container, canvas); - - const onResize: ResizeObserverCallback = ([entry]) => { - if (!entry) { - return; - } - - // Despite what the types say this property does not exist in Safari (hence the optional chaining). - const dpcb = entry.devicePixelContentBoxSize?.[0] as ResizeObserverSize | undefined; - - const dpr = dpcb ? 1 : window.devicePixelRatio || 1; - const box = - dpcb ?? - (Array.isArray(entry.contentBoxSize) ? entry.contentBoxSize[0] : entry.contentBoxSize); - - if (!box) { - return; - } - - newCanvas.width = Math.round(box.inlineSize * dpr); - newCanvas.height = Math.round(box.blockSize * dpr); - }; - - const observer = new ResizeObserver(onResize); - observer.observe(newCanvas); - observers.push(observer); - } - - return () => { - for (const observer of observers) { - observer.disconnect(); - } - }; - }, [exampleHtmlRef]); -} - /** * NOTE: this function only filters common files used in src files. * Common files used in other common files will not be included. diff --git a/apps/typegpu-docs/src/components/landing-examples/HoverExampleLive.tsx b/apps/typegpu-docs/src/components/landing-examples/HoverExampleLive.tsx index bc39d9887d..734e30ad6e 100644 --- a/apps/typegpu-docs/src/components/landing-examples/HoverExampleLive.tsx +++ b/apps/typegpu-docs/src/components/landing-examples/HoverExampleLive.tsx @@ -1,8 +1,9 @@ -import type { TgpuRoot } from 'typegpu'; +import { common, type TgpuRoot } from 'typegpu'; import { useConfigureContext, useRoot } from '@typegpu/react'; import { useEffect, useState } from 'react'; interface ExampleState { + onResize?(): void; onCleanup(): void; } @@ -14,6 +15,9 @@ export default function HoverExampleLive({ setup }: HoverExampleLiveProps) { const root = useRoot(); const { ctxRef, ref: canvasRef } = useConfigureContext({ alphaMode: 'premultiplied', + // Resizing is handled by `common.attachAutoResizer` below, so that the + // example can react to the new size. + autoResize: false, }); const [isActive, setIsActive] = useState(false); const [error, setError] = useState(null); @@ -39,8 +43,19 @@ export default function HoverExampleLive({ setup }: HoverExampleLiveProps) { if (cancelled) return; try { + const canvas = ctxRef.current.canvas as HTMLCanvasElement; const example = await setup(root, ctxRef.current); - onCleanup = () => example.onCleanup(); + const autoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + example.onResize?.(); + }, + }); + onCleanup = () => { + autoResizer.detach(); + example.onCleanup(); + }; if (cancelled) { onCleanup(); diff --git a/apps/typegpu-docs/src/components/stackblitz/stackBlitzIndex.ts b/apps/typegpu-docs/src/components/stackblitz/stackBlitzIndex.ts index c707a4371a..b5abd8329e 100644 --- a/apps/typegpu-docs/src/components/stackblitz/stackBlitzIndex.ts +++ b/apps/typegpu-docs/src/components/stackblitz/stackBlitzIndex.ts @@ -10,52 +10,6 @@ body.style.margin = '0'; body.style.boxSizing = 'border-box'; body.style.padding = '1rem'; -// Resize canvases -for (const canvas of document.querySelectorAll('canvas')) { - if ('width' in canvas.attributes || 'height' in canvas.attributes) { - continue; // custom canvas, not replacing with resizable - } - - const container = document.createElement('div'); - const frame = document.createElement('div'); - - canvas.parentElement?.replaceChild(container, canvas); - - frame.appendChild(canvas); - container.appendChild(frame); - - container.style.display = 'flex'; - container.style.flex = '1'; - container.style.justifyContent = 'center'; - container.style.alignItems = 'top'; - container.style.width = '100%'; - - container.style.containerType = 'size'; - - frame.style.position = 'relative'; - - if (canvas.dataset.fitToContainer !== undefined) { - frame.style.width = '100%'; - frame.style.height = '100%'; - } else { - const aspectRatio = canvas.dataset.aspectRatio ?? '1'; - frame.style.aspectRatio = aspectRatio; - frame.style.height = `min(calc(min(100cqw, 100cqh)/(${aspectRatio})), min(100cqw, 100cqh))`; - } - - canvas.style.position = 'absolute'; - canvas.style.width = '100%'; - canvas.style.height = '100%'; - - const onResize = () => { - canvas.width = frame.clientWidth * window.devicePixelRatio; - canvas.height = frame.clientHeight * window.devicePixelRatio; - }; - - onResize(); - new ResizeObserver(onResize).observe(container); -} - const controlsPanel = document.createElement('div'); controlsPanel.style.display = 'grid'; controlsPanel.style.gridTemplateColumns = '1fr 1fr'; diff --git a/apps/typegpu-docs/src/examples/algorithms/genetic-racing/index.html b/apps/typegpu-docs/src/examples/algorithms/genetic-racing/index.html index c732f96f62..60b1adde94 100644 --- a/apps/typegpu-docs/src/examples/algorithms/genetic-racing/index.html +++ b/apps/typegpu-docs/src/examples/algorithms/genetic-racing/index.html @@ -1,4 +1,4 @@ - +
+ + +