more webgpu scaffolding
This commit is contained in:
parent
c245a4700a
commit
441ab63660
21 changed files with 253 additions and 74 deletions
|
@ -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
|
||||
|
|
49
src/002-webgpu-instead/rainbow-plane.wgsl
Normal file
49
src/002-webgpu-instead/rainbow-plane.wgsl
Normal file
|
@ -0,0 +1,49 @@
|
|||
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,
|
||||
) -> @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);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { Telemetry } from "./telemetry.js";
|
||||
import { Telemetry } from "./renderer/telemetry";
|
||||
|
||||
export class App {
|
||||
constructor(
|
||||
|
|
0
src/meshes/plane.ts
Normal file
0
src/meshes/plane.ts
Normal file
16
src/renderer/mesh.ts
Normal file
16
src/renderer/mesh.ts
Normal file
|
@ -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) {}
|
||||
}
|
26
src/renderer/oops.wgsl
Normal file
26
src/renderer/oops.wgsl
Normal file
|
@ -0,0 +1,26 @@
|
|||
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);
|
||||
}
|
19
src/renderer/shader.ts
Normal file
19
src/renderer/shader.ts
Normal file
|
@ -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]);
|
|
@ -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) {
|
86
src/renderer/webgpu.ts
Normal file
86
src/renderer/webgpu.ts
Normal file
|
@ -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 = `<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,
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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 = `<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