Adjust font uvs;

- Padding is automatically computed from spread.
  - Spread increases detail at small sizes.
  - Remove failure cases where padding < spread/2
- UVs are un16x2, making room for color
- Don't center glyphs inside their atlas bounding box
- Cache normalized UVs and update them (for glyphs and vertices) when
  the atlas changes size.
  - Updating the UVs is UGLY and duplicates a lot of code.  It may be
    better to normalize the UVs on the fly, or just re-render the entire
    string if the atlas is updated.
This commit is contained in:
bjorn 2022-06-26 20:28:30 -07:00
parent 24fd9e0c04
commit cbe24f482f
6 changed files with 80 additions and 65 deletions

View File

@ -219,7 +219,7 @@ static int l_lovrRasterizerGetGlyphImage(lua_State* L) {
uint32_t height = 2 + (uint32_t) ceilf(box[3] - box[1]);
Image* image = lovrImageCreateRaw(width, height, FORMAT_RGBA32F);
void* pixels = lovrImageGetLayerData(image, 0, 0);
lovrRasterizerGetGlyphPixels(rasterizer, codepoint, pixels, width, height, 4., 1);
lovrRasterizerGetGlyphPixels(rasterizer, codepoint, pixels, width, height, 4.);
luax_pushtype(L, Image, image);
lovrRelease(image, lovrImageDestroy);
return 1;

View File

@ -1241,7 +1241,6 @@ static int l_lovrGraphicsNewFont(lua_State* L) {
FontInfo info = { 0 };
info.rasterizer = luax_totype(L, 1, Rasterizer);
info.padding = 1;
info.spread = 4.;
if (!info.rasterizer) {
@ -1250,20 +1249,17 @@ static int l_lovrGraphicsNewFont(lua_State* L) {
if (lua_type(L, 1) == LUA_TNUMBER || lua_isnoneornil(L, 1)) {
size = luax_optfloat(L, 1, 32.f);
info.padding = luaL_optinteger(L, 2, info.padding);
info.spread = luaL_optnumber(L, 3, info.spread);
info.spread = luaL_optnumber(L, 2, info.spread);
} else {
blob = luax_readblob(L, 1, "Font");
size = luax_optfloat(L, 2, 32.f);
info.padding = luaL_optinteger(L, 3, info.padding);
info.spread = luaL_optnumber(L, 4, info.spread);
info.spread = luaL_optnumber(L, 3, info.spread);
}
info.rasterizer = lovrRasterizerCreate(blob, size);
lovrRelease(blob, lovrBlobDestroy);
} else {
info.padding = luaL_optinteger(L, 2, info.padding);
info.spread = luaL_optnumber(L, 3, info.spread);
info.spread = luaL_optnumber(L, 2, info.spread);
}
Font* font = lovrFontCreate(&info);

View File

@ -176,7 +176,7 @@ bool lovrRasterizerGetGlyphCurves(Rasterizer* rasterizer, uint32_t codepoint, vo
return true;
}
bool lovrRasterizerGetGlyphPixels(Rasterizer* rasterizer, uint32_t codepoint, float* pixels, uint32_t width, uint32_t height, double spread, uint32_t padding) {
bool lovrRasterizerGetGlyphPixels(Rasterizer* rasterizer, uint32_t codepoint, float* pixels, uint32_t width, uint32_t height, double spread) {
int id = stbtt_FindGlyphIndex(&rasterizer->font, codepoint);
if (stbtt_IsGlyphEmpty(&rasterizer->font, id)) {
@ -221,13 +221,10 @@ bool lovrRasterizerGetGlyphPixels(Rasterizer* rasterizer, uint32_t codepoint, fl
int x0, y0, x1, y1;
stbtt_GetGlyphBox(&rasterizer->font, id, &x0, &y0, &x1, &y1);
double unused;
float scale = rasterizer->scale;
float centerOffsetX = (1.f - modf((x1 - x0) * scale, &unused)) / 2.f;
float centerOffsetY = (1.f - modf((y1 - y0) * scale, &unused)) / 2.f;
float offsetX = -x0 + ((padding + centerOffsetX) / rasterizer->scale);
float offsetY = -y1 - ((padding + centerOffsetY) / rasterizer->scale);
uint32_t padding = ceil(spread / 2.);
float offsetX = -x0 + padding / scale;
float offsetY = -y1 - padding / scale;
msShapeNormalize(shape);
msEdgeColoringSimple(shape, 3., 0);

View File

@ -26,4 +26,4 @@ float lovrRasterizerGetGlyphBearing(Rasterizer* rasterizer, uint32_t codepoint);
void lovrRasterizerGetGlyphBoundingBox(Rasterizer* rasterizer, uint32_t codepoint, float box[4]);
bool lovrRasterizerIsGlyphEmpty(Rasterizer* rasterizer, uint32_t codepoint);
bool lovrRasterizerGetGlyphCurves(Rasterizer* rasterizer, uint32_t codepoint, void (*fn)(void* context, uint32_t degree, float* points), void* context);
bool lovrRasterizerGetGlyphPixels(Rasterizer* rasterizer, uint32_t codepoint, float* pixels, uint32_t width, uint32_t height, double spread, uint32_t padding);
bool lovrRasterizerGetGlyphPixels(Rasterizer* rasterizer, uint32_t codepoint, float* pixels, uint32_t width, uint32_t height, double spread);

View File

@ -34,7 +34,8 @@ typedef struct {
typedef struct {
struct { float x, y; } position;
struct { float u, v; } uv;
struct { uint16_t u, v; } uv;
struct { uint8_t r, g, b, a; } color;
} GlyphVertex;
typedef struct {
@ -125,10 +126,10 @@ struct Material {
typedef struct {
uint32_t codepoint;
uint16_t atlas[4];
float advance;
uint16_t x, y;
uint16_t uv[4];
float box[4];
float uv[4];
} Glyph;
struct Font {
@ -140,6 +141,7 @@ struct Font {
map_t kerning;
float pixelDensity;
float lineSpacing;
uint32_t padding;
Texture* atlas;
uint32_t atlasWidth;
uint32_t atlasHeight;
@ -477,8 +479,8 @@ bool lovrGraphicsInit(bool debug, bool vsync) {
.bufferStrides[0] = sizeof(GlyphVertex),
.attributes[0] = { 0, 10, offsetof(GlyphVertex, position), GPU_TYPE_F32x2 },
.attributes[1] = { 1, 11, 0, GPU_TYPE_F32x4 },
.attributes[2] = { 0, 12, offsetof(GlyphVertex, uv), GPU_TYPE_F32x2 },
.attributes[3] = { 1, 13, 16, GPU_TYPE_F32x4 },
.attributes[2] = { 0, 12, offsetof(GlyphVertex, uv), GPU_TYPE_UN16x2 },
.attributes[3] = { 0, 13, offsetof(GlyphVertex, color), GPU_TYPE_UN8x4 },
.attributes[4] = { 1, 14, 0, GPU_TYPE_F32x4 }
};
@ -644,8 +646,7 @@ Font* lovrGraphicsGetDefaultFont() {
if (!state.defaultFont) {
state.defaultFont = lovrFontCreate(&(FontInfo) {
.rasterizer = lovrRasterizerCreate(NULL, 32),
.padding = 1,
.spread = 4.
.spread = 2.
});
}
@ -1787,6 +1788,7 @@ Font* lovrFontCreate(FontInfo* info) {
font->pixelDensity = lovrRasterizerGetHeight(info->rasterizer);
font->lineSpacing = 1.f;
font->padding = (uint32_t) ceil(info->spread / 2.);
return font;
}
@ -1838,9 +1840,7 @@ static Glyph* lovrFontGetGlyph(Font* font, uint32_t codepoint) {
glyph->advance = lovrRasterizerGetGlyphAdvance(font->info.rasterizer, codepoint);
if (lovrRasterizerIsGlyphEmpty(font->info.rasterizer, codepoint)) {
memset(glyph->atlas, 0, sizeof(glyph->atlas));
memset(glyph->box, 0, sizeof(glyph->atlas));
memset(glyph->uv, 0, sizeof(glyph->atlas));
memset(glyph->box, 0, sizeof(glyph->box));
return glyph;
}
@ -1848,9 +1848,8 @@ static Glyph* lovrFontGetGlyph(Font* font, uint32_t codepoint) {
float width = glyph->box[2] - glyph->box[0];
float height = glyph->box[3] - glyph->box[1];
uint32_t pixelWidth = 2 * font->info.padding + (uint32_t) ceilf(width);
uint32_t pixelHeight = 2 * font->info.padding + (uint32_t) ceilf(height);
uint32_t pixelWidth = 2 * font->padding + (uint32_t) ceilf(width);
uint32_t pixelHeight = 2 * font->padding + (uint32_t) ceilf(height);
if (font->atlasX + pixelWidth > font->atlasWidth) {
font->atlasX = font->atlasWidth == font->atlasHeight ? 0 : font->atlasWidth >> 1;
@ -1870,17 +1869,12 @@ static Glyph* lovrFontGetGlyph(Font* font, uint32_t codepoint) {
}
}
glyph->atlas[0] = font->atlasX;
glyph->atlas[1] = font->atlasY;
glyph->atlas[2] = pixelWidth;
glyph->atlas[3] = pixelHeight;
double unused;
// The glyph is centered in its rasterized rectangle (needs to be kept in sync with Rasterizer)
glyph->uv[0] = font->atlasX + font->info.padding + (1.f - modf(width, &unused)) / 2.f;
glyph->uv[1] = font->atlasY + font->info.padding + (1.f - modf(height, &unused)) / 2.f;
glyph->uv[2] = glyph->uv[0] + width;
glyph->uv[3] = glyph->uv[1] + height;
glyph->x = font->atlasX + font->padding;
glyph->y = font->atlasY + font->padding;
glyph->uv[0] = (uint16_t) ((float) glyph->x / font->atlasWidth * 65535.f + .5f);
glyph->uv[1] = (uint16_t) ((float) glyph->y / font->atlasHeight * 65535.f + .5f);
glyph->uv[2] = (uint16_t) ((float) (glyph->x + width) / font->atlasWidth * 65535.f + .5f);
glyph->uv[3] = (uint16_t) ((float) (glyph->y + height) / font->atlasHeight * 65535.f + .5f);
font->atlasX += pixelWidth;
font->rowHeight = MAX(font->rowHeight, font->atlasY + pixelHeight);
@ -1888,11 +1882,12 @@ static Glyph* lovrFontGetGlyph(Font* font, uint32_t codepoint) {
return glyph;
}
static void lovrFontUploadNewGlyphs(Font* font, uint32_t start) {
static void lovrFontUploadNewGlyphs(Font* font, uint32_t start, const char* str, uint32_t length, GlyphVertex* vertices) {
if (start >= font->glyphs.length) {
return;
}
// Atlas resize
if (!font->atlas || font->atlasWidth > font->atlas->info.width || font->atlasHeight > font->atlas->info.height) {
lovrCheck(font->atlasWidth <= 65536, "Font atlas is too big!");
@ -1931,6 +1926,7 @@ static void lovrFontUploadNewGlyphs(Font* font, uint32_t start) {
font->atlas = atlas;
// Material
lovrRelease(font->material, lovrMaterialDestroy);
font->material = lovrMaterialCreate(&(MaterialInfo) {
.data.color = { 1.f, 1.f, 1.f, 1.f },
@ -1938,23 +1934,55 @@ static void lovrFontUploadNewGlyphs(Font* font, uint32_t start) {
.data.sdfRange = { font->info.spread / font->atlasWidth, font->info.spread / font->atlasHeight },
.texture = font->atlas
});
// Recompute all glyph uvs after atlas resize
for (size_t i = 0; i < font->glyphs.length; i++) {
Glyph* glyph = &font->glyphs.data[i];
glyph->uv[0] = (uint16_t) ((float) glyph->x / font->atlasWidth * 65535.f + .5f);
glyph->uv[1] = (uint16_t) ((float) glyph->y / font->atlasHeight * 65535.f + .5f);
glyph->uv[2] = (uint16_t) ((float) (glyph->x + glyph->box[2] - glyph->box[0]) / font->atlasWidth * 65535.f + .5f);
glyph->uv[3] = (uint16_t) ((float) (glyph->y + glyph->box[3] - glyph->box[1]) / font->atlasHeight * 65535.f + .5f);
}
// Adjust current vertex uvs
size_t bytes;
uint32_t codepoint;
const char* end = str + length;
while ((bytes = utf8_decode(str, end, &codepoint)) > 0) {
Glyph* glyph = lovrFontGetGlyph(font, codepoint);
if (glyph->box[2] - glyph->box[0] != 0.f) {
vertices[0].uv.u = glyph->uv[0];
vertices[0].uv.v = glyph->uv[1];
vertices[1].uv.u = glyph->uv[2];
vertices[1].uv.v = glyph->uv[1];
vertices[2].uv.u = glyph->uv[0];
vertices[2].uv.v = glyph->uv[3];
vertices[3].uv.u = glyph->uv[2];
vertices[3].uv.v = glyph->uv[3];
vertices += 4;
}
str += bytes;
}
}
gpu_buffer* scratchpad = tempAlloc(gpu_sizeof_buffer());
for (uint32_t i = start; i < font->glyphs.length; i++) {
Glyph* glyph = &font->glyphs.data[i];
uint32_t width = glyph->atlas[2];
uint32_t height = glyph->atlas[3];
if (width == 0 || height == 0) {
float width = glyph->box[2] - glyph->box[0];
float height = glyph->box[3] - glyph->box[1];
if (width == 0.f || height == 0.f) {
continue;
}
void* pixels = gpu_map(scratchpad, width * height * 16, 16, GPU_MAP_WRITE);
lovrRasterizerGetGlyphPixels(font->info.rasterizer, glyph->codepoint, pixels, width, height, font->info.spread, font->info.padding);
uint32_t dstOffset[4] = { glyph->atlas[0], glyph->atlas[1], 0, 0 };
uint32_t extent[3] = { width, height, 1 };
uint32_t w = 2 * font->padding + ceilf(width);
uint32_t h = 2 * font->padding + ceilf(height);
void* pixels = gpu_map(scratchpad, w * h * 16, 16, GPU_MAP_WRITE);
lovrRasterizerGetGlyphPixels(font->info.rasterizer, glyph->codepoint, pixels, w, h, font->info.spread);
uint32_t dstOffset[4] = { glyph->x - font->padding, glyph->y - font->padding, 0, 0 };
uint32_t extent[3] = { w, h, 1 };
gpu_copy_buffer_texture(state.stream, scratchpad, font->atlas->gpu, 0, dstOffset, extent);
}
@ -3372,14 +3400,15 @@ void lovrPassText(Pass* pass, Font* font, const char* text, uint32_t length, flo
size_t bytes;
uint32_t codepoint;
uint32_t previous = '\0';
const char* str = text;
const char* end = text + length;
while ((bytes = utf8_decode(text, end, &codepoint)) > 0) {
while ((bytes = utf8_decode(str, end, &codepoint)) > 0) {
if (codepoint == ' ' || codepoint == '\t') {
Glyph* glyph = lovrFontGetGlyph(font, ' ');
wordStart = vertexCount;
x += codepoint == '\t' ? glyph->advance * 4.f : glyph->advance;
previous = '\0';
text += bytes;
str += bytes;
continue;
} else if (codepoint == '\n') {
aline(vertices, lineStart, vertexCount, halign);
@ -3389,7 +3418,7 @@ void lovrPassText(Pass* pass, Font* font, const char* text, uint32_t length, flo
y -= leading;
lineCount++;
previous = '\0';
text += bytes;
str += bytes;
continue;
}
@ -3424,29 +3453,23 @@ void lovrPassText(Pass* pass, Font* font, const char* text, uint32_t length, flo
// Vertices
float* bb = glyph->box;
float* uv = glyph->uv;
vertices[vertexCount++] = (GlyphVertex) { { x + bb[0], y + bb[3] }, { uv[0], uv[1] } };
vertices[vertexCount++] = (GlyphVertex) { { x + bb[2], y + bb[3] }, { uv[2], uv[1] } };
vertices[vertexCount++] = (GlyphVertex) { { x + bb[0], y + bb[1] }, { uv[0], uv[3] } };
vertices[vertexCount++] = (GlyphVertex) { { x + bb[2], y + bb[1] }, { uv[2], uv[3] } };
uint16_t* uv = glyph->uv;
vertices[vertexCount++] = (GlyphVertex) { { x + bb[0], y + bb[3] }, { uv[0], uv[1] }, { 255, 255, 255, 255 } };
vertices[vertexCount++] = (GlyphVertex) { { x + bb[2], y + bb[3] }, { uv[2], uv[1] }, { 255, 255, 255, 255 } };
vertices[vertexCount++] = (GlyphVertex) { { x + bb[0], y + bb[1] }, { uv[0], uv[3] }, { 255, 255, 255, 255 } };
vertices[vertexCount++] = (GlyphVertex) { { x + bb[2], y + bb[1] }, { uv[2], uv[3] }, { 255, 255, 255, 255 } };
glyphCount++;
// Advance
x += glyph->advance;
text += bytes;
str += bytes;
}
// Align last line
aline(vertices, lineStart, vertexCount, halign);
// Normalize UVs now that final atlas size is known
for (uint32_t i = 0; i < vertexCount; i++) {
vertices[i].uv.u /= font->atlasWidth;
vertices[i].uv.v /= font->atlasHeight;
}
// Resize atlas, rasterize new glyphs, upload them into atlas, recreate material
lovrFontUploadNewGlyphs(font, originalGlyphCount);
lovrFontUploadNewGlyphs(font, originalGlyphCount, text, length, vertices);
mat4_scale(transform, scale, scale, scale);
float totalHeight = height + leading * (lineCount - 1);

View File

@ -325,7 +325,6 @@ const MaterialInfo* lovrMaterialGetInfo(Material* material);
typedef struct {
struct Rasterizer* rasterizer;
uint32_t padding;
double spread;
} FontInfo;