Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 47 additions & 4 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "opentui",
"name": "@opentui/core",
"dependencies": {
"@typegpu/noise": "^0.1.0",
"bun-webgpu": "0.1.0",
"jimp": "1.6.0",
"three": "0.177.0",
"typegpu": "^0.7.0",
"unplugin-typegpu": "^0.2.2",
"yoga-layout": "3.2.1",
},
"devDependencies": {
Expand All @@ -23,6 +26,8 @@
},
},
"packages": {
"@babel/standalone": ["@babel/[email protected]", "", {}, "sha512-1kjA8XzBRN68HoDDYKP38bucHtxYWCIX8XdYwe1drRNUOjOVNt8EMy9jiE6UwaGFfU7NOHCG+C8KgBc9CR08nA=="],

"@dimforge/rapier2d-simd-compat": ["@dimforge/[email protected]", "", {}, "sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg=="],

"@dimforge/rapier3d-compat": ["@dimforge/[email protected]", "", {}, "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow=="],
Expand Down Expand Up @@ -83,15 +88,21 @@

"@jimp/utils": ["@jimp/[email protected]", "", { "dependencies": { "@jimp/types": "1.6.0", "tinycolor2": "^1.6.0" } }, "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA=="],

"@jridgewell/sourcemap-codec": ["@jridgewell/[email protected]", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="],

"@tokenizer/token": ["@tokenizer/[email protected]", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],

"@tweenjs/tween.js": ["@tweenjs/[email protected]", "", {}, "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA=="],

"@typegpu/noise": ["@typegpu/[email protected]", "", { "peerDependencies": { "typegpu": "^0.6.0" } }, "sha512-94CQfZhsszv/FEsKdhswa5yWTM0VQIuum4myNuPSDyHKyJfE86xnJPF8LvNPhmYOPkMpY2czyKFVh0CG90fEfw=="],

"@types/bun": ["@types/[email protected]", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="],

"@types/node": ["@types/[email protected]", "", {}, "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="],
"@types/estree": ["@types/[email protected]", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],

"@types/react": ["@types/[email protected]", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
"@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="],

"@types/react": ["@types/[email protected]", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA=="],

"@types/stats.js": ["@types/[email protected]", "", {}, "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA=="],

Expand All @@ -103,6 +114,8 @@

"abort-controller": ["[email protected]", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],

"acorn": ["[email protected]", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],

"any-base": ["[email protected]", "", {}, "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="],

"await-to-js": ["[email protected]", "", {}, "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="],
Expand All @@ -121,6 +134,10 @@

"csstype": ["[email protected]", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],

"defu": ["[email protected]", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],

"estree-walker": ["[email protected]", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],

"event-target-shim": ["[email protected]", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],

"events": ["[email protected]", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
Expand All @@ -141,6 +158,10 @@

"jpeg-js": ["[email protected]", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="],

"magic-string": ["[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],

"magic-string-ast": ["[email protected]", "", { "dependencies": { "magic-string": "^0.30.17" } }, "sha512-8rbuNizut2gW94kv7pqgt0dvk+AHLPVIm0iJtpSgQJ9dx21eWx5SBel8z3jp1xtC0j6/iyK3AWGhAR1H61s7LA=="],

"meshoptimizer": ["[email protected]", "", {}, "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw=="],

"mime": ["[email protected]", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
Expand All @@ -155,8 +176,12 @@

"parse-bmfont-xml": ["[email protected]", "", { "dependencies": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.5.0" } }, "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA=="],

"pathe": ["[email protected]", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],

"peek-readable": ["[email protected]", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="],

"picomatch": ["[email protected]", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],

"pixelmatch": ["[email protected]", "", { "dependencies": { "pngjs": "^6.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q=="],

"planck": ["[email protected]", "", { "peerDependencies": { "stage-js": "^1.0.0-alpha.12" } }, "sha512-mNbhnV3g8X2rwGxzcesjmN8BDA6qfXgQxXVMkWau9MCRlQY0RLNEkyHlVp6yFy/X6qrzAXyNONCnZ1cGDLrNew=="],
Expand Down Expand Up @@ -187,12 +212,28 @@

"tinycolor2": ["[email protected]", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="],

"tinyest": ["[email protected]", "", {}, "sha512-YNHlB8BOXgW6RPzrfqqAkgyY9xj33sjXJcJlOl3MwY0BXXx26m3JUqf5yV8iBdwJPNe51DmxypR9Zbbd266biQ=="],

"tinyest-for-wgsl": ["[email protected]", "", { "dependencies": { "tinyest": "~0.1.1" } }, "sha512-yJ49SoJIpEi4ADsBVNE54GVJ5JZMIAKNkRueeNpYhIiq0z1Nn9THJNMNP1b9HI0VQt7LzCrxT0ZP29muiUtcRg=="],

"token-types": ["[email protected]", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="],

"typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"typed-binary": ["[email protected]", "", {}, "sha512-HT3pIBM2njCZUmeczDaQUUErGiM6GXFCqMsHegE12HCoBtvHCkfR10JJni0TeGOTnLilTd6YFyj+YhflqQDrDQ=="],

"typegpu": ["[email protected]", "", { "dependencies": { "tinyest": "~0.1.1", "typed-binary": "^4.3.1" } }, "sha512-BueQ/74zgUCwqg/nmSxZ2aL7NFnv3jzANlUmgyKbGtKg0jkCu9kUAVtPjwdjWDmlah5WlzAAXi7qD5URlU6wtA=="],

"typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],

"undici-types": ["[email protected]", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],

"unplugin": ["[email protected]", "", { "dependencies": { "acorn": "^8.14.1", "picomatch": "^4.0.2", "webpack-virtual-modules": "^0.6.2" } }, "sha512-RyWSb5AHmGtjjNQ6gIlA67sHOsWpsbWpwDokLwTcejVdOjEkJZh7QKu14J00gDDVSh8kGH4KYC/TNBceXFZhtw=="],

"unplugin-typegpu": ["[email protected]", "", { "dependencies": { "@babel/standalone": "^7.27.0", "defu": "^6.1.4", "estree-walker": "^3.0.3", "magic-string-ast": "^1.0.0", "pathe": "^2.0.3", "picomatch": "^4.0.3", "tinyest": "~0.1.1", "tinyest-for-wgsl": "~0.1.2", "unplugin": "^2.3.5" }, "peerDependencies": { "typegpu": "^0.7.0" } }, "sha512-5pbwv0cTMRMxRCQXEPAMUsUoAKMQsz2B4zlhpRRHNy3qtMk+rP3Uxe/5zZUtr2yAASCVO2iciblH5HqcUoijjQ=="],

"utif2": ["[email protected]", "", { "dependencies": { "pako": "^1.0.11" } }, "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w=="],

"webpack-virtual-modules": ["[email protected]", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],

"xml-parse-from-string": ["[email protected]", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="],

"xml2js": ["[email protected]", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],
Expand All @@ -203,6 +244,8 @@

"zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],

"image-q/@types/node": ["@types/[email protected]", "", {}, "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="],

"pixelmatch/pngjs": ["[email protected]", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
}
}
1 change: 1 addition & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
preload = ["./preload.ts"]
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@
"printWidth": 120
},
"dependencies": {
"@typegpu/noise": "^0.1.0",
"bun-webgpu": "0.1.0",
"jimp": "1.6.0",
"three": "0.177.0",
"typegpu": "^0.7.0",
"unplugin-typegpu": "^0.2.2",
"yoga-layout": "3.2.1"
}
}
8 changes: 8 additions & 0 deletions preload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { plugin } from "bun"
import typegpu from "unplugin-typegpu/bun"

plugin(
typegpu({
include: /\.ts$/,
}),
)
231 changes: 231 additions & 0 deletions src/examples/caustics-demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
#!/usr/bin/env bun

import { perlin3d } from "@typegpu/noise"
import { createWebGPUDevice, setupGlobals } from "bun-webgpu"
import tgpu, { type TgpuRoot } from "typegpu"
import * as d from "typegpu/data"
import * as std from "typegpu/std"
import { CLICanvas, type CliRenderer, GroupRenderable, SuperSampleType } from "../index"

/** With supersampling, the scene is rendered at 2x the resolution */
const pixelRatio = 2
/** Controls the angle of rotation for the pool tile texture */
const angle = 0.2
/** The scene fades into this color at a distance */
const fogColor = d.vec3f(0.05, 0.2, 0.7)
/** The ambient light color */
const ambientColor = d.vec3f(0.2, 0.5, 1)
/** Color tint of the god rays */
const godRayTint = d.vec3f(0.18, 0.3, 0.5)
const tileDensity = 3

const layout = tgpu.bindGroupLayout({
aspect: { uniform: d.f32 },
time: { uniform: d.f32 },
})

const mainVertex = tgpu["~unstable"].vertexFn({
in: { vertexIndex: d.builtin.vertexIndex },
out: { pos: d.builtin.position, uv: d.vec2f },
})(({ vertexIndex }) => {
const pos = [d.vec2f(-1, -1), d.vec2f(3, -1), d.vec2f(-1, 3)]
const left = 0.5 - layout.$.aspect * 0.5
const right = 0.5 + layout.$.aspect * 0.5
const uv = [d.vec2f(left, 0), d.vec2f(right, 0), d.vec2f(left, 2)]

return {
pos: d.vec4f(pos[vertexIndex], 0, 1),
uv: uv[vertexIndex],
}
})

/**
* Given a coordinate, it returns a grayscale floor tile pattern at that
* location.
*/
const tilePattern = tgpu.fn(
[d.vec2f],
d.f32,
)((uv) => {
const tiledUv = std.fract(uv)
const proximity = std.abs(tiledUv.mul(2).sub(1))
const maxProximity = std.max(proximity.x, proximity.y)
return std.clamp(std.pow(1 - maxProximity, 0.8) * 5, 0, 1)
})

const caustics = tgpu.fn(
[d.vec2f, d.f32, d.vec3f],
d.vec3f,
)((uv, time, profile) => {
const distortion = perlin3d.sample(d.vec3f(uv.mul(0.5), time * 0.2))
// Distorting UV coordinates
const uv2 = uv.add(distortion)
const noise = std.abs(perlin3d.sample(d.vec3f(uv2.mul(5), time)))
return std.pow(d.vec3f(1 - noise), profile)
})

/**
* Returns a transformation matrix that represents an `angle` rotation
* in the XY plane (around the imaginary Z axis)
*/
const rotateXY = tgpu.fn(
[d.f32],
d.mat2x2f,
)((angle) => {
return d.mat2x2f(
/* right */ d.vec2f(std.cos(angle), std.sin(angle)),
/* up */ d.vec2f(-std.sin(angle), std.cos(angle)),
)
})

const mainFragment = tgpu["~unstable"].fragmentFn({
in: { uv: d.vec2f },
out: d.vec4f,
})(({ uv }) => {
const time = layout.$.time
/**
* A transformation matrix that skews the perspective a bit
* when applied to UV coordinates
*/
const skewMat = d.mat2x2f(
d.vec2f(std.cos(angle), std.sin(angle)),
d.vec2f(-std.sin(angle) * 5 + uv.x * 2, std.cos(angle) * 5),
)
const skewedUv = skewMat.mul(uv)
const tile = tilePattern(skewedUv.mul(tileDensity))
const albedo = std.mix(d.vec3f(0.1), d.vec3f(1), tile)

// Transforming coordinates to simulate perspective squash
const cuv = d.vec2f(uv.x * (std.pow(uv.y * 1.5, 3) + 0.1) * 5, std.pow((uv.y * 1.5 + 0.1) * 1.5, 3) * 1)
// Generating two layers of caustics (large scale, and small scale)
const c1 = caustics(cuv, time * 0.2, d.vec3f(4, 4, 1))
// Tinting
.mul(d.vec3f(0.4, 0.65, 1))
const c2 = caustics(cuv.mul(2), time * 0.4, d.vec3f(16, 1, 4))
// Tinting
.mul(d.vec3f(0.18, 0.3, 0.5))

// -- BLEND --

const blendCoord = d.vec3f(uv.mul(d.vec2f(5, 10)), layout.$.time * 0.2 + 5)
// A smooth blending factor, so that caustics only appear at certain spots
const blend = std.clamp(perlin3d.sample(blendCoord) + 0.3, 0, 1)

// -- FOG --

const noFogColor = albedo.mul(std.mix(ambientColor, c1.add(c2), blend))
// Fog blending factor, based on the height of the pixels
const fog = std.min(std.pow(uv.y, 0.5) * 1.2, 1)

// -- GOD RAYS --

const godRayUv = rotateXY(-0.3).mul(uv).mul(d.vec2f(10, 2))
const godRay1 = perlin3d.sample(d.vec3f(godRayUv, time * 0.5)) + 1
const godRay2 = perlin3d.sample(d.vec3f(godRayUv.mul(2), time * 0.3)) + 1
const godRayBlend = std.pow(uv.y, 2) * 0.5
const godRays = godRayTint.mul(godRay1 + godRay2).mul(godRayBlend * 0.6)

return d.vec4f(std.mix(noFogColor, fogColor, fog).add(godRays), 1)
})

let isRunning = true
let root: TgpuRoot | undefined
let keyHandler: ((key: Buffer) => void) | undefined
let handleResize: ((width: number, height: number) => void) | undefined
let parentContainer: GroupRenderable | undefined

export async function run(renderer: CliRenderer): Promise<void> {
isRunning = true
renderer.start()
const WIDTH = renderer.terminalWidth
const HEIGHT = renderer.terminalHeight

parentContainer = new GroupRenderable("shader-container", {
x: 0,
y: 0,
zIndex: 10,
visible: true,
})
renderer.add(parentContainer)

// Bun WebGPU setup
setupGlobals()
const device = await createWebGPUDevice()
const canvas = new CLICanvas(device, WIDTH * pixelRatio, HEIGHT * pixelRatio, SuperSampleType.GPU)

root = tgpu.initFromDevice({ device })

/** Seconds passed since the start of the example, wrapped to the range [0, 1000) */
const timeBuffer = root.createBuffer(d.f32).$usage("uniform")
/** Aspect ratio of the canvas */
const aspectBuffer = root.createBuffer(d.f32, WIDTH / HEIGHT).$usage("uniform")

const bindGroup = root.createBindGroup(layout, {
time: timeBuffer,
aspect: aspectBuffer,
})

handleResize = (width: number, height: number) => {
aspectBuffer.write(width / height)
canvas.setSize(width * pixelRatio, height * pixelRatio)
}

renderer.on("resize", handleResize)

// Assuming a format...
const presentationFormat = "rgba8unorm" as const
const context = canvas.getContext("webgpu") as GPUCanvasContext

context.configure({
device: root.device,
format: presentationFormat,
alphaMode: "premultiplied",
})

const pipeline = root["~unstable"]
.withVertex(mainVertex, {})
.withFragment(mainFragment, { format: presentationFormat })
.createPipeline()
// ---
.with(layout, bindGroup)

let time = 0

renderer.setFrameCallback(async (deltaMs) => {
if (!isRunning) return

time += deltaMs / 1000
timeBuffer.write(time)

pipeline
.withColorAttachment({
view: context.getCurrentTexture().createView(),
loadOp: "clear",
storeOp: "store",
})
.draw(3)

await canvas.readPixelsIntoBuffer(renderer.nextRenderBuffer)
})
}

export function destroy(renderer: CliRenderer): void {
isRunning = false
if (keyHandler) {
process.stdin.off("data", keyHandler)
keyHandler = undefined
}

if (handleResize) {
renderer.off("resize", handleResize)
handleResize = undefined
}

renderer.clearFrameCallbacks()
root?.destroy()

if (parentContainer) {
renderer.remove("shader-container")
parentContainer = undefined
}
}
Loading