diff options
-rw-r--r-- | TODO | 5 | ||||
-rw-r--r-- | include/mgl/graphics/text.h | 20 | ||||
-rw-r--r-- | include/mgl/mgl.h | 1 | ||||
-rw-r--r-- | include/mgl/system/utf8.h | 17 | ||||
-rw-r--r-- | include/mgl/window/event.h | 3 | ||||
-rw-r--r-- | include/mgl/window/window.h | 3 | ||||
-rw-r--r-- | src/gl.c | 2 | ||||
-rw-r--r-- | src/graphics/font.c | 7 | ||||
-rw-r--r-- | src/graphics/text.c | 121 | ||||
-rw-r--r-- | src/mgl.c | 12 | ||||
-rw-r--r-- | src/system/utf8.c | 73 | ||||
-rw-r--r-- | src/window/window.c | 27 | ||||
-rw-r--r-- | tests/main.c | 30 |
13 files changed, 217 insertions, 104 deletions
@@ -1,8 +1,5 @@ -Handle window close (window destroyed event, disconnected from server and socket becomes invalid (check select return?)).\ Bind texture and cache the bound texture to reduce calls to opengl.\ Use gl triangle instead of quad.\ -Fix crash on exit.\ 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.\ -Respond to wm ping.
\ No newline at end of file +Verify if using a separate glx context for every window is the correct approach.
\ No newline at end of file diff --git a/include/mgl/graphics/text.h b/include/mgl/graphics/text.h index f832605..aca0650 100644 --- a/include/mgl/graphics/text.h +++ b/include/mgl/graphics/text.h @@ -16,33 +16,27 @@ typedef struct mgl_font mgl_font; typedef struct mgl_context mgl_context; typedef struct { - /* Optional */ - void (*before_syntax_highlight)(void *userdata); - /* Return true if the text format should be changed. Optional */ - bool (*syntax_highlight)(void *userdata, const char *str, size_t size, mgl_color *color); - void *userdata; -} mgl_text_options; - -typedef struct { mgl_font *font; /* nullable */ const char *text; /* nullable */ size_t text_size; mgl_color color; mgl_vec2f position; mgl_vec2f bounds; - mgl_text_options options; } mgl_text; /* Note: keeps a reference to |text|. |text| needs to be valid as long as |self| is used. |font| and |text| may be NULL. - |load_options| can be NULL, in which case the default options are used. */ -int mgl_text_init(mgl_text *self, mgl_font *font, const char *str, size_t str_size, mgl_text_options *options); +int mgl_text_init(mgl_text *self, mgl_font *font, const char *str, size_t str_size); void mgl_text_deinit(mgl_text *self); -/* Note: keeps a reference to |text|. |text| needs to be valid as long as |self| is used. |text| may be NULL. */ -void mgl_text_set_string(mgl_text *self, const char *str, size_t str_size); +/* + Note: keeps a reference to |text|. |text| needs to be valid as long as |self| is used. + |text| may be NULL. + |str| will be checked if its a valid utf8 string. +*/ +int mgl_text_set_string(mgl_text *self, const char *str, size_t str_size); /* |font| may be NULL */ void mgl_text_set_font(mgl_text *self, mgl_font *font); void mgl_text_set_position(mgl_text *self, mgl_vec2f position); diff --git a/include/mgl/mgl.h b/include/mgl/mgl.h index b9d007e..7b02ceb 100644 --- a/include/mgl/mgl.h +++ b/include/mgl/mgl.h @@ -12,6 +12,7 @@ struct mgl_context { mgl_gl gl; _XVisualInfo *visual_info; unsigned long wm_delete_window_atom; + unsigned long net_wm_ping_atom; }; /* diff --git a/include/mgl/system/utf8.h b/include/mgl/system/utf8.h new file mode 100644 index 0000000..663e7e3 --- /dev/null +++ b/include/mgl/system/utf8.h @@ -0,0 +1,17 @@ +#ifndef MGL_UTF8_H +#define MGL_UTF8_H + +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> + +bool mgl_utf8_is_valid(const unsigned char *str, size_t size); + +/* + Returns the byte length of the decoded codepoint. + Note: does not validate if the input |str| is a valid utf8 string. +*/ +size_t mgl_utf8_decode(const unsigned char *str, uint32_t *decoded_codepoint); + +#endif /* MGL_UTF8_H */ + diff --git a/include/mgl/window/event.h b/include/mgl/window/event.h index 020d5c9..c928d0e 100644 --- a/include/mgl/window/event.h +++ b/include/mgl/window/event.h @@ -41,7 +41,8 @@ typedef struct { typedef enum { MGL_EVENT_UNKNOWN, - MGL_EVENT_RESIZED, + MGL_EVENT_CLOSED, /* Window closed */ + MGL_EVENT_RESIZED, /* Window resized */ MGL_EVENT_KEY_PRESSED, MGL_EVENT_KEY_RELEASED, MGL_EVENT_MOUSE_BUTTON_PRESSED, diff --git a/include/mgl/window/window.h b/include/mgl/window/window.h index ad8e1dd..cb8baf6 100644 --- a/include/mgl/window/window.h +++ b/include/mgl/window/window.h @@ -26,6 +26,7 @@ struct mgl_window { /* relative to the top left of the window. only updates when the cursor is inside the window */ mgl_vec2i cursor_position; mgl_view view; + bool open; }; int mgl_window_create(mgl_window *self, const char *title, int width, int height); @@ -47,4 +48,6 @@ void mgl_window_display(mgl_window *self); void mgl_window_set_view(mgl_window *self, mgl_view *new_view); void mgl_window_get_view(mgl_window *self, mgl_view *view); +bool mgl_window_is_open(const mgl_window *self); + #endif /* MGL_WINDOW_H */ @@ -1,6 +1,5 @@ #include "../include/mgl/gl.h" #include <dlfcn.h> -/*#include <GL/gl.h>*/ #include <stdio.h> typedef struct { @@ -100,6 +99,7 @@ int mgl_gl_load(mgl_gl *self) { { &self->glXSwapIntervalEXT, "glXSwapIntervalEXT" }, { &self->glXSwapIntervalMESA, "glXGetSwapIntervalMESA" }, { &self->glXSwapIntervalSGI, "glXSwapIntervalSGI" }, + { NULL, NULL } }; diff --git a/src/graphics/font.c b/src/graphics/font.c index c017755..175c571 100644 --- a/src/graphics/font.c +++ b/src/graphics/font.c @@ -84,6 +84,11 @@ int mgl_font_load_from_file(mgl_font *self, const char *filepath, unsigned int c continue; } + /*if(!stbtt_PackFontRange(&pc, filedata.data, 0, self->character_size, 0x00004E00, self->num_packed_chars, self->packed_chars)) { + stbtt_PackEnd(&pc); + continue; + }*/ + stbtt_PackEnd(&pc); atlas_created = true; break; @@ -124,7 +129,7 @@ void mgl_font_unload(mgl_font *self) { 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 >= self->num_packed_chars) + if(codepoint < 0 || codepoint >= 0 + self->num_packed_chars) return -1; float x = 0.0f; diff --git a/src/graphics/text.c b/src/graphics/text.c index 31ae794..9804ab2 100644 --- a/src/graphics/text.c +++ b/src/graphics/text.c @@ -1,27 +1,12 @@ #include "../../include/mgl/graphics/text.h" #include "../../include/mgl/graphics/font.h" +#include "../../include/mgl/system/utf8.h" #include "../../include/mgl/mgl.h" - -/* - TODO: Do syntax highlight in constructor/mgl_text_set_string for optimization. - Syntax highlighting should the first text that is visible in the text editor and find the start and end, - and start syntax highlight from there until the last visible text. - In that case there should also be a function to rerun the syntax highlighting, to update it - when an asynchronous process finishes that parses code, such as a lsp client parsing C++ code which - takes a while. -*/ +#include <stdio.h> #define TAB_WIDTH 4 -static bool default_syntax_highlight(void *userdata, const char *str, size_t size, mgl_color *color) { - (void)userdata; - (void)str; - (void)size; - (void)color; - return false; -} - -static float fmax(float a, float b) { +static float max_float(float a, float b) { return a >= b ? a : b; } @@ -33,63 +18,65 @@ static mgl_vec2f mgl_text_calculate_bounds(mgl_text *self) { /* TODO: Combine this with the loop in mgl_text_draw */ mgl_font_glyph glyph; float width = 0.0f; - for(size_t i = 0; i < self->text_size; ++i) { - unsigned char c = *(unsigned char*)&self->text[i]; - if((c >= 32 && c < 128)) { - if(mgl_font_get_glyph(self->font, c, &glyph) == 0) { - width += glyph.advance; - bounds.x = fmax(bounds.x, width); - } - } else if(c == '\t') { + for(size_t i = 0; i < self->text_size;) { + unsigned char *cp = (unsigned char*)&self->text[i]; + uint32_t codepoint = 0; + const size_t clen = mgl_utf8_decode(cp, &codepoint); + if(codepoint == '\t') { if(mgl_font_get_glyph(self->font, ' ', &glyph) == 0) { width += (glyph.advance * TAB_WIDTH); - bounds.x = fmax(bounds.x, width); + bounds.x = max_float(bounds.x, width); } - } else if(c == '\n') { + } else if(codepoint == '\n') { width = 0.0f; bounds.y += self->font->character_size; + } else { + if(mgl_font_get_glyph(self->font, codepoint, &glyph) == 0) { + width += glyph.advance; + bounds.x = max_float(bounds.x, width); + } } + i += clen; } return bounds; } -int mgl_text_init(mgl_text *self, mgl_font *font, const char *str, size_t str_size, mgl_text_options *options) { +int mgl_text_init(mgl_text *self, mgl_font *font, const char *str, size_t str_size) { self->font = font; - mgl_text_set_string(self, str, str_size); self->color = (mgl_color){ 255, 255, 255, 255 }; self->position = (mgl_vec2f){ 0.0f, 0.0f }; - - if(options) { - self->options = *options; - } else { - self->options.userdata = NULL; - self->options.before_syntax_highlight = NULL; - } - - if(!options || !options->syntax_highlight) - self->options.syntax_highlight = default_syntax_highlight; - - return 0; + self->text = NULL; + self->text_size = 0; + return mgl_text_set_string(self, str, str_size); } void mgl_text_deinit(mgl_text *self) { (void)self; } -void mgl_text_set_string(mgl_text *self, const char *str, size_t str_size) { +int mgl_text_set_string(mgl_text *self, const char *str, size_t str_size) { + if(str) { + if(!mgl_utf8_is_valid((const unsigned char*)str, str_size)) { + fprintf(stderr, "Error: mgl_text_set_string received an invalid utf8 string\n"); + return -1; + } + } + self->text = str; self->text_size = str_size; if(self->text && self->text_size > 0 && self->font) self->bounds = mgl_text_calculate_bounds(self); else self->bounds = (mgl_vec2f){ 0.0f, 0.0f }; + + return 0; } void mgl_text_set_font(mgl_text *self, mgl_font *font) { self->font = font; if(self->font) { - if(self->bounds.x < 0.001f && self->bounds.y < 0.001f) + if(self->bounds.x < 0.001f && self->bounds.y < 0.001f && self->text && self->text_size > 0) self->bounds = mgl_text_calculate_bounds(self); } else { self->bounds = (mgl_vec2f){ 0.0f, 0.0f }; @@ -122,19 +109,6 @@ static void mgl_text_draw_glyph(mgl_context *context, mgl_font_glyph *glyph, mgl context->gl.glVertex3f(position.x + glyph->position.x, position.y + glyph->position.y + glyph->size.y, 0.0f); } -static void mgl_text_run_syntax_highlight(mgl_context *context, mgl_text *text, const char *str, size_t size, mgl_color *color, bool *color_changed) { - if(text->options.syntax_highlight(text->options.userdata, str, size, color)) { - context->gl.glColor4ub(color->r, color->g, color->b, color->a); - *color_changed = true; - } else { - if(*color_changed) { - *color = text->color; - context->gl.glColor4ub(color->r, color->g, color->b, color->a); - *color_changed = false; - } - } -} - /* TODO: Use opengl buffer object instead */ /* TODO: Cache texture bind to not bind texture if its already bound and do not bind texture 0 */ void mgl_text_draw(mgl_context *context, mgl_text *text) { @@ -145,34 +119,27 @@ void mgl_text_draw(mgl_context *context, mgl_text *text) { mgl_vec2f position = text->position; position.y += text->font->character_size; - mgl_color color = text->color; - bool color_changed = false; - - if(text->options.before_syntax_highlight) - text->options.before_syntax_highlight(text->options.userdata); - 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); context->gl.glBegin(GL_QUADS); - for(size_t i = 0; i < text->text_size; ++i) { - unsigned char c = *(unsigned char*)&text->text[i]; - if((c >= 32 && c < 128)) { - if(mgl_font_get_glyph(text->font, c, &glyph) == 0) { - mgl_text_run_syntax_highlight(context, text, (const char*)&c, 1, &color, &color_changed); - mgl_text_draw_glyph(context, &glyph, position); - position.x += glyph.advance; - } - } else if(c == '\t') { + for(size_t i = 0; i < text->text_size;) { + unsigned char *cp = (unsigned char*)&text->text[i]; + uint32_t codepoint = 0; + const size_t clen = mgl_utf8_decode(cp, &codepoint); + if(codepoint == '\t') { if(mgl_font_get_glyph(text->font, ' ', &glyph) == 0) { - for(int i = 0; i < TAB_WIDTH; ++i) { - mgl_text_draw_glyph(context, &glyph, position); - position.x += glyph.advance; - } + position.x += (glyph.advance * TAB_WIDTH); } - } else if(c == '\n') { + } else if(codepoint == '\n') { position.x = text->position.x; position.y += text->font->character_size; + } else { + if(mgl_font_get_glyph(text->font, codepoint, &glyph) == 0) { + mgl_text_draw_glyph(context, &glyph, position); + position.x += glyph.advance; + } } + i += clen; } context->gl.glEnd(); context->gl.glBindTexture(GL_TEXTURE_2D, 0); @@ -74,6 +74,8 @@ int mgl_init(void) { return -1; } + context.net_wm_ping_atom = XInternAtom(context.connection, "_NET_WM_PING", True); + if(mgl_gl_load(&context.gl) != 0) { mgl_deinit(); return -1; @@ -90,9 +92,6 @@ int mgl_init(void) { void mgl_deinit(void) { if(init_count == 1) { if(context.connection) { - glx_context_deinit(); - mgl_gl_unload(&context.gl); - XSetIOErrorHandler(prev_xioerror); prev_xioerror = NULL; @@ -101,6 +100,13 @@ void mgl_deinit(void) { XCloseDisplay(context.connection); context.connection = NULL; + + /* + GLX needs to be unloaded after closing the display on nvidia because + nvidia registers cleanup callbacks on exit, that uses the x11 display. + */ + glx_context_deinit(); + mgl_gl_unload(&context.gl); } } diff --git a/src/system/utf8.c b/src/system/utf8.c new file mode 100644 index 0000000..cb14691 --- /dev/null +++ b/src/system/utf8.c @@ -0,0 +1,73 @@ +#include "../../include/mgl/system/utf8.h" + +/* TODO: Optimize (remove branching, etc) */ +bool mgl_utf8_is_valid(const unsigned char *str, size_t size) { + size_t i = 0; + while(i < size) { + size_t codepoint_length = 0; + const unsigned char b = str[i]; + + if((b & 0x80) == 0) + codepoint_length = 1; + else if((b & 0xE0) == 0xC0) + codepoint_length = 2; + else if((b & 0xF0) == 0xE0) + codepoint_length = 3; + else if((b & 0xF8) == 0xF0) + codepoint_length = 4; + else + return false; + + const size_t next = i + codepoint_length; + if(next > size) + return false; + + /* TODO: Remove this overflow check? */ + /* Check overflow */ + if(next <= i) + return false; + + ++i; + for(; i < next; ++i) { + if((str[i] & 0xC0) != 0x80) + return false; + } + } + return true; +} + +static inline size_t utf8_get_codepoint_length(unsigned char b) { + const unsigned int length1 = b >> 7; + const unsigned int length2 = length1 & ((b & 0x40) >> 6); + const unsigned int length3 = length2 & ((b & 0x20) >> 5); + const unsigned int length4 = length3 & ((b & 0x10) >> 4); + return (length1 ^ 0x01) + length1 + length2 + length3 + length4; +} + +/* TODO: Optimize (remove branching, etc) */ +size_t mgl_utf8_decode(const unsigned char *str, uint32_t *decoded_codepoint) { + const size_t length = utf8_get_codepoint_length(str[0]); + uint32_t codepoint; + switch(length) { + case 1: + codepoint = (uint32_t)(str[0] & 0x7F); + break; + case 2: + codepoint = ((uint32_t)(str[0] & 0x1F) << 6); + codepoint |= (uint32_t)(str[1] & 0x3F); + break; + case 3: + codepoint = ((uint32_t)(str[0] & 0x0F) << 12); + codepoint |= ((uint32_t)(str[1] & 0x3F) << 6); + codepoint |= (uint32_t)(str[2] & 0x3F); + break; + case 4: + codepoint = ((uint32_t)(str[0] & 0x07) << 18); + codepoint |= ((uint32_t)(str[1] & 0x3F) << 12); + codepoint |= ((uint32_t)(str[2] & 0x3F) << 6); + codepoint |= (uint32_t)(str[3] & 0x3F); + break; + } + *decoded_codepoint = codepoint; + return length; +} diff --git a/src/window/window.c b/src/window/window.c index 30abebb..b7f9fd8 100644 --- a/src/window/window.c +++ b/src/window/window.c @@ -5,6 +5,8 @@ #include <errno.h> #include <stdio.h> +/* TODO: Use gl OML present for other platforms than nvidia? nvidia doesn't support present yet */ + /* TODO: check for glx swap control extension string (GLX_EXT_swap_control, etc) */ static void set_vertical_sync_enabled(Window window, int enabled) { int result = 0; @@ -95,6 +97,12 @@ static int mgl_window_init(mgl_window *self, const char *title, int width, int h XMapWindow(context->connection, self->window); } + /* TODO: Call XGetWMProtocols and add wm_delete_window_atom on top, to not overwrite existing wm protocol atoms */ + Atom wm_protocol_atoms[2] = { + context->wm_delete_window_atom, + context->net_wm_ping_atom + }; + XSetWMProtocols(context->connection, self->window, wm_protocol_atoms, 2); XFlush(context->connection); /* TODO: Check for failure? */ @@ -118,6 +126,7 @@ static int mgl_window_init(mgl_window *self, const char *title, int width, int h XQueryPointer(context->connection, self->window, &dummy_w, &dummy_w, &dummy_i, &dummy_i, &self->cursor_position.x, &self->cursor_position.y, &dummy_u); mgl_window_on_resize(self, gwa.width, gwa.height); + self->open = true; return 0; } @@ -246,7 +255,7 @@ static bool mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event event->size.height = self->size.y; return true; } - return false; + break; } case MotionNotify: { while(XCheckTypedWindowEvent(mgl_get_context()->connection, self->window, MotionNotify, xev)) {} @@ -258,6 +267,18 @@ static bool mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event event->mouse_move.y = self->cursor_position.y; return true; } + case ClientMessage: { + mgl_context *context = mgl_get_context(); + if(xev->xclient.format == 32 && (unsigned long)xev->xclient.data.l[0] == context->wm_delete_window_atom) { + event->type = MGL_EVENT_CLOSED; + self->open = false; + return true; + } else if(xev->xclient.format == 32 && (unsigned long)xev->xclient.data.l[0] == context->net_wm_ping_atom) { + xev->xclient.window = DefaultRootWindow(context->connection); + XSendEvent(context->connection, DefaultRootWindow(context->connection), False, SubstructureNotifyMask | SubstructureRedirectMask, xev); + } + break; + } } /*fprintf(stderr, "unhandled event type: %d\n", xev->type);*/ event->type = MGL_EVENT_UNKNOWN; @@ -300,3 +321,7 @@ void mgl_window_set_view(mgl_window *self, mgl_view *new_view) { void mgl_window_get_view(mgl_window *self, mgl_view *view) { *view = self->view; } + +bool mgl_window_is_open(const mgl_window *self) { + return self->open; +} diff --git a/tests/main.c b/tests/main.c index d430ab2..3ead877 100644 --- a/tests/main.c +++ b/tests/main.c @@ -15,10 +15,13 @@ 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; + int fps_counter; } Userdata; static void draw(mgl_window *window, void *userdata) { @@ -41,14 +44,28 @@ static void draw(mgl_window *window, void *userdata) { mgl_sprite_draw(context, &sprite); mgl_shader_program_use(NULL); + ++u->fps_counter; + if(mgl_clock_get_elapsed_time_seconds(&u->clock) >= 1.0) { + mgl_clock_restart(&u->clock); + u->fps = u->fps_counter; + u->fps_counter = 0; + } char str[255]; - snprintf(str, sizeof(str), "Hello world!\nelapsed time: %f", mgl_clock_get_elapsed_time_seconds(&u->clock)); + snprintf(str, sizeof(str), "fps: %d", u->fps); mgl_text text; - mgl_text_init(&text, u->font, str, strlen(str), NULL); + mgl_text_init(&text, u->font, str, strlen(str)); mgl_text_draw(context, &text); mgl_text_deinit(&text); + const char *cjk_str = "一丏"; + + mgl_text cjk_text; + mgl_text_init(&cjk_text, u->cjk_font, cjk_str, strlen(cjk_str)); + mgl_text_set_position(&cjk_text, (mgl_vec2f){ 0.0f, 500.0f }); + 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); @@ -101,6 +118,7 @@ int main(int argc, char **argv) { 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; @@ -108,9 +126,12 @@ int main(int argc, char **argv) { Userdata userdata; userdata.texture = &texture; 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; mgl_clock_init(&userdata.clock); mgl_window window; @@ -123,6 +144,9 @@ int main(int argc, char **argv) { if(mgl_font_load_from_file(&font, "/usr/share/fonts/noto/NotoSans-Regular.ttf", 32) != 0) return 1; + if(mgl_font_load_from_file(&cjk_font, "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc", 32) != 0) + return 1; + if(mgl_shader_program_init(&shader_program) != 0) return 1; @@ -190,7 +214,7 @@ int main(int argc, char **argv) { fprintf(stderr, "Font texture width: %d, texture height: %d\n", font.texture.width, font.texture.height); mgl_event event; - for(;;) { + while(mgl_window_is_open(&window)) { while(mgl_window_poll_event(&window, &event)) { } |