Dengine/src/AssetManager.c

438 lines
13 KiB
C
Raw Normal View History

#define STBI_FAILURE_USERMSG
#define STB_IMAGE_IMPLEMENTATION
#include <STB/stb_image.h>
#define SBTT_STATIC
#define STB_TRUETYPE_IMPLEMENTATION
#include <STB/stb_truetype.h>
2016-06-28 06:00:03 +00:00
#include "Dengine/Platform.h"
#include "Dengine/AssetManager.h"
#include "Dengine/Debug.h"
2016-06-28 06:00:03 +00:00
//#define WT_RENDER_FONT_FILE
#ifdef WT_RENDER_FONT_FILE
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <STB/stb_image_write.h>
#endif
Texture *asset_getTexture(AssetManager *assetManager, const enum TexList type)
2016-06-08 07:29:16 +00:00
{
if (type < texlist_count)
return &assetManager->textures[type];
2016-06-08 07:29:16 +00:00
#ifdef DENGINE_DEBUG
ASSERT(INVALID_CODE_PATH);
#endif
return NULL;
2016-06-08 07:29:16 +00:00
}
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)
2016-06-08 07:29:16 +00:00
{
/* Open the texture image */
i32 imgWidth, imgHeight, bytesPerPixel;
stbi_set_flip_vertically_on_load(TRUE);
2016-06-17 14:40:40 +00:00
u8 *image =
stbi_load(path, &imgWidth, &imgHeight, &bytesPerPixel, 0);
2016-06-08 07:29:16 +00:00
#ifdef DENGINE_DEBUG
if (imgWidth != imgHeight)
{
printf(
"worldTraveller_gameInit() warning: Sprite sheet is not square: "
"%dx%dpx\n", imgWidth, imgHeight);
}
#endif
2016-06-08 07:29:16 +00:00
if (!image)
{
printf("stdbi_load() failed: %s\n", stbi_failure_reason());
2016-06-08 07:29:16 +00:00
return -1;
}
Texture tex = texture_gen(CAST(GLuint)(imgWidth), CAST(GLuint)(imgHeight),
CAST(GLint)(bytesPerPixel), image);
2016-06-17 14:40:40 +00:00
glCheckError();
2016-06-08 07:29:16 +00:00
stbi_image_free(image);
assetManager->textures[type] = tex;
2016-06-08 07:29:16 +00:00
return 0;
}
Shader *asset_getShader(AssetManager *assetManager, const enum ShaderList type)
2016-06-08 07:29:16 +00:00
{
if (type < shaderlist_count)
return &assetManager->shaders[type];
2016-06-08 07:29:16 +00:00
#ifdef DENGINE_DEBUG
ASSERT(INVALID_CODE_PATH);
#endif
return NULL;
2016-06-08 07:29:16 +00:00
}
INTERNAL GLuint createShaderFromPath(MemoryArena *arena, const char *const path, GLuint shadertype)
2016-06-08 07:29:16 +00:00
{
PlatformFileRead file = {0};
2016-06-08 07:29:16 +00:00
i32 status = platform_readFileToBuffer(arena, path, &file);
if (status)
return status;
2016-06-08 07:29:16 +00:00
const GLchar *source = CAST(char *)file.buffer;
2016-06-08 07:29:16 +00:00
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);
2016-06-08 07:29:16 +00:00
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)
2016-06-08 07:29:16 +00:00
{
GLuint vertexShader = createShaderFromPath(arena, vertexPath, GL_VERTEX_SHADER);
2016-06-17 14:40:40 +00:00
GLuint fragmentShader =
createShaderFromPath(arena, fragmentPath, GL_FRAGMENT_SHADER);
2016-06-08 07:29:16 +00:00
Shader shader;
i32 result = shaderLoadProgram(&shader, vertexShader, fragmentShader);
2016-06-08 07:29:16 +00:00
if (result)
return result;
assetManager->shaders[type] = shader;
2016-06-08 07:29:16 +00:00
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;
}