From 046b2b7a38ec66208c96be59c030294b6d10351b Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 16 Oct 2021 19:36:53 +0200 Subject: Add font rendering --- src/graphics/font.c | 119 +++++++++++++++++++++++++++++++++++++++++++++++++ src/graphics/sprite.c | 6 +-- src/graphics/text.c | 65 +++++++++++++++++++++++++++ src/graphics/texture.c | 54 +++++++++++++++++++--- 4 files changed, 236 insertions(+), 8 deletions(-) create mode 100644 src/graphics/font.c create mode 100644 src/graphics/text.c (limited to 'src/graphics') diff --git a/src/graphics/font.c b/src/graphics/font.c new file mode 100644 index 0000000..9a1d637 --- /dev/null +++ b/src/graphics/font.c @@ -0,0 +1,119 @@ +#include "../../include/mgl/graphics/font.h" +#include "../../include/mgl/system/fileutils.h" +#include + +#define STB_RECT_PACK_IMPLEMENTATION +#include "../../external/stb_rect_pack.h" + +#define STB_TRUETYPE_IMPLEMENTATION +#include "../../external/stb_truetype.h" + +/* TODO: Test and fix .tcc files */ + +int mgl_font_load_from_file(mgl_font *self, const char *filepath, unsigned int font_size) { + self->texture.id = 0; + + self->font_atlas.atlas = NULL; + self->font_atlas.width = 0; + self->font_atlas.height = 0; + + self->size = font_size; + self->packed_chars = NULL; + self->num_packed_chars = 0; + + mgl_filedata filedata; + if(mgl_load_file(filepath, &filedata) != 0) { + fprintf(stderr, "Error: failed to load font %s, error: mgl_load_file failed\n", filepath); + return -1; + } + + stbtt_fontinfo font; + if(!stbtt_InitFont(&font, filedata.data, stbtt_GetFontOffsetForIndex(filedata.data, 0))) { + fprintf(stderr, "Error: failed to load font %s, error: stbtt_InitFont failed\n", filepath); + mgl_filedata_free(&filedata); + return -1; + } + + /* TODO: Optimize */ + self->font_atlas.width = 1024; + self->font_atlas.height = 1024; + self->font_atlas.atlas = malloc(self->font_atlas.width * self->font_atlas.height); + if(!self->font_atlas.atlas) { + fprintf(stderr, "Error: failed to load font %s, error: out of memory\n", filepath); + mgl_filedata_free(&filedata); + mgl_font_unload(self); + return -1; + } + + self->num_packed_chars = 256; + self->packed_chars = malloc(self->num_packed_chars * sizeof(stbtt_packedchar)); + if(!self->packed_chars) { + fprintf(stderr, "Error: failed to load font %s, error: out of memory\n", filepath); + mgl_filedata_free(&filedata); + mgl_font_unload(self); + return -1; + } + + stbtt_pack_context pc; + + if(!stbtt_PackBegin(&pc, self->font_atlas.atlas, self->font_atlas.width, self->font_atlas.height, self->font_atlas.width, 1, NULL)) { + fprintf(stderr, "Error: failed to load font %s, error: stbtt_PackBegin failed\n", filepath); + mgl_filedata_free(&filedata); + mgl_font_unload(self); + return -1; + } + + if(!stbtt_PackFontRange(&pc, filedata.data, 0, self->size, 0, self->num_packed_chars, self->packed_chars)) { + fprintf(stderr, "Error: failed to load font %s, error: stbtt_PackFontRange failed\n", filepath); + mgl_filedata_free(&filedata); + mgl_font_unload(self); + stbtt_PackEnd(&pc); + return -1; + } + + stbtt_PackEnd(&pc); + + if(mgl_texture_load_from_memory(&self->texture, self->font_atlas.atlas, self->font_atlas.width, self->font_atlas.height, MGL_TEXTURE_ALPHA, NULL) != 0) { + fprintf(stderr, "Error: failed to load font %s, error: mgl_texture_load_from_memory failed\n", filepath); + mgl_filedata_free(&filedata); + mgl_font_unload(self); + return -1; + } + + /* TODO: Use stbtt_GetCodepointSDF */ + /* TODO: Use stbtt_PackSetOversampling */ + + mgl_filedata_free(&filedata); + return 0; +} + +void mgl_font_unload(mgl_font *self) { + mgl_texture_unload(&self->texture); + + free(self->font_atlas.atlas); + self->font_atlas.atlas = NULL; + self->font_atlas.width = 0; + self->font_atlas.height = 0; + + free(self->packed_chars); + self->packed_chars = NULL; + self->num_packed_chars = 0; +} + +int mgl_font_get_glyph(mgl_font *self, uint32_t codepoint, mgl_font_glyph *glyph) { + stbtt_packedchar *packed_chars = self->packed_chars; + if(codepoint >= self->num_packed_chars) + return -1; + + float x = 0.0f; + float y = 0.0f; + stbtt_aligned_quad quad; + stbtt_GetPackedQuad(packed_chars, self->font_atlas.width, self->font_atlas.height, codepoint, &x, &y, &quad, 1); + + glyph->position = (mgl_vec2f){ quad.x0, quad.y0 }; + glyph->size = (mgl_vec2f){ quad.x1 - quad.x0, quad.y1 - quad.y0 }; + glyph->texture_position = (mgl_vec2f){ quad.s0, quad.t0 }; + glyph->texture_size = (mgl_vec2f){ quad.s1 - quad.s0, quad.t1 - quad.t0 }; + glyph->advance = x; + return 0; +} diff --git a/src/graphics/sprite.c b/src/graphics/sprite.c index 1ec942a..274278c 100644 --- a/src/graphics/sprite.c +++ b/src/graphics/sprite.c @@ -15,16 +15,16 @@ void mgl_sprite_draw(mgl_context *context, mgl_sprite *sprite) { context->gl.glBindTexture(GL_TEXTURE_2D, sprite->texture->id); context->gl.glBegin(GL_QUADS); context->gl.glTexCoord2f(0.0f, 0.0f); - context->gl.glVertex3f(sprite->position.x, sprite->position.y, 0.0f); + context->gl.glVertex3f(sprite->position.x, sprite->position.y, 0.0f); context->gl.glTexCoord2f(1.0f, 0.0f); - context->gl.glVertex3f(sprite->position.x + sprite->texture->width * sprite->scale.x, sprite->position.y, 0.0f); + context->gl.glVertex3f(sprite->position.x + sprite->texture->width * sprite->scale.x, sprite->position.y, 0.0f); context->gl.glTexCoord2f(1.0f, 1.0f); context->gl.glVertex3f(sprite->position.x + sprite->texture->width * sprite->scale.x, sprite->position.y + sprite->texture->height * sprite->scale.y, 0.0f); context->gl.glTexCoord2f(0.0f, 1.0f); - context->gl.glVertex3f(sprite->position.x, sprite->position.y + sprite->texture->height * sprite->scale.y, 0.0f); + context->gl.glVertex3f(sprite->position.x, sprite->position.y + sprite->texture->height * sprite->scale.y, 0.0f); context->gl.glEnd(); context->gl.glBindTexture(GL_TEXTURE_2D, 0); } diff --git a/src/graphics/text.c b/src/graphics/text.c new file mode 100644 index 0000000..204d1e0 --- /dev/null +++ b/src/graphics/text.c @@ -0,0 +1,65 @@ +#include "../../include/mgl/graphics/text.h" +#include "../../include/mgl/graphics/font.h" +#include "../../include/mgl/mgl.h" + +int mgl_text_init(mgl_text *self, mgl_font *font, const char *text, float x, float y) { + self->font = font; + self->text = text; + self->color = (mgl_color){ 1.0f, 1.0f, 1.0f, 1.0f }; + self->position = (mgl_vec2f){ x, y }; + return 0; +} + +void mgl_text_deinit(mgl_text *self) { + +} + +static void mgl_text_draw_glyph(mgl_context *context, mgl_font_glyph *glyph, mgl_vec2f position) { + context->gl.glTexCoord2f(glyph->texture_position.x, glyph->texture_position.y); + context->gl.glVertex3f(position.x + glyph->position.x, position.y + glyph->position.y, 0.0f); + + context->gl.glTexCoord2f(glyph->texture_position.x + glyph->texture_size.x, glyph->texture_position.y); + context->gl.glVertex3f(position.x + glyph->position.x + glyph->size.x, position.y + glyph->position.y, 0.0f); + + context->gl.glTexCoord2f(glyph->texture_position.x + glyph->texture_size.x, glyph->texture_position.y + glyph->texture_size.y); + context->gl.glVertex3f(position.x + glyph->position.x + glyph->size.x, position.y + glyph->position.y + glyph->size.y, 0.0f); + + context->gl.glTexCoord2f(glyph->texture_position.x, glyph->texture_position.y + glyph->texture_size.y); + context->gl.glVertex3f(position.x + glyph->position.x, position.y + glyph->position.y + glyph->size.y, 0.0f); +} + +/* TODO: Use opengl buffer object instead */ +/* TODO: Cache texture bind to not bind texture if its already bound and do not bind texture 0 */ +void mgl_text_draw(mgl_context *context, mgl_text *text) { + const char *str = text->text; + mgl_font_glyph glyph; + mgl_vec2f position = text->position; + position.y += text->font->size; + + context->gl.glColor4f(text->color.r, text->color.g, text->color.b, text->color.a); + context->gl.glBindTexture(GL_TEXTURE_2D, text->font->texture.id); + context->gl.glBegin(GL_QUADS); + while(*str) { + unsigned char c = *(unsigned char*)str; + if((c >= 32 && c < 128)) { + if(mgl_font_get_glyph(text->font, c, &glyph) == 0) { + mgl_text_draw_glyph(context, &glyph, position); + position.x += glyph.advance; + } + } else if(c == '\t') { + if(mgl_font_get_glyph(text->font, ' ', &glyph) == 0) { + const int tab_width = 4; + for(int i = 0; i < tab_width; ++i) { + mgl_text_draw_glyph(context, &glyph, position); + position.x += glyph.advance; + } + } + } else if(c == '\n') { + position.x = text->position.x; + position.y += text->font->size; + } + ++str; + } + context->gl.glEnd(); + context->gl.glBindTexture(GL_TEXTURE_2D, 0); +} diff --git a/src/graphics/texture.c b/src/graphics/texture.c index b3a1fab..383f3f8 100644 --- a/src/graphics/texture.c +++ b/src/graphics/texture.c @@ -9,32 +9,51 @@ #define STB_IMAGE_IMPLEMENTATION #include "../../external/stb_image.h" +/* TODO: Check for glTexImage2D failure */ + static int mgl_texture_format_to_opengl_format(mgl_texture_format format) { switch(format) { + case MGL_TEXTURE_ALPHA: return GL_ALPHA8; case MGL_TEXTURE_GRAY: return GL_LUMINANCE8; case MGL_TEXTURE_GRAY_ALPHA: return GL_LUMINANCE8_ALPHA8; case MGL_TEXTURE_RGB: return GL_RGB8; - case MGL_TEXTURE_RGB_ALPHA: return GL_RGBA8; + case MGL_TEXTURE_RGBA: return GL_RGBA8; } return 0; } static int mgl_texture_format_to_compressed_opengl_format(mgl_texture_format format) { switch(format) { + case MGL_TEXTURE_ALPHA: return GL_ALPHA8; case MGL_TEXTURE_GRAY: return GL_LUMINANCE8; case MGL_TEXTURE_GRAY_ALPHA: return GL_LUMINANCE8_ALPHA8; case MGL_TEXTURE_RGB: return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; - case MGL_TEXTURE_RGB_ALPHA: return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + case MGL_TEXTURE_RGBA: return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; } return 0; } static int mgl_texture_format_to_source_opengl_format(mgl_texture_format format) { switch(format) { - case MGL_TEXTURE_GRAY: return GL_LUMINANCE8_ALPHA8; + case MGL_TEXTURE_ALPHA: return GL_ALPHA; + case MGL_TEXTURE_GRAY: return GL_LUMINANCE8; case MGL_TEXTURE_GRAY_ALPHA: return GL_LUMINANCE8_ALPHA8; case MGL_TEXTURE_RGB: return GL_RGB; - case MGL_TEXTURE_RGB_ALPHA: return GL_RGBA; + case MGL_TEXTURE_RGBA: return GL_RGBA; + } + return 0; +} + +static mgl_texture_format stbi_format_to_mgl_texture_format(int stbi_format) { + switch(stbi_format) { + case STBI_grey: + return MGL_TEXTURE_GRAY; + case STBI_grey_alpha: + return MGL_TEXTURE_GRAY_ALPHA; + case STBI_rgb: + return MGL_TEXTURE_RGB; + case STBI_rgb_alpha: + return MGL_TEXTURE_RGBA; } return 0; } @@ -50,11 +69,12 @@ int mgl_texture_load_from_file(mgl_texture *self, const char *filepath, mgl_text fprintf(stderr, "Error: failed to load image %s, error: %s\n", filepath, stbi_failure_reason()); return -1; } - self->format = format; + self->format = stbi_format_to_mgl_texture_format(format); mgl_context *context = mgl_get_context(); context->gl.glGenTextures(1, &self->id); if(self->id == 0) { + fprintf(stderr, "Error: failed to load image %s", filepath); stbi_image_free(image_data); return -1; } @@ -73,6 +93,30 @@ int mgl_texture_load_from_file(mgl_texture *self, const char *filepath, mgl_text return 0; } +int mgl_texture_load_from_memory(mgl_texture *self, const unsigned char *data, int width, int height, mgl_texture_format format, mgl_texture_load_options *load_options) { + self->id = 0; + self->width = width; + self->height = height; + self->format = format; + + mgl_context *context = mgl_get_context(); + context->gl.glGenTextures(1, &self->id); + if(self->id == 0) + return -1; + + const int opengl_texture_format = load_options && load_options->compressed ? mgl_texture_format_to_compressed_opengl_format(self->format) : mgl_texture_format_to_opengl_format(self->format); + + context->gl.glBindTexture(GL_TEXTURE_2D, self->id); + context->gl.glTexImage2D(GL_TEXTURE_2D, 0, opengl_texture_format, self->width, self->height, 0, mgl_texture_format_to_source_opengl_format(self->format), GL_UNSIGNED_BYTE, data); + context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + context->gl.glBindTexture(GL_TEXTURE_2D, 0); + + return 0; +} + void mgl_texture_unload(mgl_texture *self) { mgl_context *context = mgl_get_context(); if(self->id) { -- cgit v1.2.3