draw sdf glyph

This commit is contained in:
Zack Buhman 2026-08-05 13:19:11 -05:00
parent 6abcee60fb
commit dc0d6df205
8 changed files with 186 additions and 33 deletions

2
.gitignore vendored
View File

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

138
font.js
View File

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

View File

@ -6,13 +6,22 @@ struct Configuration {
aspect: f32, aspect: f32,
}; };
struct GlyphBitmap { struct GlyphBuffer {
position: vec2f, position: vec2f,
velocity: vec2f, size: vec2f,
};
struct LayoutBuffer {
position: vec4f, // glyph_index w
}; };
@group(0) @binding(0) var<uniform> config: Configuration; @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 { struct VertexInput {
@builtin(vertex_index) vertex_index: u32, @builtin(vertex_index) vertex_index: u32,
@ -35,11 +44,11 @@ fn vertexMain(input: VertexInput) -> VertexOutput
{ {
let texture = vertices[input.vertex_index]; 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; var output: VertexOutput;
output.position = vec4f(position, 0.0, 1.0); output.position = vec4f(position * 0.5, 0.0, 1.0);
output.texture = position * vec2f(config.aspect, 1); output.texture = texture;
return output; return output;
} }
@ -48,5 +57,12 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4f
{ {
var color = vec3f(input.texture.xy, 0); 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__texture_address \
-Wl,--export=font_layout__glyph_buffer_size \ -Wl,--export=font_layout__glyph_buffer_size \
-Wl,--export=font_layout__glyph_buffer_address \ -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__texture_address \
-Wl,--export=font_layout__layout_buffer_size \ -Wl,--export=font_layout__layout_buffer_size \
-Wl,--export=font_layout__layout_buffer_address \ -Wl,--export=font_layout__layout_buffer_address \
-Wl,--export=font_layout__layout_buffer_index \ -Wl,--export=font_layout__layout_buffer_index \
-Wl,--export=font_layout__draw \
-Wl,--import-undefined \ -Wl,--import-undefined \
-Wl,--print-map \ -Wl,--print-map \
-Wl,--import-memory \ -Wl,--import-memory \

View File

@ -19,10 +19,14 @@ extern "C" {
return font_layout->glyph_buffer; 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;
return font_layout->font->texture_width * font_layout->font->texture_width * bytes_per_pixel; }
size_t font_layout__texture_height(FontLayout * font_layout)
{
return font_layout->font->texture_height;
} }
void * font_layout__texture_address(FontLayout * font_layout) void * font_layout__texture_address(FontLayout * font_layout)
@ -44,4 +48,9 @@ extern "C" {
{ {
return font_layout->layout_buffer_index; 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) 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) 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); 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; LayoutBuffer * layout_buffer;
void init(size_t buffer, int layout_buffer_length); 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*>(&font), (sizeof (font)), 1, out);
fwrite(reinterpret_cast<void*>(&glyphs[0]), (sizeof (glyph)), num_glyphs, 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); fclose(out);
} }