Spaces:
Running
Running
File size: 5,303 Bytes
6d53e86 46ec188 bc152ba 46ec188 d13e19f 46ec188 0741357 bc152ba 6d53e86 d13e19f 6d53e86 cf65f18 538ade1 6d53e86 d13e19f 538ade1 d13e19f 6d53e86 538ade1 551d60d 6d53e86 538ade1 d13e19f 6d53e86 d13e19f 6d53e86 551d60d 6d53e86 538ade1 6d53e86 538ade1 6d53e86 538ade1 0f567ca d13e19f 6d53e86 d13e19f 6d53e86 538ade1 6d53e86 d13e19f 6d53e86 551d60d 538ade1 6d53e86 d13e19f 6d53e86 d13e19f 6d53e86 d13e19f 6d53e86 d13e19f ff401af 9eb5291 538ade1 ff401af 538ade1 abb941d 538ade1 9eb5291 538ade1 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 |
import { mat4 } from 'https://webgpufundamentals.org/3rdparty/wgpu-matrix.module.js';
import { initializeWebGPU } from './wgpu-device.js';
import { createState } from './wgpu-state.js';
import { generateGlyphTextureAtlas, createTextureFromSource } from './wgpu-utility.js';
import { createPipeline } from './wgpu-pipeline.js';
import { fetchShaderCode } from './wgpu-shader.js';
import { generateGlyphVerticesForText } from './wgpu-text.js';
import { config } from './wgpu-config.js';
import { CANVAS, CTX, COLORS, RENDER_PASS_DESCRIPTOR } from './wgpu-constants.js';
const canvas = document.querySelector('canvas');
const state = createState(config);
async function main() {
const adapter = await navigator.gpu?.requestAdapter();
const { device, context, presentationFormat } = await initializeWebGPU(navigator, adapter, canvas);
if (!device) return;
state.device = device;
// Load shader code
const shaderCode = await fetchShaderCode('shaders.wgsl');
const vertexSize = config.floatsPerVertex * 4;
// Create pipeline
state.pipeline = await createPipeline(state.device, presentationFormat, vertexSize, shaderCode);
// Generate and display glyph texture atlas
const glyphCanvas = generateGlyphTextureAtlas(CANVAS, CTX, config);
document.body.appendChild(glyphCanvas);
glyphCanvas.style.backgroundColor = '#222';
// Create vertex and index buffers
createBuffers();
// Generate vertex buffer data and texture
generateVertexDataAndTexture(glyphCanvas);
// Start the game loop
gameLoop(context);
}
function createBuffers() {
const vertexBufferSize = config.maxGlyphs * config.vertsPerGlyph * config.floatsPerVertex * 4;
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 = generateIndices(config.maxGlyphs);
state.device.queue.writeBuffer(state.indexBuffer, 0, new Uint32Array(indices));
}
function generateIndices(maxGlyphs) {
return Array.from({ length: 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];
});
}
function generateVertexDataAndTexture(glyphCanvas) {
const { vertexData, numGlyphs, width, height } = generateGlyphVerticesForText('Hello\nworld!\nText in\nWebGPU!', COLORS, config, 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;
}
function gameLoop(context) {
let lastTime = performance.now();
const frameInterval = 1000 / config.maxFPS;
function tick() {
const currentTime = performance.now();
const deltaTime = (currentTime - lastTime) / 1000;
if (currentTime - lastTime >= frameInterval) {
update(deltaTime, context);
lastTime = currentTime;
}
setTimeout(tick, 0);
}
tick();
}
function update(deltaTime, context) {
state.time += deltaTime;
// Set up projection and view matrices
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, state.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()]);
}
main();
|