Rudimentary font rasterisation at runtime with STB
This commit is contained in:
parent
76d6dfcf2f
commit
e03d3fd14a
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define WT_DEBUG
|
||||
|
||||
typedef uint8_t u8;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user