Wed, 15 Nov 2023 22:51:40 +0100
add text rendering and demo FPS counter
--- a/shader/font_frag.glsl Wed Nov 08 23:17:07 2023 +0100 +++ b/shader/font_frag.glsl Wed Nov 15 22:51:40 2023 +0100 @@ -1,10 +1,10 @@ #version 400 core +layout(location = 0) out vec4 diffuse; in vec2 texcoord; -out vec4 fragment; uniform sampler2DRect surface; void main(void) { - fragment = texture(surface, texcoord); + diffuse = texture(surface, texcoord); }
--- a/shader/font_vtx.glsl Wed Nov 08 23:17:07 2023 +0100 +++ b/shader/font_vtx.glsl Wed Nov 15 22:51:40 2023 +0100 @@ -1,6 +1,6 @@ #version 400 core -in vec2 position; +layout(location = 0) in vec2 position; out vec2 texcoord; uniform mat4 projection; @@ -8,5 +8,5 @@ void main(void) { gl_Position = projection*model*vec4(position, 0.0, 1.0); - texcoord = position; + texcoord = vec2(model[0].x, model[1].y)*position; }
--- a/src/Makefile Wed Nov 08 23:17:07 2023 +0100 +++ b/src/Makefile Wed Nov 15 22:51:40 2023 +0100 @@ -27,7 +27,7 @@ BUILD_DIR=../build/lib -SRC = context.c error.c window.c font.c files.c shader.c +SRC = context.c error.c window.c files.c shader.c font.c text.c primitives.c OBJ = $(SRC:%.c=$(BUILD_DIR)/%.o) @@ -40,15 +40,16 @@ FORCE: -$(BUILD_DIR)/context.o: context.c ascension/context.h ascension/window.h \ - ascension/datatypes.h ascension/font.h ascension/error.h \ - ascension/utils.h ascension/shader.h +$(BUILD_DIR)/context.o: context.c ascension/context.h \ + ascension/datatypes.h ascension/window.h ascension/primitives.h \ + ascension/mesh.h ascension/font.h ascension/error.h ascension/utils.h \ + ascension/shader.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $< -$(BUILD_DIR)/error.o: error.c ascension/context.h ascension/window.h \ - ascension/datatypes.h ascension/font.h ascension/error.h \ - ascension/utils.h +$(BUILD_DIR)/error.o: error.c ascension/context.h ascension/datatypes.h \ + ascension/window.h ascension/primitives.h ascension/mesh.h \ + ascension/font.h ascension/error.h ascension/utils.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $< @@ -57,8 +58,15 @@ $(CC) -o $@ $(CFLAGS) -c $< $(BUILD_DIR)/font.o: font.c ascension/font.h ascension/context.h \ - ascension/window.h ascension/datatypes.h ascension/font.h \ - ascension/error.h + ascension/datatypes.h ascension/window.h ascension/primitives.h \ + ascension/mesh.h ascension/font.h ascension/error.h + @echo "Compiling $<" + $(CC) -o $@ $(CFLAGS) -c $< + +$(BUILD_DIR)/primitives.o: primitives.c ascension/primitives.h \ + ascension/mesh.h ascension/error.h ascension/context.h \ + ascension/datatypes.h ascension/window.h ascension/primitives.h \ + ascension/font.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $< @@ -67,9 +75,16 @@ @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $< -$(BUILD_DIR)/window.o: window.c ascension/window.h ascension/datatypes.h \ - ascension/context.h ascension/window.h ascension/font.h \ - ascension/error.h ascension/utils.h +$(BUILD_DIR)/text.o: text.c ascension/text.h ascension/font.h \ + ascension/datatypes.h ascension/context.h ascension/window.h \ + ascension/primitives.h ascension/mesh.h ascension/error.h \ + ascension/shader.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $< +$(BUILD_DIR)/window.o: window.c ascension/window.h ascension/datatypes.h \ + ascension/primitives.h ascension/mesh.h ascension/context.h \ + ascension/window.h ascension/font.h ascension/error.h ascension/utils.h + @echo "Compiling $<" + $(CC) -o $@ $(CFLAGS) -c $< +
--- a/src/ascension/ascension.h Wed Nov 08 23:17:07 2023 +0100 +++ b/src/ascension/ascension.h Wed Nov 15 22:51:40 2023 +0100 @@ -31,6 +31,7 @@ #include "error.h" #include "context.h" #include "shader.h" +#include "text.h" #endif /* ASCENSION_H */
--- a/src/ascension/context.h Wed Nov 08 23:17:07 2023 +0100 +++ b/src/ascension/context.h Wed Nov 15 22:51:40 2023 +0100 @@ -28,6 +28,7 @@ #ifndef ASCENSION_CONTEXT_H #define ASCENSION_CONTEXT_H +#include "datatypes.h" #include "window.h" #include "font.h" @@ -50,8 +51,13 @@ unsigned int flags; CxBuffer error_buffer; AscWindow windows[ASC_MAX_WINDOWS]; + AscWindow const *active_window; AscFont fonts[ASC_MAX_FONTS]; unsigned int fonts_loaded; + AscFont const *active_font; + asc_col4i ink; + unsigned int elapsed_millis; + float elapsed; } AscContext; /** Global ascension context. */ @@ -60,5 +66,25 @@ void asc_context_initialize(void); void asc_context_destroy(void); + +/** + * Dispatches events and synchronizes all initialized windows. + * + * @return false, if the application wants to quit, true otherwise + */ +bool asc_loop_next(void); + +/** + * Sets the active drawing color. + */ +#define asc_ink(color) asc_context.ink = (color) +#define asc_ink_rgba(r,g,b,a) asc_context.ink = (asc_col4i){(r),(g),(b),(a)} +#define asc_ink_rgb(r,g,b) asc_context.ink = (asc_col4i){(r),(g),(b),255u} + +/** + * Sets the active drawing font. + */ +#define asc_set_font(font) asc_context.active_font = (font) + #endif /* ASCENSION_CONTEXT_H */
--- a/src/ascension/datatypes.h Wed Nov 08 23:17:07 2023 +0100 +++ b/src/ascension/datatypes.h Wed Nov 15 22:51:40 2023 +0100 @@ -33,7 +33,7 @@ #endif #include <stdbool.h> -#include <SDL2/SDL_endian.h> +#include <string.h> // -------------------------------------------------------------------------- // Datatype Definitions @@ -48,25 +48,15 @@ struct { int width, height; }; } asc_vec2i; -typedef union asc_col4i { - asc_ubyte data[4]; -#if SDL_BYTEORDER == SDL_BIG_ENDIAN - struct { asc_ubyte red, green, blue, alpha; }; -#else - struct { asc_ubyte alpha, blue, green, red; }; -#endif +typedef struct asc_col4i { + asc_ubyte red, green, blue, alpha; } asc_col4i; -typedef union asc_col4f { - float data[4]; -#if SDL_BYTEORDER == SDL_BIG_ENDIAN - struct { float red, green, blue, alpha; }; -#else - struct { float alpha, blue, green, red; }; -#endif +typedef struct asc_col4f { + float red, green, blue, alpha; } asc_col4f; -typedef float asc_mat4f[4][4]; +typedef float asc_mat4f[16]; // -------------------------------------------------------------------------- // General Utility Functions @@ -104,6 +94,21 @@ // Matrix Functions // -------------------------------------------------------------------------- +/** + * Computes a matrix index in column-major order. + * @param col the column + * @param row the row + * @param rows the number of rows + */ +#define asc_mat_index(col, row, rows) ((row) + (col) * (rows)) + +/** + * Computes a 4x4 matrix index in column-major order. + * @param col the column + * @param row the row + */ +#define asc_mat4_index(col, row) asc_mat_index(col, row, 4) + static inline void asc_mat4f_ortho( asc_mat4f mat, float left, @@ -112,11 +117,12 @@ float top ) { memset(mat, 0, sizeof(float) * 16); - mat[0][0] = 2.f / (right - left); - mat[1][1] = 2.f / (top - bottom); - mat[2][2] = -1; - mat[3][0] = -(right + left) / (right - left); - mat[3][1] = -(top + bottom) / (top - bottom); + mat[asc_mat4_index(0,0)] = 2.f / (right - left); + mat[asc_mat4_index(1,1)] = 2.f / (top - bottom); + mat[asc_mat4_index(2,2)] = -1; + mat[asc_mat4_index(3,0)] = -(right + left) / (right - left); + mat[asc_mat4_index(3,1)] = -(top + bottom) / (top - bottom); + mat[asc_mat4_index(3,3)] = 1; } #endif //ASCENSION_DATATYPES_H
--- a/src/ascension/error.h Wed Nov 08 23:17:07 2023 +0100 +++ b/src/ascension/error.h Wed Nov 15 22:51:40 2023 +0100 @@ -42,6 +42,8 @@ unsigned char*: asc_error_cuchar, \ cxstring: asc_error_cxstr)(text) +void asc_error_gl(unsigned code, cxstring message); + bool asc_has_error(void); char const* asc_get_error(void); void asc_clear_error(void);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ascension/mesh.h Wed Nov 15 22:51:40 2023 +0100 @@ -0,0 +1,37 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * Copyright 2023 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ASCENSION_MESH_H +#define ASCENSION_MESH_H + +typedef struct AscMesh { + unsigned vbo; + unsigned vao; + unsigned vertices; +} AscMesh; + +#endif //ASCENSION_MESH_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ascension/primitives.h Wed Nov 15 22:51:40 2023 +0100 @@ -0,0 +1,57 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * Copyright 2023 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ASCENSION_PRIMITIVES_H +#define ASCENSION_PRIMITIVES_H + +#include <stdbool.h> + +#include "mesh.h" + +typedef struct AscPrimitives { + AscMesh plane; +} AscPrimitives; + + +void asc_primitives_draw_plane(void); + +/** + * Automatically called after initializing the OpenGL context. + * + * @param primitives the data structure to initialize + * @return true on success, false otherwise + */ +bool asc_primitives_init(AscPrimitives *primitives); + +/** + * Automatically called when the OpenGL context is destroyed. + * + * @param primitives containing information about the OpenGL objects + */ +void asc_primitives_destroy(AscPrimitives *primitives); + +#endif //ASCENSION_PRIMITIVES_H
--- a/src/ascension/shader.h Wed Nov 08 23:17:07 2023 +0100 +++ b/src/ascension/shader.h Wed Nov 15 22:51:40 2023 +0100 @@ -34,12 +34,18 @@ typedef struct AscShaderProgram { unsigned int id; - AscShader vertex; - AscShader fragment; + int model; + int view; + int projection; } AscShaderProgram; +typedef struct AscShaderFont { + AscShaderProgram base; + int surface; +} AscShaderFont; -extern AscShaderProgram ASC_SHADER_FONT; + +extern AscShaderFont ASC_SHADER_FONT; /**
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ascension/text.h Wed Nov 15 22:51:40 2023 +0100 @@ -0,0 +1,102 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * Copyright 2023 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ASCENSION_TEXT_H +#define ASCENSION_TEXT_H + +#include "font.h" +#include "datatypes.h" +#include <cx/string.h> + +typedef struct AscTextNode { + asc_vec2i position; + asc_vec2i dimension; + unsigned tex_id; +} AscTextNode; + + +/** + * Draws text on screen. + * + * The current context ink and font will be used. + * + * The text must be zero-terminated (use cx_strdup() e.g. to achieve that) and can + * be freed after the call. + * + * @param node a previously created node or + * @param position the position where to draw the text + * @param max_width the maximum width before breaking the text into new lines + * @param text the text to draw (must be zero-terminated!) + * @see asc_ink() + * @see asc_font() + */ +void asc_text_draw_lb( + AscTextNode *node, + asc_vec2i position, + unsigned max_width, + cxmutstr text); + +/** + * Draws text on screen. + * + * The current context ink and font will be used. + * + * The text must be zero-terminated (use cx_strdup() e.g. to achieve that) and can + * be freed after the call. + * + * @param node a previously created node or + * @param position the position where to draw the text + * @param text the text to draw (must be zero-terminated!) + * @see asc_ink() + * @see asc_font() + */ +void asc_text_draw( + AscTextNode *node, + asc_vec2i position, + cxmutstr text); + +/** + * Just redraw the text node in the current frame. + * + * Invoke this method, when the text did not change and the texture does not need + * to be re-generated. You can change the position of the node. When you change the dimension, + * the text will be scaled. + * + * @param node the text node + */ +void asc_text_redraw(AscTextNode *node); + +/** + * Releases the graphics memory for this node. + * + * The structure can be reused anytime for other draw calls. + * + * @param node the text node + */ +void asc_text_destroy(AscTextNode *node); + +#endif //ASCENSION_TEXT_H
--- a/src/ascension/window.h Wed Nov 08 23:17:07 2023 +0100 +++ b/src/ascension/window.h Wed Nov 15 22:51:40 2023 +0100 @@ -31,6 +31,7 @@ #include <SDL2/SDL.h> #include "datatypes.h" +#include "primitives.h" #ifndef ASC_MAX_WINDOWS /** The maximum number of windows that can exist simultaneously. */ @@ -48,21 +49,14 @@ } AscWindowSettings; typedef struct AscWindow { + Uint32 id; SDL_Window* window; SDL_GLContext glctx; - Uint32 id; + AscPrimitives primitives; asc_vec2i dimensions; asc_mat4f projection; } AscWindow; - -/** - * Dispatches events and synchronizes all initialized windows. - * - * @return false, if the application wants to quit, true otherwise - */ -bool asc_loop_next(void); - /** * Initializes the settings structure with default values. * @@ -73,6 +67,8 @@ /** * Creates and initializes a new window and a corresponding OpenGL context. * + * The new window will also be automatically activated (see asc_window_activate()). + * * The index specified must not be in use by another window already. * The maximum number of windows is defined by #ASC_MAX_WINDOWS. * @@ -85,6 +81,8 @@ /** * Destroys the window and its OpenGL context. * + * When this window is currently active, there will be \em no active window afterwards. + * * Still alive windows will also be destroyed by asc_context_destroy() * automatically. * @@ -102,5 +100,15 @@ */ void asc_window_sync(AscWindow const *window); +/** + * Switches the active window. + * + * In particular that makes the corresponding OpenGL context "current". + * When you only want to draw into one window, you'll never need this. + * + * @param the window to activate + */ +void asc_window_activate(AscWindow const *window); + #endif /* ASCENSION_WINDOW_H */
--- a/src/context.c Wed Nov 08 23:17:07 2023 +0100 +++ b/src/context.c Wed Nov 15 22:51:40 2023 +0100 @@ -81,3 +81,59 @@ asc_context.flags = 0; asc_dprintf("Ascension context destroyed."); } + +static void asc_event_window_resized(Uint32 id, Sint32 width, Sint32 height) { + for (unsigned int i = 0 ; i < ASC_MAX_WINDOWS ; i++) { + if (asc_context.windows[i].id == id) { + asc_context.windows[i].dimensions.width = width; + asc_context.windows[i].dimensions.height = height; + asc_mat4f_ortho(asc_context.windows[i].projection, 0, (float) width, (float) height, 0); + return; + } + } +} + +bool asc_loop_next(void) { + // dispatch SDL events + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + asc_set_flag(&asc_context.flags, ASC_FLAG_QUIT); + break; + case SDL_WINDOWEVENT: { + if (event.window.event == SDL_WINDOWEVENT_RESIZED) + asc_event_window_resized( + event.window.windowID, + event.window.data1, + event.window.data2 + ); + break; + } + case SDL_KEYDOWN: + // TODO: remove this code and implement key press map instead + if (event.key.keysym.sym == SDLK_ESCAPE) + return false; + break; + case SDL_KEYUP: + // TODO: implement key press map + break; + } + } + + // sync the windows + for (unsigned int i = 0 ; i < ASC_MAX_WINDOWS ; i++) { + if (asc_context.windows[i].id > 0) { + asc_window_sync(&asc_context.windows[i]); + } + } + + // compute frame time + static Uint32 ticks; + Uint32 ticks_elapsed = SDL_GetTicks() - ticks; + ticks = SDL_GetTicks(); + asc_context.elapsed_millis = ticks_elapsed; + asc_context.elapsed = (float) ticks_elapsed / 1000.0f; + + return !asc_test_flag(asc_context.flags, ASC_FLAG_QUIT); +}
--- a/src/error.c Wed Nov 08 23:17:07 2023 +0100 +++ b/src/error.c Wed Nov 15 22:51:40 2023 +0100 @@ -30,6 +30,7 @@ #include "ascension/utils.h" #include <cx/buffer.h> +#include <GL/gl.h> void asc_error_cchar(char const* text) { asc_error_cxstr(cx_str(text)); @@ -69,3 +70,37 @@ cxBufferClear(&asc_context.error_buffer); asc_clear_flag(&asc_context.flags, ASC_FLAG_HAS_ERROR); } + +void asc_error_gl(unsigned code, cxstring message) { + const char *glerr; + switch(code) { + case GL_NO_ERROR: + return; + case GL_INVALID_ENUM: + glerr = "invalid enum"; + break; + case GL_INVALID_VALUE: + glerr = "invalid value"; + break; + case GL_INVALID_OPERATION: + glerr = "invalid operation"; + break; + case GL_INVALID_FRAMEBUFFER_OPERATION: + glerr = "invalid framebuffer operation"; + break; + case GL_OUT_OF_MEMORY: + glerr = "out of memory"; + break; + case GL_STACK_UNDERFLOW: + glerr = "stack underflow"; + break; + case GL_STACK_OVERFLOW: + glerr = "stack overflow"; + break; + default: + glerr = "unknown GL error"; + } + cxmutstr msg = cx_strcat(3, message, CX_STR(" GL Error: "), cx_str(glerr)); + asc_error_cxstr(cx_strcast(msg)); + cx_strfree(&msg); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/primitives.c Wed Nov 15 22:51:40 2023 +0100 @@ -0,0 +1,93 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * Copyright 2023 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ascension/primitives.h" +#include "ascension/error.h" +#include "ascension/context.h" + +#include <string.h> +#include <GL/glew.h> + +static void asc_primitives_init_plane(AscMesh *mesh) { + asc_dprintf("Create primitive plane in VBO %u and VAO %u", mesh->vbo, mesh->vao); + mesh->vertices = 4; + float data[8] = { + 0.0f, 0.0f, // bottom left + 0.0f, 1.0f, // top left + 1.0f, 0.0f, // bottom right + 1.0f, 1.0f // top right + }; + glBindBuffer(GL_ARRAY_BUFFER, mesh->vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW); + glBindVertexArray(mesh->vao); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL); + glEnableVertexAttribArray(0); +} + +bool asc_primitives_init(AscPrimitives *primitives) { + asc_dprintf("Create primitives for the GL context of active window."); + // TODO: more primitives + + GLuint buffers[1]; + GLuint arrays[1]; + glGenBuffers(1, buffers); + glGenVertexArrays(1, arrays); + + AscMesh *plane = &(primitives->plane); + plane->vbo = buffers[0]; + plane->vao = arrays[0]; + asc_primitives_init_plane(plane); + + GLenum error = glGetError(); + if (error == GL_NO_ERROR) { + return true; + } else { + asc_error_gl(error, CX_STR("Initialization of primitive meshes failed.")); + return false; + } +} + +void asc_primitives_destroy(AscPrimitives *primitives) { + asc_dprintf("Destroy primitives in GL context of active window."); + + GLuint buffers[1]; + GLuint arrays[1]; + + buffers[0] = primitives->plane.vbo; + arrays[0] = primitives->plane.vao; + + glDeleteBuffers(1, buffers); + glDeleteVertexArrays(1, arrays); + + memset(primitives, 0, sizeof(AscPrimitives)); +} + +void asc_primitives_draw_plane(void) { + AscMesh const *mesh = &(asc_context.active_window->primitives.plane); + glBindVertexArray(mesh->vao); + glDrawArrays(GL_TRIANGLE_STRIP, 0, mesh->vertices); +} \ No newline at end of file
--- a/src/shader.c Wed Nov 08 23:17:07 2023 +0100 +++ b/src/shader.c Wed Nov 15 22:51:40 2023 +0100 @@ -30,8 +30,9 @@ #include "ascension/error.h" #include <GL/glew.h> +#include <string.h> -AscShaderProgram ASC_SHADER_FONT; +AscShaderFont ASC_SHADER_FONT; AscShader asc_shader_compile(unsigned int type, @@ -93,7 +94,12 @@ glDetachShader(id, fragment.id); if (success) { asc_dprintf("Shader Program %u linked (vtf: %u, frag: %u)", id, vertex.id, fragment.id); - return (AscShaderProgram) {id}; + AscShaderProgram prog; + prog.id = id; + prog.model = glGetUniformLocation(id, "model"); + prog.view = glGetUniformLocation(id, "view"); + prog.projection = glGetUniformLocation(id, "projection"); + return prog; } else { char *log = malloc(1024); glGetProgramInfoLog(id, 1024, NULL, log); @@ -131,9 +137,10 @@ } void asc_shader_initialize_predefined(void) { - ASC_SHADER_FONT = asc_shader_compile_link_discard("shader/font_vtx.glsl", "shader/font_frag.glsl"); + ASC_SHADER_FONT.base = asc_shader_compile_link_discard("shader/font_vtx.glsl", "shader/font_frag.glsl"); + ASC_SHADER_FONT.surface = glGetUniformLocation(ASC_SHADER_FONT.base.id, "surface"); } void asc_shader_destroy_predefined(void) { - asc_shader_program_destroy(ASC_SHADER_FONT); + asc_shader_program_destroy(ASC_SHADER_FONT.base); } \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/text.c Wed Nov 15 22:51:40 2023 +0100 @@ -0,0 +1,129 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * Copyright 2023 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ascension/text.h" +#include "ascension/context.h" +#include "ascension/error.h" +#include "ascension/shader.h" + +#include <GL/glew.h> + +void asc_text_draw_lb( + AscTextNode *node, + asc_vec2i position, + unsigned max_width, + cxmutstr text) { + + // Generate new texture, if required + if (node->tex_id == 0) { + glGenTextures(1, &node->tex_id); + glBindTexture(GL_TEXTURE_RECTANGLE, node->tex_id); + glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + asc_dprintf("Generated new texture for text node: %u", node->tex_id); + } + + // Render text onto a surface + SDL_Surface *surface = TTF_RenderText_Blended_Wrapped( + asc_context.active_font->ptr, + text.ptr, + (SDL_Color) { + .r = asc_context.ink.red, + .g = asc_context.ink.green, + .b = asc_context.ink.blue, + .a = asc_context.ink.alpha + }, + max_width + ); + if (surface == NULL) { + asc_error(SDL_GetError()); + return; + } + + // Store basic node information + node->position = position; + node->dimension.width = surface->w; + node->dimension.height = surface->h; + + // Transfer Image Data + // TODO: move the image data transfer to a separate function - we will need it more often + glBindTexture(GL_TEXTURE_RECTANGLE, node->tex_id); + glPixelStorei(GL_UNPACK_ROW_LENGTH, surface->pitch / surface->format->BytesPerPixel); + glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA, + surface->w, surface->h, + 0, GL_BGRA, GL_UNSIGNED_BYTE, surface->pixels); + + // Free the surface + SDL_FreeSurface(surface); + + // Redraw the node + asc_text_redraw(node); +} + +void asc_text_draw( + AscTextNode *node, + asc_vec2i position, + cxmutstr text) { + unsigned max_width = asc_context.active_window->dimensions.width - position.width; + asc_text_draw_lb(node, position, max_width, text); +} + +void asc_text_redraw(AscTextNode *node) { + if (node->tex_id == 0) { + asc_error("Tried to redraw text node after destruction"); + return; + } + + glUseProgram(ASC_SHADER_FONT.base.id); + + // Upload projection + // TODO: when we group UI draw calls, we don't need this + glUniformMatrix4fv(ASC_SHADER_FONT.base.projection, 1, GL_FALSE, asc_context.active_window->projection); + + // Upload model matrix + asc_mat4f model = {0}; + model[asc_mat4_index(0, 0)] = node->dimension.width; + model[asc_mat4_index(1, 1)] = node->dimension.height; + model[asc_mat4_index(3, 0)] = node->position.x; + model[asc_mat4_index(3, 1)] = node->position.y; + model[asc_mat4_index(3, 3)] = 1; + glUniformMatrix4fv(ASC_SHADER_FONT.base.model, 1, GL_FALSE, model); + + // Upload surface + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_RECTANGLE, node->tex_id); + glUniform1i(ASC_SHADER_FONT.surface, 0); + + // Draw mesh + asc_primitives_draw_plane(); +} + +void asc_text_destroy(AscTextNode *node) { + asc_dprintf("Release text node texture: %u", node->tex_id); + glDeleteTextures(1, &node->tex_id); + node->tex_id = 0; +} \ No newline at end of file
--- a/src/window.c Wed Nov 08 23:17:07 2023 +0100 +++ b/src/window.c Wed Nov 15 22:51:40 2023 +0100 @@ -45,61 +45,11 @@ if (type == GL_DEBUG_TYPE_ERROR) { asc_error(buf.ptr); } else { - asc_dprintf("GL debug: %*.s", (int)buf.length, buf.ptr); + asc_dprintf("GL debug: %.*s", (int)buf.length, buf.ptr); } cx_strfree(&buf); } - -static void asc_event_window_resized(Uint32 id, Sint32 width, Sint32 height) { - for (unsigned int i = 0 ; i < ASC_MAX_WINDOWS ; i++) { - if (asc_context.windows[i].id == id) { - asc_context.windows[i].dimensions.width = width; - asc_context.windows[i].dimensions.height = height; - asc_mat4f_ortho(asc_context.windows[i].projection, 0, (float) width, (float) height, 0); - return; - } - } -} - -bool asc_loop_next(void) { - // dispatch SDL events - SDL_Event event; - while (SDL_PollEvent(&event)) { - switch (event.type) { - case SDL_QUIT: - asc_set_flag(&asc_context.flags, ASC_FLAG_QUIT); - break; - case SDL_WINDOWEVENT: { - if (event.window.type == SDL_WINDOWEVENT_RESIZED) - asc_event_window_resized( - event.window.windowID, - event.window.data1, - event.window.data2 - ); - break; - } - case SDL_KEYDOWN: - // TODO: remove this code and implement key press map instead - if (event.key.keysym.sym == SDLK_ESCAPE) - return false; - break; - case SDL_KEYUP: - // TODO: implement key press map - break; - } - } - - // sync the windows - for (unsigned int i = 0 ; i < ASC_MAX_WINDOWS ; i++) { - if (asc_context.windows[i].id > 0) { - asc_window_sync(&asc_context.windows[i]); - } - } - - return !asc_test_flag(asc_context.flags, ASC_FLAG_QUIT); -} - void asc_window_settings_init_defaults(AscWindowSettings* settings) { settings->depth_size = 24; settings->vsync = 1; @@ -170,8 +120,14 @@ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(asc_gl_debug_callback, NULL); + asc_dprintf("Window %u initialized", window->id); - return window; + if (asc_primitives_init(&window->primitives)) { + asc_context.active_window = window; + return window; + } else { + asc_dprintf("!!! Creating primitives for window %u failed !!!", window->id); + } } else { asc_error(glewGetErrorString(err)); } @@ -191,6 +147,15 @@ // safeguard if (window->id == 0) return; + // this window cannot be active anymore + if (asc_context.active_window == window) { + asc_context.active_window = NULL; + } + + // release context related data (we have to make the GL context current for this) + SDL_GL_MakeCurrent(window->window, window->glctx); + asc_primitives_destroy(&window->primitives); + // destroy the GL context and the window if (window->glctx != NULL) { SDL_GL_DeleteContext(window->glctx); @@ -199,15 +164,32 @@ SDL_DestroyWindow(window->window); } + // if another window was active, make the other context current again + if (asc_context.active_window != NULL) { + AscWindow const *aw = asc_context.active_window; + SDL_GL_MakeCurrent(aw->window, aw->glctx); + } + // clean the data asc_dprintf("Window %u and its OpenGL context destroyed.", window->id); memset(window, 0, sizeof(AscWindow)); } void asc_window_sync(AscWindow const* window) { - SDL_GL_MakeCurrent(window->window, window->glctx); + AscWindow const *active_window = asc_context.active_window; + if (window != active_window) { + asc_window_activate(window); + } SDL_GL_SwapWindow(window->window); glViewport(0, 0, window->dimensions.width, window->dimensions.height); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + if (window != active_window) { + asc_window_activate(active_window); + } } + +void asc_window_activate(AscWindow const *window) { + SDL_GL_MakeCurrent(window->window, window->glctx); + asc_context.active_window = window; +}
--- a/test/Makefile Wed Nov 08 23:17:07 2023 +0100 +++ b/test/Makefile Wed Nov 15 22:51:40 2023 +0100 @@ -40,8 +40,10 @@ $(BUILD_DIR)/sandbox.o: sandbox.c ../src/ascension/ascension.h \ ../src/ascension/error.h ../src/ascension/context.h \ - ../src/ascension/window.h ../src/ascension/datatypes.h \ - ../src/ascension/font.h ../src/ascension/shader.h + ../src/ascension/datatypes.h ../src/ascension/window.h \ + ../src/ascension/primitives.h ../src/ascension/mesh.h \ + ../src/ascension/font.h ../src/ascension/shader.h \ + ../src/ascension/text.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $<
--- a/test/sandbox.c Wed Nov 08 23:17:07 2023 +0100 +++ b/test/sandbox.c Wed Nov 15 22:51:40 2023 +0100 @@ -26,6 +26,7 @@ */ #include <ascension/ascension.h> +#include <cx/printf.h> static bool show_message_box_on_error(SDL_Window* window) { if (asc_has_error()) { @@ -50,13 +51,34 @@ AscWindow *window = asc_window_initialize(0, &settings); asc_shader_initialize_predefined(); + + AscTextNode fps_counter = {0}; + unsigned last_fps = 0; + while (asc_loop_next()) { // quit application on any error if (show_message_box_on_error(window->window)) break; + // fps counter + if (asc_context.elapsed_millis > 0) { + unsigned fps = 1000u / asc_context.elapsed_millis; + if (fps != last_fps) { + last_fps = fps; + asc_set_font(asc_font(ASC_FONT_REGULAR, 24)); + asc_ink_rgb(255, 0, 0); + cxmutstr fpstext = cx_asprintf("%u FPS", fps); + asc_text_draw(&fps_counter, (asc_vec2i) {50, 50}, fpstext); + cx_strfree(&fpstext); + } else { + asc_text_redraw(&fps_counter); + } + } } + // TODO: maybe nodes should also be "garbage collected" + asc_text_destroy(&fps_counter); + asc_context_destroy(); return 0; }