diff options
Diffstat (limited to 'js/wgpu')
| -rw-r--r-- | js/wgpu/core/Buffer.js | 50 | ||||
| -rw-r--r-- | js/wgpu/core/Mesh.js | 48 | ||||
| -rw-r--r-- | js/wgpu/core/Object.js | 105 | ||||
| -rw-r--r-- | js/wgpu/core/Renderer.js | 172 | ||||
| -rw-r--r-- | js/wgpu/core/Shader.js | 289 | ||||
| -rw-r--r-- | js/wgpu/core/Texture.js | 51 | ||||
| -rw-r--r-- | js/wgpu/io/File.js | 11 | ||||
| -rw-r--r-- | js/wgpu/io/Input.js | 62 | ||||
| -rw-r--r-- | js/wgpu/math/Mat4.js | 126 | ||||
| -rw-r--r-- | js/wgpu/math/Math.js | 3 | ||||
| -rw-r--r-- | js/wgpu/math/Vec2.js | 103 | ||||
| -rw-r--r-- | js/wgpu/math/Vec3.js | 156 | ||||
| -rw-r--r-- | js/wgpu/math/Vec4.js | 132 | ||||
| -rw-r--r-- | js/wgpu/wgpu.js | 84 |
14 files changed, 1392 insertions, 0 deletions
diff --git a/js/wgpu/core/Buffer.js b/js/wgpu/core/Buffer.js new file mode 100644 index 0000000..190852a --- /dev/null +++ b/js/wgpu/core/Buffer.js @@ -0,0 +1,50 @@ +import { device } from '../wgpu.js' + +class Buffer { + #buffer + #type + + constructor(usage, type, length) { + let desc = { + size: length * (type).BYTES_PER_ELEMENT, + usage: usage | GPUBufferUsage.COPY_DST, + mappedAtCreation: false, + + }; + this.#buffer = device.createBuffer(desc); + this.#type = type + } + + write(data) { + if (Array.isArray(data)) { + data = new (this.#type)(data); + } + device.queue.writeBuffer(this.#buffer, 0, data); + } + + get() { + return this.#buffer + } + + delete() { + this.#buffer.destroy() + } +} + +export class VertexBuffer extends Buffer { + constructor(length) { + super(GPUBufferUsage.VERTEX, Float32Array, length) + } +} + +export class IndexBuffer extends Buffer { + constructor(length) { + super(GPUBufferUsage.INDEX, Uint16Array, length) + } +} + +export class UniformBuffer extends Buffer { + constructor(length) { + super(GPUBufferUsage.UNIFORM, Float32Array, length) + } +} diff --git a/js/wgpu/core/Mesh.js b/js/wgpu/core/Mesh.js new file mode 100644 index 0000000..00cbcde --- /dev/null +++ b/js/wgpu/core/Mesh.js @@ -0,0 +1,48 @@ +import { VertexBuffer, IndexBuffer } from './Buffer.js' + +export class Mesh { + + #vertexCount + #vertexBuffers + #indexBuffer + + constructor(vertexCount) { + this.#vertexCount = vertexCount + this.#vertexBuffers = [] + this.#indexBuffer = null + } + + store(data) { + let buffer = new VertexBuffer(data.length); + buffer.write(data); + this.#vertexBuffers.push(buffer); + return this; + } + + indicies(data) { + this.#indexBuffer = new IndexBuffer(data.length); + this.#indexBuffer.write(data); + return this; + } + + draw(pass) { + this.#vertexBuffers.forEach((buffer, idx) => { + pass.setVertexBuffer(idx, buffer.get()); + }); + if (this.#indexBuffer) { + pass.setIndexBuffer(this.#indexBuffer.get(), "uint16"); + pass.drawIndexed(this.#vertexCount, 1); + } else { + pass.draw(this.#vertexCount, 1); + } + } + + delete() { + for (let buffer of this.#vertexBuffers) { + buffer.delete(); + } + if (this.#indexBuffer) { + this.#indexBuffer.delete(); + } + } +} diff --git a/js/wgpu/core/Object.js b/js/wgpu/core/Object.js new file mode 100644 index 0000000..b759bea --- /dev/null +++ b/js/wgpu/core/Object.js @@ -0,0 +1,105 @@ +import { Vec3 } from '../math/Vec3.js' +import { Mat4 } from '../math/Mat4.js' + +export class Object { + + static #id = 0 + + constructor(mesh = null) { + this.mesh = mesh; + this.position = new Vec3(); + this.rotation = new Vec3(); + this.scale = new Vec3(1, 1, 1); + this.id = Object.#id; + Object.#id++; + } + + view() { + const view = new Mat4(); + const d = view.data; + + const c3 = Math.cos(this.rotation.z * (Math.PI / 180)); + const s3 = Math.sin(this.rotation.z * (Math.PI / 180)); + const c2 = Math.cos(this.rotation.x * (Math.PI / 180)); + const s2 = Math.sin(this.rotation.x * (Math.PI / 180)); + const c1 = Math.cos(this.rotation.y * (Math.PI / 180)); + const s1 = Math.sin(this.rotation.y * (Math.PI / 180)); + + const u = new Vec3((c1 * c3 + s1 * s2 * s3), (c2 * s3), (c1 * s2 * s3 - c3 * s1)); + const v = new Vec3((c3 * s1 * s2 - c1 * s3), (c2 * c3), (c1 * c3 * s2 + s1 * s3)); + const w = new Vec3((c2 * s1), (-s2), (c1 * c2)); + + d[0] = u.x; + d[1] = v.x; + d[2] = w.x; + d[3] = 0; + d[4] = u.y; + d[5] = v.y; + d[6] = w.y; + d[7] = 0; + d[8] = u.z; + d[9] = v.z; + d[10] = w.z; + d[11] = 0; + d[12] = -u.dot(this.position); + d[13] = -v.dot(this.position); + d[14] = -w.dot(this.position); + d[15] = 1; + + return view + } + + axis() { + const view = this.view().invert().data; + const forward = new Vec3(0, 0, 1); + + const u = new Vec3(view[0], view[4], view[8]); + const v = new Vec3(view[1], view[5], view[9]); + const w = new Vec3(view[2], view[6], view[10]); + + return new Vec3( + forward.dot(u), + forward.dot(v), + forward.dot(w), + ); + } + + tran() { + this.rotation.x %= 360; + this.rotation.y %= 360; + this.rotation.z %= 360; + + const tran = Mat4.identity(); + const d = tran.data; + + const c3 = Math.cos(this.rotation.z * (Math.PI / 180)); + const s3 = Math.sin(this.rotation.z * (Math.PI / 180)); + const c2 = Math.cos(this.rotation.x * (Math.PI / 180)); + const s2 = Math.sin(this.rotation.x * (Math.PI / 180)); + const c1 = Math.cos(this.rotation.y * (Math.PI / 180)); + const s1 = Math.sin(this.rotation.y * (Math.PI / 180)); + + d[0] = this.scale.x * (c1 * c3 + s1 * s2 * s3); + d[1] = this.scale.x * (c2 * s3); + d[2] = this.scale.x * (c1 * s2 * s3 - c3 * s1); + d[3] = 0; + + d[4] = this.scale.y * (c3 * s1 * s2 - c1 * s3); + d[5] = this.scale.y * (c2 * c3); + d[6] = this.scale.y * (c1 * c3 * s2 + s1 * s3); + d[7] = 0; + + d[8] = this.scale.z * (c2 * s1); + d[9] = this.scale.z * (-s2); + d[10] = this.scale.z * (c1 * c2); + d[11] = 0; + + d[12] = this.position.x; + d[13] = this.position.y; + d[14] = this.position.z; + d[15] = 1; + + return tran; + } + +} diff --git a/js/wgpu/core/Renderer.js b/js/wgpu/core/Renderer.js new file mode 100644 index 0000000..708b9d0 --- /dev/null +++ b/js/wgpu/core/Renderer.js @@ -0,0 +1,172 @@ +import { device, canvas, Vec3 } from '../wgpu.js' +import { Mat4 } from '../math/Mat4.js'; +import { UniformBuffer } from './Buffer.js'; + +export class Renderer { + + static sampleCount = 4; + + #context + #colorTexture + #depthTexture + + #objectBindGroups + #objectBindGroupsUsage + + #commandEncoder + #passEncoder + + constructor() { + let context = canvas.getContext('webgpu'); + const canvasConfig = { + device, + format: navigator.gpu.getPreferredCanvasFormat(), + usage: GPUTextureUsage.RENDER_ATTACHMENT, + alphaMode: 'opaque' + }; + context.configure(canvasConfig); + + let colorTexture = device.createTexture({ + size: [canvas.width, canvas.height], + format: 'bgra8unorm', + usage: GPUTextureUsage.RENDER_ATTACHMENT, + sampleCount: Renderer.sampleCount, + }); + + let depthTexture = device.createTexture({ + size: [canvas.width, canvas.height], + format: 'depth24plus', + usage: GPUTextureUsage.RENDER_ATTACHMENT, + sampleCount: Renderer.sampleCount, + }); + + this.#context = context; + this.#colorTexture = colorTexture; + this.#depthTexture = depthTexture; + this.#objectBindGroups = {}; + + this.FOV = 90 + this.FAR = 1000 + this.NEAR = .1 + } + + #allocateObjectBindGroup(shader) { + let perShaderBindGroups = this.#objectBindGroups[shader.id]; + if (!perShaderBindGroups) + perShaderBindGroups = []; + + let pipeline = shader.pipeline(); + + // holds a single 4x4 matrix + let uniformBuffer = new UniformBuffer(16); + let bindGroupDesc = { + label: shader.label, + layout: pipeline.getBindGroupLayout(1), + entries: [ + { + binding: 0, + resource: { + buffer: uniformBuffer.get(), + }, + } + ], + }; + let bindGroup = device.createBindGroup(bindGroupDesc); + + perShaderBindGroups.push([bindGroup, uniformBuffer]); + this.#objectBindGroups[shader.id] = perShaderBindGroups; + + return [bindGroup, uniformBuffer]; + } + + #getObjectBindGroup(shader) { + let usage = this.#objectBindGroupsUsage; + let perShaderUsage = usage[shader.id]; + + if (!perShaderUsage) + perShaderUsage = 0; + + let bindGroup; + let perShaderBindGroups = this.#objectBindGroups[shader.id]; + if (perShaderBindGroups && perShaderUsage < perShaderBindGroups.length) { + bindGroup = perShaderBindGroups[perShaderUsage]; + } else { + bindGroup = this.#allocateObjectBindGroup(shader); + } + + this.#objectBindGroupsUsage[shader.id] = perShaderUsage + 1; + + return bindGroup; + } + + beginFrame(clearColor) { + // get valid "sky" clear color + clearColor = clearColor ?? new Vec3(0.0, 0.0, 0.0); + + let colorAttachment = { + view: this.#colorTexture.createView(), + resolveTarget: this.#context.getCurrentTexture().createView(), + clearValue: { r: clearColor.x, g: clearColor.y, b: clearColor.z, a: 1 }, + loadOp: 'clear', + storeOp: 'store', + }; + + let depthAttachment = { + view: this.#depthTexture.createView(), + depthClearValue: 1.0, + depthLoadOp: 'clear', + depthStoreOp: 'store', + }; + + let renderPassDesc = { + colorAttachments: [colorAttachment], + depthStencilAttachment: depthAttachment, + }; + + this.#commandEncoder = device.createCommandEncoder(); + this.#passEncoder = this.#commandEncoder.beginRenderPass(renderPassDesc); + this.#passEncoder.setViewport(0, 0, canvas.width, canvas.height, 0, 1); + this.#objectBindGroupsUsage = {}; + } + + draw(camera, shader, objects = []) { + shader.bind(this.#passEncoder); + shader.load("proj", this.proj()); + shader.load("view", camera.view()); + shader.flush(); + for (let object of objects) { + if (!object.mesh) + continue; + + // write object transformation matrix + // to per object uniform + let [bindGroup, uniformBuffer] = this.#getObjectBindGroup(shader); + uniformBuffer.write(new Float32Array(object.tran().data)); + + this.#passEncoder.setBindGroup(1, bindGroup); + object.mesh.draw(this.#passEncoder); + + } + } + + endFrame() { + this.#passEncoder.end(); + device.queue.submit([this.#commandEncoder.finish()]); + } + + proj() { + const proj = new Mat4() + const d = proj.data + + const tanHalfFovy = Math.tan((this.FOV * (Math.PI/180)) / 2.0); + const aspect = canvas.width / canvas.height + + d[0] = 1.0 / (aspect * tanHalfFovy) + d[5] = 1.0 / tanHalfFovy + d[10] = this.FAR / (this.FAR - this.NEAR) + d[11] = 1.0 + d[14] = -(this.FAR * this.NEAR) / (this.FAR - this.NEAR) + + return proj + } +} diff --git a/js/wgpu/core/Shader.js b/js/wgpu/core/Shader.js new file mode 100644 index 0000000..dd87b17 --- /dev/null +++ b/js/wgpu/core/Shader.js @@ -0,0 +1,289 @@ +import { device, Renderer } from '../wgpu.js' +import { UniformBuffer } from './Buffer.js'; + +export class Shader { + + static #id = 0 + + #pipeline + #bindGroup + + #attributes + #uniformBuffer + #uniforms + #dirty + + constructor(code, opts = {}) { + // set the shader id + this.id = Shader.#id; + Shader.#id++; + + // read opts + let label = opts.label ?? ''; + let vertex_args = opts.vertex ?? ["vec3"]; + let texture_args = opts.texture ?? []; + let custom_uniform_args = opts.uniforms ?? []; + + // set label + this.label = label; + + // parse uniforms + let attributes = {} + let uniformSize = 0; + let forced_uniform_args = [ + { name: "proj", type: "mat4" }, + { name: "view", type: "mat4" }, + ]; + let uniform_args = forced_uniform_args.concat(custom_uniform_args); + uniform_args.forEach((arg) => { + let name = arg.name; + let type = arg.type; + let size = 0; + let alignment = 0; + switch (type) { + case "mat4": + size = 16; + alignment = 4; + break; + case "vec3": + size = 3; + alignment = 4; + break; + case "float": + size = 1; + alignment = 1; + break; + case "bool": + size = 1; + alignment = 1; + break; + }; + + if (size > 0) { + let idx = Math.floor((uniformSize + alignment - 1) / alignment) * alignment; + attributes[name] = { idx, type }; + uniformSize = idx + size; + } + }); + + // parse and load shader code + let shaderDesc = { code }; + let shaderModule = device.createShaderModule(shaderDesc); + + // create bind group layout + let bindGroupLayoutAttribs = [ + // default bind for uniforms + { + binding: 0, + visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, + buffer: { type: "uniform" }, + }, + // default bind for trans/model matrix + ]; + // add texture binds + for (let arg of texture_args) { + if (arg.sampler) { + bindGroupLayoutAttribs.push({ + binding: arg.sampler.bind, + visibility: GPUShaderStage.FRAGMENT, + sampler: { + type: "filtering", + }, + }); + } else if (arg.texture) { + bindGroupLayoutAttribs.push({ + binding: arg.texture.bind, + visibility: GPUShaderStage.FRAGMENT, + texture: { + sampleType: "float", + viewDimension: "2d-array", + multisampled: false, + }, + }); + + } + } + let bindGroupLayoutDesc = { + entries: bindGroupLayoutAttribs, + }; + let bindGroupLayout = device.createBindGroupLayout(bindGroupLayoutDesc); + + let bindGroupLayoutDesc2 = { + entries: [ + { + binding: 0, + visibility: GPUShaderStage.VERTEX, + buffer: { type: "uniform" }, + } + ] + }; + let bindGroupLayout2 = device.createBindGroupLayout(bindGroupLayoutDesc2); + + // create pipeline layout + let pipelineLayoutDesc = { + bindGroupLayouts: [bindGroupLayout, bindGroupLayout2], + }; + let layout = device.createPipelineLayout(pipelineLayoutDesc); + + // create vertex attributes + let vertexAttributes = []; + for (let idx in vertex_args) { + let arg = vertex_args[idx]; + let format; + let stride; + switch (arg) { + case "vec3": + stride = 3; + format = "float32x3"; + break; + case "vec2": + stride = 2; + format = "float32x2"; + break; + case "float": + stride = 1; + format = "float32"; + break; + } + vertexAttributes.push({ + arrayStride: Float32Array.BYTES_PER_ELEMENT * stride, + attributes: [ + { shaderLocation: idx, offset: 0, format }, + ], + }); + } + + // create pipeline + let colorState = { + format: 'bgra8unorm' + }; + let pipelineDesc = { + label, + layout, + vertex: { + module: shaderModule, + entryPoint: 'vs_main', + buffers: vertexAttributes, + }, + fragment: { + module: shaderModule, + entryPoint: 'fs_main', + targets: [colorState] + }, + depthStencil: { + depthWriteEnabled: true, + depthCompare: 'less', + format: 'depth24plus', + }, + primitive: { + topology: 'triangle-list', + frontFace: 'cw', + cullMode: 'back' + }, + multisample: { + count: Renderer.sampleCount, + }, + }; + let pipeline = device.createRenderPipeline(pipelineDesc); + + // create uniform buffer + let uniformBuffer = new UniformBuffer(uniformSize); + + // create bind group + let bindGroupAttribs = [ + // default bind for uniforms + { + binding: 0, + resource: { + buffer: uniformBuffer.get(), + }, + } + ]; + for (let arg of texture_args) { + if (arg.sampler) { + bindGroupAttribs.push({ + binding: arg.sampler.bind, + resource: device.createSampler(), + }); + } else if (arg.texture) { + bindGroupAttribs.push({ + binding: arg.texture.bind, + resource: arg.texture.resource.view(), + }); + } + } + let uniformBindGroupDesc = { + label, + layout: pipeline.getBindGroupLayout(0), + entries: bindGroupAttribs, + }; + let uniformBindGroup = device.createBindGroup(uniformBindGroupDesc); + + this.#pipeline = pipeline; + this.#bindGroup = uniformBindGroup; + + this.#attributes = attributes; + this.#uniformBuffer = uniformBuffer; + this.#uniforms = new Float32Array(uniformSize); + this.#dirty = false; + } + + load(name, value) { + let attribute = this.#attributes[name]; + if (!attribute) { + return; + } + let idx = attribute.idx; + switch (attribute.type) { + case "float": + this.#loadFloat(idx, value); + break; + case "bool": + this.#loadBool(idx, value); + break; + case "vec3": + this.#loadVec3(idx, value); + break; + case "mat4": + this.#loadMat4(idx, value); + break; + } + this.#dirty = true; + } + + #loadFloat(idx, f) { + this.#uniforms[idx] = f; + } + + #loadBool(idx, b) { + this.#uniforms[idx] = b ? 1 : 0; + } + + #loadVec3(idx, v) { + this.#uniforms[idx + 0] = v.x; + this.#uniforms[idx + 1] = v.y; + this.#uniforms[idx + 2] = v.z; + } + + #loadMat4(idx, m) { + for (let i = 0; i < m.data.length; i++) { + this.#uniforms[idx + i] = m.data[i]; + } + } + + bind(pass) { + pass.setPipeline(this.#pipeline); + pass.setBindGroup(0, this.#bindGroup); + } + + flush() { + if (this.#dirty) { + this.#uniformBuffer.write(this.#uniforms); + this.#dirty = false; + } + } + + pipeline() { + return this.#pipeline; + } +} diff --git a/js/wgpu/core/Texture.js b/js/wgpu/core/Texture.js new file mode 100644 index 0000000..311aead --- /dev/null +++ b/js/wgpu/core/Texture.js @@ -0,0 +1,51 @@ +import { device } from "../wgpu.js" + +export class Texture { + + #texture + + constructor(width, height, depth = 1) { + this.width = width; + this.height = height; + + let mips = 1; + + let texture = device.createTexture({ + size: [width, height, depth], + format: 'rgba8unorm', + usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST, + mipLevelCount: mips, + dimension: '2d', + }); + + this.#texture = texture; + this.depth = depth; + this.mips = mips; + } + + store(data, layer = 0, depth = 1) { + let texture = this.#texture; + let width = this.width; + let height = this.height; + + device.queue.writeTexture( + { texture, origin: { x: 0, y: 0, z: layer } }, + data, + { bytesPerRow: width * 4, rowsPerImage: height }, + { width, height, depthOrArrayLayers: depth }, + ); + } + + view() { + return this.#texture.createView({ + dimension: '2d-array', + baseArrayLayer: 0, + arrayLayerCount: this.depth, + mipLevelCount: this.mips, + }); + } + + get() { + return this.#texture; + } +} diff --git a/js/wgpu/io/File.js b/js/wgpu/io/File.js new file mode 100644 index 0000000..44338a3 --- /dev/null +++ b/js/wgpu/io/File.js @@ -0,0 +1,11 @@ +export const File = {} + +File.readFileAsync = async (path, type = "text") => { + try { + let data = await fetch(path, { cache: 'no-store' }); + let res = await data[type](); + return res; + } catch (err) { + return undefined + } +} diff --git a/js/wgpu/io/Input.js b/js/wgpu/io/Input.js new file mode 100644 index 0000000..0f177e8 --- /dev/null +++ b/js/wgpu/io/Input.js @@ -0,0 +1,62 @@ +import { canvas } from "../wgpu.js"; + +export const Input = {} + +const keys = {}; +let dx = 0; +let dy = 0; + +let leftClick = false; +let rightClick = false; + +Input.setup = () => { + document.onkeydown = function(e) { + keys[e.key.toLowerCase()] = true + + }; + document.onkeyup = function(e) { + keys[e.key.toLowerCase()] = false + }; + document.onmousemove = function(e) { + if (document.pointerLockElement !== canvas) + return; + dx += e.movementX / canvas.width; + dy += e.movementY / canvas.height; + }; + document.onmousedown = function(e) { + if (document.pointerLockElement !== canvas) + return; + if (e.buttons == 1) + leftClick = true; + if (e.buttons == 2) + rightClick = true; + } + canvas.onclick = () => { + canvas.requestPointerLock(); + }; +} + +Input.isKeyDown = (key) => { + return keys[key.toLowerCase()] +} + +Input.getMouseMovement = () => { + let res = { dx, dy }; + dx = 0; + dy = 0; + return res; +} + +Input.getLeftClick = () => { + if (!leftClick) + return false; + leftClick = false; + return true; +}; + +Input.getRightClick = () => { + if (!rightClick) + return false; + rightClick = false; + return true; +}; diff --git a/js/wgpu/math/Mat4.js b/js/wgpu/math/Mat4.js new file mode 100644 index 0000000..d4846f1 --- /dev/null +++ b/js/wgpu/math/Mat4.js @@ -0,0 +1,126 @@ +export class Mat4 { + + constructor(data) { + this.data = data ?? [ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 + ] + } + + mulV(v) { + let d = this.data; + return new Vec4( + new Vec4(d[0], d[1], d[2], d[3]).dot(v), + new Vec4(d[4], d[5], d[6], d[7]).dot(v), + new Vec4(d[8], d[9], d[10], d[11]).dot(v), + new Vec4(d[12], d[13], d[14], d[15]).dot(v), + ) + } + + determinant() { + // taken from gl-matrix + + let a = this.data; + let a00 = a[0], + a01 = a[1], + a02 = a[2], + a03 = a[3]; + let a10 = a[4], + a11 = a[5], + a12 = a[6], + a13 = a[7]; + let a20 = a[8], + a21 = a[9], + a22 = a[10], + a23 = a[11]; + let a30 = a[12], + a31 = a[13], + a32 = a[14], + a33 = a[15]; + let b0 = a00 * a11 - a01 * a10; + let b1 = a00 * a12 - a02 * a10; + let b2 = a01 * a12 - a02 * a11; + let b3 = a20 * a31 - a21 * a30; + let b4 = a20 * a32 - a22 * a30; + let b5 = a21 * a32 - a22 * a31; + let b6 = a00 * b5 - a01 * b4 + a02 * b3; + let b7 = a10 * b5 - a11 * b4 + a12 * b3; + let b8 = a20 * b2 - a21 * b1 + a22 * b0; + let b9 = a30 * b2 - a31 * b1 + a32 * b0; + + // Calculate the determinant + return a13 * b6 - a03 * b7 + a33 * b8 - a23 * b9; + } + + invert() { + // taken from gl-matrix + + let a = this.data; + let a00 = a[0], + a01 = a[1], + a02 = a[2], + a03 = a[3]; + let a10 = a[4], + a11 = a[5], + a12 = a[6], + a13 = a[7]; + let a20 = a[8], + a21 = a[9], + a22 = a[10], + a23 = a[11]; + let a30 = a[12], + a31 = a[13], + a32 = a[14], + a33 = a[15]; + let b00 = a00 * a11 - a01 * a10; + let b01 = a00 * a12 - a02 * a10; + let b02 = a00 * a13 - a03 * a10; + let b03 = a01 * a12 - a02 * a11; + let b04 = a01 * a13 - a03 * a11; + let b05 = a02 * a13 - a03 * a12; + let b06 = a20 * a31 - a21 * a30; + let b07 = a20 * a32 - a22 * a30; + let b08 = a20 * a33 - a23 * a30; + let b09 = a21 * a32 - a22 * a31; + let b10 = a21 * a33 - a23 * a31; + let b11 = a22 * a33 - a23 * a32; + + let det = this.determinant(); + if (!det) + return null; + + det = 1.0 / det; + + let out = []; + out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; + out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; + out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; + out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; + out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; + out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; + out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; + out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; + out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; + out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; + out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; + out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; + out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; + out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; + out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; + out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; + + return new Mat4(out); + } + + static identity() { + return new Mat4([ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ]); + } + +} diff --git a/js/wgpu/math/Math.js b/js/wgpu/math/Math.js new file mode 100644 index 0000000..45e5bf8 --- /dev/null +++ b/js/wgpu/math/Math.js @@ -0,0 +1,3 @@ +export const M = {} + +M.clamp = (num, min, max) => Math.min(Math.max(num, min), max) diff --git a/js/wgpu/math/Vec2.js b/js/wgpu/math/Vec2.js new file mode 100644 index 0000000..9c7f68b --- /dev/null +++ b/js/wgpu/math/Vec2.js @@ -0,0 +1,103 @@ +export class Vec2 { + + constructor(x = 0, y = 0) { + this.x = x + this.y = y + } + + setAxis(axis, value) { + switch (axis) { + case 0: + this.x = value; + break; + case 1: + this.y = value; + break; + }; + } + + getAxis(axis) { + switch (axis) { + case 0: + return this.x; + case 1: + return this.y; + }; + } + + add(n) { + return new Vec2( + this.x + n, + this.y + n, + ); + } + + addV(v) { + return new Vec2( + this.x + v.x, + this.y + v.y, + ); + } + + sub(n) { + return new Vec2( + this.x - n, + this.y - n, + ); + } + + subV(v) { + return new Vec2( + this.x - v.x, + this.y - v.y, + ); + } + + mul(s) { + return new Vec2( + this.x * s, + this.y * s, + ); + } + + mulV(v) { + return new Vec2( + this.x * v.x, + this.y * v.y, + ); + } + + div(s) { + return new Vec2( + this.x / s, + this.y / s, + ); + } + + divV(v) { + return new Vec2( + this.x / v.x, + this.y / v.y, + ); + } + + invert() { + return new Vec2( + 1 / this.x, + 1 / this.y, + ); + } + + normalize() { + return this.div(this.length() || 1); + } + + dot(v) { + return this.x * v.x + this.y * v.y; + } + + length() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + +} diff --git a/js/wgpu/math/Vec3.js b/js/wgpu/math/Vec3.js new file mode 100644 index 0000000..f084fee --- /dev/null +++ b/js/wgpu/math/Vec3.js @@ -0,0 +1,156 @@ +export class Vec3 { + + constructor(x = 0, y = 0, z = 0) { + this.x = x + this.y = y + this.z = z + } + + setAxis(axis, value) { + switch (axis) { + case 0: + this.x = value; + break; + case 1: + this.y = value; + break; + case 2: + this.z = value; + break; + }; + } + + getAxis(axis) { + switch (axis) { + case 0: + return this.x; + case 1: + return this.y; + case 2: + return this.z; + }; + } + + add(n) { + return new Vec3( + this.x + n, + this.y + n, + this.z + n, + ); + } + + addV(v) { + return new Vec3( + this.x + v.x, + this.y + v.y, + this.z + v.z, + ); + } + + sub(n) { + return new Vec3( + this.x - n, + this.y - n, + this.z - n, + ); + } + + subV(v) { + return new Vec3( + this.x - v.x, + this.y - v.y, + this.z - v.z, + ); + } + + mul(s) { + return new Vec3( + this.x * s, + this.y * s, + this.z * s, + ); + } + + mulV(v) { + return new Vec3( + this.x * v.x, + this.y * v.y, + this.z * v.z, + ); + } + + div(s) { + return new Vec3( + this.x / s, + this.y / s, + this.z / s, + ); + } + + divV(v) { + return new Vec3( + this.x / v.x, + this.y / v.y, + this.z / v.z, + ); + } + + invert() { + return new Vec3( + 1 / this.x, + 1 / this.y, + 1 / this.z, + ); + } + + normalize() { + return this.div(this.length() || 1); + } + + dot(v) { + return this.x * v.x + this.y * v.y + this.z * v.z; + } + + length() { + return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + } + + floor() { + return new Vec3( + Math.floor(this.x), + Math.floor(this.y), + Math.floor(this.z), + ) + } + + ciel() { + return new Vec3( + Math.ceil(this.x), + Math.ceil(this.y), + Math.ceil(this.z), + ) + } + + trunc() { + return new Vec3( + Math.trunc(this.x), + Math.trunc(this.y), + Math.trunc(this.z), + ) + } + + and(n) { + return new Vec3( + this.x & n, + this.y & n, + this.z & n, + ); + } + + eq(v) { + return v && + this.x == v.x && + this.y == v.y && + this.z == v.z + } +} diff --git a/js/wgpu/math/Vec4.js b/js/wgpu/math/Vec4.js new file mode 100644 index 0000000..ea3ded2 --- /dev/null +++ b/js/wgpu/math/Vec4.js @@ -0,0 +1,132 @@ +export class Vec4 { + + constructor(x = 0, y = 0, z = 0, w = 0) { + this.x = x + this.y = y + this.z = z + this.w = w + } + + setAxis(axis, value) { + switch (axis) { + case 0: + this.x = value; + break; + case 1: + this.y = value; + break; + case 2: + this.z = value; + break; + case 3: + this.w = value; + break; + }; + } + + getAxis(axis) { + switch (axis) { + case 0: + return this.x; + case 1: + return this.y; + case 2: + return this.z; + case 3: + return this.w; + }; + } + + add(n) { + return new Vec4( + this.x + n, + this.y + n, + this.z + n, + this.w + n, + ); + } + + addV(v) { + return new Vec4( + this.x + v.x, + this.y + v.y, + this.z + v.z, + this.w + v.w, + ); + } + + sub(n) { + return new Vec4( + this.x - n, + this.y - n, + this.z - n, + this.w - n, + ); + } + + subV(v) { + return new Vec4( + this.x - v.x, + this.y - v.y, + this.z - v.z, + this.w - v.w, + ); + } + + mul(s) { + return new Vec4( + this.x * s, + this.y * s, + this.z * s, + this.w * s, + ); + } + + mulV(v) { + return new Vec4( + this.x * v.x, + this.y * v.y, + this.z * v.z, + this.w * v.w, + ); + } + + div(s) { + return new Vec4( + this.x / s, + this.y / s, + this.z / s, + this.w / s, + ); + } + + divV(v) { + return new Vec4( + this.x / v.x, + this.y / v.y, + this.z / v.z, + this.w / v.w, + ); + } + + invert() { + return new Vec4( + 1 / this.x, + 1 / this.y, + 1 / this.z, + 1 / this.w, + ); + } + + normalize() { + return this.div(this.length() || 1); + } + + dot(v) { + return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; + } + + length() { + return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w); + } +} diff --git a/js/wgpu/wgpu.js b/js/wgpu/wgpu.js new file mode 100644 index 0000000..85dcf41 --- /dev/null +++ b/js/wgpu/wgpu.js @@ -0,0 +1,84 @@ +export { Mesh } from './core/Mesh.js' +export { Object } from './core/Object.js' +export { Renderer } from './core/Renderer.js' +export { Shader } from './core/Shader.js' +export { Texture } from './core/Texture.js' +export { Mat4 } from './math/Mat4.js' +export { Vec2 } from './math/Vec2.js' +export { Vec3 } from './math/Vec3.js' +export { Vec4 } from './math/Vec4.js' +export { M } from './math/Math.js' +export { File } from './io/File.js' +export { Input } from './io/Input.js' + +export { Run, Init } + +import { Input } from './io/Input.js' + +export let adapter = null; +export let device = null; +export let canvas = null; + +async function Init(callback) { + // Wgpu is already loaded + if (adapter && device && canvas) { + callback(); + return; + } + + // Check to see if WebGPU can run + if (!navigator.gpu) { + console.error("WebGPU not supported on this browser."); + return; + } + + // get webgpu browser software layer for graphics device + adapter = await navigator.gpu.requestAdapter(); + if (!adapter) { + console.error("No appropriate GPUAdapter found."); + return; + } + + // get the instantiation of webgpu on this device + device = await adapter.requestDevice(); + if (!device) { + console.error("Failed to request Device."); + return; + } + + canvas = document.createElement("canvas"); + canvas.id = "canvas"; + canvas.width = window.screen.width; + canvas.height = window.screen.height; + canvas.style = "display: block;"; + document.body.appendChild(canvas); + document.body.style.margin = 0; + + let context = canvas.getContext('webgpu'); + const canvasConfig = { + device, + format: navigator.gpu.getPreferredCanvasFormat(), + usage: GPUTextureUsage.RENDER_ATTACHMENT, + alphaMode: 'opaque' + }; + context.configure(canvasConfig); + + callback(); +} + +var DT = 0; +var last = Date.now() +async function Run(fn) { + // setup keyboard input + Input.setup() + + // create and run main game loop + const callback = () => { + var now = Date.now(); + DT = ( now - last) / 1000 + last = now + fn(DT) + window.requestAnimationFrame(callback) + }; + callback() +} |