diff options
author | dec05eba <dec05eba@protonmail.com> | 2021-11-15 08:20:13 +0100 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2021-11-15 08:20:13 +0100 |
commit | a3c6774f211ee765f910df76837548bdadd4e959 (patch) | |
tree | 499b29166c04fa62cb946c7d395f8a5299c78fbf | |
parent | c4f84e1969f4c856a5bf0352e99fcb73a4cf56cf (diff) |
Add dynamic font atlas creation (not finished)
-rw-r--r-- | TODO | 3 | ||||
-rw-r--r-- | external/stb_rect_pack.h | 623 | ||||
-rw-r--r-- | include/mgl/gl.h | 3 | ||||
-rw-r--r-- | include/mgl/gl_macro.h | 3 | ||||
-rw-r--r-- | include/mgl/graphics/font.h | 34 | ||||
-rw-r--r-- | include/mgl/graphics/font_char_map.h | 50 | ||||
-rw-r--r-- | include/mgl/graphics/font_glyph.h | 14 | ||||
-rw-r--r-- | include/mgl/graphics/rectangle.h | 2 | ||||
-rw-r--r-- | include/mgl/graphics/texture.h | 10 | ||||
-rw-r--r-- | include/mgl/system/fileutils.h | 6 | ||||
-rw-r--r-- | src/gl.c | 5 | ||||
-rw-r--r-- | src/graphics/font.c | 283 | ||||
-rw-r--r-- | src/graphics/font_char_map.c | 214 | ||||
-rw-r--r-- | src/graphics/sprite.c | 17 | ||||
-rw-r--r-- | src/graphics/text.c | 10 | ||||
-rw-r--r-- | src/graphics/texture.c | 117 | ||||
-rw-r--r-- | src/graphics/vertex.c | 4 | ||||
-rw-r--r-- | src/graphics/vertex_buffer.c | 4 | ||||
-rw-r--r-- | src/window/window.c | 5 | ||||
-rw-r--r-- | tests/main.c | 181 |
20 files changed, 743 insertions, 845 deletions
@@ -2,4 +2,5 @@ Bind texture and cache the bound texture to reduce calls to opengl.\ Use gl triangle instead of quad.\ Support using multiple textures in shaders by using glActiveTexture for each one and set the uniform sampler2D value for each as as the index.\ Make sure clock is monotonic (there has been some issues with CLOCK\_MONOTONIC not being monotonic on linux).\ -Verify if using a separate glx context for every window is the correct approach.
\ No newline at end of file +Verify if using a separate glx context for every window is the correct approach.\ +Experiment with reducing latency by removing GLX_DOUBLEBUFFER and using glFlush&&glFinish&&glXWaitVideoSyncSGI(1, 0, &rem);. Only glFinish is needed when using a compositor and that fixes the flickering. Flipping also needs to be enabled in gpu driver settings. Use glXWaitVideoSyncSGI to limit fps to vsync. Tearing fix with flipping for fullscreen applications only works when fullscreen covers all monitors which is usually not the case, so it really only works when one monitor is plugged in. diff --git a/external/stb_rect_pack.h b/external/stb_rect_pack.h deleted file mode 100644 index ab24c48..0000000 --- a/external/stb_rect_pack.h +++ /dev/null @@ -1,623 +0,0 @@ -// stb_rect_pack.h - v1.01 - public domain - rectangle packing -// Sean Barrett 2014 -// -// Useful for e.g. packing rectangular textures into an atlas. -// Does not do rotation. -// -// Before #including, -// -// #define STB_RECT_PACK_IMPLEMENTATION -// -// in the file that you want to have the implementation. -// -// Not necessarily the awesomest packing method, but better than -// the totally naive one in stb_truetype (which is primarily what -// this is meant to replace). -// -// Has only had a few tests run, may have issues. -// -// More docs to come. -// -// No memory allocations; uses qsort() and assert() from stdlib. -// Can override those by defining STBRP_SORT and STBRP_ASSERT. -// -// This library currently uses the Skyline Bottom-Left algorithm. -// -// Please note: better rectangle packers are welcome! Please -// implement them to the same API, but with a different init -// function. -// -// Credits -// -// Library -// Sean Barrett -// Minor features -// Martins Mozeiko -// github:IntellectualKitty -// -// Bugfixes / warning fixes -// Jeremy Jaussaud -// Fabian Giesen -// -// Version history: -// -// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section -// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles -// 0.99 (2019-02-07) warning fixes -// 0.11 (2017-03-03) return packing success/fail result -// 0.10 (2016-10-25) remove cast-away-const to avoid warnings -// 0.09 (2016-08-27) fix compiler warnings -// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) -// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) -// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort -// 0.05: added STBRP_ASSERT to allow replacing assert -// 0.04: fixed minor bug in STBRP_LARGE_RECTS support -// 0.01: initial release -// -// LICENSE -// -// See end of file for license information. - -////////////////////////////////////////////////////////////////////////////// -// -// INCLUDE SECTION -// - -#ifndef STB_INCLUDE_STB_RECT_PACK_H -#define STB_INCLUDE_STB_RECT_PACK_H - -#define STB_RECT_PACK_VERSION 1 - -#ifdef STBRP_STATIC -#define STBRP_DEF static -#else -#define STBRP_DEF extern -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct stbrp_context stbrp_context; -typedef struct stbrp_node stbrp_node; -typedef struct stbrp_rect stbrp_rect; - -typedef int stbrp_coord; - -#define STBRP__MAXVAL 0x7fffffff -// Mostly for internal use, but this is the maximum supported coordinate value. - -STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); -// Assign packed locations to rectangles. The rectangles are of type -// 'stbrp_rect' defined below, stored in the array 'rects', and there -// are 'num_rects' many of them. -// -// Rectangles which are successfully packed have the 'was_packed' flag -// set to a non-zero value and 'x' and 'y' store the minimum location -// on each axis (i.e. bottom-left in cartesian coordinates, top-left -// if you imagine y increasing downwards). Rectangles which do not fit -// have the 'was_packed' flag set to 0. -// -// You should not try to access the 'rects' array from another thread -// while this function is running, as the function temporarily reorders -// the array while it executes. -// -// To pack into another rectangle, you need to call stbrp_init_target -// again. To continue packing into the same rectangle, you can call -// this function again. Calling this multiple times with multiple rect -// arrays will probably produce worse packing results than calling it -// a single time with the full rectangle array, but the option is -// available. -// -// The function returns 1 if all of the rectangles were successfully -// packed and 0 otherwise. - -struct stbrp_rect -{ - // reserved for your use: - int id; - - // input: - stbrp_coord w, h; - - // output: - stbrp_coord x, y; - int was_packed; // non-zero if valid packing - -}; // 16 bytes, nominally - - -STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); -// Initialize a rectangle packer to: -// pack a rectangle that is 'width' by 'height' in dimensions -// using temporary storage provided by the array 'nodes', which is 'num_nodes' long -// -// You must call this function every time you start packing into a new target. -// -// There is no "shutdown" function. The 'nodes' memory must stay valid for -// the following stbrp_pack_rects() call (or calls), but can be freed after -// the call (or calls) finish. -// -// Note: to guarantee best results, either: -// 1. make sure 'num_nodes' >= 'width' -// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' -// -// If you don't do either of the above things, widths will be quantized to multiples -// of small integers to guarantee the algorithm doesn't run out of temporary storage. -// -// If you do #2, then the non-quantized algorithm will be used, but the algorithm -// may run out of temporary storage and be unable to pack some rectangles. - -STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); -// Optionally call this function after init but before doing any packing to -// change the handling of the out-of-temp-memory scenario, described above. -// If you call init again, this will be reset to the default (false). - - -STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); -// Optionally select which packing heuristic the library should use. Different -// heuristics will produce better/worse results for different data sets. -// If you call init again, this will be reset to the default. - -enum -{ - STBRP_HEURISTIC_Skyline_default=0, - STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, - STBRP_HEURISTIC_Skyline_BF_sortHeight -}; - - -////////////////////////////////////////////////////////////////////////////// -// -// the details of the following structures don't matter to you, but they must -// be visible so you can handle the memory allocations for them - -struct stbrp_node -{ - stbrp_coord x,y; - stbrp_node *next; -}; - -struct stbrp_context -{ - int width; - int height; - int align; - int init_mode; - int heuristic; - int num_nodes; - stbrp_node *active_head; - stbrp_node *free_head; - stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' -}; - -#ifdef __cplusplus -} -#endif - -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// IMPLEMENTATION SECTION -// - -#ifdef STB_RECT_PACK_IMPLEMENTATION -#ifndef STBRP_SORT -#include <stdlib.h> -#define STBRP_SORT qsort -#endif - -#ifndef STBRP_ASSERT -#include <assert.h> -#define STBRP_ASSERT assert -#endif - -#ifdef _MSC_VER -#define STBRP__NOTUSED(v) (void)(v) -#define STBRP__CDECL __cdecl -#else -#define STBRP__NOTUSED(v) (void)sizeof(v) -#define STBRP__CDECL -#endif - -enum -{ - STBRP__INIT_skyline = 1 -}; - -STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) -{ - switch (context->init_mode) { - case STBRP__INIT_skyline: - STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); - context->heuristic = heuristic; - break; - default: - STBRP_ASSERT(0); - } -} - -STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) -{ - if (allow_out_of_mem) - // if it's ok to run out of memory, then don't bother aligning them; - // this gives better packing, but may fail due to OOM (even though - // the rectangles easily fit). @TODO a smarter approach would be to only - // quantize once we've hit OOM, then we could get rid of this parameter. - context->align = 1; - else { - // if it's not ok to run out of memory, then quantize the widths - // so that num_nodes is always enough nodes. - // - // I.e. num_nodes * align >= width - // align >= width / num_nodes - // align = ceil(width/num_nodes) - - context->align = (context->width + context->num_nodes-1) / context->num_nodes; - } -} - -STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) -{ - int i; - - for (i=0; i < num_nodes-1; ++i) - nodes[i].next = &nodes[i+1]; - nodes[i].next = NULL; - context->init_mode = STBRP__INIT_skyline; - context->heuristic = STBRP_HEURISTIC_Skyline_default; - context->free_head = &nodes[0]; - context->active_head = &context->extra[0]; - context->width = width; - context->height = height; - context->num_nodes = num_nodes; - stbrp_setup_allow_out_of_mem(context, 0); - - // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) - context->extra[0].x = 0; - context->extra[0].y = 0; - context->extra[0].next = &context->extra[1]; - context->extra[1].x = (stbrp_coord) width; - context->extra[1].y = (1<<30); - context->extra[1].next = NULL; -} - -// find minimum y position if it starts at x1 -static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) -{ - stbrp_node *node = first; - int x1 = x0 + width; - int min_y, visited_width, waste_area; - - STBRP__NOTUSED(c); - - STBRP_ASSERT(first->x <= x0); - - #if 0 - // skip in case we're past the node - while (node->next->x <= x0) - ++node; - #else - STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency - #endif - - STBRP_ASSERT(node->x <= x0); - - min_y = 0; - waste_area = 0; - visited_width = 0; - while (node->x < x1) { - if (node->y > min_y) { - // raise min_y higher. - // we've accounted for all waste up to min_y, - // but we'll now add more waste for everything we've visted - waste_area += visited_width * (node->y - min_y); - min_y = node->y; - // the first time through, visited_width might be reduced - if (node->x < x0) - visited_width += node->next->x - x0; - else - visited_width += node->next->x - node->x; - } else { - // add waste area - int under_width = node->next->x - node->x; - if (under_width + visited_width > width) - under_width = width - visited_width; - waste_area += under_width * (min_y - node->y); - visited_width += under_width; - } - node = node->next; - } - - *pwaste = waste_area; - return min_y; -} - -typedef struct -{ - int x,y; - stbrp_node **prev_link; -} stbrp__findresult; - -static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) -{ - int best_waste = (1<<30), best_x, best_y = (1 << 30); - stbrp__findresult fr; - stbrp_node **prev, *node, *tail, **best = NULL; - - // align to multiple of c->align - width = (width + c->align - 1); - width -= width % c->align; - STBRP_ASSERT(width % c->align == 0); - - // if it can't possibly fit, bail immediately - if (width > c->width || height > c->height) { - fr.prev_link = NULL; - fr.x = fr.y = 0; - return fr; - } - - node = c->active_head; - prev = &c->active_head; - while (node->x + width <= c->width) { - int y,waste; - y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); - if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL - // bottom left - if (y < best_y) { - best_y = y; - best = prev; - } - } else { - // best-fit - if (y + height <= c->height) { - // can only use it if it first vertically - if (y < best_y || (y == best_y && waste < best_waste)) { - best_y = y; - best_waste = waste; - best = prev; - } - } - } - prev = &node->next; - node = node->next; - } - - best_x = (best == NULL) ? 0 : (*best)->x; - - // if doing best-fit (BF), we also have to try aligning right edge to each node position - // - // e.g, if fitting - // - // ____________________ - // |____________________| - // - // into - // - // | | - // | ____________| - // |____________| - // - // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned - // - // This makes BF take about 2x the time - - if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { - tail = c->active_head; - node = c->active_head; - prev = &c->active_head; - // find first node that's admissible - while (tail->x < width) - tail = tail->next; - while (tail) { - int xpos = tail->x - width; - int y,waste; - STBRP_ASSERT(xpos >= 0); - // find the left position that matches this - while (node->next->x <= xpos) { - prev = &node->next; - node = node->next; - } - STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); - y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); - if (y + height <= c->height) { - if (y <= best_y) { - if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { - best_x = xpos; - STBRP_ASSERT(y <= best_y); - best_y = y; - best_waste = waste; - best = prev; - } - } - } - tail = tail->next; - } - } - - fr.prev_link = best; - fr.x = best_x; - fr.y = best_y; - return fr; -} - -static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) -{ - // find best position according to heuristic - stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); - stbrp_node *node, *cur; - - // bail if: - // 1. it failed - // 2. the best node doesn't fit (we don't always check this) - // 3. we're out of memory - if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { - res.prev_link = NULL; - return res; - } - - // on success, create new node - node = context->free_head; - node->x = (stbrp_coord) res.x; - node->y = (stbrp_coord) (res.y + height); - - context->free_head = node->next; - - // insert the new node into the right starting point, and - // let 'cur' point to the remaining nodes needing to be - // stiched back in - - cur = *res.prev_link; - if (cur->x < res.x) { - // preserve the existing one, so start testing with the next one - stbrp_node *next = cur->next; - cur->next = node; - cur = next; - } else { - *res.prev_link = node; - } - - // from here, traverse cur and free the nodes, until we get to one - // that shouldn't be freed - while (cur->next && cur->next->x <= res.x + width) { - stbrp_node *next = cur->next; - // move the current node to the free list - cur->next = context->free_head; - context->free_head = cur; - cur = next; - } - - // stitch the list back in - node->next = cur; - - if (cur->x < res.x + width) - cur->x = (stbrp_coord) (res.x + width); - -#ifdef _DEBUG - cur = context->active_head; - while (cur->x < context->width) { - STBRP_ASSERT(cur->x < cur->next->x); - cur = cur->next; - } - STBRP_ASSERT(cur->next == NULL); - - { - int count=0; - cur = context->active_head; - while (cur) { - cur = cur->next; - ++count; - } - cur = context->free_head; - while (cur) { - cur = cur->next; - ++count; - } - STBRP_ASSERT(count == context->num_nodes+2); - } -#endif - - return res; -} - -static int STBRP__CDECL rect_height_compare(const void *a, const void *b) -{ - const stbrp_rect *p = (const stbrp_rect *) a; - const stbrp_rect *q = (const stbrp_rect *) b; - if (p->h > q->h) - return -1; - if (p->h < q->h) - return 1; - return (p->w > q->w) ? -1 : (p->w < q->w); -} - -static int STBRP__CDECL rect_original_order(const void *a, const void *b) -{ - const stbrp_rect *p = (const stbrp_rect *) a; - const stbrp_rect *q = (const stbrp_rect *) b; - return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); -} - -STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) -{ - int i, all_rects_packed = 1; - - // we use the 'was_packed' field internally to allow sorting/unsorting - for (i=0; i < num_rects; ++i) { - rects[i].was_packed = i; - } - - // sort according to heuristic - STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); - - for (i=0; i < num_rects; ++i) { - if (rects[i].w == 0 || rects[i].h == 0) { - rects[i].x = rects[i].y = 0; // empty rect needs no space - } else { - stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); - if (fr.prev_link) { - rects[i].x = (stbrp_coord) fr.x; - rects[i].y = (stbrp_coord) fr.y; - } else { - rects[i].x = rects[i].y = STBRP__MAXVAL; - } - } - } - - // unsort - STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); - - // set was_packed flags and all_rects_packed status - for (i=0; i < num_rects; ++i) { - rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); - if (!rects[i].was_packed) - all_rects_packed = 0; - } - - // return the all_rects_packed status - return all_rects_packed; -} -#endif - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/
\ No newline at end of file diff --git a/include/mgl/gl.h b/include/mgl/gl.h index fc3ad3c..75bc764 100644 --- a/include/mgl/gl.h +++ b/include/mgl/gl.h @@ -27,6 +27,7 @@ typedef struct { void (*glGenTextures)(int n, unsigned int *textures); void (*glDeleteTextures)(int n, const unsigned int *textures); void (*glTexImage2D)(unsigned int target, int level, int internalFormat, int width, int height, int border, unsigned int format, unsigned int type, const void *pixels); + void (*glTexSubImage2D)(unsigned int target, int level, int xoffset, int yoffset, int width, int height, unsigned int format, unsigned int type, const void *pixels); void (*glBindTexture)(unsigned int target, unsigned int texture); void (*glTexParameteri)(unsigned int target, unsigned int pname, int param); void (*glHint)(unsigned int target, unsigned int mode); @@ -40,6 +41,7 @@ typedef struct { void (*glPushMatrix)(void); void (*glPopMatrix)(void); void (*glLoadIdentity)(void); + void (*glLoadMatrixf)(const float *m); void (*glTranslatef)(float x, float y, float z); void (*glGenBuffers)(int n, unsigned int *buffers); void (*glBindBuffer)(unsigned int target, unsigned int buffer); @@ -69,6 +71,7 @@ typedef struct { void (*glUniform2f)(int location, float v0, float v1); unsigned int (*glGetError)(void); const unsigned char* (*glGetString)(unsigned int name); + void (*glGetIntegerv)(unsigned int pname, int *params); /* Optional*/ void (*glXSwapIntervalEXT)(Display * dpy, GLXDrawable drawable, int interval); diff --git a/include/mgl/gl_macro.h b/include/mgl/gl_macro.h index aa264ee..5e634df 100644 --- a/include/mgl/gl_macro.h +++ b/include/mgl/gl_macro.h @@ -29,6 +29,7 @@ #define GL_TEXTURE_MAG_FILTER 0x2800 #define GL_TEXTURE_MIN_FILTER 0x2801 #define GL_UNSIGNED_BYTE 0x1401 +#define GL_MAX_TEXTURE_SIZE 0x0D33 #define GL_ALPHA 0x1906 #define GL_LUMINANCE 0x1909 @@ -59,7 +60,9 @@ #define GL_QUAD_STRIP 0x0008 #define GL_POLYGON 0x0009 +#define GL_MODELVIEW 0x1700 #define GL_PROJECTION 0x1701 +#define GL_TEXTURE 0x1702 #define GL_STREAM_DRAW 0x88E0 #define GL_STATIC_DRAW 0x88E4 diff --git a/include/mgl/graphics/font.h b/include/mgl/graphics/font.h index c4904ff..adcc8a7 100644 --- a/include/mgl/graphics/font.h +++ b/include/mgl/graphics/font.h @@ -1,37 +1,45 @@ #ifndef MGL_FONT_H #define MGL_FONT_H -#include "../system/vec.h" +#include "../system/fileutils.h" +#include "font_char_map.h" #include "texture.h" #include <stdint.h> +typedef struct mgl_memory_mapped_file mgl_memory_mapped_file; typedef struct mgl_font mgl_font; -typedef struct { - mgl_vec2f position; - mgl_vec2f size; - mgl_vec2f texture_position; - mgl_vec2f texture_size; - float advance; -} mgl_font_glyph; +typedef enum { + MGL_ATLAS_SECTION_INITIAL, + MGL_ATLAS_SECTION_RIGHT, + MGL_ATLAS_SECTION_BOTTOM +} mgl_font_atlas_render_section; typedef struct { - unsigned char *atlas; int width; int height; + int prev_width; + int prev_height; + mgl_vec2i pointer_position; + mgl_font_atlas_render_section render_section; } mgl_font_atlas; struct mgl_font { mgl_texture texture; mgl_font_atlas font_atlas; unsigned int character_size; - void *packed_chars; - uint32_t num_packed_chars; + mgl_font_char_map char_map; + int current_line_max_height; + void *font_info; }; -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); void mgl_font_unload(mgl_font *self); -int mgl_font_get_glyph(const mgl_font *self, uint32_t codepoint, mgl_font_glyph *glyph); +/* Note: loads the glyph if it hasn't been loaded yet */ +int mgl_font_get_glyph(mgl_font *self, uint32_t codepoint, mgl_font_glyph *glyph); + +/* Returns the kerning */ +int mgl_font_get_kerning(const mgl_font *self, uint32_t prev_codepoint, uint32_t codepoint); #endif /* MGL_FONT_H */ diff --git a/include/mgl/graphics/font_char_map.h b/include/mgl/graphics/font_char_map.h new file mode 100644 index 0000000..1daf4a2 --- /dev/null +++ b/include/mgl/graphics/font_char_map.h @@ -0,0 +1,50 @@ +#ifndef MGL_FONT_CHAR_MAP_H +#define MGL_FONT_CHAR_MAP_H + +#include "font_glyph.h" +#include <stdint.h> +#include <stddef.h> +#include <stdbool.h> + +typedef struct mgl_font_char_entry mgl_font_char_entry; +typedef struct mgl_font_char_map mgl_font_char_map; + +struct mgl_font_char_entry { + uint32_t key; + mgl_font_glyph value; + mgl_font_char_entry *next; + /* TODO: Remove when fonts copy textures */ + mgl_vec2i render_offset; + /* TODO: Remove when fonts copy textures */ + bool rendered; +}; + +typedef struct { + mgl_font_char_map *char_map; + size_t index; + mgl_font_char_entry *value; +} mgl_font_char_iterator; + +struct mgl_font_char_map { + mgl_font_char_entry **entries; + size_t size; + size_t capacity; +}; + +void mgl_font_char_map_init(mgl_font_char_map *self); +void mgl_font_char_map_deinit(mgl_font_char_map *self); + +/* + |value| is copied. + |inserted_entry| is the newly allocated and inserted entry. the entry wont ever reallocate. may be set to NULL to ignore it. +*/ +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); +/* The returned value is only valid until the next insert */ +mgl_font_char_entry* mgl_font_char_map_get(const mgl_font_char_map *self, uint32_t key); +void mgl_font_char_map_clear_rendered(mgl_font_char_map *self); + +/* Iterator value is NULL when the end has been reached */ +mgl_font_char_iterator mgl_font_char_map_begin(mgl_font_char_map *self); +void mgl_font_char_iterator_next(mgl_font_char_iterator *self); + +#endif /* MGL_FONT_CHAR_MAP_H */ diff --git a/include/mgl/graphics/font_glyph.h b/include/mgl/graphics/font_glyph.h new file mode 100644 index 0000000..0d9cb2d --- /dev/null +++ b/include/mgl/graphics/font_glyph.h @@ -0,0 +1,14 @@ +#ifndef MGL_FONT_GLYPH_H +#define MGL_FONT_GLYPH_H + +#include "../system/vec.h" + +typedef struct { + mgl_vec2i position; + mgl_vec2i size; + mgl_vec2i texture_position; /* In pixel space */ + mgl_vec2i texture_size; /* In pixel space */ + int advance; +} mgl_font_glyph; + +#endif /* MGL_FONT_GLYPH_H */ diff --git a/include/mgl/graphics/rectangle.h b/include/mgl/graphics/rectangle.h index 5bcc521..c48d108 100644 --- a/include/mgl/graphics/rectangle.h +++ b/include/mgl/graphics/rectangle.h @@ -7,9 +7,9 @@ typedef struct mgl_context mgl_context; typedef struct { - mgl_color color; mgl_vec2f position; mgl_vec2f size; + mgl_color color; } mgl_rectangle; void mgl_rectangle_draw(mgl_context *context, mgl_rectangle *rect); diff --git a/include/mgl/graphics/texture.h b/include/mgl/graphics/texture.h index 9e7a423..61ac273 100644 --- a/include/mgl/graphics/texture.h +++ b/include/mgl/graphics/texture.h @@ -19,18 +19,28 @@ struct mgl_texture { int width; int height; mgl_texture_format format; + int max_width; + int max_height; + bool pixel_coordinates; }; typedef struct { bool compressed; /* false by default */ + bool pixel_coordinates; /* false by default, texture coordinates are normalized */ } mgl_texture_load_options; +int mgl_texture_init(mgl_texture *self); /* |load_options| can be null, in which case the default options are used */ int mgl_texture_load_from_file(mgl_texture *self, const char *filepath, mgl_texture_load_options *load_options); /* |load_options| can be null, in which case the default options are used */ int mgl_texture_load_from_image(mgl_texture *self, const mgl_image *image, mgl_texture_load_options *load_options); /* |load_options| can be null, in which case the default options are used */ 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); +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); +/* |load_options| can be null, in which case the default options are used */ +int mgl_texture_resize(mgl_texture *self, int new_width, int new_height, mgl_texture_load_options *load_options); +/* If |texture| is NULL then no texture is used */ +void mgl_texture_use(const mgl_texture *texture); void mgl_texture_unload(mgl_texture *self); #endif /* MGL_TEXTURE_H */ diff --git a/include/mgl/system/fileutils.h b/include/mgl/system/fileutils.h index 5e45cb3..5add345 100644 --- a/include/mgl/system/fileutils.h +++ b/include/mgl/system/fileutils.h @@ -4,6 +4,8 @@ #include <stddef.h> #include <stdbool.h> +typedef struct mgl_memory_mapped_file mgl_memory_mapped_file; + typedef struct { unsigned char *data; size_t size; @@ -13,11 +15,11 @@ typedef struct { bool null_terminated; /* false by default */ } mgl_file_load_options; -typedef struct { +struct mgl_memory_mapped_file { void *data; size_t size; int fd; -} mgl_memory_mapped_file; +}; typedef struct { bool readable; /* true by default */ @@ -19,7 +19,7 @@ static void* dlsym_print_fail(void *handle, const char *name) { } int mgl_gl_load(mgl_gl *self) { - const char *glx_path = "/usr/lib/libGL.so.1"; + const char *glx_path = "libGL.so.1"; self->handle = dlopen(glx_path, RTLD_LAZY); if(!self->handle) { fprintf(stderr, "dlopen(\"%s\", RTLD_LAZY) failed\n", glx_path); @@ -41,6 +41,7 @@ int mgl_gl_load(mgl_gl *self) { { &self->glGenTextures, "glGenTextures" }, { &self->glDeleteTextures, "glDeleteTextures" }, { &self->glTexImage2D, "glTexImage2D" }, + { &self->glTexSubImage2D, "glTexSubImage2D" }, { &self->glBindTexture, "glBindTexture" }, { &self->glTexParameteri, "glTexParameteri" }, { &self->glHint, "glHint" }, @@ -54,6 +55,7 @@ int mgl_gl_load(mgl_gl *self) { { &self->glPushMatrix, "glPushMatrix" }, { &self->glPopMatrix, "glPopMatrix" }, { &self->glLoadIdentity, "glLoadIdentity" }, + { &self->glLoadMatrixf, "glLoadMatrixf" }, { &self->glTranslatef, "glTranslatef" }, { &self->glGenBuffers, "glGenBuffers" }, { &self->glBindBuffer, "glBindBuffer" }, @@ -83,6 +85,7 @@ int mgl_gl_load(mgl_gl *self) { { &self->glUniform2f, "glUniform2f" }, { &self->glGetError, "glGetError" }, { &self->glGetString, "glGetString" }, + { &self->glGetIntegerv, "glGetIntegerv" }, { NULL, NULL } }; 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 <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 */ +/* 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 <stdlib.h> + +/* 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 <stdio.h> +/* 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 <stdio.h> -/* 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(); } diff --git a/src/window/window.c b/src/window/window.c index 642609b..4d72635 100644 --- a/src/window/window.c +++ b/src/window/window.c @@ -171,7 +171,7 @@ static int mgl_window_init(mgl_window *self, const char *title, int width, int h KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask | ButtonMotionMask | - StructureNotifyMask; + StructureNotifyMask | EnterWindowMask | LeaveWindowMask | VisibilityChangeMask | PropertyChangeMask | FocusChangeMask; window_attr.bit_gravity = NorthWestGravity; if(existing_window) { @@ -356,7 +356,7 @@ static void mgl_window_handle_key_event(mgl_window *self, XKeyEvent *xkey, mgl_e event->key.alt = ((xkey->state & Mod1Mask) != 0); event->key.control = ((xkey->state & ControlMask) != 0); event->key.shift = ((xkey->state & ShiftMask) != 0); - event->key.system = ((xkey->state & Mod4Mask) != 0); /* TODO: Fix, doesn't work */ + event->key.system = ((xkey->state & Mod4Mask) != 0); x11_context_update_key_state(self->context, event->key.code, pressed); } @@ -506,6 +506,7 @@ void mgl_window_set_view(mgl_window *self, mgl_view *new_view) { context->gl.glMatrixMode(GL_PROJECTION); context->gl.glLoadIdentity(); context->gl.glOrtho(0.0, new_view->size.x, new_view->size.y, 0.0, 0.0, 1.0); + context->gl.glMatrixMode(GL_MODELVIEW); } void mgl_window_get_view(mgl_window *self, mgl_view *view) { diff --git a/tests/main.c b/tests/main.c index ad6113c..3dc8c98 100644 --- a/tests/main.c +++ b/tests/main.c @@ -1,5 +1,6 @@ #include <stdio.h> #include <string.h> +#include <stdlib.h> #include <mgl/mgl.h> #include <mgl/window/window.h> #include <mgl/window/event.h> @@ -11,13 +12,16 @@ #include <mgl/graphics/vertex_buffer.h> #include <mgl/graphics/shader.h> #include <mgl/system/clock.h> +#include <mgl/system/fileutils.h> + +#define require_equals(a, b) do { if((a) != (b)) { fprintf(stderr, "Assert failed on line %d: %s == %s\n", __LINE__, #a, #b); abort(); } } while(0) +#define require_not_equals(a, b) do { if((a) == (b)) { fprintf(stderr, "Assert failed on line %d: %s != %s\n", __LINE__, #a, #b); abort(); } } while(0) typedef struct { mgl_texture *texture; mgl_font *font; mgl_font *cjk_font; mgl_vertex_buffer *vertex_buffer1; - mgl_vertex_buffer *vertex_buffer2; mgl_shader_program *shader_program; mgl_clock clock; int fps; @@ -28,13 +32,6 @@ static void draw(mgl_window *window, void *userdata) { mgl_context *context = mgl_get_context(); Userdata *u = userdata; - mgl_rectangle rect = { - .color = { 255, 0, 0, 255 }, - .position = { window->cursor_position.x, window->cursor_position.y }, - .size = { 100.0f, 500.0f } - }; - mgl_rectangle_draw(context, &rect); - mgl_shader_program_set_uniform_vec2f(u->shader_program, "resolution", (mgl_vec2f){ u->texture->width, u->texture->height }); mgl_sprite sprite; @@ -49,16 +46,24 @@ static void draw(mgl_window *window, void *userdata) { mgl_clock_restart(&u->clock); u->fps = u->fps_counter; u->fps_counter = 0; + fprintf(stderr, "fps: %d\n", u->fps); } char str[255]; - snprintf(str, sizeof(str), "hello|world\nfps: %d", u->fps); + snprintf(str, sizeof(str), "hello|fps\nåäö: %d", u->fps); mgl_text text; mgl_text_init(&text, u->font, str, strlen(str)); mgl_text_draw(context, &text); mgl_text_deinit(&text); - const char *cjk_str = "一丏"; + const char *cjk_str = "饕餮 로 초성, 중성, 종성을 東京都と周辺7県で首都圏を構成\n" + "【システマ流】誰でも出来る「不眠.ストレス」に打ち勝つ呼吸法(東京都・埼玉県・千葉県・神奈川県)の総人口は約 千葉県・神奈川日本の民間研究所が年に発表した「世界の都市総合力ランキング」では、ロンド\n" + "ンとニューヨークに次ぐ世界3位と評価された年に首都圏整備法の施行に伴い廃止された。このように首都建設法の廃止により区部の東部には、隅田川、荒川、江戸川、中川などの河口部に沖積平野が広がっ日本国内におけ\n" + "る気候区分では23区〜多摩東部および伊豆諸島は太平洋側気候、多摩西部などは中央高地式気候に属する。小笠原諸島は南日本気候である。特徴としては、四季の変化が明瞭であり、天気が日によって変化しやすい。夏季は\n" + "高温・多雨となり、冬季は晴れて乾燥する東京都区部 - 気象庁露場のあった大手町付近の観測による最低気温が最も高くなることも珍しくなかった。しかし、夏場の最高気温自体はそれほど高くもない。一方、内陸寄りにあ\n" + "る練馬区のアメダス観測[注 7][22]地域では冬日は珍しくなく、新宿区や渋谷区などの都心部でも冬日の観測はよく見られる。また、気象観測所のある千代田区内におい足立区、荒川区、板橋区、江戸川区、大田区、葛飾区、\n" + "北区、江東区、品川区、渋谷区、新宿区、杉並区、墨田区、世田谷区、台東区、中央区、千代田区、豊島区、中野区、練馬区、文京区、港区、目黒区昭島市、あきる野市、稲城市、青梅市、清瀬市、国立市、小金井市、国分寺市、\n" + "小平市、狛江市、立川市、多摩市、調布市、西東京市、八王子市、羽村市、東久留米市、東村山市、東大和市、日野市、府中市、福生市、町田市、三鷹市、武蔵野市、武蔵村山市"; mgl_text cjk_text; mgl_text_init(&cjk_text, u->cjk_font, cjk_str, strlen(cjk_str)); @@ -66,12 +71,6 @@ static void draw(mgl_window *window, void *userdata) { mgl_text_draw(context, &cjk_text); mgl_text_deinit(&cjk_text); - mgl_vertex_buffer_set_position(u->vertex_buffer1, (mgl_vec2f){ window->cursor_position.x, window->cursor_position.y }); - mgl_vertex_buffer_draw(context, u->vertex_buffer1, &u->font->texture); - - mgl_vertex_buffer_set_position(u->vertex_buffer2, (mgl_vec2f){ window->cursor_position.x, window->cursor_position.y + 500 }); - mgl_vertex_buffer_draw(context, u->vertex_buffer2, &u->font->texture); - mgl_view prev_view; mgl_window_get_view(window, &prev_view); @@ -110,17 +109,91 @@ static void draw(mgl_window *window, void *userdata) { mgl_vertices_draw(context, vertices, 4, MGL_PRIMITIVE_QUADS); mgl_window_set_view(window, &prev_view); + + mgl_rectangle rect = { + .position = { window->cursor_position.x, window->cursor_position.y }, + .size = { u->cjk_font->texture.width, u->cjk_font->texture.height }, + .color = { 255, 255, 255, 255 }, + }; + mgl_rectangle_draw(context, &rect); + + mgl_vertex vertices1[4] = { + (mgl_vertex){ + .position = {0.0f, 0.0f}, + .texcoords = {0.0f, 0.0f}, + .color = {255, 0, 0, 100} + }, + (mgl_vertex){ + .position = {u->cjk_font->texture.width, 0.0f}, + .texcoords = {u->cjk_font->texture.width, 0.0f}, + .color = {0, 255, 0, 100}, + }, + (mgl_vertex){ + .position = {u->cjk_font->texture.width, u->cjk_font->texture.height}, + .texcoords = {u->cjk_font->texture.width, u->cjk_font->texture.height}, + .color = {0, 0, 255, 100}, + }, + (mgl_vertex){ + .position = {0.0f, u->cjk_font->texture.height}, + .texcoords = {0.0f, u->cjk_font->texture.height}, + .color = {255, 0, 255, 100} + } + }; + + mgl_vertex_buffer_update(u->vertex_buffer1, vertices1, 4, MGL_PRIMITIVE_QUADS, MGL_USAGE_STREAM); + mgl_vertex_buffer_set_position(u->vertex_buffer1, (mgl_vec2f){ window->cursor_position.x, window->cursor_position.y }); + mgl_vertex_buffer_draw(context, u->vertex_buffer1, &u->cjk_font->texture); +} + +static void test_hash_map_get_insert(mgl_font_char_map *char_map, uint32_t unicode, bool exists) { + mgl_font_glyph font_glyph; + font_glyph.advance = 5.0f; + if(exists) { + require_not_equals(mgl_font_char_map_get(char_map, unicode), NULL); + } else { + require_equals(mgl_font_char_map_get(char_map, unicode), NULL); + require_equals(mgl_font_char_map_insert(char_map, unicode, &font_glyph, NULL), 0); + } +} + +static void test_hash_map() { + mgl_font_char_map char_map; + mgl_font_char_map_init(&char_map); + + test_hash_map_get_insert(&char_map, 'h', false); + test_hash_map_get_insert(&char_map, 'e', false); + test_hash_map_get_insert(&char_map, 'l', false); + test_hash_map_get_insert(&char_map, 'l', true); + test_hash_map_get_insert(&char_map, 'o', false); + test_hash_map_get_insert(&char_map, ' ', false); + test_hash_map_get_insert(&char_map, 'f', false); + test_hash_map_get_insert(&char_map, 'p', false); + test_hash_map_get_insert(&char_map, 's', false); + + require_not_equals(mgl_font_char_map_get(&char_map, 'h'), NULL); + require_not_equals(mgl_font_char_map_get(&char_map, 'e'), NULL); + require_not_equals(mgl_font_char_map_get(&char_map, 'l'), NULL); + require_not_equals(mgl_font_char_map_get(&char_map, 'o'), NULL); + require_not_equals(mgl_font_char_map_get(&char_map, ' '), NULL); + require_not_equals(mgl_font_char_map_get(&char_map, 'f'), NULL); + require_not_equals(mgl_font_char_map_get(&char_map, 'p'), NULL); + require_not_equals(mgl_font_char_map_get(&char_map, 's'), NULL); + + mgl_font_char_map_deinit(&char_map); } int main(int argc, char **argv) { + test_hash_map(); + if(mgl_init() != 0) return 1; mgl_texture texture; + mgl_memory_mapped_file font_file; + mgl_memory_mapped_file cjk_font_file; mgl_font font; mgl_font cjk_font; mgl_vertex_buffer vertex_buffer1; - mgl_vertex_buffer vertex_buffer2; mgl_shader_program shader_program; Userdata userdata; @@ -128,7 +201,6 @@ int main(int argc, char **argv) { userdata.font = &font; userdata.cjk_font = &cjk_font; userdata.vertex_buffer1 = &vertex_buffer1; - userdata.vertex_buffer2 = &vertex_buffer2; userdata.shader_program = &shader_program; userdata.fps = 0; userdata.fps_counter = 0; @@ -138,80 +210,34 @@ int main(int argc, char **argv) { if(mgl_window_create(&window, "mgl", 1280, 720) != 0) return 1; - if(mgl_texture_load_from_file(&texture, "tests/X11.jpg", NULL) != 0) + if(mgl_texture_init(&texture) != 0) return 1; - if(mgl_font_load_from_file(&font, "/usr/share/fonts/noto/NotoSans-Regular.ttf", 32) != 0) + if(mgl_texture_load_from_file(&texture, "tests/X11.jpg", &(mgl_texture_load_options){ false, true }) != 0) return 1; - if(mgl_font_load_from_file(&cjk_font, "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc", 32) != 0) + if(mgl_mapped_file_load("/usr/share/fonts/noto/NotoSans-Regular.ttf", &font_file, &(mgl_memory_mapped_file_load_options){ .readable = true, .writable = false }) != 0) return 1; - if(mgl_shader_program_init(&shader_program) != 0) + if(mgl_mapped_file_load("/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc", &cjk_font_file, &(mgl_memory_mapped_file_load_options){ .readable = true, .writable = false }) != 0) return 1; - if(mgl_shader_program_add_shader_from_file(&shader_program, "tests/circle_mask.glsl", MGL_SHADER_FRAGMENT) != 0) + if(mgl_font_load_from_file(&font, &font_file, 26) != 0) return 1; - if(mgl_shader_program_finalize(&shader_program) != 0) + if(mgl_font_load_from_file(&cjk_font, &cjk_font_file, 26) != 0) return 1; - mgl_vertex_buffer_init(&vertex_buffer1); - mgl_vertex_buffer_init(&vertex_buffer2); - - mgl_vertex vertices1[4] = { - (mgl_vertex){ - .position = {0.0f, 0.0f}, - .texcoords = {0.0f, 0.0f}, - .color = {255, 0, 0, 100} - }, - (mgl_vertex){ - .position = {font.texture.width, 0.0f}, - .texcoords = {1.0f, 0.0f}, - .color = {0, 255, 0, 100}, - }, - (mgl_vertex){ - .position = {font.texture.width, font.texture.height/2}, - .texcoords = {1.0f, 0.5f}, - .color = {0, 0, 255, 100}, - }, - (mgl_vertex){ - .position = {0.0f, font.texture.height/2}, - .texcoords = {0.0f, 0.5f}, - .color = {255, 0, 255, 100} - } - }; - - mgl_vertex vertices2[4] = { - (mgl_vertex){ - .position = {0.0f, 0.0f}, - .texcoords = {0.0f, 0.5f}, - .color = {255, 0, 0, 100} - }, - (mgl_vertex){ - .position = {font.texture.width, 0.0f}, - .texcoords = {1.0f, 0.5f}, - .color = {0, 255, 0, 100}, - }, - (mgl_vertex){ - .position = {font.texture.width, font.texture.height/2}, - .texcoords = {1.0f, 1.0f}, - .color = {0, 0, 255, 100}, - }, - (mgl_vertex){ - .position = {0.0f, font.texture.height/2}, - .texcoords = {0.0f, 1.0f}, - .color = {255, 0, 255, 100} - } - }; + if(mgl_shader_program_init(&shader_program) != 0) + return 1; - if(mgl_vertex_buffer_update(&vertex_buffer1, vertices1, 4, MGL_PRIMITIVE_QUADS, MGL_USAGE_STATIC) != 0) + if(mgl_shader_program_add_shader_from_file(&shader_program, "tests/circle_mask.glsl", MGL_SHADER_FRAGMENT) != 0) return 1; - if(mgl_vertex_buffer_update(&vertex_buffer2, vertices2, 4, MGL_PRIMITIVE_QUADS, MGL_USAGE_STATIC) != 0) + if(mgl_shader_program_finalize(&shader_program) != 0) return 1; - fprintf(stderr, "Font texture width: %d, texture height: %d\n", font.texture.width, font.texture.height); + mgl_vertex_buffer_init(&vertex_buffer1); mgl_event event; while(mgl_window_is_open(&window)) { @@ -232,16 +258,17 @@ int main(int argc, char **argv) { } } - mgl_window_clear(&window, (mgl_color){0, 0, 0, 255}); + mgl_window_clear(&window, (mgl_color){0, 0, 0, 0}); draw(&window, &userdata); mgl_window_display(&window); } - mgl_vertex_buffer_deinit(&vertex_buffer2); mgl_vertex_buffer_deinit(&vertex_buffer1); mgl_shader_program_deinit(&shader_program); mgl_font_unload(&cjk_font); mgl_font_unload(&font); + mgl_mapped_file_unload(&cjk_font_file); + mgl_mapped_file_unload(&font_file); mgl_texture_unload(&texture); mgl_window_deinit(&window); mgl_deinit(); |