switch to bun build instead of statics
This commit is contained in:
parent
8cbe93d74d
commit
adc3a38395
26 changed files with 357 additions and 230 deletions
71
src/001-platform-provenance/main.ts
Normal file
71
src/001-platform-provenance/main.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { Shader } from "../shader";
|
||||
import { BasicPlane } from "../basic-plane";
|
||||
import { App } from "../app";
|
||||
|
||||
const app = new App({ fov: 20 });
|
||||
const gl = app.gl;
|
||||
|
||||
const shader = new Shader(app)
|
||||
.attach(
|
||||
gl.VERTEX_SHADER,
|
||||
`
|
||||
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;
|
||||
}
|
||||
`
|
||||
)
|
||||
.attach(
|
||||
gl.FRAGMENT_SHADER,
|
||||
`
|
||||
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);
|
||||
}
|
||||
`
|
||||
)
|
||||
.link();
|
||||
|
||||
const plane = new BasicPlane(app);
|
||||
plane.attachShader(shader);
|
||||
|
||||
app.loop();
|
36
src/002-enter-the-third/main.ts
Normal file
36
src/002-enter-the-third/main.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { Shader } from "../shader";
|
||||
import { BasicPlane } from "../basic-plane";
|
||||
import { App } from "../app";
|
||||
|
||||
const app = new App({ fov: 20 });
|
||||
const gl = app.gl;
|
||||
|
||||
const shader = new Shader(app)
|
||||
.attach(
|
||||
gl.VERTEX_SHADER,
|
||||
`
|
||||
attribute vec4 aVertexPosition;
|
||||
attribute vec2 aTextureCoord;
|
||||
|
||||
uniform mat4 uModelViewMatrix;
|
||||
uniform mat4 uProjectionMatrix;
|
||||
|
||||
void main() {
|
||||
gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
|
||||
}
|
||||
`
|
||||
)
|
||||
.attach(
|
||||
gl.FRAGMENT_SHADER,
|
||||
`
|
||||
void main() {
|
||||
gl_FragColor = vec4(1.0);
|
||||
}
|
||||
`
|
||||
)
|
||||
.link();
|
||||
|
||||
const plane = new BasicPlane(app);
|
||||
plane.attachShader(shader);
|
||||
|
||||
app.loop();
|
120
src/app.js
Normal file
120
src/app.js
Normal file
|
@ -0,0 +1,120 @@
|
|||
import { Telemetry } from "./telemetry.js";
|
||||
|
||||
export class App {
|
||||
constructor(
|
||||
config = {
|
||||
fov: 45,
|
||||
}
|
||||
) {
|
||||
this._now = 0;
|
||||
this.registry = {
|
||||
onStart: [],
|
||||
onUpdate: [],
|
||||
onBeforeUpdate: [],
|
||||
onAfterUpdate: [],
|
||||
};
|
||||
|
||||
this.config = config;
|
||||
this.canvas = document.querySelector("canvas");
|
||||
this.canvas.width = window.innerWidth;
|
||||
this.canvas.height = window.innerHeight;
|
||||
this.gl = this.canvas.getContext("webgl2");
|
||||
|
||||
if (this.gl === null) {
|
||||
document.querySelector(
|
||||
"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."
|
||||
);
|
||||
}
|
||||
|
||||
const gl = this.gl;
|
||||
|
||||
const fieldOfView = (this.config.fov * Math.PI) / 180; // in radians
|
||||
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
|
||||
const zNear = 0.1;
|
||||
const zFar = 100.0;
|
||||
const projectionMatrix = glMatrix.mat4.create();
|
||||
glMatrix.mat4.perspective(
|
||||
projectionMatrix,
|
||||
fieldOfView,
|
||||
aspect,
|
||||
zNear,
|
||||
zFar
|
||||
);
|
||||
|
||||
const modelViewMatrix = glMatrix.mat4.create();
|
||||
glMatrix.mat4.translate(
|
||||
modelViewMatrix,
|
||||
modelViewMatrix,
|
||||
[-0.0, 0.0, -6.0]
|
||||
);
|
||||
|
||||
this.projectionMatrix = projectionMatrix;
|
||||
this.modelViewMatrix = modelViewMatrix;
|
||||
|
||||
this.clear();
|
||||
this.onBeforeUpdate(() => this.clear());
|
||||
|
||||
this.telemetry = new Telemetry(this);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
onStart(fn) {
|
||||
this.registry.onStart.push(fn);
|
||||
}
|
||||
|
||||
onUpdate(fn) {
|
||||
this.registry.onUpdate.push(fn);
|
||||
}
|
||||
|
||||
onBeforeUpdate(fn) {
|
||||
this.registry.onBeforeUpdate.push(fn);
|
||||
}
|
||||
|
||||
onAfterUpdate(fn) {
|
||||
this.registry.onAfterUpdate.push(fn);
|
||||
}
|
||||
|
||||
start() {
|
||||
this.registry.onStart.forEach((fn) => fn(this));
|
||||
}
|
||||
|
||||
update() {
|
||||
this.registry.onBeforeUpdate.forEach((fn) => fn(this));
|
||||
this.registry.onUpdate.forEach((fn) => fn(this));
|
||||
this.registry.onAfterUpdate.forEach((fn) => fn(this));
|
||||
}
|
||||
|
||||
oneShot() {
|
||||
requestAnimationFrame((now) => {
|
||||
this._now = now;
|
||||
this.start();
|
||||
this.update();
|
||||
});
|
||||
}
|
||||
|
||||
loop() {
|
||||
const run = (now) => {
|
||||
this._now = now;
|
||||
this.update();
|
||||
requestAnimationFrame(run);
|
||||
};
|
||||
|
||||
requestAnimationFrame(run);
|
||||
}
|
||||
|
||||
now() {
|
||||
return this._now;
|
||||
}
|
||||
}
|
17
src/basic-plane.js
Normal file
17
src/basic-plane.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { Object } from "./object.js";
|
||||
|
||||
export class BasicPlane extends Object {
|
||||
constructor(app) {
|
||||
super(app);
|
||||
|
||||
this.vertexPositions = new Float32Array([
|
||||
-1.0, -1.0, +1.0, -1.0, -1.0, +1.0, +1.0, +1.0,
|
||||
]);
|
||||
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());
|
||||
}
|
||||
}
|
12
src/gltf-object.js
Normal file
12
src/gltf-object.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { NetworkObject } from "./network-object";
|
||||
|
||||
export class glTFObject extends NetworkObject {
|
||||
constructor(app) {
|
||||
super(app);
|
||||
this.register();
|
||||
}
|
||||
|
||||
async handleModelData(response) {
|
||||
const raw = await response.json();
|
||||
}
|
||||
}
|
23
src/network-object.js
Normal file
23
src/network-object.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { Object } from "./object";
|
||||
|
||||
export class NetworkObject extends Object {
|
||||
constructor(app) {
|
||||
super(app);
|
||||
|
||||
this.loaded = false;
|
||||
}
|
||||
|
||||
register() {
|
||||
app.onUpdate(() => {
|
||||
if (this.loaded) {
|
||||
this.draw3D();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async load(url) {
|
||||
const response = await fetch(url);
|
||||
await this.handleModelData(response);
|
||||
this.loaded = true;
|
||||
}
|
||||
}
|
68
src/object.js
Normal file
68
src/object.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
export class Object {
|
||||
constructor(app) {
|
||||
this.gl = app.gl;
|
||||
this.app = app;
|
||||
|
||||
this.vertexPositions = new Float32Array([]);
|
||||
this.positionBuffer = null;
|
||||
this.textureBuffer = null;
|
||||
}
|
||||
|
||||
initBuffer(data, draw = this.gl.STATIC_DRAW) {
|
||||
const buffer = this.gl.createBuffer();
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
|
||||
this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(data), draw);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
attachShader(shader) {
|
||||
this.shader = shader;
|
||||
this.vertexPosition = shader.location("aVertexPosition");
|
||||
this.textureCoord = shader.location("aTextureCoord");
|
||||
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
|
||||
this.gl.vertexAttribPointer(
|
||||
this.vertexPosition,
|
||||
2,
|
||||
this.gl.FLOAT,
|
||||
false,
|
||||
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,
|
||||
false,
|
||||
0,
|
||||
0
|
||||
);
|
||||
this.gl.enableVertexAttribArray(this.textureCoord);
|
||||
|
||||
return 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
|
||||
);
|
||||
}
|
||||
}
|
116
src/ply-object.js
Normal file
116
src/ply-object.js
Normal file
|
@ -0,0 +1,116 @@
|
|||
import { NetworkObject } from "./network-object";
|
||||
|
||||
export class PlyObject extends NetworkObject {
|
||||
constructor(app) {
|
||||
super(app);
|
||||
this.register();
|
||||
}
|
||||
|
||||
async handleModelData(response) {
|
||||
const raw = await response.text();
|
||||
|
||||
const config = {
|
||||
vertex: {
|
||||
count: 0,
|
||||
properties: [],
|
||||
},
|
||||
face: {
|
||||
count: 0,
|
||||
properties: [],
|
||||
},
|
||||
};
|
||||
|
||||
const data = {
|
||||
vertexes: [],
|
||||
faces: [],
|
||||
};
|
||||
|
||||
let phase = "header"; // "vertex", "face", ...
|
||||
let currentField = null;
|
||||
|
||||
const headerHandlers = {
|
||||
ply: () => {},
|
||||
format: (ascii, version) => {
|
||||
if (ascii !== "ascii") {
|
||||
throw new Error("Only ascii ply files are supported");
|
||||
}
|
||||
|
||||
if (version !== "1.0") {
|
||||
throw new Error("Only version 1.0 ply files are supported");
|
||||
}
|
||||
},
|
||||
comment: () => {},
|
||||
element: (which, value) => {
|
||||
currentField = which;
|
||||
if (which === "vertex") {
|
||||
config.vertex.count = Number(value);
|
||||
} else if (which === "face") {
|
||||
config.face.count = Number(value);
|
||||
}
|
||||
},
|
||||
property: (what, ...data) => {
|
||||
if (what === "float") {
|
||||
config[currentField].properties.push({
|
||||
name: data[1],
|
||||
type: "float",
|
||||
});
|
||||
} else if (what === "list") {
|
||||
config[currentField].properties.push({
|
||||
name: data[2],
|
||||
type: "list",
|
||||
indexType: data[0],
|
||||
valueType: data[1],
|
||||
});
|
||||
}
|
||||
},
|
||||
end_header: () => {
|
||||
phase = "vertex";
|
||||
},
|
||||
};
|
||||
|
||||
const lines = raw.split("\n");
|
||||
for (const line of lines) {
|
||||
const parts = line.split(" ");
|
||||
|
||||
if (phase === "header") {
|
||||
const handler = headerHandlers[parts[0]];
|
||||
if (handler) {
|
||||
handler(...parts.slice(1));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (phase === "vertex") {
|
||||
const vertex = {};
|
||||
for (let i = 0; i < config.vertex.properties.length; i++) {
|
||||
const property = config.vertex.properties[i];
|
||||
if (property.type === "float") {
|
||||
vertex[property.name] = Number(parts[i]);
|
||||
}
|
||||
}
|
||||
data.vertexes.push(vertex);
|
||||
|
||||
if (data.vertexes.length === config.vertex.count) {
|
||||
phase = "face";
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (phase === "face") {
|
||||
const face = [];
|
||||
for (let i = 1; i < parts.length; i++) {
|
||||
face.push(Number(parts[i]));
|
||||
}
|
||||
data.faces.push(face);
|
||||
|
||||
if (data.faces.length === config.face.count) {
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
52
src/public/index.css
Normal file
52
src/public/index.css
Normal file
|
@ -0,0 +1,52 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
font-size: 2.5rem;
|
||||
align-items: center;
|
||||
|
||||
& .subtext {
|
||||
font-size: 1rem;
|
||||
margin-left: 0.5rem;
|
||||
color: hsl(39, 68.6%, 31.2%);
|
||||
& a:hover {
|
||||
color: hsl(39, 100%, 80%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding-left: 3rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
li {
|
||||
& a {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: transparent;
|
||||
}
|
||||
&:hover {
|
||||
color: hsl(39, 100%, 80%);
|
||||
& a {
|
||||
text-decoration-color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "▸";
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
12
src/public/work.css
Normal file
12
src/public/work.css
Normal file
|
@ -0,0 +1,12 @@
|
|||
#telemetry {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
color: white;
|
||||
font: monospace;
|
||||
z-index: 100;
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
display: none;
|
||||
}
|
107
src/shader.js
Normal file
107
src/shader.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
export class Shader {
|
||||
constructor(app) {
|
||||
this.gl = app.gl;
|
||||
this.app = app;
|
||||
this.program = this.gl.createProgram();
|
||||
}
|
||||
|
||||
attach(type, source) {
|
||||
console.log("attaching shader", { type, source });
|
||||
const shader = this.gl.createShader(type);
|
||||
|
||||
this.gl.shaderSource(shader, source);
|
||||
this.gl.compileShader(shader);
|
||||
|
||||
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
|
||||
throw new Error(
|
||||
"An error occurred compiling the shaders: " +
|
||||
this.gl.getShaderInfoLog(shader)
|
||||
);
|
||||
}
|
||||
|
||||
this.gl.attachShader(this.program, shader);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
link() {
|
||||
this.gl.linkProgram(this.program);
|
||||
|
||||
if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {
|
||||
throw new Error(
|
||||
"Unable to initialize the shader program: " +
|
||||
this.gl.getProgramInfoLog(this.program)
|
||||
);
|
||||
}
|
||||
|
||||
console.log("shader linked");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
location(name) {
|
||||
if (name[0] === "a") {
|
||||
return this.gl.getAttribLocation(this.program, name);
|
||||
} else if (name[0] === "u") {
|
||||
return this.gl.getUniformLocation(this.program, name);
|
||||
}
|
||||
}
|
||||
|
||||
updateTime() {
|
||||
const time = this.app.now();
|
||||
const sinTime = Math.sin(time);
|
||||
const cosTime = Math.cos(time);
|
||||
this.gl.uniform1f(this.location("uTime"), time);
|
||||
this.gl.uniform1f(this.location("uSinTime"), sinTime);
|
||||
this.gl.uniform1f(this.location("uCosTime"), cosTime);
|
||||
}
|
||||
|
||||
activate(projectionMatrix, modelViewMatrix) {
|
||||
this.gl.useProgram(this.program);
|
||||
this.gl.uniformMatrix4fv(
|
||||
this.location("uProjectionMatrix"),
|
||||
false,
|
||||
projectionMatrix
|
||||
);
|
||||
this.gl.uniformMatrix4fv(
|
||||
this.location("uModelViewMatrix"),
|
||||
false,
|
||||
modelViewMatrix
|
||||
);
|
||||
this.updateTime();
|
||||
}
|
||||
}
|
||||
|
||||
export const colorUtils = `
|
||||
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);
|
||||
}
|
||||
`;
|
||||
|
||||
export const commonFrag = `
|
||||
precision highp float;
|
||||
|
||||
uniform float uTime;
|
||||
uniform float uSinTime;
|
||||
uniform float uCosTime;
|
||||
`;
|
||||
|
||||
export const commonVert = `
|
||||
attribute vec4 aVertexPosition;
|
||||
attribute vec2 aTextureCoord;
|
||||
|
||||
uniform mat4 uModelViewMatrix;
|
||||
uniform mat4 uProjectionMatrix;
|
||||
`;
|
43
src/telemetry.js
Normal file
43
src/telemetry.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
export class Telemetry {
|
||||
constructor(app, selector = "#telemetry") {
|
||||
this.app = app;
|
||||
this.el = document.querySelector(selector);
|
||||
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) {
|
||||
this.frameTimes.push(time);
|
||||
|
||||
if (this.frameTimes.length > this.maxFrameTimes) {
|
||||
this.frameTimes.shift();
|
||||
}
|
||||
}
|
||||
|
||||
onAfterUpdate() {
|
||||
const frameTime = this.app.now() - this.lastFrameTime;
|
||||
this.insertTime(frameTime);
|
||||
|
||||
const averageFrameTime =
|
||||
this.frameTimes.reduce((a, b) => a + b, 0) / this.frameTimes.length;
|
||||
|
||||
const framesPerSecond = 1000 / averageFrameTime;
|
||||
|
||||
this.el.innerHTML = `
|
||||
${framesPerSecond.toFixed(1)} FPS (${averageFrameTime.toFixed(
|
||||
3
|
||||
)} ms)<br />
|
||||
bU: ${this.app.registry.onBeforeUpdate.length} | U: ${
|
||||
this.app.registry.onUpdate.length
|
||||
} | aU: ${this.app.registry.onAfterUpdate.length}
|
||||
`;
|
||||
|
||||
this.lastFrameTime = this.app.now();
|
||||
}
|
||||
}
|
40
src/webgpu-app.js
Normal file
40
src/webgpu-app.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
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 = `<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();
|
||||
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");
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue