diff --git a/src/graphics/font.c b/src/graphics/font.c index 44267ac4..3cf631a0 100644 --- a/src/graphics/font.c +++ b/src/graphics/font.c @@ -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); + } } diff --git a/src/graphics/font.h b/src/graphics/font.h index ab944c06..7a1d4928 100644 --- a/src/graphics/font.h +++ b/src/graphics/font.h @@ -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); diff --git a/src/graphics/graphics.c b/src/graphics/graphics.c index 31adfe77..84bb9f9c 100644 --- a/src/graphics/graphics.c +++ b/src/graphics/graphics.c @@ -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() { diff --git a/src/graphics/graphics.h b/src/graphics/graphics.h index a7a36fce..bb1d5202 100644 --- a/src/graphics/graphics.h +++ b/src/graphics/graphics.h @@ -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]; diff --git a/src/graphics/texture.c b/src/graphics/texture.c index 6e7b2b96..fdc29a1e 100644 --- a/src/graphics/texture.c +++ b/src/graphics/texture.c @@ -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); } diff --git a/src/graphics/texture.h b/src/graphics/texture.h index a6695cec..c4ee7865 100644 --- a/src/graphics/texture.h +++ b/src/graphics/texture.h @@ -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); diff --git a/src/headset/vive.c b/src/headset/vive.c index be4de5f6..910be794 100644 --- a/src/headset/vive.c +++ b/src/headset/vive.c @@ -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; diff --git a/src/loaders/font.c b/src/loaders/font.c index 12d627d9..83619b5b 100644 --- a/src/loaders/font.c +++ b/src/loaders/font.c @@ -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); } diff --git a/src/loaders/font.h b/src/loaders/font.h index 160e50dc..820fb45e 100644 --- a/src/loaders/font.h +++ b/src/loaders/font.h @@ -1,5 +1,6 @@ #include "graphics/font.h" #include -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); diff --git a/src/loaders/texture.c b/src/loaders/texture.c index 123598d1..a4bdee65 100644 --- a/src/loaders/texture.c +++ b/src/loaders/texture.c @@ -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); +} diff --git a/src/loaders/texture.h b/src/loaders/texture.h index 2eabb6e1..a30deadd 100644 --- a/src/loaders/texture.h +++ b/src/loaders/texture.h @@ -3,6 +3,7 @@ #include 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); diff --git a/src/lovr/graphics.c b/src/lovr/graphics.c index 5a4440a4..50ae0378 100644 --- a/src/lovr/graphics.c +++ b/src/lovr/graphics.c @@ -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); } diff --git a/src/lovr/types/texture.c b/src/lovr/types/texture.c index 69d2190e..1baa5ae2 100644 --- a/src/lovr/types/texture.c +++ b/src/lovr/types/texture.c @@ -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; }