diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..25fa621
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "typescript.tsdk": "node_modules/typescript/lib"
+}
diff --git a/build.ts b/build.ts
index 0193d84..57247d3 100644
--- a/build.ts
+++ b/build.ts
@@ -31,5 +31,5 @@ const publics = globSync("src/public/*");
for (const file of publics) {
const dest = file.replace("src/public/", "html/");
cpSync(file, dest);
- console.log(chalk.yellow(` -> html/${dest}...`));
+ console.log(chalk.yellow(` -> ${dest}...`));
}
diff --git a/bun.lockb b/bun.lockb
index 01b6040..01d3de1 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/generate.ts b/generate.ts
index 433e247..cc8774f 100644
--- a/generate.ts
+++ b/generate.ts
@@ -2,6 +2,7 @@ import indexTemplate from "./templates/index.html.txt";
import workTemplate from "./templates/work.html.txt";
import readmeTemplate from "./templates/README.md.txt";
import chalk from "chalk";
+import { mkdirSync } from "node:fs";
export const generate = async (works: string[]) => {
const allWorks = works
@@ -12,6 +13,7 @@ export const generate = async (works: string[]) => {
for (const work of allWorks) {
const html = `${workTemplate}`.replace(/##name##/g, work);
+ mkdirSync(`html/${work}`, { recursive: true });
await Bun.write(`html/${work}/index.html`, html);
console.log(chalk.yellow(` -> html/${work}/index.html...`));
}
diff --git a/html/001-platform-provenance/main.js b/html/001-platform-provenance/main.js
index 38350db..d1b453f 100644
--- a/html/001-platform-provenance/main.js
+++ b/html/001-platform-provenance/main.js
@@ -1,4 +1,4 @@
-import{a as k} from"../chunk-9587381547d8adb9.js";class K{constructor(C){this.gl=C.gl,this.app=C,this.program=this.gl.createProgram()}attach(C,R){console.log("attaching shader",{type:C,source:R});const q=this.gl.createShader(C);if(this.gl.shaderSource(q,R),this.gl.compileShader(q),!this.gl.getShaderParameter(q,this.gl.COMPILE_STATUS))throw new Error("An error occurred compiling the shaders: "+this.gl.getShaderInfoLog(q));return this.gl.attachShader(this.program,q),this}link(){if(this.gl.linkProgram(this.program),!this.gl.getProgramParameter(this.program,this.gl.LINK_STATUS))throw new Error("Unable to initialize the shader program: "+this.gl.getProgramInfoLog(this.program));return console.log("shader linked"),this}location(C){if(C[0]==="a")return this.gl.getAttribLocation(this.program,C);else if(C[0]==="u")return this.gl.getUniformLocation(this.program,C)}updateTime(){const C=this.app.now(),R=Math.sin(C),q=Math.cos(C);this.gl.uniform1f(this.location("uTime"),C),this.gl.uniform1f(this.location("uSinTime"),R),this.gl.uniform1f(this.location("uCosTime"),q)}activate(C,R){this.gl.useProgram(this.program),this.gl.uniformMatrix4fv(this.location("uProjectionMatrix"),!1,C),this.gl.uniformMatrix4fv(this.location("uModelViewMatrix"),!1,R),this.updateTime()}}class P{constructor(C){this.gl=C.gl,this.app=C,this.vertexPositions=new Float32Array([]),this.positionBuffer=null,this.textureBuffer=null}initBuffer(C,R=this.gl.STATIC_DRAW){const q=this.gl.createBuffer();return this.gl.bindBuffer(this.gl.ARRAY_BUFFER,q),this.gl.bufferData(this.gl.ARRAY_BUFFER,new Float32Array(C),R),q}attachShader(C){return this.shader=C,this.vertexPosition=C.location("aVertexPosition"),this.textureCoord=C.location("aTextureCoord"),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.positionBuffer),this.gl.vertexAttribPointer(this.vertexPosition,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.vertexPosition),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.textureBuffer),this.gl.vertexAttribPointer(this.textureCoord,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.textureCoord),this}draw2D(){this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.positionBuffer),this.shader.activate(this.app.projectionMatrix,this.app.modelViewMatrix),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,this.vertexPositions.length/2)}draw3D(){this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.positionBuffer),this.shader.activate(this.app.projectionMatrix,this.app.modelViewMatrix),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,this.vertexPositions.length/3)}}class U extends P{constructor(C){super(C);this.vertexPositions=new Float32Array([-1,-1,1,-1,-1,1,1,1]),this.positionBuffer=this.initBuffer(this.vertexPositions),this.textureBuffer=this.initBuffer(new Float32Array([0,0,1,0,0,1,1,1])),this.app.onUpdate(()=>this.draw2D())}}class Y{constructor(C={fov:45}){if(this._now=0,this.registry={onStart:[],onUpdate:[],onBeforeUpdate:[],onAfterUpdate:[]},this.config=C,this.canvas=document.querySelector("canvas"),this.canvas.width=window.innerWidth,this.canvas.height=window.innerHeight,this.gl=this.canvas.getContext("webgl2"),this.gl===null)throw document.querySelector("main").innerHTML="
your browser didn't let me set up webgl
",new Error("Unable to initialize WebGL. Your browser or machine may not support it.");const R=this.gl,q=this.config.fov*Math.PI/180,G=R.canvas.clientWidth/R.canvas.clientHeight,H=0.1,J=100,_=glMatrix.mat4.create();glMatrix.mat4.perspective(_,q,G,H,J);const I=glMatrix.mat4.create();glMatrix.mat4.translate(I,I,[-0,0,-6]),this.projectionMatrix=_,this.modelViewMatrix=I,this.clear(),this.onBeforeUpdate(()=>this.clear()),this.telemetry=new k(this)}clear(){const C=this.gl;C.clearColor(0,0,0,1),C.clearDepth(1),C.enable(C.DEPTH_TEST),C.depthFunc(C.LEQUAL),C.clear(C.COLOR_BUFFER_BIT|C.DEPTH_BUFFER_BIT)}onStart(C){this.registry.onStart.push(C)}onUpdate(C){this.registry.onUpdate.push(C)}onBeforeUpdate(C){this.registry.onBeforeUpdate.push(C)}onAfterUpdate(C){this.registry.onAfterUpdate.push(C)}start(){this.registry.onStart.forEach((C)=>C(this))}update(){this.registry.onBeforeUpdate.forEach((C)=>C(this)),this.registry.onUpdate.forEach((C)=>C(this)),this.registry.onAfterUpdate.forEach((C)=>C(this))}oneShot(){requestAnimationFrame((C)=>{this._now=C,this.start(),this.update()})}loop(){const C=(R)=>{this._now=R,this.update(),requestAnimationFrame(C)};requestAnimationFrame(C)}now(){return this._now}}var v=`precision highp float;
+import{a as Y} from"../chunk-32eefd9b41c928dd.js";class E{constructor(C){this.gl=C.gl,this.app=C,this.program=this.gl.createProgram()}attach(C,D){console.log("attaching shader",{type:C,source:D});const R=this.gl.createShader(C);if(this.gl.shaderSource(R,D),this.gl.compileShader(R),!this.gl.getShaderParameter(R,this.gl.COMPILE_STATUS))throw new Error("An error occurred compiling the shaders: "+this.gl.getShaderInfoLog(R));return this.gl.attachShader(this.program,R),this}link(){if(this.gl.linkProgram(this.program),!this.gl.getProgramParameter(this.program,this.gl.LINK_STATUS))throw new Error("Unable to initialize the shader program: "+this.gl.getProgramInfoLog(this.program));return console.log("shader linked"),this}location(C){if(C[0]==="a")return this.gl.getAttribLocation(this.program,C);else if(C[0]==="u")return this.gl.getUniformLocation(this.program,C)}updateTime(){const C=this.app.now(),D=Math.sin(C),R=Math.cos(C);this.gl.uniform1f(this.location("uTime"),C),this.gl.uniform1f(this.location("uSinTime"),D),this.gl.uniform1f(this.location("uCosTime"),R)}activate(C,D){this.gl.useProgram(this.program),this.gl.uniformMatrix4fv(this.location("uProjectionMatrix"),!1,C),this.gl.uniformMatrix4fv(this.location("uModelViewMatrix"),!1,D),this.updateTime()}}class I{constructor(C){this.gl=C.gl,this.app=C,this.vertexPositions=new Float32Array([]),this.positionBuffer=null,this.textureBuffer=null}initBuffer(C,D=this.gl.STATIC_DRAW){const R=this.gl.createBuffer();return this.gl.bindBuffer(this.gl.ARRAY_BUFFER,R),this.gl.bufferData(this.gl.ARRAY_BUFFER,new Float32Array(C),D),R}attachShader(C){return this.shader=C,this.vertexPosition=C.location("aVertexPosition"),this.textureCoord=C.location("aTextureCoord"),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.positionBuffer),this.gl.vertexAttribPointer(this.vertexPosition,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.vertexPosition),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.textureBuffer),this.gl.vertexAttribPointer(this.textureCoord,2,this.gl.FLOAT,!1,0,0),this.gl.enableVertexAttribArray(this.textureCoord),this}draw2D(){this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.positionBuffer),this.shader.activate(this.app.projectionMatrix,this.app.modelViewMatrix),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,this.vertexPositions.length/2)}draw3D(){this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.positionBuffer),this.shader.activate(this.app.projectionMatrix,this.app.modelViewMatrix),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,this.vertexPositions.length/3)}}class K extends I{constructor(C){super(C);this.vertexPositions=new Float32Array([-1,-1,1,-1,-1,1,1,1]),this.positionBuffer=this.initBuffer(this.vertexPositions),this.textureBuffer=this.initBuffer(new Float32Array([0,0,1,0,0,1,1,1])),this.app.onUpdate(()=>this.draw2D())}}class L{constructor(C={fov:45}){if(this._now=0,this.registry={onStart:[],onUpdate:[],onBeforeUpdate:[],onAfterUpdate:[]},this.config=C,this.canvas=document.querySelector("canvas"),this.canvas.width=window.innerWidth,this.canvas.height=window.innerHeight,this.gl=this.canvas.getContext("webgl2"),this.gl===null)throw document.querySelector("main").innerHTML="your browser didn't let me set up webgl
",new Error("Unable to initialize WebGL. Your browser or machine may not support it.");const D=this.gl,R=this.config.fov*Math.PI/180,G=D.canvas.clientWidth/D.canvas.clientHeight,H=0.1,J=100,P=glMatrix.mat4.create();glMatrix.mat4.perspective(P,R,G,H,J);const q=glMatrix.mat4.create();glMatrix.mat4.translate(q,q,[-0,0,-6]),this.projectionMatrix=P,this.modelViewMatrix=q,this.clear(),this.onBeforeUpdate(()=>this.clear()),this.telemetry=new Y(this)}clear(){const C=this.gl;C.clearColor(0,0,0,1),C.clearDepth(1),C.enable(C.DEPTH_TEST),C.depthFunc(C.LEQUAL),C.clear(C.COLOR_BUFFER_BIT|C.DEPTH_BUFFER_BIT)}onStart(C){this.registry.onStart.push(C)}onUpdate(C){this.registry.onUpdate.push(C)}onBeforeUpdate(C){this.registry.onBeforeUpdate.push(C)}onAfterUpdate(C){this.registry.onAfterUpdate.push(C)}start(){this.registry.onStart.forEach((C)=>C(this))}update(){this.registry.onBeforeUpdate.forEach((C)=>C(this)),this.registry.onUpdate.forEach((C)=>C(this)),this.registry.onAfterUpdate.forEach((C)=>C(this))}oneShot(){requestAnimationFrame((C)=>{this._now=C,this.start(),this.update()})}loop(){const C=(D)=>{this._now=D,this.update(),requestAnimationFrame(C)};requestAnimationFrame(C)}now(){return this._now}}var k=`precision highp float;
uniform float uTime;
uniform float uSinTime;
@@ -32,7 +32,7 @@ void main() {
gl_FragColor = vec4(rgb, 1.0);
gl_FragColor = clamp(gl_FragColor, 0.0, 1.0);
-}`;var B=`attribute vec4 aVertexPosition;
+}`;var A=`attribute vec4 aVertexPosition;
attribute vec2 aTextureCoord;
uniform mat4 uModelViewMatrix;
@@ -43,4 +43,4 @@ varying highp vec2 vTextureCoord;
void main() {
gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
vTextureCoord = aTextureCoord;
-}`;var D=new Y({fov:20}),E=D.gl,W=new K(D).attach(E.VERTEX_SHADER,B).attach(E.FRAGMENT_SHADER,v).link(),X=new U(D);X.attachShader(W);D.loop();
+}`;var _=new L({fov:20}),B=_.gl,U=new E(_).attach(B.VERTEX_SHADER,A).attach(B.FRAGMENT_SHADER,k).link(),W=new K(_);W.attachShader(U);_.loop();
diff --git a/html/002-webgpu-instead/main.js b/html/002-webgpu-instead/main.js
index 4859990..3a1eb9f 100644
--- a/html/002-webgpu-instead/main.js
+++ b/html/002-webgpu-instead/main.js
@@ -1 +1 @@
-import{a as i} from"../chunk-9587381547d8adb9.js";class d{constructor(w){this.config=w,this.canvas=document.querySelector("canvas"),this.canvas.width=window.innerWidth,this.canvas.height=window.innerHeight,this.telemetry=new i(this),this.init().catch((h)=>{throw console.error(h),document.querySelector("main").innerHTML="your browser didn't let me set up webgpu. firefox nightly or enable dom.webgpu.enable
.
",new Error("Unable to initialize WebGPU. Your browser or machine may not support it.",h)})}async init(){if(!navigator.gpu)throw new Error("WebGPU not supported");if(this.adapter=await navigator.gpu.requestAdapter(),!this.adapter)throw new Error("No GPU adapter found");if(this.device=await this.adapter.requestDevice(),!this.device)throw new Error("No GPU device found");this.context=this.canvas.getContext("webgpu")}}var x=new d({fov:20});
+import{a as s} from"../chunk-32eefd9b41c928dd.js";class i{a;canvas;_adapter;_device;_context;telemetry;constructor(a={}){this.config=a;this.config={fov:45,...a},this.canvas=document.querySelector("canvas"),this.canvas.width=window.innerWidth,this.canvas.height=window.innerHeight,this.telemetry=new s(this),this.init().catch((c)=>{const v=document.querySelector("main");if(v)v.innerHTML="your browser didn't let me set up webgpu. firefox nightly or enable dom.webgpu.enable
.
";throw new Error("Unable to initialize WebGPU. Your browser or machine may not support it.",c)})}async init(){if(!navigator.gpu)throw new Error("WebGPU not supported");if(this._adapter=await navigator.gpu.requestAdapter(),!this._adapter)throw new Error("No GPU adapter found");if(this._device=await this.adapter.requestDevice(),!this._device)throw new Error("No GPU device found");this._context=this.canvas.getContext("webgpu"),this.context.configure({device:this.device,format:"bgra8unorm"})}get context(){if(!this._context)throw new Error("WebGPU context not initialized");return this._context}get adapter(){if(!this._adapter)throw new Error("WebGPU adapter not initialized");return this._adapter}get device(){if(!this._device)throw new Error("WebGPU device not initialized");return this._device}}var G=new i({fov:20});
diff --git a/html/chunk-32eefd9b41c928dd.js b/html/chunk-32eefd9b41c928dd.js
new file mode 100644
index 0000000..7964611
--- /dev/null
+++ b/html/chunk-32eefd9b41c928dd.js
@@ -0,0 +1,5 @@
+class h{n;el;frameTimes=[];maxFrameTimes=100;lastFrameTime=0;constructor(n,u="#telemetry"){this.app=n;if(this.el=document.querySelector(u),this.el&&location.search.includes("telemetry"))this.el.style.display="block",this.app.onAfterUpdate(()=>this.onAfterUpdate())}insertTime(n){if(this.frameTimes.push(n),this.frameTimes.length>this.maxFrameTimes)this.frameTimes.shift()}onAfterUpdate(){const n=this.app.now()-this.lastFrameTime;this.insertTime(n);const u=this.frameTimes.reduce((w,d)=>w+d,0)/this.frameTimes.length,p=1000/u;this.el.innerHTML=`
+ ${p.toFixed(1)} FPS (${u.toFixed(3)} ms)
+ bU: ${this.app.registry.onBeforeUpdate.length} | U: ${this.app.registry.onUpdate.length} | aU: ${this.app.registry.onAfterUpdate.length}
+ `,this.lastFrameTime=this.app.now()}}
+export{h as a};
diff --git a/html/chunk-9587381547d8adb9.js b/html/chunk-9587381547d8adb9.js
deleted file mode 100644
index 1b0c0df..0000000
--- a/html/chunk-9587381547d8adb9.js
+++ /dev/null
@@ -1,5 +0,0 @@
-class q{constructor(h,y="#telemetry"){if(this.app=h,this.el=document.querySelector(y),this.el&&location.search.includes("telemetry"))this.el.style.display="block",this.app.onAfterUpdate(()=>this.onAfterUpdate());this.frameTimes=[],this.maxFrameTimes=100,this.lastFrameTime=0}insertTime(h){if(this.frameTimes.push(h),this.frameTimes.length>this.maxFrameTimes)this.frameTimes.shift()}onAfterUpdate(){const h=this.app.now()-this.lastFrameTime;this.insertTime(h);const y=this.frameTimes.reduce((j,k)=>j+k,0)/this.frameTimes.length,f=1000/y;this.el.innerHTML=`
- ${f.toFixed(1)} FPS (${y.toFixed(3)} ms)
- bU: ${this.app.registry.onBeforeUpdate.length} | U: ${this.app.registry.onUpdate.length} | aU: ${this.app.registry.onAfterUpdate.length}
- `,this.lastFrameTime=this.app.now()}}
-export{q as a};
diff --git a/stuff.d.ts b/index.d.ts
similarity index 70%
rename from stuff.d.ts
rename to index.d.ts
index 3457f60..d82326c 100644
--- a/stuff.d.ts
+++ b/index.d.ts
@@ -1,3 +1,6 @@
+///
+///
+
declare module "*.glsl" {
const content: string;
export default content;
diff --git a/package.json b/package.json
index 7e550c6..dd72e8e 100644
--- a/package.json
+++ b/package.json
@@ -9,8 +9,9 @@
"dev": "run-p serve build:watch"
},
"devDependencies": {
+ "@webgpu/types": "^0.1.37",
"bun-types": "latest",
- "npm-run-all2": "^6.0.6",
+ "npm-run-all2": "^6.1.1",
"prettier": "^3.0.3"
},
"peerDependencies": {
@@ -19,6 +20,7 @@
"dependencies": {
"chalk": "^5.3.0",
"glob": "^10.3.10",
- "serve": "^14.2.1"
+ "serve": "^14.2.1",
+ "typescript": "^5.2.2"
}
}
diff --git a/src/002-webgpu-instead/main.ts b/src/002-webgpu-instead/main.ts
index 4032a7d..b63e8d4 100644
--- a/src/002-webgpu-instead/main.ts
+++ b/src/002-webgpu-instead/main.ts
@@ -1,3 +1,8 @@
-import { WebGPUApp } from "../webgpu-app";
+import { WebGPUApp } from "../renderer/webgpu";
const app = new WebGPUApp({ fov: 20 });
+
+// TODO:
+// - plane
+// - white shader
+// - real shader with UVs and uniforms
diff --git a/src/002-webgpu-instead/rainbow-plane.wgsl b/src/002-webgpu-instead/rainbow-plane.wgsl
new file mode 100644
index 0000000..991cbd9
--- /dev/null
+++ b/src/002-webgpu-instead/rainbow-plane.wgsl
@@ -0,0 +1,49 @@
+struct Uniforms {
+ modelViewProjectionMatrix: mat4x4,
+ time: f32,
+}
+
+@group(0) @binding(0) var uniforms: Uniforms;
+
+struct v2f {
+ @builtin(position) position : vec4f,
+ @location(0) uv : vec2f,
+}
+
+@vertex
+fn vertex_main(
+ @builtin(position) position : vec4f,
+ @location(0) uv : vec2f,
+) -> v2f {
+ return v2f(uniforms.modelViewProjectionMatrix * position, uv);
+}
+
+@fragment
+fn fragment_main(
+ @location(0) uv : vec2f,
+) -> @location(0) vec4f {
+ f32 zComponent = sin(uniforms.time) * 0.001 * 0.5 + 0.5;
+ vec3f hsv = vec3f(uv.x, uv.y, zComponent);
+ hsv.x += uniforms.time * 0.0001;
+ hsv.y = 1.0;
+ hsv.z = 1.0;
+ vec3f rgb = hsv2rgb(hsv);
+
+ return saturate(vec4f(rgb, 1.0));
+}
+
+fn rgb2hsv(vec3f c) -> vec3f {
+ vec4f K = vec4f(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
+ vec4f p = mix(vec4f(c.bg, K.wz), vec4f(c.gb, K.xy), step(c.b, c.g));
+ vec4f q = mix(vec4f(p.xyw, c.r), vec4f(c.r, p.yzx), step(p.x, c.r));
+
+ f32 d = q.x - min(q.w, q.y);
+ f32 e = 1.0e-10;
+ return vec3f(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
+}
+
+fn hsv2rgb(vec3f c) -> vec3f {
+ vec4f K = vec4f(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
+ vec3f p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
+ return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
+}
\ No newline at end of file
diff --git a/src/app.js b/src/app.js
index 30db9c5..6c1a6bb 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1,4 +1,4 @@
-import { Telemetry } from "./telemetry.js";
+import { Telemetry } from "./renderer/telemetry";
export class App {
constructor(
diff --git a/src/meshes/plane.ts b/src/meshes/plane.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/renderer/mesh.ts b/src/renderer/mesh.ts
new file mode 100644
index 0000000..c4303cc
--- /dev/null
+++ b/src/renderer/mesh.ts
@@ -0,0 +1,16 @@
+import { Oops, Shader } from "./shader";
+
+export type MeshConfig = {
+ vertexData: Float32Array;
+ vertexSize: number;
+ positionOffset: number;
+ colorOffset: number;
+ uvOffset: number;
+};
+
+export class Mesh {
+ private shader: Shader = Oops;
+
+ constructor(public config: MeshConfig) {}
+ shader(shader: Shader) {}
+}
diff --git a/src/renderer/oops.wgsl b/src/renderer/oops.wgsl
new file mode 100644
index 0000000..a07e038
--- /dev/null
+++ b/src/renderer/oops.wgsl
@@ -0,0 +1,26 @@
+struct Uniforms {
+ modelViewProjectionMatrix: mat4x4,
+ time: f32,
+}
+
+@group(0) @binding(0) var uniforms: Uniforms;
+
+struct v2f {
+ @builtin(position) position : vec4f,
+ @location(0) uv : vec2f,
+}
+
+@vertex
+fn vertex_main(
+ @builtin(position) position : vec4f,
+ @location(0) uv : vec2f,
+) -> v2f {
+ return v2f(uniforms.modelViewProjectionMatrix * position, uv);
+}
+
+@fragment
+fn fragment_main(
+ @location(0) uv : vec2f,
+) -> vec4f {
+ return vec4f(1.0, 0.0, 1.0, 1.0);
+}
\ No newline at end of file
diff --git a/src/renderer/shader.ts b/src/renderer/shader.ts
new file mode 100644
index 0000000..1dc425d
--- /dev/null
+++ b/src/renderer/shader.ts
@@ -0,0 +1,19 @@
+import { WebGPUApp } from "./webgpu";
+import oopsWsgl from "./oops.wgsl";
+
+export class Shader {
+ private _module: GPUShaderModule | null = null;
+ constructor(private code: string[]) {}
+
+ module(app: WebGPUApp) {
+ this._module =
+ this._module ||
+ (this._module = app.device.createShaderModule({
+ code: this.code.join("\n"),
+ }));
+
+ return this._module;
+ }
+}
+
+export const Oops = new Shader([oopsWsgl]);
diff --git a/src/telemetry.js b/src/renderer/telemetry.ts
similarity index 75%
rename from src/telemetry.js
rename to src/renderer/telemetry.ts
index 844f79e..bc6e8ac 100644
--- a/src/telemetry.js
+++ b/src/renderer/telemetry.ts
@@ -1,18 +1,20 @@
export class Telemetry {
- constructor(app, selector = "#telemetry") {
- this.app = app;
- this.el = document.querySelector(selector);
+ public el: HTMLElement;
+ public frameTimes: number[] = [];
+ public maxFrameTimes: number = 100;
+ public lastFrameTime: number = 0;
+ constructor(
+ public app: any,
+ selector = "#telemetry"
+ ) {
+ this.el = document.querySelector(selector) as HTMLElement;
if (this.el && location.search.includes("telemetry")) {
this.el.style.display = "block";
this.app.onAfterUpdate(() => this.onAfterUpdate());
}
-
- this.frameTimes = [];
- this.maxFrameTimes = 100;
- this.lastFrameTime = 0;
}
- insertTime(time) {
+ insertTime(time: number) {
this.frameTimes.push(time);
if (this.frameTimes.length > this.maxFrameTimes) {
diff --git a/src/renderer/webgpu.ts b/src/renderer/webgpu.ts
new file mode 100644
index 0000000..63c92db
--- /dev/null
+++ b/src/renderer/webgpu.ts
@@ -0,0 +1,86 @@
+import { Telemetry } from "./telemetry";
+
+export type WebGPUAppConfig = {
+ fov?: number;
+ context?: GPUCanvasConfiguration;
+};
+
+export class WebGPUApp {
+ public canvas: HTMLCanvasElement;
+ private _adapter?: GPUAdapter;
+ private _device?: GPUDevice;
+ private _context?: GPUCanvasContext;
+ public telemetry: Telemetry;
+
+ constructor(public config: WebGPUAppConfig = {}) {
+ this.config = {
+ fov: 45,
+ ...config,
+ };
+ this.canvas = document.querySelector("canvas") as HTMLCanvasElement;
+ this.canvas.width = window.innerWidth;
+ this.canvas.height = window.innerHeight;
+ this.telemetry = new Telemetry(this);
+
+ this.init().catch((e) => {
+ const main = document.querySelector("main");
+ if (main) {
+ main.innerHTML = `your browser didn't let me set up webgpu. firefox nightly or enable dom.webgpu.enable
.
`;
+ }
+
+ throw new Error(
+ "Unable to initialize WebGPU. Your browser or machine may not support it.",
+ e
+ );
+ });
+ }
+
+ async init() {
+ if (!navigator.gpu) {
+ throw new Error("WebGPU not supported");
+ }
+
+ this._adapter = (await navigator.gpu.requestAdapter()) as GPUAdapter;
+ if (!this._adapter) {
+ throw new Error("No GPU adapter found");
+ }
+
+ this._device = await this.adapter.requestDevice();
+ if (!this._device) {
+ throw new Error("No GPU device found");
+ }
+
+ this._context = this.canvas.getContext("webgpu") as GPUCanvasContext;
+
+ this.context.configure({
+ device: this.device,
+ format: "bgra8unorm",
+ alphaMode: "premultiplied",
+ ...this.config.context,
+ });
+ }
+
+ get context() {
+ if (!this._context) {
+ throw new Error("WebGPU context not initialized");
+ }
+
+ return this._context;
+ }
+
+ get adapter() {
+ if (!this._adapter) {
+ throw new Error("WebGPU adapter not initialized");
+ }
+
+ return this._adapter;
+ }
+
+ get device() {
+ if (!this._device) {
+ throw new Error("WebGPU device not initialized");
+ }
+
+ return this._device;
+ }
+}
diff --git a/src/webgpu-app.js b/src/webgpu-app.js
deleted file mode 100644
index a2c3141..0000000
--- a/src/webgpu-app.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import { Telemetry } from "./telemetry.js";
-
-export class WebGPUApp {
- constructor(config) {
- this.config = config;
- this.canvas = document.querySelector("canvas");
- this.canvas.width = window.innerWidth;
- this.canvas.height = window.innerHeight;
- this.telemetry = new Telemetry(this);
-
- this.init().catch((e) => {
- console.error(e);
- document.querySelector(
- "main"
- ).innerHTML = `your browser didn't let me set up webgpu. firefox nightly or enable dom.webgpu.enable
.
`;
- throw new Error(
- "Unable to initialize WebGPU. Your browser or machine may not support it.",
- e
- );
- });
- }
-
- async init() {
- if (!navigator.gpu) {
- throw new Error("WebGPU not supported");
- }
-
- this.adapter = await navigator.gpu.requestAdapter();
- if (!this.adapter) {
- throw new Error("No GPU adapter found");
- }
-
- this.device = await this.adapter.requestDevice();
- if (!this.device) {
- throw new Error("No GPU device found");
- }
-
- this.context = this.canvas.getContext("webgpu");
- }
-}
diff --git a/tsconfig.json b/tsconfig.json
index 7556e1d..5e21cb1 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,22 +1,28 @@
{
"compilerOptions": {
- "lib": ["ESNext"],
+ // add Bun type definitions
+ "types": ["bun-types", "@webgpu/types"],
+
+ // enable latest features
+ "lib": ["esnext"],
"module": "esnext",
"target": "esnext",
+
+ // if TS 5.x+
"moduleResolution": "bundler",
- "moduleDetection": "force",
- "allowImportingTsExtensions": true,
"noEmit": true,
- "composite": true,
+ "allowImportingTsExtensions": true,
+ "moduleDetection": "force",
+ // if TS 4.x or earlier
+ // "moduleResolution": "nodenext",
+
+ "jsx": "react-jsx", // support JSX
+ "allowJs": true, // allow importing `.js` from `.ts`
+ "esModuleInterop": true, // allow default imports for CommonJS modules
+
+ // best practices
"strict": true,
- "downlevelIteration": true,
- "skipLibCheck": true,
- "jsx": "react-jsx",
- "allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
- "allowJs": true,
- "types": [
- "bun-types" // add Bun global
- ]
+ "skipLibCheck": true
}
}