diff --git a/apps/typegpu-docs/src/components/translator/lib/rolldown.ts b/apps/typegpu-docs/src/components/translator/lib/rolldown.ts index cd8114184a..cf2c70ceb3 100644 --- a/apps/typegpu-docs/src/components/translator/lib/rolldown.ts +++ b/apps/typegpu-docs/src/components/translator/lib/rolldown.ts @@ -2,7 +2,7 @@ import type { InputOptions, OutputOptions } from '@rolldown/browser'; import { join } from 'pathe'; export interface BundleResult { - output: Record; + output: Record>; warnings?: string[] | undefined; } @@ -88,7 +88,7 @@ export async function bundle( result.output.map((chunk) => chunk.type === 'chunk' ? [chunk.fileName, chunk.code] - : [chunk.fileName, chunk.source] + : [chunk.fileName, chunk.source.slice()] ), ); diff --git a/apps/typegpu-docs/src/content/docs/fundamentals/functions/index.mdx b/apps/typegpu-docs/src/content/docs/fundamentals/functions/index.mdx index 3ed1193088..a7c2c4fc24 100644 --- a/apps/typegpu-docs/src/content/docs/fundamentals/functions/index.mdx +++ b/apps/typegpu-docs/src/content/docs/fundamentals/functions/index.mdx @@ -323,7 +323,7 @@ function manhattanDistance(a: d.v3f, b: d.v3f) { const dy = std.abs(a.y - b.y); const dz = std.abs(a.z - b.z); - return std.max(dx, std.max(dy, dz)); + return std.max(dx, dy, dz); } ``` diff --git a/apps/typegpu-docs/src/content/docs/fundamentals/utils.mdx b/apps/typegpu-docs/src/content/docs/fundamentals/utils.mdx index e5d27950c7..f2e833b405 100644 --- a/apps/typegpu-docs/src/content/docs/fundamentals/utils.mdx +++ b/apps/typegpu-docs/src/content/docs/fundamentals/utils.mdx @@ -128,6 +128,76 @@ tgpu.resolve([innerPipeline]); ``` ::: +## *tgpu.comptime* + +`tgpu['~unstable'].comptime(func)` creates a version of `func` that instead of being transpiled to WGSL, will be called during the WGSL code generation. +This can be used to precompute and inject a value into the final shader code. + +```ts twoslash +import tgpu from 'typegpu'; +import * as d from 'typegpu/data'; + +const hsvToRgb = tgpu.fn([d.f32, d.f32, d.f32], d.vec3f)``; + +// ---cut--- +const injectRand01 = tgpu['~unstable'] + .comptime(() => Math.random()); + +const getColor = tgpu.fn([d.vec3f])((diffuse) => { + 'use gpu'; + const albedo = hsvToRgb(injectRand01(), 1, 0.5); + return albedo.mul(diffuse); +}); +``` + +Note how the function passed into `comptime` doesn't have to be marked with +`'use gpu'` and can use `Math`. That's because the function doesn't execute on the GPU, it gets +executed before the shader code gets sent to the GPU. + +## *tgpu.rawCodeSnippet* + +When working on top of some existing shader code, sometimes you may know for certain that some variable will be already defined and should be accessible in the code. +In such scenario you can use `tgpu['~unstable'].rawCodeSnippet` -- an advanced API that creates a typed shader expression which can be injected into the final shader bundle upon use. + +```ts twoslash +import tgpu from 'typegpu'; +import * as d from 'typegpu/data'; + +// ---cut--- +// `EXISTING_GLOBAL` is an identifier that we know will be in the +// final shader bundle, but we cannot +// refer to it in any other way. +const existingGlobal = tgpu['~unstable'] + .rawCodeSnippet('EXISTING_GLOBAL', d.f32, 'constant'); + +const foo = tgpu.fn([], d.f32)(() => { + 'use gpu'; + return existingGlobal.$ * 22; +}); + +const wgsl = tgpu.resolve([foo]); +// fn foo() -> f32 { +// return (EXISTING_GLOBAL * 22f); +// } +``` + +:::note +There currently is no way to create a `rawCodeSnippet` that refers to an existing function. +::: + +### Which origin to choose? + +The optional third parameter `origin` lets TypeGPU transpiler know how to optimize the code snippet, as well as allows for some transpilation-time validity checks. + +Usually `'runtime'` (the default) is a safe bet, but if you're sure that the expression or +computation is constant (either a reference to a constant, a numeric literal, +or an operation on constants), then pass `'constant'` as it might lead to better +optimizations. + +If what the expression is a direct reference to an existing value (e.g. a uniform, a +storage binding, ...), then choose from `'uniform'`, `'mutable'`, `'readonly'`, `'workgroup'`, +`'private'` or `'handle'` depending on the address space of the referred value. + ## *console.log* Yes, you read that correctly, TypeGPU implements logging to the console on the GPU! diff --git a/apps/typegpu-docs/src/examples/algorithms/mnist-inference/index.html b/apps/typegpu-docs/src/examples/algorithms/mnist-inference/index.html index db412b1f24..7aeb43a990 100644 --- a/apps/typegpu-docs/src/examples/algorithms/mnist-inference/index.html +++ b/apps/typegpu-docs/src/examples/algorithms/mnist-inference/index.html @@ -2,8 +2,6 @@
-
Predictions (-.--ms)
-
O
1
@@ -51,11 +49,6 @@ gap: 1rem; } - .predictions-label { - margin-bottom: 0.5rem; - font-size: 1.25rem; - } - .bars-container { display: flex; flex-direction: column; 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 05a5fb8ae7..4425ae8049 100644 --- a/apps/typegpu-docs/src/examples/geometry/lines-combinations/index.ts +++ b/apps/typegpu-docs/src/examples/geometry/lines-combinations/index.ts @@ -454,6 +454,7 @@ export const controls = { initial: Object.keys(testCases)[0], options: Object.keys(testCases), onSelectChange: async (selected: keyof typeof testCases) => { + // biome-ignore lint/performance/noDynamicNamespaceImportAccess: no other way testCase = testCases[selected]; pipelines = createPipelines(); }, 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 dde2d38f3e..0369a44d82 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 @@ -313,7 +313,7 @@ async function updateLUT(file: string) { device.queue.writeTexture( { texture: root.unwrap(currentLUTTexture) }, - parsed.data, + parsed.data.buffer, { bytesPerRow: parsed.size * 4 * 2, rowsPerImage: parsed.size, 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 77219c318e..1136fade8a 100644 --- a/apps/typegpu-docs/src/examples/rendering/box-raytracing/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/box-raytracing/index.ts @@ -209,7 +209,6 @@ const fragmentFunction = tgpu['~unstable'].fragmentFn({ let density = d.f32(0); let invColor = d.vec3f(0, 0, 0); - let tMin = d.f32(0); let intersectionFound = false; for (let i = 0; i < X; i++) { @@ -240,7 +239,6 @@ const fragmentFunction = tgpu['~unstable'].fragmentFn({ div(d.vec3f(1), boxMatrix.$[i][j][k].albedo), ), ); - tMin = intersection.tMin; intersectionFound = true; } } diff --git a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/cubemap.ts b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/cubemap.ts index 0c83a8e341..836c135d9d 100644 --- a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/cubemap.ts +++ b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/cubemap.ts @@ -74,7 +74,7 @@ export async function loadCubemap( chosenCubemap: CubemapNames, ) { const images = await Promise.all( - getCubemapUrls(chosenCubemap).map(async (url, i) => { + getCubemapUrls(chosenCubemap).map(async (url) => { const response = await fetch(url); const blob = await response.blob(); return await createImageBitmap(blob); 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 8731a57e14..4ae8f334e3 100644 --- a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/index.ts @@ -352,21 +352,14 @@ resizeObserver.observe(canvas); let isDragging = false; let prevX = 0; let prevY = 0; +let lastPinchDist = 0; let orbitRadius = std.length(cameraInitialPos); // Yaw and pitch angles facing the origin. let orbitYaw = Math.atan2(cameraInitialPos.x, cameraInitialPos.z); let orbitPitch = Math.asin(cameraInitialPos.y / orbitRadius); -function updateCameraOrbit(dx: number, dy: number) { - const orbitSensitivity = 0.005; - orbitYaw += -dx * orbitSensitivity; - orbitPitch += dy * orbitSensitivity; - // Clamp pitch to avoid flipping - const maxPitch = Math.PI / 2 - 0.01; - if (orbitPitch > maxPitch) orbitPitch = maxPitch; - if (orbitPitch < -maxPitch) orbitPitch = -maxPitch; - // Convert spherical coordinates to cartesian coordinates +function updateCameraPosition() { const newCamX = orbitRadius * Math.sin(orbitYaw) * Math.cos(orbitPitch); const newCamY = orbitRadius * Math.sin(orbitPitch); const newCamZ = orbitRadius * Math.cos(orbitYaw) * Math.cos(orbitPitch); @@ -381,21 +374,24 @@ function updateCameraOrbit(dx: number, dy: number) { cameraBuffer.writePartial({ view: newView, position: newCameraPos }); } -canvas.addEventListener('wheel', (event: WheelEvent) => { - event.preventDefault(); - const zoomSensitivity = 0.05; - orbitRadius = std.clamp(orbitRadius + event.deltaY * zoomSensitivity, 3, 100); - const newCamX = orbitRadius * Math.sin(orbitYaw) * Math.cos(orbitPitch); - const newCamY = orbitRadius * Math.sin(orbitPitch); - const newCamZ = orbitRadius * Math.cos(orbitYaw) * Math.cos(orbitPitch); - const newCameraPos = d.vec4f(newCamX, newCamY, newCamZ, 1); - const newView = m.mat4.lookAt( - newCameraPos, - d.vec3f(0, 0, 0), - d.vec3f(0, 1, 0), - d.mat4x4f(), +function updateCameraOrbit(dx: number, dy: number) { + orbitYaw += -dx * 0.005; + orbitPitch = std.clamp( + orbitPitch + dy * 0.005, + -Math.PI / 2 + 0.01, + Math.PI / 2 - 0.01, ); - cameraBuffer.writePartial({ view: newView, position: newCameraPos }); + updateCameraPosition(); +} + +function zoomCamera(delta: number) { + orbitRadius = std.clamp(orbitRadius + delta, 3, 100); + updateCameraPosition(); +} + +canvas.addEventListener('wheel', (e: WheelEvent) => { + e.preventDefault(); + zoomCamera(e.deltaY * 0.05); }, { passive: false }); canvas.addEventListener('mousedown', (event) => { @@ -404,12 +400,17 @@ canvas.addEventListener('mousedown', (event) => { prevY = event.clientY; }); -canvas.addEventListener('touchstart', (event) => { - event.preventDefault(); - if (event.touches.length === 1) { +canvas.addEventListener('touchstart', (e) => { + e.preventDefault(); + if (e.touches.length === 1) { isDragging = true; - prevX = event.touches[0].clientX; - prevY = event.touches[0].clientY; + prevX = e.touches[0].clientX; + prevY = e.touches[0].clientY; + } else if (e.touches.length === 2) { + isDragging = false; + const dx = e.touches[0].clientX - e.touches[1].clientX; + const dy = e.touches[0].clientY - e.touches[1].clientY; + lastPinchDist = Math.sqrt(dx * dx + dy * dy); } }, { passive: false }); @@ -418,8 +419,14 @@ const mouseUpEventListener = () => { }; window.addEventListener('mouseup', mouseUpEventListener); -const touchEndEventListener = () => { - isDragging = false; +const touchEndEventListener = (e: TouchEvent) => { + if (e.touches.length === 1) { + isDragging = true; + prevX = e.touches[0].clientX; + prevY = e.touches[0].clientY; + } else { + isDragging = false; + } }; window.addEventListener('touchend', touchEndEventListener); @@ -435,14 +442,13 @@ const mouseMoveEventListener = (event: MouseEvent) => { }; window.addEventListener('mousemove', mouseMoveEventListener); -const touchMoveEventListener = (event: TouchEvent) => { - if (isDragging && event.touches.length === 1) { - event.preventDefault(); - const dx = event.touches[0].clientX - prevX; - const dy = event.touches[0].clientY - prevY; - prevX = event.touches[0].clientX; - prevY = event.touches[0].clientY; - +const touchMoveEventListener = (e: TouchEvent) => { + if (e.touches.length === 1 && isDragging) { + e.preventDefault(); + const dx = e.touches[0].clientX - prevX; + const dy = e.touches[0].clientY - prevY; + prevX = e.touches[0].clientX; + prevY = e.touches[0].clientY; updateCameraOrbit(dx, dy); } }; @@ -450,6 +456,17 @@ window.addEventListener('touchmove', touchMoveEventListener, { passive: false, }); +canvas.addEventListener('touchmove', (e) => { + if (e.touches.length === 2) { + e.preventDefault(); + const dx = e.touches[0].clientX - e.touches[1].clientX; + const dy = e.touches[0].clientY - e.touches[1].clientY; + const pinchDist = Math.sqrt(dx * dx + dy * dy); + zoomCamera((lastPinchDist - pinchDist) * 0.05); + lastPinchDist = pinchDist; + } +}, { passive: false }); + function hideHelp() { const helpElem = document.getElementById('help'); if (helpElem) { diff --git a/apps/typegpu-docs/src/examples/rendering/disco/index.ts b/apps/typegpu-docs/src/examples/rendering/disco/index.ts index f2ecad00a5..0a69c1edc3 100644 --- a/apps/typegpu-docs/src/examples/rendering/disco/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/disco/index.ts @@ -1,9 +1,8 @@ import tgpu from 'typegpu'; import * as d from 'typegpu/data'; -import { mainVertex } from './shaders/vertex.ts'; import { resolutionAccess, timeAccess } from './consts.ts'; import { - mainFragment, + mainFragment1, mainFragment2, mainFragment3, mainFragment4, @@ -11,6 +10,7 @@ import { mainFragment6, mainFragment7, } from './shaders/fragment.ts'; +import { mainVertex } from './shaders/vertex.ts'; const canvas = document.querySelector('canvas') as HTMLCanvasElement; const context = canvas.getContext('webgpu') as GPUCanvasContext; @@ -32,7 +32,7 @@ const resolutionUniform = root.createUniform( ); const fragmentShaders = [ - mainFragment, + mainFragment1, mainFragment2, mainFragment3, mainFragment4, @@ -107,4 +107,14 @@ export const controls = { } }, }, + 'Test Resolution': import.meta.env.DEV && { + onButtonClick() { + const namespace = tgpu['~unstable'].namespace(); + Array.from({ length: 6 }).map((_, i) => + root.device.createShaderModule({ + code: tgpu.resolve([pipelines[i + 1]], { names: namespace }), + }) + ); + }, + }, }; diff --git a/apps/typegpu-docs/src/examples/rendering/disco/shaders/fragment.ts b/apps/typegpu-docs/src/examples/rendering/disco/shaders/fragment.ts index 59a12c7e1e..00691258da 100644 --- a/apps/typegpu-docs/src/examples/rendering/disco/shaders/fragment.ts +++ b/apps/typegpu-docs/src/examples/rendering/disco/shaders/fragment.ts @@ -21,33 +21,32 @@ const accumulate = tgpu.fn( d.vec3f, )((acc, col, weight) => acc.add(col.mul(weight))); -export const mainFragment = tgpu['~unstable'].fragmentFn({ +// Variation1 +export const mainFragment1 = tgpu['~unstable'].fragmentFn({ in: { uv: d.vec2f }, out: d.vec4f, })(({ uv }) => { - { - const originalUv = aspectCorrected(uv); + const originalUv = aspectCorrected(uv); - let aspectUv = d.vec2f(originalUv); - let accumulatedColor = d.vec3f(); - for (let iteration = 0.0; iteration < 5.0; iteration++) { - aspectUv = std.fract(aspectUv.mul(1.3 * std.sin(timeAccess.$))).sub(0.5); - let radialLength = std.length(aspectUv) * - std.exp(-std.length(originalUv) * 2); - radialLength = std.sin(radialLength * 8 + timeAccess.$) / 8; - radialLength = std.abs(radialLength); - radialLength = std.smoothstep(0.0, 0.1, radialLength); - radialLength = 0.06 / radialLength; + let aspectUv = d.vec2f(originalUv); + let accumulatedColor = d.vec3f(); + for (let iteration = 0.0; iteration < 5.0; iteration++) { + aspectUv = std.fract(aspectUv.mul(1.3 * std.sin(timeAccess.$))).sub(0.5); + let radialLength = std.length(aspectUv) * + std.exp(-std.length(originalUv) * 2); + radialLength = std.sin(radialLength * 8 + timeAccess.$) / 8; + radialLength = std.abs(radialLength); + radialLength = std.smoothstep(0.0, 0.1, radialLength); + radialLength = 0.06 / radialLength; - const paletteColor = palette(std.length(originalUv) + timeAccess.$ * 0.9); - accumulatedColor = accumulate( - accumulatedColor, - paletteColor, - radialLength, - ); - } - return d.vec4f(accumulatedColor, 1.0); + const paletteColor = palette(std.length(originalUv) + timeAccess.$ * 0.9); + accumulatedColor = accumulate( + accumulatedColor, + paletteColor, + radialLength, + ); } + return d.vec4f(accumulatedColor, 1.0); }); // Variation2 @@ -55,28 +54,26 @@ export const mainFragment2 = tgpu['~unstable'].fragmentFn({ in: { uv: d.vec2f }, out: d.vec4f, })(({ uv }) => { - { - const originalUv = aspectCorrected(uv); - let aspectUv = d.vec2f(originalUv); + const originalUv = aspectCorrected(uv); + let aspectUv = d.vec2f(originalUv); - let accumulatedColor = d.vec3f(); - for (let iteration = 0.0; iteration < 3.0; iteration++) { - aspectUv = std.fract(aspectUv.mul(-0.9)).sub(0.5); - let radialLength = std.length(aspectUv) * - std.exp(-std.length(originalUv) * 0.5); - const paletteColor = palette(std.length(originalUv) + timeAccess.$ * 0.9); - radialLength = std.sin(radialLength * 8 + timeAccess.$) / 8; - radialLength = std.abs(radialLength); - radialLength = std.smoothstep(0.0, 0.1, radialLength); - radialLength = 0.1 / radialLength; - accumulatedColor = accumulate( - accumulatedColor, - paletteColor, - radialLength, - ); - } - return d.vec4f(accumulatedColor, 1.0); + let accumulatedColor = d.vec3f(); + for (let iteration = 0.0; iteration < 3.0; iteration++) { + aspectUv = std.fract(aspectUv.mul(-0.9)).sub(0.5); + let radialLength = std.length(aspectUv) * + std.exp(-std.length(originalUv) * 0.5); + const paletteColor = palette(std.length(originalUv) + timeAccess.$ * 0.9); + radialLength = std.sin(radialLength * 8 + timeAccess.$) / 8; + radialLength = std.abs(radialLength); + radialLength = std.smoothstep(0.0, 0.1, radialLength); + radialLength = 0.1 / radialLength; + accumulatedColor = accumulate( + accumulatedColor, + paletteColor, + radialLength, + ); } + return d.vec4f(accumulatedColor, 1.0); }); // Variation3 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 7d7f7f0906..5ba3f68ff3 100644 --- a/apps/typegpu-docs/src/examples/rendering/jelly-slider/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/jelly-slider/index.ts @@ -697,7 +697,7 @@ const renderBackground = ( ); }; -const rayMarch = (rayOrigin: d.v3f, rayDirection: d.v3f, uv: d.v2f) => { +const rayMarch = (rayOrigin: d.v3f, rayDirection: d.v3f, _uv: d.v2f) => { 'use gpu'; let totalSteps = d.u32(); diff --git a/apps/typegpu-docs/src/examples/rendering/jelly-slider/slider.ts b/apps/typegpu-docs/src/examples/rendering/jelly-slider/slider.ts index 599b178eae..2f071099ae 100644 --- a/apps/typegpu-docs/src/examples/rendering/jelly-slider/slider.ts +++ b/apps/typegpu-docs/src/examples/rendering/jelly-slider/slider.ts @@ -398,7 +398,6 @@ export class Slider { } #computeControlPoints() { - const eps = 1e-6; for (let i = 0; i < this.n - 1; i++) { const A = this.#pos[i]; const B = this.#pos[i + 1]; diff --git a/apps/typegpu-docs/src/examples/rendering/jelly-slider/utils.ts b/apps/typegpu-docs/src/examples/rendering/jelly-slider/utils.ts index d4b96cf9bc..a3e3d0e221 100644 --- a/apps/typegpu-docs/src/examples/rendering/jelly-slider/utils.ts +++ b/apps/typegpu-docs/src/examples/rendering/jelly-slider/utils.ts @@ -33,8 +33,8 @@ export const intersectBox = ( const tMinVec = std.min(t1, t2); const tMaxVec = std.max(t1, t2); - const tMin = std.max(std.max(tMinVec.x, tMinVec.y), tMinVec.z); - const tMax = std.min(std.min(tMaxVec.x, tMaxVec.y), tMaxVec.z); + const tMin = std.max(tMinVec.x, tMinVec.y, tMinVec.z); + const tMax = std.min(tMaxVec.x, tMaxVec.y, tMaxVec.z); const result = BoxIntersection(); result.hit = tMax >= tMin && tMax >= 0.0; 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 bc43edfaf0..db56c5b951 100644 --- a/apps/typegpu-docs/src/examples/rendering/jelly-switch/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/jelly-switch/index.ts @@ -416,7 +416,7 @@ const renderBackground = ( return d.vec4f(backgroundColor.xyz, 1); }; -const rayMarch = (rayOrigin: d.v3f, rayDirection: d.v3f, uv: d.v2f) => { +const rayMarch = (rayOrigin: d.v3f, rayDirection: d.v3f, _uv: d.v2f) => { 'use gpu'; let totalSteps = d.u32(); @@ -652,7 +652,7 @@ canvas.addEventListener('touchstart', (event) => { event.preventDefault(); }); -canvas.addEventListener('touchend', (event) => { +canvas.addEventListener('touchend', () => { switchBehavior.pressed = false; switchBehavior.toggled = !switchBehavior.toggled; }); @@ -668,7 +668,7 @@ canvas.addEventListener('mouseup', (event) => { event.stopPropagation(); }); -window.addEventListener('mouseup', (event) => { +window.addEventListener('mouseup', () => { switchBehavior.pressed = false; }); diff --git a/apps/typegpu-docs/src/examples/rendering/jelly-switch/taa.ts b/apps/typegpu-docs/src/examples/rendering/jelly-switch/taa.ts index f721d40670..c5beeb4f58 100644 --- a/apps/typegpu-docs/src/examples/rendering/jelly-switch/taa.ts +++ b/apps/typegpu-docs/src/examples/rendering/jelly-switch/taa.ts @@ -49,8 +49,6 @@ export const taaResolveFn = tgpu['~unstable'].computeFn({ const historyColorClamped = std.clamp(historyColor.xyz, minColor, maxColor); - const uv = d.vec2f(gid.xy).div(d.vec2f(dimensions.xy)); - const blendFactor = d.f32(0.9); const resolvedColor = d.vec4f( diff --git a/apps/typegpu-docs/src/examples/rendering/jelly-switch/utils.ts b/apps/typegpu-docs/src/examples/rendering/jelly-switch/utils.ts index c7277a7f36..ed5b0fa35d 100644 --- a/apps/typegpu-docs/src/examples/rendering/jelly-switch/utils.ts +++ b/apps/typegpu-docs/src/examples/rendering/jelly-switch/utils.ts @@ -32,8 +32,8 @@ export const intersectBox = ( const tMinVec = std.min(t1, t2); const tMaxVec = std.max(t1, t2); - const tMin = std.max(std.max(tMinVec.x, tMinVec.y), tMinVec.z); - const tMax = std.min(std.min(tMaxVec.x, tMaxVec.y), tMaxVec.z); + const tMin = std.max(tMinVec.x, tMinVec.y, tMinVec.z); + const tMax = std.min(tMaxVec.x, tMaxVec.y, tMaxVec.z); const result = BoxIntersection(); result.hit = tMax >= tMin && tMax >= 0.0; diff --git a/apps/typegpu-docs/src/examples/rendering/phong-reflection/setup-orbit-camera.ts b/apps/typegpu-docs/src/examples/rendering/phong-reflection/setup-orbit-camera.ts index 39b376d4cd..b1dbe74493 100644 --- a/apps/typegpu-docs/src/examples/rendering/phong-reflection/setup-orbit-camera.ts +++ b/apps/typegpu-docs/src/examples/rendering/phong-reflection/setup-orbit-camera.ts @@ -119,6 +119,7 @@ export function setupOrbitCamera( let isDragging = false; let prevX = 0; let prevY = 0; + let lastPinchDist = 0; // mouse/touch events canvas.addEventListener('wheel', (event: WheelEvent) => { @@ -138,6 +139,11 @@ export function setupOrbitCamera( isDragging = true; prevX = event.touches[0].clientX; prevY = event.touches[0].clientY; + } else if (event.touches.length === 2) { + isDragging = false; + const dx = event.touches[0].clientX - event.touches[1].clientX; + const dy = event.touches[0].clientY - event.touches[1].clientY; + lastPinchDist = Math.sqrt(dx * dx + dy * dy); } }, { passive: false }); @@ -146,8 +152,14 @@ export function setupOrbitCamera( }; window.addEventListener('mouseup', mouseUpEventListener); - const touchEndEventListener = () => { - isDragging = false; + const touchEndEventListener = (e: TouchEvent) => { + if (e.touches.length === 1) { + isDragging = true; + prevX = e.touches[0].clientX; + prevY = e.touches[0].clientY; + } else { + isDragging = false; + } }; window.addEventListener('touchend', touchEndEventListener); @@ -164,7 +176,7 @@ export function setupOrbitCamera( window.addEventListener('mousemove', mouseMoveEventListener); const touchMoveEventListener = (event: TouchEvent) => { - if (isDragging && event.touches.length === 1) { + if (event.touches.length === 1 && isDragging) { event.preventDefault(); const dx = event.touches[0].clientX - prevX; const dy = event.touches[0].clientY - prevY; @@ -178,6 +190,17 @@ export function setupOrbitCamera( passive: false, }); + canvas.addEventListener('touchmove', (e) => { + if (e.touches.length === 2) { + e.preventDefault(); + const dx = e.touches[0].clientX - e.touches[1].clientX; + const dy = e.touches[0].clientY - e.touches[1].clientY; + const pinchDist = Math.sqrt(dx * dx + dy * dy); + zoomCamera((lastPinchDist - pinchDist) * 0.5); + lastPinchDist = pinchDist; + } + }, { passive: false }); + function cleanupCamera() { window.removeEventListener('mouseup', mouseUpEventListener); window.removeEventListener('mousemove', mouseMoveEventListener); diff --git a/apps/typegpu-docs/src/examples/rendering/point-light-shadow/box-geometry.ts b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/box-geometry.ts new file mode 100644 index 0000000000..0c1049e960 --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/box-geometry.ts @@ -0,0 +1,152 @@ +import type { IndexFlag, TgpuBuffer, TgpuRoot, VertexFlag } from 'typegpu'; +import * as d from 'typegpu/data'; +import type { GeometryData } from './types.ts'; +import { InstanceData, VertexData } from './types.ts'; + +export class BoxGeometry { + static #vertexBuffer: + | (TgpuBuffer> & VertexFlag) + | null = null; + static #indexBuffer: (TgpuBuffer> & IndexFlag) | null = + null; + static #indexCount = 0; + + #modelMatrix = d.mat4x4f.identity(); + #position = d.vec3f(0, 0, 0); + #scale = d.vec3f(1, 1, 1); + #rotation = d.vec3f(0, 0, 0); + + constructor(root: TgpuRoot) { + if (!BoxGeometry.#vertexBuffer || !BoxGeometry.#indexBuffer) { + this.#initBuffers(root); + } + } + + #initBuffers(root: TgpuRoot) { + const vertices: GeometryData = []; + const indices: number[] = []; + let vertexOffset = 0; + + const addFace = ( + u: number, + v: number, + w: number, + udir: number, + vdir: number, + depth: number, + ) => { + for (let iy = 0; iy < 2; iy++) { + for (let ix = 0; ix < 2; ix++) { + const pos = [0, 0, 0]; + pos[u] = (ix - 0.5) * udir; + pos[v] = (iy - 0.5) * vdir; + pos[w] = 0.5 * Math.sign(depth); + + const norm = [0, 0, 0]; + norm[w] = Math.sign(depth); + + vertices.push( + { + position: d.vec3f(pos[0], pos[1], pos[2]), + normal: d.vec3f(norm[0], norm[1], norm[2]), + uv: d.vec2f(ix, 1 - iy), + }, + ); + } + } + + indices.push( + vertexOffset, + vertexOffset + 1, + vertexOffset + 2, + vertexOffset + 1, + vertexOffset + 3, + vertexOffset + 2, + ); + vertexOffset += 4; + }; + + addFace(2, 1, 0, -1, 1, 1); // +X + addFace(2, 1, 0, 1, 1, -1); // -X + addFace(0, 2, 1, 1, 1, 1); // +Y + addFace(0, 2, 1, 1, -1, -1); // -Y + addFace(0, 1, 2, 1, 1, 1); // +Z + addFace(0, 1, 2, -1, 1, -1); // -Z + + BoxGeometry.#vertexBuffer = root + .createBuffer(d.arrayOf(VertexData, vertices.length), vertices) + .$usage('vertex'); + BoxGeometry.#indexBuffer = root + .createBuffer(d.arrayOf(d.u16, indices.length), indices) + .$usage('index'); + BoxGeometry.#indexCount = indices.length; + } + + set position(value: d.v3f) { + this.#position = value; + this.#updateModelMatrix(); + } + + get position() { + return this.#position; + } + + set scale(value: d.v3f) { + this.#scale = value; + this.#updateModelMatrix(); + } + + get scale() { + return this.#scale; + } + + set rotation(value: d.v3f) { + this.#rotation = value; + this.#updateModelMatrix(); + } + + get rotation() { + return this.#rotation; + } + + get instanceData(): d.Infer { + return InstanceData({ + column1: this.#modelMatrix.columns[0], + column2: this.#modelMatrix.columns[1], + column3: this.#modelMatrix.columns[2], + column4: this.#modelMatrix.columns[3], + }); + } + + static get vertexBuffer() { + if (!BoxGeometry.#vertexBuffer) { + throw new Error('BoxGeometry buffers not initialized'); + } + return BoxGeometry.#vertexBuffer; + } + + static get indexBuffer() { + if (!BoxGeometry.#indexBuffer) { + throw new Error('BoxGeometry buffers not initialized'); + } + return BoxGeometry.#indexBuffer; + } + + static get indexCount() { + return BoxGeometry.#indexCount; + } + + static clearBuffers() { + BoxGeometry.#vertexBuffer = null; + BoxGeometry.#indexBuffer = null; + } + + #updateModelMatrix() { + this.#modelMatrix = d.mat4x4f + .translation(this.#position) + .mul(d.mat4x4f.rotationZ(this.#rotation.z)) + .mul(d.mat4x4f.rotationY(this.#rotation.y)) + .mul(d.mat4x4f.rotationX(this.#rotation.x)) + .mul(d.mat4x4f.scaling(this.#scale)); + } +} diff --git a/apps/typegpu-docs/src/examples/rendering/point-light-shadow/camera.ts b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/camera.ts new file mode 100644 index 0000000000..9f561207f8 --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/camera.ts @@ -0,0 +1,90 @@ +import type { TgpuRoot } from 'typegpu'; +import * as d from 'typegpu/data'; +import * as m from 'wgpu-matrix'; +import { CameraData } from './types.ts'; + +export class Camera { + readonly #uniform; + + #position = d.vec3f(0, 0, 0); + #target = d.vec3f(0, 0, -1); + #up = d.vec3f(0, 1, 0); + #fov: number; + #near: number; + #far: number; + + constructor(root: TgpuRoot, fov = 60, near = 0.1, far = 1000) { + this.#fov = fov; + this.#near = near; + this.#far = far; + this.#uniform = root.createUniform(CameraData, this.#computeData()); + } + + set position(pos: d.v3f) { + this.#position = pos; + this.#update(); + } + + get position() { + return this.#position; + } + + set target(tgt: d.v3f) { + this.#target = tgt; + this.#update(); + } + + get target() { + return this.#target; + } + + set up(upVec: d.v3f) { + this.#up = upVec; + this.#update(); + } + + get up() { + return this.#up; + } + + set fov(fovDegrees: number) { + this.#fov = fovDegrees; + this.#update(); + } + + get fov() { + return this.#fov; + } + + get uniform() { + return this.#uniform; + } + + #computeData() { + const view = m.mat4.lookAt( + this.#position, + this.#target, + this.#up, + d.mat4x4f(), + ); + + const projection = m.mat4.perspective( + (this.#fov * Math.PI) / 180, + 1, + this.#near, + this.#far, + d.mat4x4f(), + ); + + const viewProjectionMatrix = m.mat4.mul(projection, view, d.mat4x4f()); + + return CameraData({ + viewProjectionMatrix, + inverseViewProjectionMatrix: m.mat4.invert(viewProjectionMatrix), + }); + } + + #update() { + this.#uniform.write(this.#computeData()); + } +} 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 new file mode 100644 index 0000000000..aa8cc321b3 --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.html @@ -0,0 +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 new file mode 100644 index 0000000000..050f7f6d9e --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/index.ts @@ -0,0 +1,701 @@ +import tgpu from 'typegpu'; +import { fullScreenTriangle } from 'typegpu/common'; +import * as d from 'typegpu/data'; +import * as std from 'typegpu/std'; +import { BoxGeometry } from './box-geometry.ts'; +import { Camera } from './camera.ts'; +import { PointLight } from './point-light.ts'; +import { Scene } from './scene.ts'; +import { + CameraData, + InstanceData, + instanceLayout, + VertexData, + vertexLayout, +} from './types.ts'; + +const root = await tgpu.init(); +const device = root.device; +const canvas = document.querySelector('canvas') as HTMLCanvasElement; +const context = canvas.getContext('webgpu') as GPUCanvasContext; +const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); + +context.configure({ device, format: presentationFormat }); + +const mainCamera = new Camera(root); +mainCamera.position = d.vec3f(5, 5, -5); +mainCamera.target = d.vec3f(0, 0, 0); + +const pointLight = new PointLight(root, d.vec3f(4.5, 1, 4), { + far: 100.0, + shadowMapSize: 1024, +}); + +const scene = new Scene(root); + +const cube = new BoxGeometry(root); +cube.scale = d.vec3f(3, 1, 0.2); + +const orbitingCubes: BoxGeometry[] = []; +for (let i = 0; i < 10; i++) { + const orbitingCube = new BoxGeometry(root); + const angle = (i / 10) * Math.PI * 2; + const radius = 4; + orbitingCube.position = d.vec3f( + Math.cos(angle) * radius, + 0.5, + Math.sin(angle) * radius, + ); + orbitingCube.scale = d.vec3f(0.5, 0.5, 0.5); + orbitingCubes.push(orbitingCube); +} + +const floorCube = new BoxGeometry(root); +floorCube.scale = d.vec3f(10, 0.1, 10); +floorCube.position = d.vec3f(0, -0.5, 0); +scene.add([cube, floorCube, ...orbitingCubes]); + +let depthTexture = root['~unstable'] + .createTexture({ + size: [canvas.width, canvas.height], + format: 'depth24plus', + sampleCount: 4, + }) + .$usage('render'); + +let msaaTexture = root['~unstable'] + .createTexture({ + size: [canvas.width, canvas.height], + format: presentationFormat, + sampleCount: 4, + }) + .$usage('render'); + +const shadowSampler = root['~unstable'].createComparisonSampler({ + compare: 'less-equal', + magFilter: 'linear', + minFilter: 'linear', +}); + +const renderLayout = tgpu.bindGroupLayout({ + camera: { uniform: CameraData }, + lightPosition: { uniform: d.vec3f }, +}); + +const renderLayoutWithShadow = tgpu.bindGroupLayout({ + camera: { uniform: CameraData }, + shadowDepthCube: { texture: d.textureDepthCube() }, + shadowSampler: { sampler: 'comparison' }, + lightPosition: { uniform: d.vec3f }, +}); + +const vertexDepth = tgpu['~unstable'].vertexFn({ + in: { ...VertexData.propTypes, ...InstanceData.propTypes }, + out: { pos: d.builtin.position, worldPos: d.vec3f }, +})(({ position, column1, column2, column3, column4 }) => { + const modelMatrix = d.mat4x4f(column1, column2, column3, column4); + const worldPos = modelMatrix.mul(d.vec4f(position, 1)).xyz; + const pos = renderLayout.$.camera.viewProjectionMatrix.mul( + d.vec4f(worldPos, 1), + ); + return { pos, worldPos }; +}); + +const fragmentDepth = tgpu['~unstable'].fragmentFn({ + in: { worldPos: d.vec3f }, + out: d.builtin.fragDepth, +})(({ worldPos }) => { + const dist = std.length(worldPos.sub(renderLayout.$.lightPosition)); + return dist / pointLight.far; +}); + +const vertexMain = tgpu['~unstable'].vertexFn({ + in: { ...VertexData.propTypes, ...InstanceData.propTypes }, + out: { + pos: d.builtin.position, + worldPos: d.vec3f, + uv: d.vec2f, + normal: d.vec3f, + }, +})(({ position, uv, normal, column1, column2, column3, column4 }) => { + const modelMatrix = d.mat4x4f(column1, column2, column3, column4); + const worldPos = modelMatrix.mul(d.vec4f(position, 1)).xyz; + const pos = renderLayoutWithShadow.$.camera.viewProjectionMatrix.mul( + d.vec4f(worldPos, 1), + ); + const worldNormal = std.normalize(modelMatrix.mul(d.vec4f(normal, 0)).xyz); + return { pos, worldPos, uv, normal: worldNormal }; +}); + +const shadowParams = root.createUniform( + d.struct({ + pcfSamples: d.u32, + diskRadius: d.f32, + normalBiasBase: d.f32, + normalBiasSlope: d.f32, + }), + { + pcfSamples: 32, + diskRadius: 0.01, + normalBiasBase: 0.027, + normalBiasSlope: 0.335, + }, +); + +const MAX_PCF_SAMPLES = 64; +const samplesUniform = root.createUniform( + d.arrayOf(d.vec4f, MAX_PCF_SAMPLES), + Array.from({ length: MAX_PCF_SAMPLES }, (_, i) => { + const index = i; + const theta = index * 2.3999632; // golden angle + const r = std.sqrt(index / d.f32(MAX_PCF_SAMPLES)); + return d.vec4f(d.vec2f(std.cos(theta) * r, std.sin(theta) * r), 0, 0); + }), +); + +const fragmentMain = tgpu['~unstable'].fragmentFn({ + in: { worldPos: d.vec3f, uv: d.vec2f, normal: d.vec3f }, + out: d.vec4f, +})(({ worldPos, normal }) => { + const lightPos = renderLayoutWithShadow.$.lightPosition; + const toLight = lightPos.sub(worldPos); + const dist = std.length(toLight); + const lightDir = toLight.div(dist); + const ndotl = std.max(std.dot(normal, lightDir), 0.0); + + const normalBiasWorld = shadowParams.$.normalBiasBase + + shadowParams.$.normalBiasSlope * (1.0 - ndotl); + const biasedPos = worldPos.add(normal.mul(normalBiasWorld)); + const toLightBiased = biasedPos.sub(lightPos); + const distBiased = std.length(toLightBiased); + const dir = toLightBiased.div(distBiased).mul(d.vec3f(-1, 1, 1)); + const depthRef = distBiased / pointLight.far; + + const up = std.select( + d.vec3f(1, 0, 0), + d.vec3f(0, 1, 0), + std.abs(dir.y) < d.f32(0.9999), + ); + const right = std.normalize(std.cross(up, dir)); + const realUp = std.cross(dir, right); + + const PCF_SAMPLES = shadowParams.$.pcfSamples; + const diskRadius = shadowParams.$.diskRadius; + + let visibilityAcc = 0.0; + for (let i = 0; i < PCF_SAMPLES; i++) { + const o = samplesUniform.$[i].xy.mul(diskRadius); + + const sampleDir = dir + .add(right.mul(o.x)) + .add(realUp.mul(o.y)); + + visibilityAcc += std.textureSampleCompare( + renderLayoutWithShadow.$.shadowDepthCube, + renderLayoutWithShadow.$.shadowSampler, + sampleDir, + depthRef, + ); + } + + const rawNdotl = std.dot(normal, lightDir); + const visibility = std.select( + visibilityAcc / d.f32(PCF_SAMPLES), + 0.0, + rawNdotl < 0.0, + ); + + const baseColor = d.vec3f(1.0, 0.5, 0.31); + const color = baseColor.mul(ndotl * visibility + 0.1); + return d.vec4f(color, 1.0); +}); + +const lightIndicatorLayout = tgpu.bindGroupLayout({ + camera: { uniform: CameraData }, + lightPosition: { uniform: d.vec3f }, +}); + +const vertexLightIndicator = tgpu['~unstable'].vertexFn({ + in: { position: d.vec3f }, + out: { pos: d.builtin.position }, +})(({ position }) => { + const worldPos = position.mul(0.15).add(lightIndicatorLayout.$.lightPosition); + const pos = lightIndicatorLayout.$.camera.viewProjectionMatrix.mul( + d.vec4f(worldPos, 1), + ); + return { pos }; +}); + +const fragmentLightIndicator = tgpu['~unstable'].fragmentFn({ + out: d.vec4f, +})(() => d.vec4f(1.0, 1.0, 0.5, 1.0)); + +const previewSampler = root['~unstable'].createSampler({ + minFilter: 'nearest', + magFilter: 'nearest', +}); +const previewView = pointLight.createDepthArrayView(); + +const depthToColor = tgpu.fn([d.f32], d.vec3f)((depth) => { + const linear = std.clamp(1 - depth * 6, 0, 1); + const t = linear * linear; + const r = std.clamp(t * 2 - 0.5, 0, 1); + const g = std.clamp(1 - std.abs(t - 0.5) * 2, 0, 0.9) * t; + const b = std.clamp(1 - t * 1.5, 0, 1) * t; + return d.vec3f(r, g, b); +}); + +const fragmentDistanceView = tgpu['~unstable'].fragmentFn({ + in: { worldPos: d.vec3f, uv: d.vec2f, normal: d.vec3f }, + out: d.vec4f, +})(({ worldPos }) => { + const lightPos = renderLayoutWithShadow.$.lightPosition; + const dist = std.length(worldPos.sub(lightPos)); + const color = depthToColor(dist / pointLight.far); + return d.vec4f(color, 1.0); +}); + +const previewFragment = tgpu['~unstable'].fragmentFn({ + in: { uv: d.vec2f }, + out: d.vec4f, +})(({ uv }) => { + const gridX = d.i32(std.floor(uv.x * 4)); + const gridY = d.i32(std.floor(uv.y * 3)); + + const localU = std.fract(uv.x * 4); + const localV = std.fract(uv.y * 3); + const localUV = d.vec2f(localU, localV); + + const bgColor = d.vec3f(0.1, 0.1, 0.12); + + let faceIndex = d.i32(-1); + + // Top row: +Y (index 2) + if (gridY === 0 && gridX === 1) { + faceIndex = 2; + } + // Middle row: -X, +Z, +X, -Z + if (gridY === 1) { + if (gridX === 0) { + faceIndex = 0; // -X + } + if (gridX === 1) { + faceIndex = 4; // +Z + } + if (gridX === 2) { + faceIndex = 1; // +X + } + if (gridX === 3) { + faceIndex = 5; // -Z + } + } + // Bottom row: -Y (index 3) + if (gridY === 2 && gridX === 1) { + faceIndex = 3; + } + + const depth = std.textureSample( + previewView.$, + previewSampler.$, + localUV, + faceIndex, + ); + + if (faceIndex < 0) { + return d.vec4f(bgColor, 1.0); + } + + const color = depthToColor(depth); + + const border = 0.02; + const isBorder = localU < border || localU > 1 - border || localV < border || + localV > 1 - border; + const finalColor = std.select(color, std.mul(0.5, color), isBorder); + + return d.vec4f(finalColor, 1.0); +}); + +const pipelineDepthOne = root['~unstable'] + .withVertex(vertexDepth, { ...vertexLayout.attrib, ...instanceLayout.attrib }) + .withFragment(fragmentDepth, {}) + .withDepthStencil({ + format: 'depth24plus', + depthWriteEnabled: true, + depthCompare: 'less', + }) + .createPipeline(); + +const pipelineMain = root['~unstable'] + .withVertex(vertexMain, { ...vertexLayout.attrib, ...instanceLayout.attrib }) + .withFragment(fragmentMain, { format: presentationFormat }) + .withDepthStencil({ + format: 'depth24plus', + depthWriteEnabled: true, + depthCompare: 'less', + }) + .withMultisample({ count: 4 }) + .createPipeline(); + +const pipelinePreview = root['~unstable'] + .withVertex(fullScreenTriangle, {}) + .withFragment(previewFragment, { format: presentationFormat }) + .createPipeline(); + +const pipelineLightIndicator = root['~unstable'] + .withVertex(vertexLightIndicator, vertexLayout.attrib) + .withFragment(fragmentLightIndicator, { format: presentationFormat }) + .withDepthStencil({ + format: 'depth24plus', + depthWriteEnabled: true, + depthCompare: 'less', + }) + .withMultisample({ count: 4 }) + .createPipeline(); + +const pipelineDistanceView = root['~unstable'] + .withVertex(vertexMain, { ...vertexLayout.attrib, ...instanceLayout.attrib }) + .withFragment(fragmentDistanceView, { format: presentationFormat }) + .withDepthStencil({ + format: 'depth24plus', + depthWriteEnabled: true, + depthCompare: 'less', + }) + .withMultisample({ count: 4 }) + .createPipeline(); + +const mainBindGroup = root.createBindGroup(renderLayoutWithShadow, { + camera: mainCamera.uniform.buffer, + shadowDepthCube: pointLight.createCubeView(), + shadowSampler, + lightPosition: pointLight.positionUniform.buffer, +}); + +const lightIndicatorBindGroup = root.createBindGroup(lightIndicatorLayout, { + camera: mainCamera.uniform.buffer, + lightPosition: pointLight.positionUniform.buffer, +}); + +let showDepthPreview = false; +let showDistanceView = false; +let lastTime = performance.now(); +let time = 0; + +function render(timestamp: number) { + const dt = (timestamp - lastTime) / 1000; + 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(); + pointLight.renderShadowMaps(pipelineDepthOne, renderLayout, scene); + + if (showDepthPreview) { + pipelinePreview + .withColorAttachment({ + view: context.getCurrentTexture().createView(), + loadOp: 'clear', + storeOp: 'store', + }) + .draw(3); + requestAnimationFrame(render); + return; + } + + const mainPipeline = showDistanceView ? pipelineDistanceView : pipelineMain; + + mainPipeline + .withDepthStencilAttachment({ + view: depthTexture, + depthClearValue: 1, + depthLoadOp: 'clear', + depthStoreOp: 'store', + }) + .withColorAttachment({ + resolveTarget: context.getCurrentTexture().createView(), + view: root.unwrap(msaaTexture).createView(), + loadOp: 'clear', + storeOp: 'store', + }) + .with(mainBindGroup) + .withIndexBuffer(BoxGeometry.indexBuffer) + .with(vertexLayout, BoxGeometry.vertexBuffer) + .with(instanceLayout, scene.instanceBuffer) + .drawIndexed(BoxGeometry.indexCount, scene.instanceCount); + + pipelineLightIndicator + .withDepthStencilAttachment({ + view: depthTexture, + depthLoadOp: 'load', + depthStoreOp: 'store', + }) + .withColorAttachment({ + resolveTarget: context.getCurrentTexture().createView(), + view: root.unwrap(msaaTexture).createView(), + loadOp: 'load', + storeOp: 'store', + }) + .with(lightIndicatorBindGroup) + .withIndexBuffer(BoxGeometry.indexBuffer) + .with(vertexLayout, BoxGeometry.vertexBuffer) + .drawIndexed(BoxGeometry.indexCount); + + requestAnimationFrame(render); +} +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), + ); + + depthTexture = root['~unstable'] + .createTexture({ + size: [canvas.width, canvas.height], + format: 'depth24plus', + sampleCount: 4, + }) + .$usage('render'); + msaaTexture = root['~unstable'] + .createTexture({ + size: [canvas.width, canvas.height], + format: presentationFormat, + sampleCount: 4, + }) + .$usage('render'); + } +}); +resizeObserver.observe(canvas); + +const initialCamPos = { x: 5, y: 5, z: -5 }; +let theta = Math.atan2(initialCamPos.z, initialCamPos.x); +let phi = Math.acos( + initialCamPos.y / + Math.sqrt( + initialCamPos.x ** 2 + initialCamPos.y ** 2 + initialCamPos.z ** 2, + ), +); +let radius = Math.sqrt( + initialCamPos.x ** 2 + initialCamPos.y ** 2 + initialCamPos.z ** 2, +); + +let isDragging = false; +let prevX = 0; +let prevY = 0; +let lastPinchDist = 0; + +function updateCameraPosition() { + mainCamera.position = d.vec3f( + radius * Math.sin(phi) * Math.cos(theta), + radius * Math.cos(phi), + radius * Math.sin(phi) * Math.sin(theta), + ); +} + +function updateCameraOrbit(dx: number, dy: number) { + theta += dx * 0.01; + phi = Math.max(0.1, Math.min(Math.PI - 0.1, phi - dy * 0.01)); + updateCameraPosition(); +} + +function zoomCamera(delta: number) { + radius = Math.max(1, Math.min(50, radius + delta)); + updateCameraPosition(); +} + +canvas.addEventListener('wheel', (e) => { + e.preventDefault(); + zoomCamera(e.deltaY * 0.01); +}, { passive: false }); + +canvas.addEventListener('mousedown', (e) => { + isDragging = true; + prevX = e.clientX; + prevY = e.clientY; +}); + +canvas.addEventListener('touchstart', (e) => { + e.preventDefault(); + if (e.touches.length === 1) { + isDragging = true; + prevX = e.touches[0].clientX; + prevY = e.touches[0].clientY; + } else if (e.touches.length === 2) { + isDragging = false; + const dx = e.touches[0].clientX - e.touches[1].clientX; + const dy = e.touches[0].clientY - e.touches[1].clientY; + lastPinchDist = Math.sqrt(dx * dx + dy * dy); + } +}, { passive: false }); + +const mouseUpEventListener = () => { + isDragging = false; +}; +window.addEventListener('mouseup', mouseUpEventListener); + +const touchEndEventListener = (e: TouchEvent) => { + if (e.touches.length === 1) { + isDragging = true; + prevX = e.touches[0].clientX; + prevY = e.touches[0].clientY; + } else { + isDragging = false; + } +}; +window.addEventListener('touchend', touchEndEventListener); + +const mouseMoveEventListener = (e: MouseEvent) => { + if (!isDragging) return; + const dx = e.clientX - prevX; + const dy = e.clientY - prevY; + prevX = e.clientX; + prevY = e.clientY; + updateCameraOrbit(dx, dy); +}; +window.addEventListener('mousemove', mouseMoveEventListener); + +const touchMoveEventListener = (e: TouchEvent) => { + if (e.touches.length === 1 && isDragging) { + e.preventDefault(); + const dx = e.touches[0].clientX - prevX; + const dy = e.touches[0].clientY - prevY; + prevX = e.touches[0].clientX; + prevY = e.touches[0].clientY; + updateCameraOrbit(dx, dy); + } +}; +window.addEventListener('touchmove', touchMoveEventListener, { + passive: false, +}); + +canvas.addEventListener('touchmove', (e) => { + if (e.touches.length === 2) { + e.preventDefault(); + const dx = e.touches[0].clientX - e.touches[1].clientX; + const dy = e.touches[0].clientY - e.touches[1].clientY; + const pinchDist = Math.sqrt(dx * dx + dy * dy); + zoomCamera((lastPinchDist - pinchDist) * 0.05); + lastPinchDist = pinchDist; + } +}, { passive: false }); + +// #region Example controls and cleanup + +export const controls = { + 'Light X': { + initial: 4.5, + min: -10, + max: 10, + step: 0.1, + onSliderChange: (v: number) => { + pointLight.position = d.vec3f( + v, + pointLight.position.y, + pointLight.position.z, + ); + }, + }, + 'Light Y': { + initial: 1, + min: 0.5, + max: 10, + step: 0.1, + onSliderChange: (v: number) => { + pointLight.position = d.vec3f( + pointLight.position.x, + v, + pointLight.position.z, + ); + }, + }, + 'Light Z': { + initial: 4, + min: -10, + max: 10, + step: 0.1, + onSliderChange: (v: number) => { + pointLight.position = d.vec3f( + pointLight.position.x, + pointLight.position.y, + v, + ); + }, + }, + 'Show Depth Cubemap': { + initial: false, + onToggleChange: (v: boolean) => { + showDepthPreview = v; + }, + }, + 'Show Distance View': { + initial: false, + onToggleChange: (v: boolean) => { + showDistanceView = v; + }, + }, + 'PCF Samples': { + initial: 16, + min: 1, + max: 64, + step: 1, + onSliderChange: (v: number) => { + shadowParams.writePartial({ pcfSamples: v }); + }, + }, + 'PCF Disk Radius': { + initial: 0.01, + min: 0.0, + max: 0.1, + step: 0.001, + onSliderChange: (v: number) => { + shadowParams.writePartial({ diskRadius: v }); + }, + }, + 'Normal Bias Base': { + initial: 0.027, + min: 0.0, + max: 0.1, + step: 0.0001, + onSliderChange: (v: number) => { + shadowParams.writePartial({ normalBiasBase: v }); + }, + }, + 'Normal Bias Slope': { + initial: 0.335, + min: 0.0, + max: 0.5, + step: 0.0005, + onSliderChange: (v: number) => { + shadowParams.writePartial({ normalBiasSlope: v }); + }, + }, +}; + +export function onCleanup() { + BoxGeometry.clearBuffers(); + window.removeEventListener('mouseup', mouseUpEventListener); + window.removeEventListener('mousemove', mouseMoveEventListener); + window.removeEventListener('touchend', touchEndEventListener); + window.removeEventListener('touchmove', touchMoveEventListener); + resizeObserver.disconnect(); + root.destroy(); +} + +// #endregion diff --git a/apps/typegpu-docs/src/examples/rendering/point-light-shadow/meta.json b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/meta.json new file mode 100644 index 0000000000..ff8ffe4f12 --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Point Light Shadow", + "category": "rendering", + "tags": ["3d"] +} diff --git a/apps/typegpu-docs/src/examples/rendering/point-light-shadow/point-light.ts b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/point-light.ts new file mode 100644 index 0000000000..f9b05a450b --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/point-light.ts @@ -0,0 +1,124 @@ +import type { + TgpuBindGroup, + TgpuBindGroupLayout, + TgpuRenderPipeline, + TgpuRoot, +} from 'typegpu'; +import * as d from 'typegpu/data'; +import { BoxGeometry } from './box-geometry.ts'; +import { Camera } from './camera.ts'; +import type { Scene } from './scene.ts'; +import { instanceLayout, vertexLayout } from './types.ts'; + +const FACE_CONFIGS = [ + { name: 'right', dir: d.vec3f(-1, 0, 0), up: d.vec3f(0, 1, 0) }, + { name: 'left', dir: d.vec3f(1, 0, 0), up: d.vec3f(0, 1, 0) }, + { name: 'up', dir: d.vec3f(0, 1, 0), up: d.vec3f(0, 0, -1) }, + { name: 'down', dir: d.vec3f(0, -1, 0), up: d.vec3f(0, 0, 1) }, + { name: 'forward', dir: d.vec3f(0, 0, 1), up: d.vec3f(0, 1, 0) }, + { name: 'backward', dir: d.vec3f(0, 0, -1), up: d.vec3f(0, 1, 0) }, +] as const; + +export class PointLight { + readonly far: number; + readonly #root: TgpuRoot; + readonly #positionUniform; + readonly #depthCubeTexture; + readonly #shadowCameras: Camera[]; + readonly #bindGroups: TgpuBindGroup[] = []; + + #position: d.v3f; + + constructor( + root: TgpuRoot, + position: d.v3f, + options: { far?: number; shadowMapSize?: number } = {}, + ) { + this.#root = root; + this.#position = position; + this.far = options.far ?? 100.0; + const shadowMapSize = options.shadowMapSize ?? 512; + + this.#depthCubeTexture = root['~unstable'] + .createTexture({ + size: [shadowMapSize, shadowMapSize, 6], + dimension: '2d', + format: 'depth24plus', + }) + .$usage('render', 'sampled'); + + this.#positionUniform = root.createUniform(d.vec3f, position); + this.#shadowCameras = FACE_CONFIGS.map( + () => new Camera(root, 90, 0.1, this.far), + ); + this.#configureCameras(); + } + + #configureCameras() { + FACE_CONFIGS.forEach((config, i) => { + const camera = this.#shadowCameras[i]; + camera.position = this.#position; + camera.target = this.#position.add(config.dir); + camera.up = config.up; + }); + } + + set position(pos: d.v3f) { + this.#position = pos; + this.#positionUniform.write(pos); + this.#configureCameras(); + } + + get position() { + return this.#position; + } + + get positionUniform() { + return this.#positionUniform; + } + + createCubeView() { + return this.#depthCubeTexture.createView(d.textureDepthCube()); + } + + createDepthArrayView() { + return this.#depthCubeTexture.createView(d.textureDepth2dArray(), { + baseArrayLayer: 0, + arrayLayerCount: 6, + aspect: 'depth-only', + }); + } + + renderShadowMaps( + pipeline: TgpuRenderPipeline, + bindGroupLayout: TgpuBindGroupLayout, + scene: Scene, + ) { + this.#shadowCameras.forEach((camera, i) => { + if (!this.#bindGroups[i]) { + this.#bindGroups[i] = this.#root.createBindGroup(bindGroupLayout, { + camera: camera.uniform.buffer, + lightPosition: this.#positionUniform.buffer, + }); + } + + const view = this.#depthCubeTexture.createView(d.textureDepth2d(), { + baseArrayLayer: i, + arrayLayerCount: 1, + }); + + pipeline + .withDepthStencilAttachment({ + view: this.#root.unwrap(view), + depthClearValue: 1, + depthLoadOp: 'clear', + depthStoreOp: 'store', + }) + .with(vertexLayout, BoxGeometry.vertexBuffer) + .with(instanceLayout, scene.instanceBuffer) + .with(this.#bindGroups[i]) + .withIndexBuffer(BoxGeometry.indexBuffer) + .drawIndexed(BoxGeometry.indexCount, scene.instanceCount); + }); + } +} diff --git a/apps/typegpu-docs/src/examples/rendering/point-light-shadow/scene.ts b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/scene.ts new file mode 100644 index 0000000000..f8e88ea7c2 --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/scene.ts @@ -0,0 +1,59 @@ +import type { TgpuRoot } from 'typegpu'; +import * as d from 'typegpu/data'; +import type { BoxGeometry } from './box-geometry.ts'; +import { InstanceData } from './types.ts'; + +export class Scene { + readonly #root: TgpuRoot; + readonly #objects: BoxGeometry[] = []; + + #instanceBuffer; + + constructor(root: TgpuRoot) { + this.#root = root; + this.#instanceBuffer = root + .createBuffer(d.arrayOf(InstanceData, 0), []) + .$usage('vertex'); + } + + add(object: BoxGeometry | BoxGeometry[]) { + const items = Array.isArray(object) ? object : [object]; + if (items.length === 0) { + return; + } + this.#objects.push(...items); + this.#rebuildBuffer(); + } + + remove(object: BoxGeometry | BoxGeometry[]) { + const items = Array.isArray(object) ? object : [object]; + if (items.length === 0) { + return; + } + this.#objects.splice( + 0, + this.#objects.length, + ...this.#objects.filter((obj) => !items.includes(obj)), + ); + this.#rebuildBuffer(); + } + + update() { + this.#instanceBuffer.write(this.#objects.map((obj) => obj.instanceData)); + } + + #rebuildBuffer() { + const data = this.#objects.map((obj) => obj.instanceData); + this.#instanceBuffer = this.#root + .createBuffer(d.arrayOf(InstanceData, data.length), data) + .$usage('vertex'); + } + + get instanceBuffer() { + return this.#instanceBuffer; + } + + get instanceCount() { + return this.#objects.length; + } +} diff --git a/apps/typegpu-docs/src/examples/rendering/point-light-shadow/thumbnail.png b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/thumbnail.png new file mode 100644 index 0000000000..a9a51b654f Binary files /dev/null and b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/thumbnail.png differ diff --git a/apps/typegpu-docs/src/examples/rendering/point-light-shadow/types.ts b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/types.ts new file mode 100644 index 0000000000..efd1f86637 --- /dev/null +++ b/apps/typegpu-docs/src/examples/rendering/point-light-shadow/types.ts @@ -0,0 +1,31 @@ +import tgpu from 'typegpu'; +import * as d from 'typegpu/data'; + +export const CameraData = d.struct({ + viewProjectionMatrix: d.mat4x4f, + inverseViewProjectionMatrix: d.mat4x4f, +}); +export type CameraData = typeof CameraData; + +export const VertexData = d.struct({ + position: d.vec3f, + normal: d.vec3f, + uv: d.vec2f, +}); +export type VertexData = typeof VertexData; + +export const InstanceData = d.struct({ + column1: d.vec4f, + column2: d.vec4f, + column3: d.vec4f, + column4: d.vec4f, +}); +export type InstanceData = typeof InstanceData; + +export const vertexLayout = tgpu.vertexLayout(d.arrayOf(VertexData)); +export const instanceLayout = tgpu.vertexLayout( + d.arrayOf(InstanceData), + 'instance', +); + +export type GeometryData = d.Infer[]; 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 50a11ece63..9209e7c2fd 100644 --- a/apps/typegpu-docs/src/examples/rendering/two-boxes/index.html +++ b/apps/typegpu-docs/src/examples/rendering/two-boxes/index.html @@ -24,9 +24,7 @@

Controls

Left Mouse Button / Drag: Orbit camera

-

- Right Mouse Button / Two-Finger Drag: Rotate cubes -

-

Scroll: Zoom

+

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 6f8d25b940..8495f023c8 100644 --- a/apps/typegpu-docs/src/examples/rendering/two-boxes/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/two-boxes/index.ts @@ -319,6 +319,7 @@ let isRightDragging = false; let isDragging = false; let prevX = 0; let prevY = 0; +let lastPinchDist = 0; let orbitRadius = Math.sqrt( cameraInitialPos.x * cameraInitialPos.x + cameraInitialPos.y * cameraInitialPos.y + @@ -355,16 +356,7 @@ function updateCubesRotation(dx: number, dy: number) { secondTransformBuffer.write({ model: cube2Transform }); } -function updateCameraOrbit(dx: number, dy: number) { - const orbitSensitivity = 0.005; - orbitYaw += -dx * orbitSensitivity; - orbitPitch += dy * orbitSensitivity; - // if we didn't limit pitch, it would lead to flipping the camera which is disorienting. - const maxPitch = Math.PI / 2 - 0.01; - if (orbitPitch > maxPitch) orbitPitch = maxPitch; - if (orbitPitch < -maxPitch) orbitPitch = -maxPitch; - // basically converting spherical coordinates to cartesian. - // like sampling points on a unit sphere and then scaling them by the radius. +function updateCameraPosition() { const newCamX = orbitRadius * Math.sin(orbitYaw) * Math.cos(orbitPitch); const newCamY = orbitRadius * Math.sin(orbitPitch); const newCamZ = orbitRadius * Math.cos(orbitYaw) * Math.cos(orbitPitch); @@ -379,6 +371,20 @@ function updateCameraOrbit(dx: number, dy: number) { cameraBuffer.write({ view: newView, projection: cameraInitial.projection }); } +function updateCameraOrbit(dx: number, dy: number) { + orbitYaw += -dx * 0.005; + orbitPitch = Math.max( + -Math.PI / 2 + 0.01, + Math.min(Math.PI / 2 - 0.01, orbitPitch + dy * 0.005), + ); + updateCameraPosition(); +} + +function zoomCamera(delta: number) { + orbitRadius = Math.max(1, orbitRadius + delta); + updateCameraPosition(); +} + // Prevent the context menu from appearing on right click. canvas.addEventListener('contextmenu', (event) => { event.preventDefault(); @@ -398,46 +404,51 @@ canvas.addEventListener('touchend', () => { helpInfo.style.opacity = '1'; }); -canvas.addEventListener('wheel', (event: WheelEvent) => { - event.preventDefault(); - const zoomSensitivity = 0.05; - orbitRadius = Math.max(1, orbitRadius + event.deltaY * zoomSensitivity); - const newCamX = orbitRadius * Math.sin(orbitYaw) * Math.cos(orbitPitch); - const newCamY = orbitRadius * Math.sin(orbitPitch); - const newCamZ = orbitRadius * Math.cos(orbitYaw) * Math.cos(orbitPitch); - const newCameraPos = d.vec4f(newCamX, newCamY, newCamZ, 1); - const newView = m.mat4.lookAt( - newCameraPos, - target, - d.vec3f(0, 1, 0), - d.mat4x4f(), - ); - cameraBuffer.writePartial({ view: newView }); +canvas.addEventListener('wheel', (e: WheelEvent) => { + e.preventDefault(); + zoomCamera(e.deltaY * 0.05); }, { passive: false }); -canvas.addEventListener('mousedown', (event) => { - if (event.button === 0) { - // Left Mouse Button controls Camera Orbit. +canvas.addEventListener('mousedown', (e) => { + if (e.button === 0) { isDragging = true; - } else if (event.button === 2) { - // Right Mouse Button controls Cube Rotation. + } else if (e.button === 2) { isRightDragging = true; } - prevX = event.clientX; - prevY = event.clientY; + prevX = e.clientX; + prevY = e.clientY; }); +canvas.addEventListener('touchstart', (e) => { + e.preventDefault(); + if (e.touches.length === 1) { + isDragging = true; + prevX = e.touches[0].clientX; + prevY = e.touches[0].clientY; + } else if (e.touches.length === 2) { + isDragging = false; + const dx = e.touches[0].clientX - e.touches[1].clientX; + const dy = e.touches[0].clientY - e.touches[1].clientY; + lastPinchDist = Math.sqrt(dx * dx + dy * dy); + } +}, { passive: false }); + const mouseUpEventListener = () => { isRightDragging = false; isDragging = false; }; window.addEventListener('mouseup', mouseUpEventListener); -canvas.addEventListener('mousemove', (event) => { - const dx = event.clientX - prevX; - const dy = event.clientY - prevY; - prevX = event.clientX; - prevY = event.clientY; +const touchEndEventListener = () => { + isDragging = false; +}; +window.addEventListener('touchend', touchEndEventListener); + +const mouseMoveEventListener = (e: MouseEvent) => { + const dx = e.clientX - prevX; + const dy = e.clientY - prevY; + prevX = e.clientX; + prevY = e.clientY; if (isDragging) { updateCameraOrbit(dx, dy); @@ -445,47 +456,33 @@ canvas.addEventListener('mousemove', (event) => { if (isRightDragging) { updateCubesRotation(dx, dy); } -}); - -// Mobile touch support. -canvas.addEventListener('touchstart', (event: TouchEvent) => { - event.preventDefault(); - if (event.touches.length === 1) { - // Single touch controls Camera Orbit. - isDragging = true; - } else if (event.touches.length === 2) { - // Two-finger touch controls Cube Rotation. - isRightDragging = true; - } - // Use the first touch for rotation. - prevX = event.touches[0].clientX; - prevY = event.touches[0].clientY; -}, { passive: false }); - -canvas.addEventListener('touchmove', (event: TouchEvent) => { - event.preventDefault(); - const touch = event.touches[0]; - const dx = touch.clientX - prevX; - const dy = touch.clientY - prevY; - prevX = touch.clientX; - prevY = touch.clientY; - - if (isDragging && event.touches.length === 1) { +}; +window.addEventListener('mousemove', mouseMoveEventListener); + +const touchMoveEventListener = (e: TouchEvent) => { + if (e.touches.length === 1 && isDragging) { + e.preventDefault(); + const dx = e.touches[0].clientX - prevX; + const dy = e.touches[0].clientY - prevY; + prevX = e.touches[0].clientX; + prevY = e.touches[0].clientY; updateCameraOrbit(dx, dy); } - if (isRightDragging && event.touches.length === 2) { - updateCubesRotation(dx, dy); - } -}, { passive: false }); +}; +window.addEventListener('touchmove', touchMoveEventListener, { + passive: false, +}); -const touchEndEventListener = (event: TouchEvent) => { - event.preventDefault(); - if (event.touches.length === 0) { - isRightDragging = false; - isDragging = false; +canvas.addEventListener('touchmove', (e) => { + if (e.touches.length === 2) { + e.preventDefault(); + const dx = e.touches[0].clientX - e.touches[1].clientX; + const dy = e.touches[0].clientY - e.touches[1].clientY; + const pinchDist = Math.sqrt(dx * dx + dy * dy); + zoomCamera((lastPinchDist - pinchDist) * 0.05); + lastPinchDist = pinchDist; } -}; -window.addEventListener('touchend', touchEndEventListener); +}, { passive: false }); const resizeObserver = new ResizeObserver(() => { createDepthAndMsaaTextures(); @@ -495,7 +492,9 @@ resizeObserver.observe(canvas); export function onCleanup() { disposed = true; window.removeEventListener('mouseup', mouseUpEventListener); + window.removeEventListener('mousemove', mouseMoveEventListener); window.removeEventListener('touchend', touchEndEventListener); + window.removeEventListener('touchmove', touchMoveEventListener); resizeObserver.disconnect(); 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 2a6e2cc004..995c5461a4 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 @@ -365,8 +365,8 @@ const rayBoxIntersection = ( const t1 = boxMax.sub(rayOrigin).mul(invDir); const tmin = std.min(t0, t1); const tmax = std.max(t0, t1); - const tNear = std.max(std.max(tmin.x, tmin.y), tmin.z); - const tFar = std.min(std.min(tmax.x, tmax.y), tmax.z); + const tNear = std.max(tmin.x, tmin.y, tmin.z); + const tFar = std.min(tmax.x, tmax.y, tmax.z); const hit = tFar >= tNear && tFar >= 0; return RayBoxResult({ tNear, tFar, hit }); }; 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 70af959fe5..61ddfcfdb8 100644 --- a/apps/typegpu-docs/src/examples/simulation/slime-mold/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/slime-mold/index.ts @@ -60,7 +60,7 @@ root['~unstable'].createGuardedComputePipeline((x) => { const params = root.createUniform(Params, defaultParams); const deltaTime = root.createUniform(d.f32, 0.016); -const textures = [0, 1].map((i) => +const textures = [0, 1].map(() => root['~unstable'] .createTexture({ size: [resolution.x, resolution.y], diff --git a/apps/typegpu-docs/src/examples/tests/copy-error/index.ts b/apps/typegpu-docs/src/examples/tests/copy-error/index.ts index ad99fd7d89..e45ea63b35 100644 --- a/apps/typegpu-docs/src/examples/tests/copy-error/index.ts +++ b/apps/typegpu-docs/src/examples/tests/copy-error/index.ts @@ -1,7 +1,3 @@ -// irrelevant import so the file becomes a module -import tgpu from 'typegpu'; -const t = tgpu; - // setup const adapter = await navigator.gpu?.requestAdapter(); const device = await adapter?.requestDevice(); diff --git a/apps/typegpu-docs/src/examples/tests/dispatch/index.ts b/apps/typegpu-docs/src/examples/tests/dispatch/index.ts index 7e3bad6ae7..f4e993bd57 100644 --- a/apps/typegpu-docs/src/examples/tests/dispatch/index.ts +++ b/apps/typegpu-docs/src/examples/tests/dispatch/index.ts @@ -69,7 +69,7 @@ async function test3d(): Promise { async function testWorkgroupSize(): Promise { const mutable = root.createMutable(d.atomic(d.u32)); - root['~unstable'].createGuardedComputePipeline((x, y, z) => { + root['~unstable'].createGuardedComputePipeline((_x, _y, _z) => { 'use gpu'; std.atomicAdd(mutable.$, 1); }).dispatchThreads(4, 3, 2); diff --git a/apps/typegpu-docs/src/examples/threejs/compute-cloth/index.ts b/apps/typegpu-docs/src/examples/threejs/compute-cloth/index.ts index ffee9ac3a7..5cb48a58d4 100644 --- a/apps/typegpu-docs/src/examples/threejs/compute-cloth/index.ts +++ b/apps/typegpu-docs/src/examples/threejs/compute-cloth/index.ts @@ -339,3 +339,7 @@ async function render() { await renderer.renderAsync(scene, camera); } + +export function onCleanup() { + renderer.dispose(); +} diff --git a/apps/typegpu-docs/src/examples/threejs/compute-cloth/verlet.ts b/apps/typegpu-docs/src/examples/threejs/compute-cloth/verlet.ts index 456f2b0eef..3d56797afe 100644 --- a/apps/typegpu-docs/src/examples/threejs/compute-cloth/verlet.ts +++ b/apps/typegpu-docs/src/examples/threejs/compute-cloth/verlet.ts @@ -67,8 +67,8 @@ export class VerletSimulation { THREE.StorageBufferNode >; - readonly computeSpringForces: TSL.ShaderNodeObject; - readonly computeVertexForces: TSL.ShaderNodeObject; + readonly computeSpringForces: THREE.TSL.NodeObject; + readonly computeVertexForces: THREE.TSL.NodeObject; constructor( { sphereRadius, sphereUniform, spherePositionUniform }: diff --git a/apps/typegpu-docs/src/utils/examples/sandboxModules.ts b/apps/typegpu-docs/src/utils/examples/sandboxModules.ts index 791261dc69..a87d10e72c 100644 --- a/apps/typegpu-docs/src/utils/examples/sandboxModules.ts +++ b/apps/typegpu-docs/src/utils/examples/sandboxModules.ts @@ -124,13 +124,13 @@ export const SANDBOX_MODULES: Record = { // Three.js, for examples of @typegpu/three 'three': { - typeDef: { reroute: ['@types/three/build/three.module.d.ts'] }, + typeDef: { reroute: '@types/three/build/three.module.d.ts' }, }, 'three/webgpu': { - typeDef: { reroute: ['@types/three/build/three.webgpu.d.ts'] }, + typeDef: { reroute: '@types/three/build/three.webgpu.d.ts' }, }, 'three/tsl': { - typeDef: { reroute: ['@types/three/build/three.tsl.d.ts'] }, + typeDef: { reroute: '@types/three/build/three.tsl.d.ts' }, }, // Utility modules @@ -143,6 +143,6 @@ export const SANDBOX_MODULES: Record = { typeDef: { reroute: 'typegpu-color/src/index.ts' }, }, '@typegpu/three': { - typeDef: { reroute: ['typegpu-three/src/index.ts'] }, + typeDef: { reroute: 'typegpu-three/src/index.ts' }, }, }; diff --git a/packages/tinyest-for-wgsl/src/parsers.ts b/packages/tinyest-for-wgsl/src/parsers.ts index 81fcd3688e..8ec4fe9933 100644 --- a/packages/tinyest-for-wgsl/src/parsers.ts +++ b/packages/tinyest-for-wgsl/src/parsers.ts @@ -144,10 +144,7 @@ const Transpilers: Partial< }, ThisExpression(ctx) { - if (ctx.ignoreExternalDepth === 0) { - ctx.externalNames.add('this'); - } - + ctx.externalNames.add('this'); return 'this'; }, diff --git a/packages/typegpu-three/README.md b/packages/typegpu-three/README.md index bdeade36c1..093aac81ed 100644 --- a/packages/typegpu-three/README.md +++ b/packages/typegpu-three/README.md @@ -20,7 +20,7 @@ import tgpu3, { uv } from '@typegpu/three'; const material = new THREE.MeshBasicNodeMaterial(); // We reexport builtin TSL nodes as `accessors` material.colorNode = toTSL(() => { - 'kernel'; + 'use gpu'; return fract(uv.$.mul(4)); }); @@ -28,7 +28,7 @@ material.colorNode = toTSL(() => { const pattern = TSL.texture(detailMap, TSL.uv().mul(10)); const patternAccess = fromTSL(pattern, { type: d.vec4f }); material.colorNode = toTSL(() => { - 'kernel'; + 'use gpu'; return patternAccess.$; }); ``` diff --git a/packages/typegpu-three/src/index.ts b/packages/typegpu-three/src/index.ts index 907684ba31..cfdfdcad7b 100644 --- a/packages/typegpu-three/src/index.ts +++ b/packages/typegpu-three/src/index.ts @@ -1,4 +1,4 @@ // export { TypeGPUMaterial } from './typegpu-material.ts'; export { time, uv } from './builtin-accessors.ts'; -export { fromTSL, type TSLAccessor, toTSL } from './typegpu-node.ts'; +export { fromTSL, toTSL, type TSLAccessor } from './typegpu-node.ts'; diff --git a/packages/typegpu-three/src/typegpu-material.ts b/packages/typegpu-three/src/typegpu-material.ts index 898f81c7c2..f350ff5e8d 100644 --- a/packages/typegpu-three/src/typegpu-material.ts +++ b/packages/typegpu-three/src/typegpu-material.ts @@ -4,12 +4,12 @@ import tgpu, { type TgpuFn } from 'typegpu'; class FragmentNode extends THREE.CodeNode { private tgslFn: TgpuFn; private functionName: string | null | undefined; - private threeVars: THREE.TSL.ShaderNodeObject[] | undefined; + private threeVars: THREE.TSL.NodeObject[] | undefined; private argNames: string[] | undefined; constructor( tgslFn: TgpuFn, - threeRequirements?: THREE.TSL.ShaderNodeObject[] | undefined, + threeRequirements?: THREE.TSL.NodeObject[] | undefined, ) { const resolved = tgpu.resolve({ template: '___ID___ fnName', @@ -96,14 +96,12 @@ class FragmentNode extends THREE.CodeNode { export class TypeGPUMaterial extends THREE.NodeMaterial { constructor( fragmentFn: TgpuFn, - threeRequirements?: THREE.TSL.ShaderNodeObject< - // biome-ignore lint/suspicious/noExplicitAny: + threeRequirements?: THREE.TSL.NodeObject< THREE.Node | THREE.UniformNode >[] | undefined, ) { super(); - // @ts-expect-error this.fragmentNode = new FragmentNode(fragmentFn, threeRequirements); } } diff --git a/packages/typegpu-three/src/typegpu-node.ts b/packages/typegpu-three/src/typegpu-node.ts index 1d2aacbadb..bd82bd21a6 100644 --- a/packages/typegpu-three/src/typegpu-node.ts +++ b/packages/typegpu-three/src/typegpu-node.ts @@ -197,7 +197,7 @@ class TgpuFnNode extends THREE.Node { export function toTSL( fn: () => unknown, -): THREE.TSL.ShaderNodeObject { +): THREE.TSL.NodeObject { return TSL.nodeObject(new TgpuFnNode(fn)); } @@ -205,10 +205,10 @@ export class TSLAccessor { readonly #dataType: T; readonly var: TgpuVar<'private', T> | undefined; - readonly node: TSL.ShaderNodeObject; + readonly node: THREE.TSL.NodeObject; constructor( - node: TSL.ShaderNodeObject, + node: THREE.TSL.NodeObject, dataType: T, ) { this.node = node; @@ -243,15 +243,15 @@ export class TSLAccessor { } export function fromTSL( - node: TSL.ShaderNodeObject, + node: THREE.TSL.NodeObject, options: { type: (length: number) => T }, ): TSLAccessor; export function fromTSL( - node: TSL.ShaderNodeObject, + node: THREE.TSL.NodeObject, options: { type: T }, ): TSLAccessor; export function fromTSL( - node: TSL.ShaderNodeObject, + node: THREE.TSL.NodeObject, options: { type: T } | { type: (length: number) => T }, ): TSLAccessor { return new TSLAccessor( diff --git a/packages/typegpu/src/core/function/fnCore.ts b/packages/typegpu/src/core/function/fnCore.ts index f409e0a4a2..8eb9ceeee6 100644 --- a/packages/typegpu/src/core/function/fnCore.ts +++ b/packages/typegpu/src/core/function/fnCore.ts @@ -142,6 +142,8 @@ export function createFnCore( // get data generated by the plugin const pluginData = getMetaData(implementation); + // Passing a record happens prior to version 0.9.0 + // TODO: Support for this can be removed down the line const pluginExternals = typeof pluginData?.externals === 'function' ? pluginData.externals() : pluginData?.externals; diff --git a/packages/typegpu/src/core/function/ioSchema.ts b/packages/typegpu/src/core/function/ioSchema.ts index b39fedbabd..eeb1b77a4d 100644 --- a/packages/typegpu/src/core/function/ioSchema.ts +++ b/packages/typegpu/src/core/function/ioSchema.ts @@ -75,6 +75,8 @@ export function createIoSchema< return ( isData(layout) ? isVoid(layout) + ? layout + : isBuiltin(layout) ? layout : getCustomLocation(layout) !== undefined ? layout diff --git a/packages/typegpu/src/core/function/tgpuFragmentFn.ts b/packages/typegpu/src/core/function/tgpuFragmentFn.ts index 3c1c5b47c7..76e97bb85b 100644 --- a/packages/typegpu/src/core/function/tgpuFragmentFn.ts +++ b/packages/typegpu/src/core/function/tgpuFragmentFn.ts @@ -77,7 +77,7 @@ export type TgpuFragmentFnShell< input: InferIO, out: FragmentOut extends IORecord ? WgslStruct : FragmentOut, ) => InferIO, - ) => TgpuFragmentFn, OmitBuiltins>) + ) => TgpuFragmentFn, FragmentOut>) & /** * @param implementation * Raw WGSL function implementation with header and body @@ -85,11 +85,11 @@ export type TgpuFragmentFnShell< * e.g. `"(x: f32) -> f32 { return x; }"`; */ (( implementation: string, - ) => TgpuFragmentFn, OmitBuiltins>) + ) => TgpuFragmentFn, FragmentOut>) & (( strings: TemplateStringsArray, ...values: unknown[] - ) => TgpuFragmentFn, OmitBuiltins>) + ) => TgpuFragmentFn, FragmentOut>) & { /** * @deprecated Invoke the shell as a function instead. @@ -97,7 +97,7 @@ export type TgpuFragmentFnShell< does: & (( implementation: (input: InferIO) => InferIO, - ) => TgpuFragmentFn, OmitBuiltins>) + ) => TgpuFragmentFn, FragmentOut>) & /** * @param implementation * Raw WGSL function implementation with header and body @@ -105,7 +105,7 @@ export type TgpuFragmentFnShell< * e.g. `"(x: f32) -> f32 { return x; }"`; */ (( implementation: string, - ) => TgpuFragmentFn, OmitBuiltins>); + ) => TgpuFragmentFn, FragmentOut>); }; export interface TgpuFragmentFn< diff --git a/packages/typegpu/src/core/pipeline/connectAttachmentToShader.ts b/packages/typegpu/src/core/pipeline/connectAttachmentToShader.ts index accb0616cb..eb171cb7d1 100644 --- a/packages/typegpu/src/core/pipeline/connectAttachmentToShader.ts +++ b/packages/typegpu/src/core/pipeline/connectAttachmentToShader.ts @@ -17,6 +17,9 @@ export function connectAttachmentToShader( attachment: AnyFragmentColorAttachment, ): ColorAttachment[] { if (isData(shaderOutputLayout)) { + if (isBuiltin(shaderOutputLayout)) { + return []; + } if (!isColorAttachment(attachment)) { throw new Error('Expected a single color attachment, not a record.'); } diff --git a/packages/typegpu/src/core/pipeline/connectTargetsToShader.ts b/packages/typegpu/src/core/pipeline/connectTargetsToShader.ts index 4c4d8b3426..099d416122 100644 --- a/packages/typegpu/src/core/pipeline/connectTargetsToShader.ts +++ b/packages/typegpu/src/core/pipeline/connectTargetsToShader.ts @@ -18,6 +18,9 @@ export function connectTargetsToShader( if (isVoid(shaderOutputLayout)) { return []; } + if (shaderOutputLayout.type === 'decorated') { + return []; + } if (!isColorTargetState(targets)) { throw new Error( diff --git a/packages/typegpu/src/core/pipeline/renderPipeline.ts b/packages/typegpu/src/core/pipeline/renderPipeline.ts index e02b5b8448..945a012c1d 100644 --- a/packages/typegpu/src/core/pipeline/renderPipeline.ts +++ b/packages/typegpu/src/core/pipeline/renderPipeline.ts @@ -11,6 +11,7 @@ import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; import type { WgslTexture } from '../../data/texture.ts'; import { type AnyWgslData, + type Decorated, isWgslData, type U16, type U32, @@ -141,15 +142,23 @@ export interface TgpuRenderPipeline } export type FragmentOutToTargets = T extends IOData - ? GPUColorTargetState - : T extends Record - ? { [Key in keyof T]: GPUColorTargetState } + ? T extends Decorated ? Record + : GPUColorTargetState + : T extends Record ? { + [Key in keyof T as T[Key] extends Decorated ? never : Key]: + GPUColorTargetState; + } : T extends { type: 'void' } ? Record : never; export type FragmentOutToColorAttachment = T extends IOData - ? ColorAttachment - : T extends Record ? { [Key in keyof T]: ColorAttachment } + ? T extends Decorated ? Record + : ColorAttachment + : T extends Record ? { + [Key in keyof T as T[Key] extends Decorated ? never : Key]: + ColorAttachment; + } + : T extends { type: 'void' } ? Record : never; export type AnyFragmentTargets = @@ -203,7 +212,16 @@ export interface DepthStencilAttachment { * A {@link GPUTextureView} | ({@link TgpuTexture} & {@link RenderFlag}) describing the texture subresource that will be output to * and read from for this depth/stencil attachment. */ - view: (TgpuTexture & RenderFlag) | GPUTextureView; + view: + | ( + & TgpuTexture<{ + size: [number, number]; + format: 'depth24plus' | 'depth24plus-stencil8' | 'depth32float'; + sampleCount?: number; + }> + & RenderFlag + ) + | GPUTextureView; /** * Indicates the value to clear {@link GPURenderPassDepthStencilAttachment#view}'s depth component * to prior to executing the render pass. Ignored if {@link GPURenderPassDepthStencilAttachment#depthLoadOp} diff --git a/packages/typegpu/src/core/rawCodeSnippet/tgpuRawCodeSnippet.ts b/packages/typegpu/src/core/rawCodeSnippet/tgpuRawCodeSnippet.ts index b9a6fddfb5..42077e3c7d 100644 --- a/packages/typegpu/src/core/rawCodeSnippet/tgpuRawCodeSnippet.ts +++ b/packages/typegpu/src/core/rawCodeSnippet/tgpuRawCodeSnippet.ts @@ -21,7 +21,7 @@ import { valueProxyHandler } from '../valueProxyUtils.ts'; // ---------- /** - * Extra declaration that shall be included in final WGSL code, + * Extra declaration that will be included in final WGSL code * when resolving objects that use it. */ export interface TgpuRawCodeSnippet { @@ -54,7 +54,11 @@ export type RawCodeSnippetOrigin = Exclude< * Usually 'runtime' (the default) is a safe bet, but if you're sure that the expression or * computation is constant (either a reference to a constant, a numeric literal, * or an operation on constants), then pass 'constant' as it might lead to better +<<<<<<< HEAD * optimisations. +======= + * optimizations. +>>>>>>> main * * If what the expression is a direct reference to an existing value (e.g. a uniform, a * storage binding, ...), then choose from 'uniform', 'mutable', 'readonly', 'workgroup', @@ -84,7 +88,7 @@ export function rawCodeSnippet( type: TDataType, origin: RawCodeSnippetOrigin | undefined = 'runtime', ): TgpuRawCodeSnippet { - return new TgpuRawCodeSnippetImpl(expression, type, 'runtime'); + return new TgpuRawCodeSnippetImpl(expression, type, origin); } // -------------- diff --git a/packages/typegpu/src/core/texture/texture.ts b/packages/typegpu/src/core/texture/texture.ts index 516ae6cdb1..8ba63bd8d0 100644 --- a/packages/typegpu/src/core/texture/texture.ts +++ b/packages/typegpu/src/core/texture/texture.ts @@ -494,7 +494,7 @@ class TgpuTextureImpl texture: this[$internal].unwrap(), mipLevel, }, - source, + 'buffer' in source ? source.buffer : source, { bytesPerRow: this.#formatInfo.texelSize * mipWidth, rowsPerImage: mipHeight, @@ -623,6 +623,9 @@ class TgpuFixedTextureViewImpl if (this.#descriptor?.arrayLayerCount !== undefined) { descriptor.arrayLayerCount = this.#descriptor.arrayLayerCount; } + if (this.#descriptor?.baseArrayLayer !== undefined) { + descriptor.baseArrayLayer = this.#descriptor.baseArrayLayer; + } this.#view = this.#baseTexture[$internal] .unwrap() diff --git a/packages/typegpu/src/data/ref.ts b/packages/typegpu/src/data/ref.ts index ee2eec20e3..b5c5c80937 100644 --- a/packages/typegpu/src/data/ref.ts +++ b/packages/typegpu/src/data/ref.ts @@ -44,7 +44,10 @@ export interface ref { $: T; } -export const ref: DualFn<(value: T) => ref> = (() => { +// biome has issues with this type being inline +type RefFn = (value: T) => ref; + +export const ref = (() => { const gpuImpl = (value: Snippet) => { if (value.origin === 'argument') { throw new WgslTypeError( @@ -98,7 +101,7 @@ export const ref: DualFn<(value: T) => ref> = (() => { }, }); - return impl as unknown as DualFn<(value: T) => ref>; + return impl as unknown as DualFn; })(); export function isRef(value: unknown | ref): value is ref { diff --git a/packages/typegpu/src/data/texture.ts b/packages/typegpu/src/data/texture.ts index 11834f7e52..3b5c83ab00 100644 --- a/packages/typegpu/src/data/texture.ts +++ b/packages/typegpu/src/data/texture.ts @@ -178,7 +178,7 @@ export interface WgslTextureDepthMultisampled2d extends multisampled: true; }> { readonly type: 'texture_depth_multisampled_2d'; - readonly [$repr]: textureMultisampled2d; + readonly [$repr]: textureDepthMultisampled2d; } export interface WgslTextureDepth2dArray extends @@ -188,7 +188,7 @@ export interface WgslTextureDepth2dArray extends multisampled: false; }> { readonly type: 'texture_depth_2d_array'; - readonly [$repr]: texture2dArray; + readonly [$repr]: textureDepth2dArray; } export interface WgslTextureDepthCube extends @@ -198,7 +198,7 @@ export interface WgslTextureDepthCube extends multisampled: false; }> { readonly type: 'texture_depth_cube'; - readonly [$repr]: textureCube; + readonly [$repr]: textureDepthCube; } export interface WgslTextureDepthCubeArray extends @@ -208,7 +208,7 @@ export interface WgslTextureDepthCubeArray extends multisampled: false; }> { readonly type: 'texture_depth_cube_array'; - readonly [$repr]: textureCubeArray; + readonly [$repr]: textureDepthCubeArray; } // Storage textures diff --git a/packages/typegpu/src/std/numeric.ts b/packages/typegpu/src/std/numeric.ts index de02cf4e78..17526c9af6 100644 --- a/packages/typegpu/src/std/numeric.ts +++ b/packages/typegpu/src/std/numeric.ts @@ -14,7 +14,7 @@ import { i32, u32, } from '../data/numeric.ts'; -import { snip } from '../data/snippet.ts'; +import { snip, Snippet } from '../data/snippet.ts'; import { abstruct } from '../data/struct.ts'; import { vec2f, @@ -56,6 +56,45 @@ import { mul, sub } from './operators.ts'; type NumVec = AnyNumericVecInstance; +// helpers + +const unaryIdentitySignature = (arg: AnyData) => { + return { + argTypes: [arg], + returnType: arg, + }; +}; + +const variadicUnifySignature = (...args: AnyData[]) => { + const uargs = unify(args) ?? args; + return ({ + argTypes: uargs, + returnType: uargs[0] as AnyData, + }); +}; + +function variadicReduce(fn: (a: T, b: T) => T) { + return (fst: T, ...rest: T[]): T => { + let acc = fst; + for (const r of rest) { + acc = fn(acc, r); + } + return acc; + }; +} + +function variadicStitch(wrapper: string) { + return (fst: Snippet, ...rest: Snippet[]): string => { + let acc = stitch`${fst}`; + for (const r of rest) { + acc = stitch`${wrapper}(${acc}, ${r})`; + } + return acc; + }; +} + +// std + function cpuAbs(value: number): number; function cpuAbs(value: T): T; function cpuAbs(value: T): T { @@ -65,13 +104,6 @@ function cpuAbs(value: T): T { return VectorOps.abs[value.kind](value) as T; } -const unaryIdentitySignature = (arg: AnyData) => { - return { - argTypes: [arg], - returnType: arg, - }; -}; - export const abs = dualImpl({ name: 'abs', signature: unaryIdentitySignature, @@ -231,10 +263,7 @@ function cpuClamp(value: T, low: T, high: T): T { export const clamp = dualImpl({ name: 'clamp', - signature: (...args) => { - const uargs = unify(args) ?? args; - return { argTypes: uargs, returnType: uargs[0] }; - }, + signature: variadicUnifySignature, normalImpl: cpuClamp, codegenImpl: (value, low, high) => stitch`clamp(${value}, ${low}, ${high})`, }); @@ -767,17 +796,16 @@ function cpuMax(a: T, b: T): T { return VectorOps.max[a.kind](a, b as NumVec) as T; } +type VariadicOverload = { + (fst: number, ...rest: number[]): number; + (fst: T, ...rest: T[]): T; +}; + export const max = dualImpl({ name: 'max', - signature: (...args) => { - const uargs = unify(args) ?? args; - return ({ - argTypes: uargs, - returnType: uargs[0], - }); - }, - normalImpl: cpuMax, - codegenImpl: (a, b) => stitch`max(${a}, ${b})`, + signature: variadicUnifySignature, + normalImpl: variadicReduce(cpuMax) as VariadicOverload, + codegenImpl: variadicStitch('max'), }); function cpuMin(a: number, b: number): number; @@ -791,15 +819,9 @@ function cpuMin(a: T, b: T): T { export const min = dualImpl({ name: 'min', - signature: (...args) => { - const uargs = unify(args) ?? args; - return ({ - argTypes: uargs, - returnType: uargs[0], - }); - }, - normalImpl: cpuMin, - codegenImpl: (a, b) => stitch`min(${a}, ${b})`, + signature: variadicUnifySignature, + normalImpl: variadicReduce(cpuMin) as VariadicOverload, + codegenImpl: variadicStitch('min'), }); function cpuMix(e1: number, e2: number, e3: number): number; @@ -828,13 +850,7 @@ function cpuMix( export const mix = dualImpl({ name: 'mix', - signature: (...args) => { - const uargs = unify(args) ?? args; - return ({ - argTypes: uargs, - returnType: uargs[0], - }); - }, + signature: variadicUnifySignature, normalImpl: cpuMix, codegenImpl: (e1, e2, e3) => stitch`mix(${e1}, ${e2}, ${e3})`, }); diff --git a/packages/typegpu/tests/examples/individual/box-raytracing.test.ts b/packages/typegpu/tests/examples/individual/box-raytracing.test.ts index 669948dbfd..0f25cdaea0 100644 --- a/packages/typegpu/tests/examples/individual/box-raytracing.test.ts +++ b/packages/typegpu/tests/examples/individual/box-raytracing.test.ts @@ -146,7 +146,6 @@ describe('box raytracing example', () => { } var density = 0f; var invColor = vec3f(); - var tMin = 0f; var intersectionFound = false; for (var i = 0; (i < 7i); i++) { for (var j = 0; (j < 7i); j++) { @@ -160,7 +159,6 @@ describe('box raytracing example', () => { let boxDensity = (max(0f, (intersection.tMax - intersection.tMin)) * pow(uniforms_1.materialDensity, 2f)); density += boxDensity; invColor = (invColor + (boxDensity * (vec3f(1) / boxMatrix_10[i][j][k].albedo))); - tMin = intersection.tMin; intersectionFound = true; } } diff --git a/packages/typegpu/tests/examples/individual/disco.test.ts b/packages/typegpu/tests/examples/individual/disco.test.ts index 40dacd0bda..fadb77af7f 100644 --- a/packages/typegpu/tests/examples/individual/disco.test.ts +++ b/packages/typegpu/tests/examples/individual/disco.test.ts @@ -62,27 +62,240 @@ describe('disco example', () => { return (acc + (col * weight)); } - struct mainFragment_Input_9 { + struct mainFragment2_Input_9 { @location(0) uv: vec2f, } - @fragment fn mainFragment_3(_arg_0: mainFragment_Input_9) -> @location(0) vec4f { - { - var originalUv = aspectCorrected_4(_arg_0.uv); - var aspectUv = originalUv; - var accumulatedColor = vec3f(); - for (var iteration = 0; (iteration < 5i); iteration++) { - aspectUv = (fract((aspectUv * (1.3f * sin(time_6)))) - 0.5); - var radialLength = (length(aspectUv) * exp((-(length(originalUv)) * 2f))); - radialLength = (sin(((radialLength * 8f) + time_6)) / 8f); - radialLength = abs(radialLength); - radialLength = smoothstep(0, 0.1, radialLength); - radialLength = (0.06f / radialLength); - var paletteColor = palette_7((length(originalUv) + (time_6 * 0.9f))); - accumulatedColor = accumulate_8(accumulatedColor, paletteColor, radialLength); - } - return vec4f(accumulatedColor, 1f); + @fragment fn mainFragment2_3(_arg_0: mainFragment2_Input_9) -> @location(0) vec4f { + var originalUv = aspectCorrected_4(_arg_0.uv); + var aspectUv = originalUv; + var accumulatedColor = vec3f(); + for (var iteration = 0; (iteration < 3i); iteration++) { + aspectUv = (fract((aspectUv * -0.9)) - 0.5); + var radialLength = (length(aspectUv) * exp((-(length(originalUv)) * 0.5f))); + var paletteColor = palette_7((length(originalUv) + (time_6 * 0.9f))); + radialLength = (sin(((radialLength * 8f) + time_6)) / 8f); + radialLength = abs(radialLength); + radialLength = smoothstep(0, 0.1, radialLength); + radialLength = (0.1f / radialLength); + accumulatedColor = accumulate_8(accumulatedColor, paletteColor, radialLength); } + return vec4f(accumulatedColor, 1f); + } + + struct mainFragment3_Input_11 { + @location(0) uv: vec2f, + } + + @fragment fn mainFragment3_10(_arg_0: mainFragment3_Input_11) -> @location(0) vec4f { + var originalUv = aspectCorrected_4(_arg_0.uv); + var aspectUv = originalUv; + var accumulatedColor = vec3f(); + let baseAngle = (time_6 * 0.3f); + let cosBaseAngle = cos(baseAngle); + let sinBaseAngle = sin(baseAngle); + for (var iteration = 0; (iteration < 4i); iteration++) { + let iterationF32 = f32(iteration); + let rotatedX = ((aspectUv.x * cosBaseAngle) - (aspectUv.y * sinBaseAngle)); + let rotatedY = ((aspectUv.x * sinBaseAngle) + (aspectUv.y * cosBaseAngle)); + aspectUv = vec2f(rotatedX, rotatedY); + aspectUv = (aspectUv * (1.15f + (iterationF32 * 0.05f))); + aspectUv = (fract((aspectUv * (1.2f * sin(((time_6 * 0.9f) + (iterationF32 * 0.3f)))))) - 0.5); + var radialLength = (length(aspectUv) * exp((-(length(originalUv)) * 1.6f))); + var paletteColor = palette_7(((length(originalUv) + (time_6 * 0.8f)) + (iterationF32 * 0.05f))); + radialLength = (sin(((radialLength * 7f) + (time_6 * 0.9f))) / 8f); + radialLength = abs(radialLength); + radialLength = smoothstep(0, 0.11, radialLength); + radialLength = (0.055f / (radialLength + 1e-5f)); + accumulatedColor = accumulate_8(accumulatedColor, paletteColor, radialLength); + } + return vec4f(accumulatedColor, 1f); + } + + struct mainFragment4_Input_13 { + @location(0) uv: vec2f, + } + + @fragment fn mainFragment4_12(_arg_0: mainFragment4_Input_13) -> @location(0) vec4f { + var aspectUv = aspectCorrected_4(_arg_0.uv); + var mirroredUv = ((vec2f(abs((fract((aspectUv.x * 1.2f)) - 0.5f)), abs((fract((aspectUv.y * 1.2f)) - 0.5f))) * 2) - 1); + aspectUv = mirroredUv; + var originalUv = aspectUv; + var accumulatedColor = vec3f(); + let time = time_6; + for (var iteration = 0; (iteration < 4i); iteration++) { + let iterationF32 = f32(iteration); + let angle = ((time * (0.4f + (iterationF32 * 0.1f))) + (iterationF32 * 0.9f)); + let cosAngle = cos(angle); + let sinAngle = sin(angle); + let rotatedX = ((aspectUv.x * cosAngle) - (aspectUv.y * sinAngle)); + let rotatedY = ((aspectUv.x * sinAngle) + (aspectUv.y * cosAngle)); + aspectUv = (vec2f(rotatedX, rotatedY) * (1.1f + (iterationF32 * 0.07f))); + aspectUv = (fract((aspectUv * (1.25f + (iterationF32 * 0.15f)))) - 0.5); + var radialLength = (length(aspectUv) * exp((-(length(originalUv)) * (1.3f + (iterationF32 * 0.06f))))); + radialLength = (sin(((radialLength * (7.2f + (iterationF32 * 0.8f))) + (time * (1.1f + (iterationF32 * 0.2f))))) / 8f); + radialLength = abs(radialLength); + radialLength = smoothstep(0, 0.105, radialLength); + radialLength = ((0.058f + (iterationF32 * 6e-3f)) / (radialLength + 1e-5f)); + var paletteColor = palette_7(((length(originalUv) + (time * 0.65f)) + (iterationF32 * 0.045f))); + accumulatedColor = accumulate_8(accumulatedColor, paletteColor, radialLength); + } + return vec4f(accumulatedColor, 1f); + } + + struct mainFragment5_Input_15 { + @location(0) uv: vec2f, + } + + @fragment fn mainFragment5_14(_arg_0: mainFragment5_Input_15) -> @location(0) vec4f { + var originalUv = aspectCorrected_4(_arg_0.uv); + var aspectUv = originalUv; + var accumulatedColor = vec3f(); + for (var iteration = 0; (iteration < 3i); iteration++) { + let iterationF32 = f32(iteration); + let radius = (length(aspectUv) + 1e-4f); + let angle = ((radius * (8f + (iterationF32 * 2f))) - (time_6 * (1.5f + (iterationF32 * 0.2f)))); + let cosAngle = cos(angle); + let sinAngle = sin(angle); + let rotatedX = ((aspectUv.x * cosAngle) - (aspectUv.y * sinAngle)); + let rotatedY = ((aspectUv.x * sinAngle) + (aspectUv.y * cosAngle)); + aspectUv = (vec2f(rotatedX, rotatedY) * (-0.85f - (iterationF32 * 0.07f))); + aspectUv = (fract(aspectUv) - 0.5); + var radialLength = (length(aspectUv) * exp((-(length(originalUv)) * (0.4f + (iterationF32 * 0.1f))))); + var paletteColor = palette_7(((length(originalUv) + (time_6 * 0.9f)) + (iterationF32 * 0.08f))); + radialLength = (sin(((radialLength * (6f + iterationF32)) + time_6)) / 8f); + radialLength = abs(radialLength); + radialLength = smoothstep(0, 0.1, radialLength); + radialLength = ((0.085f + (iterationF32 * 5e-3f)) / (radialLength + 1e-5f)); + accumulatedColor = accumulate_8(accumulatedColor, paletteColor, radialLength); + } + return vec4f(accumulatedColor, 1f); + } + + struct mainFragment6_Input_17 { + @location(0) uv: vec2f, + } + + @fragment fn mainFragment6_16(_arg_0: mainFragment6_Input_17) -> @location(0) vec4f { + var aspectUv = aspectCorrected_4(_arg_0.uv); + var originalUv = aspectUv; + var accumulatedColor = vec3f(); + let time = time_6; + for (var iteration = 0; (iteration < 5i); iteration++) { + let iterationF32 = f32(iteration); + let angle = ((time * (0.25f + (iterationF32 * 0.05f))) + (iterationF32 * 0.6f)); + let cosAngle = cos(angle); + let sinAngle = sin(angle); + let rotatedX = ((aspectUv.x * cosAngle) - (aspectUv.y * sinAngle)); + let rotatedY = ((aspectUv.x * sinAngle) + (aspectUv.y * cosAngle)); + aspectUv = (vec2f(rotatedX, rotatedY) * (1.08f + (iterationF32 * 0.04f))); + var warpedUv = (fract((aspectUv * (1.3f + (iterationF32 * 0.2f)))) - 0.5); + var radialLength = (length(warpedUv) * exp((-(length(originalUv)) * (1.4f + (iterationF32 * 0.05f))))); + radialLength = (sin(((radialLength * (7f + (iterationF32 * 0.7f))) + (time * (0.9f + (iterationF32 * 0.15f))))) / 8f); + radialLength = abs(radialLength); + radialLength = smoothstep(0, 0.1, radialLength); + radialLength = ((0.05f + (iterationF32 * 5e-3f)) / (radialLength + 1e-5f)); + var paletteColor = palette_7(((length(originalUv) + (time * 0.7f)) + (iterationF32 * 0.04f))); + accumulatedColor = accumulate_8(accumulatedColor, paletteColor, radialLength); + } + return vec4f(accumulatedColor, 1f); + } + + struct mainFragment7_Input_19 { + @location(0) uv: vec2f, + } + + @fragment fn mainFragment7_18(_arg_0: mainFragment7_Input_19) -> @location(0) vec4f { + var aspectUv = aspectCorrected_4(_arg_0.uv); + aspectUv = (vec2f(abs((fract((aspectUv.x * 1.5f)) - 0.5f)), abs((fract((aspectUv.y * 1.5f)) - 0.5f))) * 2); + var originalUv = aspectUv; + var accumulatedColor = vec3f(); + let time = time_6; + for (var iteration = 0; (iteration < 4i); iteration++) { + let iterationF32 = f32(iteration); + let angle = ((iterationF32 * 0.8f) + (time * 0.35f)); + let cosAngle = cos(angle); + let sinAngle = sin(angle); + let rotatedX = ((aspectUv.x * cosAngle) - (aspectUv.y * sinAngle)); + let rotatedY = ((aspectUv.x * sinAngle) + (aspectUv.y * cosAngle)); + aspectUv = (vec2f(rotatedX, rotatedY) * (1.18f + (iterationF32 * 0.06f))); + let radius = (length(aspectUv) + 1e-4f); + let swirl = sin(((radius * 10f) - (time * (1.2f + (iterationF32 * 0.2f))))); + aspectUv = (aspectUv + vec2f((swirl * 0.02f), (swirl * -0.02f))); + var radialLength = (length(aspectUv) * exp((-(length(originalUv)) * (1.2f + (iterationF32 * 0.08f))))); + radialLength = (sin(((radialLength * (7.5f + iterationF32)) + (time * (1f + (iterationF32 * 0.1f))))) / 8f); + radialLength = abs(radialLength); + radialLength = smoothstep(0, 0.11, radialLength); + radialLength = ((0.06f + (iterationF32 * 5e-3f)) / (radialLength + 1e-5f)); + var paletteColor = palette_7(((length(originalUv) + (time * 0.75f)) + (iterationF32 * 0.05f))); + accumulatedColor = accumulate_8(accumulatedColor, paletteColor, radialLength); + } + return vec4f(accumulatedColor, 1f); + } + + struct mainVertex_Output_1 { + @builtin(position) outPos: vec4f, + @location(0) uv: vec2f, + } + + struct mainVertex_Input_2 { + @builtin(vertex_index) vertexIndex: u32, + } + + @vertex fn mainVertex_0(_arg_0: mainVertex_Input_2) -> mainVertex_Output_1 { + var pos = array(vec2f(-1, 1), vec2f(-1), vec2f(1, -1), vec2f(-1, 1), vec2f(1, -1), vec2f(1)); + var uv = array(vec2f(0, 1), vec2f(), vec2f(1, 0), vec2f(0, 1), vec2f(1, 0), vec2f(1)); + return mainVertex_Output_1(vec4f(pos[_arg_0.vertexIndex], 0f, 1f), uv[_arg_0.vertexIndex]); + } + + @group(0) @binding(0) var resolutionUniform_5: vec2f; + + fn aspectCorrected_4(uv: vec2f) -> vec2f { + var v = ((uv - 0.5) * 2); + let aspect = (resolutionUniform_5.x / resolutionUniform_5.y); + if ((aspect > 1f)) { + v.x *= aspect; + } + else { + v.y /= aspect; + } + return v; + } + + @group(0) @binding(1) var time_6: f32; + + fn palette_7(t: f32) -> vec3f { + var a = vec3f(0.5, 0.5899999737739563, 0.8500000238418579); + var b = vec3f(0.18000000715255737, 0.41999998688697815, 0.4000000059604645); + var c = vec3f(0.18000000715255737, 0.47999998927116394, 0.4099999964237213); + var e = vec3f(0.3499999940395355, 0.12999999523162842, 0.3199999928474426); + var expr = cos((6.28318 * ((c * t) + e))); + return (a + (b * expr)); + } + + fn accumulate_8(acc: vec3f, col: vec3f, weight: f32) -> vec3f { + return (acc + (col * weight)); + } + + struct mainFragment1_Input_9 { + @location(0) uv: vec2f, + } + + @fragment fn mainFragment1_3(_arg_0: mainFragment1_Input_9) -> @location(0) vec4f { + var originalUv = aspectCorrected_4(_arg_0.uv); + var aspectUv = originalUv; + var accumulatedColor = vec3f(); + for (var iteration = 0; (iteration < 5i); iteration++) { + aspectUv = (fract((aspectUv * (1.3f * sin(time_6)))) - 0.5); + var radialLength = (length(aspectUv) * exp((-(length(originalUv)) * 2f))); + radialLength = (sin(((radialLength * 8f) + time_6)) / 8f); + radialLength = abs(radialLength); + radialLength = smoothstep(0, 0.1, radialLength); + radialLength = (0.06f / radialLength); + var paletteColor = palette_7((length(originalUv) + (time_6 * 0.9f))); + accumulatedColor = accumulate_8(accumulatedColor, paletteColor, radialLength); + } + return vec4f(accumulatedColor, 1f); }" `); }); diff --git a/packages/typegpu/tests/examples/individual/dispatch.test.ts b/packages/typegpu/tests/examples/individual/dispatch.test.ts index db8a5db8b5..ee6c310b98 100644 --- a/packages/typegpu/tests/examples/individual/dispatch.test.ts +++ b/packages/typegpu/tests/examples/individual/dispatch.test.ts @@ -97,7 +97,7 @@ describe('tgsl parsing test example', () => { @group(0) @binding(1) var mutable_3: atomic; - fn wrappedCallback_2(x: u32, y: u32, z: u32) { + fn wrappedCallback_2(_x: u32, _y: u32, _z: u32) { atomicAdd(&mutable_3, 1u); } diff --git a/packages/typegpu/tests/examples/individual/jelly-slider.test.ts b/packages/typegpu/tests/examples/individual/jelly-slider.test.ts index bbd4ce31ce..aecbc656bc 100644 --- a/packages/typegpu/tests/examples/individual/jelly-slider.test.ts +++ b/packages/typegpu/tests/examples/individual/jelly-slider.test.ts @@ -516,7 +516,7 @@ describe('jelly-slider example', () => { return exp((sigma * -(dist))); } - fn rayMarch_12(rayOrigin: vec3f, rayDirection: vec3f, uv: vec2f) -> vec4f { + fn rayMarch_12(rayOrigin: vec3f, rayDirection: vec3f, _uv: vec2f) -> vec4f { var totalSteps = 0u; var backgroundDist = 0f; for (var i = 0; (i < 64i); i++) { diff --git a/packages/typegpu/tests/examples/individual/phong-reflection.test.ts b/packages/typegpu/tests/examples/individual/phong-reflection.test.ts new file mode 100644 index 0000000000..648c60f74b --- /dev/null +++ b/packages/typegpu/tests/examples/individual/phong-reflection.test.ts @@ -0,0 +1,89 @@ +/** + * @vitest-environment jsdom + */ + +import { describe, expect } from 'vitest'; +import { it } from '../../utils/extendedIt.ts'; +import { runExampleTest, setupCommonMocks } from '../utils/baseTest.ts'; +import { + mock3DModelLoading, + mockResizeObserver, +} from '../utils/commonMocks.ts'; + +describe('phong reflection example', () => { + setupCommonMocks(); + + it('should produce valid code', async ({ device }) => { + const shaderCodes = await runExampleTest({ + category: 'rendering', + name: 'phong-reflection', + setupMocks: () => { + mockResizeObserver(); + mock3DModelLoading(); + }, + expectedCalls: 1, + }, device); + + expect(shaderCodes).toMatchInlineSnapshot(` + "struct Camera_2 { + position: vec4f, + targetPos: vec4f, + view: mat4x4f, + projection: mat4x4f, + } + + @group(0) @binding(0) var cameraUniform_1: Camera_2; + + struct vertexShader_Output_3 { + @location(0) worldPosition: vec3f, + @location(1) worldNormal: vec3f, + @builtin(position) canvasPosition: vec4f, + } + + struct vertexShader_Input_4 { + @location(0) modelPosition: vec3f, + @location(1) modelNormal: vec3f, + @builtin(instance_index) instanceIndex: u32, + } + + @vertex fn vertexShader_0(input: vertexShader_Input_4) -> vertexShader_Output_3 { + var worldPosition = vec4f(input.modelPosition, 1f); + let camera = (&cameraUniform_1); + var canvasPosition = (((*camera).projection * (*camera).view) * worldPosition); + return vertexShader_Output_3(input.modelPosition, input.modelNormal, canvasPosition); + } + + struct ExampleControls_7 { + lightColor: vec3f, + lightDirection: vec3f, + ambientColor: vec3f, + ambientStrength: f32, + specularExponent: f32, + } + + @group(0) @binding(1) var exampleControlsUniform_6: ExampleControls_7; + + struct fragmentShader_Input_8 { + @location(0) worldPosition: vec3f, + @location(1) worldNormal: vec3f, + @builtin(position) canvasPosition: vec4f, + } + + @fragment fn fragmentShader_5(input: fragmentShader_Input_8) -> @location(0) vec4f { + var lightColor = normalize(exampleControlsUniform_6.lightColor); + var lightDirection = normalize(exampleControlsUniform_6.lightDirection); + let ambientColor = (&exampleControlsUniform_6.ambientColor); + let ambientStrength = exampleControlsUniform_6.ambientStrength; + let specularStrength = exampleControlsUniform_6.specularExponent; + var ambient = ((*ambientColor) * ambientStrength); + let cosTheta = dot(input.worldNormal, lightDirection); + var diffuse = (lightColor * max(0f, cosTheta)); + var reflectionDirection = reflect((lightDirection * -1), input.worldNormal); + var viewDirection = normalize((cameraUniform_1.position.xyz - input.worldPosition)); + var specular = (lightColor * pow(max(0f, dot(reflectionDirection, viewDirection)), specularStrength)); + var color = ((ambient + diffuse) + specular); + return vec4f(color, 1f); + }" + `); + }); +}); diff --git a/packages/typegpu/tests/examples/individual/point-light-shadow.test.ts b/packages/typegpu/tests/examples/individual/point-light-shadow.test.ts new file mode 100644 index 0000000000..4f107f2ade --- /dev/null +++ b/packages/typegpu/tests/examples/individual/point-light-shadow.test.ts @@ -0,0 +1,175 @@ +/** + * @vitest-environment jsdom + */ + +import { describe, expect } from 'vitest'; +import { it } from '../../utils/extendedIt.ts'; +import { runExampleTest, setupCommonMocks } from '../utils/baseTest.ts'; +import { mockResizeObserver } from '../utils/commonMocks.ts'; + +describe('point light shadow example', () => { + setupCommonMocks(); + + it('should produce valid code', async ({ device }) => { + const shaderCodes = await runExampleTest({ + category: 'rendering', + name: 'point-light-shadow', + setupMocks: mockResizeObserver, + expectedCalls: 3, + }, device); + + expect(shaderCodes).toMatchInlineSnapshot(` + "struct CameraData_2 { + viewProjectionMatrix: mat4x4f, + inverseViewProjectionMatrix: mat4x4f, + } + + @group(0) @binding(0) var camera_1: CameraData_2; + + struct vertexDepth_Output_3 { + @builtin(position) pos: vec4f, + @location(0) worldPos: vec3f, + } + + struct vertexDepth_Input_4 { + @location(0) position: vec3f, + @location(1) normal: vec3f, + @location(2) uv: vec2f, + @location(3) column1: vec4f, + @location(4) column2: vec4f, + @location(5) column3: vec4f, + @location(6) column4: vec4f, + } + + @vertex fn vertexDepth_0(_arg_0: vertexDepth_Input_4) -> vertexDepth_Output_3 { + var modelMatrix = mat4x4f(_arg_0.column1, _arg_0.column2, _arg_0.column3, _arg_0.column4); + var worldPos = (modelMatrix * vec4f(_arg_0.position, 1f)).xyz; + var pos = (camera_1.viewProjectionMatrix * vec4f(worldPos, 1f)); + return vertexDepth_Output_3(pos, worldPos); + } + + @group(0) @binding(1) var lightPosition_6: vec3f; + + struct fragmentDepth_Input_7 { + @location(0) worldPos: vec3f, + } + + @fragment fn fragmentDepth_5(_arg_0: fragmentDepth_Input_7) -> @builtin(frag_depth) f32 { + let dist = length((_arg_0.worldPos - lightPosition_6)); + return (dist / 100f); + } + + struct CameraData_2 { + viewProjectionMatrix: mat4x4f, + inverseViewProjectionMatrix: mat4x4f, + } + + @group(1) @binding(0) var camera_1: CameraData_2; + + struct vertexMain_Output_3 { + @builtin(position) pos: vec4f, + @location(0) worldPos: vec3f, + @location(1) uv: vec2f, + @location(2) normal: vec3f, + } + + struct vertexMain_Input_4 { + @location(0) position: vec3f, + @location(1) normal: vec3f, + @location(2) uv: vec2f, + @location(3) column1: vec4f, + @location(4) column2: vec4f, + @location(5) column3: vec4f, + @location(6) column4: vec4f, + } + + @vertex fn vertexMain_0(_arg_0: vertexMain_Input_4) -> vertexMain_Output_3 { + var modelMatrix = mat4x4f(_arg_0.column1, _arg_0.column2, _arg_0.column3, _arg_0.column4); + var worldPos = (modelMatrix * vec4f(_arg_0.position, 1f)).xyz; + var pos = (camera_1.viewProjectionMatrix * vec4f(worldPos, 1f)); + var worldNormal = normalize((modelMatrix * vec4f(_arg_0.normal, 0f)).xyz); + return vertexMain_Output_3(pos, worldPos, _arg_0.uv, worldNormal); + } + + @group(1) @binding(3) var lightPosition_6: vec3f; + + struct item_8 { + pcfSamples: u32, + diskRadius: f32, + normalBiasBase: f32, + normalBiasSlope: f32, + } + + @group(0) @binding(0) var shadowParams_7: item_8; + + @group(0) @binding(1) var samplesUniform_9: array; + + @group(1) @binding(1) var shadowDepthCube_10: texture_depth_cube; + + @group(1) @binding(2) var shadowSampler_11: sampler_comparison; + + struct fragmentMain_Input_12 { + @location(0) worldPos: vec3f, + @location(1) uv: vec2f, + @location(2) normal: vec3f, + } + + @fragment fn fragmentMain_5(_arg_0: fragmentMain_Input_12) -> @location(0) vec4f { + let lightPos = (&lightPosition_6); + var toLight = ((*lightPos) - _arg_0.worldPos); + let dist = length(toLight); + var lightDir = (toLight / dist); + let ndotl = max(dot(_arg_0.normal, lightDir), 0f); + let normalBiasWorld = (shadowParams_7.normalBiasBase + (shadowParams_7.normalBiasSlope * (1f - ndotl))); + var biasedPos = (_arg_0.worldPos + (_arg_0.normal * normalBiasWorld)); + var toLightBiased = (biasedPos - (*lightPos)); + let distBiased = length(toLightBiased); + var dir = ((toLightBiased / distBiased) * vec3f(-1, 1, 1)); + let depthRef = (distBiased / 100f); + var up = select(vec3f(1, 0, 0), vec3f(0, 1, 0), (abs(dir.y) < 0.9998999834060669f)); + var right = normalize(cross(up, dir)); + var realUp = cross(dir, right); + let PCF_SAMPLES = shadowParams_7.pcfSamples; + let diskRadius = shadowParams_7.diskRadius; + var visibilityAcc = 0; + for (var i = 0; (i < i32(PCF_SAMPLES)); i++) { + var o = (samplesUniform_9[i].xy * diskRadius); + var sampleDir = ((dir + (right * o.x)) + (realUp * o.y)); + visibilityAcc += i32(textureSampleCompare(shadowDepthCube_10, shadowSampler_11, sampleDir, depthRef)); + } + let rawNdotl = dot(_arg_0.normal, lightDir); + let visibility = select((f32(visibilityAcc) / f32(PCF_SAMPLES)), 0f, (rawNdotl < 0f)); + var baseColor = vec3f(1, 0.5, 0.3100000023841858); + var color = (baseColor * ((ndotl * visibility) + 0.1f)); + return vec4f(color, 1f); + } + + @group(0) @binding(1) var lightPosition_1: vec3f; + + struct CameraData_3 { + viewProjectionMatrix: mat4x4f, + inverseViewProjectionMatrix: mat4x4f, + } + + @group(0) @binding(0) var camera_2: CameraData_3; + + struct vertexLightIndicator_Output_4 { + @builtin(position) pos: vec4f, + } + + struct vertexLightIndicator_Input_5 { + @location(0) position: vec3f, + } + + @vertex fn vertexLightIndicator_0(_arg_0: vertexLightIndicator_Input_5) -> vertexLightIndicator_Output_4 { + var worldPos = ((_arg_0.position * 0.15) + lightPosition_1); + var pos = (camera_2.viewProjectionMatrix * vec4f(worldPos, 1f)); + return vertexLightIndicator_Output_4(pos); + } + + @fragment fn fragmentLightIndicator_6() -> @location(0) vec4f { + return vec4f(1, 1, 0.5, 1); + }" + `); + }); +}); diff --git a/packages/typegpu/tests/renderPipeline.test.ts b/packages/typegpu/tests/renderPipeline.test.ts index 22d3f35177..0dd3f383b3 100644 --- a/packages/typegpu/tests/renderPipeline.test.ts +++ b/packages/typegpu/tests/renderPipeline.test.ts @@ -139,6 +139,52 @@ describe('TgpuRenderPipeline', () => { ).toEqualTypeOf>(); }); + it('properly handles custom depth output in fragment functions', ({ root }) => { + const vertices = tgpu.const(d.arrayOf(d.vec2f, 3), [ + d.vec2f(-1, -1), + d.vec2f(3, -1), + d.vec2f(-1, 3), + ]); + const vertexMain = tgpu['~unstable'].vertexFn({ + in: { vid: d.builtin.vertexIndex }, + out: { pos: d.builtin.position }, + // biome-ignore lint/style/noNonNullAssertion: it's fine + })(({ vid }) => ({ pos: d.vec4f(vertices.$[vid]!, 0, 1) })); + + const fragmentMain = tgpu['~unstable'].fragmentFn({ + out: { color: d.vec4f, depth: d.builtin.fragDepth }, + })(() => ({ color: d.vec4f(1, 0, 0, 1), depth: 0.5 })); + + const pipeline = root + .withVertex(vertexMain, {}) + .withFragment(fragmentMain, { color: { format: 'rgba8unorm' } }) + .createPipeline(); + + pipeline.withColorAttachment({ + color: { + view: {} as unknown as GPUTextureView, + loadOp: 'clear', + storeOp: 'store', + }, + }); + + expect(() => { + pipeline.withColorAttachment({ + color: { + view: {} as unknown as GPUTextureView, + loadOp: 'clear', + storeOp: 'store', + }, + // @ts-expect-error + depth: { + view: {} as unknown as GPUTextureView, + loadOp: 'clear', + storeOp: 'store', + }, + }); + }); + }); + it('type checks passed bind groups', ({ root }) => { const vertexMain = tgpu['~unstable'].vertexFn({ out: { bar: d.location(0, d.vec3f) }, diff --git a/packages/typegpu/tests/std/numeric/max.test.ts b/packages/typegpu/tests/std/numeric/max.test.ts new file mode 100644 index 0000000000..259442b8c6 --- /dev/null +++ b/packages/typegpu/tests/std/numeric/max.test.ts @@ -0,0 +1,108 @@ +import { describe, expect, it } from 'vitest'; +import tgpu from '../../../src/index.ts'; +import * as d from '../../../src/data/index.ts'; +import * as std from '../../../src/std/index.ts'; + +describe('max', () => { + it('acts as identity when called with one argument', () => { + const myMax = tgpu.fn([d.f32], d.f32)((a: number) => { + 'use gpu'; + return std.max(a); + }); + + expect(myMax(6)).toBe(6); + expect(tgpu.resolve([myMax])).toMatchInlineSnapshot(` + "fn myMax(a: f32) -> f32 { + return a; + }" + `); + }); + + it('works with two arguments', () => { + const myMax = tgpu.fn([d.f32, d.f32], d.f32)((a, b) => { + 'use gpu'; + return std.max(a, b); + }); + + expect(myMax(1, 2)).toBe(2); + expect(tgpu.resolve([myMax])).toMatchInlineSnapshot(` + "fn myMax(a: f32, b: f32) -> f32 { + return max(a, b); + }" + `); + }); + + it('works with multiple arguments', () => { + const myMax = tgpu.fn([d.f32, d.f32, d.f32, d.f32], d.f32)( + (a, b, c, d) => { + 'use gpu'; + return std.max(a, b, c, d); + }, + ); + + expect(myMax(2, 1, 4, 5)).toBe(5); + expect(tgpu.resolve([myMax])).toMatchInlineSnapshot(` + "fn myMax(a: f32, b: f32, c: f32, d2: f32) -> f32 { + return max(max(max(a, b), c), d2); + }" + `); + }); + + it('unifies arguments', () => { + const myMax = tgpu.fn([], d.f32)(() => { + 'use gpu'; + const a = d.u32(9); + const b = d.i32(1); + const c = d.f32(4); + return std.max(a, b, 3.3, c, 7); + }); + + expect(myMax()).toBe(9); + expect(tgpu.resolve([myMax])).toMatchInlineSnapshot(` + "fn myMax() -> f32 { + const a = 9u; + const b = 1i; + const c = 4f; + return max(max(max(max(f32(a), f32(b)), 3.3f), c), 7f); + }" + `); + }); + + it('works with vectors', () => { + const myMax = tgpu.fn([d.vec3u, d.vec3u], d.vec3u)((a, b) => { + 'use gpu'; + return std.max(a, b); + }); + + expect(myMax(d.vec3u(1, 2, 3), d.vec3u(3, 2, 1))) + .toStrictEqual(d.vec3u(3, 2, 3)); + expect(tgpu.resolve([myMax])).toMatchInlineSnapshot(` + "fn myMax(a: vec3u, b: vec3u) -> vec3u { + return max(a, b); + }" + `); + }); + + it('does comptime reduction', () => { + const myMax = tgpu.fn([], d.u32)(() => { + 'use gpu'; + return std.max(12, 33, 12333, 444); + }); + + expect(myMax()).toBe(12333); + expect(tgpu.resolve([myMax])).toMatchInlineSnapshot(` + "fn myMax() -> u32 { + return 12333u; + }" + `); + }); + + it('cannot be called with invalid arguments', () => { + // @ts-expect-error + (() => std.max()); + // @ts-expect-error + (() => std.max(1, d.vec2f())); + // @ts-expect-error + (() => std.max(d.vec3f(), d.vec2f())); + }); +}); diff --git a/packages/typegpu/tests/std/numeric/min.test.ts b/packages/typegpu/tests/std/numeric/min.test.ts new file mode 100644 index 0000000000..579f78465b --- /dev/null +++ b/packages/typegpu/tests/std/numeric/min.test.ts @@ -0,0 +1,108 @@ +import { describe, expect, it } from 'vitest'; +import tgpu from '../../../src/index.ts'; +import * as d from '../../../src/data/index.ts'; +import * as std from '../../../src/std/index.ts'; + +describe('min', () => { + it('acts as identity when called with one argument', () => { + const myMin = tgpu.fn([d.f32], d.f32)((a: number) => { + 'use gpu'; + return std.min(a); + }); + + expect(myMin(6)).toBe(6); + expect(tgpu.resolve([myMin])).toMatchInlineSnapshot(` + "fn myMin(a: f32) -> f32 { + return a; + }" + `); + }); + + it('works with two arguments', () => { + const myMin = tgpu.fn([d.f32, d.f32], d.f32)((a, b) => { + 'use gpu'; + return std.min(a, b); + }); + + expect(myMin(1, 2)).toBe(1); + expect(tgpu.resolve([myMin])).toMatchInlineSnapshot(` + "fn myMin(a: f32, b: f32) -> f32 { + return min(a, b); + }" + `); + }); + + it('works with multiple arguments', () => { + const myMin = tgpu.fn([d.f32, d.f32, d.f32, d.f32], d.f32)( + (a, b, c, d) => { + 'use gpu'; + return std.min(a, b, c, d); + }, + ); + + expect(myMin(2, 1, 4, 5)).toBe(1); + expect(tgpu.resolve([myMin])).toMatchInlineSnapshot(` + "fn myMin(a: f32, b: f32, c: f32, d2: f32) -> f32 { + return min(min(min(a, b), c), d2); + }" + `); + }); + + it('unifies arguments', () => { + const myMin = tgpu.fn([], d.f32)(() => { + 'use gpu'; + const a = d.u32(9); + const b = d.i32(1); + const c = d.f32(4); + return std.min(a, b, 3.3, c, 7); + }); + + expect(myMin()).toBe(1); + expect(tgpu.resolve([myMin])).toMatchInlineSnapshot(` + "fn myMin() -> f32 { + const a = 9u; + const b = 1i; + const c = 4f; + return min(min(min(min(f32(a), f32(b)), 3.3f), c), 7f); + }" + `); + }); + + it('works with vectors', () => { + const myMin = tgpu.fn([d.vec3u, d.vec3u], d.vec3u)((a, b) => { + 'use gpu'; + return std.min(a, b); + }); + + expect(myMin(d.vec3u(1, 2, 3), d.vec3u(3, 2, 1))) + .toStrictEqual(d.vec3u(1, 2, 1)); + expect(tgpu.resolve([myMin])).toMatchInlineSnapshot(` + "fn myMin(a: vec3u, b: vec3u) -> vec3u { + return min(a, b); + }" + `); + }); + + it('does comptime reduction', () => { + const myMin = tgpu.fn([], d.u32)(() => { + 'use gpu'; + return std.min(33, 12, 444, 12333); + }); + + expect(myMin()).toBe(12); + expect(tgpu.resolve([myMin])).toMatchInlineSnapshot(` + "fn myMin() -> u32 { + return 12u; + }" + `); + }); + + it('cannot be called with invalid arguments', () => { + // @ts-expect-error + (() => std.min()); + // @ts-expect-error + (() => std.min(1, d.vec2f())); + // @ts-expect-error + (() => std.min(d.vec3f(), d.vec2f())); + }); +}); diff --git a/packages/typegpu/tests/texture.test.ts b/packages/typegpu/tests/texture.test.ts index e0b499105f..23b69e9dea 100644 --- a/packages/typegpu/tests/texture.test.ts +++ b/packages/typegpu/tests/texture.test.ts @@ -473,7 +473,7 @@ Overload 2 of 2, '(schema: "(Error) Storage texture format 'rgba8snorm' incompat texture: expect.anything(), mipLevel: 0, }), - data, + data.buffer, expect.objectContaining({ bytesPerRow: 16, // 4 pixels * 4 bytes per pixel rowsPerImage: 4, @@ -494,7 +494,7 @@ Overload 2 of 2, '(schema: "(Error) Storage texture format 'rgba8snorm' incompat expect(device.mock.queue.writeTexture).toHaveBeenCalledWith( { texture: expect.anything(), mipLevel: 2 }, - data, + data.buffer, { bytesPerRow: 8, rowsPerImage: 2 }, // 2 pixels * 4 bytes per pixel [2, 2, 1], // Mip level 2 dimensions ); diff --git a/packages/typegpu/tests/tgsl/codeGen.test.ts b/packages/typegpu/tests/tgsl/codeGen.test.ts index eb2245d758..f1f62b871a 100644 --- a/packages/typegpu/tests/tgsl/codeGen.test.ts +++ b/packages/typegpu/tests/tgsl/codeGen.test.ts @@ -34,4 +34,23 @@ describe('codeGen', () => { `); }); }); + + it('should properly resolve the "this" keyword', ({ root }) => { + class MyController { + myBuffer = root.createUniform(d.u32); + myFn = tgpu.fn([], d.u32)(() => { + return this.myBuffer.$; + }); + } + + const myController = new MyController(); + + expect(tgpu.resolve([myController.myFn])).toMatchInlineSnapshot(` + "@group(0) @binding(0) var item_1: u32; + + fn item() -> u32 { + return item_1; + }" + `); + }); }); diff --git a/packages/typegpu/tests/tgsl/comptime.test.ts b/packages/typegpu/tests/tgsl/comptime.test.ts new file mode 100644 index 0000000000..173dfa3ff0 --- /dev/null +++ b/packages/typegpu/tests/tgsl/comptime.test.ts @@ -0,0 +1,51 @@ +import { describe, expect } from 'vitest'; +import { it } from '../utils/extendedIt.ts'; +import * as d from '../../src/data/index.ts'; +import tgpu from '../../src/index.ts'; + +describe('comptime', () => { + it('should work in JS', () => { + const myComptime = tgpu['~unstable'].comptime(() => 0.5); + + const myFn = tgpu.fn([], d.f32)(() => { + return myComptime(); + }); + + expect(myFn()).toBe(0.5); + }); + + it('should work when returning a constant', () => { + const myComptime = tgpu['~unstable'].comptime(() => 0.5); + + const myFn = tgpu.fn([], d.f32)(() => { + return myComptime(); + }); + + expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` + "fn myFn() -> f32 { + return 0.5f; + }" + `); + }); + + it('should work when returning a reference', () => { + let a = 0; + const myComptime = tgpu['~unstable'].comptime(() => a); + const myFn = tgpu.fn([], d.f32)(() => { + return myComptime(); + }); + + expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` + "fn myFn() -> f32 { + return 0f; + }" + `); + + a = 1; + expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` + "fn myFn() -> f32 { + return 1f; + }" + `); + }); +}); diff --git a/packages/typegpu/tests/tgsl/rawCodeSnippet.test.ts b/packages/typegpu/tests/tgsl/rawCodeSnippet.test.ts new file mode 100644 index 0000000000..d01876e792 --- /dev/null +++ b/packages/typegpu/tests/tgsl/rawCodeSnippet.test.ts @@ -0,0 +1,119 @@ +import { describe, expect } from 'vitest'; +import { it } from '../utils/extendedIt.ts'; +import * as d from '../../src/data/index.ts'; +import tgpu from '../../src/index.ts'; + +describe('rawCodeSnippet', () => { + it('should throw a descriptive error when called in JS', () => { + const rawSnippet = tgpu['~unstable'].rawCodeSnippet('3', d.f32); + + const myFn = tgpu.fn([], d.f32)(() => { + return rawSnippet.$; + }); + + expect(() => myFn()).toThrowErrorMatchingInlineSnapshot(` + [Error: Execution of the following tree failed: + - fn:myFn: Raw code snippets can only be used on the GPU.] + `); + }); + + it('should properly inline', () => { + const rawSnippet = tgpu['~unstable'].rawCodeSnippet('3f', d.f32); + + const myFn = tgpu.fn([], d.f32)(() => { + return rawSnippet.$; + }); + + expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` + "fn myFn() -> f32 { + return 3f; + }" + `); + }); + + it('should use the origin', () => { + const rawSnippet = tgpu['~unstable'].rawCodeSnippet( + '3f', + d.f32, + 'constant', + ); + + const myFn = tgpu.fn([], d.f32)(() => { + const a = rawSnippet.$; // should resolve to 'const' instead of 'let' + return a; + }); + + expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` + "fn myFn() -> f32 { + const a = 3f; + return a; + }" + `); + }); + + it('should properly resolve dependencies', ({ root }) => { + const myBuffer = root.createUniform(d.u32, 7); + + const rawSnippet = tgpu['~unstable'].rawCodeSnippet( + 'myBuffer', + d.u32, + 'uniform', + ).$uses({ myBuffer }); + + const myFn = tgpu.fn([], d.u32)(() => { + return rawSnippet.$; + }); + + expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` + "@group(0) @binding(0) var myBuffer: u32; + + fn myFn() -> u32 { + return myBuffer; + }" + `); + }); + + it('should properly resolve layout dependencies', ({ root }) => { + const myLayout = tgpu.bindGroupLayout({ myBuffer: { uniform: d.u32 } }); + + const rawSnippet = tgpu['~unstable'].rawCodeSnippet( + 'myLayout.$.myBuffer', + d.u32, + 'uniform', + ).$uses({ myLayout }); + + const myFn = tgpu.fn([], d.u32)(() => { + return rawSnippet.$; + }); + + expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` + "@group(0) @binding(0) var myBuffer: u32; + + fn myFn() -> u32 { + return myBuffer; + }" + `); + }); + + it('should not duplicate dependencies', ({ root }) => { + const myBuffer = root.createUniform(d.u32, 7); + + const rawSnippet = tgpu['~unstable'].rawCodeSnippet( + 'myBuffer', + d.u32, + 'uniform', + ).$uses({ myBuffer }); + + const myFn = tgpu.fn([], d.u32)(() => { + return myBuffer.$ + rawSnippet.$; + }); + + expect(tgpu.resolve([myFn])).toMatchInlineSnapshot(` + "@group(0) @binding(0) var myBuffer: u32; + + fn myFn() -> u32 { + return (myBuffer + myBuffer); + }" + `); + }); +}); diff --git a/packages/typegpu/tests/tgsl/shellless.test.ts b/packages/typegpu/tests/tgsl/shellless.test.ts index 12b98b6399..05d697719c 100644 --- a/packages/typegpu/tests/tgsl/shellless.test.ts +++ b/packages/typegpu/tests/tgsl/shellless.test.ts @@ -55,7 +55,7 @@ describe('shellless', () => { return dot(a, a); } - fn item_0() -> f32 { + fn foo() -> f32 { return (dot2(vec2f(1, 2)) + dot2_1(vec3f(3, 4, 5))); }" `); diff --git a/packages/unplugin-typegpu/test/tgsl-transpiling.test.ts b/packages/unplugin-typegpu/test/tgsl-transpiling.test.ts index 358db1b119..12270509e2 100644 --- a/packages/unplugin-typegpu/test/tgsl-transpiling.test.ts +++ b/packages/unplugin-typegpu/test/tgsl-transpiling.test.ts @@ -179,6 +179,48 @@ describe('[BABEL] plugin for transpiling tgsl functions to tinyest', () => { }) && $.f)({}));" `); }); + + it('correctly lists "this" in externals', () => { + const code = ` + import tgpu from 'typegpu'; + import * as d from 'typegpu/data'; + + const root = await tgpu.init(); + + class MyController { + myBuffer = root.createUniform(d.u32); + myFn = tgpu.fn([], d.u32)(() => { + return this.myBuffer.$; + }); + } + + const myController = new MyController(); + + console.log(tgpu.resolve([myController.myFn]));`; + + expect(babelTransform(code)).toMatchInlineSnapshot(` + "import tgpu from 'typegpu'; + import * as d from 'typegpu/data'; + const root = await tgpu.init(); + class MyController { + myBuffer = root.createUniform(d.u32); + myFn = tgpu.fn([], d.u32)(($ => (globalThis.__TYPEGPU_META__ ??= new WeakMap()).set($.f = () => { + return this.myBuffer.$; + }, { + v: 1, + name: void 0, + ast: {"params":[],"body":[0,[[10,[7,[7,"this","myBuffer"],"$"]]]],"externalNames":["this"]}, + externals: () => { + return { + this: this + }; + } + }) && $.f)({})); + } + const myController = new MyController(); + console.log(tgpu.resolve([myController.myFn]));" + `); + }); }); describe('[ROLLUP] plugin for transpiling tgsl functions to tinyest', () => { @@ -292,4 +334,47 @@ describe('[ROLLUP] plugin for transpiling tgsl functions to tinyest', () => { " `); }); + + it('correctly lists "this" in externals', async () => { + const code = ` + import tgpu from 'typegpu'; + import * as d from 'typegpu/data'; + + const root = await tgpu.init(); + + class MyController { + myBuffer = root.createUniform(d.u32); + myFn = tgpu.fn([], d.u32)(() => { + return this.myBuffer.$; + }); + } + + const myController = new MyController(); + + console.log(tgpu.resolve([myController.myFn]));`; + + expect(await rollupTransform(code)).toMatchInlineSnapshot(` + "import tgpu from 'typegpu'; + import * as d from 'typegpu/data'; + + const root = await tgpu.init(); + + class MyController { + myBuffer = root.createUniform(d.u32); + myFn = tgpu.fn([], d.u32)((($ => (globalThis.__TYPEGPU_META__ ??= new WeakMap()).set($.f = (() => { + return this.myBuffer.$; + }), { + v: 1, + name: undefined, + ast: {"params":[],"body":[0,[[10,[7,[7,"this","myBuffer"],"$"]]]],"externalNames":["this"]}, + externals: () => ({"this": this}), + }) && $.f)({}))); + } + + const myController = new MyController(); + + console.log(tgpu.resolve([myController.myFn])); + " + `); + }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9c4b6ce12..13d971a122 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,8 +21,8 @@ catalogs: version: 2.1.25 example: three: - specifier: ^0.180.0 - version: 0.180.0 + specifier: ^0.181.0 + version: 0.181.2 wgpu-matrix: specifier: ^3.4.0 version: 3.4.0 @@ -39,14 +39,14 @@ catalogs: version: 3.2.4 types: '@types/three': - specifier: ^0.180.0 - version: 0.180.0 + specifier: ^0.181.0 + version: 0.181.0 '@webgpu/types': specifier: ^0.1.66 version: 0.1.66 typescript: - specifier: ^5.8.2 - version: 5.8.3 + specifier: ^5.9.3 + version: 5.9.3 overrides: rollup: 4.34.8 @@ -63,7 +63,7 @@ importers: version: link:packages/tgpu-dev-cli '@vitest/browser': specifier: ^3.2.4 - version: 3.2.4(msw@2.10.2(@types/node@24.10.0)(typescript@5.8.3))(vite@6.3.6(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4) + version: 3.2.4(msw@2.10.2(@types/node@24.10.0)(typescript@5.9.3))(vite@6.3.6(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4) '@vitest/coverage-v8': specifier: 3.1.2 version: 3.1.2(@vitest/browser@3.2.4)(vitest@3.2.4) @@ -81,10 +81,10 @@ importers: version: 0.0.41 tsup: specifier: catalog:build - version: 8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1) + version: 8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) typescript: specifier: catalog:types - version: 5.8.3 + version: 5.9.3 unplugin-typegpu: specifier: workspace:* version: link:packages/unplugin-typegpu @@ -93,7 +93,7 @@ importers: version: 9.0.0(rollup@4.34.8) vitest: specifier: catalog:test - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(@vitest/browser@3.2.4)(jiti@2.6.0)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.10.2(@types/node@24.10.0)(typescript@5.8.3))(tsx@4.20.6)(yaml@2.8.1) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(@vitest/browser@3.2.4)(jiti@2.6.0)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.10.2(@types/node@24.10.0)(typescript@5.9.3))(tsx@4.20.6)(yaml@2.8.1) apps/bun-example: dependencies: @@ -124,7 +124,7 @@ importers: dependencies: '@astrojs/check': specifier: ^0.9.4 - version: 0.9.4(prettier@3.6.2)(typescript@5.8.3) + version: 0.9.4(prettier@3.6.2)(typescript@5.9.3) '@astrojs/react': specifier: ^4.3.1 version: 4.3.1(@types/node@24.10.0)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(jiti@2.6.0)(lightningcss@1.30.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tsx@4.20.6)(yaml@2.8.1) @@ -133,13 +133,13 @@ importers: version: 3.6.0 '@astrojs/starlight': specifier: ^0.36.1 - version: 0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)) + version: 0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)) '@astrojs/starlight-tailwind': specifier: ^4.0.1 - version: 4.0.1(@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)))(tailwindcss@4.1.11) + version: 4.0.1(@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)))(tailwindcss@4.1.11) '@astrojs/tailwind': specifier: ^6.0.2 - version: 6.0.2(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1))(tailwindcss@4.1.11) + version: 6.0.2(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(tailwindcss@4.1.11) '@babel/standalone': specifier: ^7.27.0 version: 7.27.0 @@ -193,13 +193,13 @@ importers: version: 2.1.25 astro: specifier: ^5.14.5 - version: 5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1) + version: 5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) classnames: specifier: ^2.5.1 version: 2.5.1 expressive-code-twoslash: specifier: ^0.5.3 - version: 0.5.3(@expressive-code/core@0.41.2)(expressive-code@0.41.2)(typescript@5.8.3) + version: 0.5.3(@expressive-code/core@0.41.2)(expressive-code@0.41.2)(typescript@5.9.3) fuse.js: specifier: catalog:frontend version: 7.1.0 @@ -253,28 +253,28 @@ importers: version: 0.34.2 starlight-blog: specifier: ^0.23.2 - version: 0.23.2(@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)))(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)) + version: 0.23.2(@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)))(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)) starlight-typedoc: specifier: ^0.19.0 - version: 0.19.0(@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)))(typedoc-plugin-markdown@4.3.0(typedoc@0.27.9(typescript@5.8.3)))(typedoc@0.27.9(typescript@5.8.3)) + version: 0.19.0(@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)))(typedoc-plugin-markdown@4.3.0(typedoc@0.27.9(typescript@5.9.3)))(typedoc@0.27.9(typescript@5.9.3)) three: specifier: catalog:example - version: 0.180.0 + version: 0.181.2 tinybench: specifier: ^3.1.0 version: 3.1.1 typedoc: specifier: ^0.27.9 - version: 0.27.9(typescript@5.8.3) + version: 0.27.9(typescript@5.9.3) typedoc-plugin-markdown: specifier: 4.3.0 - version: 4.3.0(typedoc@0.27.9(typescript@5.8.3)) + version: 4.3.0(typedoc@0.27.9(typescript@5.9.3)) typegpu: specifier: workspace:* version: link:../../packages/typegpu typescript: specifier: catalog:types - version: 5.8.3 + version: 5.9.3 unplugin-typegpu: specifier: workspace:* version: link:../../packages/unplugin-typegpu @@ -302,7 +302,7 @@ importers: version: 24.10.0 '@types/three': specifier: catalog:types - version: 0.180.0 + version: 0.181.0 '@vitejs/plugin-basic-ssl': specifier: ^2.1.0 version: 2.1.0(vite@6.3.6(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1)) @@ -378,7 +378,7 @@ importers: devDependencies: vitest: specifier: catalog:test - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(@vitest/browser@3.2.4)(jiti@2.6.0)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.10.2(@types/node@24.10.0)(typescript@5.8.3))(tsx@4.20.6)(yaml@2.8.1) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(@vitest/browser@3.2.4)(jiti@2.6.0)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.10.2(@types/node@24.10.0)(typescript@5.9.3))(tsx@4.20.6)(yaml@2.8.1) packages/tgpu-wgsl-parser: dependencies: @@ -403,16 +403,16 @@ importers: version: 0.5.2 tsup: specifier: catalog:build - version: 8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1) + version: 8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) tsx: specifier: ^4.20.6 version: 4.20.6 typescript: specifier: catalog:types - version: 5.8.3 + version: 5.9.3 vitest: specifier: catalog:test - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(@vitest/browser@3.2.4)(jiti@2.6.0)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.10.2(@types/node@24.10.0)(typescript@5.8.3))(tsx@4.20.6)(yaml@2.8.1) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(@vitest/browser@3.2.4)(jiti@2.6.0)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.10.2(@types/node@24.10.0)(typescript@5.9.3))(tsx@4.20.6)(yaml@2.8.1) publishDirectory: dist packages/tinyest: @@ -422,10 +422,10 @@ importers: version: link:../tgpu-dev-cli tsup: specifier: catalog:build - version: 8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1) + version: 8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) typescript: specifier: catalog:types - version: 5.8.3 + version: 5.9.3 publishDirectory: dist packages/tinyest-for-wgsl: @@ -448,10 +448,10 @@ importers: version: 8.14.1 tsup: specifier: catalog:build - version: 8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1) + version: 8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) typescript: specifier: catalog:types - version: 5.8.3 + version: 5.9.3 publishDirectory: dist packages/typegpu: @@ -465,7 +465,7 @@ importers: devDependencies: '@ark/attest': specifier: ^0.53.0 - version: 0.53.0(typescript@5.8.3) + version: 0.53.0(typescript@5.9.3) '@typegpu/tgpu-dev-cli': specifier: workspace:* version: link:../tgpu-dev-cli @@ -483,10 +483,10 @@ importers: version: 27.0.0(canvas@3.2.0)(postcss@8.5.6) tsup: specifier: catalog:build - version: 8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1) + version: 8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) typescript: specifier: catalog:types - version: 5.8.3 + version: 5.9.3 unplugin-typegpu: specifier: workspace:* version: link:../unplugin-typegpu @@ -508,10 +508,10 @@ importers: version: link:../typegpu typescript: specifier: catalog:types - version: 5.8.3 + version: 5.9.3 unbuild: specifier: catalog:build - version: 3.5.0(typescript@5.8.3) + version: 3.5.0(typescript@5.9.3) unplugin-typegpu: specifier: workspace:* version: link:../unplugin-typegpu @@ -530,10 +530,10 @@ importers: version: link:../typegpu typescript: specifier: catalog:types - version: 5.8.3 + version: 5.9.3 unbuild: specifier: catalog:build - version: 3.5.0(typescript@5.8.3) + version: 3.5.0(typescript@5.9.3) unplugin-typegpu: specifier: workspace:* version: link:../unplugin-typegpu @@ -552,10 +552,10 @@ importers: version: link:../typegpu typescript: specifier: catalog:types - version: 5.8.3 + version: 5.9.3 unbuild: specifier: catalog:build - version: 3.5.0(typescript@5.8.3) + version: 3.5.0(typescript@5.9.3) unplugin-typegpu: specifier: workspace:* version: link:../unplugin-typegpu @@ -574,10 +574,10 @@ importers: version: link:../typegpu typescript: specifier: catalog:types - version: 5.8.3 + version: 5.9.3 unbuild: specifier: catalog:build - version: 3.5.0(typescript@5.8.3) + version: 3.5.0(typescript@5.9.3) unplugin-typegpu: specifier: workspace:* version: link:../unplugin-typegpu @@ -594,7 +594,7 @@ importers: version: link:../tgpu-dev-cli '@types/three': specifier: catalog:types - version: 0.180.0 + version: 0.181.0 '@webgpu/types': specifier: catalog:types version: 0.1.66 @@ -603,10 +603,10 @@ importers: version: link:../typegpu typescript: specifier: catalog:types - version: 5.8.3 + version: 5.9.3 unbuild: specifier: catalog:build - version: 3.5.0(typescript@5.8.3) + version: 3.5.0(typescript@5.9.3) unplugin-typegpu: specifier: workspace:* version: link:../unplugin-typegpu @@ -683,10 +683,10 @@ importers: version: 4.34.8 tsup: specifier: catalog:build - version: 8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1) + version: 8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) typescript: specifier: catalog:types - version: 5.8.3 + version: 5.9.3 publishDirectory: dist packages: @@ -2629,8 +2629,8 @@ packages: '@types/statuses@2.0.6': resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} - '@types/three@0.180.0': - resolution: {integrity: sha512-ykFtgCqNnY0IPvDro7h+9ZeLY+qjgUWv+qEvUt84grhenO60Hqd4hScHE7VTB9nOQ/3QM8lkbNE+4vKjEpUxKg==} + '@types/three@0.181.0': + resolution: {integrity: sha512-MLF1ks8yRM2k71D7RprFpDb9DOX0p22DbdPqT/uAkc6AtQXjxWCVDjCy23G9t1o8HcQPk7woD2NIyiaWcWPYmA==} '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} @@ -5633,8 +5633,8 @@ packages: three@0.178.0: resolution: {integrity: sha512-ybFIB0+x8mz0wnZgSGy2MO/WCO6xZhQSZnmfytSPyNpM0sBafGRVhdaj+erYh5U+RhQOAg/eXqw5uVDiM2BjhQ==} - three@0.180.0: - resolution: {integrity: sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==} + three@0.181.2: + resolution: {integrity: sha512-k/CjiZ80bYss6Qs7/ex1TBlPD11whT9oKfT8oTGiHa34W4JRd1NiH/Tr1DbHWQ2/vMUypxksLnF2CfmlmM5XFQ==} through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} @@ -5809,6 +5809,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + uc.micro@2.1.0: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} @@ -6411,16 +6416,16 @@ snapshots: '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 - '@ark/attest@0.53.0(typescript@5.8.3)': + '@ark/attest@0.53.0(typescript@5.9.3)': dependencies: '@ark/fs': 0.53.0 '@ark/util': 0.53.0 '@prettier/sync': 0.6.1(prettier@3.6.2) '@typescript/analyze-trace': 0.10.1 - '@typescript/vfs': 1.6.1(typescript@5.8.3) + '@typescript/vfs': 1.6.1(typescript@5.9.3) arktype: 2.1.25 prettier: 3.6.2 - typescript: 5.8.3 + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -6450,12 +6455,12 @@ snapshots: '@asamuzakjp/nwsapi@2.3.9': {} - '@astrojs/check@0.9.4(prettier@3.6.2)(typescript@5.8.3)': + '@astrojs/check@0.9.4(prettier@3.6.2)(typescript@5.9.3)': dependencies: - '@astrojs/language-server': 2.15.4(prettier@3.6.2)(typescript@5.8.3) + '@astrojs/language-server': 2.15.4(prettier@3.6.2)(typescript@5.9.3) chokidar: 4.0.3 kleur: 4.1.5 - typescript: 5.8.3 + typescript: 5.9.3 yargs: 17.7.2 transitivePeerDependencies: - prettier @@ -6469,12 +6474,12 @@ snapshots: '@astrojs/internal-helpers@0.7.4': {} - '@astrojs/language-server@2.15.4(prettier@3.6.2)(typescript@5.8.3)': + '@astrojs/language-server@2.15.4(prettier@3.6.2)(typescript@5.9.3)': dependencies: '@astrojs/compiler': 2.13.0 '@astrojs/yaml2ts': 0.2.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@volar/kit': 2.4.11(typescript@5.8.3) + '@volar/kit': 2.4.11(typescript@5.9.3) '@volar/language-core': 2.4.11 '@volar/language-server': 2.4.11 '@volar/language-service': 2.4.11 @@ -6572,12 +6577,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/mdx@4.2.6(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1))': + '@astrojs/mdx@4.2.6(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))': dependencies: '@astrojs/markdown-remark': 6.3.1 '@mdx-js/mdx': 3.1.0(acorn@8.15.0) acorn: 8.15.0 - astro: 5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1) + astro: 5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) es-module-lexer: 1.7.0 estree-util-visit: 2.0.0 hast-util-to-html: 9.0.5 @@ -6633,22 +6638,22 @@ snapshots: stream-replace-string: 2.0.0 zod: 3.25.76 - '@astrojs/starlight-tailwind@4.0.1(@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)))(tailwindcss@4.1.11)': + '@astrojs/starlight-tailwind@4.0.1(@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)))(tailwindcss@4.1.11)': dependencies: - '@astrojs/starlight': 0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)) + '@astrojs/starlight': 0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)) tailwindcss: 4.1.11 - '@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1))': + '@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))': dependencies: '@astrojs/markdown-remark': 6.3.6 - '@astrojs/mdx': 4.2.6(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)) + '@astrojs/mdx': 4.2.6(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)) '@astrojs/sitemap': 3.6.0 '@pagefind/default-ui': 1.3.0 '@types/hast': 3.0.4 '@types/js-yaml': 4.0.9 '@types/mdast': 4.0.4 - astro: 5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1) - astro-expressive-code: 0.41.2(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)) + astro: 5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) + astro-expressive-code: 0.41.2(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)) bcp-47: 2.1.0 hast-util-from-html: 2.0.3 hast-util-select: 6.0.4 @@ -6671,9 +6676,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/tailwind@6.0.2(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1))(tailwindcss@4.1.11)': + '@astrojs/tailwind@6.0.2(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(tailwindcss@4.1.11)': dependencies: - astro: 5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1) + astro: 5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) autoprefixer: 10.4.21(postcss@8.5.3) postcss: 8.5.3 postcss-load-config: 4.0.2(postcss@8.5.3) @@ -8314,7 +8319,7 @@ snapshots: '@types/statuses@2.0.6': optional: true - '@types/three@0.180.0': + '@types/three@0.181.0': dependencies: '@dimforge/rapier3d-compat': 0.12.0 '@tweenjs/tween.js': 23.1.3 @@ -8348,10 +8353,10 @@ snapshots: treeify: 1.1.0 yargs: 16.2.0 - '@typescript/vfs@1.6.1(typescript@5.8.3)': + '@typescript/vfs@1.6.1(typescript@5.9.3)': dependencies: debug: 4.4.3 - typescript: 5.8.3 + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -8373,16 +8378,16 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/browser@3.2.4(msw@2.10.2(@types/node@24.10.0)(typescript@5.8.3))(vite@6.3.6(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)': + '@vitest/browser@3.2.4(msw@2.10.2(@types/node@24.10.0)(typescript@5.9.3))(vite@6.3.6(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)': dependencies: '@testing-library/dom': 10.4.0 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.0) - '@vitest/mocker': 3.2.4(msw@2.10.2(@types/node@24.10.0)(typescript@5.8.3))(vite@6.3.6(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(msw@2.10.2(@types/node@24.10.0)(typescript@5.9.3))(vite@6.3.6(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1)) '@vitest/utils': 3.2.4 magic-string: 0.30.17 sirv: 3.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(@vitest/browser@3.2.4)(jiti@2.6.0)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.10.2(@types/node@24.10.0)(typescript@5.8.3))(tsx@4.20.6)(yaml@2.8.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(@vitest/browser@3.2.4)(jiti@2.6.0)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.10.2(@types/node@24.10.0)(typescript@5.9.3))(tsx@4.20.6)(yaml@2.8.1) ws: 8.18.3 transitivePeerDependencies: - bufferutil @@ -8404,9 +8409,9 @@ snapshots: std-env: 3.9.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(@vitest/browser@3.2.4)(jiti@2.6.0)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.10.2(@types/node@24.10.0)(typescript@5.8.3))(tsx@4.20.6)(yaml@2.8.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(@vitest/browser@3.2.4)(jiti@2.6.0)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.10.2(@types/node@24.10.0)(typescript@5.9.3))(tsx@4.20.6)(yaml@2.8.1) optionalDependencies: - '@vitest/browser': 3.2.4(msw@2.10.2(@types/node@24.10.0)(typescript@5.8.3))(vite@6.3.6(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4) + '@vitest/browser': 3.2.4(msw@2.10.2(@types/node@24.10.0)(typescript@5.9.3))(vite@6.3.6(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4) transitivePeerDependencies: - supports-color @@ -8418,13 +8423,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(msw@2.10.2(@types/node@24.10.0)(typescript@5.8.3))(vite@6.3.6(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))': + '@vitest/mocker@3.2.4(msw@2.10.2(@types/node@24.10.0)(typescript@5.9.3))(vite@6.3.6(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: - msw: 2.10.2(@types/node@24.10.0)(typescript@5.8.3) + msw: 2.10.2(@types/node@24.10.0)(typescript@5.9.3) vite: 6.3.6(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) '@vitest/pretty-format@3.2.4': @@ -8453,12 +8458,12 @@ snapshots: loupe: 3.1.4 tinyrainbow: 2.0.0 - '@volar/kit@2.4.11(typescript@5.8.3)': + '@volar/kit@2.4.11(typescript@5.9.3)': dependencies: '@volar/language-service': 2.4.11 '@volar/typescript': 2.4.11 typesafe-path: 0.2.2 - typescript: 5.8.3 + typescript: 5.9.3 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 @@ -8591,9 +8596,9 @@ snapshots: astring@1.9.0: {} - astro-expressive-code@0.41.2(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)): + astro-expressive-code@0.41.2(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)): dependencies: - astro: 5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1) + astro: 5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) rehype-expressive-code: 0.41.2 astro-remote@0.3.3: @@ -8612,7 +8617,7 @@ snapshots: '@vtbag/turn-signal': 1.3.1 '@vtbag/utensil-drawer': 1.2.11 - astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1): + astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1): dependencies: '@astrojs/compiler': 2.13.0 '@astrojs/internal-helpers': 0.7.4 @@ -8662,7 +8667,7 @@ snapshots: smol-toml: 1.4.2 tinyexec: 1.0.1 tinyglobby: 0.2.15 - tsconfck: 3.1.6(typescript@5.8.3) + tsconfck: 3.1.6(typescript@5.9.3) ultrahtml: 1.6.0 unifont: 0.6.0 unist-util-visit: 5.0.0 @@ -8675,7 +8680,7 @@ snapshots: yocto-spinner: 0.2.3 zod: 3.25.76 zod-to-json-schema: 3.24.6(zod@3.25.76) - zod-to-ts: 1.2.0(typescript@5.8.3)(zod@3.25.76) + zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76) optionalDependencies: sharp: 0.34.2 transitivePeerDependencies: @@ -9332,7 +9337,7 @@ snapshots: glob: 10.4.5 ora: 5.4.1 tslib: 2.8.1 - typescript: 5.8.3 + typescript: 5.9.3 yargs: 17.7.2 dset@3.1.4: {} @@ -9506,15 +9511,15 @@ snapshots: expect-type@1.2.1: {} - expressive-code-twoslash@0.5.3(@expressive-code/core@0.41.2)(expressive-code@0.41.2)(typescript@5.8.3): + expressive-code-twoslash@0.5.3(@expressive-code/core@0.41.2)(expressive-code@0.41.2)(typescript@5.9.3): dependencies: '@expressive-code/core': 0.41.2 expressive-code: 0.41.2 mdast-util-from-markdown: 2.0.2 mdast-util-gfm: 3.1.0 mdast-util-to-hast: 13.2.0 - twoslash: 0.2.12(typescript@5.8.3) - typescript: 5.8.3 + twoslash: 0.2.12(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -10821,7 +10826,7 @@ snapshots: mkdirp@3.0.1: {} - mkdist@2.2.0(typescript@5.8.3): + mkdist@2.2.0(typescript@5.9.3): dependencies: autoprefixer: 10.4.21(postcss@8.5.6) citty: 0.1.6 @@ -10837,7 +10842,7 @@ snapshots: semver: 7.7.3 tinyglobby: 0.2.15 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.3 mlly@1.7.4: dependencies: @@ -10875,7 +10880,7 @@ snapshots: ms@2.1.3: {} - msw@2.10.2(@types/node@24.10.0)(typescript@5.8.3): + msw@2.10.2(@types/node@24.10.0)(typescript@5.9.3): dependencies: '@bundled-es-modules/cookie': 2.0.1 '@bundled-es-modules/statuses': 1.0.1 @@ -10896,7 +10901,7 @@ snapshots: type-fest: 4.41.0 yargs: 17.7.2 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.3 transitivePeerDependencies: - '@types/node' optional: true @@ -11728,11 +11733,11 @@ snapshots: '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.33 '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.33 - rollup-plugin-dts@6.1.1(rollup@4.34.8)(typescript@5.8.3): + rollup-plugin-dts@6.1.1(rollup@4.34.8)(typescript@5.9.3): dependencies: magic-string: 0.30.19 rollup: 4.34.8 - typescript: 5.8.3 + typescript: 5.9.3 optionalDependencies: '@babel/code-frame': 7.27.1 @@ -11908,12 +11913,12 @@ snapshots: stackback@0.0.2: {} - starlight-blog@0.23.2(@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)))(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)): + starlight-blog@0.23.2(@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)))(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)): dependencies: '@astrojs/markdown-remark': 6.3.1 - '@astrojs/mdx': 4.2.6(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)) + '@astrojs/mdx': 4.2.6(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)) '@astrojs/rss': 4.0.11 - '@astrojs/starlight': 0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)) + '@astrojs/starlight': 0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)) astro-remote: 0.3.3 github-slugger: 2.0.0 marked: 15.0.7 @@ -11925,12 +11930,12 @@ snapshots: - astro - supports-color - starlight-typedoc@0.19.0(@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)))(typedoc-plugin-markdown@4.3.0(typedoc@0.27.9(typescript@5.8.3)))(typedoc@0.27.9(typescript@5.8.3)): + starlight-typedoc@0.19.0(@astrojs/starlight@0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)))(typedoc-plugin-markdown@4.3.0(typedoc@0.27.9(typescript@5.9.3)))(typedoc@0.27.9(typescript@5.9.3)): dependencies: - '@astrojs/starlight': 0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1)) + '@astrojs/starlight': 0.36.1(astro@5.14.5(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(rollup@4.34.8)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)) github-slugger: 2.0.0 - typedoc: 0.27.9(typescript@5.8.3) - typedoc-plugin-markdown: 4.3.0(typedoc@0.27.9(typescript@5.8.3)) + typedoc: 0.27.9(typescript@5.9.3) + typedoc-plugin-markdown: 4.3.0(typedoc@0.27.9(typescript@5.9.3)) state-local@1.0.7: {} @@ -12090,7 +12095,7 @@ snapshots: three@0.178.0: {} - three@0.180.0: {} + three@0.181.2: {} through2@4.0.2: dependencies: @@ -12164,13 +12169,13 @@ snapshots: ts-interface-checker@0.1.13: {} - tsconfck@3.1.6(typescript@5.8.3): + tsconfck@3.1.6(typescript@5.9.3): optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.3 tslib@2.8.1: {} - tsup@8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.8.3)(yaml@2.8.1): + tsup@8.5.0(jiti@2.6.0)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1): dependencies: bundle-require: 5.1.0(esbuild@0.25.10) cac: 6.7.14 @@ -12191,7 +12196,7 @@ snapshots: tree-kill: 1.2.2 optionalDependencies: postcss: 8.5.6 - typescript: 5.8.3 + typescript: 5.9.3 transitivePeerDependencies: - jiti - supports-color @@ -12212,11 +12217,11 @@ snapshots: twoslash-protocol@0.2.12: {} - twoslash@0.2.12(typescript@5.8.3): + twoslash@0.2.12(typescript@5.9.3): dependencies: - '@typescript/vfs': 1.6.1(typescript@5.8.3) + '@typescript/vfs': 1.6.1(typescript@5.9.3) twoslash-protocol: 0.2.12 - typescript: 5.8.3 + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -12228,17 +12233,17 @@ snapshots: typed-binary@4.3.2: {} - typedoc-plugin-markdown@4.3.0(typedoc@0.27.9(typescript@5.8.3)): + typedoc-plugin-markdown@4.3.0(typedoc@0.27.9(typescript@5.9.3)): dependencies: - typedoc: 0.27.9(typescript@5.8.3) + typedoc: 0.27.9(typescript@5.9.3) - typedoc@0.27.9(typescript@5.8.3): + typedoc@0.27.9(typescript@5.9.3): dependencies: '@gerrit0/mini-shiki': 1.27.2 lunr: 2.3.9 markdown-it: 14.1.0 minimatch: 9.0.5 - typescript: 5.8.3 + typescript: 5.9.3 yaml: 2.8.1 typesafe-path@0.2.2: {} @@ -12249,13 +12254,15 @@ snapshots: typescript@5.8.3: {} + typescript@5.9.3: {} + uc.micro@2.1.0: {} ufo@1.6.1: {} ultrahtml@1.6.0: {} - unbuild@3.5.0(typescript@5.8.3): + unbuild@3.5.0(typescript@5.9.3): dependencies: '@rollup/plugin-alias': 5.1.1(rollup@4.34.8) '@rollup/plugin-commonjs': 28.0.3(rollup@4.34.8) @@ -12271,18 +12278,18 @@ snapshots: hookable: 5.5.3 jiti: 2.6.0 magic-string: 0.30.19 - mkdist: 2.2.0(typescript@5.8.3) + mkdist: 2.2.0(typescript@5.9.3) mlly: 1.7.4 pathe: 2.0.3 pkg-types: 2.1.0 pretty-bytes: 6.1.1 rollup: 4.34.8 - rollup-plugin-dts: 6.1.1(rollup@4.34.8)(typescript@5.8.3) + rollup-plugin-dts: 6.1.1(rollup@4.34.8)(typescript@5.9.3) scule: 1.3.0 tinyglobby: 0.2.15 untyped: 2.0.0 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.3 transitivePeerDependencies: - sass - vue @@ -12512,11 +12519,11 @@ snapshots: optionalDependencies: vite: 6.3.6(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) - vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(@vitest/browser@3.2.4)(jiti@2.6.0)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.10.2(@types/node@24.10.0)(typescript@5.8.3))(tsx@4.20.6)(yaml@2.8.1): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.0)(@vitest/browser@3.2.4)(jiti@2.6.0)(jsdom@27.0.0(canvas@3.2.0)(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.10.2(@types/node@24.10.0)(typescript@5.9.3))(tsx@4.20.6)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.10.2(@types/node@24.10.0)(typescript@5.8.3))(vite@6.3.6(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(msw@2.10.2(@types/node@24.10.0)(typescript@5.9.3))(vite@6.3.6(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -12540,7 +12547,7 @@ snapshots: optionalDependencies: '@types/debug': 4.1.12 '@types/node': 24.10.0 - '@vitest/browser': 3.2.4(msw@2.10.2(@types/node@24.10.0)(typescript@5.8.3))(vite@6.3.6(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4) + '@vitest/browser': 3.2.4(msw@2.10.2(@types/node@24.10.0)(typescript@5.9.3))(vite@6.3.6(@types/node@24.10.0)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4) jsdom: 27.0.0(canvas@3.2.0)(postcss@8.5.6) transitivePeerDependencies: - jiti @@ -12829,9 +12836,9 @@ snapshots: dependencies: zod: 3.25.76 - zod-to-ts@1.2.0(typescript@5.8.3)(zod@3.25.76): + zod-to-ts@1.2.0(typescript@5.9.3)(zod@3.25.76): dependencies: - typescript: 5.8.3 + typescript: 5.9.3 zod: 3.25.76 zod@3.25.76: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index edc72964d2..0bbfc52922 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,6 @@ packages: - - "packages/*" - - "apps/*" + - 'packages/*' + - 'apps/*' catalog: arktype: ^2.1.22 @@ -11,14 +11,14 @@ catalogs: unbuild: ^3.5.0 jiti: ^2.6.0 types: - typescript: ^5.8.2 - "@webgpu/types": ^0.1.66 - "@types/three": "^0.180.0" + typescript: ^5.9.3 + '@webgpu/types': ^0.1.66 + '@types/three': '^0.181.0' test: vitest: ^3.2.4 frontend: - "vite-imagetools": ^9.0.0 - "fuse.js": ^7.1.0 + 'vite-imagetools': ^9.0.0 + 'fuse.js': ^7.1.0 example: - "wgpu-matrix": ^3.4.0 - three: ^0.180.0 + 'wgpu-matrix': ^3.4.0 + three: ^0.181.0