ok webgpu sucks lets jump back to webgl
This commit is contained in:
parent
000f35f19d
commit
87563cb9e3
41 changed files with 587 additions and 6925 deletions
|
@ -9,7 +9,7 @@ https://art.mekanoe.com
|
||||||
## Artworks
|
## Artworks
|
||||||
|
|
||||||
- [./001-platform-provenance](https://art.mekanoe.com/001-platform-provenance)
|
- [./001-platform-provenance](https://art.mekanoe.com/001-platform-provenance)
|
||||||
- [./002-webgpu-instead](https://art.mekanoe.com/002-webgpu-instead)
|
- [./002-webgl-engine](https://art.mekanoe.com/002-webgl-engine)
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
|
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
@ -13,19 +13,28 @@ const works = globSync("src/*/main.ts");
|
||||||
|
|
||||||
console.log(chalk.green`>> Building ...`);
|
console.log(chalk.green`>> Building ...`);
|
||||||
console.log(chalk.yellow(` Found ${works.length} works.`));
|
console.log(chalk.yellow(` Found ${works.length} works.`));
|
||||||
|
console.log(chalk.yellow(` Running Bun.build()`));
|
||||||
|
|
||||||
await Bun.build({
|
const results = await Bun.build({
|
||||||
entrypoints: works,
|
entrypoints: works,
|
||||||
outdir: "html",
|
outdir: "html",
|
||||||
splitting: true,
|
splitting: true,
|
||||||
loader: {
|
loader: {
|
||||||
".glsl": "text",
|
".glsl": "text",
|
||||||
".wgsl": "text",
|
".wgsl": "text",
|
||||||
|
".vert": "text",
|
||||||
|
".frag": "text",
|
||||||
},
|
},
|
||||||
minify: true,
|
minify: process.env.MINIFY === "false" ? false : true,
|
||||||
plugins: [glslPlugin],
|
plugins: [glslPlugin],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!results.success) {
|
||||||
|
console.error(chalk.red("XX Bun.build() Failed."));
|
||||||
|
console.error(chalk.red(JSON.stringify(results.logs, null, 2)));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(chalk.green`>> Generating HTML and Markdown ...`);
|
console.log(chalk.green`>> Generating HTML and Markdown ...`);
|
||||||
await generate(works);
|
await generate(works);
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,16 @@ export const convertMeshes = async () => {
|
||||||
|
|
||||||
const [header, body] = ply.split("end_header");
|
const [header, body] = ply.split("end_header");
|
||||||
const colorSize = header.includes("red") ? 4 : 0;
|
const colorSize = header.includes("red") ? 4 : 0;
|
||||||
|
const headerLines = header.split("\n");
|
||||||
|
const vertexCount = Number(
|
||||||
|
headerLines
|
||||||
|
.find((header) => header.startsWith("element vertex"))
|
||||||
|
?.replace("element vertex ", "")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!vertexCount) {
|
||||||
|
throw new Error("couldn't get vertex count...");
|
||||||
|
}
|
||||||
|
|
||||||
const values: number[] = [];
|
const values: number[] = [];
|
||||||
|
|
||||||
|
@ -43,13 +53,16 @@ export const convertMeshes = async () => {
|
||||||
import { Mesh } from "../renderer/mesh";
|
import { Mesh } from "../renderer/mesh";
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const mesh = new Float32Array(${JSON.stringify(values, null, 2)});
|
const mesh = new Float32Array(${JSON.stringify(values)});
|
||||||
|
|
||||||
export default new Mesh({
|
export default new Mesh({
|
||||||
mesh,
|
mesh,
|
||||||
positionSize: 4 * 4,
|
positionSize: 4,
|
||||||
colorSize: ${colorSize} * 4,
|
colorSize: ${colorSize},
|
||||||
uvSize: 2 * 4,
|
uvSize: 2,
|
||||||
|
vertexCount: ${vertexCount},
|
||||||
|
stride: ${4 + colorSize + 2},
|
||||||
|
name: ${JSON.stringify(file)}
|
||||||
});
|
});
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import glsl from "esbuild-plugin-glsl";
|
import glsl from "esbuild-plugin-glsl";
|
||||||
|
|
||||||
export default glsl({
|
export default glsl({
|
||||||
minify: true,
|
minify: process.env.MINIFY === "false" ? false : true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,10 +22,4 @@
|
||||||
<canvas id="canvas" width="1280" height="720"></canvas>
|
<canvas id="canvas" width="1280" height="720"></canvas>
|
||||||
<div id="telemetry">XX.X FPS (XX.X ms)</div>
|
<div id="telemetry">XX.X FPS (XX.X ms)</div>
|
||||||
</main>
|
</main>
|
||||||
<script
|
|
||||||
src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/3.4.2/gl-matrix-min.min.js"
|
|
||||||
integrity="sha512-cR3oS5mKRWD+38vYi1CNJk1DLpi104ovuQBuVv9p7nNxeqzSNiHzlboK2BZQybmpTi1QNnQ5unYajpURcMjeZQ=="
|
|
||||||
crossorigin="anonymous"
|
|
||||||
referrerpolicy="no-referrer"
|
|
||||||
></script>
|
|
||||||
<script src="/##name##/main.js" type="module"></script>
|
<script src="/##name##/main.js" type="module"></script>
|
||||||
|
|
|
@ -22,10 +22,4 @@
|
||||||
<canvas id="canvas" width="1280" height="720"></canvas>
|
<canvas id="canvas" width="1280" height="720"></canvas>
|
||||||
<div id="telemetry">XX.X FPS (XX.X ms)</div>
|
<div id="telemetry">XX.X FPS (XX.X ms)</div>
|
||||||
</main>
|
</main>
|
||||||
<script
|
|
||||||
src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/3.4.2/gl-matrix-min.min.js"
|
|
||||||
integrity="sha512-cR3oS5mKRWD+38vYi1CNJk1DLpi104ovuQBuVv9p7nNxeqzSNiHzlboK2BZQybmpTi1QNnQ5unYajpURcMjeZQ=="
|
|
||||||
crossorigin="anonymous"
|
|
||||||
referrerpolicy="no-referrer"
|
|
||||||
></script>
|
|
||||||
<script src="/001-platform-provenance/main.js" type="module"></script>
|
<script src="/001-platform-provenance/main.js" type="module"></script>
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,6 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>com.mekanoe.art // 002-webgpu-instead</title>
|
<title>com.mekanoe.art // 002-webgl-engine</title>
|
||||||
<style>
|
<style>
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
|
@ -22,10 +22,4 @@
|
||||||
<canvas id="canvas" width="1280" height="720"></canvas>
|
<canvas id="canvas" width="1280" height="720"></canvas>
|
||||||
<div id="telemetry">XX.X FPS (XX.X ms)</div>
|
<div id="telemetry">XX.X FPS (XX.X ms)</div>
|
||||||
</main>
|
</main>
|
||||||
<script
|
<script src="/002-webgl-engine/main.js" type="module"></script>
|
||||||
src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/3.4.2/gl-matrix-min.min.js"
|
|
||||||
integrity="sha512-cR3oS5mKRWD+38vYi1CNJk1DLpi104ovuQBuVv9p7nNxeqzSNiHzlboK2BZQybmpTi1QNnQ5unYajpURcMjeZQ=="
|
|
||||||
crossorigin="anonymous"
|
|
||||||
referrerpolicy="no-referrer"
|
|
||||||
></script>
|
|
||||||
<script src="/002-webgpu-instead/main.js" type="module"></script>
|
|
1
html/002-webgl-engine/main.js
Normal file
1
html/002-webgl-engine/main.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
html/chunk-1da54319833c650d.js
Normal file
2
html/chunk-1da54319833c650d.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,5 +0,0 @@
|
||||||
class L{d;constructor(d){this.app=d;this.onStart&&d.onStart(this.onStart.bind(this)),this.onUpdate&&d.onUpdate(this.onUpdate.bind(this)),this.onAfterUpdate&&d.onAfterUpdate(this.onAfterUpdate.bind(this)),this.onBeforeUpdate&&d.onBeforeUpdate(this.onBeforeUpdate.bind(this))}}class h extends L{d;el;frameTimes=[];maxFrameTimes=100;lastFrameTime=0;constructor(d,n="#telemetry"){super(d);this.app=d;if(this.el=document.querySelector(n),this.el&&location.search.includes("telemetry"))this.el.style.display="block"}insertTime(d){if(this.frameTimes.push(d),this.frameTimes.length>this.maxFrameTimes)this.frameTimes.shift()}onStart(){this.lastFrameTime=0,this.frameTimes=[],setInterval(()=>{const d=this.frameTimes.reduce((U,c)=>U+c,0)/this.frameTimes.length,n=1000/d;this.el.innerHTML=`
|
|
||||||
${n.toFixed(1)} FPS (${d.toFixed(3)} ms)<br />
|
|
||||||
bU: ${this.app.registry.onBeforeUpdate.length} | U: ${this.app.registry.onUpdate.length} | aU: ${this.app.registry.onAfterUpdate.length}
|
|
||||||
`},1000)}onAfterUpdate(d){const n=d-this.lastFrameTime;this.insertTime(n),this.lastFrameTime=d}}
|
|
||||||
export{L as a,h as b};
|
|
|
@ -31,7 +31,7 @@
|
||||||
</header>
|
</header>
|
||||||
<section id="works">
|
<section id="works">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/002-webgpu-instead">./002-webgpu-instead</a></li>
|
<li><a href="/002-webgl-engine">./002-webgl-engine</a></li>
|
||||||
<li><a href="/001-platform-provenance">./001-platform-provenance</a></li>
|
<li><a href="/001-platform-provenance">./001-platform-provenance</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
10
index.d.ts
vendored
10
index.d.ts
vendored
|
@ -10,3 +10,13 @@ declare module "*.wgsl" {
|
||||||
const content: string;
|
const content: string;
|
||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module "*.vert" {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "*.frag" {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
"dev": "run-p serve build:watch"
|
"dev": "run-p serve build:watch"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@webgpu/types": "^0.1.37",
|
|
||||||
"bun-types": "latest",
|
"bun-types": "latest",
|
||||||
"npm-run-all2": "^6.1.1",
|
"npm-run-all2": "^6.1.1",
|
||||||
"prettier": "^3.0.3"
|
"prettier": "^3.0.3"
|
||||||
|
@ -20,9 +19,9 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"esbuild-plugin-glsl": "^1.2.2",
|
"esbuild-plugin-glsl": "^1.2.2",
|
||||||
|
"gl-matrix": "^3.4.3",
|
||||||
"glob": "^10.3.10",
|
"glob": "^10.3.10",
|
||||||
"serve": "^14.2.1",
|
"serve": "^14.2.1",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2"
|
||||||
"wgpu-matrix": "^2.5.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Telemetry } from "../renderer/telemetry";
|
import { Telemetry } from "../renderer/telemetry";
|
||||||
|
import { mat4 } from "gl-matrix";
|
||||||
|
|
||||||
export class App {
|
export class App {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -35,21 +36,11 @@ export class App {
|
||||||
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
|
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
|
||||||
const zNear = 0.1;
|
const zNear = 0.1;
|
||||||
const zFar = 100.0;
|
const zFar = 100.0;
|
||||||
const projectionMatrix = glMatrix.mat4.create();
|
const projectionMatrix = mat4.create();
|
||||||
glMatrix.mat4.perspective(
|
mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);
|
||||||
projectionMatrix,
|
|
||||||
fieldOfView,
|
|
||||||
aspect,
|
|
||||||
zNear,
|
|
||||||
zFar
|
|
||||||
);
|
|
||||||
|
|
||||||
const modelViewMatrix = glMatrix.mat4.create();
|
const modelViewMatrix = mat4.create();
|
||||||
glMatrix.mat4.translate(
|
mat4.translate(modelViewMatrix, modelViewMatrix, [-0.0, 0.0, -6.0]);
|
||||||
modelViewMatrix,
|
|
||||||
modelViewMatrix,
|
|
||||||
[-0.0, 0.0, -6.0]
|
|
||||||
);
|
|
||||||
|
|
||||||
this.projectionMatrix = projectionMatrix;
|
this.projectionMatrix = projectionMatrix;
|
||||||
this.modelViewMatrix = modelViewMatrix;
|
this.modelViewMatrix = modelViewMatrix;
|
||||||
|
|
43
src/002-webgl-engine/main.ts
Normal file
43
src/002-webgl-engine/main.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import { MeshRenderer } from "../renderer/mesh-renderer";
|
||||||
|
import plane from "../meshes/plane";
|
||||||
|
import { WebGLApp } from "../renderer/webgl";
|
||||||
|
import { Renderable } from "../renderer/renderable";
|
||||||
|
import { Transform } from "../renderer/transform";
|
||||||
|
import { uvRainbow } from "../common-shaders/uv-rainbow";
|
||||||
|
import { quat } from "gl-matrix";
|
||||||
|
import torus from "../meshes/torus";
|
||||||
|
|
||||||
|
const app = new WebGLApp({ fov: 45 });
|
||||||
|
|
||||||
|
const camera = new Transform(
|
||||||
|
[0, 0, -6],
|
||||||
|
quat.fromEuler(quat.create(), 15, 0, 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
(window as any).ANGLE_X = 15;
|
||||||
|
(window as any).ANGLE_Y = 0;
|
||||||
|
(window as any).ANGLE_Z = 0;
|
||||||
|
|
||||||
|
app.onUpdate((time: number) => {
|
||||||
|
const stride = 2;
|
||||||
|
const x = Math.sin(time * 0.0001) * (stride * 2 - stride * 0.5);
|
||||||
|
// const y = Math.tan(time * 0.001) * (stride * 2 - stride * 0.5);
|
||||||
|
|
||||||
|
camera.rotation = quat.fromEuler(quat.create(), x, 0, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
new Renderable(
|
||||||
|
app,
|
||||||
|
new Transform([0, 0, 4]),
|
||||||
|
new MeshRenderer(app, torus, uvRainbow(app), camera, {
|
||||||
|
drawMode: app.gl.TRIANGLE_FAN,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// new Renderable(
|
||||||
|
// app,
|
||||||
|
// new Transform([1, 0, 0]),
|
||||||
|
// new MeshRenderer(app, plane, uvRainbow(app))
|
||||||
|
// );
|
||||||
|
|
||||||
|
app.start();
|
|
@ -1,12 +0,0 @@
|
||||||
import { WebGPUApp } from "../renderer/webgpu";
|
|
||||||
import { MeshRenderer } from "../renderer/mesh-renderer";
|
|
||||||
import plane from "../meshes/plane";
|
|
||||||
import rainbowPlane from "./rainbow-plane.wgsl";
|
|
||||||
import { Shader } from "../renderer/shader";
|
|
||||||
|
|
||||||
const app = new WebGPUApp({ fov: 20 });
|
|
||||||
|
|
||||||
const shader = new Shader(rainbowPlane);
|
|
||||||
const renderer = new MeshRenderer(app, plane, shader);
|
|
||||||
|
|
||||||
app.start();
|
|
|
@ -1,17 +0,0 @@
|
||||||
#include "../color-conv.wgsl"
|
|
||||||
#include "../uniforms.wgsl"
|
|
||||||
#include "../basic-vert.wgsl"
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn main(
|
|
||||||
@location(0) uv : vec2f,
|
|
||||||
) -> @location(0) vec4f {
|
|
||||||
f32 z = sin(uniforms.time) * 0.001 * 0.5 + 0.5;
|
|
||||||
vec3f hsv = vec3f(uv.x, uv.y, z);
|
|
||||||
hsv.x += uniforms.time * 0.0001;
|
|
||||||
hsv.y = 1.0;
|
|
||||||
hsv.z = 1.0;
|
|
||||||
vec3f rgb = hsv2rgb(hsv);
|
|
||||||
|
|
||||||
return saturate(vec4f(rgb, 1.0));
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
struct v2f {
|
|
||||||
@builtin(position) position : vec4f,
|
|
||||||
@location(0) color : vec4f,
|
|
||||||
@location(1) uv : vec2f,
|
|
||||||
}
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn main(
|
|
||||||
@builtin(position) position : vec4f,
|
|
||||||
@location(0) color : vec4f,
|
|
||||||
@location(1) uv : vec2f,
|
|
||||||
) -> v2f {
|
|
||||||
return v2f(uniforms.modelViewProjectionMatrix * position, color, uv);
|
|
||||||
}
|
|
|
@ -1,15 +0,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);
|
|
||||||
}
|
|
5
src/common-shaders/error.frag
Normal file
5
src/common-shaders/error.frag
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);
|
||||||
|
}
|
7
src/common-shaders/error.ts
Normal file
7
src/common-shaders/error.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { Shader } from "../renderer/shader";
|
||||||
|
import { WebGLApp } from "../renderer/webgl";
|
||||||
|
import frag from "./error.frag";
|
||||||
|
import vert from "./error.vert";
|
||||||
|
|
||||||
|
export const errorShader = (app: WebGLApp) =>
|
||||||
|
new Shader().vertex(vert).fragment(frag).app(app);
|
8
src/common-shaders/error.vert
Normal file
8
src/common-shaders/error.vert
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
attribute vec4 aVertexPosition;
|
||||||
|
|
||||||
|
uniform mat4 uModelViewMatrix;
|
||||||
|
uniform mat4 uProjectionMatrix;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
|
||||||
|
}
|
35
src/common-shaders/uv-rainbow.frag
Normal file
35
src/common-shaders/uv-rainbow.frag
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
uniform float uTime;
|
||||||
|
uniform float uSinTime;
|
||||||
|
uniform float uCosTime;
|
||||||
|
|
||||||
|
varying highp vec2 vTextureCoord;
|
||||||
|
|
||||||
|
vec3 rgb2hsv(vec3 c) {
|
||||||
|
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
|
||||||
|
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
|
||||||
|
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
|
||||||
|
|
||||||
|
float d = q.x - min(q.w, q.y);
|
||||||
|
float e = 1.0e-10;
|
||||||
|
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 hsv2rgb(vec3 c) {
|
||||||
|
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
||||||
|
vec3 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float zComponent = uSinTime * 0.001 * 0.5 + 0.5;
|
||||||
|
vec3 hsv = rgb2hsv(vec3(vTextureCoord, zComponent));
|
||||||
|
hsv.x += uTime * 0.0001;
|
||||||
|
hsv.y = 1.0;
|
||||||
|
hsv.z = 1.0;
|
||||||
|
vec3 rgb = hsv2rgb(hsv);
|
||||||
|
|
||||||
|
gl_FragColor = vec4(rgb, 1.0);
|
||||||
|
gl_FragColor = clamp(gl_FragColor, 0.0, 1.0);
|
||||||
|
}
|
7
src/common-shaders/uv-rainbow.ts
Normal file
7
src/common-shaders/uv-rainbow.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { Shader } from "../renderer/shader";
|
||||||
|
import { WebGLApp } from "../renderer/webgl";
|
||||||
|
import frag from "./uv-rainbow.frag";
|
||||||
|
import vert from "./uv-rainbow.vert";
|
||||||
|
|
||||||
|
export const uvRainbow = (app: WebGLApp) =>
|
||||||
|
new Shader({ time: true }).vertex(vert).fragment(frag).app(app);
|
12
src/common-shaders/uv-rainbow.vert
Normal file
12
src/common-shaders/uv-rainbow.vert
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
attribute vec4 aVertexPosition;
|
||||||
|
attribute vec2 aTextureCoord;
|
||||||
|
|
||||||
|
uniform mat4 uModelViewMatrix;
|
||||||
|
uniform mat4 uProjectionMatrix;
|
||||||
|
|
||||||
|
varying highp vec2 vTextureCoord;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
|
||||||
|
vTextureCoord = aTextureCoord;
|
||||||
|
}
|
|
@ -11,7 +11,10 @@ const mesh = new Float32Array([
|
||||||
|
|
||||||
export default new Mesh({
|
export default new Mesh({
|
||||||
mesh,
|
mesh,
|
||||||
positionSize: 4 * 4,
|
positionSize: 4,
|
||||||
colorSize: 4 * 4,
|
colorSize: 4,
|
||||||
uvSize: 2 * 4,
|
uvSize: 2,
|
||||||
|
vertexCount: 4,
|
||||||
|
stride: 10,
|
||||||
|
name: "plane",
|
||||||
});
|
});
|
||||||
|
|
6382
src/meshes/torus.ts
6382
src/meshes/torus.ts
File diff suppressed because one or more lines are too long
|
@ -1,11 +1,11 @@
|
||||||
import { WebGPUApp } from "./webgpu";
|
import { WebGLApp } from "./webgl";
|
||||||
|
|
||||||
export abstract class Behavior {
|
export abstract class Behavior {
|
||||||
onStart?(...args: any[]): void;
|
onStart?(...args: any[]): void;
|
||||||
onBeforeUpdate?(...args: any[]): void;
|
onBeforeUpdate?(...args: any[]): void;
|
||||||
onUpdate?(...args: any[]): void;
|
onUpdate?(...args: any[]): void;
|
||||||
onAfterUpdate?(...args: any[]): void;
|
onAfterUpdate?(...args: any[]): void;
|
||||||
constructor(public app: WebGPUApp) {
|
constructor(public app: WebGLApp) {
|
||||||
this.onStart && app.onStart(this.onStart.bind(this));
|
this.onStart && app.onStart(this.onStart.bind(this));
|
||||||
this.onUpdate && app.onUpdate(this.onUpdate.bind(this));
|
this.onUpdate && app.onUpdate(this.onUpdate.bind(this));
|
||||||
this.onAfterUpdate && app.onAfterUpdate(this.onAfterUpdate.bind(this));
|
this.onAfterUpdate && app.onAfterUpdate(this.onAfterUpdate.bind(this));
|
||||||
|
|
|
@ -1,146 +1,136 @@
|
||||||
import { Mat4, mat4, vec3 } from "wgpu-matrix";
|
import { mat4, vec3 } from "gl-matrix";
|
||||||
import { Behavior } from "./behavior";
|
import { Behavior } from "./behavior";
|
||||||
import { Mesh } from "./mesh";
|
import { Mesh } from "./mesh";
|
||||||
import { Shader } from "./shader";
|
import { Shader } from "./shader";
|
||||||
import { WebGPUApp } from "./webgpu";
|
import { WebGLApp } from "./webgl";
|
||||||
|
import { Transform } from "./transform";
|
||||||
|
import { errorShader } from "../common-shaders/error";
|
||||||
|
|
||||||
|
export type MeshRendererConfig = {
|
||||||
|
drawMode?: number;
|
||||||
|
};
|
||||||
|
|
||||||
export class MeshRenderer extends Behavior {
|
export class MeshRenderer extends Behavior {
|
||||||
private depthTexture?: GPUTexture;
|
private modelMatrix = mat4.create();
|
||||||
private uniformBuffer?: GPUBuffer;
|
private projectionMatrix = mat4.create();
|
||||||
private vertexBuffer?: GPUBuffer;
|
private _meshBuffer?: WebGLBuffer;
|
||||||
private texture?: GPUTexture;
|
|
||||||
private sampler?: GPUSampler;
|
|
||||||
private uniformBindGroup?: GPUBindGroup;
|
|
||||||
private renderPassDescriptor?: GPURenderPassDescriptor;
|
|
||||||
private pipeline?: GPURenderPipeline;
|
|
||||||
private viewMatrix = mat4.translate(
|
|
||||||
mat4.identity(),
|
|
||||||
vec3.fromValues(0, 0, -4)
|
|
||||||
);
|
|
||||||
private projectionMatrix = mat4.perspective(
|
|
||||||
2 * Math.PI * 0.2,
|
|
||||||
1920 / 1080,
|
|
||||||
1,
|
|
||||||
100
|
|
||||||
);
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public app: WebGPUApp,
|
public app: WebGLApp,
|
||||||
public mesh: Mesh,
|
public mesh: Mesh,
|
||||||
public shader: Shader,
|
public shader: Shader,
|
||||||
public textures?: any[]
|
public camera: Transform = new Transform([0, 0, -6]),
|
||||||
|
public config: MeshRendererConfig = {}
|
||||||
) {
|
) {
|
||||||
super(app);
|
super(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get meshBuffer() {
|
||||||
|
if (this._meshBuffer) {
|
||||||
|
return this._meshBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("mesh buffer not ready");
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeMeshBuffer() {
|
||||||
|
const gl = this.app.gl;
|
||||||
|
const buffer = gl.createBuffer();
|
||||||
|
if (!buffer) {
|
||||||
|
throw new Error("failed to create mesh buffer");
|
||||||
|
}
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, this.mesh.config.mesh, gl.STATIC_DRAW);
|
||||||
|
|
||||||
|
this._meshBuffer = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeAttributes() {
|
||||||
|
const gl = this.app.gl;
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.meshBuffer);
|
||||||
|
|
||||||
|
const positionLocation = this.shader.attrib("aVertexPosition");
|
||||||
|
if (positionLocation !== -1) {
|
||||||
|
gl.vertexAttribPointer(
|
||||||
|
positionLocation,
|
||||||
|
this.mesh.config.positionSize,
|
||||||
|
gl.FLOAT,
|
||||||
|
false,
|
||||||
|
4 * this.mesh.config.stride,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
gl.enableVertexAttribArray(positionLocation);
|
||||||
|
this.shader.bindAttrib(positionLocation, "aVertexPosition");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mesh.config.colorSize !== 0) {
|
||||||
|
const colorLocation = this.shader.attrib("aVertexColor");
|
||||||
|
if (colorLocation !== -1) {
|
||||||
|
gl.vertexAttribPointer(
|
||||||
|
colorLocation,
|
||||||
|
this.mesh.config.colorSize,
|
||||||
|
gl.FLOAT,
|
||||||
|
false,
|
||||||
|
4 * this.mesh.config.stride,
|
||||||
|
4 * this.mesh.config.positionSize
|
||||||
|
);
|
||||||
|
gl.enableVertexAttribArray(colorLocation);
|
||||||
|
this.shader.bindAttrib(colorLocation, "aVertexColor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uvLocation = this.shader.attrib("aTextureCoord");
|
||||||
|
if (uvLocation !== -1) {
|
||||||
|
gl.vertexAttribPointer(
|
||||||
|
uvLocation,
|
||||||
|
this.mesh.config.uvSize,
|
||||||
|
gl.FLOAT,
|
||||||
|
false,
|
||||||
|
4 * this.mesh.config.stride,
|
||||||
|
4 * (this.mesh.config.positionSize + this.mesh.config.colorSize)
|
||||||
|
);
|
||||||
|
gl.enableVertexAttribArray(uvLocation);
|
||||||
|
this.shader.bindAttrib(uvLocation, "aTextureCoord");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onStart() {
|
onStart() {
|
||||||
this.projectionMatrix = mat4.perspective(
|
mat4.perspective(
|
||||||
2 * Math.PI * 0.2,
|
this.projectionMatrix,
|
||||||
|
this.app.config.fov || 45,
|
||||||
this.app.canvas.width / this.app.canvas.height,
|
this.app.canvas.width / this.app.canvas.height,
|
||||||
1,
|
this.app.config.zNear || 0.1,
|
||||||
100
|
this.app.config.zFar || 100
|
||||||
);
|
);
|
||||||
|
|
||||||
this.depthTexture = this.app.device.createTexture({
|
this.shader.compile();
|
||||||
size: [this.app.canvas.width, this.app.canvas.height],
|
this.initializeMeshBuffer();
|
||||||
format: "depth24plus",
|
this.initializeAttributes();
|
||||||
usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
this.shader.link();
|
||||||
});
|
|
||||||
|
|
||||||
// float32x4x4 + float32
|
|
||||||
this.uniformBuffer = this.app.device.createBuffer({
|
|
||||||
size: 4 * 16 + 4,
|
|
||||||
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.pipeline = this.mesh.pipeline(this.app, this.shader, {});
|
|
||||||
|
|
||||||
this.uniformBindGroup = this.app.device.createBindGroup({
|
|
||||||
layout: this.pipeline.getBindGroupLayout(0),
|
|
||||||
entries: [
|
|
||||||
{
|
|
||||||
binding: 0,
|
|
||||||
resource: {
|
|
||||||
buffer: this.uniformBuffer,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
this.renderPassDescriptor = {
|
|
||||||
colorAttachments: [
|
|
||||||
// {
|
|
||||||
// view: undefined as any, // defined in onUpdate
|
|
||||||
// clearValue: { r: 1, g: 0, b: 1, a: 1 },
|
|
||||||
// loadOp: "clear",
|
|
||||||
// storeOp: "store",
|
|
||||||
// },
|
|
||||||
],
|
|
||||||
depthStencilAttachment: {
|
|
||||||
view: this.depthTexture.createView(),
|
|
||||||
depthClearValue: 1.0,
|
|
||||||
depthLoadOp: "clear",
|
|
||||||
depthStoreOp: "store",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.vertexBuffer = this.mesh.buffer(this.app);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private writeUniforms(modelViewProjection: Mat4, time: number) {
|
onRenderableUpdate(time: number, transform: Transform) {
|
||||||
if (!this.uniformBuffer) {
|
const gl = this.app.gl;
|
||||||
return;
|
this.shader.use();
|
||||||
}
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.meshBuffer);
|
||||||
|
this.shader.setupUniforms(
|
||||||
const {
|
time,
|
||||||
device: { queue },
|
this.projectionMatrix,
|
||||||
} = this.app;
|
transform,
|
||||||
|
this.camera
|
||||||
const mvpBuf = modelViewProjection as Float32Array;
|
);
|
||||||
queue.writeBuffer(
|
gl.drawArrays(
|
||||||
this.uniformBuffer,
|
this.config.drawMode ?? gl.TRIANGLE_STRIP,
|
||||||
0,
|
0,
|
||||||
mvpBuf.buffer,
|
this.mesh.config.vertexCount
|
||||||
mvpBuf.byteOffset,
|
|
||||||
mvpBuf.length
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const timeBuf = new Float32Array([time]);
|
const err = gl.getError();
|
||||||
queue.writeBuffer(
|
if (err !== 0) {
|
||||||
this.uniformBuffer,
|
console.log({ err });
|
||||||
mvpBuf.length + 1,
|
throw new Error(
|
||||||
timeBuf.buffer,
|
`(MeshRenderer<Mesh#${this.mesh.name}>) webgl failure: ${err}`
|
||||||
timeBuf.byteOffset,
|
);
|
||||||
timeBuf.byteLength
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onUpdate(time: number) {
|
|
||||||
if (
|
|
||||||
!this.renderPassDescriptor ||
|
|
||||||
!this.pipeline ||
|
|
||||||
!this.uniformBindGroup ||
|
|
||||||
!this.vertexBuffer
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mvp = mat4.multiply(this.projectionMatrix, this.viewMatrix);
|
|
||||||
this.writeUniforms(mvp, time);
|
|
||||||
|
|
||||||
const { device } = this.app;
|
|
||||||
|
|
||||||
const commandEncoder = device.createCommandEncoder();
|
|
||||||
const passEncoder = commandEncoder.beginRenderPass(
|
|
||||||
this.renderPassDescriptor
|
|
||||||
);
|
|
||||||
|
|
||||||
passEncoder.setPipeline(this.pipeline);
|
|
||||||
passEncoder.setBindGroup(0, this.uniformBindGroup);
|
|
||||||
passEncoder.setVertexBuffer(0, this.vertexBuffer);
|
|
||||||
passEncoder.draw(this.mesh.config.vertexCount);
|
|
||||||
passEncoder.end();
|
|
||||||
|
|
||||||
this.app.commit(commandEncoder.finish());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,80 +1,17 @@
|
||||||
import { Oops, Shader } from "./shader";
|
|
||||||
import { WebGPUApp } from "./webgpu";
|
|
||||||
|
|
||||||
export type MeshConfig = {
|
export type MeshConfig = {
|
||||||
mesh: Float32Array;
|
mesh: Float32Array;
|
||||||
positionSize: number;
|
positionSize: number;
|
||||||
colorSize: number;
|
colorSize: number;
|
||||||
uvSize: number;
|
uvSize: number;
|
||||||
vertexCount: number;
|
vertexCount: number;
|
||||||
|
stride: number;
|
||||||
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Mesh {
|
export class Mesh {
|
||||||
constructor(public config: MeshConfig) {}
|
constructor(public config: MeshConfig) {}
|
||||||
|
|
||||||
buffer(app: WebGPUApp) {
|
get name() {
|
||||||
const buffer = app.device.createBuffer({
|
return this.config.name;
|
||||||
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, shader: Shader, config: Record<string, any>) {
|
|
||||||
const module = 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",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
struct Uniforms {
|
|
||||||
modelViewProjectionMatrix: mat4x4<f32>,
|
|
||||||
time: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
@group(0) @binding(0) var<uniform> 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);
|
|
||||||
}
|
|
18
src/renderer/renderable.ts
Normal file
18
src/renderer/renderable.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { Behavior } from "./behavior";
|
||||||
|
import { MeshRenderer } from "./mesh-renderer";
|
||||||
|
import { Transform } from "./transform";
|
||||||
|
import { WebGLApp } from "./webgl";
|
||||||
|
|
||||||
|
export class Renderable extends Behavior {
|
||||||
|
constructor(
|
||||||
|
public app: WebGLApp,
|
||||||
|
public transform: Transform,
|
||||||
|
public renderer: MeshRenderer
|
||||||
|
) {
|
||||||
|
super(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdate(time: number) {
|
||||||
|
this.renderer.onRenderableUpdate(time, this.transform);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,38 +1,132 @@
|
||||||
import { WebGPUApp } from "./webgpu";
|
import { mat4, vec3 } from "gl-matrix";
|
||||||
import oopsWsgl from "./oops.wgsl";
|
import { Transform } from "./transform";
|
||||||
|
import { WebGLApp } from "./webgl";
|
||||||
|
|
||||||
const pragmaRegexp = new RegExp("#pragma ([a-z]+) ([a-zA-Z_0-9]+)", "g");
|
export type ShaderConfig = {
|
||||||
|
time?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export class Shader {
|
export class Shader {
|
||||||
private _module: GPUShaderModule | null = null;
|
static VERTEX = 35633;
|
||||||
public vertexMain: string = "main";
|
static FRAGMENT = 35632;
|
||||||
public fragmentMain: string = "main";
|
|
||||||
public code: string;
|
|
||||||
|
|
||||||
constructor(...code: string[]) {
|
constructor(private config: ShaderConfig = {}) {}
|
||||||
this.code = code.join("\n");
|
|
||||||
|
|
||||||
// pragma preprocessing
|
private vertexCode = "";
|
||||||
const matches = this.code.matchAll(pragmaRegexp);
|
private fragmentCode = "";
|
||||||
for (const match of matches || []) {
|
private _app?: WebGLApp;
|
||||||
switch (match[1]) {
|
private _program: WebGLProgram | null = null;
|
||||||
case "fragment":
|
|
||||||
this.fragmentMain = match[2];
|
get gl() {
|
||||||
break;
|
const gl = this._app?.gl;
|
||||||
case "vertex":
|
if (!gl) {
|
||||||
this.vertexMain = match[2];
|
throw new Error("GL context not defined at shader compile time.");
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
|
return gl;
|
||||||
|
}
|
||||||
|
|
||||||
|
app(app: WebGLApp) {
|
||||||
|
this._app = app;
|
||||||
|
this._program = app.gl.createProgram();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
vertex(code: string) {
|
||||||
|
this.vertexCode = code;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment(code: string) {
|
||||||
|
this.fragmentCode = code;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
attrib(name: string) {
|
||||||
|
return this.gl.getAttribLocation(this._program as WebGLProgram, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
uniform(name: string) {
|
||||||
|
return this.gl.getUniformLocation(this._program as WebGLProgram, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
attach(which: number, source: string) {
|
||||||
|
const gl = this.gl;
|
||||||
|
|
||||||
|
const shader = gl.createShader(which);
|
||||||
|
if (!shader) {
|
||||||
|
throw new Error(`failed to init ${humanShaderType(which)} shader`);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.shaderSource(shader, source);
|
||||||
|
gl.compileShader(shader);
|
||||||
|
gl.attachShader(this._program as WebGLProgram, shader);
|
||||||
|
}
|
||||||
|
|
||||||
|
compile() {
|
||||||
|
const gl = this.gl;
|
||||||
|
this.attach(gl.FRAGMENT_SHADER, this.fragmentCode);
|
||||||
|
this.attach(gl.VERTEX_SHADER, this.vertexCode);
|
||||||
|
this.link();
|
||||||
|
}
|
||||||
|
|
||||||
|
link() {
|
||||||
|
this.gl.linkProgram(this._program as WebGLProgram);
|
||||||
|
if (
|
||||||
|
!this.gl.getProgramParameter(
|
||||||
|
this._program as WebGLProgram,
|
||||||
|
this.gl.LINK_STATUS
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"Unable to initialize the shader program: " +
|
||||||
|
this.gl.getProgramInfoLog(this._program as WebGLProgram)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module(app: WebGPUApp) {
|
bindAttrib(attribLocation: number, name: string) {
|
||||||
return (this._module =
|
this.gl.bindAttribLocation(
|
||||||
this._module ||
|
this._program as WebGLProgram,
|
||||||
(this._module = app.device.createShaderModule({
|
attribLocation,
|
||||||
code: this.code,
|
name
|
||||||
})));
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupUniforms(
|
||||||
|
time: number,
|
||||||
|
projection: mat4,
|
||||||
|
model: Transform,
|
||||||
|
view: Transform
|
||||||
|
) {
|
||||||
|
const { gl } = this._app as WebGLApp;
|
||||||
|
gl.useProgram(this._program as WebGLProgram);
|
||||||
|
gl.uniformMatrix4fv(this.uniform("uProjectionMatrix"), false, projection);
|
||||||
|
|
||||||
|
if (this.config.time) {
|
||||||
|
gl.uniform1f(this.uniform("uTime"), time);
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelMat = mat4.clone(model.toMat4());
|
||||||
|
mat4.fromQuat(modelMat, view.rotation);
|
||||||
|
mat4.translate(modelMat, modelMat, view.position);
|
||||||
|
|
||||||
|
gl.uniformMatrix4fv(this.uniform("uModelViewMatrix"), false, modelMat);
|
||||||
|
}
|
||||||
|
|
||||||
|
use() {
|
||||||
|
this._app?.gl.useProgram(this._program);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Oops = new Shader(oopsWsgl);
|
const humanShaderType = (which: number): string => {
|
||||||
|
switch (which) {
|
||||||
|
case Shader.FRAGMENT:
|
||||||
|
return "fragment";
|
||||||
|
case Shader.VERTEX:
|
||||||
|
return "vertex";
|
||||||
|
default:
|
||||||
|
return "some unknown type of";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Behavior } from "./behavior";
|
import { Behavior } from "./behavior";
|
||||||
import { WebGPUApp } from "./webgpu";
|
import { WebGLApp } from "./webgl";
|
||||||
|
|
||||||
export class Telemetry extends Behavior {
|
export class Telemetry extends Behavior {
|
||||||
public el: HTMLElement;
|
public el: HTMLElement;
|
||||||
|
@ -7,7 +7,7 @@ export class Telemetry extends Behavior {
|
||||||
public maxFrameTimes: number = 100;
|
public maxFrameTimes: number = 100;
|
||||||
public lastFrameTime: number = 0;
|
public lastFrameTime: number = 0;
|
||||||
constructor(
|
constructor(
|
||||||
public app: WebGPUApp,
|
public app: WebGLApp,
|
||||||
selector = "#telemetry"
|
selector = "#telemetry"
|
||||||
) {
|
) {
|
||||||
super(app);
|
super(app);
|
||||||
|
@ -35,14 +35,13 @@ export class Telemetry extends Behavior {
|
||||||
|
|
||||||
const framesPerSecond = 1000 / averageFrameTime;
|
const framesPerSecond = 1000 / averageFrameTime;
|
||||||
|
|
||||||
this.el.innerHTML = `
|
this.el.innerHTML = `${framesPerSecond.toFixed(
|
||||||
${framesPerSecond.toFixed(1)} FPS (${averageFrameTime.toFixed(
|
1
|
||||||
3
|
)} FPS (${averageFrameTime.toFixed(3)} ms)<br />bU: ${
|
||||||
)} ms)<br />
|
this.app.registry.onBeforeUpdate.length
|
||||||
bU: ${this.app.registry.onBeforeUpdate.length} | U: ${
|
} | U: ${this.app.registry.onUpdate.length} | aU: ${
|
||||||
this.app.registry.onUpdate.length
|
this.app.registry.onAfterUpdate.length
|
||||||
} | aU: ${this.app.registry.onAfterUpdate.length}
|
}`;
|
||||||
`;
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
18
src/renderer/transform.ts
Normal file
18
src/renderer/transform.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { mat4, vec3, quat } from "gl-matrix";
|
||||||
|
|
||||||
|
export class Transform {
|
||||||
|
constructor(
|
||||||
|
public position = vec3.create(),
|
||||||
|
public rotation = quat.create(),
|
||||||
|
public scale = vec3.fromValues(1, 1, 1)
|
||||||
|
) {}
|
||||||
|
|
||||||
|
toMat4() {
|
||||||
|
return mat4.fromRotationTranslationScale(
|
||||||
|
mat4.create(),
|
||||||
|
this.rotation,
|
||||||
|
this.position,
|
||||||
|
this.scale
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
120
src/renderer/webgl.ts
Normal file
120
src/renderer/webgl.ts
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import { Telemetry } from "./telemetry";
|
||||||
|
|
||||||
|
export type WebGPUAppConfig = {
|
||||||
|
fov?: number;
|
||||||
|
context?: GPUCanvasConfiguration;
|
||||||
|
zNear?: number;
|
||||||
|
zFar?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RenderHandle = (time: number, app: WebGLApp) => void;
|
||||||
|
|
||||||
|
export class WebGLApp {
|
||||||
|
public canvas: HTMLCanvasElement;
|
||||||
|
public telemetry?: Telemetry;
|
||||||
|
public gl: WebGL2RenderingContext;
|
||||||
|
|
||||||
|
public registry: {
|
||||||
|
onBeforeUpdate: RenderHandle[];
|
||||||
|
onAfterUpdate: RenderHandle[];
|
||||||
|
onUpdate: RenderHandle[];
|
||||||
|
onStart: RenderHandle[];
|
||||||
|
} = {
|
||||||
|
onBeforeUpdate: [],
|
||||||
|
onAfterUpdate: [],
|
||||||
|
onUpdate: [],
|
||||||
|
onStart: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(public config: WebGPUAppConfig = {}) {
|
||||||
|
try {
|
||||||
|
this.canvas = document.querySelector("canvas") as HTMLCanvasElement;
|
||||||
|
this.canvas.width = window.innerWidth;
|
||||||
|
this.canvas.height = window.innerHeight;
|
||||||
|
|
||||||
|
const context = this.canvas.getContext("webgl2");
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("Canvas was unable to get a webgl2 context");
|
||||||
|
}
|
||||||
|
this.gl = context;
|
||||||
|
|
||||||
|
if (location.search.includes("telemetry")) {
|
||||||
|
this.telemetry = new Telemetry(this);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
const main = document.querySelector("main");
|
||||||
|
if (main) {
|
||||||
|
main.innerHTML = `<div><i>your browser didn't let me set up webgl.</i></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`Unable to initialize WebGL. Your browser or machine may not support it.\n -> ${e}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
const gl = this.gl;
|
||||||
|
gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black, fully opaque
|
||||||
|
gl.clearDepth(1.0); // Clear everything
|
||||||
|
gl.enable(gl.DEPTH_TEST); // Enable depth testing
|
||||||
|
gl.depthFunc(gl.LEQUAL); // Near things obscure far things
|
||||||
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUpdate(handle: RenderHandle) {
|
||||||
|
this.registry.onBeforeUpdate.push(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
onAfterUpdate(handle: RenderHandle) {
|
||||||
|
this.registry.onAfterUpdate.push(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdate(handle: RenderHandle) {
|
||||||
|
this.registry.onUpdate.push(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
onStart(handle: RenderHandle) {
|
||||||
|
this.registry.onStart.push(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
doUpdate(time: number) {
|
||||||
|
// this.jobsToSubmitThisFrame = [];
|
||||||
|
|
||||||
|
this.registry.onBeforeUpdate.forEach((handle) => handle(time, this));
|
||||||
|
this.registry.onUpdate.forEach((handle) => handle(time, this));
|
||||||
|
this.registry.onAfterUpdate.forEach((handle) => handle(time, this));
|
||||||
|
|
||||||
|
// if (this.jobsToSubmitThisFrame.length !== 0) {
|
||||||
|
// this.device.queue.submit(this.jobsToSubmitThisFrame);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
doStart(time: number = 0) {
|
||||||
|
this.clear();
|
||||||
|
this.registry.onStart.forEach((handle) => handle(time, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
async oneShot(time: number = 0) {
|
||||||
|
// await this.awaitRendererReady();
|
||||||
|
|
||||||
|
this.doStart(time);
|
||||||
|
this.doUpdate(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
// await this.awaitRendererReady();
|
||||||
|
|
||||||
|
this.doStart();
|
||||||
|
|
||||||
|
const run = (time: number) => {
|
||||||
|
this.doUpdate(time);
|
||||||
|
requestAnimationFrame(run);
|
||||||
|
};
|
||||||
|
requestAnimationFrame(run);
|
||||||
|
}
|
||||||
|
|
||||||
|
// commit(commandEncoder: GPUCommandBuffer) {
|
||||||
|
// this.jobsToSubmitThisFrame.push(commandEncoder);
|
||||||
|
// }
|
||||||
|
}
|
|
@ -1,177 +0,0 @@
|
||||||
import { Telemetry } from "./telemetry";
|
|
||||||
|
|
||||||
export type WebGPUAppConfig = {
|
|
||||||
fov?: number;
|
|
||||||
context?: GPUCanvasConfiguration;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RenderHandle = (time: number, app: WebGPUApp) => void;
|
|
||||||
|
|
||||||
export class WebGPUApp {
|
|
||||||
public canvas: HTMLCanvasElement;
|
|
||||||
private _adapter?: GPUAdapter;
|
|
||||||
private _device?: GPUDevice;
|
|
||||||
private _context?: GPUCanvasContext;
|
|
||||||
public telemetry?: Telemetry;
|
|
||||||
private jobsToSubmitThisFrame: GPUCommandBuffer[] = [];
|
|
||||||
private renderOK = false;
|
|
||||||
|
|
||||||
public registry: {
|
|
||||||
onBeforeUpdate: RenderHandle[];
|
|
||||||
onAfterUpdate: RenderHandle[];
|
|
||||||
onUpdate: RenderHandle[];
|
|
||||||
onStart: RenderHandle[];
|
|
||||||
} = {
|
|
||||||
onBeforeUpdate: [],
|
|
||||||
onAfterUpdate: [],
|
|
||||||
onUpdate: [],
|
|
||||||
onStart: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
if (location.search.includes("telemetry")) {
|
|
||||||
this.telemetry = new Telemetry(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.init().catch((e) => {
|
|
||||||
const main = document.querySelector("main");
|
|
||||||
if (main) {
|
|
||||||
main.innerHTML = `<div><i>your browser didn't let me set up webgpu. firefox nightly or enable <code>dom.webgpu.enable</code>.</i></div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.renderOK = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
awaitRendererReady(timeout: number = 5000) {
|
|
||||||
const start = Date.now();
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
if (this.renderOK) {
|
|
||||||
return resolve(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Date.now() - start > timeout) {
|
|
||||||
return reject(`Renderer was not OK within ${timeout}ms`);
|
|
||||||
}
|
|
||||||
}, 10);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
onBeforeUpdate(handle: RenderHandle) {
|
|
||||||
this.registry.onBeforeUpdate.push(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
onAfterUpdate(handle: RenderHandle) {
|
|
||||||
this.registry.onAfterUpdate.push(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
onUpdate(handle: RenderHandle) {
|
|
||||||
this.registry.onUpdate.push(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
onStart(handle: RenderHandle) {
|
|
||||||
this.registry.onStart.push(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
doUpdate(time: number) {
|
|
||||||
this.jobsToSubmitThisFrame = [];
|
|
||||||
|
|
||||||
this.registry.onBeforeUpdate.forEach((handle) => handle(time, this));
|
|
||||||
this.registry.onUpdate.forEach((handle) => handle(time, this));
|
|
||||||
this.registry.onAfterUpdate.forEach((handle) => handle(time, this));
|
|
||||||
|
|
||||||
if (this.jobsToSubmitThisFrame.length !== 0) {
|
|
||||||
this.device.queue.submit(this.jobsToSubmitThisFrame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
doStart(time: number = 0) {
|
|
||||||
this.registry.onStart.forEach((handle) => handle(time, this));
|
|
||||||
}
|
|
||||||
|
|
||||||
async oneShot(time: number = 0) {
|
|
||||||
await this.awaitRendererReady();
|
|
||||||
|
|
||||||
this.doStart(time);
|
|
||||||
this.doUpdate(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
async start() {
|
|
||||||
await this.awaitRendererReady();
|
|
||||||
|
|
||||||
this.doStart();
|
|
||||||
|
|
||||||
const run = (time: number) => {
|
|
||||||
this.doUpdate(time);
|
|
||||||
requestAnimationFrame(run);
|
|
||||||
};
|
|
||||||
requestAnimationFrame(run);
|
|
||||||
}
|
|
||||||
|
|
||||||
commit(commandEncoder: GPUCommandBuffer) {
|
|
||||||
this.jobsToSubmitThisFrame.push(commandEncoder);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
struct Uniforms {
|
|
||||||
modelViewProjectionMatrix: mat4x4<f32>,
|
|
||||||
time: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
Loading…
Add table
Reference in a new issue