Compare commits

...

3 Commits

Author SHA1 Message Date
bjorn 9d84d3907b Font texture is u8;
Originally we made the font texture f16 due to "clamping" of the
distance field, and kept it as floats (but f32 since conversion isn't
automatic with Vulkan) here.  However, clamping isn't really an issue.
You can increase the spread of the font to literally get a wider spread
of the SDF for glows, etc.  Switching to u8 uses 4x less texture memory,
which is significant.
2022-06-26 20:57:57 -07:00
bjorn bac57dc0d2 Add stack allocation to temp allocator;
It can be used to push the current cursor onto the stack, perform some
tmep allocations, and then pop the stack to "free" them all at once.
This can be nice if you're doing some temporary allocations that aren't
going to be needed when the function returns, since it reduces the
amount of allocator growth a bit.

This allocator is meant to be threadlocal eventually, so there are no
thread-safety concerns.
2022-06-26 20:53:12 -07:00
bjorn cbe24f482f 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.
2022-06-26 20:28:30 -07:00
6 changed files with 107 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;
@ -317,6 +319,8 @@ static struct {
static void* tempAlloc(size_t size);
static void* tempGrow(void* p, size_t size);
static uint32_t tempPush(void);
static void tempPop(uint32_t stack);
static void beginFrame(void);
static void cleanupPasses(void);
static uint32_t getLayout(gpu_slot* slots, uint32_t count);
@ -477,8 +481,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,7 +648,6 @@ Font* lovrGraphicsGetDefaultFont() {
if (!state.defaultFont) {
state.defaultFont = lovrFontCreate(&(FontInfo) {
.rasterizer = lovrRasterizerCreate(NULL, 32),
.padding = 1,
.spread = 4.
});
}
@ -1787,6 +1790,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 +1842,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 +1850,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 +1871,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,17 +1884,18 @@ 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!");
Texture* atlas = lovrTextureCreate(&(TextureInfo) {
.type = TEXTURE_2D,
.format = FORMAT_RGBA32F,
.format = FORMAT_RGBA8,
.width = font->atlasWidth,
.height = font->atlasHeight,
.depth = 1,
@ -1931,6 +1928,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 +1936,72 @@ 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);
uint32_t stack = tempPush();
float* pixels = tempAlloc(w * h * 4 * sizeof(float));
lovrRasterizerGetGlyphPixels(font->info.rasterizer, glyph->codepoint, pixels, w, h, font->info.spread);
uint8_t* dst = gpu_map(scratchpad, w * h * 4 * sizeof(uint8_t), 4, GPU_MAP_WRITE);
float* src = pixels;
for (uint32_t y = 0; y < h; y++) {
for (uint32_t x = 0; x < w; x++) {
for (uint32_t c = 0; c < 4; c++) {
float f = *src++; // CLAMP evaluates multiple times
*dst++ = (uint8_t) (CLAMP(f, 0.f, 1.f) * 255.f + .5f);
}
}
}
tempPop(stack);
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 +3419,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 +3437,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 +3472,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);
@ -3741,6 +3783,14 @@ static void* tempGrow(void* p, size_t size) {
return memcpy(new, p, size >> 1);
}
static uint32_t tempPush(void) {
return state.allocator.cursor;
}
static void tempPop(uint32_t stack) {
state.allocator.cursor = stack;
}
static void beginFrame(void) {
if (state.active) {
return;

View File

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