#define STBI_FAILURE_USERMSG #define STB_IMAGE_IMPLEMENTATION #include #define SBTT_STATIC #define STB_TRUETYPE_IMPLEMENTATION #include #include "Dengine/Platform.h" #include "Dengine/AssetManager.h" #include "Dengine/Debug.h" //#define WT_RENDER_FONT_FILE #ifdef WT_RENDER_FONT_FILE #define STB_IMAGE_WRITE_IMPLEMENTATION #include #endif Texture *asset_getTexture(AssetManager *assetManager, const enum TexList type) { if (type < texlist_count) return &assetManager->textures[type]; #ifdef DENGINE_DEBUG ASSERT(INVALID_CODE_PATH); #endif return NULL; } TexAtlas *asset_getTextureAtlas(AssetManager *assetManager, const enum TexList type) { if (type < texlist_count) return &assetManager->texAtlas[type]; #ifdef DENGINE_DEBUG ASSERT(INVALID_CODE_PATH); #endif return NULL; } Animation *asset_getAnim(AssetManager *assetManager, i32 type) { if (type < animlist_count) return &assetManager->anims[type]; #ifdef DENGINE_DEBUG ASSERT(INVALID_CODE_PATH); #endif return NULL; } const i32 asset_loadTextureImage(AssetManager *assetManager, const char *const path, const enum TexList type) { /* Open the texture image */ i32 imgWidth, imgHeight, bytesPerPixel; stbi_set_flip_vertically_on_load(TRUE); u8 *image = stbi_load(path, &imgWidth, &imgHeight, &bytesPerPixel, 0); #ifdef DENGINE_DEBUG if (imgWidth != imgHeight) { printf( "worldTraveller_gameInit() warning: Sprite sheet is not square: " "%dx%dpx\n", imgWidth, imgHeight); } #endif if (!image) { printf("stdbi_load() failed: %s\n", stbi_failure_reason()); return -1; } Texture tex = texture_gen(CAST(GLuint)(imgWidth), CAST(GLuint)(imgHeight), CAST(GLint)(bytesPerPixel), image); glCheckError(); stbi_image_free(image); assetManager->textures[type] = tex; return 0; } Shader *asset_getShader(AssetManager *assetManager, const enum ShaderList type) { if (type < shaderlist_count) return &assetManager->shaders[type]; #ifdef DENGINE_DEBUG ASSERT(INVALID_CODE_PATH); #endif return NULL; } INTERNAL GLuint createShaderFromPath(MemoryArena *arena, const char *const path, GLuint shadertype) { PlatformFileRead file = {0}; i32 status = platform_readFileToBuffer(arena, path, &file); if (status) return status; const GLchar *source = CAST(char *)file.buffer; GLuint result = glCreateShader(shadertype); glShaderSource(result, 1, &source, NULL); glCompileShader(result); GLint success; GLchar infoLog[512]; glGetShaderiv(result, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(result, 512, NULL, infoLog); printf("glCompileShader() failed: %s\n", infoLog); } platform_closeFileRead(arena, &file); return result; } INTERNAL i32 shaderLoadProgram(Shader *const shader, const GLuint vertexShader, const GLuint fragmentShader) { shader->id = glCreateProgram(); glAttachShader(shader->id, vertexShader); glAttachShader(shader->id, fragmentShader); glLinkProgram(shader->id); glDeleteShader(fragmentShader); glDeleteShader(vertexShader); GLint success; GLchar infoLog[512]; glGetProgramiv(shader->id, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(shader->id, 512, NULL, infoLog); printf("glLinkProgram failed: %s\n", infoLog); return -1; } return 0; } const i32 asset_loadShaderFiles(AssetManager *assetManager, MemoryArena *arena, const char *const vertexPath, const char *const fragmentPath, const enum ShaderList type) { GLuint vertexShader = createShaderFromPath(arena, vertexPath, GL_VERTEX_SHADER); GLuint fragmentShader = createShaderFromPath(arena, fragmentPath, GL_FRAGMENT_SHADER); Shader shader; i32 result = shaderLoadProgram(&shader, vertexShader, fragmentShader); if (result) return result; assetManager->shaders[type] = shader; return 0; } /* Individual glyph bitmap generated from STB used for creating a font sheet */ typedef struct GlyphBitmap { v2 dimensions; u32 *pixels; i32 codepoint; } GlyphBitmap; const i32 asset_loadTTFont(AssetManager *assetManager, MemoryArena *arena, const char *filePath) { PlatformFileRead fontFileRead = {0}; platform_readFileToBuffer(arena, filePath, &fontFileRead); stbtt_fontinfo fontInfo = {0}; stbtt_InitFont(&fontInfo, fontFileRead.buffer, stbtt_GetFontOffsetForIndex(fontFileRead.buffer, 0)); /* Initialise Assetmanager Font */ Font *font = &assetManager->font; font->codepointRange = V2i(32, 127); v2 codepointRange = font->codepointRange; const i32 numGlyphs = CAST(i32)(codepointRange.y - codepointRange.x); GlyphBitmap *glyphBitmaps = PLATFORM_MEM_ALLOC(arena, numGlyphs, GlyphBitmap); v2 largestGlyphDimension = V2(0, 0); const f32 targetFontHeight = 15.0f; f32 scaleY = stbtt_ScaleForPixelHeight(&fontInfo, targetFontHeight); i32 ascent, descent, lineGap; stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap); ascent = CAST(i32)(ascent * scaleY); descent = CAST(i32)(descent * scaleY); lineGap = CAST(i32)(lineGap * scaleY); font->metrics = CAST(FontMetrics){ascent, descent, lineGap}; font->charMetrics = PLATFORM_MEM_ALLOC(arena, numGlyphs, CharMetrics); /* Use STB_TrueType to generate a series of bitmap characters */ i32 glyphIndex = 0; for (i32 codepoint = CAST(i32) codepointRange.x; codepoint < CAST(i32) codepointRange.y; codepoint++) { // NOTE(doyle): ScaleX if not specified, is then calculated based on the // ScaleY component i32 width, height, xOffset, yOffset; u8 *const monoBitmap = stbtt_GetCodepointBitmap(&fontInfo, 0, scaleY, codepoint, &width, &height, &xOffset, &yOffset); u8 *source = monoBitmap; u32 *colorBitmap = PLATFORM_MEM_ALLOC(arena, width * height, 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); } } /* Get individual character metrics */ i32 advance, leftSideBearing; stbtt_GetCodepointHMetrics(&fontInfo, codepoint, &advance, &leftSideBearing); advance = CAST(i32)(advance * scaleY); leftSideBearing = CAST(i32)(leftSideBearing * scaleY); font->charMetrics[glyphIndex] = CAST(CharMetrics){advance, leftSideBearing, NULL, V2i(xOffset, yOffset), V2i(width, height)}; /* Store bitmap into intermediate storage */ stbtt_FreeBitmap(monoBitmap, NULL); // TODO(doyle): Dimensions is used twice in font->trueSize and this glyphBitmaps[glyphIndex].dimensions = V2i(width, height); glyphBitmaps[glyphIndex].codepoint = codepoint; glyphBitmaps[glyphIndex++].pixels = colorBitmap; if (height > CAST(f32)largestGlyphDimension.h) largestGlyphDimension.h = CAST(f32)height; if (width > CAST(f32)largestGlyphDimension.w) largestGlyphDimension.w = CAST(f32)width; #ifdef DENGINE_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. * * 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. */ font->maxSize = largestGlyphDimension; i32 glyphsPerRow = (MAX_TEXTURE_SIZE / CAST(i32)font->maxSize.w); #ifdef DENGINE_DEBUG i32 glyphsPerCol = MAX_TEXTURE_SIZE / CAST(i32)font->maxSize.h; if ((glyphsPerRow * glyphsPerCol) <= numGlyphs) { printf( "asset_loadTTFont() warning: The target font height creates a " "glyph sheet that exceeds the available space!"); ASSERT(INVALID_CODE_PATH); } #endif i32 bitmapSize = SQUARED(TARGET_TEXTURE_SIZE) * TARGET_BYTES_PER_PIXEL; u32 *fontBitmap = PLATFORM_MEM_ALLOC(arena, bitmapSize, u32); const 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 < glyphsRemaining) ? glyphsPerRow : glyphsRemaining; // TODO(doyle): We copy over the bitmap direct to the font sheet, should we // align the baselines up so we don't need to do baseline adjusting at // render? i32 atlasIndex = 0; 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]; /* Store the location of glyph into atlas */ if (verticalPixelsBlitted == 0) { TexAtlas *fontAtlas = &assetManager->texAtlas[texlist_font]; #ifdef DENGINE_DEBUG ASSERT(activeGlyph.codepoint < ARRAY_COUNT(fontAtlas->texRect)); #endif v2 origin = V2(CAST(f32)(glyphIndex * font->maxSize.w), CAST(f32) row); #if 1 fontAtlas->texRect[atlasIndex++] = math_getRect(origin, V2(CAST(f32) font->maxSize.w, CAST(f32) font->maxSize.h)); #else v2i fontSize = font->charMetrics[activeGlyph.codepoint - 32].trueSize; fontAtlas->texRect[atlasIndex++] = math_getRect( origin, V2(CAST(f32) fontSize.x, CAST(f32) fontSize.y)); #endif } /* Copy over exactly one row of pixels */ i32 numPixelsToPad = CAST(i32)font->maxSize.w; if (verticalPixelsBlitted < activeGlyph.dimensions.h) { const i32 srcPitch = CAST(i32) activeGlyph.dimensions.w * verticalPixelsBlitted; const u32 *src = activeGlyph.pixels + srcPitch; const i32 numPixelsToCopy = CAST(i32)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 = CAST(i32)(font->maxSize.w - activeGlyph.dimensions.w); } destRow += numPixelsToPad; } /* A row of glyphs has been fully formed on the atlas */ if (verticalPixelsBlitted++ >= font->maxSize.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 = texture_gen(MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE, 4, CAST(u8 *) fontBitmap); assetManager->textures[texlist_font] = tex; #ifdef WT_RENDER_FONT_FILE /* save out a 4 channel image */ stbi_write_png("out.png", MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE, 4, fontBitmap, MAX_TEXTURE_SIZE * 4); #endif PLATFORM_MEM_FREE(arena, fontBitmap, bitmapSize); font->tex = &assetManager->textures[texlist_font]; font->atlas = &assetManager->texAtlas[texlist_font]; for (i32 i = 0; i < numGlyphs; i++) { i32 glyphBitmapSizeInBytes = CAST(i32) glyphBitmaps[i].dimensions.w * CAST(i32) glyphBitmaps[i].dimensions.h * sizeof(u32); PLATFORM_MEM_FREE(arena, glyphBitmaps[i].pixels, glyphBitmapSizeInBytes); } PLATFORM_MEM_FREE(arena, glyphBitmaps, numGlyphs * sizeof(GlyphBitmap)); platform_closeFileRead(arena, &fontFileRead); return 0; } void asset_addAnimation(AssetManager *assetManager, MemoryArena *arena, i32 texId, i32 animId, i32 *frameIndex, i32 numFrames, f32 frameDuration) { #ifdef DENGINE_DEBUG ASSERT(assetManager && frameIndex) ASSERT(!assetManager->anims[animId].frameIndex); #endif Animation anim = {0}; anim.atlas = asset_getTextureAtlas(assetManager, texId); anim.frameIndex = PLATFORM_MEM_ALLOC(arena, numFrames, i32); for (i32 i = 0; i < numFrames; i++) anim.frameIndex[i] = frameIndex[i]; anim.numFrames = numFrames; anim.frameDuration = frameDuration; assetManager->anims[animId] = anim; }