From 6ca685c93a673035fd03d5116aec25f5fe9ac330 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 21 May 2026 23:01:41 +0200 Subject: [PATCH 1/8] feat: common.attachAutoResizer --- .../examples/algorithms/bitonic-sort/index.ts | 22 ++++------ .../rendering/cubemap-reflection/index.ts | 18 ++++---- .../src/examples/simple/triangle/index.ts | 25 +++++++---- .../typegpu/src/common/attachAutoResizer.ts | 42 +++++++++++++++++++ packages/typegpu/src/common/index.ts | 1 + 5 files changed, 76 insertions(+), 32 deletions(-) create mode 100644 packages/typegpu/src/common/attachAutoResizer.ts diff --git a/apps/typegpu-docs/src/examples/algorithms/bitonic-sort/index.ts b/apps/typegpu-docs/src/examples/algorithms/bitonic-sort/index.ts index ad5bb150c0..5e54900d30 100644 --- a/apps/typegpu-docs/src/examples/algorithms/bitonic-sort/index.ts +++ b/apps/typegpu-docs/src/examples/algorithms/bitonic-sort/index.ts @@ -1,4 +1,4 @@ -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; import { type BitonicSorter, type BitonicSorterOptions, @@ -6,7 +6,6 @@ import { decomposeWorkgroups, } from '@typegpu/sort'; import { randf } from '@typegpu/noise'; -import { fullScreenTriangle } from 'typegpu/common'; import { defineControls } from '../../common/defineControls.ts'; const maxBufferSize = await navigator.gpu.requestAdapter().then((adapter) => { @@ -32,7 +31,10 @@ const querySet = hasTimestampQuery ? root.createQuerySet('timestamp', 2) : null; const canvas = document.querySelector('canvas') as HTMLCanvasElement; const context = root.configureContext({ canvas }); -const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, +}); const maxSide = Math.floor(Math.sqrt(maxBufferSize / 4)); const minLog = 2; // log_2(4) @@ -75,17 +77,11 @@ const state = { const WORKGROUP_SIZE = 256; const renderLayout = tgpu.bindGroupLayout({ - data: { - storage: d.arrayOf(d.u32), - access: 'readonly', - }, + data: { storage: d.arrayOf(d.u32), access: 'readonly' }, }); const initLayout = tgpu.bindGroupLayout({ - data: { - storage: d.arrayOf(d.u32), - access: 'mutable', - }, + data: { storage: d.arrayOf(d.u32), access: 'mutable' }, }); const initSeed = root.createUniform(d.f32, 0); @@ -135,9 +131,8 @@ const initKernel = tgpu.computeFn({ }); const renderPipeline = root.createRenderPipeline({ - vertex: fullScreenTriangle, + vertex: common.fullScreenTriangle, fragment: fragmentFn, - targets: { format: presentationFormat }, }); const initPipeline = root.createComputePipeline({ compute: initKernel }); @@ -275,6 +270,7 @@ export function onCleanup() { for (const s of Object.values(sorters)) { s.destroy(); } + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/index.ts b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/index.ts index e82e818df7..34f9ba787e 100644 --- a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/index.ts @@ -1,4 +1,4 @@ -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; import * as m from 'wgpu-matrix'; import { type CubemapNames, cubeVertices, loadCubemap } from './cubemap.ts'; import { Camera, CubeVertex, DirectionalLight, Material, Vertex } from './dataTypes.ts'; @@ -273,13 +273,10 @@ loop(); // #region Example controls and cleanup -const resizeObserver = new ResizeObserver((entries) => { - for (const entry of entries) { - const dpr = window.devicePixelRatio; - const width = entry.contentRect.width; - const height = entry.contentRect.height; - canvas.width = width * dpr; - canvas.height = height * dpr; +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { const newProj = m.mat4.perspective( Math.PI / 4, canvas.width / canvas.height, @@ -288,9 +285,8 @@ const resizeObserver = new ResizeObserver((entries) => { d.mat4x4f(), ); cameraBuffer.patch({ projection: newProj }); - } + }, }); -resizeObserver.observe(canvas); // Variables for mouse interaction. let isDragging = false; @@ -499,7 +495,7 @@ export function onCleanup() { window.removeEventListener('mousemove', mouseMoveEventListener); window.removeEventListener('touchmove', touchMoveEventListener); window.removeEventListener('touchend', touchEndEventListener); - resizeObserver.unobserve(canvas); + detachAutoResizer(); icosphereGenerator.destroy(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simple/triangle/index.ts b/apps/typegpu-docs/src/examples/simple/triangle/index.ts index 5794dfc374..9d98ea8c1d 100644 --- a/apps/typegpu-docs/src/examples/simple/triangle/index.ts +++ b/apps/typegpu-docs/src/examples/simple/triangle/index.ts @@ -1,22 +1,22 @@ -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; // Constants and helper functions const purple = d.vec4f(0.769, 0.392, 1, 1); const blue = d.vec4f(0.114, 0.447, 0.941, 1); -const getGradientColor = (ratio: number) => { +function getGradientColor(ratio: number) { 'use gpu'; return std.mix(purple, blue, ratio); -}; +} -const pos = tgpu.const(d.arrayOf(d.vec2f, 3), [ +const pos = tgpu.const(d.arrayOf(d.vec2f), [ d.vec2f(0.0, 0.5), d.vec2f(-0.5, -0.5), d.vec2f(0.5, -0.5), ]); -const uv = tgpu.const(d.arrayOf(d.vec2f, 3), [ +const uv = tgpu.const(d.arrayOf(d.vec2f), [ d.vec2f(0.5, 1.0), d.vec2f(0.0, 0.0), d.vec2f(1.0, 0.0), @@ -39,18 +39,27 @@ const pipeline = root.createRenderPipeline({ }, }); -// Setting up the canvas and drawing to it +// Setting up the canvas +const canvas = document.querySelector('canvas') as HTMLCanvasElement; const context = root.configureContext({ - canvas: document.querySelector('canvas') as HTMLCanvasElement, + canvas, alphaMode: 'premultiplied', }); -pipeline.withColorAttachment({ view: context }).draw(3); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + // Drawing once, and then each time the canvas resizes + pipeline.withColorAttachment({ view: context }).draw(3); + }, +}); // #region Cleanup export function onCleanup() { + detachAutoResizer(); root.destroy(); } diff --git a/packages/typegpu/src/common/attachAutoResizer.ts b/packages/typegpu/src/common/attachAutoResizer.ts new file mode 100644 index 0000000000..7f43ad00c8 --- /dev/null +++ b/packages/typegpu/src/common/attachAutoResizer.ts @@ -0,0 +1,42 @@ +import type { TgpuRoot } from '../core/root/rootTypes.ts'; + +export interface AttachAutoResizerOptions { + root: TgpuRoot; + canvas: HTMLCanvasElement; + onResize?(): void; +} + +export function attachAutoResizer({ + root, + canvas, + onResize, +}: AttachAutoResizerOptions): () => void { + const observer = new ResizeObserver((entries) => { + for (const entry of entries) { + if (!entry) { + return; + } + const width = + entry.devicePixelContentBoxSize?.[0]?.inlineSize || + (entry.contentBoxSize[0]?.inlineSize ?? 0) * window.devicePixelRatio; + const height = + entry.devicePixelContentBoxSize?.[0]?.blockSize || + (entry.contentBoxSize[0]?.blockSize ?? 0) * window.devicePixelRatio; + const canvas = entry.target as HTMLCanvasElement; + canvas.width = Math.max(1, Math.min(width, root.device.limits.maxTextureDimension2D)); + canvas.height = Math.max(1, Math.min(height, root.device.limits.maxTextureDimension2D)); + + onResize?.(); + } + }); + + try { + observer.observe(canvas, { box: 'device-pixel-content-box' }); + } catch { + observer.observe(canvas, { box: 'content-box' }); + } + + return () => { + observer.disconnect(); + }; +} diff --git a/packages/typegpu/src/common/index.ts b/packages/typegpu/src/common/index.ts index 9249008035..555b267f06 100644 --- a/packages/typegpu/src/common/index.ts +++ b/packages/typegpu/src/common/index.ts @@ -1,4 +1,5 @@ // NOTE: This is a barrel file, internal files should not import things from this file export { fullScreenTriangle } from './fullScreenTriangle.ts'; +export { attachAutoResizer } from './attachAutoResizer.ts'; export { writeSoA } from './writeSoA.ts'; From 4072a1a79fa21ddd43340f0aaff4a2b8e54eb121 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Fri, 22 May 2026 15:22:03 +0200 Subject: [PATCH 2/8] Update examples to use common.attachAutoResizer --- .../examples/algorithms/bitonic-sort/index.ts | 13 +++--- .../algorithms/genetic-racing/index.ts | 6 +++ .../algorithms/jump-flood-distance/index.ts | 20 ++++----- .../algorithms/jump-flood-voronoi/index.ts | 20 ++++----- .../src/examples/geometry/circles/index.ts | 45 ++++++++++--------- .../geometry/lines-combinations/index.ts | 5 ++- .../image-processing/ascii-filter/index.ts | 6 +++ .../background-segmentation/index.ts | 6 +++ .../examples/image-processing/blur/index.ts | 9 +++- .../camera-thresholding/index.ts | 6 +++ .../image-processing/chroma-keying/index.ts | 6 +++ .../image-processing/image-tuning/index.ts | 11 ++++- .../src/examples/rendering/3d-fish/index.ts | 39 ++++++++-------- .../rendering/box-raytracing/index.ts | 5 ++- .../src/examples/rendering/caustics/index.ts | 5 ++- .../src/examples/rendering/clouds/index.ts | 3 ++ .../src/examples/rendering/disco/index.ts | 8 +++- .../rendering/function-visualizer/index.ts | 5 ++- .../examples/rendering/jelly-slider/index.ts | 11 +++-- .../examples/rendering/jelly-switch/index.ts | 11 +++-- .../examples/rendering/perlin-noise/index.ts | 3 ++ .../rendering/phong-reflection/index.ts | 5 ++- .../rendering/point-light-shadow/index.ts | 16 +++---- .../src/examples/rendering/pom/index.ts | 13 ++++-- .../radiance-cascades-drawing/index.ts | 3 ++ .../rendering/radiance-cascades/index.ts | 13 +++--- .../examples/rendering/ray-marching/index.ts | 8 +++- .../rendering/render-bundles-with/index.ts | 5 ++- .../rendering/render-bundles/index.ts | 8 +++- .../examples/rendering/simple-shadow/index.ts | 35 ++++++++------- .../rendering/smoky-triangle/index.ts | 5 ++- .../src/examples/rendering/suika-sdf/index.ts | 29 ++++++------ .../src/examples/rendering/two-boxes/index.ts | 5 ++- .../rendering/xor-dev-centrifuge-2/index.ts | 5 ++- .../rendering/xor-dev-runner/index.ts | 8 +++- .../examples/simple/gradient-tiles/index.ts | 9 +++- .../src/examples/simple/liquid-glass/index.ts | 3 ++ .../examples/simple/mesh-skinning/index.ts | 3 ++ .../src/examples/simple/oklab/index.ts | 11 +++-- .../src/examples/simple/ripple-cube/index.ts | 5 ++- .../src/examples/simple/square/index.ts | 12 ++++- .../src/examples/simple/stencil/index.ts | 5 ++- .../src/examples/simple/vaporrave/index.ts | 5 ++- .../src/examples/simulation/boids/index.ts | 5 ++- .../src/examples/simulation/confetti/index.ts | 5 ++- .../fluid-double-buffering/index.ts | 5 ++- .../simulation/fluid-with-atomics/index.ts | 3 ++ .../examples/simulation/game-of-life/index.ts | 3 ++ .../src/examples/simulation/gravity/index.ts | 5 ++- .../simulation/slime-mold-3d/index.ts | 3 ++ .../examples/simulation/slime-mold/index.ts | 5 ++- .../examples/simulation/stable-fluid/index.ts | 4 ++ .../src/examples/simulation/wind-map/index.ts | 5 ++- .../src/examples/tests/log-test/index.ts | 5 ++- .../examples/tests/rc-docs-examples/index.ts | 3 ++ .../examples/tests/sdf-docs-examples/index.ts | 3 ++ .../src/examples/tests/texture-test/index.ts | 3 ++ .../src/examples/tests/uniformity/index.ts | 13 +++--- 58 files changed, 364 insertions(+), 160 deletions(-) diff --git a/apps/typegpu-docs/src/examples/algorithms/bitonic-sort/index.ts b/apps/typegpu-docs/src/examples/algorithms/bitonic-sort/index.ts index 5e54900d30..ab44e89723 100644 --- a/apps/typegpu-docs/src/examples/algorithms/bitonic-sort/index.ts +++ b/apps/typegpu-docs/src/examples/algorithms/bitonic-sort/index.ts @@ -31,11 +31,6 @@ const querySet = hasTimestampQuery ? root.createQuerySet('timestamp', 2) : null; const canvas = document.querySelector('canvas') as HTMLCanvasElement; const context = root.configureContext({ canvas }); -const detachAutoResizer = common.attachAutoResizer({ - root, - canvas, -}); - const maxSide = Math.floor(Math.sqrt(maxBufferSize / 4)); const minLog = 2; // log_2(4) const maxLog = Math.floor(Math.log2(maxSide)); @@ -241,6 +236,14 @@ async function sort() { hideOverlay(); } +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + render(); + }, +}); + // #region Example controls & Cleanup const sortOrderKeys = Object.keys(sortOrders) as SortOrderKey[]; diff --git a/apps/typegpu-docs/src/examples/algorithms/genetic-racing/index.ts b/apps/typegpu-docs/src/examples/algorithms/genetic-racing/index.ts index 86a13accbb..5eec334d28 100644 --- a/apps/typegpu-docs/src/examples/algorithms/genetic-racing/index.ts +++ b/apps/typegpu-docs/src/examples/algorithms/genetic-racing/index.ts @@ -532,6 +532,11 @@ applyGridSize(...GRID_SIZES[gridSizeKey]); applyTrack(generateGridTrack(trackSeed, ...GRID_SIZES[gridSizeKey])); startSimulation(); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, +}); + // #region Example controls & Cleanup export const controls = defineControls({ @@ -605,6 +610,7 @@ export const controls = defineControls({ export function onCleanup() { cancelAnimationFrame(rafHandle); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/algorithms/jump-flood-distance/index.ts b/apps/typegpu-docs/src/examples/algorithms/jump-flood-distance/index.ts index ce3767e05b..b68242d354 100644 --- a/apps/typegpu-docs/src/examples/algorithms/jump-flood-distance/index.ts +++ b/apps/typegpu-docs/src/examples/algorithms/jump-flood-distance/index.ts @@ -342,8 +342,6 @@ function recreateResources() { sourceIdx = 0; } -clearCanvas(); - function getTouchPosition(rect: DOMRect, touches: TouchList) { if (touches.length === 2) { return { @@ -357,17 +355,16 @@ function getTouchPosition(rect: DOMRect, touches: TouchList) { }; } -// #region Example controls & Cleanup - -let resizeTimeout: ReturnType; -const resizeObserver = new ResizeObserver(() => { - clearTimeout(resizeTimeout); - resizeTimeout = setTimeout(() => { +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { recreateResources(); clearCanvas(); - }, 100); + }, }); -resizeObserver.observe(canvas); + +// #region Example controls & Cleanup const onMouseDown = (e: MouseEvent) => { if (e.button !== 0 && e.button !== 2) { @@ -475,8 +472,7 @@ export const controls = defineControls({ }); export function onCleanup() { - clearTimeout(resizeTimeout); - resizeObserver.disconnect(); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/algorithms/jump-flood-voronoi/index.ts b/apps/typegpu-docs/src/examples/algorithms/jump-flood-voronoi/index.ts index fa64ed38b4..1d8019bd8c 100644 --- a/apps/typegpu-docs/src/examples/algorithms/jump-flood-voronoi/index.ts +++ b/apps/typegpu-docs/src/examples/algorithms/jump-flood-voronoi/index.ts @@ -239,19 +239,16 @@ function reset() { void runFloodAnimated(currentRunId); } -reset(); - -// #region Example controls & Cleanup - -let resizeTimeout: ReturnType; -const resizeObserver = new ResizeObserver(() => { - clearTimeout(resizeTimeout); - resizeTimeout = setTimeout(() => { +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { recreateResources(); reset(); - }, 100); + }, }); -resizeObserver.observe(canvas); + +// #region Example controls & Cleanup export const controls = defineControls({ 'Run Algorithm': { @@ -295,8 +292,7 @@ export const controls = defineControls({ }); export function onCleanup() { - clearTimeout(resizeTimeout); - resizeObserver.disconnect(); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/geometry/circles/index.ts b/apps/typegpu-docs/src/examples/geometry/circles/index.ts index 549f4b0d4b..2790b4c6a9 100644 --- a/apps/typegpu-docs/src/examples/geometry/circles/index.ts +++ b/apps/typegpu-docs/src/examples/geometry/circles/index.ts @@ -1,5 +1,5 @@ import { circle, circleVertexCount } from '@typegpu/geometry'; -import tgpu, { d, std as s } from 'typegpu'; +import tgpu, { common, d, std as s } from 'typegpu'; const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); const canvas = document.querySelector('canvas'); @@ -128,29 +128,34 @@ const pipeline = root.createRenderPipeline({ multisample: { count: multisample ? 4 : 1 }, }); -setTimeout(() => { - pipeline - .with(uniformsBindGroup) - .withColorAttachment({ - ...(multisample - ? { - view: msaaTextureView, - resolveTarget: context, - } - : { view: context }), - clearValue: [0, 0, 0, 0], - loadOp: 'clear', - storeOp: 'store', - }) - .withPerformanceCallback((a, b) => { - console.log((Number(b - a) * 1e-6).toFixed(3), 'ms'); - }) - .draw(circleVertexCount(4), circleCount); -}, 100); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + pipeline + .with(uniformsBindGroup) + .withColorAttachment({ + ...(multisample + ? { + view: msaaTextureView, + resolveTarget: context, + } + : { view: context }), + clearValue: [0, 0, 0, 0], + loadOp: 'clear', + storeOp: 'store', + }) + .withPerformanceCallback((a, b) => { + console.log((Number(b - a) * 1e-6).toFixed(3), 'ms'); + }) + .draw(circleVertexCount(4), circleCount); + }, +}); // #region Example controls & Cleanup export function onCleanup() { + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/geometry/lines-combinations/index.ts b/apps/typegpu-docs/src/examples/geometry/lines-combinations/index.ts index 074dec69ee..a2252d8553 100644 --- a/apps/typegpu-docs/src/examples/geometry/lines-combinations/index.ts +++ b/apps/typegpu-docs/src/examples/geometry/lines-combinations/index.ts @@ -10,7 +10,7 @@ import { startCapSlot, arrowCapParamsSlot, } from '@typegpu/geometry'; -import tgpu, { type ColorAttachment } from 'typegpu'; +import tgpu, { common, type ColorAttachment } from 'typegpu'; import { arrayOf, builtin, @@ -409,6 +409,8 @@ const runAnimationFrame = (timeMs: number) => { }; runAnimationFrame(0); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + const fillOptions = { none: 0, solid: 1, @@ -505,6 +507,7 @@ export const controls = defineControls({ }); export function onCleanup() { + detachAutoResizer(); root.destroy(); cancelAnimationFrame(frameId); } diff --git a/apps/typegpu-docs/src/examples/image-processing/ascii-filter/index.ts b/apps/typegpu-docs/src/examples/image-processing/ascii-filter/index.ts index 0adc3b6b4f..8a41daff68 100644 --- a/apps/typegpu-docs/src/examples/image-processing/ascii-filter/index.ts +++ b/apps/typegpu-docs/src/examples/image-processing/ascii-filter/index.ts @@ -241,6 +241,11 @@ if (isIOS) { videoFrameCallbackId = video.requestVideoFrameCallback(processVideoFrame); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, +}); + export const controls = defineControls({ 'use extended characters': { initial: false, @@ -279,5 +284,6 @@ export function onCleanup() { } } + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/image-processing/background-segmentation/index.ts b/apps/typegpu-docs/src/examples/image-processing/background-segmentation/index.ts index 0c08feb8f6..9c44286576 100644 --- a/apps/typegpu-docs/src/examples/image-processing/background-segmentation/index.ts +++ b/apps/typegpu-docs/src/examples/image-processing/background-segmentation/index.ts @@ -295,6 +295,11 @@ async function processVideoFrame(_: number, metadata: VideoFrameCallbackMetadata } videoFrameCallbackId = video.requestVideoFrameCallback(processVideoFrame); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, +}); + // #region Example controls & Cleanup export const controls = defineControls({ @@ -355,6 +360,7 @@ export function onCleanup() { adapter.requestDevice = oldRequestDevice; } + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/image-processing/blur/index.ts b/apps/typegpu-docs/src/examples/image-processing/blur/index.ts index a6bd7bd0de..f31f1ea4b6 100644 --- a/apps/typegpu-docs/src/examples/image-processing/blur/index.ts +++ b/apps/typegpu-docs/src/examples/image-processing/blur/index.ts @@ -171,7 +171,13 @@ function render() { renderPipeline.withColorAttachment({ view: context }).draw(3); } -render(); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + render(); + }, +}); // #region Example controls & Cleanup @@ -200,6 +206,7 @@ export const controls = defineControls({ }); export function onCleanup() { + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/image-processing/camera-thresholding/index.ts b/apps/typegpu-docs/src/examples/image-processing/camera-thresholding/index.ts index b72f79e1fb..edf4576432 100644 --- a/apps/typegpu-docs/src/examples/image-processing/camera-thresholding/index.ts +++ b/apps/typegpu-docs/src/examples/image-processing/camera-thresholding/index.ts @@ -133,6 +133,11 @@ function processVideoFrame(_: number, metadata: VideoFrameCallbackMetadata) { } videoFrameCallbackId = video.requestVideoFrameCallback(processVideoFrame); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, +}); + // #region Example controls & Cleanup export const controls = defineControls({ @@ -160,6 +165,7 @@ export function onCleanup() { track.stop(); } } + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/image-processing/chroma-keying/index.ts b/apps/typegpu-docs/src/examples/image-processing/chroma-keying/index.ts index 498c3cf8b6..cae9ffc6cf 100644 --- a/apps/typegpu-docs/src/examples/image-processing/chroma-keying/index.ts +++ b/apps/typegpu-docs/src/examples/image-processing/chroma-keying/index.ts @@ -125,6 +125,11 @@ function processVideoFrame(_: number, metadata: VideoFrameCallbackMetadata) { videoFrameCallbackId = video.requestVideoFrameCallback(processVideoFrame); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, +}); + // #region Example controls & Cleanup export const controls = defineControls({ @@ -152,6 +157,7 @@ export function onCleanup() { track.stop(); } } + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/image-processing/image-tuning/index.ts b/apps/typegpu-docs/src/examples/image-processing/image-tuning/index.ts index 4720c0aeb9..f4d60a043b 100644 --- a/apps/typegpu-docs/src/examples/image-processing/image-tuning/index.ts +++ b/apps/typegpu-docs/src/examples/image-processing/image-tuning/index.ts @@ -344,10 +344,17 @@ export const controls = defineControls({ }, }); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + render(); + }, +}); + export function onCleanup() { + detachAutoResizer(); root.destroy(); } // #endregion - -render(); diff --git a/apps/typegpu-docs/src/examples/rendering/3d-fish/index.ts b/apps/typegpu-docs/src/examples/rendering/3d-fish/index.ts index e94a1d7b5d..aa04d7e9bf 100644 --- a/apps/typegpu-docs/src/examples/rendering/3d-fish/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/3d-fish/index.ts @@ -1,5 +1,5 @@ import { randf } from '@typegpu/noise'; -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; import * as m from 'wgpu-matrix'; import { simulate } from './compute.ts'; import { loadModel } from './load-model.ts'; @@ -421,23 +421,26 @@ window.addEventListener('touchmove', touchMoveEventListener); // observer and cleanup -const resizeObserver = new ResizeObserver(() => { - camera.projection = m.mat4.perspective( - Math.PI / 4, - canvas.clientWidth / canvas.clientHeight, - 0.1, - 1000, - d.mat4x4f(), - ); - - depthTexture.destroy(); - depthTexture = root.device.createTexture({ - size: [canvas.width, canvas.height, 1], - format: 'depth24plus', - usage: GPUTextureUsage.RENDER_ATTACHMENT, - }); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + camera.projection = m.mat4.perspective( + Math.PI / 4, + canvas.clientWidth / canvas.clientHeight, + 0.1, + 1000, + d.mat4x4f(), + ); + + depthTexture.destroy(); + depthTexture = root.device.createTexture({ + size: [canvas.width, canvas.height, 1], + format: 'depth24plus', + usage: GPUTextureUsage.RENDER_ATTACHMENT, + }); + }, }); -resizeObserver.observe(canvas); export function onCleanup() { disposed = true; @@ -445,7 +448,7 @@ export function onCleanup() { window.removeEventListener('mouseup', mouseUpEventListener); window.removeEventListener('mousemove', mouseMoveEventListener); window.removeEventListener('touchmove', touchMoveEventListener); - resizeObserver.disconnect(); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/box-raytracing/index.ts b/apps/typegpu-docs/src/examples/rendering/box-raytracing/index.ts index b75bb209ef..3e6d6011e0 100644 --- a/apps/typegpu-docs/src/examples/rendering/box-raytracing/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/box-raytracing/index.ts @@ -1,5 +1,5 @@ import { linearToSrgb, srgbToLinear } from '@typegpu/color'; -import tgpu, { d, type TgpuFragmentFn, type TgpuVertexFn } from 'typegpu'; +import tgpu, { common, d, type TgpuFragmentFn, type TgpuVertexFn } from 'typegpu'; import { discard, max, min, mul, normalize, pow, sub } from 'typegpu/std'; import { mat4 } from 'wgpu-matrix'; import { defineControls } from '../../common/defineControls.ts'; @@ -274,6 +274,8 @@ const runner = (timestamp: number) => { }; animationFrameId = requestAnimationFrame(runner); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + // #region Example controls and cleanup export const controls = defineControls({ @@ -320,6 +322,7 @@ export const controls = defineControls({ export function onCleanup() { cancelAnimationFrame(animationFrameId); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/caustics/index.ts b/apps/typegpu-docs/src/examples/rendering/caustics/index.ts index 5b970d312b..3f34e808e3 100644 --- a/apps/typegpu-docs/src/examples/rendering/caustics/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/caustics/index.ts @@ -1,5 +1,5 @@ import { perlin3d } from '@typegpu/noise'; -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; import { defineControls } from '../../common/defineControls.ts'; const mainVertex = tgpu.vertexFn({ @@ -146,6 +146,8 @@ function draw(timestamp: number) { } requestAnimationFrame(draw); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + // #region Example controls and cleanup export const controls = defineControls({ @@ -162,6 +164,7 @@ export const controls = defineControls({ export function onCleanup() { isRunning = false; + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/clouds/index.ts b/apps/typegpu-docs/src/examples/rendering/clouds/index.ts index dd23f5f385..03de360513 100644 --- a/apps/typegpu-docs/src/examples/rendering/clouds/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/clouds/index.ts @@ -109,6 +109,8 @@ function render(timestamp: number) { frameId = requestAnimationFrame(render); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + const qualityOptions = { 'very high': { maxSteps: 150, @@ -145,5 +147,6 @@ export const controls = defineControls({ export function onCleanup() { cancelAnimationFrame(frameId); resizeObserver.disconnect(); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/disco/index.ts b/apps/typegpu-docs/src/examples/rendering/disco/index.ts index 85ecec9513..303b42da3b 100644 --- a/apps/typegpu-docs/src/examples/rendering/disco/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/disco/index.ts @@ -1,4 +1,4 @@ -import tgpu, { d } from 'typegpu'; +import tgpu, { common, d } from 'typegpu'; import { resolutionAccess, timeAccess } from './consts.ts'; import { mainFragment1, @@ -61,8 +61,14 @@ function render(timestamp: number) { frameId = requestAnimationFrame(render); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, +}); + export function onCleanup() { cancelAnimationFrame(frameId); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/function-visualizer/index.ts b/apps/typegpu-docs/src/examples/rendering/function-visualizer/index.ts index b426e67e53..42aaf2691c 100644 --- a/apps/typegpu-docs/src/examples/rendering/function-visualizer/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/function-visualizer/index.ts @@ -1,5 +1,5 @@ import type { TgpuGuardedComputePipeline, TgpuRawCodeSnippet } from 'typegpu'; -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; import { mat4 } from 'wgpu-matrix'; import { defineControls } from '../../common/defineControls.ts'; @@ -398,6 +398,8 @@ const resizeObserver = new ResizeObserver(() => { resizeObserver.observe(canvas); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + // #region Example controls and cleanup export const controls = defineControls({ @@ -457,6 +459,7 @@ export function onCleanup() { window.removeEventListener('touchmove', touchMoveEventListener); window.removeEventListener('touchend', touchEndEventListener); resizeObserver.disconnect(); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/jelly-slider/index.ts b/apps/typegpu-docs/src/examples/rendering/jelly-slider/index.ts index 36f5ba3c56..05dc5f9070 100644 --- a/apps/typegpu-docs/src/examples/rendering/jelly-slider/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/jelly-slider/index.ts @@ -792,10 +792,13 @@ function handleResize() { bindGroups = createBindGroups(); } -const resizeObserver = new ResizeObserver(() => { - handleResize(); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + handleResize(); + }, }); -resizeObserver.observe(canvas); animationFrameHandle = requestAnimationFrame(render); @@ -906,7 +909,7 @@ export const controls = defineControls({ export function onCleanup() { cancelAnimationFrame(animationFrameHandle); - resizeObserver.disconnect(); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/jelly-switch/index.ts b/apps/typegpu-docs/src/examples/rendering/jelly-switch/index.ts index a84d6f9e2f..940a662671 100644 --- a/apps/typegpu-docs/src/examples/rendering/jelly-switch/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/jelly-switch/index.ts @@ -545,10 +545,13 @@ function handleResize() { bindGroups = createBindGroups(); } -const resizeObserver = new ResizeObserver(() => { - handleResize(); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + handleResize(); + }, }); -resizeObserver.observe(canvas); animationFrameHandle = requestAnimationFrame(render); @@ -684,7 +687,7 @@ export const controls = defineControls({ export function onCleanup() { cancelAnimationFrame(animationFrameHandle); - resizeObserver.disconnect(); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/perlin-noise/index.ts b/apps/typegpu-docs/src/examples/rendering/perlin-noise/index.ts index 9d3de4ec8e..21c8622e9b 100644 --- a/apps/typegpu-docs/src/examples/rendering/perlin-noise/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/perlin-noise/index.ts @@ -80,6 +80,8 @@ function draw(timestamp: number) { requestAnimationFrame(draw); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + export const controls = defineControls({ 'grid size': { initial: '4', @@ -109,5 +111,6 @@ export const controls = defineControls({ export function onCleanup() { isRunning = false; + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/phong-reflection/index.ts b/apps/typegpu-docs/src/examples/rendering/phong-reflection/index.ts index 5005a7cdd6..c217e87c3f 100644 --- a/apps/typegpu-docs/src/examples/rendering/phong-reflection/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/phong-reflection/index.ts @@ -1,4 +1,4 @@ -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; import * as p from './params.ts'; import { ExampleControls, @@ -175,10 +175,13 @@ const resizeObserver = new ResizeObserver(() => { }); resizeObserver.observe(canvas); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + export function onCleanup() { cancelAnimationFrame(frameId); cleanupCamera(); resizeObserver.unobserve(canvas); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.ts b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.ts index 6a047a4580..c317000cc9 100644 --- a/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.ts @@ -420,13 +420,10 @@ function render(timestamp: number) { } animationFrameId = requestAnimationFrame(render); -const resizeObserver = new ResizeObserver((entries) => { - for (const entry of entries) { - const width = entry.contentBoxSize[0].inlineSize; - const height = entry.contentBoxSize[0].blockSize; - canvas.width = Math.max(1, Math.min(width, device.limits.maxTextureDimension2D)); - canvas.height = Math.max(1, Math.min(height, device.limits.maxTextureDimension2D)); - +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { depthTexture = root .createTexture({ size: [canvas.width, canvas.height], @@ -441,9 +438,8 @@ const resizeObserver = new ResizeObserver((entries) => { sampleCount: 4, }) .$usage('render'); - } + }, }); -resizeObserver.observe(canvas); const initialCamPos = { x: 5, y: 5, z: -5 }; let theta = Math.atan2(initialCamPos.z, initialCamPos.x); @@ -651,7 +647,7 @@ export function onCleanup() { window.removeEventListener('mousemove', mouseMoveEventListener); window.removeEventListener('touchend', touchEndEventListener); window.removeEventListener('touchmove', touchMoveEventListener); - resizeObserver.disconnect(); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/pom/index.ts b/apps/typegpu-docs/src/examples/rendering/pom/index.ts index 7778bd2e79..d2632fc2a5 100644 --- a/apps/typegpu-docs/src/examples/rendering/pom/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/pom/index.ts @@ -1,5 +1,5 @@ import { randf } from '@typegpu/noise'; -import tgpu, { d, std, type RenderFlag, type TgpuTexture } from 'typegpu'; +import tgpu, { common, d, std, type RenderFlag, type TgpuTexture } from 'typegpu'; import { Camera, setupOrbitCamera } from '../../common/setup-orbit-camera.ts'; import { defineControls } from '../../common/defineControls.ts'; import { @@ -483,13 +483,18 @@ export const controls = defineControls({ }, }); -const resizeObserver = new ResizeObserver(createDepthTexture); -resizeObserver.observe(canvas); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + createDepthTexture(); + }, +}); export function onCleanup() { cancelAnimationFrame(frameId); cleanupCamera(); - resizeObserver.unobserve(canvas); + detachAutoResizer(); depthTexture.destroy(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/radiance-cascades-drawing/index.ts b/apps/typegpu-docs/src/examples/rendering/radiance-cascades-drawing/index.ts index 9d317a16a5..dedad17c0b 100644 --- a/apps/typegpu-docs/src/examples/rendering/radiance-cascades-drawing/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/radiance-cascades-drawing/index.ts @@ -215,6 +215,8 @@ function frame(timestamp: number) { frameId = requestAnimationFrame(frame); } +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + // #region Example controls and cleanup export const controls = defineControls({ @@ -247,6 +249,7 @@ export const controls = defineControls({ export function onCleanup() { cancelAnimationFrame(frameId); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/radiance-cascades/index.ts b/apps/typegpu-docs/src/examples/rendering/radiance-cascades/index.ts index 2895fbc41e..04e8dcf39f 100644 --- a/apps/typegpu-docs/src/examples/rendering/radiance-cascades/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/radiance-cascades/index.ts @@ -475,15 +475,18 @@ const dragController = new DragController(canvas, onDrag, onDrag); // #region Example controls and cleanup let resizeTimeout: ReturnType; -const resizeObserver = new ResizeObserver(() => { - clearTimeout(resizeTimeout); - resizeTimeout = setTimeout(recreateSizedResources, 100); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(recreateSizedResources, 100); + }, }); -resizeObserver.observe(canvas); export function onCleanup() { dragController.destroy(); - resizeObserver.disconnect(); + detachAutoResizer(); clearTimeout(resizeTimeout); if (frameId !== null) { cancelAnimationFrame(frameId); diff --git a/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts b/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts index fd87b161d9..e7ccc894af 100644 --- a/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts @@ -1,5 +1,5 @@ import { sdBoxFrame3d, sdPlane, sdSphere } from '@typegpu/sdf'; -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; const root = await tgpu.init(); const canvas = document.querySelector('canvas') as HTMLCanvasElement; @@ -232,7 +232,13 @@ function run(timestamp: number) { animationFrame = requestAnimationFrame(run); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, +}); + export function onCleanup() { cancelAnimationFrame(animationFrame); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/render-bundles-with/index.ts b/apps/typegpu-docs/src/examples/rendering/render-bundles-with/index.ts index ac7bf9f395..abe3e16944 100644 --- a/apps/typegpu-docs/src/examples/rendering/render-bundles-with/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/render-bundles-with/index.ts @@ -1,5 +1,5 @@ import { perlin2d } from '@typegpu/noise'; -import tgpu, { d } from 'typegpu'; +import tgpu, { common, d } from 'typegpu'; import * as m from 'wgpu-matrix'; import { defineControls } from '../../common/defineControls.ts'; import { setupOrbitCamera } from '../../common/setup-orbit-camera.ts'; @@ -197,6 +197,8 @@ function frame() { requestAnimationFrame(frame); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + // #region Example controls and cleanup export const controls = defineControls({ @@ -220,5 +222,6 @@ export function onCleanup() { cleanupCamera(); perlinCache.destroy(); depthTexture.destroy(); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/render-bundles/index.ts b/apps/typegpu-docs/src/examples/rendering/render-bundles/index.ts index dd0b073176..1a31f6fea2 100644 --- a/apps/typegpu-docs/src/examples/rendering/render-bundles/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/render-bundles/index.ts @@ -1,5 +1,5 @@ import { perlin2d } from '@typegpu/noise'; -import tgpu, { d } from 'typegpu'; +import tgpu, { common, d } from 'typegpu'; import * as m from 'wgpu-matrix'; import { defineControls } from '../../common/defineControls.ts'; import { setupOrbitCamera } from '../../common/setup-orbit-camera.ts'; @@ -197,6 +197,11 @@ function frame() { requestAnimationFrame(frame); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, +}); + // #region Example controls and cleanup export const controls = defineControls({ @@ -218,6 +223,7 @@ export const controls = defineControls({ export function onCleanup() { disposed = true; cleanupCamera(); + detachAutoResizer(); perlinCache.destroy(); depthTexture.destroy(); root.destroy(); diff --git a/apps/typegpu-docs/src/examples/rendering/simple-shadow/index.ts b/apps/typegpu-docs/src/examples/rendering/simple-shadow/index.ts index 33fb648256..82d5bebe47 100644 --- a/apps/typegpu-docs/src/examples/rendering/simple-shadow/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/simple-shadow/index.ts @@ -1,4 +1,4 @@ -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; import { mat4 } from 'wgpu-matrix'; import { createCuboid, createPlane } from './geometry.ts'; import { @@ -340,21 +340,24 @@ function render() { } frameId = requestAnimationFrame(render); -const resizeObserver = new ResizeObserver(() => { - canvasTextures = createCanvasTextures(); - - const newProjection = mat4.perspective( - Math.PI / 4, - canvas.width / canvas.height, - 0.1, - 100, - d.mat4x4f(), - ); - cameraUniform.patch({ - projection: newProjection, - }); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + canvasTextures = createCanvasTextures(); + + const newProjection = mat4.perspective( + Math.PI / 4, + canvas.width / canvas.height, + 0.1, + 100, + d.mat4x4f(), + ); + cameraUniform.patch({ + projection: newProjection, + }); + }, }); -resizeObserver.observe(canvas); export const controls = defineControls({ 'camera X': { @@ -451,6 +454,6 @@ export function onCleanup() { if (frameId !== null) { cancelAnimationFrame(frameId); } - resizeObserver.disconnect(); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/smoky-triangle/index.ts b/apps/typegpu-docs/src/examples/rendering/smoky-triangle/index.ts index bb750e7a7e..5e085f56d3 100644 --- a/apps/typegpu-docs/src/examples/rendering/smoky-triangle/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/smoky-triangle/index.ts @@ -1,5 +1,5 @@ import { perlin3d } from '@typegpu/noise'; -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; const Params = d.struct({ fromColor: d.vec3f, @@ -93,6 +93,8 @@ function frame(timestamp: number) { } frameId = requestAnimationFrame(frame); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + export const controls = { Distortion: { initial: 0.05, @@ -164,5 +166,6 @@ export const controls = { export function onCleanup() { cancelAnimationFrame(frameId); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/suika-sdf/index.ts b/apps/typegpu-docs/src/examples/rendering/suika-sdf/index.ts index c2c5b6450a..37fce94715 100644 --- a/apps/typegpu-docs/src/examples/rendering/suika-sdf/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/suika-sdf/index.ts @@ -1,5 +1,5 @@ import * as sdf from '@typegpu/sdf'; -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; import { fullScreenTriangle } from 'typegpu/common'; import { DROP_Y, @@ -414,18 +414,21 @@ const renderPipeline = root.createRenderPipeline({ }, }); -const resizeObserver = new ResizeObserver(() => { - mergedField.distance.destroy(); - mergedField.info.destroy(); - mergedField = createMergedFieldResources(); - distanceView = mergedField.distance.createView(d.texture2d()); - infoView = mergedField.info.createView(d.texture2d()); - mergedFieldBindGroup = root.createBindGroup(mergedFieldLayout, { - distance: distanceView, - info: infoView, - }); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + mergedField.distance.destroy(); + mergedField.info.destroy(); + mergedField = createMergedFieldResources(); + distanceView = mergedField.distance.createView(d.texture2d()); + infoView = mergedField.info.createView(d.texture2d()); + mergedFieldBindGroup = root.createBindGroup(mergedFieldLayout, { + distance: distanceView, + info: infoView, + }); + }, }); -resizeObserver.observe(canvas); canvas.addEventListener('touchstart', (e) => e.preventDefault(), { passive: false, signal }); canvas.addEventListener('touchmove', (e) => e.preventDefault(), { passive: false, signal }); @@ -676,6 +679,6 @@ export const controls = defineControls({ export function onCleanup() { cleanupController.abort(); cancelAnimationFrame(animationFrameId); - resizeObserver.disconnect(); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/two-boxes/index.ts b/apps/typegpu-docs/src/examples/rendering/two-boxes/index.ts index feecac1b2a..11f52c24ee 100644 --- a/apps/typegpu-docs/src/examples/rendering/two-boxes/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/two-boxes/index.ts @@ -1,5 +1,5 @@ import type { RenderFlag, TgpuBindGroup, TgpuBuffer, TgpuTexture, VertexFlag } from 'typegpu'; -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; import * as m from 'wgpu-matrix'; // Initialization @@ -465,6 +465,8 @@ const resizeObserver = new ResizeObserver(() => { }); resizeObserver.observe(canvas); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + export function onCleanup() { cancelAnimationFrame(animationFrameId); window.removeEventListener('mouseup', mouseUpEventListener); @@ -472,6 +474,7 @@ export function onCleanup() { window.removeEventListener('touchend', touchEndEventListener); window.removeEventListener('touchmove', touchMoveEventListener); resizeObserver.disconnect(); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/xor-dev-centrifuge-2/index.ts b/apps/typegpu-docs/src/examples/rendering/xor-dev-centrifuge-2/index.ts index 20dbe57d37..26809424c9 100644 --- a/apps/typegpu-docs/src/examples/rendering/xor-dev-centrifuge-2/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/xor-dev-centrifuge-2/index.ts @@ -10,7 +10,7 @@ * ``` */ -import tgpu, { d } from 'typegpu'; +import tgpu, { common, d } from 'typegpu'; import { abs, atan2, cos, gt, length, normalize, select, sign, tanh } from 'typegpu/std'; import { defineControls } from '../../common/defineControls.ts'; @@ -120,6 +120,8 @@ function draw(timestamp: number) { requestAnimationFrame(draw); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + // #region Example controls and cleanup export const controls = defineControls({ @@ -178,6 +180,7 @@ export const controls = defineControls({ export function onCleanup() { isRunning = false; + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/xor-dev-runner/index.ts b/apps/typegpu-docs/src/examples/rendering/xor-dev-runner/index.ts index 99225f1ed4..0d660e6c94 100644 --- a/apps/typegpu-docs/src/examples/rendering/xor-dev-runner/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/xor-dev-runner/index.ts @@ -12,7 +12,7 @@ * ``` */ -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; import { abs, add, cos, max, min, mul, select, sign, sin, sub, tanh } from 'typegpu/std'; import { defineControls } from '../../common/defineControls.ts'; import { Camera, setupFirstPersonCamera } from '../../common/setup-first-person-camera.ts'; @@ -172,6 +172,11 @@ function draw() { requestAnimationFrame(draw); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, +}); + // #region Example controls and cleanup export const controls = defineControls({ @@ -210,6 +215,7 @@ export const controls = defineControls({ export function onCleanup() { isRunning = false; cleanupCamera(); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simple/gradient-tiles/index.ts b/apps/typegpu-docs/src/examples/simple/gradient-tiles/index.ts index f53a95ac67..8063b03bea 100644 --- a/apps/typegpu-docs/src/examples/simple/gradient-tiles/index.ts +++ b/apps/typegpu-docs/src/examples/simple/gradient-tiles/index.ts @@ -33,7 +33,13 @@ function draw(spanXValue: number, spanYValue: number) { let spanX = 10; let spanY = 10; -draw(spanX, spanY); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + draw(spanX, spanY); + }, +}); // #region Example controls and cleanup @@ -62,6 +68,7 @@ export const controls = defineControls({ }); export function onCleanup() { + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts b/apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts index fd572f9cef..c5e0a8ee27 100644 --- a/apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts +++ b/apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts @@ -187,6 +187,8 @@ function render() { } frameId = requestAnimationFrame(render); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + export const controls = defineControls({ 'Rectangle dims': { initial: defaultParams.rectDims, @@ -305,5 +307,6 @@ export function onCleanup() { cancelAnimationFrame(frameId); } window.removeEventListener('mousemove', handleMouseMove); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simple/mesh-skinning/index.ts b/apps/typegpu-docs/src/examples/simple/mesh-skinning/index.ts index 397903641f..da9da9c0e2 100644 --- a/apps/typegpu-docs/src/examples/simple/mesh-skinning/index.ts +++ b/apps/typegpu-docs/src/examples/simple/mesh-skinning/index.ts @@ -525,6 +525,8 @@ function render(frameTimeMs: number) { let animationId: number | undefined; animationId = requestAnimationFrame(render); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + export const controls = defineControls({ Animation: { initial: toLabel(selectedVariant.id), @@ -561,5 +563,6 @@ export function onCleanup() { } resizeObserver.disconnect(); cleanupCamera(); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simple/oklab/index.ts b/apps/typegpu-docs/src/examples/simple/oklab/index.ts index 41dc549df1..4bb3a9798d 100644 --- a/apps/typegpu-docs/src/examples/simple/oklab/index.ts +++ b/apps/typegpu-docs/src/examples/simple/oklab/index.ts @@ -143,9 +143,13 @@ function draw() { pipeline.withColorAttachment({ view: context }).draw(3); } -setTimeout(() => { - draw(); -}, 100); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + draw(); + }, +}); // #region Example controls and cleanup @@ -211,6 +215,7 @@ export const controls = defineControls({ }); export function onCleanup() { + detachAutoResizer(); root.destroy(); cleanupController.abort(); } diff --git a/apps/typegpu-docs/src/examples/simple/ripple-cube/index.ts b/apps/typegpu-docs/src/examples/simple/ripple-cube/index.ts index 1ddb0d0a29..198e228bba 100644 --- a/apps/typegpu-docs/src/examples/simple/ripple-cube/index.ts +++ b/apps/typegpu-docs/src/examples/simple/ripple-cube/index.ts @@ -1,6 +1,6 @@ import { perlin3d, randf } from '@typegpu/noise'; import * as sdf from '@typegpu/sdf'; -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; import { Camera, setupOrbitCamera } from '../../common/setup-orbit-camera.ts'; import { createBackgroundCubemap } from './background.ts'; import { GRID_SIZE, halton, LIGHT_COUNT, MAX_DIST, MAX_STEPS, SURF_DIST } from './constants.ts'; @@ -223,6 +223,8 @@ function run(timestamp: number) { animationFrame = requestAnimationFrame(run); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + export const controls = defineControls({ metallic: { min: 0, @@ -299,5 +301,6 @@ export const controls = defineControls({ export function onCleanup() { cancelAnimationFrame(animationFrame); cameraResult.cleanupCamera(); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simple/square/index.ts b/apps/typegpu-docs/src/examples/simple/square/index.ts index f4aa851d4d..fb4b115495 100644 --- a/apps/typegpu-docs/src/examples/simple/square/index.ts +++ b/apps/typegpu-docs/src/examples/simple/square/index.ts @@ -1,4 +1,4 @@ -import tgpu, { d } from 'typegpu'; +import tgpu, { common, d } from 'typegpu'; import { defineControls } from '../../common/defineControls.ts'; const root = await tgpu.init(); @@ -61,7 +61,14 @@ const pipeline = root function render() { pipeline.with(vertexLayout, colorBuffer).withColorAttachment({ view: context }).drawIndexed(6); } -render(); + +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + render(); + }, +}); // #region Example controls & Cleanup @@ -92,6 +99,7 @@ export const controls = defineControls({ }); export function onCleanup() { + detachAutoResizer(); root.destroy(); } // #endregion diff --git a/apps/typegpu-docs/src/examples/simple/stencil/index.ts b/apps/typegpu-docs/src/examples/simple/stencil/index.ts index 2cfdb28016..b37b88251d 100644 --- a/apps/typegpu-docs/src/examples/simple/stencil/index.ts +++ b/apps/typegpu-docs/src/examples/simple/stencil/index.ts @@ -1,4 +1,4 @@ -import tgpu, { d } from 'typegpu'; +import tgpu, { common, d } from 'typegpu'; const root = await tgpu.init(); const canvas = document.querySelector('canvas') as HTMLCanvasElement; @@ -124,9 +124,12 @@ const resizeObserver = new ResizeObserver(() => { }); resizeObserver.observe(canvas); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + export function onCleanup() { if (frameId) { cancelAnimationFrame(frameId); } + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simple/vaporrave/index.ts b/apps/typegpu-docs/src/examples/simple/vaporrave/index.ts index 5dabe04333..b76ab18446 100644 --- a/apps/typegpu-docs/src/examples/simple/vaporrave/index.ts +++ b/apps/typegpu-docs/src/examples/simple/vaporrave/index.ts @@ -1,6 +1,6 @@ import { perlin3d } from '@typegpu/noise'; import { sdPlane } from '@typegpu/sdf'; -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; import * as c from './constants.ts'; import { circles, grid } from './floor.ts'; @@ -157,10 +157,13 @@ function run(timestamp: number) { animationFrame = requestAnimationFrame(run); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + // #region Example controls and cleanup export function onCleanup() { cancelAnimationFrame(animationFrame); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simulation/boids/index.ts b/apps/typegpu-docs/src/examples/simulation/boids/index.ts index 0f484559cb..6be2f42814 100644 --- a/apps/typegpu-docs/src/examples/simulation/boids/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/boids/index.ts @@ -1,4 +1,4 @@ -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; import { defineControls } from '../../common/defineControls.ts'; const triangleAmount = 1000; @@ -242,6 +242,8 @@ function frame() { animationFrameId = requestAnimationFrame(frame); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + // #region Example controls and cleanup export const controls = defineControls({ @@ -288,6 +290,7 @@ export const controls = defineControls({ export function onCleanup() { cancelAnimationFrame(animationFrameId); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simulation/confetti/index.ts b/apps/typegpu-docs/src/examples/simulation/confetti/index.ts index f92e441102..8aeeaa4ac7 100644 --- a/apps/typegpu-docs/src/examples/simulation/confetti/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/confetti/index.ts @@ -1,4 +1,4 @@ -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; import { defineControls } from '../../common/defineControls.ts'; // constants @@ -151,6 +151,8 @@ const runner = (timestamp: number) => { animationFrameId = requestAnimationFrame(runner); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + // example controls and cleanup export const controls = defineControls({ @@ -161,5 +163,6 @@ export const controls = defineControls({ export function onCleanup() { cancelAnimationFrame(animationFrameId); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts b/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts index 06b40b268a..43d62216b7 100644 --- a/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts @@ -1,5 +1,5 @@ import { randf } from '@typegpu/noise'; -import tgpu, { d, std, type TgpuBufferMutable, type TgpuBufferReadonly } from 'typegpu'; +import tgpu, { common, d, std, type TgpuBufferMutable, type TgpuBufferReadonly } from 'typegpu'; import { defineControls } from '../../common/defineControls.ts'; const MAX_GRID_SIZE = 1024; @@ -537,6 +537,8 @@ const runner = (timestamp: number) => { animationFrameId = requestAnimationFrame(runner); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + export const controls = defineControls({ 'source intensity': { initial: sourceIntensity, @@ -598,5 +600,6 @@ export const controls = defineControls({ export function onCleanup() { cancelAnimationFrame(animationFrameId); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simulation/fluid-with-atomics/index.ts b/apps/typegpu-docs/src/examples/simulation/fluid-with-atomics/index.ts index 3e550a0eff..0e3b3f5733 100644 --- a/apps/typegpu-docs/src/examples/simulation/fluid-with-atomics/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/fluid-with-atomics/index.ts @@ -625,6 +625,8 @@ function run(timestamp: number) { } animationFrame = requestAnimationFrame(run); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + export const controls = defineControls({ size: { initial: 32, @@ -694,6 +696,7 @@ export const controls = defineControls({ export function onCleanup() { cancelAnimationFrame(animationFrame); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simulation/game-of-life/index.ts b/apps/typegpu-docs/src/examples/simulation/game-of-life/index.ts index f6276a9478..90ad1dd5bb 100644 --- a/apps/typegpu-docs/src/examples/simulation/game-of-life/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/game-of-life/index.ts @@ -402,6 +402,8 @@ function frame(timestamp: number) { } frameId = requestAnimationFrame(frame); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + // #region Example controls & Cleanup export const controls = defineControls({ @@ -506,6 +508,7 @@ export const controls = defineControls({ export function onCleanup() { cancelAnimationFrame(frameId); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/index.ts b/apps/typegpu-docs/src/examples/simulation/gravity/index.ts index 39601f5c9e..4e4b57a18f 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/index.ts @@ -1,4 +1,4 @@ -import tgpu, { d, type StorageFlag, type TgpuBindGroup, type TgpuBuffer } from 'typegpu'; +import tgpu, { common, d, type StorageFlag, type TgpuBindGroup, type TgpuBuffer } from 'typegpu'; import { computeCollisionsShader, computeGravityShader } from './compute.ts'; import { collisionBehaviors, @@ -260,6 +260,8 @@ const resizeObserver = new ResizeObserver(() => { }); resizeObserver.observe(canvas); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + function hideHelp() { const helpElem = document.getElementById('help'); if (helpElem) { @@ -274,6 +276,7 @@ export function onCleanup() { destroyed = true; cleanupCamera(); resizeObserver.unobserve(canvas); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts b/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts index dbe6d00016..746a3ef6c9 100644 --- a/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts @@ -471,6 +471,8 @@ function frame(timestamp: number) { } requestAnimationFrame(frame); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + // #region Example controls and cleanup let isDragging = false; @@ -620,6 +622,7 @@ export const controls = defineControls({ }); export function onCleanup() { + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simulation/slime-mold/index.ts b/apps/typegpu-docs/src/examples/simulation/slime-mold/index.ts index ecc3184d6f..1b9dac673d 100644 --- a/apps/typegpu-docs/src/examples/simulation/slime-mold/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/slime-mold/index.ts @@ -1,5 +1,5 @@ import { randf } from '@typegpu/noise'; -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; import { defineControls } from '../../common/defineControls.ts'; const root = await tgpu.init(); @@ -243,6 +243,8 @@ function frame(now: number) { } requestAnimationFrame(frame); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + // #region Example controls and cleanup export const controls = defineControls({ @@ -294,6 +296,7 @@ export const controls = defineControls({ }); export function onCleanup() { + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simulation/stable-fluid/index.ts b/apps/typegpu-docs/src/examples/simulation/stable-fluid/index.ts index 420c81d294..679013a5f9 100644 --- a/apps/typegpu-docs/src/examples/simulation/stable-fluid/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/stable-fluid/index.ts @@ -1,4 +1,5 @@ import tgpu, { + common, d, type TgpuBindGroup, type TgpuComputeFn, @@ -362,6 +363,8 @@ function loop() { loop(); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + // #region Example controls and cleanup canvas.addEventListener('mousedown', (e) => { @@ -484,6 +487,7 @@ export const controls = defineControls({ export function onCleanup() { window.removeEventListener('mouseup', mouseUpEventListener); window.removeEventListener('touchend', touchEndEventListener); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simulation/wind-map/index.ts b/apps/typegpu-docs/src/examples/simulation/wind-map/index.ts index 2ac9277b7c..90a75698cb 100644 --- a/apps/typegpu-docs/src/examples/simulation/wind-map/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/wind-map/index.ts @@ -6,7 +6,7 @@ import { polylineVariableWidth, startCapSlot, } from '@typegpu/geometry'; -import tgpu from 'typegpu'; +import tgpu, { common } from 'typegpu'; import { arrayOf, builtin, f32, i32, struct, u16, u32, vec2f, vec4f } from 'typegpu/data'; import { clamp, mix, normalize, select } from 'typegpu/std'; import { defineControls } from '../../common/defineControls.ts'; @@ -267,6 +267,8 @@ const runAnimationFrame = () => { }; runAnimationFrame(); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + // #region Example controls & Cleanup export const controls = defineControls({ @@ -279,6 +281,7 @@ export const controls = defineControls({ }); export function onCleanup() { + detachAutoResizer(); root.destroy(); root.device.destroy(); cancelAnimationFrame(frameId); diff --git a/apps/typegpu-docs/src/examples/tests/log-test/index.ts b/apps/typegpu-docs/src/examples/tests/log-test/index.ts index 585a9bccbb..daf9edf0d2 100644 --- a/apps/typegpu-docs/src/examples/tests/log-test/index.ts +++ b/apps/typegpu-docs/src/examples/tests/log-test/index.ts @@ -1,4 +1,4 @@ -import tgpu, { d, std, type TgpuVertexFn } from 'typegpu'; +import tgpu, { common, d, std, type TgpuVertexFn } from 'typegpu'; import { defineControls } from '../../common/defineControls.ts'; const root = await tgpu.init({ @@ -288,7 +288,10 @@ export const controls = defineControls({ }, }); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + export function onCleanup() { + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/tests/rc-docs-examples/index.ts b/apps/typegpu-docs/src/examples/tests/rc-docs-examples/index.ts index cd1b337b22..6f709488c8 100644 --- a/apps/typegpu-docs/src/examples/tests/rc-docs-examples/index.ts +++ b/apps/typegpu-docs/src/examples/tests/rc-docs-examples/index.ts @@ -270,6 +270,8 @@ function frame() { frameId = requestAnimationFrame(frame); } +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + function setSnippet(snippet: RcSnippet) { snippetMode.write(RC_SNIPPETS.indexOf(snippet)); } @@ -283,6 +285,7 @@ export const controls = defineControls({ }); export function onCleanup() { + detachAutoResizer(); cancelAnimationFrame(frameId); basicRunner.destroy(); customRunner.destroy(); diff --git a/apps/typegpu-docs/src/examples/tests/sdf-docs-examples/index.ts b/apps/typegpu-docs/src/examples/tests/sdf-docs-examples/index.ts index 8702000434..e360880d0f 100644 --- a/apps/typegpu-docs/src/examples/tests/sdf-docs-examples/index.ts +++ b/apps/typegpu-docs/src/examples/tests/sdf-docs-examples/index.ts @@ -291,6 +291,8 @@ function frame() { frameId = requestAnimationFrame(frame); } +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + function setSnippet(snippet: SdfSnippet) { snippetMode.write(SDF_SNIPPETS.indexOf(snippet)); } @@ -304,6 +306,7 @@ export const controls = defineControls({ }); export function onCleanup() { + detachAutoResizer(); cancelAnimationFrame(frameId); floodRunner.destroy(); sourceTexture.destroy(); diff --git a/apps/typegpu-docs/src/examples/tests/texture-test/index.ts b/apps/typegpu-docs/src/examples/tests/texture-test/index.ts index 9b0f7efccf..8bffe204fc 100644 --- a/apps/typegpu-docs/src/examples/tests/texture-test/index.ts +++ b/apps/typegpu-docs/src/examples/tests/texture-test/index.ts @@ -135,6 +135,8 @@ function render() { } requestAnimationFrame(render); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + export const controls = defineControls({ Format: { initial: currentFormat, @@ -179,5 +181,6 @@ export const controls = defineControls({ }); export function onCleanup() { + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/tests/uniformity/index.ts b/apps/typegpu-docs/src/examples/tests/uniformity/index.ts index 62267e1c4a..191d81c369 100644 --- a/apps/typegpu-docs/src/examples/tests/uniformity/index.ts +++ b/apps/typegpu-docs/src/examples/tests/uniformity/index.ts @@ -83,14 +83,17 @@ export const controls = defineControls({ }, }); -const resizeObserver = new ResizeObserver(() => { - canvasRatioUniform.write(canvas.width / canvas.height); - redraw(); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + canvasRatioUniform.write(canvas.width / canvas.height); + redraw(); + }, }); -resizeObserver.observe(canvas); export function onCleanup() { - resizeObserver.disconnect(); + detachAutoResizer(); root.destroy(); } From ab9aaa9915af69922473763eb61bc416797ef58e Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Tue, 16 Jun 2026 16:49:30 +0200 Subject: [PATCH 3/8] Update examples and not resizing canvases by default --- .../src/components/ExampleView.tsx | 82 +-------- .../algorithms/bitonic-sort/index.html | 2 +- .../algorithms/genetic-racing/index.html | 2 +- .../algorithms/jump-flood-distance/index.html | 2 +- .../algorithms/jump-flood-voronoi/index.html | 2 +- .../algorithms/mnist-inference/index.html | 6 +- .../algorithms/mnist-inference/index.ts | 5 +- .../src/examples/geometry/circles/index.html | 2 +- .../src/examples/geometry/circles/index.ts | 9 +- .../geometry/lines-combinations/index.html | 2 +- .../geometry/lines-combinations/index.ts | 16 +- .../image-processing/ascii-filter/index.html | 2 +- .../image-processing/ascii-filter/index.ts | 41 +++-- .../background-segmentation/index.html | 2 +- .../background-segmentation/index.ts | 45 +++-- .../examples/image-processing/blur/index.html | 2 +- .../examples/image-processing/blur/index.ts | 6 + .../camera-thresholding/index.html | 2 +- .../camera-thresholding/index.ts | 35 ++-- .../image-processing/chroma-keying/index.html | 2 +- .../image-processing/chroma-keying/index.ts | 36 ++-- .../image-processing/image-tuning/index.html | 2 +- .../image-processing/image-tuning/index.ts | 4 + .../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 | 42 +++-- .../rendering/box-raytracing/index.html | 2 +- .../rendering/box-raytracing/index.ts | 18 +- .../examples/rendering/caustics/index.html | 2 +- .../src/examples/rendering/caustics/index.ts | 28 +++- .../src/examples/rendering/clouds/index.html | 2 +- .../src/examples/rendering/clouds/index.ts | 45 +++-- .../src/examples/rendering/clouds/types.ts | 1 + .../rendering/cubemap-reflection/index.html | 2 +- .../rendering/cubemap-reflection/index.ts | 2 + .../src/examples/rendering/disco/index.html | 2 +- .../src/examples/rendering/disco/index.ts | 22 ++- .../rendering/function-visualizer/index.html | 2 +- .../rendering/function-visualizer/index.ts | 31 ++-- .../rendering/jelly-slider/index.html | 2 +- .../examples/rendering/jelly-slider/index.ts | 39 +++-- .../rendering/jelly-switch/index.html | 2 +- .../examples/rendering/jelly-switch/index.ts | 35 ++-- .../examples/rendering/os-awards/index.html | 2 +- .../src/examples/rendering/os-awards/index.ts | 42 +++-- .../rendering/perlin-noise/index.html | 2 +- .../examples/rendering/perlin-noise/index.ts | 27 ++- .../rendering/phong-reflection/index.html | 2 +- .../rendering/phong-reflection/index.ts | 36 ++-- .../rendering/point-light-shadow/index.html | 2 +- .../rendering/point-light-shadow/index.ts | 53 +++--- .../src/examples/rendering/pom/index.html | 2 +- .../src/examples/rendering/pom/index.ts | 25 +-- .../drawInteraction.ts | 12 +- .../radiance-cascades-drawing/index.html | 2 +- .../radiance-cascades-drawing/index.ts | 23 ++- .../rendering/radiance-cascades/index.html | 2 +- .../rendering/radiance-cascades/index.ts | 5 + .../rendering/ray-marching/index.html | 2 +- .../rendering/render-bundles-with/index.html | 2 +- .../rendering/render-bundles-with/index.ts | 22 ++- .../rendering/render-bundles/index.html | 2 +- .../rendering/render-bundles/index.ts | 13 +- .../rendering/simple-shadow/index.html | 2 +- .../examples/rendering/simple-shadow/index.ts | 16 +- .../rendering/smoky-triangle/index.html | 2 +- .../rendering/smoky-triangle/index.ts | 18 +- .../examples/rendering/suika-sdf/index.html | 2 +- .../src/examples/rendering/suika-sdf/index.ts | 33 ++-- .../examples/rendering/two-boxes/index.html | 2 +- .../src/examples/rendering/two-boxes/index.ts | 19 ++- .../rendering/xor-dev-centrifuge-2/index.html | 2 +- .../rendering/xor-dev-runner/index.html | 2 +- .../examples/simple/gradient-tiles/index.html | 2 +- .../examples/simple/gradient-tiles/index.ts | 4 + .../examples/simple/liquid-glass/index.html | 2 +- .../src/examples/simple/liquid-glass/index.ts | 22 ++- .../examples/simple/mesh-skinning/index.html | 2 +- .../examples/simple/mesh-skinning/index.ts | 20 +-- .../src/examples/simple/oklab/index.html | 6 +- .../src/examples/simple/oklab/index.ts | 27 ++- .../examples/simple/ripple-cube/index.html | 2 +- .../src/examples/simple/ripple-cube/index.ts | 52 ++++-- .../simple/ripple-cube/post-processing.ts | 156 ++++++++++++------ .../src/examples/simple/square/index.html | 2 +- .../src/examples/simple/square/index.ts | 4 + .../src/examples/simple/stencil/index.html | 2 +- .../src/examples/simple/stencil/index.ts | 48 +++--- .../src/examples/simple/triangle/index.html | 2 +- .../src/examples/simple/triangle/index.ts | 5 + .../src/examples/simple/vaporrave/index.html | 2 +- .../src/examples/simple/vaporrave/index.ts | 22 ++- .../src/examples/simulation/boids/index.html | 2 +- .../src/examples/simulation/boids/index.ts | 29 +++- .../examples/simulation/confetti/index.html | 2 +- .../fluid-double-buffering/index.html | 2 +- .../fluid-double-buffering/index.ts | 12 +- .../simulation/fluid-with-atomics/index.html | 2 +- .../simulation/fluid-with-atomics/index.ts | 12 +- .../simulation/game-of-life/index.html | 2 +- .../examples/simulation/game-of-life/index.ts | 30 +++- .../examples/simulation/game-of-life/input.ts | 15 +- .../examples/simulation/gravity/index.html | 2 +- .../src/examples/simulation/gravity/index.ts | 22 +-- .../simulation/slime-mold-3d/index.html | 2 +- .../simulation/slime-mold-3d/index.ts | 28 +++- .../examples/simulation/slime-mold/index.html | 2 +- .../examples/simulation/slime-mold/index.ts | 28 +++- .../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 | 12 +- .../tests/rc-docs-examples/index.html | 2 +- .../tests/sdf-docs-examples/index.html | 2 +- .../examples/tests/texture-test/index.html | 2 +- .../src/examples/tests/uniformity/index.html | 2 +- .../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-mismatched-types/index.html | 2 +- .../threejs/test-reused-functions/index.html | 2 +- .../src/examples/threejs/varyings/index.html | 2 +- .../typegpu/src/common/attachAutoResizer.ts | 2 +- 129 files changed, 1072 insertions(+), 659 deletions(-) diff --git a/apps/typegpu-docs/src/components/ExampleView.tsx b/apps/typegpu-docs/src/components/ExampleView.tsx index 61446fdc46..339216e0a9 100644 --- a/apps/typegpu-docs/src/components/ExampleView.tsx +++ b/apps/typegpu-docs/src/components/ExampleView.tsx @@ -1,6 +1,6 @@ import cs from 'classnames'; import { useAtom, useAtomValue, useSetAtom } from 'jotai'; -import { type RefObject, useEffect, useLayoutEffect, useRef, useState } from 'react'; +import { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { currentSnackbarAtom } from '../utils/examples/currentSnackbarAtom.ts'; import { codeEditorShownAtom, tsoverUsedAtom } from '../utils/examples/exampleViewStateAtoms.ts'; import { ExecutionCancelledError } from '../utils/examples/errors.ts'; @@ -91,7 +91,6 @@ export function ExampleView({ example, common }: Props) { }, [htmlFile]); useExample(tsImport, setSnackbarText); - useResizableCanvas(exampleHtmlRef); return ( <> @@ -207,85 +206,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/examples/algorithms/bitonic-sort/index.html b/apps/typegpu-docs/src/examples/algorithms/bitonic-sort/index.html index 65c5005787..b3805f0cfc 100644 --- a/apps/typegpu-docs/src/examples/algorithms/bitonic-sort/index.html +++ b/apps/typegpu-docs/src/examples/algorithms/bitonic-sort/index.html @@ -1,4 +1,4 @@ - + - + diff --git a/apps/typegpu-docs/src/examples/rendering/3d-fish/index.ts b/apps/typegpu-docs/src/examples/rendering/3d-fish/index.ts index aa04d7e9bf..4061e7ebda 100644 --- a/apps/typegpu-docs/src/examples/rendering/3d-fish/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/3d-fish/index.ts @@ -219,22 +219,7 @@ const computeBindGroups = [0, 1].map((idx) => }), ); -// frame - -let odd = false; -let lastTimestamp: DOMHighResTimeStamp = 0; -let animationFrameId: number; - -function frame(timestamp: DOMHighResTimeStamp) { - odd = !odd; - - currentTimeBuffer.write(timestamp); - timePassedBuffer.write((timestamp - lastTimestamp) * speedMultiplier); - lastTimestamp = timestamp; - cameraBuffer.write(camera); - - simulatePipeline.with(computeBindGroups[odd ? 1 : 0]).dispatchThreads(p.fishAmount); - +function render() { renderPipeline .withColorAttachment({ view: context, @@ -267,6 +252,25 @@ function frame(timestamp: DOMHighResTimeStamp) { .with(renderInstanceLayout, fishDataBuffers[odd ? 1 : 0]) .with(renderFishBindGroups[odd ? 1 : 0]) .draw(fishModel.polygonCount, p.fishAmount); +} + +// frame + +let odd = false; +let lastTimestamp: DOMHighResTimeStamp = 0; +let animationFrameId: number; + +function frame(timestamp: DOMHighResTimeStamp) { + odd = !odd; + + currentTimeBuffer.write(timestamp); + timePassedBuffer.write((timestamp - lastTimestamp) * speedMultiplier); + lastTimestamp = timestamp; + cameraBuffer.write(camera); + + simulatePipeline.with(computeBindGroups[odd ? 1 : 0]).dispatchThreads(p.fishAmount); + + render(); animationFrameId = requestAnimationFrame(frame); } @@ -433,12 +437,18 @@ const detachAutoResizer = common.attachAutoResizer({ d.mat4x4f(), ); + cameraBuffer.patch({ + projection: camera.projection, + }); + depthTexture.destroy(); depthTexture = root.device.createTexture({ size: [canvas.width, canvas.height, 1], format: 'depth24plus', usage: GPUTextureUsage.RENDER_ATTACHMENT, }); + + render(); }, }); diff --git a/apps/typegpu-docs/src/examples/rendering/box-raytracing/index.html b/apps/typegpu-docs/src/examples/rendering/box-raytracing/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/rendering/box-raytracing/index.html +++ b/apps/typegpu-docs/src/examples/rendering/box-raytracing/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/rendering/box-raytracing/index.ts b/apps/typegpu-docs/src/examples/rendering/box-raytracing/index.ts index 3e6d6011e0..d2e7e582fe 100644 --- a/apps/typegpu-docs/src/examples/rendering/box-raytracing/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/box-raytracing/index.ts @@ -246,6 +246,10 @@ const pipeline = root.createRenderPipeline({ // UI +function render() { + pipeline.withColorAttachment({ view: context }).draw(3); +} + let animationFrameId: number; let lastTime: number | null = null; @@ -269,12 +273,22 @@ const runner = (timestamp: number) => { frame += (rotationSpeed * deltaTime) / 1000; - pipeline.withColorAttachment({ view: context }).draw(3); + render(); animationFrameId = requestAnimationFrame(runner); }; animationFrameId = requestAnimationFrame(runner); -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + render(); + }, +}); // #region Example controls and cleanup diff --git a/apps/typegpu-docs/src/examples/rendering/caustics/index.html b/apps/typegpu-docs/src/examples/rendering/caustics/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/rendering/caustics/index.html +++ b/apps/typegpu-docs/src/examples/rendering/caustics/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/rendering/caustics/index.ts b/apps/typegpu-docs/src/examples/rendering/caustics/index.ts index 3f34e808e3..4d2b84e84d 100644 --- a/apps/typegpu-docs/src/examples/rendering/caustics/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/caustics/index.ts @@ -135,18 +135,30 @@ const pipeline = root.createRenderPipeline({ let isRunning = true; -function draw(timestamp: number) { +function render() { + pipeline.withColorAttachment({ view: context }).draw(3); +} + +function frame(timestamp: number) { if (!isRunning) return; time.write((timestamp * 0.001) % 1000); - - pipeline.withColorAttachment({ view: context }).draw(3); - - requestAnimationFrame(draw); + render(); + requestAnimationFrame(frame); } -requestAnimationFrame(draw); - -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); +requestAnimationFrame(frame); + +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + render(); + }, +}); // #region Example controls and cleanup diff --git a/apps/typegpu-docs/src/examples/rendering/clouds/index.html b/apps/typegpu-docs/src/examples/rendering/clouds/index.html index 581d6789f8..367ab17706 100644 --- a/apps/typegpu-docs/src/examples/rendering/clouds/index.html +++ b/apps/typegpu-docs/src/examples/rendering/clouds/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/rendering/clouds/index.ts b/apps/typegpu-docs/src/examples/rendering/clouds/index.ts index 03de360513..3b710ca6b6 100644 --- a/apps/typegpu-docs/src/examples/rendering/clouds/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/clouds/index.ts @@ -1,4 +1,5 @@ import tgpu, { common, d, std } from 'typegpu'; +import { randf } from '@typegpu/noise'; import { FOV_FACTOR, NOISE_TEXTURE_SIZE, @@ -11,7 +12,6 @@ import { } from './consts.ts'; import { raymarch } from './utils.ts'; import { cloudsLayout, CloudsParams } from './types.ts'; -import { randf } from '@typegpu/noise'; import { defineControls } from '../../common/defineControls.ts'; const root = await tgpu.init(); @@ -21,10 +21,10 @@ const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); const paramsUniform = root.createUniform(CloudsParams, { time: 0, + aspectRatio: 1, maxSteps: 50, maxDistance: 10.0, }); -const resolutionUniform = root.createUniform(d.vec2f, d.vec2f(canvas.width, canvas.height)); const noiseData = new Uint8Array(NOISE_TEXTURE_SIZE * NOISE_TEXTURE_SIZE); for (let i = 0; i < noiseData.length; i += 1) { @@ -57,8 +57,7 @@ const pipeline = root.createRenderPipeline({ fragment: ({ uv }) => { 'use gpu'; randf.seed2(uv * cloudsLayout.$.params.time); - const screenRes = resolutionUniform.$; - const aspect = screenRes.x / screenRes.y; + const aspect = cloudsLayout.$.params.aspectRatio; let screenPos = (uv - 0.5) * 2; screenPos = d.vec2f(screenPos.x * std.max(aspect, 1), screenPos.y * std.max(1 / aspect, 1)); @@ -86,16 +85,7 @@ const pipeline = root.createRenderPipeline({ targets: { format: presentationFormat }, }); -const resizeObserver = new ResizeObserver(() => { - resolutionUniform.write(d.vec2f(canvas.width, canvas.height)); -}); -resizeObserver.observe(canvas); - -let frameId: number; - -function render(timestamp: number) { - paramsUniform.patch({ time: (timestamp / 1000) % 500 }); - +function render() { pipeline .with(bindGroup) .withColorAttachment({ @@ -103,13 +93,33 @@ function render(timestamp: number) { clearValue: [0, 0, 0, 1], }) .draw(6); +} + +let frameId: number; - frameId = requestAnimationFrame(render); +function frame(timestamp: number) { + paramsUniform.patch({ time: (timestamp / 1000) % 500 }); + render(); + frameId = requestAnimationFrame(frame); } -frameId = requestAnimationFrame(render); +frameId = requestAnimationFrame(frame); -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + // The clouds are very blurry, so there is no benefit from full-resolution + canvas.width = Math.max(1, canvas.width / 4); + canvas.height = Math.max(1, canvas.height / 4); + + paramsUniform.patch({ + aspectRatio: canvas.width / canvas.height, + }); + + render(); + }, +}); const qualityOptions = { 'very high': { @@ -146,7 +156,6 @@ export const controls = defineControls({ export function onCleanup() { cancelAnimationFrame(frameId); - resizeObserver.disconnect(); detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/clouds/types.ts b/apps/typegpu-docs/src/examples/rendering/clouds/types.ts index c4945fb27c..7a9504ac24 100644 --- a/apps/typegpu-docs/src/examples/rendering/clouds/types.ts +++ b/apps/typegpu-docs/src/examples/rendering/clouds/types.ts @@ -2,6 +2,7 @@ import tgpu, { d } from 'typegpu'; export const CloudsParams = d.struct({ time: d.f32, + aspectRatio: d.f32, maxSteps: d.i32, maxDistance: d.f32, }); diff --git a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/index.html b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/index.html index cb4615fc87..a2d13746bb 100644 --- a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/index.html +++ b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/index.html @@ -38,4 +38,4 @@

Controls (click to dismiss)

under CC BY 4.0 / Modified from original

- + diff --git a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/index.ts b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/index.ts index 34f9ba787e..f712e8952a 100644 --- a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/index.ts @@ -285,6 +285,8 @@ const detachAutoResizer = common.attachAutoResizer({ d.mat4x4f(), ); cameraBuffer.patch({ projection: newProj }); + + render(); }, }); diff --git a/apps/typegpu-docs/src/examples/rendering/disco/index.html b/apps/typegpu-docs/src/examples/rendering/disco/index.html index 581d6789f8..a1d12c864f 100644 --- a/apps/typegpu-docs/src/examples/rendering/disco/index.html +++ b/apps/typegpu-docs/src/examples/rendering/disco/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/rendering/disco/index.ts b/apps/typegpu-docs/src/examples/rendering/disco/index.ts index 303b42da3b..825bd139ff 100644 --- a/apps/typegpu-docs/src/examples/rendering/disco/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/disco/index.ts @@ -42,11 +42,7 @@ let currentPipeline = pipelines[0]; let startTime: null | number = null; let frameId: number; -function render(timestamp: number) { - if (startTime === null) { - startTime = timestamp; - } - time.write((timestamp - startTime) / 1000); +function render() { resolutionUniform.write(d.vec2f(canvas.width, canvas.height)); currentPipeline @@ -55,15 +51,27 @@ function render(timestamp: number) { clearValue: [0, 0, 0, 1], }) .draw(6); +} + +function frame(timestamp: number) { + if (startTime === null) { + startTime = timestamp; + } + time.write((timestamp - startTime) / 1000); + + render(); - frameId = requestAnimationFrame(render); + frameId = requestAnimationFrame(frame); } -frameId = requestAnimationFrame(render); +frameId = requestAnimationFrame(frame); const detachAutoResizer = common.attachAutoResizer({ root, canvas, + onResize() { + render(); + }, }); export function onCleanup() { diff --git a/apps/typegpu-docs/src/examples/rendering/function-visualizer/index.html b/apps/typegpu-docs/src/examples/rendering/function-visualizer/index.html index 581d6789f8..367ab17706 100644 --- a/apps/typegpu-docs/src/examples/rendering/function-visualizer/index.html +++ b/apps/typegpu-docs/src/examples/rendering/function-visualizer/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/rendering/function-visualizer/index.ts b/apps/typegpu-docs/src/examples/rendering/function-visualizer/index.ts index 42aaf2691c..0d1b20a46e 100644 --- a/apps/typegpu-docs/src/examples/rendering/function-visualizer/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/function-visualizer/index.ts @@ -383,23 +383,23 @@ window.addEventListener('touchend', touchEndEventListener); // Resize observer and cleanup -const resizeObserver = new ResizeObserver(() => { - queuePropertiesBufferUpdate(); - - msTexture.destroy(); - msTexture = device.createTexture({ - size: [canvas.width, canvas.height], - sampleCount: 4, - format: presentationFormat, - usage: GPUTextureUsage.RENDER_ATTACHMENT, - }); - msView = msTexture.createView(); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + queuePropertiesBufferUpdate(); + + msTexture.destroy(); + msTexture = device.createTexture({ + size: [canvas.width, canvas.height], + sampleCount: 4, + format: presentationFormat, + usage: GPUTextureUsage.RENDER_ATTACHMENT, + }); + msView = msTexture.createView(); + }, }); -resizeObserver.observe(canvas); - -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); - // #region Example controls and cleanup export const controls = defineControls({ @@ -458,7 +458,6 @@ export function onCleanup() { window.removeEventListener('mousemove', mouseMoveEventListener); window.removeEventListener('touchmove', touchMoveEventListener); window.removeEventListener('touchend', touchEndEventListener); - resizeObserver.disconnect(); detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/jelly-slider/index.html b/apps/typegpu-docs/src/examples/rendering/jelly-slider/index.html index a1a36aafb9..0942059962 100644 --- a/apps/typegpu-docs/src/examples/rendering/jelly-slider/index.html +++ b/apps/typegpu-docs/src/examples/rendering/jelly-slider/index.html @@ -33,4 +33,4 @@ Inspired by work of Voicu Apostol - + diff --git a/apps/typegpu-docs/src/examples/rendering/jelly-slider/index.ts b/apps/typegpu-docs/src/examples/rendering/jelly-slider/index.ts index 05dc5f9070..ae859fdfef 100644 --- a/apps/typegpu-docs/src/examples/rendering/jelly-slider/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/jelly-slider/index.ts @@ -714,7 +714,6 @@ const renderPipeline = root.createRenderPipeline({ }); const eventHandler = new EventHandler(canvas); -let lastTimestamp: number | null = null; let frameCount = 0; const taaResolver = new TAAResolver(root, width, height); @@ -748,21 +747,13 @@ function createBindGroups() { let bindGroups = createBindGroups(); -let animationFrameHandle: number; -function render(timestamp: number) { +function render() { frameCount++; - camera.jitter(); - const deltaTime = Math.min(lastTimestamp !== null ? (timestamp - lastTimestamp) * 0.001 : 0, 0.1); - lastTimestamp = timestamp; + const currentFrame = frameCount % 2; + camera.jitter(); randomUniform.write(d.vec2f((Math.random() - 0.5) * 2, (Math.random() - 0.5) * 2)); - eventHandler.update(); - slider.setDragX(eventHandler.currentMouseX); - slider.update(deltaTime); - - const currentFrame = frameCount % 2; - rayMarchPipeline .withColorAttachment({ view: textures[currentFrame].sampled, @@ -777,11 +768,14 @@ function render(timestamp: number) { .withColorAttachment({ view: context }) .with(bindGroups.render[currentFrame]) .draw(3); - - animationFrameHandle = requestAnimationFrame(render); } function handleResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + [width, height] = [canvas.width * qualityScale, canvas.height * qualityScale]; camera.updateProjection(Math.PI / 4, width, height); textures = createTextures(root, width, height); @@ -790,6 +784,7 @@ function handleResize() { frameCount = 0; bindGroups = createBindGroups(); + render(); } const detachAutoResizer = common.attachAutoResizer({ @@ -800,7 +795,21 @@ const detachAutoResizer = common.attachAutoResizer({ }, }); -animationFrameHandle = requestAnimationFrame(render); +let lastTimestamp: number | null = null; +let animationFrameHandle: number; +function frame(timestamp: number) { + const deltaTime = Math.min(lastTimestamp !== null ? (timestamp - lastTimestamp) * 0.001 : 0, 0.1); + lastTimestamp = timestamp; + + eventHandler.update(); + slider.setDragX(eventHandler.currentMouseX); + slider.update(deltaTime); + + render(); + + animationFrameHandle = requestAnimationFrame(frame); +} +animationFrameHandle = requestAnimationFrame(frame); // #region Example controls and cleanup diff --git a/apps/typegpu-docs/src/examples/rendering/jelly-switch/index.html b/apps/typegpu-docs/src/examples/rendering/jelly-switch/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/rendering/jelly-switch/index.html +++ b/apps/typegpu-docs/src/examples/rendering/jelly-switch/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/rendering/jelly-switch/index.ts b/apps/typegpu-docs/src/examples/rendering/jelly-switch/index.ts index 940a662671..1248fee99d 100644 --- a/apps/typegpu-docs/src/examples/rendering/jelly-switch/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/jelly-switch/index.ts @@ -484,7 +484,6 @@ const renderPipeline = root.createRenderPipeline({ targets: { format: presentationFormat }, }); -let lastTimestamp: number | null = null; let frameCount = 0; const taaResolver = new TAAResolver(root, width, height); @@ -503,19 +502,13 @@ function createBindGroups() { let bindGroups = createBindGroups(); -let animationFrameHandle: number; -function render(timestamp: number) { +function render() { frameCount++; - camera.jitter(); - const deltaTime = Math.min(lastTimestamp !== null ? (timestamp - lastTimestamp) * 0.001 : 0, 0.1); - lastTimestamp = timestamp; + const currentFrame = frameCount % 2; + camera.jitter(); randomUniform.write(d.vec2f((Math.random() - 0.5) * 2, (Math.random() - 0.5) * 2)); - switchBehavior.update(deltaTime); - - const currentFrame = frameCount % 2; - rayMarchPipeline .withColorAttachment({ view: textures[currentFrame].sampled, @@ -530,11 +523,14 @@ function render(timestamp: number) { .withColorAttachment({ view: context }) .with(bindGroups.render[currentFrame]) .draw(3); - - animationFrameHandle = requestAnimationFrame(render); } function handleResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + [width, height] = [canvas.width * qualityScale, canvas.height * qualityScale]; camera.updateProjection(Math.PI / 4, width, height); textures = createTextures(root, width, height); @@ -543,6 +539,7 @@ function handleResize() { frameCount = 0; bindGroups = createBindGroups(); + render(); } const detachAutoResizer = common.attachAutoResizer({ @@ -553,7 +550,19 @@ const detachAutoResizer = common.attachAutoResizer({ }, }); -animationFrameHandle = requestAnimationFrame(render); +let lastTimestamp: number | null = null; +let animationFrameHandle: number; +function frame(timestamp: number) { + const deltaTime = Math.min(lastTimestamp !== null ? (timestamp - lastTimestamp) * 0.001 : 0, 0.1); + lastTimestamp = timestamp; + + switchBehavior.update(deltaTime); + + render(); + + animationFrameHandle = requestAnimationFrame(frame); +} +animationFrameHandle = requestAnimationFrame(frame); // #region Example controls and cleanup diff --git a/apps/typegpu-docs/src/examples/rendering/os-awards/index.html b/apps/typegpu-docs/src/examples/rendering/os-awards/index.html index 972583913a..86a6e42572 100644 --- a/apps/typegpu-docs/src/examples/rendering/os-awards/index.html +++ b/apps/typegpu-docs/src/examples/rendering/os-awards/index.html @@ -12,4 +12,4 @@
Loading...
- + diff --git a/apps/typegpu-docs/src/examples/rendering/os-awards/index.ts b/apps/typegpu-docs/src/examples/rendering/os-awards/index.ts index fbaaf7f3f2..f908aab54b 100644 --- a/apps/typegpu-docs/src/examples/rendering/os-awards/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/os-awards/index.ts @@ -381,22 +381,7 @@ function createDepthTexture() { return { texture, view: texture.createView() }; } -let depth = createDepthTexture(); -const resizeObserver = new ResizeObserver(() => { - depth.texture.destroy(); - depth = createDepthTexture(); -}); -resizeObserver.observe(canvas); - -let exampleDestroyed = false; -let firstFrameDrawn = false; - -function frame(timeMs: number) { - if (exampleDestroyed) { - return; - } - updateAwardTransform(timeMs); - +function render() { envPipeline.withColorAttachment({ view: context }).draw(3); awardPipeline @@ -410,6 +395,29 @@ function frame(timeMs: number) { .with(awardVertexLayout, award.vertexBuffer) .withIndexBuffer(award.indexBuffer) .drawIndexed(award.indexCount); +} + +let depth = createDepthTexture(); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + depth.texture.destroy(); + depth = createDepthTexture(); + render(); + }, +}); + +let exampleDestroyed = false; +let firstFrameDrawn = false; + +function frame(timeMs: number) { + if (exampleDestroyed) { + return; + } + updateAwardTransform(timeMs); + + render(); if (!firstFrameDrawn) { loadingScreen.style.display = 'none'; @@ -432,6 +440,6 @@ export const controls = defineControls({ export function onCleanup() { exampleDestroyed = true; cleanupCamera(); - resizeObserver.unobserve(canvas); + detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/perlin-noise/index.html b/apps/typegpu-docs/src/examples/rendering/perlin-noise/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/rendering/perlin-noise/index.html +++ b/apps/typegpu-docs/src/examples/rendering/perlin-noise/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/rendering/perlin-noise/index.ts b/apps/typegpu-docs/src/examples/rendering/perlin-noise/index.ts index 21c8622e9b..726aac4524 100644 --- a/apps/typegpu-docs/src/examples/rendering/perlin-noise/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/perlin-noise/index.ts @@ -66,21 +66,34 @@ let activeSharpenFn: 'exponential' | 'tanh' = 'exponential'; let isRunning = true; let bindGroup = root.createBindGroup(dynamicLayout, perlinCache.bindings); -function draw(timestamp: number) { +function render() { + renderPipelines[activeSharpenFn].with(bindGroup).withColorAttachment({ view: context }).draw(3); +} + +function frame(timestamp: number) { if (!isRunning) { return; } time.write((timestamp * 0.0002) % DEPTH); + render(); - renderPipelines[activeSharpenFn].with(bindGroup).withColorAttachment({ view: context }).draw(3); - - requestAnimationFrame(draw); + requestAnimationFrame(frame); } -requestAnimationFrame(draw); - -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); +requestAnimationFrame(frame); + +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + render(); + }, +}); export const controls = defineControls({ 'grid size': { diff --git a/apps/typegpu-docs/src/examples/rendering/phong-reflection/index.html b/apps/typegpu-docs/src/examples/rendering/phong-reflection/index.html index 581d6789f8..a1d12c864f 100644 --- a/apps/typegpu-docs/src/examples/rendering/phong-reflection/index.html +++ b/apps/typegpu-docs/src/examples/rendering/phong-reflection/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/rendering/phong-reflection/index.ts b/apps/typegpu-docs/src/examples/rendering/phong-reflection/index.ts index c217e87c3f..ea87431ac4 100644 --- a/apps/typegpu-docs/src/examples/rendering/phong-reflection/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/phong-reflection/index.ts @@ -101,9 +101,7 @@ let depthTexture = root.device.createTexture({ usage: GPUTextureUsage.RENDER_ATTACHMENT, }); -// frame -let frameId: number; -function frame() { +function render() { renderPipeline .withColorAttachment({ view: context, @@ -117,11 +115,30 @@ function frame() { }) .with(modelVertexLayout, model.vertexBuffer) .draw(model.polygonCount); +} +// frame +let frameId: number; +function frame() { + render(); frameId = requestAnimationFrame(frame); } frameId = requestAnimationFrame(frame); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + depthTexture.destroy(); + depthTexture = root.device.createTexture({ + size: [canvas.width, canvas.height, 1], + format: 'depth24plus', + usage: GPUTextureUsage.RENDER_ATTACHMENT, + }); + render(); + }, +}); + // #region Example controls and cleanup export const controls = defineControls({ 'light color': { @@ -165,22 +182,9 @@ export const controls = defineControls({ }, }); -const resizeObserver = new ResizeObserver(() => { - depthTexture.destroy(); - depthTexture = root.device.createTexture({ - size: [canvas.width, canvas.height, 1], - format: 'depth24plus', - usage: GPUTextureUsage.RENDER_ATTACHMENT, - }); -}); -resizeObserver.observe(canvas); - -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); - export function onCleanup() { cancelAnimationFrame(frameId); cleanupCamera(); - resizeObserver.unobserve(canvas); detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.html b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.html +++ b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.ts b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.ts index c317000cc9..76dd0c0c2b 100644 --- a/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.ts @@ -7,7 +7,6 @@ import { CameraData, InstanceData, instanceLayout, VertexData, vertexLayout } fr import { defineControls } from '../../common/defineControls.ts'; const root = await tgpu.init(); -const device = root.device; const canvas = document.querySelector('canvas') as HTMLCanvasElement; const context = root.configureContext({ canvas }); const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); @@ -356,28 +355,11 @@ let lastTime: number | null = null; let time = 0; let animationFrameId: number; -function render(timestamp: number) { - const dt = lastTime !== null ? (timestamp - lastTime) / 1000 : 0; - lastTime = timestamp; - time += dt; - - for (let i = 0; i < orbitingCubes.length; i++) { - const offset = (i / orbitingCubes.length) * Math.PI * 2; - const angle = time * 0.5 + offset; - const radius = 4 + Math.sin(time * 2 + offset * 3) * 0.5; - const x = Math.cos(angle) * radius; - const z = Math.sin(angle) * radius; - const y = 2 + Math.sin(time * 1.5 + offset * 2) * 1.5; - orbitingCubes[i].position = d.vec3f(x, y, z); - orbitingCubes[i].rotation = d.vec3f(time, time * 0.5, 0); - } - - scene.update(); +function render() { pointLight.renderShadowMaps(pipelineDepthOne, renderLayout, scene); if (showDepthPreview) { pipelinePreview.withColorAttachment({ view: context }).draw(3); - animationFrameId = requestAnimationFrame(render); return; } @@ -415,15 +397,42 @@ function render(timestamp: number) { .withIndexBuffer(BoxGeometry.indexBuffer) .with(vertexLayout, BoxGeometry.vertexBuffer) .drawIndexed(BoxGeometry.indexCount); +} + +function frame(timestamp: number) { + animationFrameId = requestAnimationFrame(frame); + + const dt = lastTime !== null ? (timestamp - lastTime) / 1000 : 0; + lastTime = timestamp; + time += dt; - animationFrameId = requestAnimationFrame(render); + for (let i = 0; i < orbitingCubes.length; i++) { + const offset = (i / orbitingCubes.length) * Math.PI * 2; + const angle = time * 0.5 + offset; + const radius = 4 + Math.sin(time * 2 + offset * 3) * 0.5; + const x = Math.cos(angle) * radius; + const z = Math.sin(angle) * radius; + const y = 2 + Math.sin(time * 1.5 + offset * 2) * 1.5; + orbitingCubes[i].position = d.vec3f(x, y, z); + orbitingCubes[i].rotation = d.vec3f(time, time * 0.5, 0); + } + + scene.update(); + + render(); } -animationFrameId = requestAnimationFrame(render); + +animationFrameId = requestAnimationFrame(frame); const detachAutoResizer = common.attachAutoResizer({ root, canvas, onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + depthTexture = root .createTexture({ size: [canvas.width, canvas.height], @@ -438,6 +447,8 @@ const detachAutoResizer = common.attachAutoResizer({ sampleCount: 4, }) .$usage('render'); + + render(); }, }); diff --git a/apps/typegpu-docs/src/examples/rendering/pom/index.html b/apps/typegpu-docs/src/examples/rendering/pom/index.html index 581d6789f8..367ab17706 100644 --- a/apps/typegpu-docs/src/examples/rendering/pom/index.html +++ b/apps/typegpu-docs/src/examples/rendering/pom/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/rendering/pom/index.ts b/apps/typegpu-docs/src/examples/rendering/pom/index.ts index d2632fc2a5..3f14ea5b94 100644 --- a/apps/typegpu-docs/src/examples/rendering/pom/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/pom/index.ts @@ -404,8 +404,7 @@ function createDepthTexture() { } createDepthTexture(); -let frameId: number; -function frame() { +function render() { pipeline .withColorAttachment({ view: context, clearValue: [0, 0, 0, 1] }) .withDepthStencilAttachment({ @@ -415,10 +414,24 @@ function frame() { depthStoreOp: 'store', }) .drawIndexed(planeMesh.indexCount); +} + +let frameId: number; +function frame() { + render(); frameId = requestAnimationFrame(frame); } frameId = requestAnimationFrame(frame); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + createDepthTexture(); + render(); + }, +}); + // #region Example controls and cleanup export const controls = defineControls({ @@ -483,14 +496,6 @@ export const controls = defineControls({ }, }); -const detachAutoResizer = common.attachAutoResizer({ - root, - canvas, - onResize() { - createDepthTexture(); - }, -}); - export function onCleanup() { cancelAnimationFrame(frameId); cleanupCamera(); diff --git a/apps/typegpu-docs/src/examples/rendering/radiance-cascades-drawing/drawInteraction.ts b/apps/typegpu-docs/src/examples/rendering/radiance-cascades-drawing/drawInteraction.ts index 732ed5c213..3b42ac0a78 100644 --- a/apps/typegpu-docs/src/examples/rendering/radiance-cascades-drawing/drawInteraction.ts +++ b/apps/typegpu-docs/src/examples/rendering/radiance-cascades-drawing/drawInteraction.ts @@ -42,9 +42,17 @@ export function createDrawInteraction({ canvas, onDraw, onStop }: DrawInteractio function mousePosition(e: MouseEvent): Point { const rect = canvas.getBoundingClientRect(); + + // Taking into account the square aspect ratio + const centerX = rect.x + rect.width / 2; + const centerY = rect.y + rect.height / 2; + const size = Math.min(rect.width, rect.height); + const squareLeft = centerX - size / 2; + const squareTop = centerY - size / 2; + return { - x: (e.clientX - rect.left) / rect.width, - y: (e.clientY - rect.top) / rect.height, + x: (e.clientX - squareLeft) / size, + y: (e.clientY - squareTop) / size, }; } diff --git a/apps/typegpu-docs/src/examples/rendering/radiance-cascades-drawing/index.html b/apps/typegpu-docs/src/examples/rendering/radiance-cascades-drawing/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/rendering/radiance-cascades-drawing/index.html +++ b/apps/typegpu-docs/src/examples/rendering/radiance-cascades-drawing/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/rendering/radiance-cascades-drawing/index.ts b/apps/typegpu-docs/src/examples/rendering/radiance-cascades-drawing/index.ts index dedad17c0b..ba94f90ec7 100644 --- a/apps/typegpu-docs/src/examples/rendering/radiance-cascades-drawing/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/radiance-cascades-drawing/index.ts @@ -4,6 +4,8 @@ import tgpu, { common, d, std } from 'typegpu'; import { defineControls } from '../../common/defineControls.ts'; import { createDrawInteraction } from './drawInteraction.ts'; +const [sceneWidth, sceneHeight] = [1024, 1024]; + const root = await tgpu.init(); const canvas = document.querySelector('canvas') as HTMLCanvasElement; @@ -15,12 +17,21 @@ context.configure({ format: presentationFormat, }); -const [width, height] = [canvas.width, canvas.height]; +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + }, +}); // Scene texture + views. const sceneTexture = root .createTexture({ - size: [width, height], + size: [sceneWidth, sceneHeight], format: 'rgba16float', }) .$usage('storage', 'sampled'); @@ -80,7 +91,7 @@ const drawCompute = root.createGuardedComputePipeline((x, y) => { std.textureStore(sceneWriteView.$, d.vec2u(x, y), out); }); -const floodSize = { width: canvas.width, height: canvas.height }; +const floodSize = { width: sceneWidth, height: sceneHeight }; const floodRunner = sdf .createJumpFlood({ root, @@ -114,7 +125,7 @@ const floodColorView = floodRunner.colorOutput.createView(); const radianceRunner = rc.createRadianceCascades({ root, - size: { width: Math.floor(width / 4), height: Math.floor(height / 4) }, + size: { width: Math.floor(sceneWidth / 4), height: Math.floor(sceneHeight / 4) }, sdfResolution: floodSize, sdf: (uv) => { 'use gpu'; @@ -176,7 +187,7 @@ const displayPipeline = root.createRenderPipeline({ let sceneDirty = false; function drawScene() { - drawCompute.dispatchThreads(width, height); + drawCompute.dispatchThreads(sceneWidth, sceneHeight); sceneDirty = true; } @@ -215,8 +226,6 @@ function frame(timestamp: number) { frameId = requestAnimationFrame(frame); } -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); - // #region Example controls and cleanup export const controls = defineControls({ diff --git a/apps/typegpu-docs/src/examples/rendering/radiance-cascades/index.html b/apps/typegpu-docs/src/examples/rendering/radiance-cascades/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/rendering/radiance-cascades/index.html +++ b/apps/typegpu-docs/src/examples/rendering/radiance-cascades/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/rendering/radiance-cascades/index.ts b/apps/typegpu-docs/src/examples/rendering/radiance-cascades/index.ts index 04e8dcf39f..2b50192000 100644 --- a/apps/typegpu-docs/src/examples/rendering/radiance-cascades/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/radiance-cascades/index.ts @@ -481,6 +481,11 @@ const detachAutoResizer = common.attachAutoResizer({ onResize() { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(recreateSizedResources, 100); + + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; }, }); diff --git a/apps/typegpu-docs/src/examples/rendering/ray-marching/index.html b/apps/typegpu-docs/src/examples/rendering/ray-marching/index.html index 581d6789f8..367ab17706 100644 --- a/apps/typegpu-docs/src/examples/rendering/ray-marching/index.html +++ b/apps/typegpu-docs/src/examples/rendering/ray-marching/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/rendering/render-bundles-with/index.html b/apps/typegpu-docs/src/examples/rendering/render-bundles-with/index.html index 581d6789f8..a1d12c864f 100644 --- a/apps/typegpu-docs/src/examples/rendering/render-bundles-with/index.html +++ b/apps/typegpu-docs/src/examples/rendering/render-bundles-with/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/rendering/render-bundles-with/index.ts b/apps/typegpu-docs/src/examples/rendering/render-bundles-with/index.ts index abe3e16944..19d9cc70dc 100644 --- a/apps/typegpu-docs/src/examples/rendering/render-bundles-with/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/render-bundles-with/index.ts @@ -147,11 +147,7 @@ setCubeCount(INITIAL_CUBE_COUNT); let useBundles = true; -let disposed = false; - -function frame() { - if (disposed) return; - +function render() { if (depthTexture.width !== canvas.width || depthTexture.height !== canvas.height) { depthTexture.destroy(); depthTexture = root.device.createTexture({ @@ -191,13 +187,27 @@ function frame() { pass.end(); root.device.queue.submit([encoder.finish()]); +} + +let disposed = false; + +function frame() { + if (disposed) return; + + render(); requestAnimationFrame(frame); } requestAnimationFrame(frame); -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + render(); + }, +}); // #region Example controls and cleanup diff --git a/apps/typegpu-docs/src/examples/rendering/render-bundles/index.html b/apps/typegpu-docs/src/examples/rendering/render-bundles/index.html index 581d6789f8..367ab17706 100644 --- a/apps/typegpu-docs/src/examples/rendering/render-bundles/index.html +++ b/apps/typegpu-docs/src/examples/rendering/render-bundles/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/rendering/render-bundles/index.ts b/apps/typegpu-docs/src/examples/rendering/render-bundles/index.ts index 1a31f6fea2..f6e5f58cfd 100644 --- a/apps/typegpu-docs/src/examples/rendering/render-bundles/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/render-bundles/index.ts @@ -147,9 +147,7 @@ let useBundles = true; let disposed = false; -function frame() { - if (disposed) return; - +function render() { if (depthTexture.width !== canvas.width || depthTexture.height !== canvas.height) { depthTexture.destroy(); depthTexture = root.device.createTexture({ @@ -191,6 +189,12 @@ function frame() { } } }); +} + +function frame() { + if (disposed) return; + + render(); requestAnimationFrame(frame); } @@ -200,6 +204,9 @@ requestAnimationFrame(frame); const detachAutoResizer = common.attachAutoResizer({ root, canvas, + onResize() { + render(); + }, }); // #region Example controls and cleanup diff --git a/apps/typegpu-docs/src/examples/rendering/simple-shadow/index.html b/apps/typegpu-docs/src/examples/rendering/simple-shadow/index.html index 581d6789f8..a1d12c864f 100644 --- a/apps/typegpu-docs/src/examples/rendering/simple-shadow/index.html +++ b/apps/typegpu-docs/src/examples/rendering/simple-shadow/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/rendering/simple-shadow/index.ts b/apps/typegpu-docs/src/examples/rendering/simple-shadow/index.ts index 82d5bebe47..ab33bd1f87 100644 --- a/apps/typegpu-docs/src/examples/rendering/simple-shadow/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/simple-shadow/index.ts @@ -280,11 +280,7 @@ function updateLightDirection(dir: d.v3f) { }); } -// Render loop -let frameId: number | null = null; function render() { - frameId = requestAnimationFrame(render); - root['~unstable'].beginRenderPass( { colorAttachments: [], @@ -338,7 +334,15 @@ function render() { }, ); } -frameId = requestAnimationFrame(render); + +// Render loop +let frameId: number | null = null; +function frame() { + frameId = requestAnimationFrame(frame); + + render(); +} +frameId = requestAnimationFrame(frame); const detachAutoResizer = common.attachAutoResizer({ root, @@ -356,6 +360,8 @@ const detachAutoResizer = common.attachAutoResizer({ cameraUniform.patch({ projection: newProjection, }); + + render(); }, }); diff --git a/apps/typegpu-docs/src/examples/rendering/smoky-triangle/index.html b/apps/typegpu-docs/src/examples/rendering/smoky-triangle/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/rendering/smoky-triangle/index.html +++ b/apps/typegpu-docs/src/examples/rendering/smoky-triangle/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/rendering/smoky-triangle/index.ts b/apps/typegpu-docs/src/examples/rendering/smoky-triangle/index.ts index 5e085f56d3..575b76306e 100644 --- a/apps/typegpu-docs/src/examples/rendering/smoky-triangle/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/smoky-triangle/index.ts @@ -80,6 +80,10 @@ const pipeline = root.pipe(perlinCache.inject()).createRenderPipeline({ const canvas = document.querySelector('canvas') as HTMLCanvasElement; const context = root.configureContext({ canvas, alphaMode: 'premultiplied' }); +function render() { + pipeline.withColorAttachment({ view: context }).draw(3); +} + let frameId: number; function frame(timestamp: number) { paramsUniform.patch({ @@ -87,13 +91,23 @@ function frame(timestamp: number) { grainSeed: Math.floor(Math.random() * 100), }); - pipeline.withColorAttachment({ view: context }).draw(3); + render(); frameId = requestAnimationFrame(frame); } frameId = requestAnimationFrame(frame); -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + render(); + }, +}); export const controls = { Distortion: { diff --git a/apps/typegpu-docs/src/examples/rendering/suika-sdf/index.html b/apps/typegpu-docs/src/examples/rendering/suika-sdf/index.html index d50df83d4b..3eda9a7c1e 100644 --- a/apps/typegpu-docs/src/examples/rendering/suika-sdf/index.html +++ b/apps/typegpu-docs/src/examples/rendering/suika-sdf/index.html @@ -1,4 +1,4 @@ - + diff --git a/apps/typegpu-docs/src/examples/rendering/suika-sdf/index.ts b/apps/typegpu-docs/src/examples/rendering/suika-sdf/index.ts index 37fce94715..2cfc90f4c3 100644 --- a/apps/typegpu-docs/src/examples/rendering/suika-sdf/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/suika-sdf/index.ts @@ -427,6 +427,8 @@ const detachAutoResizer = common.attachAutoResizer({ distance: distanceView, info: infoView, }); + + render(); }, }); @@ -531,6 +533,24 @@ function restart() { circleUniform.write(circleData); } +function render() { + frameUniform.patch({ + canvasAspect: canvas.width / canvas.height, + }); + + mergedFieldPipeline + .withColorAttachment({ + distance: { view: distanceView }, + info: { view: infoView }, + }) + .draw(3); + + renderPipeline + .with(mergedFieldBindGroup) + .withColorAttachment({ view: context, clearValue: { r: 0, g: 0, b: 0, a: 1 } }) + .draw(3); +} + let lastTime = 0; let animationFrameId = 0; @@ -645,18 +665,7 @@ function frame(now: number) { }, }); - mergedFieldPipeline - .withColorAttachment({ - distance: { view: distanceView }, - info: { view: infoView }, - }) - .draw(3); - - renderPipeline - .with(mergedFieldBindGroup) - .withColorAttachment({ view: context, clearValue: { r: 0, g: 0, b: 0, a: 1 } }) - .draw(3); - + render(); animationFrameId = requestAnimationFrame(frame); } animationFrameId = requestAnimationFrame(frame); diff --git a/apps/typegpu-docs/src/examples/rendering/two-boxes/index.html b/apps/typegpu-docs/src/examples/rendering/two-boxes/index.html index 9209e7c2fd..14e7a57baf 100644 --- a/apps/typegpu-docs/src/examples/rendering/two-boxes/index.html +++ b/apps/typegpu-docs/src/examples/rendering/two-boxes/index.html @@ -27,4 +27,4 @@

Controls

Right Mouse Button: Rotate cubes

Scroll / Two-Finger Drag: Zoom

- + diff --git a/apps/typegpu-docs/src/examples/rendering/two-boxes/index.ts b/apps/typegpu-docs/src/examples/rendering/two-boxes/index.ts index 11f52c24ee..0a336307a4 100644 --- a/apps/typegpu-docs/src/examples/rendering/two-boxes/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/two-boxes/index.ts @@ -30,7 +30,7 @@ const vertexLayout = tgpu.vertexLayout(d.arrayOf(Vertex)); // Scene Setup -const aspect = canvas.clientWidth / canvas.clientHeight; +const aspect = 1; const target = d.vec3f(0, 0, 0); const cameraInitialPos = d.vec4f(12, 5, 12, 1); @@ -460,12 +460,18 @@ canvas.addEventListener( { passive: false }, ); -const resizeObserver = new ResizeObserver(() => { - createDepthAndMsaaTextures(); -}); -resizeObserver.observe(canvas); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + createDepthAndMsaaTextures(); + }, +}); export function onCleanup() { cancelAnimationFrame(animationFrameId); @@ -473,7 +479,6 @@ export function onCleanup() { window.removeEventListener('mousemove', mouseMoveEventListener); window.removeEventListener('touchend', touchEndEventListener); window.removeEventListener('touchmove', touchMoveEventListener); - resizeObserver.disconnect(); detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/rendering/xor-dev-centrifuge-2/index.html b/apps/typegpu-docs/src/examples/rendering/xor-dev-centrifuge-2/index.html index 1edc7df14f..57ee4dca06 100644 --- a/apps/typegpu-docs/src/examples/rendering/xor-dev-centrifuge-2/index.html +++ b/apps/typegpu-docs/src/examples/rendering/xor-dev-centrifuge-2/index.html @@ -1,4 +1,4 @@ - +

Port of "Centrifuge 2" by XorDev ( +

Port of "Runner" by XorDev ( + diff --git a/apps/typegpu-docs/src/examples/simple/gradient-tiles/index.ts b/apps/typegpu-docs/src/examples/simple/gradient-tiles/index.ts index 8063b03bea..904f7b3714 100644 --- a/apps/typegpu-docs/src/examples/simple/gradient-tiles/index.ts +++ b/apps/typegpu-docs/src/examples/simple/gradient-tiles/index.ts @@ -37,6 +37,10 @@ const detachAutoResizer = common.attachAutoResizer({ root, canvas, onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; draw(spanX, spanY); }, }); diff --git a/apps/typegpu-docs/src/examples/simple/liquid-glass/index.html b/apps/typegpu-docs/src/examples/simple/liquid-glass/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/simple/liquid-glass/index.html +++ b/apps/typegpu-docs/src/examples/simple/liquid-glass/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts b/apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts index c5e0a8ee27..debb0946ba 100644 --- a/apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts +++ b/apps/typegpu-docs/src/examples/simple/liquid-glass/index.ts @@ -180,14 +180,28 @@ canvas.addEventListener('touchmove', handleTouchMove, { passive: true }); canvas.addEventListener('touchstart', handleTouchStart, { passive: true }); canvas.addEventListener('click', handleClick); -let frameId: number; function render() { - frameId = requestAnimationFrame(render); pipeline.withColorAttachment({ view: context }).draw(3); } -frameId = requestAnimationFrame(render); -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); +let frameId: number; +function frame() { + frameId = requestAnimationFrame(frame); + render(); +} +frameId = requestAnimationFrame(frame); + +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + render(); + }, +}); export const controls = defineControls({ 'Rectangle dims': { diff --git a/apps/typegpu-docs/src/examples/simple/mesh-skinning/index.html b/apps/typegpu-docs/src/examples/simple/mesh-skinning/index.html index e14a9c47e5..dc3b398f45 100644 --- a/apps/typegpu-docs/src/examples/simple/mesh-skinning/index.html +++ b/apps/typegpu-docs/src/examples/simple/mesh-skinning/index.html @@ -1,4 +1,4 @@ - + - -

+
+ +
+
diff --git a/apps/typegpu-docs/src/examples/simple/oklab/index.ts b/apps/typegpu-docs/src/examples/simple/oklab/index.ts index 4bb3a9798d..47db43b049 100644 --- a/apps/typegpu-docs/src/examples/simple/oklab/index.ts +++ b/apps/typegpu-docs/src/examples/simple/oklab/index.ts @@ -27,8 +27,15 @@ document.addEventListener( 'mousemove', (ev) => { const rect = canvas.getBoundingClientRect(); - cssProbePosition.x = (ev.clientX - rect.x) / rect.width; - cssProbePosition.y = 1 - (ev.clientY - rect.y) / rect.height; + + const centerX = rect.x + rect.width / 2; + const centerY = rect.y + rect.height / 2; + const size = Math.min(rect.width, rect.height); + const squareLeft = centerX - size / 2; + const squareTop = centerY - size / 2; + + cssProbePosition.x = std.saturate((ev.clientX - squareLeft) / size); + cssProbePosition.y = std.saturate(1 - (ev.clientY - squareTop) / size); draw(); }, { @@ -129,8 +136,16 @@ function draw() { const chroma = pos.x; const a = chroma * Math.cos(uniformsValue.hue); const b = chroma * Math.sin(uniformsValue.hue); - cssProbe.style.setProperty('--x', `${cssProbePosition.x * 100}%`); - cssProbe.style.setProperty('--y', `${cssProbePosition.y * 100}%`); + + const rect = canvas.getBoundingClientRect(); + const centerX = rect.x + rect.width / 2; + const centerY = rect.y + rect.height / 2; + const size = Math.min(rect.width, rect.height); + const squareLeft = centerX - size / 2; + const squareTop = centerY - size / 2; + + cssProbe.style.setProperty('--x', `${squareLeft - rect.left + cssProbePosition.x * size}px`); + cssProbe.style.setProperty('--y', `${squareTop - rect.top + cssProbePosition.y * size}px`); canvas.parentElement?.style.setProperty('--l', `${lightness}`); canvas.parentElement?.style.setProperty('--a', `${a}`); canvas.parentElement?.style.setProperty('--b', `${b}`); @@ -147,6 +162,10 @@ const detachAutoResizer = common.attachAutoResizer({ root, canvas, onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; draw(); }, }); diff --git a/apps/typegpu-docs/src/examples/simple/ripple-cube/index.html b/apps/typegpu-docs/src/examples/simple/ripple-cube/index.html index aa8cc321b3..367ab17706 100644 --- a/apps/typegpu-docs/src/examples/simple/ripple-cube/index.html +++ b/apps/typegpu-docs/src/examples/simple/ripple-cube/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/simple/ripple-cube/index.ts b/apps/typegpu-docs/src/examples/simple/ripple-cube/index.ts index 198e228bba..33a67f2c09 100644 --- a/apps/typegpu-docs/src/examples/simple/ripple-cube/index.ts +++ b/apps/typegpu-docs/src/examples/simple/ripple-cube/index.ts @@ -19,7 +19,7 @@ const perlinCache = perlin3d.staticCache({ size: d.vec3u(32, 32, 32), }); -const [width, height] = [canvas.width / 1.4, canvas.height / 1.4]; +const resolutionScale = 1 / 1.4; const cameraUniform = root.createUniform(Camera); const timeUniform = root.createUniform(d.f32); @@ -112,7 +112,12 @@ const envMapBindGroup = root.createBindGroup(envMapLayout, { }), }); -const postProcessing = createPostProcessingPipelines(root, width, height, initialBloom); +const postProcessing = createPostProcessingPipelines( + root, + canvas.width * resolutionScale, + canvas.height * resolutionScale, + initialBloom, +); const cameraResult = setupOrbitCamera( canvas, @@ -138,7 +143,7 @@ const rayMarchPipeline = root .createGuardedComputePipeline((x, y) => { 'use gpu'; randf.seed2(d.vec2f(d.f32(x), d.f32(y)).add(timeUniform.$)); - const textureSize = std.textureDimensions(postProcessing.result.writeView.$); + const textureSize = std.textureDimensions(postProcessing.$.result); const uv = d.vec2f(x, y).add(0.5).div(d.vec2f(textureSize)); const ray = getRayForUV(uv); @@ -186,7 +191,7 @@ const rayMarchPipeline = root finalColor = std.mix(fogColor, sceneColor, fog); } - std.textureStore(postProcessing.result.writeView.$, d.vec2u(x, y), d.vec4f(finalColor, 1)); + std.textureStore(postProcessing.$.result, d.vec2u(x, y), d.vec4f(finalColor, 1)); }); let animationFrame: number; @@ -196,34 +201,47 @@ let accumulatedTime = 0; let lastTimestamp = 0; let isExtendedRipplesEnabled = false; -function run(timestamp: number) { - const deltaTime = lastTimestamp === 0 ? 0 : (timestamp - lastTimestamp) / 1000; - lastTimestamp = timestamp; - const maxTime = isExtendedRipplesEnabled ? 62 : 28; - accumulatedTime = Math.min(maxTime, Math.max(0, accumulatedTime + deltaTime * timeScale)); - timeUniform.write(accumulatedTime); - - const jitterX = (halton(frameIndex % 16, 2) - 0.5) / width; - const jitterY = (halton(frameIndex % 16, 3) - 0.5) / height; +function render() { + const jitterX = (halton(frameIndex % 16, 2) - 0.5) / postProcessing.width; + const jitterY = (halton(frameIndex % 16, 3) - 0.5) / postProcessing.height; jitterUniform.write(d.vec2f(jitterX, jitterY)); frameIndex++; sdfPrecalcPipeline.dispatchThreads(GRID_SIZE / 2, GRID_SIZE / 2, GRID_SIZE / 2); - rayMarchPipeline.with(envMapBindGroup).with(sdfBindGroup).dispatchThreads(width, height); + rayMarchPipeline + .with(envMapBindGroup) + .with(sdfBindGroup) + .with(postProcessing.userGroup) + .dispatchThreads(postProcessing.width, postProcessing.height); postProcessing.runTaa(); - postProcessing.runBloom(); - postProcessing.render(context); +} + +function run(timestamp: number) { + const deltaTime = lastTimestamp === 0 ? 0 : (timestamp - lastTimestamp) / 1000; + lastTimestamp = timestamp; + const maxTime = isExtendedRipplesEnabled ? 62 : 28; + accumulatedTime = Math.min(maxTime, Math.max(0, accumulatedTime + deltaTime * timeScale)); + timeUniform.write(accumulatedTime); + + render(); animationFrame = requestAnimationFrame(run); } animationFrame = requestAnimationFrame(run); -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + postProcessing.resize(canvas.width * resolutionScale, canvas.height * resolutionScale); + render(); + }, +}); export const controls = defineControls({ metallic: { diff --git a/apps/typegpu-docs/src/examples/simple/ripple-cube/post-processing.ts b/apps/typegpu-docs/src/examples/simple/ripple-cube/post-processing.ts index 063c9af504..db231ee994 100644 --- a/apps/typegpu-docs/src/examples/simple/ripple-cube/post-processing.ts +++ b/apps/typegpu-docs/src/examples/simple/ripple-cube/post-processing.ts @@ -6,6 +6,10 @@ import { BloomParams } from './types.ts'; export const bloomParamsAccess = tgpu.accessor(BloomParams); +const userLayout = tgpu.bindGroupLayout({ + resultTexture: { storageTexture: d.textureStorage2d('rgba16float') }, +}); + const taaResolveLayout = tgpu.bindGroupLayout({ currentTexture: { texture: d.texture2d() }, historyTexture: { texture: d.texture2d() }, @@ -41,19 +45,11 @@ function createProcessingTexture(root: TgpuRoot, width: number, height: number) export function createPostProcessingPipelines( root: TgpuRoot, - width: number, - height: number, + initialWidth: number, + initialHeight: number, initialBloom: d.Infer, ) { const bloomUniform = root.createUniform(BloomParams, initialBloom); - const bloomWidth = Math.max(1, Math.floor(width / 2)); - const bloomHeight = Math.max(1, Math.floor(height / 2)); - - const result = createProcessingTexture(root, width, height); - const bloom = createProcessingTexture(root, bloomWidth, bloomHeight); - const blurTemp = createProcessingTexture(root, bloomWidth, bloomHeight); - const history = createProcessingTexture(root, width, height); - const taaOutput = createProcessingTexture(root, width, height); const sampler = root.createSampler({ magFilter: 'linear', @@ -63,6 +59,7 @@ export function createPostProcessingPipelines( const taaResolve = root.createGuardedComputePipeline((x, y) => { 'use gpu'; const coord = d.vec2i(d.i32(x), d.i32(y)); + const dims = std.textureDimensions(taaResolveLayout.$.currentTexture); const current = std.textureLoad(taaResolveLayout.$.currentTexture, coord, 0); const historyColor = std.textureLoad(taaResolveLayout.$.historyTexture, coord, 0); @@ -75,7 +72,7 @@ export function createPostProcessingPipelines( const clampedCoord = std.clamp( sampleCoord, d.vec2i(0, 0), - d.vec2i(d.i32(width) - 1, d.i32(height) - 1), + d.vec2i(d.i32(dims.x) - 1, d.i32(dims.y) - 1), ); const neighbor = std.textureLoad(taaResolveLayout.$.currentTexture, clampedCoord, 0).rgb; minColor = std.min(minColor, neighbor); @@ -144,56 +141,109 @@ export function createPostProcessingPipelines( fragment: fragmentMain, }); - const taaBindGroup = root.createBindGroup(taaResolveLayout, { - currentTexture: result.sampleView, - historyTexture: history.sampleView, - outputTexture: taaOutput.writeView, - }); - - const copyBindGroup = root.createBindGroup(processLayout, { - inputTexture: taaOutput.sampleView, - outputTexture: history.writeView, - sampler, - }); - - const extractBindGroup = root.createBindGroup(processLayout, { - inputTexture: result.sampleView, - outputTexture: bloom.writeView, - sampler, - }); - - const blurHorizontalBindGroup = root.createBindGroup(processLayout, { - inputTexture: bloom.sampleView, - outputTexture: blurTemp.writeView, - sampler, - }); - - const blurVerticalBindGroup = root.createBindGroup(processLayout, { - inputTexture: blurTemp.sampleView, - outputTexture: bloom.writeView, - sampler, - }); - - const compositeBindGroup = root.createBindGroup(compositeLayout, { - colorTexture: taaOutput.sampleView, - bloomTexture: bloom.sampleView, - sampler, - }); + let width = initialWidth; + let height = initialHeight; + function createResolutionDependantResources() { + const bloomWidth = Math.max(1, Math.floor(width / 2)); + const bloomHeight = Math.max(1, Math.floor(height / 2)); + + const result = createProcessingTexture(root, width, height); + const bloom = createProcessingTexture(root, bloomWidth, bloomHeight); + const blurTemp = createProcessingTexture(root, bloomWidth, bloomHeight); + const history = createProcessingTexture(root, width, height); + const taaOutput = createProcessingTexture(root, width, height); + + return { + bloomWidth, + bloomHeight, + + taaBindGroup: root.createBindGroup(taaResolveLayout, { + currentTexture: result.sampleView, + historyTexture: history.sampleView, + outputTexture: taaOutput.writeView, + }), + + copyBindGroup: root.createBindGroup(processLayout, { + inputTexture: taaOutput.sampleView, + outputTexture: history.writeView, + sampler, + }), + + extractBindGroup: root.createBindGroup(processLayout, { + inputTexture: result.sampleView, + outputTexture: bloom.writeView, + sampler, + }), + + blurHorizontalBindGroup: root.createBindGroup(processLayout, { + inputTexture: bloom.sampleView, + outputTexture: blurTemp.writeView, + sampler, + }), + + blurVerticalBindGroup: root.createBindGroup(processLayout, { + inputTexture: blurTemp.sampleView, + outputTexture: bloom.writeView, + sampler, + }), + + compositeBindGroup: root.createBindGroup(compositeLayout, { + colorTexture: taaOutput.sampleView, + bloomTexture: bloom.sampleView, + sampler, + }), + + userBindGroup: root.createBindGroup(userLayout, { + resultTexture: result.writeView, + }), + }; + } + + let resources = createResolutionDependantResources(); return { - result, bloomUniform, + get $() { + return { + result: userLayout.$.resultTexture, + }; + }, + get userGroup() { + return resources.userBindGroup; + }, + get width() { + return width; + }, + get height() { + return height; + }, + resize: (newWidth: number, newHeight: number) => { + if (newWidth !== width || newHeight !== height) { + width = newWidth; + height = newHeight; + resources = createResolutionDependantResources(); + } + }, runTaa: () => { - taaResolve.with(taaBindGroup).dispatchThreads(width, height); - copyToHistory.with(copyBindGroup).dispatchThreads(width, height); + taaResolve.with(resources.taaBindGroup).dispatchThreads(width, height); + copyToHistory.with(resources.copyBindGroup).dispatchThreads(width, height); }, runBloom: () => { - extractBright.with(extractBindGroup).dispatchThreads(bloomWidth, bloomHeight); - blurHorizontal.with(blurHorizontalBindGroup).dispatchThreads(bloomWidth, bloomHeight); - blurVertical.with(blurVerticalBindGroup).dispatchThreads(bloomWidth, bloomHeight); + extractBright + .with(resources.extractBindGroup) + .dispatchThreads(resources.bloomWidth, resources.bloomHeight); + blurHorizontal + .with(resources.blurHorizontalBindGroup) + .dispatchThreads(resources.bloomWidth, resources.bloomHeight); + blurVertical + .with(resources.blurVerticalBindGroup) + .dispatchThreads(resources.bloomWidth, resources.bloomHeight); }, render: (targetView: ColorAttachment['view']) => { - renderPipeline.with(compositeBindGroup).withColorAttachment({ view: targetView }).draw(3); + renderPipeline + .with(resources.compositeBindGroup) + .withColorAttachment({ view: targetView }) + .draw(3); }, }; } diff --git a/apps/typegpu-docs/src/examples/simple/square/index.html b/apps/typegpu-docs/src/examples/simple/square/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/simple/square/index.html +++ b/apps/typegpu-docs/src/examples/simple/square/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/simple/square/index.ts b/apps/typegpu-docs/src/examples/simple/square/index.ts index fb4b115495..cb4d49a2f6 100644 --- a/apps/typegpu-docs/src/examples/simple/square/index.ts +++ b/apps/typegpu-docs/src/examples/simple/square/index.ts @@ -66,6 +66,10 @@ const detachAutoResizer = common.attachAutoResizer({ root, canvas, onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; render(); }, }); diff --git a/apps/typegpu-docs/src/examples/simple/stencil/index.html b/apps/typegpu-docs/src/examples/simple/stencil/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/simple/stencil/index.html +++ b/apps/typegpu-docs/src/examples/simple/stencil/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/simple/stencil/index.ts b/apps/typegpu-docs/src/examples/simple/stencil/index.ts index b37b88251d..bd056b128a 100644 --- a/apps/typegpu-docs/src/examples/simple/stencil/index.ts +++ b/apps/typegpu-docs/src/examples/simple/stencil/index.ts @@ -103,28 +103,34 @@ function frame(timestamp: number) { } frameId = requestAnimationFrame(frame); -const resizeObserver = new ResizeObserver(() => { - stencilTexture = root - .createTexture({ - size: [canvas.width, canvas.height], - format: 'stencil8', - }) - .$usage('render'); - - rotationUniform.write(d.mat2x2f.identity()); - - writeStencilPipeline - .withDepthStencilAttachment({ - view: stencilTexture, - stencilClearValue: 0, - stencilLoadOp: 'clear', - stencilStoreOp: 'store', - }) - .draw(3); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + + stencilTexture = root + .createTexture({ + size: [canvas.width, canvas.height], + format: 'stencil8', + }) + .$usage('render'); + + rotationUniform.write(d.mat2x2f.identity()); + + writeStencilPipeline + .withDepthStencilAttachment({ + view: stencilTexture, + stencilClearValue: 0, + stencilLoadOp: 'clear', + stencilStoreOp: 'store', + }) + .draw(3); + }, }); -resizeObserver.observe(canvas); - -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); export function onCleanup() { if (frameId) { diff --git a/apps/typegpu-docs/src/examples/simple/triangle/index.html b/apps/typegpu-docs/src/examples/simple/triangle/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/simple/triangle/index.html +++ b/apps/typegpu-docs/src/examples/simple/triangle/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/simple/triangle/index.ts b/apps/typegpu-docs/src/examples/simple/triangle/index.ts index 9d98ea8c1d..8ac4c836ae 100644 --- a/apps/typegpu-docs/src/examples/simple/triangle/index.ts +++ b/apps/typegpu-docs/src/examples/simple/triangle/index.ts @@ -51,6 +51,11 @@ const detachAutoResizer = common.attachAutoResizer({ root, canvas, onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + // Drawing once, and then each time the canvas resizes pipeline.withColorAttachment({ view: context }).draw(3); }, diff --git a/apps/typegpu-docs/src/examples/simple/vaporrave/index.html b/apps/typegpu-docs/src/examples/simple/vaporrave/index.html index 581d6789f8..367ab17706 100644 --- a/apps/typegpu-docs/src/examples/simple/vaporrave/index.html +++ b/apps/typegpu-docs/src/examples/simple/vaporrave/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/simple/vaporrave/index.ts b/apps/typegpu-docs/src/examples/simple/vaporrave/index.ts index b76ab18446..05357d2192 100644 --- a/apps/typegpu-docs/src/examples/simple/vaporrave/index.ts +++ b/apps/typegpu-docs/src/examples/simple/vaporrave/index.ts @@ -132,6 +132,14 @@ let renderPipeline = root fragment: fragmentMain, }); +function render() { + floorAngleUniform.write(floorAngle); + sphereAngleUniform.write(sphereAngle); + resolutionUniform.write(d.vec2f(canvas.width, canvas.height)); + + renderPipeline.withColorAttachment({ view: context }).draw(3); +} + let animationFrame: number; let floorAngle = 0; let sphereAngle = 0; @@ -146,18 +154,20 @@ function run(timestamp: number) { sphereAngle += delta * sphereSpeed; sphereAngle %= c.NUM_CYCLES * Math.PI * 2; - floorAngleUniform.write(floorAngle); - sphereAngleUniform.write(sphereAngle); - resolutionUniform.write(d.vec2f(canvas.width, canvas.height)); - - renderPipeline.withColorAttachment({ view: context }).draw(3); + render(); animationFrame = requestAnimationFrame(run); } animationFrame = requestAnimationFrame(run); -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + render(); + }, +}); // #region Example controls and cleanup diff --git a/apps/typegpu-docs/src/examples/simulation/boids/index.html b/apps/typegpu-docs/src/examples/simulation/boids/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/simulation/boids/index.html +++ b/apps/typegpu-docs/src/examples/simulation/boids/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/simulation/boids/index.ts b/apps/typegpu-docs/src/examples/simulation/boids/index.ts index 6be2f42814..d062ebff5e 100644 --- a/apps/typegpu-docs/src/examples/simulation/boids/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/boids/index.ts @@ -222,13 +222,8 @@ const computeBindGroups = [0, 1].map((idx) => ); let even = false; -let animationFrameId: number; - -function frame() { - even = !even; - - simulatePipeline.with(computeBindGroups[even ? 0 : 1]).dispatchThreads(triangleAmount); +function render() { renderPipeline .withColorAttachment({ view: context, @@ -236,13 +231,33 @@ function frame() { }) .with(instanceLayout, trianglePosBuffers[even ? 1 : 0]) .draw(3, triangleAmount); +} + +let animationFrameId: number; + +function frame() { + even = !even; + + simulatePipeline.with(computeBindGroups[even ? 0 : 1]).dispatchThreads(triangleAmount); + + render(); animationFrameId = requestAnimationFrame(frame); } animationFrameId = requestAnimationFrame(frame); -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + render(); + }, +}); // #region Example controls and cleanup diff --git a/apps/typegpu-docs/src/examples/simulation/confetti/index.html b/apps/typegpu-docs/src/examples/simulation/confetti/index.html index 581d6789f8..a1d12c864f 100644 --- a/apps/typegpu-docs/src/examples/simulation/confetti/index.html +++ b/apps/typegpu-docs/src/examples/simulation/confetti/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.html b/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.html +++ b/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts b/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts index 43d62216b7..b3abdc375f 100644 --- a/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts @@ -537,7 +537,17 @@ const runner = (timestamp: number) => { animationFrameId = requestAnimationFrame(runner); -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + primary.render(); + }, +}); export const controls = defineControls({ 'source intensity': { diff --git a/apps/typegpu-docs/src/examples/simulation/fluid-with-atomics/index.html b/apps/typegpu-docs/src/examples/simulation/fluid-with-atomics/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/simulation/fluid-with-atomics/index.html +++ b/apps/typegpu-docs/src/examples/simulation/fluid-with-atomics/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/simulation/fluid-with-atomics/index.ts b/apps/typegpu-docs/src/examples/simulation/fluid-with-atomics/index.ts index 0e3b3f5733..5c8e836554 100644 --- a/apps/typegpu-docs/src/examples/simulation/fluid-with-atomics/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/fluid-with-atomics/index.ts @@ -625,7 +625,17 @@ function run(timestamp: number) { } animationFrame = requestAnimationFrame(run); -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + drawFrame(); + }, +}); export const controls = defineControls({ size: { diff --git a/apps/typegpu-docs/src/examples/simulation/game-of-life/index.html b/apps/typegpu-docs/src/examples/simulation/game-of-life/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/simulation/game-of-life/index.html +++ b/apps/typegpu-docs/src/examples/simulation/game-of-life/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/simulation/game-of-life/index.ts b/apps/typegpu-docs/src/examples/simulation/game-of-life/index.ts index 90ad1dd5bb..530859db52 100644 --- a/apps/typegpu-docs/src/examples/simulation/game-of-life/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/game-of-life/index.ts @@ -345,6 +345,16 @@ const stepOnce = (timestamp: number) => { ); }; +function render() { + const chosenDisplayPipeline = + chosenPipeline === 'bitpacked' ? bitpackedDisplayPipeline : displayPipeline; + + chosenDisplayPipeline + .withColorAttachment({ view: context }) + .with(displayBindGroups[1 - even]) + .draw(3); +} + let frameId: number; function frame(timestamp: number) { @@ -390,19 +400,23 @@ function frame(timestamp: number) { } } - const chosenDisplayPipeline = - chosenPipeline === 'bitpacked' ? bitpackedDisplayPipeline : displayPipeline; - - chosenDisplayPipeline - .withColorAttachment({ view: context }) - .with(displayBindGroups[1 - even]) - .draw(3); + render(); frameId = requestAnimationFrame(frame); } frameId = requestAnimationFrame(frame); -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + render(); + }, +}); // #region Example controls & Cleanup diff --git a/apps/typegpu-docs/src/examples/simulation/game-of-life/input.ts b/apps/typegpu-docs/src/examples/simulation/game-of-life/input.ts index bc803b4395..a7d92b175f 100644 --- a/apps/typegpu-docs/src/examples/simulation/game-of-life/input.ts +++ b/apps/typegpu-docs/src/examples/simulation/game-of-life/input.ts @@ -1,3 +1,5 @@ +import { std } from 'typegpu'; + export interface InputState { drawPos: { x: number; y: number } | null; lastDrawPos: { x: number; y: number } | null; @@ -23,10 +25,17 @@ export function setupInput(canvas: HTMLCanvasElement): InputState { y: (sy - 0.5) / state.zoomLevel + state.zoomCenter.y, }); const toScreen = (clientX: number, clientY: number) => { - const r = canvas.getBoundingClientRect(); + const rect = canvas.getBoundingClientRect(); + + const centerX = rect.x + rect.width / 2; + const centerY = rect.y + rect.height / 2; + const size = Math.min(rect.width, rect.height); + const squareLeft = centerX - size / 2; + const squareTop = centerY - size / 2; + return { - x: Math.min(1, Math.max(0, (clientX - r.left) / r.width)), - y: Math.min(1, Math.max(0, (clientY - r.top) / r.height)), + x: std.saturate((clientX - squareLeft) / size), + y: std.saturate((clientY - squareTop) / size), }; }; diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/index.html b/apps/typegpu-docs/src/examples/simulation/gravity/index.html index c83e0d8622..9f7e2b8386 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/index.html +++ b/apps/typegpu-docs/src/examples/simulation/gravity/index.html @@ -38,4 +38,4 @@

Controls (click to dismiss)

under CC BY 4.0

- + diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/index.ts b/apps/typegpu-docs/src/examples/simulation/gravity/index.ts index 4e4b57a18f..99dadb6123 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/index.ts @@ -250,17 +250,18 @@ export const controls = defineControls({ }, }); -const resizeObserver = new ResizeObserver(() => { - depthTexture.destroy(); - depthTexture = root.device.createTexture({ - size: [canvas.width, canvas.height, 1], - format: 'depth24plus', - usage: GPUTextureUsage.RENDER_ATTACHMENT, - }); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + depthTexture.destroy(); + depthTexture = root.device.createTexture({ + size: [canvas.width, canvas.height, 1], + format: 'depth24plus', + usage: GPUTextureUsage.RENDER_ATTACHMENT, + }); + }, }); -resizeObserver.observe(canvas); - -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); function hideHelp() { const helpElem = document.getElementById('help'); @@ -275,7 +276,6 @@ for (const eventName of ['click', 'keydown', 'wheel', 'touchstart']) { export function onCleanup() { destroyed = true; cleanupCamera(); - resizeObserver.unobserve(canvas); detachAutoResizer(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.html b/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.html +++ b/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts b/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts index 746a3ef6c9..9208882fc7 100644 --- a/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/slime-mold-3d/index.ts @@ -43,7 +43,7 @@ const Camera = d.struct({ const cameraTarget = resolution.div(2); const cameraUp = d.vec3f(0, 1, 0); const fov = (CAMERA_FOV_DEGREES * Math.PI) / 180; -const aspect = canvas.width / canvas.height; +const aspect = 1; const near = 0.1; const far = 1000.0; @@ -439,9 +439,16 @@ const renderBindGroups = [0, 1].map((i) => }), ); -let lastTime: number | null = null; let currentTexture = 0; +function render() { + renderPipeline + .withColorAttachment({ view: context }) + .with(renderBindGroups[1 - currentTexture]) + .draw(3); +} + +let lastTime: number | null = null; function frame(timestamp: number) { const deltaTime = Math.min(lastTime !== null ? (timestamp - lastTime) / 1000 : 0, 0.1); lastTime = timestamp; @@ -460,10 +467,7 @@ function frame(timestamp: number) { .with(bindGroups[currentTexture]) .dispatchWorkgroups(Math.ceil(NUM_AGENTS / AGENT_WORKGROUP_SIZE)); - renderPipeline - .withColorAttachment({ view: context }) - .with(renderBindGroups[1 - currentTexture]) - .draw(3); + render(); currentTexture = 1 - currentTexture; @@ -471,7 +475,17 @@ function frame(timestamp: number) { } requestAnimationFrame(frame); -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + render(); + }, +}); // #region Example controls and cleanup diff --git a/apps/typegpu-docs/src/examples/simulation/slime-mold/index.html b/apps/typegpu-docs/src/examples/simulation/slime-mold/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/simulation/slime-mold/index.html +++ b/apps/typegpu-docs/src/examples/simulation/slime-mold/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/simulation/slime-mold/index.ts b/apps/typegpu-docs/src/examples/simulation/slime-mold/index.ts index 1b9dac673d..6b504c8482 100644 --- a/apps/typegpu-docs/src/examples/simulation/slime-mold/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/slime-mold/index.ts @@ -7,7 +7,7 @@ const canvas = document.querySelector('canvas') as HTMLCanvasElement; const context = root.configureContext({ canvas, alphaMode: 'premultiplied' }); const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); -const resolution = d.vec2f(canvas.width, canvas.height); +const resolution = d.vec2f(1024, 1024); const Agent = d.struct({ position: d.vec2f, @@ -217,9 +217,16 @@ const renderBindGroups = [0, 1].map((i) => }), ); -let lastTime: number | null = null; let currentTexture = 0; +function render() { + renderPipeline + .withColorAttachment({ view: context }) + .with(renderBindGroups[1 - currentTexture]) + .draw(3); +} + +let lastTime: number | null = null; function frame(now: number) { const deltaTimeValue = Math.min(lastTime !== null ? (now - lastTime) / 1000 : 0, 0.1); lastTime = now; @@ -232,10 +239,7 @@ function frame(now: number) { computePipeline.with(bindGroups[currentTexture]).dispatchWorkgroups(Math.ceil(NUM_AGENTS / 64)); - renderPipeline - .withColorAttachment({ view: context }) - .with(renderBindGroups[1 - currentTexture]) - .draw(3); + render(); currentTexture = 1 - currentTexture; @@ -243,7 +247,17 @@ function frame(now: number) { } requestAnimationFrame(frame); -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + render(); + }, +}); // #region Example controls and cleanup diff --git a/apps/typegpu-docs/src/examples/simulation/stable-fluid/index.html b/apps/typegpu-docs/src/examples/simulation/stable-fluid/index.html index de6d9fbb40..9670960a81 100644 --- a/apps/typegpu-docs/src/examples/simulation/stable-fluid/index.html +++ b/apps/typegpu-docs/src/examples/simulation/stable-fluid/index.html @@ -27,4 +27,4 @@

Controls (click to dismiss)

Drag to stir the fluid.

- + diff --git a/apps/typegpu-docs/src/examples/simulation/stable-fluid/index.ts b/apps/typegpu-docs/src/examples/simulation/stable-fluid/index.ts index 679013a5f9..57001f3409 100644 --- a/apps/typegpu-docs/src/examples/simulation/stable-fluid/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/stable-fluid/index.ts @@ -32,9 +32,18 @@ function createComputePipeline(fn: TgpuComputeFn) { return root.createComputePipeline({ compute: fn }); } -function toGrid(x: number, y: number): [number, number] { - const gx = Math.floor((x / canvas.width) * p.SIM_N); - const gy = Math.floor(((canvas.height - y) / canvas.height) * p.SIM_N); +function toGrid(clientX: number, clientY: number): [number, number] { + const rect = canvas.getBoundingClientRect(); + + // Taking into account the square aspect ratio + const centerX = rect.x + rect.width / 2; + const centerY = rect.y + rect.height / 2; + const size = Math.min(rect.width, rect.height); + const squareLeft = centerX - size / 2; + const squareTop = centerY - size / 2; + + const gx = Math.floor(((clientX - squareLeft) / size) * p.SIM_N); + const gy = Math.floor((1 - (clientY - squareTop) / size) * p.SIM_N); return [gx, gy]; } @@ -273,10 +282,39 @@ const renderBindGroups = { ], }; +function render() { + let renderBG: TgpuBindGroup<{ + result: { texture: d.WgslTexture2d }; + background: { texture: d.WgslTexture2d }; + }>; + let pipeline: TgpuRenderPipeline; + + switch (p.params.displayMode) { + case 'ink': + renderBG = renderBindGroups.ink[inkBuffer.currentIndex]; + pipeline = renderPipelineInk; + break; + case 'image': + renderBG = renderBindGroups.ink[inkBuffer.currentIndex]; + pipeline = renderPipelineImage; + break; + case 'velocity': + renderBG = renderBindGroups.velocity[velBuffer.currentIndex]; + pipeline = renderPipelineVel; + break; + default: + throw new Error('Invalid display mode'); + } + + pipeline.withColorAttachment({ view: context }).with(renderBG).draw(3); +} + // Main rendering loop +let handle: number; function loop() { + handle = requestAnimationFrame(loop); + if (p.params.paused) { - requestAnimationFrame(loop); return; } @@ -333,45 +371,28 @@ function loop() { .dispatchWorkgroups(dispatchX, dispatchY); inkBuffer.swap(); - let renderBG: TgpuBindGroup<{ - result: { texture: d.WgslTexture2d }; - background: { texture: d.WgslTexture2d }; - }>; - let pipeline: TgpuRenderPipeline; - - switch (p.params.displayMode) { - case 'ink': - renderBG = renderBindGroups.ink[inkBuffer.currentIndex]; - pipeline = renderPipelineInk; - break; - case 'image': - renderBG = renderBindGroups.ink[inkBuffer.currentIndex]; - pipeline = renderPipelineImage; - break; - case 'velocity': - renderBG = renderBindGroups.velocity[velBuffer.currentIndex]; - pipeline = renderPipelineVel; - break; - default: - throw new Error('Invalid display mode'); - } - - pipeline.withColorAttachment({ view: context }).with(renderBG).draw(3); - - requestAnimationFrame(loop); + render(); } -loop(); - -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); +handle = requestAnimationFrame(loop); + +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + render(); + }, +}); // #region Example controls and cleanup canvas.addEventListener('mousedown', (e) => { - const x = e.offsetX * devicePixelRatio; - const y = e.offsetY * devicePixelRatio; brushState = { - pos: toGrid(x, y), + pos: toGrid(e.clientX, e.clientY), delta: [0, 0], isDown: true, }; @@ -381,11 +402,8 @@ canvas.addEventListener( (e) => { e.preventDefault(); const touch = e.touches[0]; - const rect = canvas.getBoundingClientRect(); - const x = (touch.clientX - rect.left) * devicePixelRatio; - const y = (touch.clientY - rect.top) * devicePixelRatio; brushState = { - pos: toGrid(x, y), + pos: toGrid(touch.clientX, touch.clientY), delta: [0, 0], isDown: true, }; @@ -404,9 +422,7 @@ const touchEndEventListener = () => { window.addEventListener('touchend', touchEndEventListener); canvas.addEventListener('mousemove', (e) => { - const x = e.offsetX * devicePixelRatio; - const y = e.offsetY * devicePixelRatio; - const [newX, newY] = toGrid(x, y); + const [newX, newY] = toGrid(e.clientX, e.clientY); brushState.delta = [newX - brushState.pos[0], newY - brushState.pos[1]]; brushState.pos = [newX, newY]; }); @@ -415,10 +431,7 @@ canvas.addEventListener( (e) => { e.preventDefault(); const touch = e.touches[0]; - const rect = canvas.getBoundingClientRect(); - const x = (touch.clientX - rect.left) * devicePixelRatio; - const y = (touch.clientY - rect.top) * devicePixelRatio; - const [newX, newY] = toGrid(x, y); + const [newX, newY] = toGrid(touch.clientX, touch.clientY); brushState.delta = [newX - brushState.pos[0], newY - brushState.pos[1]]; brushState.pos = [newX, newY]; }, @@ -488,6 +501,7 @@ export function onCleanup() { window.removeEventListener('mouseup', mouseUpEventListener); window.removeEventListener('touchend', touchEndEventListener); detachAutoResizer(); + cancelAnimationFrame(handle); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/simulation/wind-map/index.html b/apps/typegpu-docs/src/examples/simulation/wind-map/index.html index aa8cc321b3..8167d9f1c1 100644 --- a/apps/typegpu-docs/src/examples/simulation/wind-map/index.html +++ b/apps/typegpu-docs/src/examples/simulation/wind-map/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/simulation/wind-map/index.ts b/apps/typegpu-docs/src/examples/simulation/wind-map/index.ts index 90a75698cb..061e92efc8 100644 --- a/apps/typegpu-docs/src/examples/simulation/wind-map/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/wind-map/index.ts @@ -267,7 +267,17 @@ const runAnimationFrame = () => { }; runAnimationFrame(); -const detachAutoResizer = common.attachAutoResizer({ root, canvas }); +const detachAutoResizer = common.attachAutoResizer({ + root, + canvas, + onResize() { + // Keeping the aspect ratio 1:1 + const size = Math.min(canvas.width, canvas.height); + canvas.width = size; + canvas.height = size; + draw(); + }, +}); // #region Example controls & Cleanup diff --git a/apps/typegpu-docs/src/examples/tests/rc-docs-examples/index.html b/apps/typegpu-docs/src/examples/tests/rc-docs-examples/index.html index aa8cc321b3..367ab17706 100644 --- a/apps/typegpu-docs/src/examples/tests/rc-docs-examples/index.html +++ b/apps/typegpu-docs/src/examples/tests/rc-docs-examples/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/tests/sdf-docs-examples/index.html b/apps/typegpu-docs/src/examples/tests/sdf-docs-examples/index.html index aa8cc321b3..367ab17706 100644 --- a/apps/typegpu-docs/src/examples/tests/sdf-docs-examples/index.html +++ b/apps/typegpu-docs/src/examples/tests/sdf-docs-examples/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/tests/texture-test/index.html b/apps/typegpu-docs/src/examples/tests/texture-test/index.html index aa8cc321b3..367ab17706 100644 --- a/apps/typegpu-docs/src/examples/tests/texture-test/index.html +++ b/apps/typegpu-docs/src/examples/tests/texture-test/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/tests/uniformity/index.html b/apps/typegpu-docs/src/examples/tests/uniformity/index.html index aa8cc321b3..367ab17706 100644 --- a/apps/typegpu-docs/src/examples/tests/uniformity/index.html +++ b/apps/typegpu-docs/src/examples/tests/uniformity/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/threejs/attractors/index.html b/apps/typegpu-docs/src/examples/threejs/attractors/index.html index 581d6789f8..a1d12c864f 100644 --- a/apps/typegpu-docs/src/examples/threejs/attractors/index.html +++ b/apps/typegpu-docs/src/examples/threejs/attractors/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/threejs/compute-cloth/index.html b/apps/typegpu-docs/src/examples/threejs/compute-cloth/index.html index 581d6789f8..a1d12c864f 100644 --- a/apps/typegpu-docs/src/examples/threejs/compute-cloth/index.html +++ b/apps/typegpu-docs/src/examples/threejs/compute-cloth/index.html @@ -1 +1 @@ - + diff --git a/apps/typegpu-docs/src/examples/threejs/compute-geometry/index.html b/apps/typegpu-docs/src/examples/threejs/compute-geometry/index.html index 53fabe2eb9..037ff50a4a 100644 --- a/apps/typegpu-docs/src/examples/threejs/compute-geometry/index.html +++ b/apps/typegpu-docs/src/examples/threejs/compute-geometry/index.html @@ -1,5 +1,5 @@
Model is loading...
- +