Rudimentary font rasterisation at runtime with STB

This commit is contained in:
Doyle Thai 2016-06-29 03:17:03 +10:00
parent 76d6dfcf2f
commit e03d3fd14a
11 changed files with 253 additions and 25 deletions

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;
}

View File

@ -1,7 +1,7 @@
#include "Dengine/Platform.h"
i32 platform_readFileToBuffer(const char *const filePath,
PlatformFileReadResult *file)
PlatformFileRead *file)
{
HANDLE fileHandle = CreateFile(filePath, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_ALWAYS, 0, NULL);

View File

@ -2,23 +2,23 @@
enum BytesPerPixel
{
Greyscale = 1,
GreyscaleAlpha = 2,
RGB = 3,
RGBA = 4,
bytesPerPixel_Greyscale = 1,
bytesPerPixel_GreyscaleAlpha = 2,
bytesPerPixel_RGB = 3,
bytesPerPixel_RGBA = 4,
};
INTERNAL GLint getGLFormat(i32 bytesPerPixel, b32 srgb)
{
switch (bytesPerPixel)
{
case Greyscale:
case bytesPerPixel_Greyscale:
return GL_LUMINANCE;
case GreyscaleAlpha:
case bytesPerPixel_GreyscaleAlpha:
return GL_LUMINANCE_ALPHA;
case RGB:
case bytesPerPixel_RGB:
return (srgb ? GL_SRGB : GL_RGB);
case RGBA:
case bytesPerPixel_RGBA:
return (srgb ? GL_SRGB_ALPHA : GL_RGBA);
default:
// TODO(doyle): Invalid
@ -45,13 +45,14 @@ Texture genTexture(const GLuint width, const GLuint height,
glGenTextures(1, &tex.id);
glCheckError();
glBindTexture(GL_TEXTURE_2D, tex.id);
glCheckError();
/* Load image into texture */
// TODO(doyle) Figure out the gl format
tex.imageFormat = getGLFormat(bytesPerPixel, FALSE);
ASSERT(tex.imageFormat == GL_RGBA);
glCheckError();
glTexImage2D(GL_TEXTURE_2D, 0, tex.internalFormat, tex.width, tex.height, 0,

View File

@ -6,8 +6,8 @@
//choose to load assets outside of WorldTraveller!
#include <stdlib.h>
void updateBufferObject(Renderer *const renderer, RenderQuad *const quads,
const i32 numQuads)
INTERNAL void updateBufferObject(Renderer *const renderer,
RenderQuad *const quads, const i32 numQuads)
{
// TODO(doyle): We assume that vbo and vao are assigned
const i32 numVertexesInQuad = 4;
@ -34,6 +34,8 @@ void worldTraveller_gameInit(GameState *state)
asset_loadShaderFiles("data/shaders/sprite.vert.glsl",
"data/shaders/sprite.frag.glsl", shaderlist_sprite);
asset_loadTTFont("C:/Windows/Fonts/Arial.ttf");
glCheckError();
state->state = state_active;
@ -334,6 +336,14 @@ void worldTraveller_gameUpdateAndRender(GameState *state, const f32 dt)
renderer_object(&state->renderer, V2(0.0f, 0.0f), screenSize, 0.0f,
V3(0, 0, 0), worldTex);
Texture *font = asset_getTexture(texlist_font);
v4 fontTexRect = V4(0.0f, 1.0f, 1.0f, 0.0);
RenderQuad fontQuad = renderer_createDefaultQuad(fontTexRect);
updateBufferObject(&state->renderer, &fontQuad, 1);
renderer_object(&state->renderer, V2(128.0f, 128.0f),
V2(CAST(f32)font->width, CAST(f32)font->height), 0.0f,
V3(0, 0, 0), font);
// NOTE(doyle): Factor to normalise sprite sheet rect coords to -1, 1
Entity *const hero = &state->entityList[state->heroIndex];
texNdcFactor = 1.0f / CAST(f32) hero->tex->width;

View File

@ -35,7 +35,7 @@ int main()
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
v2 windowSize = V2(1280.0f, 720.0f);
v2 windowSize = V2(1600.0f, 900.0f);
GLFWwindow *window = glfwCreateWindow(
CAST(i32) windowSize.x, CAST(i32) windowSize.y, "Dengine", NULL, NULL);

View File

@ -4,10 +4,13 @@
#include "Dengine/Shader.h"
#include "Dengine/Texture.h"
#define MAX_TEXTURE_SIZE 1024
enum TexList
{
texlist_hero,
texlist_terrain,
texlist_font,
texlist_count,
};
@ -30,12 +33,19 @@ typedef struct TexAtlas
} TexAtlas;
typedef struct GlyphBitmap
{
v2i dimensions;
u32 *pixels;
} GlyphBitmap;
// TODO(doyle): Switch to hash based lookup
typedef struct AssetManager
{
Texture textures[256];
TexAtlas texAtlas[256];
Shader shaders[256];
Texture font;
} AssetManager;
extern AssetManager assetManager;
@ -52,4 +62,5 @@ const i32 asset_loadShaderFiles(const char *const vertexPath,
const char *const fragmentPath,
const enum ShaderList type);
const i32 asset_loadTTFont(const char *filePath);
#endif

View File

@ -3,6 +3,8 @@
#include <stdint.h>
#define WT_DEBUG
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;

View File

@ -9,6 +9,13 @@
#define absolute(x) ((x) > 0 ? (x) : -(x))
/* VECTORS */
typedef union v2i
{
struct { i32 x, y; };
struct { i32 w, h; };
i32 e[2];
} v2i;
typedef union v2
{
struct { f32 x, y; };
@ -30,6 +37,11 @@ typedef union v4
f32 e[4];
} v4;
INTERNAL inline v2i V2i(const i32 x, const i32 y)
{
v2i result = {x, y};
return result;
}
INTERNAL inline v2 V2(const f32 x, const f32 y)
{
v2 result = {x, y};
@ -63,7 +75,7 @@ INTERNAL inline v4 V4(const f32 x, const f32 y, const f32 z, const f32 w)
This is repeated for v3 and v4 for the basic math operations
*/
#define DEFINE_VECTOR_MATH(num) \
#define DEFINE_VECTOR_FLOAT_MATH(num) \
INTERNAL inline v##num v##num##_add(const v##num a, const v##num b) \
{ \
v##num result; \
@ -109,9 +121,19 @@ INTERNAL inline b32 v##num##_equals(const v##num a, const v##num b) \
return result; \
} \
DEFINE_VECTOR_MATH(2);
DEFINE_VECTOR_MATH(3);
DEFINE_VECTOR_MATH(4);
DEFINE_VECTOR_FLOAT_MATH(2);
DEFINE_VECTOR_FLOAT_MATH(3);
DEFINE_VECTOR_FLOAT_MATH(4);
#define DEFINE_VECTOR_INT_MATH(num) \
INTERNAL inline v##num##i v##num##i_add(const v##num##i a, const v##num##i b) \
{ \
v##num##i result; \
for (i32 i = 0; i < ##num; i++) { result.e[i] = a.e[i] + b.e[i]; } \
return result; \
} \
DEFINE_VECTOR_INT_MATH(2);
INTERNAL inline v3 v3_cross(const v3 a, const v3 b)
{

View File

@ -10,12 +10,12 @@ typedef struct
{
void *buffer;
i32 size;
} PlatformFileReadResult;
} PlatformFileRead;
i32 platform_readFileToBuffer(const char *const filePath,
PlatformFileReadResult *file);
PlatformFileRead *file);
inline void platform_closeFileReadResult(PlatformFileReadResult *file)
inline void platform_closeFileRead(PlatformFileRead *file)
{
if (file->buffer)
{

View File

@ -4,6 +4,9 @@
#include "Dengine/Common.h"
#include "Dengine/OpenGL.h"
#define TARGET_TEXTURE_SIZE 1024
#define TARGET_BYTES_PER_PIXEL 4
typedef struct Texture
{
// Holds the ID of the texture object, used for all texture operations to

View File

@ -1,9 +1,9 @@
#ifndef WORLDTRAVELLER_GAME_H
#define WORLDTRAVELLER_GAME_H
#include <Dengine/Common.h>
#include <Dengine/Entity.h>
#include <Dengine/Renderer.h>
#include "Dengine/Common.h"
#include "Dengine/Entity.h"
#include "Dengine/Renderer.h"
#define NUM_KEYS 1024
#define METERS_TO_PIXEL 100