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: this.sliderArray.byteLength, 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 | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }] }), }; const pipelineLayout = device.createPipelineLayout({ label: `${label} pipeline layout`, bindGroupLayouts: [ this.bindGroupLayout.view, this.bindGroupLayout.perFont, this.bindGroupLayout.perFrame, ], }); console.log(canvasFormat); this.renderPipeline = device.createRenderPipeline({ label: `${label} pipeline`, layout: pipelineLayout, vertex: { module: shaderModule, entryPoint: "vertexMain", }, fragment: { module: shaderModule, entryPoint: "fragmentMain", targets: [{ blend: { color: { dstFactor: "one-minus-src-alpha", srcFactor: "src-alpha", }, alpha: { dstFactor: "one-minus-src-alpha", srcFactor: "src-alpha", }, operation: "add", }, format: canvasFormat, }] }, primitive: { topology: "triangle-list", }, depthStencil: { depthWriteEnabled: true, depthCompare: "always", format: "depth24plus", }, }); ////////////////////////////////////////////////////////////////////// // sliders ////////////////////////////////////////////////////////////////////// this.sliders = makeSliders(); this.sliderArray = new Float32Array(4 * 4); ////////////////////////////////////////////////////////////////////// // buffers ////////////////////////////////////////////////////////////////////// this.createBuffers(device); ////////////////////////////////////////////////////////////////////// // bind group ////////////////////////////////////////////////////////////////////// this.viewBindGroup = device.createBindGroup({ label: `${label} bind group`, layout: this.bindGroupLayout.view, entries: [{ binding: 0, resource: { buffer: viewUniformBuffer }, }], }); } 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; this.sliderArray[4] = configuration.distanceThreshold; this.sliderArray[5] = configuration.distanceScale; this.sliderArray[6] = configuration.distanceOffset; this.sliderArray[8] = configuration.outlineThreshold; this.sliderArray[9] = configuration.outlineScale; this.sliderArray[10] = configuration.outlineOffset; this.sliderArray[12] = configuration.colorMix; this.sliderArray[13] = configuration.colorScale; this.sliderArray[14] = configuration.colorOffset; 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); for (let i = 0; i < this.instanceCount; i++) { renderPass.drawIndexed(6, 1, 0, 0, i); } } } 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 };