diff options
Diffstat (limited to 'src/graphics')
-rw-r--r-- | src/graphics/backend/egl.c | 248 | ||||
-rw-r--r-- | src/graphics/backend/glx.c | 173 | ||||
-rw-r--r-- | src/graphics/backend/graphics.c | 68 | ||||
-rw-r--r-- | src/graphics/font.c | 2 | ||||
-rw-r--r-- | src/graphics/shader.c | 33 | ||||
-rw-r--r-- | src/graphics/sprite.c | 32 | ||||
-rw-r--r-- | src/graphics/text.c | 166 | ||||
-rw-r--r-- | src/graphics/texture.c | 81 |
8 files changed, 723 insertions, 80 deletions
diff --git a/src/graphics/backend/egl.c b/src/graphics/backend/egl.c new file mode 100644 index 0000000..cabbf04 --- /dev/null +++ b/src/graphics/backend/egl.c @@ -0,0 +1,248 @@ +#include "../../../include/mgl/graphics/backend/egl.h" +#include "../../../include/mgl/mgl.h" + +#include <stdlib.h> +#include <stdio.h> + +#include <X11/Xutil.h> +#include <X11/extensions/Xrender.h> + +static void mgl_graphics_egl_deinit(mgl_graphics *self); + +typedef struct { + EGLDisplay display; + EGLSurface surface; + EGLContext context; + EGLConfig *configs; + EGLConfig ecfg; + XVisualInfo *visual_info; +} mgl_graphics_egl; + +static int32_t mgl_graphics_egl_get_config_attrib(mgl_graphics_egl *self, EGLConfig ecfg, int32_t attribute_name) { + mgl_context *context = mgl_get_context(); + int32_t value = 0; + context->gl.eglGetConfigAttrib(self->display, ecfg, attribute_name, &value); + return value; +} + +static bool xvisual_match_alpha(Display *dpy, XVisualInfo *visual_info, bool alpha) { + XRenderPictFormat *pict_format = XRenderFindVisualFormat(dpy, visual_info->visual); + if(!pict_format) + return false; + return (alpha && pict_format->direct.alphaMask > 0) || (!alpha && pict_format->direct.alphaMask == 0); +} + +static bool mgl_graphics_egl_choose_config(mgl_graphics_egl *self, mgl_context *context, bool alpha) { + self->configs = NULL; + self->ecfg = NULL; + self->visual_info = NULL; + + int32_t num_configs = 0; + context->gl.eglGetConfigs(self->display, NULL, 0, &num_configs); + if(num_configs == 0) { + fprintf(stderr, "mgl error: mgl_graphics_egl_choose_config: no configs found\n"); + return false; + } + + self->configs = (EGLConfig*)calloc(num_configs, sizeof(EGLConfig)); + if(!self->configs) { + fprintf(stderr, "mgl error: mgl_graphics_egl_choose_config: failed to allocate %d configs\n", (int)num_configs); + return false; + } + + context->gl.eglGetConfigs(self->display, self->configs, num_configs, &num_configs); + for(int i = 0; i < num_configs; i++) { + self->ecfg = self->configs[i]; + + //if(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_RENDERABLE_TYPE) != EGL_OPENGL_ES2_BIT) + // continue; + + if(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_COLOR_BUFFER_TYPE) != EGL_RGB_BUFFER) + continue; + + if(!(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_SURFACE_TYPE) & EGL_WINDOW_BIT)) + continue; + + if(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_RED_SIZE) != 8) + continue; + + if(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_GREEN_SIZE) != 8) + continue; + + if(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_BLUE_SIZE) != 8) + continue; + + if(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_ALPHA_SIZE) != (alpha ? 8 : 0)) + continue; + + if(context->window_system == MGL_WINDOW_SYSTEM_WAYLAND) + break; + + XVisualInfo vi = {0}; + vi.visualid = mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_NATIVE_VISUAL_ID); + if(!vi.visualid) + continue; + + int vis_count = 0; + self->visual_info = XGetVisualInfo(context->connection, VisualIDMask, &vi, &vis_count); + if(!self->visual_info) + continue; + + if(xvisual_match_alpha(context->connection, self->visual_info, alpha)) { + break; + } else { + XFree(self->visual_info); + self->visual_info = NULL; + self->ecfg = NULL; + } + } + + if(!self->ecfg) { + fprintf(stderr, "mgl error: mgl_graphics_egl_choose_config: no appropriate glx config found\n"); + return false; + } + + if(context->window_system == MGL_WINDOW_SYSTEM_X11 && !self->visual_info) { + fprintf(stderr, "mgl error: mgl_graphics_egl_choose_config: no appropriate visual found\n"); + return false; + } + + return true; +} + +static bool mgl_graphics_egl_make_context_current(mgl_graphics *self, mgl_window_handle window) { + (void)window; + mgl_graphics_egl *impl = self->impl; + mgl_context *context = mgl_get_context(); + + if(!impl->surface) { + impl->surface = context->gl.eglCreateWindowSurface(impl->display, impl->ecfg, (EGLNativeWindowType)window, NULL); + if(!impl->surface) { + fprintf(stderr, "mgl error: mgl_graphics_egl_make_context_current: failed to create window surface\n"); + return false; + } + } + + return context->gl.eglMakeCurrent(impl->display, impl->surface, impl->surface, impl->context) != 0; +} + +static void mgl_graphics_egl_swap_buffers(mgl_graphics *self, mgl_window_handle window) { + (void)window; + mgl_graphics_egl *impl = self->impl; + mgl_context *context = mgl_get_context(); + context->gl.eglSwapBuffers(impl->display, impl->surface); +} + +static bool mgl_graphics_egl_set_swap_interval(mgl_graphics *self, mgl_window_handle window, bool enabled) { + (void)window; + mgl_graphics_egl *impl = self->impl; + mgl_context *context = mgl_get_context(); + return context->gl.eglSwapInterval(impl->display, (int32_t)enabled) == 1; +} + +static void* mgl_graphics_egl_get_xvisual_info(mgl_graphics *self) { + mgl_graphics_egl *impl = self->impl; + return impl->visual_info; +} + +static void* mgl_graphics_egl_get_display(mgl_graphics *self) { + mgl_graphics_egl *impl = self->impl; + return impl->display; +} + +static void* mgl_graphics_egl_get_context(mgl_graphics *self) { + mgl_graphics_egl *impl = self->impl; + return impl->context; +} + +bool mgl_graphics_egl_init(mgl_graphics *self) { + mgl_graphics_egl *impl = calloc(1, sizeof(mgl_graphics_egl)); + if(!impl) + return false; + + self->deinit = mgl_graphics_egl_deinit; + self->make_context_current = mgl_graphics_egl_make_context_current; + self->swap_buffers = mgl_graphics_egl_swap_buffers; + self->set_swap_interval = mgl_graphics_egl_set_swap_interval; + self->get_xvisual_info = mgl_graphics_egl_get_xvisual_info; + self->get_display = mgl_graphics_egl_get_display; + self->get_context = mgl_graphics_egl_get_context; + self->impl = impl; + + const int32_t ctxattr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE, EGL_NONE + }; + + mgl_context *context = mgl_get_context(); + context->gl.eglBindAPI(EGL_OPENGL_API); + + impl->display = context->gl.eglGetDisplay((EGLNativeDisplayType)context->connection); + if(!impl->display) { + fprintf(stderr, "mgl error: mgl_graphics_egl_init: eglGetDisplay failed\n"); + mgl_graphics_egl_deinit(self); + return false; + } + + if(!context->gl.eglInitialize(impl->display, NULL, NULL)) { + fprintf(stderr, "mgl error: mgl_graphics_egl_init: eglInitialize failed\n"); + mgl_graphics_egl_deinit(self); + return false; + } + + if(!mgl_graphics_egl_choose_config(impl, context, self->alpha)) { + mgl_graphics_egl_deinit(self); + return false; + } + + impl->context = context->gl.eglCreateContext(impl->display, impl->ecfg, NULL, ctxattr); + if(!impl->context) { + fprintf(stderr, "mgl error: mgl_graphics_egl_init: failed to create egl context\n"); + mgl_graphics_egl_deinit(self); + return false; + } + + return true; +} + +void mgl_graphics_egl_deinit(mgl_graphics *self) { + mgl_graphics_egl *impl = self->impl; + if(!impl) + return; + + mgl_context *context = mgl_get_context(); + + if(impl->visual_info) { + XFree(impl->visual_info); + impl->visual_info = NULL; + } + + if(impl->configs) { + free(impl->configs); + impl->configs = NULL; + } + + if(impl->context) { + context->gl.eglMakeCurrent(impl->display, NULL, NULL, NULL); + context->gl.eglDestroyContext(impl->display, impl->context); + impl->context = NULL; + } + + if(impl->surface) { + context->gl.eglDestroySurface(impl->display, impl->surface); + impl->surface = NULL; + } + + if(impl->display) { + context->gl.eglTerminate(impl->display); + impl->display = NULL; + } + + if(impl->visual_info) { + XFree(impl->visual_info); + impl->visual_info = NULL; + } + + free(self->impl); + self->impl = NULL; +} diff --git a/src/graphics/backend/glx.c b/src/graphics/backend/glx.c new file mode 100644 index 0000000..70836e3 --- /dev/null +++ b/src/graphics/backend/glx.c @@ -0,0 +1,173 @@ +#include "../../../include/mgl/graphics/backend/glx.h" +#include "../../../include/mgl/mgl.h" + +#include <stdlib.h> +#include <stdio.h> + +#include <X11/Xutil.h> +#include <X11/extensions/Xrender.h> + +static void mgl_graphics_glx_deinit(mgl_graphics *self); + +typedef struct { + GLXContext glx_context; + GLXFBConfig *fbconfigs; + GLXFBConfig fbconfig; + XVisualInfo *visual_info; +} mgl_graphics_glx; + +static bool xvisual_match_alpha(Display *dpy, XVisualInfo *visual_info, bool alpha) { + XRenderPictFormat *pict_format = XRenderFindVisualFormat(dpy, visual_info->visual); + if(!pict_format) + return false; + return (alpha && pict_format->direct.alphaMask > 0) || (!alpha && pict_format->direct.alphaMask == 0); +} + +static bool mgl_graphics_glx_choose_config(mgl_graphics_glx *self, mgl_context *context, bool alpha) { + const int attr[] = { + GLX_RENDER_TYPE, GLX_RGBA_BIT, + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_DOUBLEBUFFER, True, + GLX_RED_SIZE, 8, + GLX_GREEN_SIZE, 8, + GLX_BLUE_SIZE, 8, + GLX_ALPHA_SIZE, alpha ? 8 : 0, + // TODO: + //GLX_DEPTH_SIZE, 0, + None + }; + + self->fbconfigs = NULL; + self->visual_info = NULL; + self->fbconfig = NULL; + + int numfbconfigs = 0; + self->fbconfigs = context->gl.glXChooseFBConfig(context->connection, DefaultScreen(context->connection), attr, &numfbconfigs); + for(int i = 0; i < numfbconfigs; i++) { + self->visual_info = (XVisualInfo*)context->gl.glXGetVisualFromFBConfig(context->connection, self->fbconfigs[i]); + if(!self->visual_info) + continue; + + if(xvisual_match_alpha(context->connection, self->visual_info, alpha)) { + self->fbconfig = self->fbconfigs[i]; + break; + } else { + XFree(self->visual_info); + self->visual_info = NULL; + self->fbconfig = NULL; + } + } + + if(!self->fbconfig) { + fprintf(stderr, "mgl error: mgl_graphics_glx_choose_config: no appropriate glx config found\n"); + return false; + } + + if(!self->visual_info) { + fprintf(stderr, "mgl error: mgl_graphics_glx_choose_config: no appropriate visual found\n"); + return false; + } + + return true; +} + +static bool mgl_graphics_glx_make_context_current(mgl_graphics *self, mgl_window_handle window) { + mgl_graphics_glx *impl = self->impl; + mgl_context *context = mgl_get_context(); + return context->gl.glXMakeContextCurrent(context->connection, (GLXDrawable)window, (GLXDrawable)window, impl->glx_context) != 0; +} + +static void mgl_graphics_glx_swap_buffers(mgl_graphics *self, mgl_window_handle window) { + (void)self; + mgl_context *context = mgl_get_context(); + context->gl.glXSwapBuffers(context->connection, (GLXDrawable)window); +} + +/* 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 bool mgl_graphics_glx_set_swap_interval(mgl_graphics *self, mgl_window_handle window, bool enabled) { + (void)self; + mgl_context *context = mgl_get_context(); + + int result = 0; + if(context->gl.glXSwapIntervalEXT) { + context->gl.glXSwapIntervalEXT(context->connection, (GLXDrawable)window, enabled); + } else if(context->gl.glXSwapIntervalMESA) { + result = context->gl.glXSwapIntervalMESA(enabled); + } else if(context->gl.glXSwapIntervalSGI) { + result = context->gl.glXSwapIntervalSGI(enabled); + } else { + static int warned = 0; + if (!warned) { + warned = 1; + fprintf(stderr, "mgl warning: setting vertical sync not supported\n"); + } + } + + if(result != 0) + fprintf(stderr, "mgl warning: setting vertical sync failed\n"); + + return result == 0; +} + +static void* mgl_graphics_glx_get_xvisual_info(mgl_graphics *self) { + mgl_graphics_glx *impl = self->impl; + return impl->visual_info; +} + +bool mgl_graphics_glx_init(mgl_graphics *self) { + mgl_graphics_glx *impl = calloc(1, sizeof(mgl_graphics_glx)); + if(!impl) + return false; + + self->deinit = mgl_graphics_glx_deinit; + self->make_context_current = mgl_graphics_glx_make_context_current; + self->swap_buffers = mgl_graphics_glx_swap_buffers; + self->set_swap_interval = mgl_graphics_glx_set_swap_interval; + self->get_xvisual_info = mgl_graphics_glx_get_xvisual_info; + self->impl = impl; + + mgl_context *context = mgl_get_context(); + if(!mgl_graphics_glx_choose_config(impl, context, self->alpha)) { + mgl_graphics_glx_deinit(self); + return false; + } + + impl->glx_context = context->gl.glXCreateNewContext(context->connection, impl->fbconfig, GLX_RGBA_TYPE, 0, True); + if(!impl->glx_context) { + fprintf(stderr, "mgl error: mgl_graphics_glx_init: glXCreateContext failed\n"); + mgl_graphics_glx_deinit(self); + return false; + } + + return true; +} + +void mgl_graphics_glx_deinit(mgl_graphics *self) { + mgl_graphics_glx *impl = self->impl; + if(!impl) + return; + + mgl_context *context = mgl_get_context(); + + if(impl->glx_context) { + context->gl.glXMakeContextCurrent(context->connection, None, None, NULL); + context->gl.glXDestroyContext(context->connection, impl->glx_context); + impl->glx_context = NULL; + } + + if(impl->visual_info) { + XFree(impl->visual_info); + impl->visual_info = NULL; + } + + if(impl->fbconfigs) { + XFree(impl->fbconfigs); + impl->fbconfigs = NULL; + } + + impl->fbconfig = NULL; + + free(self->impl); + self->impl = NULL; +} diff --git a/src/graphics/backend/graphics.c b/src/graphics/backend/graphics.c new file mode 100644 index 0000000..201b9e3 --- /dev/null +++ b/src/graphics/backend/graphics.c @@ -0,0 +1,68 @@ +#include "../../../include/mgl/graphics/backend/graphics.h" +#include "../../../include/mgl/graphics/backend/glx.h" +#include "../../../include/mgl/graphics/backend/egl.h" +#include "../../../include/mgl/mgl.h" + +#include <string.h> + +bool mgl_graphics_init(mgl_graphics *self, const mgl_graphics_create_params *params) { + memset(self, 0, sizeof(*self)); + self->graphics_api = params ? params->graphics_api : MGL_GRAPHICS_API_EGL; + self->alpha = params && params->alpha; + + switch(self->graphics_api) { + case MGL_GRAPHICS_API_GLX: + return mgl_graphics_glx_init(self); + case MGL_GRAPHICS_API_EGL: + return mgl_graphics_egl_init(self); + } + return false; +} + +void mgl_graphics_deinit(mgl_graphics *self) { + if(self->deinit) + self->deinit(self); +} + +bool mgl_graphics_make_context_current(mgl_graphics *self, mgl_window_handle window) { + const bool result = self->make_context_current(self, window); + if(result) { + mgl_context *context = mgl_get_context(); + context->gl.glEnable(GL_TEXTURE_2D); + context->gl.glEnable(GL_BLEND); + context->gl.glEnable(GL_SCISSOR_TEST); + context->gl.glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + context->gl.glEnableClientState(GL_VERTEX_ARRAY); + context->gl.glEnableClientState(GL_TEXTURE_COORD_ARRAY); + context->gl.glEnableClientState(GL_COLOR_ARRAY); + context->gl.glPixelStorei(GL_PACK_ALIGNMENT, 1); + context->gl.glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + } + return result; +} + +void mgl_graphics_swap_buffers(mgl_graphics *self, mgl_window_handle window) { + self->swap_buffers(self, window); +} + +bool mgl_graphics_set_swap_interval(mgl_graphics *self, mgl_window_handle window, bool enabled) { + return self->set_swap_interval(self, window, enabled); +} + +void* mgl_graphics_get_xvisual_info(mgl_graphics *self) { + return self->get_xvisual_info(self); +} + +void* mgl_graphics_get_display(mgl_graphics *self) { + if(self->get_display) + return self->get_display(self); + else + return NULL; +} + +void* mgl_graphics_get_context(mgl_graphics *self) { + if(self->get_context) + return self->get_context(self); + else + return NULL; +} diff --git a/src/graphics/font.c b/src/graphics/font.c index 12235f6..671d8a1 100644 --- a/src/graphics/font.c +++ b/src/graphics/font.c @@ -210,7 +210,7 @@ int mgl_font_get_glyph(mgl_font *self, uint32_t codepoint, mgl_font_glyph *glyph /* TODO: Use stbtt_MakeGlyphBitmapSubpixelPrefilter instead for better text quality */ const size_t pixels_width = (width + GLYPH_PADDING * 2); const size_t pixels_height = (height + GLYPH_PADDING * 2); - const size_t pixels_size = pixels_width * pixels_height; + const size_t pixels_size = pixels_width * pixels_height * 2; // *2 required for opengl glTexSubImage2D unsigned char *pixels = calloc(pixels_size, 1); if(pixels) { const int top_padding = GLYPH_PADDING; diff --git a/src/graphics/shader.c b/src/graphics/shader.c index 83c8407..f362490 100644 --- a/src/graphics/shader.c +++ b/src/graphics/shader.c @@ -203,6 +203,39 @@ int mgl_shader_program_set_uniform_vec2f(mgl_shader_program *self, const char *u return 0; } +int mgl_shader_program_set_uniform_vec3f(mgl_shader_program *self, const char *uniform_name, mgl_vec3f value) { + mgl_context *context = mgl_get_context(); + int uniform_location = context->gl.glGetUniformLocation(self->id, uniform_name); + if(uniform_location == -1) { + fprintf(stderr, "mgl error: no uniform by the name %s was found in the shader\n", uniform_name); + return -1; + } + + context->gl.glUseProgram(self->id); + context->gl.glUniform3f(uniform_location, value.x, value.y, value.z); + context->gl.glUseProgram(0); + return 0; +} + +int mgl_shader_program_set_uniform_vec4f(mgl_shader_program *self, const char *uniform_name, mgl_vec4f value) { + mgl_context *context = mgl_get_context(); + int uniform_location = context->gl.glGetUniformLocation(self->id, uniform_name); + if(uniform_location == -1) { + fprintf(stderr, "mgl error: no uniform by the name %s was found in the shader\n", uniform_name); + return -1; + } + + context->gl.glUseProgram(self->id); + context->gl.glUniform4f(uniform_location, value.x, value.y, value.z, value.w); + context->gl.glUseProgram(0); + return 0; +} + +int mgl_shader_program_set_uniform_color(mgl_shader_program *self, const char *uniform_name, mgl_color color) { + return mgl_shader_program_set_uniform_vec4f(self, uniform_name, + (mgl_vec4f){ color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f }); +} + /* TODO: Optimize glUseProgram */ void mgl_shader_program_use(mgl_shader_program *shader_program) { mgl_context *context = mgl_get_context(); diff --git a/src/graphics/sprite.c b/src/graphics/sprite.c index df3e902..7ef81af 100644 --- a/src/graphics/sprite.c +++ b/src/graphics/sprite.c @@ -31,6 +31,38 @@ void mgl_sprite_set_origin(mgl_sprite *self, mgl_vec2f origin) { self->origin = origin; } +void mgl_sprite_set_size(mgl_sprite *self, mgl_vec2f size) { + if(!self->texture) + return; + + self->scale.x = size.x / (float)self->texture->width; + self->scale.y = size.y / (float)self->texture->height; +} + +void mgl_sprite_set_width(mgl_sprite *self, float width) { + if(!self->texture) + return; + + self->scale.x = width / (float)self->texture->width; + self->scale.y = self->scale.x; +} + +void mgl_sprite_set_height(mgl_sprite *self, float height) { + if(!self->texture) + return; + + self->scale.y = height / (float)self->texture->height; + self->scale.x = self->scale.y; +} + +mgl_vec2f mgl_sprite_get_size(const mgl_sprite *self) { + if(self->texture) { + return (mgl_vec2f){ (float)self->texture->width * self->scale.x, (float)self->texture->height * self->scale.y }; + } else { + return (mgl_vec2f){ 0.0f, 0.0f }; + } +} + /* TODO: Cache texture bind to not bind texture if its already bound and do not bind texture 0 */ void mgl_sprite_draw(mgl_context *context, mgl_sprite *sprite) { if(!sprite->texture) diff --git a/src/graphics/text.c b/src/graphics/text.c index 0942b62..3385667 100644 --- a/src/graphics/text.c +++ b/src/graphics/text.c @@ -8,15 +8,38 @@ #define TAB_WIDTH 4 +typedef struct { + size_t codepoint_index; + uint32_t codepoint; + float kerning; + float glyph_width; + const mgl_font_glyph *glyph; + mgl_vec2f glyph_offset; +} codepoint_loop_callback_data; + +static float max_float(float a, float b) { + return a >= b ? a : b; +} + +static float float_abs(float value) { + return value >= 0.0f ? value : -value; +} + /* Return false to stop the loop. |glyph| is not valid if codepoint is '\n' */ -typedef bool (*codepoint_loop_callback)(size_t codepoint_index, uint32_t codepoint, const mgl_font_glyph *glyph, void *userdata); +typedef bool (*codepoint_loop_callback)(codepoint_loop_callback_data callback_data, void *userdata); static void mgl_text_for_each_codepoint(const mgl_text *self, codepoint_loop_callback callback, void *userdata) { mgl_font_glyph glyph; size_t codepoint_index = 0; + float kerning = 0.0f; + float glyph_width = 0.0f; + uint32_t prev_codepoint = 0; + mgl_vec2f glyph_offset = {0, 0}; + unsigned int rows = 1; + for(size_t i = 0; i < self->text_size;) { unsigned char *cp = (unsigned char*)&self->text[i]; - uint32_t codepoint; - size_t clen; + uint32_t codepoint = 0; + size_t clen = 0; if(!mgl_utf8_decode(cp, self->text_size - i, &codepoint, &clen)) { codepoint = *cp; clen = 1; @@ -24,52 +47,60 @@ static void mgl_text_for_each_codepoint(const mgl_text *self, codepoint_loop_cal if(codepoint == '\t') { if(mgl_font_get_glyph(self->font, ' ', &glyph) == 0) { - if(!callback(codepoint_index, codepoint, &glyph, userdata)) - return; + kerning = mgl_font_get_kerning(self->font, prev_codepoint, codepoint); + glyph_width = glyph.advance * TAB_WIDTH; + } else { + goto next; } } else if(codepoint == '\n') { - if(!callback(codepoint_index, codepoint, &glyph, userdata)) - return; + kerning = 0.0f; + glyph_width = 0.0f; + glyph.size = (mgl_vec2i){ (int)glyph_width, (int)self->font->character_size }; } else { if(mgl_font_get_glyph(self->font, codepoint, &glyph) == 0) { - if(!callback(codepoint_index, codepoint, &glyph, userdata)) - return; + kerning = mgl_font_get_kerning(self->font, prev_codepoint, codepoint); + glyph_width = glyph.advance; + } else { + goto next; } } + if(((glyph_offset.x + kerning + glyph_width > self->max_width) && self->max_width > 0.001f) || codepoint == '\n') { + glyph_offset.x = 0.0f; + glyph_offset.y += self->font->character_size; + prev_codepoint = 0; + + if(rows == self->max_rows) + return; + ++rows; + } else { + glyph_offset.x += kerning; + } + + if(!callback((codepoint_loop_callback_data){ codepoint_index, codepoint, kerning, glyph_width, &glyph, glyph_offset }, userdata)) + return; + + glyph_offset.x += glyph_width; + //if(codepoint == '\n') + // glyph_offset.y += self->font->character_size; + + prev_codepoint = codepoint; + + next: i += clen; ++codepoint_index; } } -static float max_float(float a, float b) { - return a >= b ? a : b; -} - typedef struct { mgl_vec2f bounds; - float line_width; - const mgl_font *font; - uint32_t prev_codepoint; + mgl_font *font; } CalculateBoundsUserdata; -static bool calculate_bounds_callback(size_t codepoint_index, uint32_t codepoint, const mgl_font_glyph *glyph, void *userdata) { - (void)codepoint_index; - +static bool calculate_bounds_callback(codepoint_loop_callback_data callback_data, void *userdata) { CalculateBoundsUserdata *calculate_bounds_userdata = userdata; - if(codepoint == '\t') { - calculate_bounds_userdata->line_width += (glyph->advance * TAB_WIDTH) + mgl_font_get_kerning(calculate_bounds_userdata->font, calculate_bounds_userdata->prev_codepoint, codepoint); - calculate_bounds_userdata->bounds.x = max_float(calculate_bounds_userdata->bounds.x, calculate_bounds_userdata->line_width); - } else if(codepoint == '\n') { - calculate_bounds_userdata->line_width = 0.0f; - calculate_bounds_userdata->bounds.y += calculate_bounds_userdata->font->character_size; - calculate_bounds_userdata->prev_codepoint = 0; - } else { - calculate_bounds_userdata->line_width += glyph->advance + mgl_font_get_kerning(calculate_bounds_userdata->font, calculate_bounds_userdata->prev_codepoint, codepoint); - calculate_bounds_userdata->bounds.x = max_float(calculate_bounds_userdata->bounds.x, calculate_bounds_userdata->line_width); - } - - calculate_bounds_userdata->prev_codepoint = codepoint; + calculate_bounds_userdata->bounds.x = max_float(calculate_bounds_userdata->bounds.x, callback_data.glyph_offset.x + callback_data.glyph_width); + calculate_bounds_userdata->bounds.y = max_float(calculate_bounds_userdata->bounds.y, callback_data.glyph_offset.y + calculate_bounds_userdata->font->character_size); return true; } @@ -77,9 +108,7 @@ static mgl_vec2f mgl_text_calculate_bounds(mgl_text *self) { CalculateBoundsUserdata calculate_bounds_userdata; calculate_bounds_userdata.bounds.x = 0.0f; calculate_bounds_userdata.bounds.y = self->font->character_size; - calculate_bounds_userdata.line_width = 0.0f; calculate_bounds_userdata.font = self->font; - calculate_bounds_userdata.prev_codepoint = 0; mgl_text_for_each_codepoint(self, calculate_bounds_callback, &calculate_bounds_userdata); return calculate_bounds_userdata.bounds; } @@ -91,6 +120,8 @@ void mgl_text_init(mgl_text *self, mgl_font *font, const char *str, size_t str_s self->text = NULL; self->text_size = 0; self->bounds_dirty = true; + self->max_width = 0.0f; + self->max_rows = 0; mgl_text_set_string(self, str, str_size); } @@ -105,14 +136,12 @@ void mgl_text_deinit(mgl_text *self) { void mgl_text_set_string(mgl_text *self, const char *str, size_t str_size) { self->text = str; self->text_size = str_size; - if(self->text && self->text_size > 0 && self->font) - self->bounds_dirty = true; + self->bounds_dirty = true; } void mgl_text_set_font(mgl_text *self, mgl_font *font) { self->font = font; - if(self->text && self->text_size > 0 && self->font) - self->bounds_dirty = true; + self->bounds_dirty = true; } void mgl_text_set_position(mgl_text *self, mgl_vec2f position) { @@ -123,11 +152,26 @@ void mgl_text_set_color(mgl_text *self, mgl_color color) { self->color = color; } +void mgl_text_set_max_width(mgl_text *self, float max_width) { + if(float_abs(max_width - self->max_width) < 0.01) + return; + self->bounds_dirty = true; + self->max_width = max_width; +} + +void mgl_text_set_max_rows(mgl_text *self, unsigned int max_rows) { + if(max_rows != self->max_rows) + self->bounds_dirty = true; + self->max_rows = max_rows; +} + mgl_vec2f mgl_text_get_bounds(mgl_text *self) { if(self->bounds_dirty) { self->bounds_dirty = false; if(self->text && self->text_size > 0 && self->font) self->bounds = mgl_text_calculate_bounds(self); + else if(self->font) + self->bounds = (mgl_vec2f){ 0.0f, (float)self->font->character_size }; else self->bounds = (mgl_vec2f){ 0.0f, 0.0f }; } @@ -135,39 +179,30 @@ mgl_vec2f mgl_text_get_bounds(mgl_text *self) { } typedef struct { - mgl_vec2f position; + mgl_vec2f glyph_offset; const mgl_text *text; size_t index; - uint32_t prev_codepoint; } FindCharacterPosUserdata; -static bool find_character_pos_callback(size_t codepoint_index, uint32_t codepoint, const mgl_font_glyph *glyph, void *userdata) { +static bool find_character_pos_callback(codepoint_loop_callback_data callback_data, void *userdata) { FindCharacterPosUserdata *find_character_pos_userdata = userdata; - if(codepoint_index >= find_character_pos_userdata->index) + find_character_pos_userdata->glyph_offset = callback_data.glyph_offset; + if(callback_data.codepoint_index >= find_character_pos_userdata->index) return false; - if(codepoint == '\t') { - find_character_pos_userdata->position.x += (glyph->advance * TAB_WIDTH) + mgl_font_get_kerning(find_character_pos_userdata->text->font, find_character_pos_userdata->prev_codepoint, codepoint); - } else if(codepoint == '\n') { - find_character_pos_userdata->position.x = find_character_pos_userdata->text->position.x; - find_character_pos_userdata->position.y += find_character_pos_userdata->text->font->character_size; - find_character_pos_userdata->prev_codepoint = 0; - } else { - find_character_pos_userdata->position.x += glyph->advance + mgl_font_get_kerning(find_character_pos_userdata->text->font, find_character_pos_userdata->prev_codepoint, codepoint); - } - - find_character_pos_userdata->prev_codepoint = codepoint; + find_character_pos_userdata->glyph_offset.x += callback_data.glyph_width; + if(callback_data.codepoint == '\n') + find_character_pos_userdata->glyph_offset.y += find_character_pos_userdata->text->font->character_size; return true; } mgl_vec2f mgl_text_find_character_pos(mgl_text *self, size_t index) { FindCharacterPosUserdata find_character_pos_userdata; - find_character_pos_userdata.position = self->position; + find_character_pos_userdata.glyph_offset = (mgl_vec2f){0.0f, 0.0f}; find_character_pos_userdata.text = self; find_character_pos_userdata.index = index; - find_character_pos_userdata.prev_codepoint = 0; mgl_text_for_each_codepoint(self, find_character_pos_callback, &find_character_pos_userdata); - return (mgl_vec2f){ find_character_pos_userdata.position.x, find_character_pos_userdata.position.y }; + return (mgl_vec2f){ self->position.x + find_character_pos_userdata.glyph_offset.x, self->position.y + find_character_pos_userdata.glyph_offset.y }; } static void mgl_text_draw_glyph(mgl_context *context, const mgl_font_glyph *glyph, mgl_vec2i position) { @@ -188,24 +223,14 @@ typedef struct { mgl_vec2f position; const mgl_text *text; mgl_context *context; - uint32_t prev_codepoint; } TextDrawUserdata; -static bool text_draw_callback(size_t codepoint_index, uint32_t codepoint, const mgl_font_glyph *glyph, void *userdata) { - (void)codepoint_index; +static bool text_draw_callback(codepoint_loop_callback_data callback_data, void *userdata) { TextDrawUserdata *text_draw_userdata = userdata; - if(codepoint == '\t') { - text_draw_userdata->position.x += (glyph->advance * TAB_WIDTH) + mgl_font_get_kerning(text_draw_userdata->text->font, text_draw_userdata->prev_codepoint, codepoint); - } else if(codepoint == '\n') { - text_draw_userdata->position.x = text_draw_userdata->text->position.x; - text_draw_userdata->position.y += text_draw_userdata->text->font->character_size; - text_draw_userdata->prev_codepoint = 0; - } else { - text_draw_userdata->position.x += mgl_font_get_kerning(text_draw_userdata->text->font, text_draw_userdata->prev_codepoint, codepoint); - mgl_text_draw_glyph(text_draw_userdata->context, glyph, (mgl_vec2i){ text_draw_userdata->position.x, text_draw_userdata->position.y }); - text_draw_userdata->position.x += glyph->advance; + if(callback_data.codepoint != '\t' && callback_data.codepoint != '\n') { + mgl_text_draw_glyph(text_draw_userdata->context, callback_data.glyph, + (mgl_vec2i){ text_draw_userdata->position.x + callback_data.glyph_offset.x, text_draw_userdata->position.y + callback_data.glyph_offset.y }); } - text_draw_userdata->prev_codepoint = codepoint; return true; } @@ -223,7 +248,6 @@ void mgl_text_draw(mgl_context *context, mgl_text *text) { text_draw_userdata.position = (mgl_vec2f){ text->position.x, text->position.y }; text_draw_userdata.text = text; text_draw_userdata.context = context; - text_draw_userdata.prev_codepoint = 0; context->gl.glColor4ub(text->color.r, text->color.g, text->color.b, text->color.a); mgl_texture_use(&text->font->texture); diff --git a/src/graphics/texture.c b/src/graphics/texture.c index 128d910..d33bf58 100644 --- a/src/graphics/texture.c +++ b/src/graphics/texture.c @@ -51,6 +51,17 @@ static mgl_texture_format mgl_image_format_to_mgl_texture_format(mgl_image_forma return 0; } +static void gl_get_texture_size(unsigned int texture_id, int *width, int *height) { + *width = 0; + *height = 0; + + mgl_context *context = mgl_get_context(); + context->gl.glBindTexture(GL_TEXTURE_2D, texture_id); + context->gl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, width); + context->gl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, height); + context->gl.glBindTexture(GL_TEXTURE_2D, 0); +} + int mgl_texture_init(mgl_texture *self) { self->id = 0; self->width = 0; @@ -59,6 +70,8 @@ int mgl_texture_init(mgl_texture *self) { self->max_width = 0; self->max_height = 0; self->pixel_coordinates = false; + self->scale_type = MGL_TEXTURE_SCALE_LINEAR; + self->owned = true; mgl_context *context = mgl_get_context(); context->gl.glGenTextures(1, &self->id); @@ -75,7 +88,29 @@ int mgl_texture_init(mgl_texture *self) { return 0; } -int mgl_texture_load_from_file(mgl_texture *self, const char *filepath, mgl_texture_load_options *load_options) { +int mgl_texture_init_reference_existing_gl_texture(mgl_texture *self, unsigned int texture_id, mgl_texture_format format, const mgl_texture_reference_options *reference_options) { + self->id = texture_id; + self->width = 0; + self->height = 0; + self->format = format; + self->max_width = 0; + self->max_height = 0; + self->pixel_coordinates = reference_options && reference_options->pixel_coordinates; + self->scale_type = reference_options ? reference_options->scale_type : MGL_TEXTURE_SCALE_LINEAR; + self->owned = false; + + gl_get_texture_size(self->id, &self->width, &self->height); + + int max_texture_size = 0; + mgl_context *context = mgl_get_context(); + 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, const mgl_texture_load_options *load_options) { mgl_image image; if(mgl_image_load_from_file(&image, filepath) != 0) return -1; @@ -85,33 +120,56 @@ int mgl_texture_load_from_file(mgl_texture *self, const char *filepath, mgl_text return result; } -int mgl_texture_load_from_image(mgl_texture *self, const mgl_image *image, mgl_texture_load_options *load_options) { +int mgl_texture_load_from_image(mgl_texture *self, const mgl_image *image, const mgl_texture_load_options *load_options) { return mgl_texture_load_from_memory(self, image->data, image->width, image->height, image->format, load_options); } -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_load_from_memory(mgl_texture *self, const unsigned char *data, int width, int height, mgl_image_format format, const mgl_texture_load_options *load_options) { if(width < 0 || height < 0) return -1; if(width > self->max_width || height > self->max_height) return -1; + mgl_context *context = mgl_get_context(); + 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; + self->pixel_coordinates = load_options && load_options->pixel_coordinates; + self->scale_type = load_options ? load_options->scale_type : MGL_TEXTURE_SCALE_LINEAR; /* TODO: Check if glGenerateMipmap is actually available */ 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); context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + switch(self->scale_type) { + default: + case MGL_TEXTURE_SCALE_LINEAR: { + context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + break; + } + case MGL_TEXTURE_SCALE_NEAREST: { + context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + break; + } + case MGL_TEXTURE_SCALE_LINEAR_MIPMAP: { + context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + if(context->gl.glGenerateMipmap) { + context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + context->gl.glGenerateMipmap(GL_TEXTURE_2D); + } else { + context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + break; + } + } context->gl.glBindTexture(GL_TEXTURE_2D, 0); return 0; @@ -125,11 +183,13 @@ int mgl_texture_update(mgl_texture *self, const unsigned char *data, int offset_ context->gl.glBindTexture(GL_TEXTURE_2D, self->id); const mgl_texture_format texture_format = mgl_image_format_to_mgl_texture_format(format); 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); + if(self->scale_type == MGL_TEXTURE_SCALE_LINEAR_MIPMAP && context->gl.glGenerateMipmap) + context->gl.glGenerateMipmap(GL_TEXTURE_2D); 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) { +int mgl_texture_resize(mgl_texture *self, int new_width, int new_height, const mgl_texture_load_options *load_options) { if(new_width == self->width && new_height == self->height) return 0; @@ -147,6 +207,8 @@ int mgl_texture_resize(mgl_texture *self, int new_width, int new_height, mgl_tex 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); + if(self->scale_type == MGL_TEXTURE_SCALE_LINEAR_MIPMAP && context->gl.glGenerateMipmap) + context->gl.glGenerateMipmap(GL_TEXTURE_2D); context->gl.glBindTexture(GL_TEXTURE_2D, 0); return 0; } @@ -185,6 +247,9 @@ const mgl_texture* mgl_texture_current_texture(void) { void mgl_texture_unload(mgl_texture *self) { mgl_context *context = mgl_get_context(); + if(!self->owned) + return; + if(self->id) { context->gl.glDeleteTextures(1, &self->id); self->id = 0; |