plasma-arc / index.js
p3nGu1nZz's picture
create devices & shaders
c36501e
raw
history blame
6.23 kB
// index.js
import { mat4 } from 'https://webgpufundamentals.org/3rdparty/wgpu-matrix.module.js';
import { fetchShaderCode, generateGlyphTextureAtlas, createTextureFromSource } from './wgpu-utility.js';
import { config } from './wgpu-config.js';
import { CANVAS, CTX, COLORS, RENDER_PASS_DESCRIPTOR } from './wgpu-constants.js';
import { createPipeline } from './wgpu-pipeline.js';
import { createState } from './wgpu-state.js';
const canvas = document.querySelector('canvas');
const context = canvas.getContext('webgpu');
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
const state = createState(config);
async function main() {
const adapter = await navigator.gpu?.requestAdapter();
state.device = await adapter?.requestDevice();
if (!state.device) {
alert('need a browser that supports WebGPU');
return;
}
context.configure({
device: state.device,
format: presentationFormat,
});
const shaderCode = await fetchShaderCode('shaders.wgsl');
const vertexSize = config.floatsPerVertex * 4;
state.pipeline = await createPipeline(state.device, presentationFormat, vertexSize, shaderCode);
const glyphCanvas = generateGlyphTextureAtlas(CANVAS, CTX, config);
document.body.appendChild(glyphCanvas);
glyphCanvas.style.backgroundColor = '#222';
const vertexBufferSize = config.maxGlyphs * config.vertsPerGlyph * vertexSize;
state.vertexBuffer = state.device.createBuffer({
label: 'vertices',
size: vertexBufferSize,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
state.indexBuffer = state.device.createBuffer({
label: 'indices',
size: config.maxGlyphs * config.vertsPerGlyph * 4,
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
});
const indices = Array.from({ length: config.maxGlyphs * 6 }, (_, i) => {
const ndx = Math.floor(i / 6) * 4;
return (i % 6 < 3 ? [ndx, ndx + 1, ndx + 2] : [ndx + 2, ndx + 1, ndx + 3])[i % 3];
});
state.device.queue.writeBuffer(state.indexBuffer, 0, new Uint32Array(indices));
const { vertexData, numGlyphs, width, height } = generateGlyphVerticesForText('Hello\nworld!\nText in\nWebGPU!', COLORS, glyphCanvas);
state.device.queue.writeBuffer(state.vertexBuffer, 0, vertexData);
state.texture = createTextureFromSource(state.device, glyphCanvas, { mips: true });
state.sampler = state.device.createSampler({
minFilter: 'linear',
magFilter: 'linear'
});
state.uniformBuffer = state.device.createBuffer({
label: 'uniforms for quad',
size: config.uniformBufferSize,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
});
state.matrix = state.uniformValues.subarray(0, 16);
state.bindGroup = state.device.createBindGroup({
layout: state.pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: state.sampler },
{ binding: 1, resource: state.texture.createView() },
{ binding: 2, resource: { buffer: state.uniformBuffer } },
],
});
state.numGlyphs = numGlyphs;
state.width = width;
state.height = height;
requestAnimationFrame((time) => render(time, context, state, RENDER_PASS_DESCRIPTOR));
}
function render(time, context, state, RENDER_PASS_DESCRIPTOR) {
time *= config.time.phase;
const fov = 60 * Math.PI / 180;
const aspect = canvas.clientWidth / canvas.clientHeight;
const projectionMatrix = mat4.perspective(fov, aspect, config.render.zNear, config.render.zFar);
const viewMatrix = mat4.lookAt([0, 0, 5], [0, 0, 0], [0, 1, 0]);
const viewProjectionMatrix = mat4.multiply(projectionMatrix, viewMatrix);
RENDER_PASS_DESCRIPTOR.colorAttachments[0].view = context.getCurrentTexture().createView();
const encoder = state.device.createCommandEncoder();
const pass = encoder.beginRenderPass(RENDER_PASS_DESCRIPTOR);
pass.setPipeline(state.pipeline);
mat4.rotateY(viewProjectionMatrix, time, state.matrix);
mat4.translate(state.matrix, [-state.width / 2, -state.height / 2, 0], state.matrix);
state.device.queue.writeBuffer(state.uniformBuffer, 0, state.uniformValues);
pass.setBindGroup(0, state.bindGroup);
pass.setVertexBuffer(0, state.vertexBuffer);
pass.setIndexBuffer(state.indexBuffer, 'uint32');
pass.drawIndexed(state.numGlyphs * 6);
pass.end();
state.device.queue.submit([encoder.finish()]);
requestAnimationFrame((t) => render(t, context, state, RENDER_PASS_DESCRIPTOR));
}
function generateGlyphVerticesForText(s, COLORS, glyphCanvas) {
const vertexData = new Float32Array(config.maxGlyphs * config.floatsPerVertex * config.vertsPerGlyph);
const glyphUVWidth = config.glyphWidth / glyphCanvas.width;
const glyphUVHeight = config.glyphHeight / glyphCanvas.height;
let offset = 0, x0 = 0, y0 = 0, x1 = 1, y1 = 1, width = 0;
let colorNdx = 0;
const addVertex = (x, y, u, v, color) => {
vertexData.set([x, y, u, v, ...color], offset);
offset += 8;
};
for (let i = 0; i < s.length; ++i) {
const c = s.charCodeAt(i);
if (c >= 33) {
const cIndex = c - 33;
const glyphX = cIndex % config.glyphsAcrossTexture;
const glyphY = Math.floor(cIndex / config.glyphsAcrossTexture);
const u0 = glyphX * config.glyphWidth / glyphCanvas.width;
const v1 = glyphY * config.glyphHeight / glyphCanvas.height;
const u1 = u0 + glyphUVWidth;
const v0 = v1 + glyphUVHeight;
width = Math.max(x1, width);
addVertex(x0, y0, u0, v0, COLORS[colorNdx]);
addVertex(x1, y0, u1, v0, COLORS[colorNdx]);
addVertex(x0, y1, u0, v1, COLORS[colorNdx]);
addVertex(x1, y1, u1, v1, COLORS[colorNdx]);
} else {
colorNdx = (colorNdx + 1) % COLORS.length;
if (c === 10) { // Newline
x0 = 0; x1 = 1; y0--; y1 = y0 + 1;
continue;
}
}
x0 += 0.55; x1 = x0 + 1;
}
return { vertexData, numGlyphs: offset / config.floatsPerVertex, width, height: y1 };
}
main();