diff --git a/src/AssetManager.c b/src/AssetManager.c index 3e7eae5..cd041a3 100644 --- a/src/AssetManager.c +++ b/src/AssetManager.c @@ -2,6 +2,10 @@ #define STB_IMAGE_IMPLEMENTATION #include +#define SBTT_STATIC +#define STB_TRUETYPE_IMPLEMENTATION +#include + #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; +} diff --git a/src/Platform.c b/src/Platform.c index b54de7b..1c5f25d 100644 --- a/src/Platform.c +++ b/src/Platform.c @@ -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); diff --git a/src/Texture.c b/src/Texture.c index c5e3759..52bde67 100644 --- a/src/Texture.c +++ b/src/Texture.c @@ -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, diff --git a/src/WorldTraveller.c b/src/WorldTraveller.c index 96275e8..f43c231 100644 --- a/src/WorldTraveller.c +++ b/src/WorldTraveller.c @@ -6,8 +6,8 @@ //choose to load assets outside of WorldTraveller! #include -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; diff --git a/src/dengine.c b/src/dengine.c index 72a6866..e4e5fef 100644 --- a/src/dengine.c +++ b/src/dengine.c @@ -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); diff --git a/src/include/Dengine/AssetManager.h b/src/include/Dengine/AssetManager.h index 981c5db..184f0e6 100644 --- a/src/include/Dengine/AssetManager.h +++ b/src/include/Dengine/AssetManager.h @@ -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 diff --git a/src/include/Dengine/Common.h b/src/include/Dengine/Common.h index e923d46..893a710 100644 --- a/src/include/Dengine/Common.h +++ b/src/include/Dengine/Common.h @@ -3,6 +3,8 @@ #include +#define WT_DEBUG + typedef uint8_t u8; typedef uint32_t u32; typedef uint64_t u64; diff --git a/src/include/Dengine/Math.h b/src/include/Dengine/Math.h index 1096135..1b95f24 100644 --- a/src/include/Dengine/Math.h +++ b/src/include/Dengine/Math.h @@ -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) { diff --git a/src/include/Dengine/Platform.h b/src/include/Dengine/Platform.h index 198b382..b75f10e 100644 --- a/src/include/Dengine/Platform.h +++ b/src/include/Dengine/Platform.h @@ -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) { diff --git a/src/include/Dengine/Texture.h b/src/include/Dengine/Texture.h index 225faee..0f302c6 100644 --- a/src/include/Dengine/Texture.h +++ b/src/include/Dengine/Texture.h @@ -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 diff --git a/src/include/WorldTraveller/WorldTraveller.h b/src/include/WorldTraveller/WorldTraveller.h index 36c36c1..6a5fb29 100644 --- a/src/include/WorldTraveller/WorldTraveller.h +++ b/src/include/WorldTraveller/WorldTraveller.h @@ -1,9 +1,9 @@ #ifndef WORLDTRAVELLER_GAME_H #define WORLDTRAVELLER_GAME_H -#include -#include -#include +#include "Dengine/Common.h" +#include "Dengine/Entity.h" +#include "Dengine/Renderer.h" #define NUM_KEYS 1024 #define METERS_TO_PIXEL 100