sdf string drawing

This commit is contained in:
Zack Buhman 2026-08-05 18:50:04 -05:00
parent 71052ffb69
commit 997bfac193
10 changed files with 261 additions and 126 deletions

75
font.js
View File

@ -1,4 +1,6 @@
import { getPath } from "./common.js"; import { getPath } from "./common.js";
import { makeSliders } from "./fontSliders.js";
import { updateSliders } from "./slider.js";
class FontRenderer { class FontRenderer {
createBuffers(device) createBuffers(device)
@ -9,14 +11,14 @@ class FontRenderer {
// glyph // glyph
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
const glyphBufferSize = this.module.instance.exports.font_layout__glyph_buffer_size(this.fontLayoutAddress); const glyphBufferSize = this.exports.font_layout__glyph_buffer_size(this.fontLayoutAddress);
const glyphBuffer = device.createBuffer({ const glyphBuffer = device.createBuffer({
label: `glyph buffer`, label: `glyph buffer`,
size: glyphBufferSize, size: glyphBufferSize,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
}); });
const glyphBufferAddress = this.module.instance.exports.font_layout__glyph_buffer_address(this.fontLayoutAddress); const glyphBufferAddress = this.exports.font_layout__glyph_buffer_address(this.fontLayoutAddress);
device.queue.writeBuffer(glyphBuffer, 0, device.queue.writeBuffer(glyphBuffer, 0,
this.module.memory.buffer, glyphBufferAddress, glyphBufferSize); this.module.memory.buffer, glyphBufferAddress, glyphBufferSize);
@ -24,9 +26,9 @@ class FontRenderer {
// texture // texture
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
const textureWidth = this.module.instance.exports.font_layout__texture_width(this.fontLayoutAddress); const textureWidth = this.exports.font_layout__texture_width(this.fontLayoutAddress);
const textureHeight = this.module.instance.exports.font_layout__texture_height(this.fontLayoutAddress); const textureHeight = this.exports.font_layout__texture_height(this.fontLayoutAddress);
const textureAddress = this.module.instance.exports.font_layout__texture_address(this.fontLayoutAddress); const textureAddress = this.exports.font_layout__texture_address(this.fontLayoutAddress);
//console.log(textureWidth, textureHeight, textureAddress); //console.log(textureWidth, textureHeight, textureAddress);
const texture = device.createTexture({ const texture = device.createTexture({
@ -69,10 +71,12 @@ class FontRenderer {
}); });
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// layout // per-frame (layout, configuration)
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
const layoutBufferSize = this.module.instance.exports.font_layout__layout_buffer_size(this.fontLayoutAddress); 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++) { for (let i = 0; i < 2; i++) {
const layoutBuffer = device.createBuffer({ const layoutBuffer = device.createBuffer({
label: `layout buffer ${i}`, label: `layout buffer ${i}`,
@ -80,17 +84,27 @@ class FontRenderer {
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, 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({ const bindGroup = device.createBindGroup({
label: `font per-frame bind group ${i}`, label: `font per-frame bind group ${i}`,
layout: this.bindGroupLayout.perFrame, layout: this.bindGroupLayout.perFrame,
entries: [{ entries: [{
binding: 0, binding: 0,
resource: { buffer: layoutBuffer }, resource: { buffer: layoutBuffer },
}, {
binding: 1,
resource: { buffer: configurationBuffer },
}] }]
}); });
this.frames.push({ this.frames.push({
layoutBuffer: layoutBuffer, layoutBuffer: layoutBuffer,
configurationBuffer: configurationBuffer,
bindGroup: bindGroup, bindGroup: bindGroup,
}); });
} }
@ -101,16 +115,17 @@ class FontRenderer {
const label = "font"; const label = "font";
this.module = module; this.module = module;
this.exports = module.instance.exports;
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// font layout // font layout
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
this.fontBufferAddress = this.module.instance.exports.mem_alloc(fontBufferSrc.byteLength); this.fontBufferAddress = this.exports.mem_alloc(fontBufferSrc.byteLength);
const fontBufferDst = new Uint8Array(this.module.memory.buffer, this.fontBufferAddress, fontBufferSrc.byteLength); const fontBufferDst = new Uint8Array(this.module.memory.buffer, this.fontBufferAddress, fontBufferSrc.byteLength);
fontBufferDst.set(new Uint8Array(fontBufferSrc)); fontBufferDst.set(new Uint8Array(fontBufferSrc));
this.maxGlyphs = 1024; this.maxGlyphs = 1024;
this.fontLayoutAddress = this.module.instance.exports.font_layout__create(this.fontBufferAddress, this.maxGlyphs); this.fontLayoutAddress = this.exports.font_layout__create(this.fontBufferAddress, this.maxGlyphs);
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// shader module // shader module
@ -167,7 +182,7 @@ class FontRenderer {
texture: {}, texture: {},
}, { // glyph buffer }, { // glyph buffer
binding: 2, binding: 2,
visibility: GPUShaderStage.FRAGMENT, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
buffer: { type: "read-only-storage" } buffer: { type: "read-only-storage" }
} }
] ]
@ -178,12 +193,11 @@ class FontRenderer {
binding: 0, binding: 0,
visibility: GPUShaderStage.VERTEX, visibility: GPUShaderStage.VERTEX,
buffer: { type: "read-only-storage" } buffer: { type: "read-only-storage" }
} }, { // configuration
/*, { // configuration
binding: 1, binding: 1,
visibility: GPUShaderStage.FRAGMENT, visibility: GPUShaderStage.VERTEX,
buffer: { type: "uniform" } buffer: { type: "uniform" }
}*/] }]
}), }),
}; };
@ -238,6 +252,33 @@ class FontRenderer {
resource: { buffer: viewUniformBuffer }, 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) render(renderPass, frameNumber)
@ -247,7 +288,8 @@ class FontRenderer {
renderPass.setBindGroup(0, this.viewBindGroup); renderPass.setBindGroup(0, this.viewBindGroup);
renderPass.setBindGroup(1, this.perFontBindGroup); renderPass.setBindGroup(1, this.perFontBindGroup);
renderPass.setBindGroup(2, this.frames[frameNumber].bindGroup); renderPass.setBindGroup(2, this.frames[frameNumber].bindGroup);
renderPass.drawIndexed(6);
renderPass.drawIndexed(6, this.instanceCount);
} }
} }
@ -259,6 +301,9 @@ async function loadFont(device, canvasFormat, viewUniformBuffer, module)
const liberationBuffer = await liberationResponse.arrayBuffer(); const liberationBuffer = await liberationResponse.arrayBuffer();
const renderer = new FontRenderer(device, canvasFormat, viewUniformBuffer, fontWgsl, module, liberationBuffer); const renderer = new FontRenderer(device, canvasFormat, viewUniformBuffer, fontWgsl, module, liberationBuffer);
module.instance.exports.font_layout__draw(renderer.fontLayoutAddress);
return renderer; return renderer;
} }

View File

@ -1,4 +1,4 @@
struct Configuration { struct View {
viewProj: mat4x4f, viewProj: mat4x4f,
lightViewProj: mat4x4f, lightViewProj: mat4x4f,
lightPosition: vec4f, lightPosition: vec4f,
@ -6,30 +6,42 @@ struct Configuration {
aspect: f32, aspect: f32,
}; };
struct Config {
scale: f32,
translateX: f32,
translateY: f32,
};
struct GlyphBuffer { struct GlyphBuffer {
position: vec2f, texPosition: vec2f,
texSize: vec2f,
size: vec2f, size: vec2f,
unused: vec2f,
}; };
struct LayoutBuffer { struct LayoutBuffer {
position: vec4f, // glyph_index w position: vec4f, // glyph_index w
}; };
@group(0) @binding(0) var<uniform> config: Configuration; @group(0) @binding(0) var<uniform> view: View;
@group(1) @binding(0) var linearSampler: sampler; @group(1) @binding(0) var linearSampler: sampler;
@group(1) @binding(1) var fontTexture: texture_2d<f32>; @group(1) @binding(1) var fontTexture: texture_2d<f32>;
@group(1) @binding(2) var<storage> glyphBuffer: array<GlyphBuffer>; @group(1) @binding(2) var<storage> glyphBuffer: array<GlyphBuffer>;
@group(2) @binding(0) var<storage> layoutBuffer: array<LayoutBuffer>; @group(2) @binding(0) var<storage> layoutBuffer: array<LayoutBuffer>;
@group(2) @binding(1) var<uniform> config: Config;
struct VertexInput { struct VertexInput {
@builtin(vertex_index) vertex_index: u32, @builtin(vertex_index) vertex_index: u32,
@builtin(instance_index) instance_index: u32,
}; };
struct VertexOutput { struct VertexOutput {
@builtin(position) position: vec4f, @builtin(position) position: vec4f,
@location(0) texture: vec2f, @location(0) texture: vec2f,
@location(1) @interpolate(flat) glyphIndex: u32,
@location(2) foo: vec4f,
}; };
const vertices = array( const vertices = array(
@ -44,11 +56,17 @@ fn vertexMain(input: VertexInput) -> VertexOutput
{ {
let texture = vertices[input.vertex_index]; let texture = vertices[input.vertex_index];
let position = vec2f(texture.x, 1.0 - texture.y) * 2.0 - 1.0; let position = vec2f(texture.x, 1.0 - texture.y);
let glyphIndex = u32(layoutBuffer[input.instance_index].position.w);
let viewPosition = position * glyphBuffer[glyphIndex].size + layoutBuffer[input.instance_index].position.xy;
let screenPosition = viewPosition * vec2f(1 / view.aspect, 1) * config.scale + vec2f(config.translateX, config.translateY);
var output: VertexOutput; var output: VertexOutput;
output.position = vec4f(position * 0.5, 0.0, 1.0); output.position = vec4f(screenPosition, 0.0, 1.0);
output.texture = texture; output.texture = texture;
output.glyphIndex = glyphIndex;
output.foo = vec4f(layoutBuffer[glyphIndex].position.xy, 0, 0);
return output; return output;
} }
@ -57,12 +75,16 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4f
{ {
var color = vec3f(input.texture.xy, 0); var color = vec3f(input.texture.xy, 0);
let p = glyphBuffer[66].position; let p = glyphBuffer[input.glyphIndex].texPosition;
let s = glyphBuffer[66].size; let s = glyphBuffer[input.glyphIndex].texSize;
let base = textureSample(fontTexture, linearSampler, input.texture * s + p); let base = textureSample(fontTexture, linearSampler, input.texture * s + p);
let t = f32(base.x > 0.5); let t = f32(base.x > 0.5);
if (base.x < 0.5) {
discard;
}
return vec4f(t, t, t, 1); return vec4f(t, t, t, 1);
} }

12
fontSliders.js Normal file
View File

@ -0,0 +1,12 @@
import { Slider } from "./slider.js"
function makeSliders()
{
return {
scale: new Slider("scale", 0.0, 0.05),
translateX: new Slider("translate x", -10, 10),
translateY: new Slider("translate y", -10, 10),
};
}
export { makeSliders };

View File

@ -9,28 +9,37 @@ extern "C" {
// metrics are 26.6 fixed point // metrics are 26.6 fixed point
struct glyph_metrics { struct glyph_metrics {
int32_t width;
int32_t height;
int32_t horiBearingX; int32_t horiBearingX;
int32_t horiBearingY; int32_t horiBearingY;
int32_t horiAdvance; int32_t horiAdvance;
} __attribute__ ((packed)); } __attribute__ ((packed));
static_assert((sizeof (struct glyph_metrics)) == ((sizeof (int32_t)) * 3)); static_assert((sizeof (struct glyph_metrics)) == ((sizeof (int32_t)) * 5));
struct glyph_bitmap { struct glyph_texture {
uint16_t x; uint16_t x;
uint16_t y; uint16_t y;
uint16_t width; uint16_t width;
uint16_t height; uint16_t height;
} __attribute__ ((packed)); } __attribute__ ((packed));
static_assert((sizeof (struct glyph_bitmap)) == ((sizeof (uint16_t)) * 4)); static_assert((sizeof (struct glyph_texture)) == ((sizeof (uint16_t)) * 4));
struct glyph_bitmap {
int32_t left;
int32_t top;
} __attribute__ ((packed));
static_assert((sizeof (struct glyph_bitmap)) == ((sizeof (uint32_t)) * 2));
struct glyph { struct glyph {
struct glyph_bitmap bitmap; struct glyph_texture texture;
struct glyph_metrics metrics; struct glyph_metrics metrics;
struct glyph_bitmap bitmap;
} __attribute__ ((packed)); } __attribute__ ((packed));
static_assert((sizeof (struct glyph)) == ((sizeof (struct glyph_bitmap)) + (sizeof (struct glyph_metrics)))); static_assert((sizeof (struct glyph)) == ((sizeof (struct glyph_texture)) + (sizeof (struct glyph_metrics)) + (sizeof (struct glyph_bitmap))));
struct font { struct font {
uint32_t first_char_code; uint32_t first_char_code;

View File

@ -47,6 +47,7 @@ const module = await WebAssembly.instantiateStreaming(fetch("src/module.wasm"),
env: { env: {
memory: memory, memory: memory,
log: console.log, log: console.log,
log4: console.log,
logStrInt: console.log, logStrInt: console.log,
} }
}); });
@ -195,73 +196,6 @@ function updateView()
viewUniformBufferSize); viewUniformBufferSize);
} }
const controlMain = document.getElementById("control-main");
function remap(low1, high1, low2, high2, value)
{
return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
}
const sliderDefaults = {"bone length":"32","velocity acc.":"385","velocity damp.":"796","bone count":"40"};
class Slider {
constructor(label, low, high, max = 1024)
{
const labelE = document.createElement("span");
labelE.className = "label";
labelE.innerHTML = label;
const valueE = document.createElement("div");
valueE.className = "value";
const inputE = document.createElement("input");
inputE.className = "range";
inputE.min = 0;
inputE.max = max;
inputE.type = "range";
const spanE = document.createElement("span");
spanE.className = "range-span code";
valueE.appendChild(inputE);
valueE.appendChild(spanE);
controlMain.appendChild(labelE);
controlMain.appendChild(valueE);
this.label = label;
this.input = inputE;
this.span = spanE;
this.low = low;
this.high = high;
this.lastValue = undefined;
const storageValue = localStorage.getItem(this.label);
if (storageValue !== null) {
this.input.value = storageValue;
} else if (this.label in sliderDefaults) {
this.input.value = sliderDefaults[this.label];
}
this.input.value = sliderDefaults[this.label];
this.update();
}
update()
{
const rawValue = parseInt(this.input.value);
const value = remap(this.input.min, this.input.max, this.low, this.high, this.input.value);
if (rawValue !== this.lastValue) {
this.lastValue = rawValue;
localStorage.setItem(this.label, rawValue);
}
if (this.high > 10) {
this.span.innerHTML = value.toFixed(0);
} else {
this.span.innerHTML = value.toFixed(5);
}
return value;
}
}
/* /*
const flatSliders = { const flatSliders = {
@ -282,21 +216,14 @@ const flatSliders = {
const mousePosition = new Float32Array([0, 0, 0, 0]); const mousePosition = new Float32Array([0, 0, 0, 0]);
/*
const snakeSliders = { const snakeSliders = {
boneCount: new Slider("bone count", 0.0, 100.0, 100), boneCount: new Slider("bone count", 0.0, 100.0, 100),
boneLength: new Slider("bone length", 0.0, 0.5), boneLength: new Slider("bone length", 0.0, 0.5),
velocityDamping: new Slider("velocity damp.", 0.0, 1.0), velocityDamping: new Slider("velocity damp.", 0.0, 1.0),
velocityAcceleration: new Slider("velocity acc.", 0.0, 1.0), velocityAcceleration: new Slider("velocity acc.", 0.0, 1.0),
}; };
*/
function updateSliders(sliders)
{
const values = {};
for (let [key, value] of Object.entries(sliders)) {
values[key] = value.update();
}
return values;
}
function onClick(e) function onClick(e)
{ {
@ -320,7 +247,7 @@ function render2()
{ {
if (canRender === true) { if (canRender === true) {
//const configuration = updateSliders(flatSliders); //const configuration = updateSliders(flatSliders);
const configuration = updateSliders(snakeSliders); //const configuration = updateSliders(snakeSliders);
canRender = false; canRender = false;
recreateDepth(false); recreateDepth(false);
@ -351,6 +278,7 @@ function render2()
//snakeRenderer.update(device, frameNumber, mousePosition, configuration); //snakeRenderer.update(device, frameNumber, mousePosition, configuration);
//snakeRenderer.render(renderPass, frameNumber); //snakeRenderer.render(renderPass, frameNumber);
fontRenderer.update(device, frameNumber);
fontRenderer.render(renderPass, frameNumber); fontRenderer.render(renderPass, frameNumber);
renderPass.end(); renderPass.end();

77
slider.js Normal file
View File

@ -0,0 +1,77 @@
const controlMain = document.getElementById("control-main");
function remap(low1, high1, low2, high2, value)
{
return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
}
const sliderDefaults = {"bone length":"32","velocity acc.":"385","velocity damp.":"796","bone count":"40"};
class Slider {
constructor(label, low, high, max = 1024)
{
const labelE = document.createElement("span");
labelE.className = "label";
labelE.innerHTML = label;
const valueE = document.createElement("div");
valueE.className = "value";
const inputE = document.createElement("input");
inputE.className = "range";
inputE.min = 0;
inputE.max = max;
inputE.type = "range";
const spanE = document.createElement("span");
spanE.className = "range-span code";
valueE.appendChild(inputE);
valueE.appendChild(spanE);
controlMain.appendChild(labelE);
controlMain.appendChild(valueE);
this.label = label;
this.input = inputE;
this.span = spanE;
this.low = low;
this.high = high;
this.lastValue = undefined;
const storageValue = localStorage.getItem(this.label);
if (storageValue !== null) {
this.input.value = storageValue;
} else if (this.label in sliderDefaults) {
this.input.value = sliderDefaults[this.label];
}
this.update();
}
update()
{
const rawValue = parseInt(this.input.value);
const value = remap(this.input.min, this.input.max, this.low, this.high, this.input.value);
if (rawValue !== this.lastValue) {
this.lastValue = rawValue;
localStorage.setItem(this.label, rawValue);
}
if (this.high > 10) {
this.span.innerHTML = value.toFixed(0);
} else {
this.span.innerHTML = value.toFixed(4);
}
return value;
}
}
function updateSliders(sliders)
{
const values = {};
for (let [key, value] of Object.entries(sliders)) {
values[key] = value.update();
}
return values;
}
export { Slider, updateSliders };

View File

@ -51,6 +51,6 @@ extern "C" {
void font_layout__draw(FontLayout * font_layout) void font_layout__draw(FontLayout * font_layout)
{ {
font_layout->draw_string("@"); font_layout->draw_string("asdf qwer");
} }
}; };

View File

@ -9,7 +9,7 @@ static inline size_t texture_offset(font * font)
void FontLayout::init(size_t buffer, int layout_buffer_length) void FontLayout::init(size_t buffer, int layout_buffer_length)
{ {
this->font = reinterpret_cast<struct font *>(buffer + 0); this->font = reinterpret_cast<struct font *>(buffer + 0);
this->glyphs = reinterpret_cast<struct glyph *>(buffer + (sizeof (font))); this->glyphs = reinterpret_cast<struct glyph *>(buffer + (sizeof (struct font)));
this->texture = reinterpret_cast<void *>(buffer + texture_offset(this->font)); this->texture = reinterpret_cast<void *>(buffer + texture_offset(this->font));
@ -19,19 +19,25 @@ void FontLayout::init(size_t buffer, int layout_buffer_length)
this->glyph_buffer = New<GlyphBuffer>(this->font->glyph_count); this->glyph_buffer = New<GlyphBuffer>(this->font->glyph_count);
for (uint16_t i = 0; i < this->font->glyph_count; i++) { for (uint16_t i = 0; i < this->font->glyph_count; i++) {
glyph_bitmap const & bitmap = this->glyphs[i].bitmap; glyph_texture const & texture = this->glyphs[i].texture;
this->glyph_buffer[i].position.x = float(bitmap.x) / float(this->font->texture_width); this->glyph_buffer[i].texPosition.x = float(texture.x) / float(this->font->texture_width);
this->glyph_buffer[i].position.y = float(bitmap.y) / float(this->font->texture_height); this->glyph_buffer[i].texPosition.y = float(texture.y) / float(this->font->texture_height);
this->glyph_buffer[i].size.x = float(bitmap.width) / float(this->font->texture_width); this->glyph_buffer[i].texSize.x = float(texture.width) / float(this->font->texture_width);
this->glyph_buffer[i].size.y = float(bitmap.height) / float(this->font->texture_height); this->glyph_buffer[i].texSize.y = float(texture.height) / float(this->font->texture_height);
this->glyph_buffer[i].size.x = float(texture.width);
this->glyph_buffer[i].size.y = float(texture.height);
} }
} }
void FontLayout::draw_string(char const * string) void FontLayout::draw_string(char const * string)
{ {
int i = 0; int i = 0;
int32_t x = 0;
int32_t y = 0;
while (true) { while (true) {
char c = string[i]; char c = string[i++];
if (c == 0) if (c == 0)
return; return;
@ -44,8 +50,17 @@ void FontLayout::draw_string(char const * string)
if (layout_buffer_index >= layout_buffer_length) if (layout_buffer_index >= layout_buffer_length)
return; return;
layout_buffer[layout_buffer_index].position = XMFLOAT3(0, 0, 0); int32_t bearingY = glyph.metrics.height - glyph.metrics.horiBearingY;
XMFLOAT3 position {
float(x + glyph.metrics.horiBearingX) * 0.015625f,
float(y - bearingY) * 0.015625f,
0.0f,
};
layout_buffer[layout_buffer_index].position = position;
layout_buffer[layout_buffer_index].glyph_index = char_index; layout_buffer[layout_buffer_index].glyph_index = char_index;
layout_buffer_index += 1; layout_buffer_index += 1;
x += glyph.metrics.horiAdvance;
} }
} }

View File

@ -4,8 +4,10 @@
#include "directxmath/DirectXMath.h" #include "directxmath/DirectXMath.h"
struct GlyphBuffer { struct GlyphBuffer {
XMFLOAT2 position; // in uv units XMFLOAT2 texPosition; // in uv units
XMFLOAT2 size; // in uv units XMFLOAT2 texSize; // in uv units
XMFLOAT2 size; // in arbitrary-scale units
XMFLOAT2 unused;
}; };
struct LayoutBuffer { struct LayoutBuffer {

View File

@ -133,17 +133,41 @@ load_outline_char(const FT_Face face,
} }
} }
glyph_bitmap& bitmap = glyph->bitmap; /*
bitmap.x = byteswap<uint16_t>(rect.x); if (char_code == 'q' || char_code == 'f' || char_code == 'w') {
bitmap.y = byteswap<uint16_t>(rect.y); std::cerr << char(char_code) << " metrics "
bitmap.width = byteswap<uint16_t>(rect.width); << (face->glyph->metrics.width >> 6) << ' ' << (face->glyph->metrics.height >> 6)
bitmap.height = byteswap<uint16_t>(rect.height); << " bitmap wr "
<< face->glyph->bitmap.width << ' ' << face->glyph->bitmap.rows
<< " slot lt "
<< face->glyph->bitmap_left << ' ' << face->glyph->bitmap_top
<< " bearingX "
<< (face->glyph->metrics.horiBearingX >> 6)
<< " bearingY "
<< (face->glyph->metrics.horiBearingY >> 6)
<< '\n';
}
*/
{
glyph_texture& texture = glyph->texture;
texture.x = byteswap<uint16_t>(rect.x);
texture.y = byteswap<uint16_t>(rect.y);
texture.width = byteswap<uint16_t>(rect.width);
texture.height = byteswap<uint16_t>(rect.height);
glyph_metrics& metrics = glyph->metrics; glyph_metrics& metrics = glyph->metrics;
metrics.width = byteswap<int32_t>(face->glyph->metrics.width);
metrics.height = byteswap<int32_t>(face->glyph->metrics.height);
metrics.horiBearingX = byteswap<int32_t>(face->glyph->metrics.horiBearingX); metrics.horiBearingX = byteswap<int32_t>(face->glyph->metrics.horiBearingX);
metrics.horiBearingY = byteswap<int32_t>(face->glyph->metrics.horiBearingY); metrics.horiBearingY = byteswap<int32_t>(face->glyph->metrics.horiBearingY);
metrics.horiAdvance = byteswap<int32_t>(face->glyph->metrics.horiAdvance); metrics.horiAdvance = byteswap<int32_t>(face->glyph->metrics.horiAdvance);
glyph_bitmap& bitmap = glyph->bitmap;
bitmap.left = byteswap<int32_t>(face->glyph->bitmap_left);
bitmap.top = byteswap<int32_t>(face->glyph->bitmap_top);
}
return 0; return 0;
} }
@ -168,8 +192,8 @@ load_all_positions(const FT_Face face,
const uint32_t num_glyphs = (end - start) + 1; const uint32_t num_glyphs = (end - start) + 1;
struct rect rects[num_glyphs]; struct rect rects[num_glyphs];
FT_Int32 load_flags = FT_LOAD_DEFAULT | FT_LOAD_TARGET_MODE(FT_RENDER_MODE_SDF) | FT_LOAD_RENDER;
FT_Render_Mode render_mode = FT_RENDER_MODE_SDF; FT_Render_Mode render_mode = FT_RENDER_MODE_SDF;
FT_Int32 load_flags = FT_LOAD_DEFAULT | FT_LOAD_TARGET_MODE(render_mode) | FT_LOAD_RENDER;
// first, load all rectangles // first, load all rectangles
for (uint32_t char_code = start; char_code <= end; char_code++) { for (uint32_t char_code = start; char_code <= end; char_code++) {
@ -284,6 +308,7 @@ int main(int argc, char *argv[])
std::cerr << "end: 0x" << std::hex << end << '\n'; std::cerr << "end: 0x" << std::hex << end << '\n';
std::cerr << "texture_width: " << std::dec << window_curve_ix.window.width << '\n'; std::cerr << "texture_width: " << std::dec << window_curve_ix.window.width << '\n';
std::cerr << "texture_height: " << std::dec << window_curve_ix.window.height << '\n'; std::cerr << "texture_height: " << std::dec << window_curve_ix.window.height << '\n';
std::cerr << "texture offset: " << (sizeof (glyph)) * num_glyphs + (sizeof (font)) << '\n';
FILE * out = fopen(argv[output_file_path], "w"); FILE * out = fopen(argv[output_file_path], "w");
if (out == NULL) { if (out == NULL) {
@ -293,7 +318,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);
} }