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 } }