Spaces:
Running
Running
File size: 4,993 Bytes
6d53e86 1889a23 d13e19f 0741357 6d53e86 d13e19f 6d53e86 cf65f18 6d53e86 cf65f18 6d53e86 cf65f18 6d53e86 cf65f18 6d53e86 cf65f18 6d53e86 d13e19f 6d53e86 551d60d 6d53e86 551d60d d13e19f 6d53e86 d13e19f 6d53e86 551d60d 6d53e86 551d60d 6d53e86 d13e19f 6d53e86 0f567ca d13e19f 6d53e86 d13e19f 6d53e86 d13e19f 6d53e86 551d60d 6d53e86 d13e19f 6d53e86 d13e19f 6d53e86 d13e19f 6d53e86 d13e19f 0f567ca d13e19f 0f567ca d13e19f 0f567ca 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 |
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';
import { generateGlyphVerticesForText } from './wgpu-text.js';
const canvas = document.querySelector('canvas');
const state = createState(config);
async function initializeWebGPU(navigator, adapter, canvas) {
const context = canvas.getContext('webgpu');
const device = await adapter?.requestDevice();
if (!device) {
alert('need a browser that supports WebGPU');
return { device: null, context: null, presentationFormat: null };
}
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
device,
format: presentationFormat,
});
return { device, context, presentationFormat };
}
async function main() {
const adapter = await navigator.gpu?.requestAdapter();
const { device, context, presentationFormat } = await initializeWebGPU(navigator, adapter, canvas);
if (!device) return;
state.device = device;
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, 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;
requestAnimationFrame((time) => render(time, mat4, context, state, RENDER_PASS_DESCRIPTOR));
}
function render(time, mat4, 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, mat4, context, state, RENDER_PASS_DESCRIPTOR));
}
main();
|