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 #define STB_IMAGE_IMPLEMENTATION
#include <STB/stb_image.h> #include <STB/stb_image.h>
#define SBTT_STATIC
#define STB_TRUETYPE_IMPLEMENTATION
#include <STB/stb_truetype.h>
#include "Dengine/Platform.h" #include "Dengine/Platform.h"
#include "Dengine/AssetManager.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) INTERNAL GLuint createShaderFromPath(const char *const path, GLuint shadertype)
{ {
PlatformFileReadResult file = {0}; PlatformFileRead file = {0};
i32 status = platform_readFileToBuffer(path, &file); i32 status = platform_readFileToBuffer(path, &file);
if (status) if (status)
@ -77,7 +81,7 @@ INTERNAL GLuint createShaderFromPath(const char *const path, GLuint shadertype)
printf("glCompileShader() failed: %s\n", infoLog); printf("glCompileShader() failed: %s\n", infoLog);
} }
platform_closeFileReadResult(&file); platform_closeFileRead(&file);
return result; return result;
} }
@ -98,3 +102,178 @@ const i32 asset_loadShaderFiles(const char *const vertexPath,
assetManager.shaders[type] = shader; assetManager.shaders[type] = shader;
return 0; 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" #include "Dengine/Platform.h"
i32 platform_readFileToBuffer(const char *const filePath, i32 platform_readFileToBuffer(const char *const filePath,
PlatformFileReadResult *file) PlatformFileRead *file)
{ {
HANDLE fileHandle = CreateFile(filePath, GENERIC_READ, FILE_SHARE_READ, HANDLE fileHandle = CreateFile(filePath, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_ALWAYS, 0, NULL); NULL, OPEN_ALWAYS, 0, NULL);

View File

@ -2,23 +2,23 @@
enum BytesPerPixel enum BytesPerPixel
{ {
Greyscale = 1, bytesPerPixel_Greyscale = 1,
GreyscaleAlpha = 2, bytesPerPixel_GreyscaleAlpha = 2,
RGB = 3, bytesPerPixel_RGB = 3,
RGBA = 4, bytesPerPixel_RGBA = 4,
}; };
INTERNAL GLint getGLFormat(i32 bytesPerPixel, b32 srgb) INTERNAL GLint getGLFormat(i32 bytesPerPixel, b32 srgb)
{ {
switch (bytesPerPixel) switch (bytesPerPixel)
{ {
case Greyscale: case bytesPerPixel_Greyscale:
return GL_LUMINANCE; return GL_LUMINANCE;
case GreyscaleAlpha: case bytesPerPixel_GreyscaleAlpha:
return GL_LUMINANCE_ALPHA; return GL_LUMINANCE_ALPHA;
case RGB: case bytesPerPixel_RGB:
return (srgb ? GL_SRGB : GL_RGB); return (srgb ? GL_SRGB : GL_RGB);
case RGBA: case bytesPerPixel_RGBA:
return (srgb ? GL_SRGB_ALPHA : GL_RGBA); return (srgb ? GL_SRGB_ALPHA : GL_RGBA);
default: default:
// TODO(doyle): Invalid // TODO(doyle): Invalid
@ -45,13 +45,14 @@ Texture genTexture(const GLuint width, const GLuint height,
glGenTextures(1, &tex.id); glGenTextures(1, &tex.id);
glCheckError(); glCheckError();
glBindTexture(GL_TEXTURE_2D, tex.id); glBindTexture(GL_TEXTURE_2D, tex.id);
glCheckError(); glCheckError();
/* Load image into texture */ /* Load image into texture */
// TODO(doyle) Figure out the gl format // TODO(doyle) Figure out the gl format
tex.imageFormat = getGLFormat(bytesPerPixel, FALSE); tex.imageFormat = getGLFormat(bytesPerPixel, FALSE);
ASSERT(tex.imageFormat == GL_RGBA);
glCheckError(); glCheckError();
glTexImage2D(GL_TEXTURE_2D, 0, tex.internalFormat, tex.width, tex.height, 0, 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! //choose to load assets outside of WorldTraveller!
#include <stdlib.h> #include <stdlib.h>
void updateBufferObject(Renderer *const renderer, RenderQuad *const quads, INTERNAL void updateBufferObject(Renderer *const renderer,
const i32 numQuads) RenderQuad *const quads, const i32 numQuads)
{ {
// TODO(doyle): We assume that vbo and vao are assigned // TODO(doyle): We assume that vbo and vao are assigned
const i32 numVertexesInQuad = 4; const i32 numVertexesInQuad = 4;
@ -34,6 +34,8 @@ void worldTraveller_gameInit(GameState *state)
asset_loadShaderFiles("data/shaders/sprite.vert.glsl", asset_loadShaderFiles("data/shaders/sprite.vert.glsl",
"data/shaders/sprite.frag.glsl", shaderlist_sprite); "data/shaders/sprite.frag.glsl", shaderlist_sprite);
asset_loadTTFont("C:/Windows/Fonts/Arial.ttf");
glCheckError(); glCheckError();
state->state = state_active; 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, renderer_object(&state->renderer, V2(0.0f, 0.0f), screenSize, 0.0f,
V3(0, 0, 0), worldTex); 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 // NOTE(doyle): Factor to normalise sprite sheet rect coords to -1, 1
Entity *const hero = &state->entityList[state->heroIndex]; Entity *const hero = &state->entityList[state->heroIndex];
texNdcFactor = 1.0f / CAST(f32) hero->tex->width; 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_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
v2 windowSize = V2(1280.0f, 720.0f); v2 windowSize = V2(1600.0f, 900.0f);
GLFWwindow *window = glfwCreateWindow( GLFWwindow *window = glfwCreateWindow(
CAST(i32) windowSize.x, CAST(i32) windowSize.y, "Dengine", NULL, NULL); CAST(i32) windowSize.x, CAST(i32) windowSize.y, "Dengine", NULL, NULL);

View File

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

View File

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

View File

@ -9,6 +9,13 @@
#define absolute(x) ((x) > 0 ? (x) : -(x)) #define absolute(x) ((x) > 0 ? (x) : -(x))
/* VECTORS */ /* VECTORS */
typedef union v2i
{
struct { i32 x, y; };
struct { i32 w, h; };
i32 e[2];
} v2i;
typedef union v2 typedef union v2
{ {
struct { f32 x, y; }; struct { f32 x, y; };
@ -30,6 +37,11 @@ typedef union v4
f32 e[4]; f32 e[4];
} v4; } 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) INTERNAL inline v2 V2(const f32 x, const f32 y)
{ {
v2 result = {x, 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 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) \ INTERNAL inline v##num v##num##_add(const v##num a, const v##num b) \
{ \ { \
v##num result; \ v##num result; \
@ -109,9 +121,19 @@ INTERNAL inline b32 v##num##_equals(const v##num a, const v##num b) \
return result; \ return result; \
} \ } \
DEFINE_VECTOR_MATH(2); DEFINE_VECTOR_FLOAT_MATH(2);
DEFINE_VECTOR_MATH(3); DEFINE_VECTOR_FLOAT_MATH(3);
DEFINE_VECTOR_MATH(4); 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) INTERNAL inline v3 v3_cross(const v3 a, const v3 b)
{ {

View File

@ -10,12 +10,12 @@ typedef struct
{ {
void *buffer; void *buffer;
i32 size; i32 size;
} PlatformFileReadResult; } PlatformFileRead;
i32 platform_readFileToBuffer(const char *const filePath, 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) if (file->buffer)
{ {

View File

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

View File

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