311 lines
10 KiB
JavaScript
311 lines
10 KiB
JavaScript
import { getPath } from "./common.js";
|
|
import { makeSliders } from "./fontSliders.js";
|
|
import { updateSliders } from "./slider.js";
|
|
|
|
class FontRenderer {
|
|
createBuffers(device)
|
|
{
|
|
this.frames = [];
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// glyph
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
const glyphBufferSize = this.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.exports.font_layout__glyph_buffer_address(this.fontLayoutAddress);
|
|
device.queue.writeBuffer(glyphBuffer, 0,
|
|
this.module.memory.buffer, glyphBufferAddress, glyphBufferSize);
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// texture
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
const textureWidth = this.exports.font_layout__texture_width(this.fontLayoutAddress);
|
|
const textureHeight = this.exports.font_layout__texture_height(this.fontLayoutAddress);
|
|
const textureAddress = this.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 },
|
|
}]
|
|
});
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// per-frame (layout, configuration)
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
this.layoutBufferAddress = this.exports.font_layout__layout_buffer_address(this.fontLayoutAddress);
|
|
|
|
const layoutBufferSize = this.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 configurationBuffer = device.createBuffer({
|
|
label: `configuration buffer ${i}`,
|
|
size: 4 * 4,
|
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
});
|
|
|
|
const bindGroup = device.createBindGroup({
|
|
label: `font per-frame bind group ${i}`,
|
|
layout: this.bindGroupLayout.perFrame,
|
|
entries: [{
|
|
binding: 0,
|
|
resource: { buffer: layoutBuffer },
|
|
}, {
|
|
binding: 1,
|
|
resource: { buffer: configurationBuffer },
|
|
}]
|
|
});
|
|
|
|
this.frames.push({
|
|
layoutBuffer: layoutBuffer,
|
|
configurationBuffer: configurationBuffer,
|
|
bindGroup: bindGroup,
|
|
});
|
|
}
|
|
}
|
|
|
|
constructor(device, canvasFormat, viewUniformBuffer, wgsl, module, fontBufferSrc)
|
|
{
|
|
const label = "font";
|
|
|
|
this.module = module;
|
|
this.exports = module.instance.exports;
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// font layout
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
this.fontBufferAddress = this.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.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.VERTEX | 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.VERTEX,
|
|
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 },
|
|
}],
|
|
});
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// sliders
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
this.sliders = makeSliders();
|
|
this.sliderArray = new Float32Array(4);
|
|
}
|
|
|
|
update(device, frameNumber)
|
|
{
|
|
this.instanceCount = this.exports.font_layout__layout_buffer_index(this.fontLayoutAddress);
|
|
const layoutBufferSize = this.instanceCount * 4 * 4;
|
|
|
|
device.queue.writeBuffer(this.frames[frameNumber].layoutBuffer, 0,
|
|
this.module.memory.buffer, this.layoutBufferAddress, layoutBufferSize);
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// sliders
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
const configuration = updateSliders(this.sliders);
|
|
this.sliderArray[0] = configuration.scale;
|
|
this.sliderArray[1] = configuration.translateX;
|
|
this.sliderArray[2] = configuration.translateY;
|
|
device.queue.writeBuffer(this.frames[frameNumber].configurationBuffer, 0,
|
|
this.sliderArray);
|
|
}
|
|
|
|
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, this.instanceCount);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
module.instance.exports.font_layout__draw(renderer.fontLayoutAddress);
|
|
|
|
return renderer;
|
|
}
|
|
|
|
export { loadFont };
|