draw sdf glyph

This commit is contained in:
Zack Buhman 2026-08-05 13:19:11 -05:00
parent 1d76808f02
commit 71052ffb69
8 changed files with 186 additions and 33 deletions

2
.gitignore vendored
View File

@ -5,3 +5,5 @@ __pycache__
*.data
.gdb_history
*.elf
favicon.ico
tools/ttf_outline/ttf_outline

138
font.js
View File

@ -3,39 +3,115 @@ import { getPath } from "./common.js";
class FontRenderer {
createBuffers(device)
{
this.maxGlyphs = 1024;
this.glyphBufferSize = 4 * this.maxGlyphs;
this.frames = [];
for (let i = 0; i < 2; i++) {
//////////////////////////////////////////////////////////////////////
// glyph
//////////////////////////////////////////////////////////////////////
const glyphBufferSize = this.module.instance.exports.font_layout__glyph_buffer_size(this.fontLayoutAddress);
const glyphBuffer = device.createBuffer({
label: `glyph buffer ${i}`,
size: this.glyphBufferSize,
label: `glyph buffer`,
size: glyphBufferSize,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
const bindGroup = device.createBindGroup({
label: `font bind group ${i}`,
layout: this.bindGroupLayout.glyph,
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({
glyphBuffer: glyphBuffer,
layoutBuffer: layoutBuffer,
bindGroup: bindGroup,
});
}
}
constructor(device, canvasFormat, viewUniformBuffer, wgsl, module)
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
//////////////////////////////////////////////////////////////////////
@ -78,13 +154,32 @@ class FontRenderer {
buffer: { type: "uniform" }
}]
}),
glyph: device.createBindGroupLayout({
label: `${label} bind group layout glyph`,
entries: [{ // glyphs
perFont: device.createBindGroupLayout({
label: `${label} bind group per-font`,
entries: [
{ // sampler
binding: 0,
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
visibility: GPUShaderStage.FRAGMENT,
sampler: {}
}, { // texture
binding: 1,
visibility: GPUShaderStage.FRAGMENT,
texture: {},
}, { // glyph buffer
binding: 2,
visibility: GPUShaderStage.FRAGMENT,
buffer: { type: "read-only-storage" }
}/*, { // configuration
}
]
}),
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" }
@ -96,7 +191,8 @@ class FontRenderer {
label: `${label} pipeline layout`,
bindGroupLayouts: [
this.bindGroupLayout.view,
this.bindGroupLayout.glyph
this.bindGroupLayout.perFont,
this.bindGroupLayout.perFrame,
],
});
@ -149,7 +245,8 @@ class FontRenderer {
renderPass.setPipeline(this.renderPipeline);
renderPass.setIndexBuffer(this.buffer, "uint16");
renderPass.setBindGroup(0, this.viewBindGroup);
renderPass.setBindGroup(1, this.frames[frameNumber].bindGroup);
renderPass.setBindGroup(1, this.perFontBindGroup);
renderPass.setBindGroup(2, this.frames[frameNumber].bindGroup);
renderPass.drawIndexed(6);
}
}
@ -158,7 +255,10 @@ async function loadFont(device, canvasFormat, viewUniformBuffer, module)
{
const fontWgsl = await getPath("font.wgsl");
const renderer = new FontRenderer(device, canvasFormat, viewUniformBuffer, fontWgsl, module);
const liberationResponse = await fetch("liberation.data");
const liberationBuffer = await liberationResponse.arrayBuffer();
const renderer = new FontRenderer(device, canvasFormat, viewUniformBuffer, fontWgsl, module, liberationBuffer);
return renderer;
}

View File

@ -6,13 +6,22 @@ struct Configuration {
aspect: f32,
};
struct GlyphBitmap {
struct GlyphBuffer {
position: vec2f,
velocity: vec2f,
size: vec2f,
};
struct LayoutBuffer {
position: vec4f, // glyph_index w
};
@group(0) @binding(0) var<uniform> config: Configuration;
@group(1) @binding(0) var<storage> glyphs: array<GlyphBitmap>;
@group(1) @binding(0) var linearSampler: sampler;
@group(1) @binding(1) var fontTexture: texture_2d<f32>;
@group(1) @binding(2) var<storage> glyphBuffer: array<GlyphBuffer>;
@group(2) @binding(0) var<storage> layoutBuffer: array<LayoutBuffer>;
struct VertexInput {
@builtin(vertex_index) vertex_index: u32,
@ -35,11 +44,11 @@ fn vertexMain(input: VertexInput) -> VertexOutput
{
let texture = vertices[input.vertex_index];
let position = texture * 2.0 - 1.0;
let position = vec2f(texture.x, 1.0 - texture.y) * 2.0 - 1.0;
var output: VertexOutput;
output.position = vec4f(position, 0.0, 1.0);
output.texture = position * vec2f(config.aspect, 1);
output.position = vec4f(position * 0.5, 0.0, 1.0);
output.texture = texture;
return output;
}
@ -48,5 +57,12 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4f
{
var color = vec3f(input.texture.xy, 0);
return vec4f(color, 1);
let p = glyphBuffer[66].position;
let s = glyphBuffer[66].size;
let base = textureSample(fontTexture, linearSampler, input.texture * s + p);
let t = f32(base.x > 0.5);
return vec4f(t, t, t, 1);
}

View File

@ -47,11 +47,13 @@ LDFLAGS = \
-Wl,--export=font_layout__texture_address \
-Wl,--export=font_layout__glyph_buffer_size \
-Wl,--export=font_layout__glyph_buffer_address \
-Wl,--export=font_layout__texture_size \
-Wl,--export=font_layout__texture_width \
-Wl,--export=font_layout__texture_height \
-Wl,--export=font_layout__texture_address \
-Wl,--export=font_layout__layout_buffer_size \
-Wl,--export=font_layout__layout_buffer_address \
-Wl,--export=font_layout__layout_buffer_index \
-Wl,--export=font_layout__draw \
-Wl,--import-undefined \
-Wl,--print-map \
-Wl,--import-memory \

View File

@ -19,10 +19,14 @@ extern "C" {
return font_layout->glyph_buffer;
}
size_t font_layout__texture_size(FontLayout * font_layout)
size_t font_layout__texture_width(FontLayout * font_layout)
{
int const bytes_per_pixel = 1;
return font_layout->font->texture_width * font_layout->font->texture_width * bytes_per_pixel;
return font_layout->font->texture_width;
}
size_t font_layout__texture_height(FontLayout * font_layout)
{
return font_layout->font->texture_height;
}
void * font_layout__texture_address(FontLayout * font_layout)
@ -44,4 +48,9 @@ extern "C" {
{
return font_layout->layout_buffer_index;
}
void font_layout__draw(FontLayout * font_layout)
{
font_layout->draw_string("@");
}
};

View File

@ -3,7 +3,7 @@
static inline size_t texture_offset(font * font)
{
return (sizeof (font)) * (sizeof (glyph)) * font->glyph_count;
return (sizeof (struct font)) + (sizeof (struct glyph)) * font->glyph_count;
}
void FontLayout::init(size_t buffer, int layout_buffer_length)
@ -26,3 +26,26 @@ void FontLayout::init(size_t buffer, int layout_buffer_length)
this->glyph_buffer[i].size.y = float(bitmap.height) / float(this->font->texture_height);
}
}
void FontLayout::draw_string(char const * string)
{
int i = 0;
while (true) {
char c = string[i];
if (c == 0)
return;
if (c < font->first_char_code || c > font->last_char_code) {
continue;
}
int char_index = c - font->first_char_code;
glyph const & glyph = glyphs[char_index];
if (layout_buffer_index >= layout_buffer_length)
return;
layout_buffer[layout_buffer_index].position = XMFLOAT3(0, 0, 0);
layout_buffer[layout_buffer_index].glyph_index = char_index;
layout_buffer_index += 1;
}
}

View File

@ -23,4 +23,5 @@ struct FontLayout {
LayoutBuffer * layout_buffer;
void init(size_t buffer, int layout_buffer_length);
void draw_string(char const * string);
};

View File

@ -293,7 +293,7 @@ int main(int argc, char *argv[])
fwrite(reinterpret_cast<void*>(&font), (sizeof (font)), 1, out);
fwrite(reinterpret_cast<void*>(&glyphs[0]), (sizeof (glyph)), num_glyphs, out);
fwrite(reinterpret_cast<void*>(&texture[0]), (sizeof (uint8_t)), texture_size, out);
//fwrite(reinterpret_cast<void*>(&texture[0]), (sizeof (uint8_t)), texture_size, out);
fclose(out);
}