Spaces:
Running
Running
File size: 6,233 Bytes
c36501e 6d53e86 1889a23 d13e19f 6d53e86 d13e19f 6d53e86 d13e19f 6d53e86 d13e19f 6d53e86 d13e19f 6d53e86 551d60d 6d53e86 551d60d d13e19f 6d53e86 d13e19f 6d53e86 551d60d 6d53e86 551d60d 6d53e86 d13e19f 6d53e86 d13e19f 6d53e86 d13e19f 6d53e86 d13e19f 6d53e86 551d60d 6d53e86 d13e19f 6d53e86 d13e19f 6d53e86 d13e19f 6d53e86 d13e19f 6d53e86 551d60d 6d53e86 551d60d 6d53e86 551d60d 6d53e86 e31d2a8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
// 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();
|