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..ab44e89723 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,8 +31,6 @@ 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 maxSide = Math.floor(Math.sqrt(maxBufferSize / 4)); const minLog = 2; // log_2(4) const maxLog = Math.floor(Math.log2(maxSide)); @@ -75,17 +72,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 +126,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 }); @@ -246,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[]; @@ -275,6 +273,7 @@ export function onCleanup() { for (const s of Object.values(sorters)) { s.destroy(); } + detachAutoResizer(); root.destroy(); } 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 6943a1e79e..d8accf2dd5 100644 --- a/apps/typegpu-docs/src/examples/geometry/lines-combinations/index.ts +++ b/apps/typegpu-docs/src/examples/geometry/lines-combinations/index.ts @@ -9,7 +9,7 @@ import { lineSegmentWireframeIndices, startCapSlot, } from '@typegpu/geometry'; -import tgpu, { type ColorAttachment } from 'typegpu'; +import tgpu, { common, type ColorAttachment } from 'typegpu'; import { arrayOf, builtin, @@ -406,6 +406,8 @@ const runAnimationFrame = (timeMs: number) => { }; runAnimationFrame(0); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + const fillOptions = { none: 0, solid: 1, @@ -502,6 +504,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 d071b5a309..b2c120fcfd 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 @@ -343,10 +343,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/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/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/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/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 0f8143b367..4b01ac9279 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 @@ -1,4 +1,4 @@ -import tgpu, { d, std } from 'typegpu'; +import tgpu, { common, d, std } from 'typegpu'; import { defineControls } from '../../common/defineControls.ts'; const root = await tgpu.init(); @@ -525,6 +525,8 @@ function run(timestamp: number) { } animationFrame = requestAnimationFrame(run); +const detachAutoResizer = common.attachAutoResizer({ root, canvas }); + export const controls = defineControls({ size: { initial: 32, @@ -603,6 +605,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 639d4914d8..7ea20bc626 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 { lineSegmentVariableWidth, 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(); } 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';