Improve MSDF font shader; Add spread/padding settings to Font;

This commit is contained in:
bjorn 2021-02-10 07:08:29 -07:00
parent 7b136b914c
commit 0919da4091
7 changed files with 77 additions and 36 deletions

View File

@ -1232,6 +1232,8 @@ static int l_lovrGraphicsNewCanvas(lua_State* L) {
static int l_lovrGraphicsNewFont(lua_State* L) {
Rasterizer* rasterizer = luax_totype(L, 1, Rasterizer);
uint32_t padding = 2;
double spread = 4.;
if (!rasterizer) {
Blob* blob = NULL;
@ -1239,16 +1241,23 @@ static int l_lovrGraphicsNewFont(lua_State* L) {
if (lua_type(L, 1) == LUA_TNUMBER || lua_isnoneornil(L, 1)) {
size = luaL_optinteger(L, 1, 32);
padding = luaL_optinteger(L, 2, padding);
spread = luaL_optnumber(L, 3, spread);
} else {
blob = luax_readblob(L, 1, "Font");
size = luaL_optinteger(L, 2, 32);
padding = luaL_optinteger(L, 3, padding);
spread = luaL_optnumber(L, 4, spread);
}
rasterizer = lovrRasterizerCreate(blob, size);
lovrRelease(blob, lovrBlobDestroy);
} else {
padding = luaL_optinteger(L, 2, padding);
spread = luaL_optnumber(L, 3, spread);
}
Font* font = lovrFontCreate(rasterizer);
Font* font = lovrFontCreate(rasterizer, padding, spread);
luax_pushtype(L, Font, font);
lovrRelease(rasterizer, lovrRasterizerDestroy);
lovrRelease(font, lovrFontDestroy);

View File

@ -99,10 +99,19 @@ bool lovrRasterizerHasGlyphs(Rasterizer* rasterizer, const char* str) {
return hasGlyphs;
}
void lovrRasterizerLoadGlyph(Rasterizer* rasterizer, uint32_t character, Glyph* glyph) {
void lovrRasterizerLoadGlyph(Rasterizer* rasterizer, uint32_t character, uint32_t padding, double spread, Glyph* glyph) {
int glyphIndex = stbtt_FindGlyphIndex(&rasterizer->font, character);
lovrAssert(glyphIndex, "No font glyph found for character code %d, try using Rasterizer:hasGlyphs", character);
int advance, bearing;
stbtt_GetGlyphHMetrics(&rasterizer->font, glyphIndex, &advance, &bearing);
if (stbtt_IsGlyphEmpty(&rasterizer->font, glyphIndex)) {
memset(glyph, 0, sizeof(Glyph));
glyph->advance = roundf(advance * rasterizer->scale);
return;
}
// Trace glyph outline
stbtt_vertex* vertices;
int vertexCount = stbtt_GetGlyphShape(&rasterizer->font, glyphIndex, &vertices);
@ -148,32 +157,27 @@ void lovrRasterizerLoadGlyph(Rasterizer* rasterizer, uint32_t character, Glyph*
stbtt_FreeShape(&rasterizer->font, vertices);
int advance, bearing;
stbtt_GetGlyphHMetrics(&rasterizer->font, glyphIndex, &advance, &bearing);
int x0, y0, x1, y1;
stbtt_GetGlyphBox(&rasterizer->font, glyphIndex, &x0, &y0, &x1, &y1);
bool empty = stbtt_IsGlyphEmpty(&rasterizer->font, glyphIndex);
// Initialize glyph data
glyph->x = 0;
glyph->y = 0;
glyph->w = empty ? 0 : ceilf((x1 - x0) * rasterizer->scale);
glyph->h = empty ? 0 : ceilf((y1 - y0) * rasterizer->scale);
glyph->tw = glyph->w + 2 * GLYPH_PADDING;
glyph->th = glyph->h + 2 * GLYPH_PADDING;
glyph->dx = empty ? 0 : roundf(bearing * rasterizer->scale);
glyph->dy = empty ? 0 : roundf(y1 * rasterizer->scale);
glyph->w = ceilf((x1 - x0) * rasterizer->scale);
glyph->h = ceilf((y1 - y0) * rasterizer->scale);
glyph->tw = glyph->w + 2 * padding;
glyph->th = glyph->h + 2 * padding;
glyph->dx = roundf(bearing * rasterizer->scale);
glyph->dy = roundf(y1 * rasterizer->scale);
glyph->advance = roundf(advance * rasterizer->scale);
glyph->data = lovrImageCreate(glyph->tw, glyph->th, NULL, 0, FORMAT_RGBA32F);
// Render SDF
float tx = GLYPH_PADDING + -glyph->dx;
float ty = GLYPH_PADDING + (float) glyph->h - glyph->dy;
float tx = (float) padding + -glyph->dx;
float ty = (float) padding + (float) glyph->h - glyph->dy;
msShapeNormalize(shape);
msEdgeColoringSimple(shape, 3., 0);
msGenerateMTSDF(glyph->data->blob->data, glyph->tw, glyph->th, shape, 4.f, 1.f, 1.f, tx, ty);
msGenerateMTSDF(glyph->data->blob->data, glyph->tw, glyph->th, shape, spread, 1.f, 1.f, tx, ty);
msShapeDestroy(shape);
}

View File

@ -3,8 +3,6 @@
#pragma once
#define GLYPH_PADDING 1
struct Blob;
struct Image;
@ -32,5 +30,5 @@ int lovrRasterizerGetAscent(Rasterizer* rasterizer);
int lovrRasterizerGetDescent(Rasterizer* rasterizer);
bool lovrRasterizerHasGlyph(Rasterizer* fontData, uint32_t character);
bool lovrRasterizerHasGlyphs(Rasterizer* fontData, const char* str);
void lovrRasterizerLoadGlyph(Rasterizer* fontData, uint32_t character, Glyph* glyph);
void lovrRasterizerLoadGlyph(Rasterizer* fontData, uint32_t character, uint32_t padding, double spread, Glyph* glyph);
int32_t lovrRasterizerGetKerning(Rasterizer* fontData, uint32_t left, uint32_t right);

View File

@ -23,6 +23,8 @@ struct Font {
Texture* texture;
FontAtlas atlas;
map_t kerning;
double spread;
uint32_t padding;
float lineHeight;
float pixelDensity;
bool flip;
@ -47,24 +49,28 @@ static void lovrFontAddGlyph(Font* font, Glyph* glyph);
static void lovrFontExpandTexture(Font* font);
static void lovrFontCreateTexture(Font* font);
Font* lovrFontCreate(Rasterizer* rasterizer) {
Font* lovrFontCreate(Rasterizer* rasterizer, uint32_t padding, double spread) {
Font* font = calloc(1, sizeof(Font));
lovrAssert(font, "Out of memory");
font->ref = 1;
lovrRetain(rasterizer);
font->rasterizer = rasterizer;
font->padding = padding;
font->spread = spread;
font->lineHeight = 1.f;
font->pixelDensity = (float) lovrRasterizerGetHeight(rasterizer);
map_init(&font->kerning, 0);
// Atlas
uint32_t padding = 1;
font->atlas.x = padding;
font->atlas.y = padding;
font->atlas.width = 128;
font->atlas.height = 128;
font->atlas.padding = padding;
// The atlas padding affects the padding of the edges of the atlas and the space between rows.
// It is different from the main font->padding, which is the padding on each individual glyph.
uint32_t atlasPadding = 1;
font->atlas.x = atlasPadding;
font->atlas.y = atlasPadding;
font->atlas.width = 256;
font->atlas.height = 256;
font->atlas.padding = atlasPadding;
arr_init(&font->atlas.glyphs, realloc);
map_init(&font->atlas.glyphMap, 0);
@ -160,8 +166,8 @@ void lovrFontRender(Font* font, const char* str, size_t length, float wrap, Hori
// Triangles
if (glyph->w > 0 && glyph->h > 0) {
float x1 = cx + glyph->dx - GLYPH_PADDING;
float y1 = cy + (glyph->dy + GLYPH_PADDING) * (flip ? -1.f : 1.f);
float x1 = cx + glyph->dx - font->padding;
float y1 = cy + (glyph->dy + font->padding) * (flip ? -1.f : 1.f);
float x2 = x1 + glyph->tw;
float y2 = y1 - glyph->th * (flip ? -1.f : 1.f);
float s1 = glyph->x / u;
@ -236,6 +242,14 @@ void lovrFontMeasure(Font* font, const char* str, size_t length, float wrap, flo
*height = ((*lineCount + 1) * lovrRasterizerGetHeight(font->rasterizer) * font->lineHeight) * (font->flip ? -1 : 1);
}
uint32_t lovrFontGetPadding(Font* font) {
return font->padding;
}
double lovrFontGetSpread(Font* font) {
return font->spread;
}
float lovrFontGetHeight(Font* font) {
return lovrRasterizerGetHeight(font->rasterizer) / font->pixelDensity;
}
@ -302,7 +316,7 @@ static Glyph* lovrFontGetGlyph(Font* font, uint32_t codepoint) {
if (index == MAP_NIL) {
index = atlas->glyphs.length;
arr_reserve(&atlas->glyphs, atlas->glyphs.length + 1);
lovrRasterizerLoadGlyph(font->rasterizer, codepoint, &atlas->glyphs.data[atlas->glyphs.length++]);
lovrRasterizerLoadGlyph(font->rasterizer, codepoint, font->padding, font->spread, &atlas->glyphs.data[atlas->glyphs.length++]);
map_set(&atlas->glyphMap, hash, index);
lovrFontAddGlyph(font, &atlas->glyphs.data[index]);
}
@ -374,7 +388,7 @@ static void lovrFontExpandTexture(Font* font) {
// Could look into using glClearTexImage when supported to make this more efficient.
static void lovrFontCreateTexture(Font* font) {
lovrRelease(font->texture, lovrTextureDestroy);
Image* image = lovrImageCreate(font->atlas.width, font->atlas.height, NULL, 0x0, FORMAT_RGB);
Image* image = lovrImageCreate(font->atlas.width, font->atlas.height, NULL, 0x0, FORMAT_RG11B10F);
font->texture = lovrTextureCreate(TEXTURE_2D, &image, 1, false, false, 0);
lovrTextureSetFilter(font->texture, (TextureFilter) { .mode = FILTER_BILINEAR });
lovrTextureSetWrap(font->texture, (TextureWrap) { .s = WRAP_CLAMP, .t = WRAP_CLAMP });

View File

@ -20,12 +20,14 @@ typedef enum {
} VerticalAlign;
typedef struct Font Font;
Font* lovrFontCreate(struct Rasterizer* rasterizer);
Font* lovrFontCreate(struct Rasterizer* rasterizer, uint32_t padding, double spread);
void lovrFontDestroy(void* ref);
struct Rasterizer* lovrFontGetRasterizer(Font* font);
struct Texture* lovrFontGetTexture(Font* font);
void lovrFontRender(Font* font, const char* str, size_t length, float wrap, HorizontalAlign halign, float* vertices, uint16_t* indices, uint16_t baseVertex);
void lovrFontMeasure(Font* font, const char* string, size_t length, float wrap, float* width, float* height, uint32_t* lineCount, uint32_t* glyphCount);
uint32_t lovrFontGetPadding(Font* font);
double lovrFontGetSpread(Font* font);
float lovrFontGetHeight(Font* font);
float lovrFontGetAscent(Font* font);
float lovrFontGetDescent(Font* font);

View File

@ -51,6 +51,7 @@ typedef union {
struct { DrawStyle style; ArcMode mode; float r1; float r2; int segments; } arc;
struct { float r1; float r2; bool capped; int segments; } cylinder;
struct { int segments; } sphere;
struct { float spread; } text;
struct { float u; float v; float w; float h; } fill;
struct { uint32_t rangeStart; uint32_t rangeCount; uint32_t instances; float* pose; } mesh;
} BatchParams;
@ -467,7 +468,7 @@ Font* lovrGraphicsGetFont() {
if (!state.font) {
if (!state.defaultFont) {
Rasterizer* rasterizer = lovrRasterizerCreate(NULL, 32);
state.defaultFont = lovrFontCreate(rasterizer);
state.defaultFont = lovrFontCreate(rasterizer, 1, 3.);
lovrRelease(rasterizer, lovrRasterizerDestroy);
}
@ -757,6 +758,13 @@ void lovrGraphicsFlush() {
lovrShaderSetBlock(batch->draw.shader, "lovrModelBlock", state.buffers[STREAM_MODEL], batch->drawStart * bufferStride[STREAM_MODEL], MAX_DRAWS * bufferStride[STREAM_MODEL], ACCESS_READ);
lovrShaderSetBlock(batch->draw.shader, "lovrColorBlock", state.buffers[STREAM_COLOR], batch->drawStart * bufferStride[STREAM_COLOR], MAX_DRAWS * bufferStride[STREAM_COLOR], ACCESS_READ);
lovrShaderSetBlock(batch->draw.shader, "lovrFrameBlock", state.buffers[STREAM_FRAME], (state.head[STREAM_FRAME] - 1) * bufferStride[STREAM_FRAME], bufferStride[STREAM_FRAME], ACCESS_READ);
if (batch->type == BATCH_TEXT) {
Texture* texture = lovrMaterialGetTexture(batch->material, TEXTURE_DIFFUSE);
uint32_t width = lovrTextureGetWidth(texture, 0);
uint32_t height = lovrTextureGetHeight(texture, 0);
float range[2] = { batch->params.text.spread / width, batch->params.text.spread / height };
lovrShaderSetFloats(batch->draw.shader, "lovrSdfRange", range, 0, 2);
}
if (batch->draw.topology == DRAW_POINTS) {
lovrShaderSetFloats(batch->draw.shader, "lovrPointSize", &state.pointSize, 0, 1);
}
@ -1277,6 +1285,7 @@ void lovrGraphicsPrint(const char* str, size_t length, mat4 transform, float wra
uint16_t baseVertex;
lovrGraphicsBatch(&(BatchRequest) {
.type = BATCH_TEXT,
.params.text.spread = lovrFontGetSpread(font),
.topology = DRAW_TRIANGLES,
.shader = SHADER_FONT,
.pipeline = &pipeline,

View File

@ -344,14 +344,19 @@ const char* lovrPanoFragmentShader = ""
"}";
const char* lovrFontFragmentShader = ""
"uniform vec2 lovrSdfRange; \n"
"float screenPxRange() { \n"
" vec2 screenTexSize = vec2(1.) / fwidth(lovrTexCoord); \n"
" return max(.5 * dot(lovrSdfRange, screenTexSize), 1.); \n"
"} \n"
"float median(float r, float g, float b) { \n"
" return max(min(r, g), min(max(r, g), b)); \n"
"} \n"
"vec4 color(vec4 graphicsColor, sampler2D image, vec2 uv) { \n"
" vec3 col = texture(lovrDiffuseTexture, lovrTexCoord).rgb; \n"
" float sdf = median(col.r, col.g, col.b); \n"
" float w = fwidth(sdf); \n"
" float alpha = smoothstep(.5 - w, .5 + w, sdf); \n"
" vec3 msd = texture(lovrDiffuseTexture, lovrTexCoord).rgb; \n"
" float sd = median(msd.r, msd.g, msd.b); \n"
" float screenPxDistance = screenPxRange() * (sd - .5); \n"
" float alpha = clamp(screenPxDistance + .5, 0., 1.); \n"
" if (alpha <= 0.0) discard; \n"
" return vec4(lovrGraphicsColor.rgb, lovrGraphicsColor.a * alpha); \n"
"}";