webgpu-app/font.js
2026-08-05 13:21:13 -05:00

266 lines
8.5 KiB
JavaScript

import { getPath } from "./common.js";
class FontRenderer {
createBuffers(device)
{
this.frames = [];
//////////////////////////////////////////////////////////////////////
// glyph
//////////////////////////////////////////////////////////////////////
const glyphBufferSize = this.module.instance.exports.font_layout__glyph_buffer_size(this.fontLayoutAddress);
const glyphBuffer = device.createBuffer({
label: `glyph buffer`,
size: glyphBufferSize,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
const glyphBufferAddress = this.module.instance.exports.font_layout__glyph_buffer_address(this.fontLayoutAddress);
device.queue.writeBuffer(glyphBuffer, 0,
this.module.memory.buffer, glyphBufferAddress, glyphBufferSize);
//////////////////////////////////////////////////////////////////////
// texture
//////////////////////////////////////////////////////////////////////
const textureWidth = this.module.instance.exports.font_layout__texture_width(this.fontLayoutAddress);
const textureHeight = this.module.instance.exports.font_layout__texture_height(this.fontLayoutAddress);
const textureAddress = this.module.instance.exports.font_layout__texture_address(this.fontLayoutAddress);
//console.log(textureWidth, textureHeight, textureAddress);
const texture = device.createTexture({
size: [textureWidth, textureHeight],
format: 'r8unorm',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
});
device.queue.writeTexture({ texture: texture },
this.module.memory.buffer,
{ offset: textureAddress, bytesPerRow: textureWidth },
{ width: textureWidth, height: textureHeight });
//////////////////////////////////////////////////////////////////////
// sampler
//////////////////////////////////////////////////////////////////////
const sampler = device.createSampler({
magFilter: "linear",
minFilter: "linear",
});
//////////////////////////////////////////////////////////////////////
// bind group
//////////////////////////////////////////////////////////////////////
this.perFontBindGroup = device.createBindGroup({
label: `font per-font bind group`,
layout: this.bindGroupLayout.perFont,
entries: [{
binding: 0,
resource: sampler,
}, {
binding: 1,
resource: texture,
}, {
binding: 2,
resource: { buffer: glyphBuffer },
}]
});
//////////////////////////////////////////////////////////////////////
// layout
//////////////////////////////////////////////////////////////////////
const layoutBufferSize = this.module.instance.exports.font_layout__layout_buffer_size(this.fontLayoutAddress);
for (let i = 0; i < 2; i++) {
const layoutBuffer = device.createBuffer({
label: `layout buffer ${i}`,
size: layoutBufferSize,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
const bindGroup = device.createBindGroup({
label: `font per-frame bind group ${i}`,
layout: this.bindGroupLayout.perFrame,
entries: [{
binding: 0,
resource: { buffer: layoutBuffer },
}]
});
this.frames.push({
layoutBuffer: layoutBuffer,
bindGroup: bindGroup,
});
}
}
constructor(device, canvasFormat, viewUniformBuffer, wgsl, module, fontBufferSrc)
{
const label = "font";
this.module = module;
//////////////////////////////////////////////////////////////////////
// font layout
//////////////////////////////////////////////////////////////////////
this.fontBufferAddress = this.module.instance.exports.mem_alloc(fontBufferSrc.byteLength);
const fontBufferDst = new Uint8Array(this.module.memory.buffer, this.fontBufferAddress, fontBufferSrc.byteLength);
fontBufferDst.set(new Uint8Array(fontBufferSrc));
this.maxGlyphs = 1024;
this.fontLayoutAddress = this.module.instance.exports.font_layout__create(this.fontBufferAddress, this.maxGlyphs);
//////////////////////////////////////////////////////////////////////
// shader module
//////////////////////////////////////////////////////////////////////
const shaderModule = device.createShaderModule({
label: `${label} shader`,
code: wgsl,
});
//////////////////////////////////////////////////////////////////////
// index buffer
//////////////////////////////////////////////////////////////////////
const array = new Uint16Array(6);
array[0] = 0;
array[1] = 1;
array[2] = 2;
array[3] = 0;
array[4] = 3;
array[5] = 1;
this.buffer = device.createBuffer({
label: `${label} renderer buffer`,
size: array.byteLength,
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(this.buffer, 0, array, 0, array.length);
//////////////////////////////////////////////////////////////////////
// pipeline
//////////////////////////////////////////////////////////////////////
this.bindGroupLayout = {
view: device.createBindGroupLayout({
label: `${label} bind group layout view`,
entries: [{
binding: 0,
visibility: GPUShaderStage.VERTEX,
buffer: { type: "uniform" }
}]
}),
perFont: device.createBindGroupLayout({
label: `${label} bind group per-font`,
entries: [
{ // sampler
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
sampler: {}
}, { // texture
binding: 1,
visibility: GPUShaderStage.FRAGMENT,
texture: {},
}, { // glyph buffer
binding: 2,
visibility: GPUShaderStage.FRAGMENT,
buffer: { type: "read-only-storage" }
}
]
}),
perFrame: device.createBindGroupLayout({
label: `${label} bind group per-frame`,
entries: [{ // layout buffer
binding: 0,
visibility: GPUShaderStage.VERTEX,
buffer: { type: "read-only-storage" }
}
/*, { // configuration
binding: 1,
visibility: GPUShaderStage.FRAGMENT,
buffer: { type: "uniform" }
}*/]
}),
};
const pipelineLayout = device.createPipelineLayout({
label: `${label} pipeline layout`,
bindGroupLayouts: [
this.bindGroupLayout.view,
this.bindGroupLayout.perFont,
this.bindGroupLayout.perFrame,
],
});
this.renderPipeline = device.createRenderPipeline({
label: `${label} pipeline`,
layout: pipelineLayout,
vertex: {
module: shaderModule,
entryPoint: "vertexMain",
},
fragment: {
module: shaderModule,
entryPoint: "fragmentMain",
targets: [{
format: canvasFormat,
}]
},
primitive: {
topology: "triangle-list",
},
depthStencil: {
depthWriteEnabled: true,
depthCompare: "less",
format: "depth24plus",
},
});
//////////////////////////////////////////////////////////////////////
// buffers
//////////////////////////////////////////////////////////////////////
this.createBuffers(device);
//////////////////////////////////////////////////////////////////////
// bind group
//////////////////////////////////////////////////////////////////////
this.viewBindGroup = device.createBindGroup({
label: `${label} bind group`,
layout: this.bindGroupLayout.view,
entries: [{
binding: 0,
resource: { buffer: viewUniformBuffer },
}],
});
}
render(renderPass, frameNumber)
{
renderPass.setPipeline(this.renderPipeline);
renderPass.setIndexBuffer(this.buffer, "uint16");
renderPass.setBindGroup(0, this.viewBindGroup);
renderPass.setBindGroup(1, this.perFontBindGroup);
renderPass.setBindGroup(2, this.frames[frameNumber].bindGroup);
renderPass.drawIndexed(6);
}
}
async function loadFont(device, canvasFormat, viewUniformBuffer, module)
{
const fontWgsl = await getPath("font.wgsl");
const liberationResponse = await fetch("liberation.data");
const liberationBuffer = await liberationResponse.arrayBuffer();
const renderer = new FontRenderer(device, canvasFormat, viewUniformBuffer, fontWgsl, module, liberationBuffer);
return renderer;
}
export { loadFont };