More font rendering;

This commit is contained in:
bjorn 2017-02-05 20:30:17 -08:00
parent 02386af34f
commit f80e3e5a13
13 changed files with 180 additions and 142 deletions

View File

@ -10,22 +10,24 @@ Font* lovrFontCreate(FontData* fontData) {
Font* font = lovrAlloc(sizeof(Font), lovrFontDestroy);
if (!font) return NULL;
font->fontData = fontData;
vec_init(&font->vertices);
// Atlas
int padding = 1;
font->atlas.x = padding;
font->atlas.y = padding;
font->atlas.width = 64;
font->atlas.height = 64;
font->atlas.rowHeight = 0;
font->atlas.offsetX = 0;
font->atlas.extentX = 0;
font->atlas.extentY = 0;
font->atlas.padding = padding;
map_init(&font->atlas.glyphs);
font->fontData = fontData;
font->texture = NULL;
lovrFontCreateTexture(font);
vec_init(&font->vertices);
// Texture
TextureData* textureData = lovrTextureDataGetBlank(font->atlas.width, font->atlas.height, 0x0, FORMAT_RG);
font->texture = lovrTextureCreate(textureData);
lovrTextureSetWrap(font->texture, WRAP_CLAMP, WRAP_CLAMP);
int swizzle[4] = { GL_RED, GL_RED, GL_RED, GL_GREEN };
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
return font;
}
@ -39,40 +41,47 @@ void lovrFontDestroy(const Ref* ref) {
free(font);
}
void lovrFontDataDestroy(FontData* fontData) {
// TODO
free(fontData);
}
void lovrFontPrint(Font* font, const char* str) {
FontAtlas* atlas = &font->atlas;
vec_reserve(&font->vertices, strlen(str) * 30);
vec_clear(&font->vertices);
// Cursor
float x = 0;
float y = 0;
float u = atlas->width;
float v = atlas->height;
for (unsigned int i = 0; i < strlen(str); i++) {
if (str[i] == '\n') {
x = 0;
y -= font->fontData->size;
continue;
}
Glyph* glyph = lovrFontGetGlyph(font, str[i]);
if (glyph->w > 0 || glyph->h > 0) {
float s = glyph->x / (float) atlas->width;
float t = glyph->y / (float) atlas->height;
float w = glyph->w;
float h = glyph->h;
float s2 = (glyph->x + w) / (float) atlas->width;
float t2 = (glyph->y + h) / (float) atlas->height;
float ox = x + glyph->ox;
float oy = -y - glyph->oy;
int gx = glyph->x;
int gy = glyph->y;
int gw = glyph->w;
int gh = glyph->h;
if (gw > 0 && gh > 0) {
float x1 = x + glyph->dx;
float y1 = y + glyph->dy;
float x2 = x1 + gw;
float y2 = y1 - gh;
float s1 = gx / u;
float t1 = gy / v;
float s2 = (gx + gw) / u;
float t2 = (gy + gh) / v;
float v[30] = {
ox, -oy, 0, s, t,
ox, -oy - h, 0, s, t2,
ox + w, -oy, 0, s2, t,
ox + w, -oy, 0, s2, t,
ox, -oy - h, 0, s, t2,
ox + w, -oy - h, 0, s2, t2
x1, y1, 0, s1, t1,
x1, y2, 0, s1, t2,
x2, y1, 0, s2, t1,
x2, y1, 0, s2, t1,
x1, y2, 0, s1, t2,
x2, y2, 0, s2, t2
};
vec_pusharr(&font->vertices, v, 30);
@ -91,77 +100,74 @@ Glyph* lovrFontGetGlyph(Font* font, char character) {
vec_glyph_t* glyphs = &atlas->glyphs;
Glyph* glyph = map_get(glyphs, key);
// Add the glyph to the atlas if it isn't there
if (!glyph) {
// Load the new glyph and add it to the cache
Glyph g;
lovrFontDataLoadGlyph(font->fontData, character, &g);
map_set(glyphs, key, g);
glyph = map_get(glyphs, key);
// Exit early if the glyph is empty to save texture space
if (glyph->w == 0 && glyph->h == 0) {
return glyph;
}
// If the glyph does not fit, you must acquit
int tooWide = 0, tooTall = 0;
do {
// New row
if ((tooWide = atlas->x + glyph->w > atlas->width - 2 * atlas->padding) == 1) {
atlas->x = atlas->offsetX + atlas->padding;
atlas->y += atlas->rowHeight + atlas->padding;
atlas->rowHeight = 0;
}
// +---+ +---+---+ +---+---+
// | | -> | | | -> | | |
// +---+ +---+---+ +---+---+
// | |
// +-------+
if ((tooTall = atlas->y + glyph->h > atlas->height - 2 * atlas->padding) == 1) {
if (atlas->width == atlas->height) {
atlas->offsetX = atlas->extentX;
atlas->x = atlas->offsetX + atlas->padding;
atlas->y = atlas->padding;
atlas->rowHeight = 0;
atlas->width <<= 1;
} else {
atlas->extentX = 0;
atlas->offsetX = 0;
atlas->height <<= 1;
}
}
} while (tooWide || tooTall);
// Keep track of glyph position in atlas
glyph->x = atlas->x;
glyph->y = atlas->y;
// Paste glyph into texture
lovrGraphicsBindTexture(font->texture);
glTexSubImage2D(GL_TEXTURE_2D, 0, atlas->x, atlas->y, glyph->w, glyph->h, GL_RG, GL_UNSIGNED_BYTE, glyph->data);
// Advance atlas cursor
atlas->x += glyph->w + atlas->padding;
atlas->rowHeight = MAX(atlas->rowHeight, glyph->h);
atlas->extentX = MAX(atlas->extentX, atlas->x);
atlas->extentY = MAX(atlas->extentY, atlas->y + atlas->rowHeight);
lovrFontAddGlyph(font, glyph);
}
return glyph;
}
void lovrFontCreateTexture(Font* font) {
if (font->texture) {
lovrRelease(&font->texture->ref);
void lovrFontAddGlyph(Font* font, Glyph* glyph) {
FontAtlas* atlas = &font->atlas;
// Don't waste space on empty glyphs
if (glyph->w == 0 && glyph->h == 0) {
return;
}
FontAtlas* atlas = &font->atlas;
TextureData* textureData = lovrTextureDataGetBlank(atlas->width * 4, atlas->height * 4, 0x0, FORMAT_RG);
font->texture = lovrTextureCreate(textureData);
lovrTextureSetWrap(font->texture, WRAP_CLAMP, WRAP_CLAMP);
int swizzle[4] = { GL_RED, GL_RED, GL_RED, GL_GREEN };
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
// If the glyph does not fit, you must acquit (new row)
if (atlas->x + glyph->w > atlas->width - 2 * atlas->padding) {
atlas->x = atlas->padding;
atlas->y += atlas->rowHeight + atlas->padding;
atlas->rowHeight = 0;
}
// Expand the texture if needed. Expanding the texture re-adds all the glyphs, so we can return.
if (atlas->y + glyph->h > atlas->height - 2 * atlas->padding) {
if (atlas->width == atlas->height) {
atlas->width <<= 1;
} else {
atlas->height <<= 1;
}
lovrFontExpandTexture(font);
return;
}
// Keep track of glyph's position in the atlas
glyph->x = atlas->x;
glyph->y = atlas->y;
// Paste glyph into texture
lovrGraphicsBindTexture(font->texture);
glTexSubImage2D(GL_TEXTURE_2D, 0, atlas->x, atlas->y, glyph->w, glyph->h, GL_RG, GL_UNSIGNED_BYTE, glyph->data);
// Advance atlas cursor
atlas->x += glyph->w + atlas->padding;
atlas->rowHeight = MAX(atlas->rowHeight, glyph->h);
}
void lovrFontExpandTexture(Font* font) {
FontAtlas* atlas = &font->atlas;
// Resize the texture storage
lovrTextureDataResize(font->texture->textureData, atlas->width, atlas->height, 0x0);
lovrTextureRefresh(font->texture);
// Reset the cursor
atlas->x = atlas->padding;
atlas->y = atlas->padding;
atlas->rowHeight = 0;
// Re-pack all the glyphs
const char* key;
map_iter_t iter = map_iter(&atlas->glyphs);
while ((key = map_next(&atlas->glyphs, &iter)) != NULL) {
Glyph* glyph = map_get(&atlas->glyphs, key);
lovrFontAddGlyph(font, glyph);
}
}

View File

@ -8,15 +8,16 @@
typedef struct {
void* rasterizer;
int size;
} FontData;
typedef struct {
int x;
int y;
int ox;
int oy;
int w;
int h;
int dx;
int dy;
int advance;
uint8_t* data;
} Glyph;
@ -29,9 +30,6 @@ typedef struct {
int width;
int height;
int rowHeight;
int offsetX;
int extentX;
int extentY;
int padding;
vec_glyph_t glyphs;
} FontAtlas;
@ -46,7 +44,7 @@ typedef struct {
Font* lovrFontCreate(FontData* fontData);
void lovrFontDestroy(const Ref* ref);
void lovrFontDataDestroy(FontData* fontData);
void lovrFontPrint(Font* font, const char* str);
Glyph* lovrFontGetGlyph(Font* font, char character);
void lovrFontCreateTexture(Font* font);
void lovrFontAddGlyph(Font* font, Glyph* glyph);
void lovrFontExpandTexture(Font* font);

View File

@ -195,7 +195,10 @@ void lovrGraphicsBindTexture(Texture* texture) {
texture = state.defaultTexture;
}
lovrTextureBind(texture);
if (texture != state.activeTexture) {
state.activeTexture = texture;
glBindTexture(GL_TEXTURE_2D, texture->id);
}
}
mat4 lovrGraphicsGetProjection() {

View File

@ -50,6 +50,7 @@ typedef struct {
Shader* skyboxShader;
Shader* fullscreenShader;
Font* activeFont;
Texture* activeTexture;
Texture* defaultTexture;
float transforms[MAX_TRANSFORMS][16];
CanvasState* canvases[MAX_CANVASES];

View File

@ -19,14 +19,9 @@ Texture* lovrTextureCreate(TextureData* textureData) {
Texture* texture = lovrAlloc(sizeof(Texture), lovrTextureDestroy);
if (!texture) return NULL;
int w = textureData->width;
int h = textureData->height;
GLenum format = getGLFormat(textureData->format);
texture->textureData = textureData;
glGenTextures(1, &texture->id);
lovrTextureBind(texture);
glTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0, format, GL_UNSIGNED_BYTE, textureData->data);
lovrTextureRefresh(texture);
lovrTextureSetFilter(texture, FILTER_LINEAR, FILTER_LINEAR);
lovrTextureSetWrap(texture, WRAP_REPEAT, WRAP_REPEAT);
@ -104,10 +99,6 @@ void lovrTextureDataDestroy(TextureData* textureData) {
free(textureData);
}
void lovrTextureBind(Texture* texture) {
glBindTexture(GL_TEXTURE_2D, texture->id);
}
void lovrTextureBindFramebuffer(Texture* texture) {
if (!texture->framebuffer) {
error("Texture cannot be used as a canvas");
@ -154,6 +145,15 @@ void lovrTextureResolveMSAA(Texture* texture) {
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
void lovrTextureRefresh(Texture* texture) {
TextureData* textureData = texture->textureData;
int w = textureData->width;
int h = textureData->height;
GLenum format = getGLFormat(textureData->format);
lovrGraphicsBindTexture(texture);
glTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0, format, GL_UNSIGNED_BYTE, textureData->data);
}
int lovrTextureGetHeight(Texture* texture) {
return texture->textureData->height;
}
@ -170,7 +170,7 @@ void lovrTextureGetFilter(Texture* texture, FilterMode* min, FilterMode* mag) {
void lovrTextureSetFilter(Texture* texture, FilterMode min, FilterMode mag) {
texture->filterMin = min;
texture->filterMag = mag;
lovrTextureBind(texture);
lovrGraphicsBindTexture(texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag);
}
@ -183,7 +183,7 @@ void lovrTextureGetWrap(Texture* texture, WrapMode* horizontal, WrapMode* vertic
void lovrTextureSetWrap(Texture* texture, WrapMode horizontal, WrapMode vertical) {
texture->wrapHorizontal = horizontal;
texture->wrapVertical = vertical;
lovrTextureBind(texture);
lovrGraphicsBindTexture(texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, horizontal);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, vertical);
}

View File

@ -57,7 +57,6 @@ Texture* lovrTextureCreate(TextureData* textureData);
Texture* lovrTextureCreateWithFramebuffer(TextureData* textureData, TextureProjection projection, int msaa);
void lovrTextureDestroy(const Ref* ref);
void lovrTextureDataDestroy(TextureData* textureData);
void lovrTextureBind(Texture* texture);
void lovrTextureBindFramebuffer(Texture* texture);
void lovrTextureResolveMSAA(Texture* texture);
void lovrTextureRefresh(Texture* texture);

View File

@ -148,7 +148,7 @@ Headset* viveInit() {
viveRefreshControllers(vive);
TextureData* textureData = lovrTextureDataGetEmpty(vive->renderWidth, vive->renderHeight);
TextureData* textureData = lovrTextureDataGetEmpty(vive->renderWidth, vive->renderHeight, FORMAT_RGBA);
vive->texture = lovrTextureCreateWithFramebuffer(textureData, PROJECTION_PERSPECTIVE, 4);
return headset;

View File

@ -6,60 +6,71 @@
static FT_Library ft = NULL;
FontData* lovrFontDataCreate(void* data, int size) {
FontData* lovrFontDataCreate(void* data, int size, int height) {
if (!ft && FT_Init_FreeType(&ft)) {
error("Error initializing FreeType");
}
FontData* fontData = malloc(sizeof(FontData));
if (FT_New_Memory_Face(ft, data, size, 0, (FT_Face*)&fontData->rasterizer)) {
error("Error loading font");
}
fontData->size = height;
if (FT_Set_Pixel_Sizes(fontData->rasterizer, 0, 64)) {
error("Problem setting font size");
FT_Error err = FT_Err_Ok;
err = err || FT_New_Memory_Face(ft, data, size, 0, (FT_Face*)&fontData->rasterizer);
err = err || FT_Set_Pixel_Sizes(fontData->rasterizer, 0, height);
if (err) {
error("Problem loading font\n");
}
return fontData;
}
void lovrFontDataDestroy(FontData* fontData) {
FT_Done_Face(fontData->rasterizer);
free(fontData);
}
void lovrFontDataLoadGlyph(FontData* fontData, uint32_t character, Glyph* glyph) {
FT_Face face = fontData->rasterizer;
FT_Error err = FT_Err_Ok;
FT_Glyph ftGlyph;
FT_Bitmap ftBitmap;
FT_BitmapGlyph ftBitmapGlyph;
FT_Glyph slot;
FT_Bitmap bitmap;
FT_BitmapGlyph bmglyph;
FT_Glyph_Metrics* metrics;
err |= FT_Load_Glyph(face, FT_Get_Char_Index(face, character), FT_LOAD_DEFAULT);
err |= FT_Get_Glyph(face->glyph, &ftGlyph);
err |= FT_Glyph_To_Bitmap(&ftGlyph, FT_RENDER_MODE_NORMAL, 0, 1);
// FreeType stuff
err = err || FT_Load_Glyph(face, FT_Get_Char_Index(face, character), FT_LOAD_DEFAULT);
err = err || FT_Get_Glyph(face->glyph, &slot);
err = err || FT_Glyph_To_Bitmap(&slot, FT_RENDER_MODE_NORMAL, 0, 1);
if (err) {
error("Error loading glyph\n");
}
ftBitmapGlyph = (FT_BitmapGlyph) ftGlyph;
ftBitmap = ftBitmapGlyph->bitmap;
bmglyph = (FT_BitmapGlyph) slot;
bitmap = bmglyph->bitmap;
metrics = &face->glyph->metrics;
FT_Glyph_Metrics* metrics = &face->glyph->metrics;
// Initialize glyph
glyph->x = 0;
glyph->y = 0;
glyph->ox = metrics->horiBearingX >> 6;
glyph->oy = metrics->horiBearingY >> 6;
glyph->w = metrics->width >> 6;
glyph->h = metrics->height >> 6;
glyph->dx = metrics->horiBearingX >> 6;
glyph->dy = metrics->horiBearingY >> 6;
glyph->advance = metrics->horiAdvance >> 6;
glyph->data = malloc(2 * glyph->w * glyph->h);
glyph->data = malloc(glyph->w * glyph->h * 2 * sizeof(uint8_t));
// Transform data into an OpenGL-friendly format
int i = 0;
uint8_t* row = ftBitmap.buffer;
uint8_t* row = bitmap.buffer;
for (int y = 0; y < glyph->h; y++) {
for (int x = 0; x < glyph->w; x++) {
glyph->data[i++] = 0xff;
glyph->data[i++] = row[x];
}
row += ftBitmap.pitch;
row += bitmap.pitch;
}
FT_Done_Glyph(ftGlyph);
FT_Done_Glyph(slot);
}

View File

@ -1,5 +1,6 @@
#include "graphics/font.h"
#include <stdint.h>
FontData* lovrFontDataCreate(void* data, int size);
FontData* lovrFontDataCreate(void* data, int size, int height);
void lovrFontDataDestroy(FontData* fontData);
void lovrFontDataLoadGlyph(FontData* fontData, uint32_t characer, Glyph* glyph);

View File

@ -28,15 +28,23 @@ TextureData* lovrTextureDataGetBlank(int width, int height, uint8_t value, Textu
return textureData;
}
TextureData* lovrTextureDataGetEmpty(int width, int height) {
TextureData* lovrTextureDataGetEmpty(int width, int height, TextureFormat format) {
TextureData* textureData = malloc(sizeof(TextureData));
if (!textureData) return NULL;
int channels = 0;
switch (format) {
case FORMAT_RED: channels = 1; break;
case FORMAT_RG: channels = 2; break;
case FORMAT_RGB: channels = 3; break;
case FORMAT_RGBA: channels = 4; break;
}
textureData->data = NULL;
textureData->width = width;
textureData->height = height;
textureData->channels = 4;
textureData->format = FORMAT_RGBA;
textureData->channels = channels;
textureData->format = format;
return textureData;
}
@ -72,3 +80,11 @@ TextureData* lovrTextureDataFromOpenVRModel(OpenVRModel* vrModel) {
textureData->format = FORMAT_RGBA;
return textureData;
}
void lovrTextureDataResize(TextureData* textureData, int width, int height, uint8_t value) {
int size = sizeof(uint8_t) * width * height * textureData->channels;
textureData->width = width;
textureData->height = height;
textureData->data = realloc(textureData->data, size);
memset(textureData->data, value, size);
}

View File

@ -3,6 +3,7 @@
#include <stdint.h>
TextureData* lovrTextureDataGetBlank(int width, int height, uint8_t value, TextureFormat format);
TextureData* lovrTextureDataGetEmpty(int width, int height);
TextureData* lovrTextureDataGetEmpty(int width, int height, TextureFormat format);
TextureData* lovrTextureDataFromFile(void* data, int size);
TextureData* lovrTextureDataFromOpenVRModel(OpenVRModel* vrModel);
void lovrTextureDataResize(TextureData* textureData, int width, int height, uint8_t value);

View File

@ -625,13 +625,15 @@ int l_lovrGraphicsNewBuffer(lua_State* L) {
int l_lovrGraphicsNewFont(lua_State* L) {
const char* path = luaL_checkstring(L, 1);
//float size = luaL_optnumber(L, 2, 16);
float fontSize = luaL_optnumber(L, 2, 12);
int fileSize;
void* data = lovrFilesystemRead(path, &fileSize);
if (!data) {
luaL_error(L, "Could not load font '%s'", path);
}
FontData* fontData = lovrFontDataCreate(data, fileSize);
FontData* fontData = lovrFontDataCreate(data, fileSize, fontSize);
Font* font = lovrFontCreate(fontData);
luax_pushtype(L, Font, font);
return 1;
@ -725,7 +727,7 @@ int l_lovrGraphicsNewTexture(lua_State* L) {
int height = luaL_checknumber(L, 2);
TextureProjection* projection = luax_optenum(L, 3, "2d", &TextureProjections, "projection");
int msaa = luaL_optnumber(L, 4, 0);
TextureData* textureData = lovrTextureDataGetEmpty(width, height);
TextureData* textureData = lovrTextureDataGetEmpty(width, height, FORMAT_RGBA);
texture = lovrTextureCreateWithFramebuffer(textureData, *projection, msaa);
}

View File

@ -17,7 +17,7 @@ const luaL_Reg lovrTexture[] = {
int l_lovrTextureBind(lua_State* L) {
Texture* texture = luax_checktype(L, 1, Texture);
lovrTextureBind(texture);
lovrGraphicsBindTexture(texture);
return 0;
}