even more webgpu stuff

This commit is contained in:
41666 2023-10-07 23:19:49 -04:00
parent 7da1fa8366
commit 96e992467f
14 changed files with 8421 additions and 30 deletions

View file

@ -2,6 +2,7 @@ import chalk from "chalk";
import { generate } from "./generate";
import { globSync } from "glob";
import { rmSync, mkdirSync, cpSync } from "node:fs";
import { convertMeshes } from "./convert-meshes";
console.log(chalk.green`>> Cleaing up ./html ...`);
rmSync("html", { recursive: true, force: true });
@ -33,3 +34,6 @@ for (const file of publics) {
cpSync(file, dest);
console.log(chalk.yellow(` -> ${dest}...`));
}
console.log(chalk.green`>> Convert meshes ...`);
await convertMeshes();

59
convert-meshes.ts Normal file
View file

@ -0,0 +1,59 @@
import chalk from "chalk";
import { globSync } from "glob";
export const convertMeshes = async () => {
const meshes = globSync("src/meshes/*.ply");
for (const file of meshes) {
const ply = await Bun.file(file).text();
const [header, body] = ply.split("end_header");
const colorSize = header.includes("red") ? 4 : 0;
const values: number[] = [];
for (const line of body.split("\n")) {
// line looks like
// x y z r g b a u v
// We need to convert it to
// x y z 1 r g b a u v
const [x, y, z, r, g, b, a, u, v] = line.split(" ");
if (!g) {
continue;
}
values.push(
parseFloat(x),
parseFloat(y),
parseFloat(z),
1,
parseFloat(r),
parseFloat(g)
);
if (colorSize > 0) {
values.push(parseFloat(b), parseFloat(a), parseFloat(u), parseFloat(v));
}
}
const outFile = file.replace(".ply", ".ts");
const outString = `
import { Mesh } from "../renderer/mesh";
// prettier-ignore
const mesh = new Float32Array(${JSON.stringify(values, null, 2)});
export default new Mesh({
mesh,
positionSize: 4 * 4,
colorSize: ${colorSize} * 4,
uvSize: 2 * 4,
});
`;
await Bun.write(outFile, outString);
console.log(chalk.yellow(` -> ${file}...`));
}
};

View file

@ -1,4 +1,4 @@
import{a as Y} from"../chunk-0cd54990cab7d3a5.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="<div><i>your browser didn't let me set up webgl</i></div>",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;
import{b as Y} from"../chunk-c8239659df5e5cce.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="<div><i>your browser didn't let me set up webgl</i></div>",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;

File diff suppressed because one or more lines are too long

View file

@ -1,5 +0,0 @@
class j{p;el;frameTimes=[];maxFrameTimes=100;lastFrameTime=0;constructor(p,x="#telemetry"){this.app=p;if(this.el=document.querySelector(x),this.el&&location.search.includes("telemetry"))this.el.style.display="block",this.app.onAfterUpdate((s)=>this.onAfterUpdate(s)),this.app.onStart(()=>this.onStart())}insertTime(p){if(this.frameTimes.push(p),this.frameTimes.length>this.maxFrameTimes)this.frameTimes.shift()}onStart(){this.lastFrameTime=0,this.frameTimes=[]}onAfterUpdate(p){const x=p-this.lastFrameTime;this.insertTime(x);const s=this.frameTimes.reduce((d,h)=>d+h,0)/this.frameTimes.length,$=1000/s;this.el.innerHTML=`
${$.toFixed(1)} FPS (${s.toFixed(3)} ms)<br />
bU: ${this.app.registry.onBeforeUpdate.length} | U: ${this.app.registry.onUpdate.length} | aU: ${this.app.registry.onAfterUpdate.length}
`,this.lastFrameTime=p}}
export{j as a};

View file

@ -0,0 +1,5 @@
class n{U;constructor(U){this.app=U;this.onBeforeUpdate&&U.onBeforeUpdate(this.onBeforeUpdate.bind(this)),this.onUpdate&&U.onUpdate(this.onUpdate.bind(this)),this.onAfterUpdate&&U.onAfterUpdate(this.onAfterUpdate.bind(this)),this.onStart&&U.onStart(this.onStart.bind(this))}onStart(U){}onBeforeUpdate(U){}onUpdate(U){}onAfterUpdate(U){}}class k extends n{U;el;frameTimes=[];maxFrameTimes=100;lastFrameTime=0;constructor(U,h="#telemetry"){super(U);this.app=U;if(this.el=document.querySelector(h),this.el&&location.search.includes("telemetry"))this.el.style.display="block"}insertTime(U){if(this.frameTimes.push(U),this.frameTimes.length>this.maxFrameTimes)this.frameTimes.shift()}onStart(){this.lastFrameTime=0,this.frameTimes=[],setInterval(()=>{const U=this.frameTimes.reduce((L,j)=>L+j,0)/this.frameTimes.length,h=1000/U;this.el.innerHTML=`
${h.toFixed(1)} FPS (${U.toFixed(3)} ms)<br />
bU: ${this.app.registry.onBeforeUpdate.length} | U: ${this.app.registry.onUpdate.length} | aU: ${this.app.registry.onAfterUpdate.length}
`},1000)}onAfterUpdate(U){const h=U-this.lastFrameTime;this.insertTime(h),this.lastFrameTime=U}}
export{n as a,k as b};

View file

@ -1,9 +1,14 @@
import { WebGPUApp } from "../renderer/webgpu";
import Torus from "../meshes/torus";
import { MeshRenderer } from "../renderer/mesh-renderer";
const app = new WebGPUApp({ fov: 20 });
app.start();
// TODO:
// - plane
// - torus!
// - white shader
// - real shader with UVs and uniforms
const torusRenderer = new MeshRenderer(app, Torus);
app.start();

View file

@ -11,7 +11,7 @@ struct v2f {
}
@vertex
fn vertex_main(
fn main(
@builtin(position) position : vec4f,
@location(0) uv : vec2f,
) -> v2f {
@ -19,7 +19,7 @@ fn vertex_main(
}
@fragment
fn fragment_main(
fn main(
@location(0) uv : vec2f,
) -> @location(0) vec4f {
f32 zComponent = sin(uniforms.time) * 0.001 * 0.5 + 0.5;

1805
src/meshes/torus.ply Normal file

File diff suppressed because it is too large Load diff

6383
src/meshes/torus.ts Normal file

File diff suppressed because it is too large Load diff

15
src/renderer/behavior.ts Normal file
View file

@ -0,0 +1,15 @@
import { WebGPUApp } from "./webgpu";
export abstract class Behavior {
constructor(public app: WebGPUApp) {
this.onBeforeUpdate && app.onBeforeUpdate(this.onBeforeUpdate.bind(this));
this.onUpdate && app.onUpdate(this.onUpdate.bind(this));
this.onAfterUpdate && app.onAfterUpdate(this.onAfterUpdate.bind(this));
this.onStart && app.onStart(this.onStart.bind(this));
}
onStart(time: number) {}
onBeforeUpdate(time: number) {}
onUpdate(time: number) {}
onAfterUpdate(time: number) {}
}

View file

@ -0,0 +1,24 @@
import { Behavior } from "./behavior";
import { Mesh } from "./mesh";
import { WebGPUApp } from "./webgpu";
export class MeshRenderer extends Behavior {
private depthTexture?: GPUTexture;
private uniformBuffer?: GPUBuffer;
private texture?: GPUTexture;
private sampler?: GPUSampler;
private uniformBindGroup?: GPUBindGroup;
private renderPassDescriptor?: GPURenderBundleDescriptor;
constructor(
public app: WebGPUApp,
public mesh: Mesh
) {
super(app);
}
onStart() {
console.log("hello from meshrenderer!");
console.log(`i've got a ${this.mesh.constructor.name}`);
}
onUpdate(time: number) {}
}

View file

@ -1,16 +1,86 @@
import { Oops, Shader } from "./shader";
import { WebGPUApp } from "./webgpu";
export type MeshConfig = {
vertexData: Float32Array;
vertexSize: number;
positionOffset: number;
colorOffset: number;
uvOffset: number;
mesh: Float32Array;
positionSize: number;
colorSize: number;
uvSize: number;
};
export class Mesh {
private shader: Shader = Oops;
private _shader: Shader = Oops;
constructor(public config: MeshConfig) {}
shader(shader: Shader) {}
shader(shader: Shader) {
this._shader = shader;
return this;
}
buffer(app: WebGPUApp) {
const buffer = app.device.createBuffer({
size: this.config.mesh.byteLength,
usage: GPUBufferUsage.VERTEX,
mappedAtCreation: true,
});
new Float32Array(buffer.getMappedRange()).set(this.config.mesh);
buffer.unmap();
return buffer;
}
pipeline(app: WebGPUApp, config: Record<string, any>) {
const module = this._shader.module(app);
return app.device.createRenderPipeline({
layout: "auto",
vertex: {
module,
entryPoint: "main",
buffers: [
{
arrayStride: 4,
attributes: [
{
// position
shaderLocation: 0,
offset: 0,
format: "float32x4",
},
{
// color
shaderLocation: 2,
offset: this.config.positionSize,
format: "float32x4",
},
{
// uv
shaderLocation: 1,
offset: this.config.positionSize + this.config.colorSize,
format: "float32x4",
},
],
},
],
},
fragment: {
module,
entryPoint: "main",
targets: [
{
format: "rgba8unorm",
},
],
},
primitive: {
topology: "triangle-list",
cullMode: config.cullMode ?? "back",
},
depthStencil: config.stencil && {
depthWriteEnabled: true,
depthCompare: "less",
format: "depth24plus",
},
});
}
}

View file

@ -1,6 +1,7 @@
import { Behavior } from "./behavior";
import { WebGPUApp } from "./webgpu";
export class Telemetry {
export class Telemetry extends Behavior {
public el: HTMLElement;
public frameTimes: number[] = [];
public maxFrameTimes: number = 100;
@ -9,11 +10,10 @@ export class Telemetry {
public app: WebGPUApp,
selector = "#telemetry"
) {
super(app);
this.el = document.querySelector(selector) as HTMLElement;
if (this.el && location.search.includes("telemetry")) {
this.el.style.display = "block";
this.app.onAfterUpdate((time) => this.onAfterUpdate(time));
this.app.onStart(() => this.onStart());
}
}
@ -28,18 +28,14 @@ export class Telemetry {
onStart() {
this.lastFrameTime = 0;
this.frameTimes = [];
}
onAfterUpdate(time: number) {
const frameTime = time - this.lastFrameTime;
this.insertTime(frameTime);
setInterval(() => {
const averageFrameTime =
this.frameTimes.reduce((a, b) => a + b, 0) / this.frameTimes.length;
const averageFrameTime =
this.frameTimes.reduce((a, b) => a + b, 0) / this.frameTimes.length;
const framesPerSecond = 1000 / averageFrameTime;
const framesPerSecond = 1000 / averageFrameTime;
this.el.innerHTML = `
this.el.innerHTML = `
${framesPerSecond.toFixed(1)} FPS (${averageFrameTime.toFixed(
3
)} ms)<br />
@ -47,7 +43,12 @@ export class Telemetry {
this.app.registry.onUpdate.length
} | aU: ${this.app.registry.onAfterUpdate.length}
`;
}, 1000);
}
onAfterUpdate(time: number) {
const frameTime = time - this.lastFrameTime;
this.insertTime(frameTime);
this.lastFrameTime = time;
}
}