Rudimentary font rasterisation at runtime with STB

This commit is contained in:
2016-06-29 03:17:03 +10:00
parent 76d6dfcf2f
commit e03d3fd14a
11 changed files with 253 additions and 25 deletions
+181 -2
View File
@@ -2,6 +2,10 @@
#define STB_IMAGE_IMPLEMENTATION
#include <STB/stb_image.h>
#define SBTT_STATIC
#define STB_TRUETYPE_IMPLEMENTATION
#include <STB/stb_truetype.h>
#include "Dengine/Platform.h"
#include "Dengine/AssetManager.h"
@@ -56,7 +60,7 @@ Shader *asset_getShader(const enum ShaderList type)
INTERNAL GLuint createShaderFromPath(const char *const path, GLuint shadertype)
{
PlatformFileReadResult file = {0};
PlatformFileRead file = {0};
i32 status = platform_readFileToBuffer(path, &file);
if (status)
@@ -77,7 +81,7 @@ INTERNAL GLuint createShaderFromPath(const char *const path, GLuint shadertype)
printf("glCompileShader() failed: %s\n", infoLog);
}
platform_closeFileReadResult(&file);
platform_closeFileRead(&file);
return result;
}
@@ -98,3 +102,178 @@ const i32 asset_loadShaderFiles(const char *const vertexPath,
assetManager.shaders[type] = shader;
return 0;
}
const i32 asset_loadTTFont(const char *filePath)
{
PlatformFileRead fontFileRead = {0};
platform_readFileToBuffer(filePath, &fontFileRead);
stbtt_fontinfo fontInfo = {0};
stbtt_InitFont(&fontInfo, fontFileRead.buffer,
stbtt_GetFontOffsetForIndex(fontFileRead.buffer, 0));
const v2i codepointRange = V2i(32, 127);
const i32 numGlyphs = codepointRange.y - codepointRange.x;
i32 glyphIndex = 0;
GlyphBitmap *glyphBitmaps =
CAST(GlyphBitmap *) calloc(numGlyphs, sizeof(GlyphBitmap));
v2i largestGlyphDimension = V2i(0, 0);
f32 targetFontHeight = 64.0f;
f32 scaleY = stbtt_ScaleForPixelHeight(&fontInfo, targetFontHeight);
/* Use STB_TrueType to generate a series of bitmap characters */
for (i32 codepoint = codepointRange.x; codepoint < codepointRange.y;
codepoint++)
{
// NOTE(doyle): ScaleX if not specified, is then calculated based on the
// ScaleY component
i32 width, height, xOffset, yOffset;
u8 *monoBitmap =
stbtt_GetCodepointBitmap(&fontInfo, 0, scaleY, codepoint, &width,
&height, &xOffset, &yOffset);
u8 *source = monoBitmap;
u32 *colorBitmap = calloc(width * height, sizeof(u32));
u32 *dest = colorBitmap;
// NOTE(doyle): STB generates 1 byte per pixel bitmaps, we use 4bpp, so
// duplicate the alpha byte (essentially) for all RGBA components
for (i32 y = 0; y < height; y++)
{
for (i32 x = 0; x < width; x++)
{
u8 monoByte = *source++;
*dest++ = (monoByte << 24) | (monoByte << 16) |
(monoByte << 8) | (monoByte << 0);
}
}
stbtt_FreeBitmap(monoBitmap, NULL);
glyphBitmaps[glyphIndex].dimensions = V2i(width, height);
glyphBitmaps[glyphIndex++].pixels = colorBitmap;
if (height > largestGlyphDimension.h)
largestGlyphDimension.h = height;
if (width > largestGlyphDimension.w)
largestGlyphDimension.w = width;
#ifdef WT_DEBUG
if ((largestGlyphDimension.h - CAST(i32)targetFontHeight) >= 50)
{
printf(
"asset_loadTTFont() warning: The loaded font file has a glyph "
"considerably larger than our target .. font packing is "
"unoptimal\n");
}
#endif
}
/*
* NOTE(doyle): Use rasterised TTF bitmap-characters combine them all into
* one bitmap as an atlas. We determine how many glyphs we can fit per row
* by determining the largest glyph size we have. Rounding it to the nearest
* multiple of 2 and dividing by our target texture size.
*
* For the amount of glyphs we fit per row, we iterate through them and
* write each row of the glyph adjacent to the next until we finish writing
* all pixels for the glyphs, then move onto the next set of glyphs.
*/
if ((largestGlyphDimension.w & 1) == 1)
largestGlyphDimension.w += 1;
if ((largestGlyphDimension.h & 1) == 1)
largestGlyphDimension.h += 1;
i32 glyphsPerRow = MAX_TEXTURE_SIZE / largestGlyphDimension.w;
#ifdef WT_DEBUG
i32 glyphsPerCol = MAX_TEXTURE_SIZE / largestGlyphDimension.h;
if ((glyphsPerRow * glyphsPerCol) <= numGlyphs)
{
printf(
"asset_loadTTFont() warning: The target font height creates a "
"glyph sheet that exceeds the available space!");
ASSERT(1);
}
#endif
u32 *fontBitmap = (u32 *)calloc(
squared(TARGET_TEXTURE_SIZE) * TARGET_BYTES_PER_PIXEL, sizeof(u32));
i32 pitch = MAX_TEXTURE_SIZE * TARGET_BYTES_PER_PIXEL;
// Check value to determine when a row of glyphs is completely printed
i32 verticalPixelsBlitted = 0;
i32 startingGlyphIndex = 0;
i32 glyphsRemaining = numGlyphs;
i32 glyphsOnCurrRow = glyphsPerRow;
for (i32 row = 0; row < MAX_TEXTURE_SIZE; row++)
{
u32 *destRow = fontBitmap + (row * MAX_TEXTURE_SIZE);
for (i32 glyphIndex = 0; glyphIndex < glyphsOnCurrRow;
glyphIndex++)
{
i32 activeGlyphIndex = startingGlyphIndex + glyphIndex;
GlyphBitmap activeGlyph = glyphBitmaps[activeGlyphIndex];
i32 numPixelsToPad = largestGlyphDimension.w;
/* Copy over exactly one row of pixels */
if (verticalPixelsBlitted < activeGlyph.dimensions.h)
{
const i32 srcPitch =
activeGlyph.dimensions.w * verticalPixelsBlitted;
const u32 *src = activeGlyph.pixels + srcPitch;
const i32 numPixelsToCopy = activeGlyph.dimensions.w;
for (i32 count = 0; count < numPixelsToCopy; count++)
*destRow++ = *src++;
/*
* NOTE(doyle): If the glyph is smaller than largest glyph
* available size, don't advance src pointer any further
* (NULL/mixes up rows), instead just advance the final bitmap
* pointer by the remaining distance
*/
numPixelsToPad =
largestGlyphDimension.w - activeGlyph.dimensions.w;
}
destRow += numPixelsToPad;
}
/* A row of glyphs has been fully formed on the atlas */
if (verticalPixelsBlitted++ >= largestGlyphDimension.h)
{
verticalPixelsBlitted = 0;
startingGlyphIndex += glyphsPerRow;
glyphsRemaining -= glyphsPerRow;
if (glyphsRemaining <= 0)
break;
else if (glyphsRemaining <= glyphsPerRow)
{
// NOTE(doyle): This allows us to modify the glyph iterator to
// prevent over-running of the available glyphs
glyphsOnCurrRow = glyphsRemaining;
}
}
}
Texture tex =
genTexture(MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE, 4, (u8 *)fontBitmap);
assetManager.textures[texlist_font] = tex;
for (i32 i = 0; i < numGlyphs; i++)
free(glyphBitmaps[i].pixels);
free(glyphBitmaps);
platform_closeFileRead(&fontFileRead);
return 0;
}