From a3c6774f211ee765f910df76837548bdadd4e959 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 15 Nov 2021 08:20:13 +0100 Subject: Add dynamic font atlas creation (not finished) --- src/graphics/font.c | 283 ++++++++++++++++++++++++++++--------------- src/graphics/font_char_map.c | 214 ++++++++++++++++++++++++++++++++ src/graphics/sprite.c | 17 ++- src/graphics/text.c | 10 +- src/graphics/texture.c | 117 +++++++++++++++--- src/graphics/vertex.c | 4 + src/graphics/vertex_buffer.c | 4 +- 7 files changed, 524 insertions(+), 125 deletions(-) create mode 100644 src/graphics/font_char_map.c (limited to 'src/graphics') diff --git a/src/graphics/font.c b/src/graphics/font.c index dfffc7e..0044d96 100644 --- a/src/graphics/font.c +++ b/src/graphics/font.c @@ -1,141 +1,228 @@ #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 */ +/* TODO: Use correct atlas padding after resize. Its incorrect because padding from previous size doesn't accumulate */ -static unsigned int to_div2_ceil(unsigned int value) { - const uint32_t v = value; - return v + (v & 1); -} +/* Need padding so filtering doesn't touch pixels in another glyphs area */ +#define GLYPH_PADDING 2 +#define GLYPH_UPSAMPLE 1 -int mgl_font_load_from_file(mgl_font *self, const char *filepath, unsigned int character_size) { +int mgl_font_load_from_file(mgl_font *self, const mgl_memory_mapped_file *mapped_file, unsigned int character_size) { self->texture.id = 0; - self->font_atlas.atlas = NULL; self->font_atlas.width = 0; self->font_atlas.height = 0; + self->font_atlas.prev_width = 0; + self->font_atlas.prev_height = 0; + self->font_atlas.pointer_position = (mgl_vec2i){ GLYPH_PADDING, GLYPH_PADDING }; + self->font_atlas.render_section = MGL_ATLAS_SECTION_INITIAL; self->character_size = character_size; - self->packed_chars = NULL; - self->num_packed_chars = 0; + mgl_font_char_map_init(&self->char_map); + self->current_line_max_height = 0; + self->font_info = NULL; - mgl_memory_mapped_file mapped_file; - if(mgl_mapped_file_load(filepath, &mapped_file, &(mgl_memory_mapped_file_load_options){ .readable = true, .writable = false }) != 0) { - fprintf(stderr, "Error: failed to load font %s, error: mgl_load_file failed\n", filepath); + self->font_info = malloc(sizeof(stbtt_fontinfo)); + if(!self->font_info) { + fprintf(stderr, "Error: failed to load font, error: out of memory\n"); return -1; } - stbtt_fontinfo font; - if(!stbtt_InitFont(&font, mapped_file.data, stbtt_GetFontOffsetForIndex(mapped_file.data, 0))) { - fprintf(stderr, "Error: failed to load font %s, error: stbtt_InitFont failed\n", filepath); - mgl_mapped_file_unload(&mapped_file); + /* TODO: Handle font offset with ttc */ + if(!stbtt_InitFont(self->font_info, mapped_file->data, stbtt_GetFontOffsetForIndex(mapped_file->data, 0))) { + fprintf(stderr, "Error: failed to load font, error: stbtt_InitFont failed\n"); + 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); - goto error; + if(mgl_texture_init(&self->texture) != 0) { + mgl_font_unload(self); + return -1; } - bool atlas_created = false; - /* TODO: Optimize */ - /* Find optimal size for atlas, starting from small to large */ - for(int i = 0; i < 4; ++i) { - /* - This to_div2_ceil is needed because otherwise for character sizes such as 33 which are not divisable by 2 - causes the font texture to be skewed. I dont know why this happens. Maybe a bug in stbtt? - TODO: Figure out why it happens. - */ - self->font_atlas.width = (14 + (8 * i)) * to_div2_ceil(self->character_size); - self->font_atlas.height = (14 + (8 * i)) * to_div2_ceil(self->character_size); - unsigned char *new_atlas = realloc(self->font_atlas.atlas, self->font_atlas.width * self->font_atlas.height); - if(!new_atlas) { - fprintf(stderr, "Error: failed to load font %s, error: out of memory\n", filepath); - goto error; - } - self->font_atlas.atlas = new_atlas; + /* TODO: Use stbtt_GetCodepointSDF */ + return 0; +} - stbtt_pack_context pc; +void mgl_font_unload(mgl_font *self) { + mgl_texture_unload(&self->texture); - 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); - goto error; - } + mgl_font_char_map_deinit(&self->char_map); + self->current_line_max_height = 0; - stbtt_PackSetOversampling(&pc, 2, 2); + free(self->font_info); + self->font_info = NULL; +} - if(!stbtt_PackFontRange(&pc, mapped_file.data, 0, self->character_size, 0, self->num_packed_chars, self->packed_chars)) { - stbtt_PackEnd(&pc); - continue; +static void mgl_font_handle_new_render_position(mgl_font *self, int glyph_width) { + if(self->font_atlas.pointer_position.x + glyph_width + GLYPH_PADDING >= self->font_atlas.width) { + if(self->font_atlas.pointer_position.y + self->current_line_max_height + GLYPH_PADDING >= self->font_atlas.height && self->font_atlas.render_section != MGL_ATLAS_SECTION_RIGHT) { + /* + Texture atlas looks like this with glyphs filling the texture: + |----| + |ABCD| + |EFGH| + |----| + + And then texture atlas gets resized: + |----|----| | + |ABCD| | <-| 1. The right side gets filled in with new glyphs. + |EDFG| | <-| + |----|----| | + | | | | + | | | | + | | | | + |----|----| | + | + ^^^^^^^^^^^ | + ---------------| + 2. The bottom side gets filled in with new glyphs after the right side is filled with glyphs. + */ + + /* Texture overflow, enlargen the font atlas! */ + /* TODO: Create a new texture and copy the texture content instead of rendering all glyphs again */ + if(mgl_texture_resize(&self->texture, self->font_atlas.width * 2, self->font_atlas.height * 2, NULL) == 0) { + self->font_atlas.prev_width = self->font_atlas.width; + self->font_atlas.prev_height = self->font_atlas.height; + self->font_atlas.width *= 2; + self->font_atlas.height *= 2; + + self->font_atlas.render_section = MGL_ATLAS_SECTION_RIGHT; + self->font_atlas.pointer_position.x = self->font_atlas.prev_width; + self->font_atlas.pointer_position.y = GLYPH_PADDING; + + mgl_font_char_map_clear_rendered(&self->char_map); + + mgl_font_glyph glyph_dummy; + mgl_font_char_iterator font_char_it = mgl_font_char_map_begin(&self->char_map); + while(font_char_it.value) { + mgl_font_get_glyph(self, font_char_it.value->key, &glyph_dummy); + mgl_font_char_iterator_next(&font_char_it); + } + } else { + fprintf(stderr, "Error: failed to resize font atlas\n"); + } + } else if(self->font_atlas.render_section == MGL_ATLAS_SECTION_RIGHT && self->font_atlas.pointer_position.y + self->current_line_max_height + GLYPH_PADDING >= self->font_atlas.prev_height) { + self->font_atlas.render_section = MGL_ATLAS_SECTION_BOTTOM; + self->font_atlas.pointer_position.x = GLYPH_PADDING; + self->font_atlas.pointer_position.y = self->font_atlas.prev_height; + } else { + if(self->font_atlas.render_section != MGL_ATLAS_SECTION_RIGHT) { + self->font_atlas.pointer_position.x = GLYPH_PADDING; + } else { + self->font_atlas.pointer_position.x = self->font_atlas.prev_width; + } + self->font_atlas.pointer_position.y += self->current_line_max_height + GLYPH_PADDING; } - - /*if(!stbtt_PackFontRange(&pc, mapped_file.data, 0, self->character_size, 0x00004E00, self->num_packed_chars, self->packed_chars)) { - stbtt_PackEnd(&pc); - continue; - }*/ - - stbtt_PackEnd(&pc); - atlas_created = true; - break; + self->current_line_max_height = 0; } +} - if(!atlas_created) { - fprintf(stderr, "Error: failed to load font %s, error: failed to create atlas\n", filepath); - goto error; +int mgl_font_get_glyph(mgl_font *self, uint32_t codepoint, mgl_font_glyph *glyph) { + if(self->font_atlas.width == 0) { + const int initial_atlas_size = 128; + /* TODO: Scale by character size */ + mgl_texture_load_options load_options = { + .compressed = false, + .pixel_coordinates = true + }; + if(mgl_texture_load_from_memory(&self->texture, NULL, initial_atlas_size, initial_atlas_size, MGL_IMAGE_FORMAT_ALPHA, &load_options) == 0) { + /*fprintf(stderr, "Error: failed to create font atlas texture, error: mgl_texture_load_from_memory failed\n");*/ + self->font_atlas.width = initial_atlas_size; + self->font_atlas.height = initial_atlas_size; + } } - if(mgl_texture_load_from_memory(&self->texture, self->font_atlas.atlas, self->font_atlas.width, self->font_atlas.height, MGL_IMAGE_FORMAT_ALPHA, NULL) != 0) { - fprintf(stderr, "Error: failed to load font %s, error: mgl_texture_load_from_memory failed\n", filepath); - goto error; + mgl_font_char_entry *existing_char_entry = mgl_font_char_map_get(&self->char_map, codepoint); + if(existing_char_entry) { + *glyph = existing_char_entry->value; + if(existing_char_entry->rendered) + return 0; } - /* TODO: Use stbtt_GetCodepointSDF */ - - mgl_mapped_file_unload(&mapped_file); - return 0; + /* + TODO: Maybe somehow use multiple textures? that would be needed when using a lot of glyphs (such as when using chinese fonts). + TODO: Map texture buffer and rasterize the glyph directly into the texture. + */ - error: - mgl_mapped_file_unload(&mapped_file); - mgl_font_unload(self); - return -1; -} + const int glyph_index = stbtt_FindGlyphIndex(self->font_info, codepoint); + if(glyph_index == 0) { + memset(glyph, 0, sizeof(mgl_font_glyph)); + return -1; + } + const float font_scale = stbtt_ScaleForPixelHeight(self->font_info, self->character_size*GLYPH_UPSAMPLE); + + int advance = 0; + int lsb; + stbtt_GetGlyphHMetrics(self->font_info, glyph_index, &advance, &lsb); + const float glyph_advance = font_scale * advance; + + int x0, y0, x1, y1; + stbtt_GetGlyphBitmapBox(self->font_info, glyph_index, font_scale, font_scale, &x0, &y0, &x1, &y1); + + int width = x1 - x0; + int height = y1 - y0; + int xoff = x0; + int yoff = y0; + + /* TODO: Use stbtt_MakeGlyphBitmapSubpixelPrefilter instead for better text quality */ + const size_t pixels_size = (width + GLYPH_PADDING * 2) * (height + GLYPH_PADDING * 2); + unsigned char *pixels = malloc(pixels_size); + if(pixels) { + /* TODO: Is this needed? */ + /*memset(pixels, 0, pixels_size);*/ + stbtt_MakeGlyphBitmapSubpixel(self->font_info, pixels, width, height, width, font_scale, font_scale, GLYPH_PADDING, GLYPH_PADDING, glyph_index); + } -void mgl_font_unload(mgl_font *self) { - mgl_texture_unload(&self->texture); + mgl_vec2i render_offset; + if(existing_char_entry) { + render_offset = existing_char_entry->render_offset; + } else { + mgl_font_handle_new_render_position(self, width); + render_offset.x = self->font_atlas.pointer_position.x; + render_offset.y = self->font_atlas.pointer_position.y; + + mgl_font_glyph new_glyph; + new_glyph.position = (mgl_vec2i){ xoff/GLYPH_UPSAMPLE, yoff/GLYPH_UPSAMPLE }; + new_glyph.size = (mgl_vec2i){ width/GLYPH_UPSAMPLE, height/GLYPH_UPSAMPLE }; + new_glyph.texture_position = (mgl_vec2i){ render_offset.x, render_offset.y }; + new_glyph.texture_size = (mgl_vec2i){ width, height }; + new_glyph.advance = glyph_advance/GLYPH_UPSAMPLE; + *glyph = new_glyph; + + if(mgl_font_char_map_insert(&self->char_map, codepoint, &new_glyph, &existing_char_entry) != 0) { + free(pixels); + memset(glyph, 0, sizeof(mgl_font_glyph)); + return 0; + } + existing_char_entry->render_offset = render_offset; - free(self->font_atlas.atlas); - self->font_atlas.atlas = NULL; - self->font_atlas.width = 0; - self->font_atlas.height = 0; + if(height > self->current_line_max_height) + self->current_line_max_height = height; - free(self->packed_chars); - self->packed_chars = NULL; - self->num_packed_chars = 0; -} + self->font_atlas.pointer_position.x += width + GLYPH_PADDING; + } -int mgl_font_get_glyph(const mgl_font *self, uint32_t codepoint, mgl_font_glyph *glyph) { - stbtt_packedchar *packed_chars = self->packed_chars; - if(codepoint < 0 || codepoint >= 0 + self->num_packed_chars) - return -1; + existing_char_entry->rendered = true; + if(!pixels) { + if(codepoint == ' ' || codepoint == '\t') { + return 0; + } else { + memset(glyph, 0, sizeof(mgl_font_glyph)); + 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, 0); + const int res = mgl_texture_update(&self->texture, pixels, render_offset.x, render_offset.y, width, height, MGL_IMAGE_FORMAT_ALPHA); + free(pixels); + return res; +} - 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; +int mgl_font_get_kerning(const mgl_font *self, uint32_t prev_codepoint, uint32_t codepoint) { return 0; + /* TODO: */ + /*return stbtt_GetCodepointKernAdvance(self->font_info, prev_codepoint, codepoint);*/ } diff --git a/src/graphics/font_char_map.c b/src/graphics/font_char_map.c new file mode 100644 index 0000000..abaa56a --- /dev/null +++ b/src/graphics/font_char_map.c @@ -0,0 +1,214 @@ +#include "../../include/mgl/graphics/font_char_map.h" +#include + +/* TODO: Optimize % to & */ + +/* Optimized div by sizeof(mgl_font_char_entry*) */ +#if INTPTR_MAX == INT64_MAX +#define CAP_NUM_ENTRIES(cap) ((cap) >> 3) +#elif INTPTR_MAX == INT32_MAX +#define CAP_NUM_ENTRIES(cap) ((cap) >> 2) +#else +#error Unsupported system. Only systems where a pointer is 32-bits or 64-bits are supported. +#endif + +static size_t align_to(size_t value, size_t align) { + const size_t is_aligned = (value & (align - 1)); + if(is_aligned == 0) { + return value; + } else { + return (value & ~(align - 1)) + align; + } +} + +static void mgl_font_char_entry_deinit(mgl_font_char_entry *self) { + mgl_font_char_entry *next = self->next; + while(next) { + mgl_font_char_entry *next_next = next->next; + free(next); + next = next_next; + } +} + +static void mgl_font_char_map_insert_entry(mgl_font_char_map *self, size_t index, uint32_t key, const mgl_font_glyph *value, mgl_font_char_entry *entry) { + entry->key = key; + entry->value = *value; + entry->next = self->entries[index]; + self->entries[index] = entry; +} + +static void mgl_font_char_map_reorder_nodes(mgl_font_char_map *self, size_t prev_capacity) { + for(size_t i = 0; i < CAP_NUM_ENTRIES(prev_capacity); ++i) { + mgl_font_char_entry *entry = self->entries[i]; + if(!entry) + continue; + + mgl_font_char_entry *prev_entry = NULL; + while(entry) { + const uint32_t hash = entry->key; + const size_t index = hash % CAP_NUM_ENTRIES(self->capacity); + mgl_font_char_entry *next_entry = entry->next; /* store next here so the next insert below doesn't cause an infinite loop */ + + if(index != i) { + /* Remove entry by replacing this entry with the next entry */ + if(prev_entry) + prev_entry->next = next_entry; + else + self->entries[i] = next_entry; + + /* Insert the entry at the new index */ + mgl_font_char_map_insert_entry(self, index, entry->key, &entry->value, entry); + } else { + prev_entry = entry; + } + + entry = next_entry; + } + } +} + +static int mgl_font_char_map_ensure_capacity(mgl_font_char_map *self, size_t target_capacity) { + if(self->capacity >= target_capacity) + return 0; + + size_t capacity = self->capacity; + if(capacity == 0) + capacity = 8; + + while(capacity < target_capacity) { + capacity = capacity + (capacity >> 1); /* *= 1.5 = 1 + 0.5 */ + } + + capacity = align_to(capacity, sizeof(mgl_font_char_entry*)); + void *new_data = realloc(self->entries, capacity); + if(!new_data) + return -1; + + const size_t prev_capacity = self->capacity; + self->entries = new_data; + self->capacity = capacity; + + for(size_t i = CAP_NUM_ENTRIES(prev_capacity); i < CAP_NUM_ENTRIES(self->capacity); ++i) { + self->entries[i] = NULL; + } + + mgl_font_char_map_reorder_nodes(self, prev_capacity); + return 0; +} + +void mgl_font_char_map_init(mgl_font_char_map *self) { + self->entries = NULL; + self->size = 0; + self->capacity = 0; +} + +void mgl_font_char_map_deinit(mgl_font_char_map *self) { + if(self->entries) { + for(size_t i = 0; i < CAP_NUM_ENTRIES(self->capacity); ++i) { + if(self->entries[i]) { + mgl_font_char_entry_deinit(self->entries[i]); + free(self->entries[i]); + } + } + free(self->entries); + self->entries = NULL; + } + self->size = 0; + self->capacity = 0; +} + +int mgl_font_char_map_insert(mgl_font_char_map *self, uint32_t key, const mgl_font_glyph *value, mgl_font_char_entry **inserted_entry) { + if(mgl_font_char_map_ensure_capacity(self, (self->size + 1) * sizeof(mgl_font_char_entry*)) != 0) + return -1; + + ++self->size; + const uint32_t hash = key; + const size_t index = hash % CAP_NUM_ENTRIES(self->capacity); + + mgl_font_char_entry *entry = malloc(sizeof(mgl_font_char_entry)); + if(!entry) + return -1; + + mgl_font_char_map_insert_entry(self, index, key, value, entry); + entry->render_offset = (mgl_vec2i){ 0, 0 }; + entry->rendered = false; + if(inserted_entry) + *inserted_entry = entry; + + return 0; +} + +mgl_font_char_entry* mgl_font_char_map_get(const mgl_font_char_map *self, uint32_t key) { + if(self->capacity == 0) + return NULL; + + const uint32_t hash = key; + const size_t index = hash % CAP_NUM_ENTRIES(self->capacity); + + mgl_font_char_entry *entry = self->entries[index]; + while(entry) { + if(entry->key == key) + return entry; + entry = entry->next; + } + + return NULL; +} + +void mgl_font_char_map_clear_rendered(mgl_font_char_map *self) { + if(!self->entries) + return; + + for(size_t i = 0; i < CAP_NUM_ENTRIES(self->capacity); ++i) { + mgl_font_char_entry *entry = self->entries[i]; + if(!entry) + continue; + + while(entry) { + entry->rendered = false; + entry = entry->next; + } + } +} + +mgl_font_char_iterator mgl_font_char_map_begin(mgl_font_char_map *self) { + mgl_font_char_iterator it; + it.char_map = self; + it.index = 0; + it.value = NULL; + + if(!self->entries) + return it; + + for(size_t i = 0; i < CAP_NUM_ENTRIES(self->capacity); ++i) { + mgl_font_char_entry *entry = self->entries[i]; + if(entry) { + it.index = i; + it.value = entry; + break; + } + } + + return it; +} + +void mgl_font_char_iterator_next(mgl_font_char_iterator *self) { + if(!self->value) + return; + + if(self->value->next) { + self->value = self->value->next; + return; + } + + for(size_t i = self->index + 1; i < CAP_NUM_ENTRIES(self->char_map->capacity); ++i) { + mgl_font_char_entry *entry = self->char_map->entries[i]; + if(entry) { + self->index = i; + self->value = entry; + return; + } + } + + self->value = NULL; +} diff --git a/src/graphics/sprite.c b/src/graphics/sprite.c index 231b554..2aecdd1 100644 --- a/src/graphics/sprite.c +++ b/src/graphics/sprite.c @@ -26,20 +26,27 @@ void mgl_sprite_draw(mgl_context *context, mgl_sprite *sprite) { if(!sprite->texture) return; + float texture_right = 1.0f; + float texture_bottom = 1.0f; + if(sprite->texture->pixel_coordinates) { + texture_right = sprite->texture->width; + texture_bottom = sprite->texture->height; + } + context->gl.glColor4ub(sprite->color.r, sprite->color.g, sprite->color.b, sprite->color.a); - context->gl.glBindTexture(GL_TEXTURE_2D, sprite->texture->id); + mgl_texture_use(sprite->texture); 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.glTexCoord2f(1.0f, 0.0f); + context->gl.glTexCoord2f(texture_right, 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.glTexCoord2f(texture_right, texture_bottom); 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.glTexCoord2f(0.0f, texture_bottom); 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); + mgl_texture_use(NULL); } diff --git a/src/graphics/text.c b/src/graphics/text.c index d330dc0..ea60b6a 100644 --- a/src/graphics/text.c +++ b/src/graphics/text.c @@ -4,6 +4,8 @@ #include "../../include/mgl/mgl.h" #include +/* TODO: Cache mgl_font_get_glyph */ + #define TAB_WIDTH 4 static float max_float(float a, float b) { @@ -95,7 +97,7 @@ mgl_vec2f mgl_text_get_bounds(const mgl_text *self) { return self->bounds; } -static void mgl_text_draw_glyph(mgl_context *context, mgl_font_glyph *glyph, mgl_vec2f position) { +static void mgl_text_draw_glyph(mgl_context *context, mgl_font_glyph *glyph, mgl_vec2i 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); @@ -116,11 +118,11 @@ void mgl_text_draw(mgl_context *context, mgl_text *text) { return; mgl_font_glyph glyph; - mgl_vec2f position = text->position; + mgl_vec2i position = (mgl_vec2i){ text->position.x, text->position.y }; position.y += text->font->character_size; context->gl.glColor4ub(text->color.r, text->color.g, text->color.b, text->color.a); - context->gl.glBindTexture(GL_TEXTURE_2D, text->font->texture.id); + mgl_texture_use(&text->font->texture); context->gl.glBegin(GL_QUADS); for(size_t i = 0; i < text->text_size;) { unsigned char *cp = (unsigned char*)&text->text[i]; @@ -147,5 +149,5 @@ void mgl_text_draw(mgl_context *context, mgl_text *text) { i += clen; } context->gl.glEnd(); - context->gl.glBindTexture(GL_TEXTURE_2D, 0); + mgl_texture_use(NULL); } diff --git a/src/graphics/texture.c b/src/graphics/texture.c index f6549f1..2387fa7 100644 --- a/src/graphics/texture.c +++ b/src/graphics/texture.c @@ -3,7 +3,7 @@ #include "../../include/mgl/mgl.h" #include -/* TODO: Check for glTexImage2D failure */ +/* TODO: Check for glTexImage2D/glTexSubImage2D failure */ static int mgl_texture_format_to_opengl_format(mgl_texture_format format) { switch(format) { @@ -49,11 +49,31 @@ static mgl_texture_format mgl_image_format_to_mgl_texture_format(mgl_image_forma return 0; } -int mgl_texture_load_from_file(mgl_texture *self, const char *filepath, mgl_texture_load_options *load_options) { +int mgl_texture_init(mgl_texture *self) { self->id = 0; self->width = 0; self->height = 0; - + self->format = 0; + self->max_width = 0; + self->max_height = 0; + self->pixel_coordinates = false; + + mgl_context *context = mgl_get_context(); + context->gl.glGenTextures(1, &self->id); + if(self->id == 0) { + fprintf(stderr, "Error: failed to init texture (glGenTextures failed)\n"); + return -1; + } + + int max_texture_size = 0; + context->gl.glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size); + self->max_width = max_texture_size; + self->max_height = max_texture_size; + + return 0; +} + +int mgl_texture_load_from_file(mgl_texture *self, const char *filepath, mgl_texture_load_options *load_options) { mgl_image image; if(mgl_image_load_from_file(&image, filepath) != 0) return -1; @@ -68,21 +88,22 @@ int mgl_texture_load_from_image(mgl_texture *self, const mgl_image *image, mgl_t } int mgl_texture_load_from_memory(mgl_texture *self, const unsigned char *data, int width, int height, mgl_image_format format, mgl_texture_load_options *load_options) { - self->id = 0; + if(width < 0 || height < 0) + return -1; + + if(width > self->max_width || height > self->max_height) + return -1; + self->width = width; self->height = height; self->format = mgl_image_format_to_mgl_texture_format(format); + self->pixel_coordinates = load_options ? load_options->pixel_coordinates : false; - mgl_context *context = mgl_get_context(); - context->gl.glGenTextures(1, &self->id); - if(self->id == 0) { - fprintf(stderr, "Error: failed to load image from memory (glGenTextures failed)\n"); - mgl_texture_unload(self); - 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); + int opengl_texture_format = mgl_texture_format_to_opengl_format(self->format); + if(load_options && load_options->compressed) + opengl_texture_format = mgl_texture_format_to_compressed_opengl_format(self->format); + mgl_context *context = mgl_get_context(); 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); @@ -94,13 +115,77 @@ int mgl_texture_load_from_memory(mgl_texture *self, const unsigned char *data, i return 0; } +int mgl_texture_update(mgl_texture *self, const unsigned char *data, int offset_x, int offset_y, int width, int height, mgl_image_format format) { + if(offset_x + width > self->width || offset_y + height > self->height) + return -1; + + mgl_context *context = mgl_get_context(); + context->gl.glBindTexture(GL_TEXTURE_2D, self->id); + const mgl_texture_format texture_format = mgl_image_format_to_mgl_texture_format(format); + /* TODO: TODO: Only do one glTexSubImage2D */ +#if 1 + for(int i = 0; i < height; ++i) { + context->gl.glTexSubImage2D(GL_TEXTURE_2D, 0, offset_x, offset_y + i, width, 1, mgl_texture_format_to_source_opengl_format(texture_format), GL_UNSIGNED_BYTE, data + (i * width)); + } +#else + context->gl.glTexSubImage2D(GL_TEXTURE_2D, 0, offset_x, offset_y, width, height, mgl_texture_format_to_source_opengl_format(texture_format), GL_UNSIGNED_BYTE, data); +#endif + context->gl.glBindTexture(GL_TEXTURE_2D, 0); + return 0; +} + +int mgl_texture_resize(mgl_texture *self, int new_width, int new_height, mgl_texture_load_options *load_options) { + if(new_width == self->width && new_height == self->height) + return 0; + + if(new_width < 0 || new_height < 0) + return -1; + + if(new_width > self->max_width || new_height > self->max_height) + return -1; + + self->width = new_width; + self->height = new_height; + + 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); + + mgl_context *context = mgl_get_context(); + 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, NULL); + context->gl.glBindTexture(GL_TEXTURE_2D, 0); + return 0; +} + +void mgl_texture_use(const mgl_texture *texture) { + mgl_context *context = mgl_get_context(); + + if(texture) { + float matrix[16] = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f, + }; + + if(texture->pixel_coordinates) { + matrix[0] = 1.0f / (float)texture->width; + matrix[5] = 1.0f / (float)texture->height; + } + + context->gl.glMatrixMode(GL_TEXTURE); + context->gl.glLoadMatrixf(matrix); + context->gl.glMatrixMode(GL_MODELVIEW); + + context->gl.glBindTexture(GL_TEXTURE_2D, texture->id); + } else { + context->gl.glBindTexture(GL_TEXTURE_2D, 0); + } +} + void mgl_texture_unload(mgl_texture *self) { mgl_context *context = mgl_get_context(); if(self->id) { context->gl.glDeleteTextures(1, &self->id); self->id = 0; } - self->width = 0; - self->height = 0; - self->format = 0; } diff --git a/src/graphics/vertex.c b/src/graphics/vertex.c index dff9661..110c86d 100644 --- a/src/graphics/vertex.c +++ b/src/graphics/vertex.c @@ -5,6 +5,10 @@ void mgl_vertices_draw(mgl_context *context, const mgl_vertex *vertices, size_t if(vertex_count == 0) return; + context->gl.glMatrixMode(GL_TEXTURE); + context->gl.glLoadIdentity(); + context->gl.glMatrixMode(GL_MODELVIEW); + context->gl.glVertexPointer(2, GL_FLOAT, sizeof(mgl_vertex), (void*)&vertices[0].position); context->gl.glTexCoordPointer(2, GL_FLOAT, sizeof(mgl_vertex), (void*)&vertices[0].texcoords); context->gl.glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(mgl_vertex), (void*)&vertices[0].color); diff --git a/src/graphics/vertex_buffer.c b/src/graphics/vertex_buffer.c index 325665c..9d15d47 100644 --- a/src/graphics/vertex_buffer.c +++ b/src/graphics/vertex_buffer.c @@ -100,12 +100,12 @@ void mgl_vertex_buffer_draw(mgl_context *context, mgl_vertex_buffer *vertex_buff context->gl.glPushMatrix(); context->gl.glTranslatef(vertex_buffer->position.x, vertex_buffer->position.y, 0.0f); - context->gl.glBindTexture(GL_TEXTURE_2D, texture ? texture->id : 0); + mgl_texture_use(texture); context->gl.glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer->id); mgl_vertex_buffer_set_gl_buffer_pointers(context); context->gl.glDrawArrays(mgl_primitive_type_to_gl_mode(vertex_buffer->primitive_type), 0, vertex_buffer->vertex_count); context->gl.glBindBuffer(GL_ARRAY_BUFFER, 0); - context->gl.glBindTexture(GL_TEXTURE_2D, 0); + mgl_texture_use(NULL); context->gl.glPopMatrix(); } -- cgit v1.2.3