aboutsummaryrefslogtreecommitdiff
path: root/src/graphics
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-10-16 19:36:53 +0200
committerdec05eba <dec05eba@protonmail.com>2021-10-16 19:46:05 +0200
commit046b2b7a38ec66208c96be59c030294b6d10351b (patch)
tree5858b113d026c47cdae0c47033eaa0bbe9f0d113 /src/graphics
parent5cbff06ff9153f7a7958202a777d98ebeae59393 (diff)
Add font rendering
Diffstat (limited to 'src/graphics')
-rw-r--r--src/graphics/font.c119
-rw-r--r--src/graphics/sprite.c6
-rw-r--r--src/graphics/text.c65
-rw-r--r--src/graphics/texture.c54
4 files changed, 236 insertions, 8 deletions
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 <stdio.h>
+
+#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) {