Tue, 23 Jan 2024 21:34:12 +0100
bring first scene graph to live
src/Makefile | file | annotate | diff | comparison | revisions | |
src/ascension/ascension.h | file | annotate | diff | comparison | revisions | |
src/ascension/scene.h | file | annotate | diff | comparison | revisions | |
src/ascension/text.h | file | annotate | diff | comparison | revisions | |
src/ascension/window.h | file | annotate | diff | comparison | revisions | |
src/scene.c | file | annotate | diff | comparison | revisions | |
src/text.c | file | annotate | diff | comparison | revisions | |
src/window.c | file | annotate | diff | comparison | revisions | |
test/Makefile | file | annotate | diff | comparison | revisions | |
test/sandbox.c | file | annotate | diff | comparison | revisions |
1.1 --- a/src/Makefile Sun Jan 21 14:01:27 2024 +0100 1.2 +++ b/src/Makefile Tue Jan 23 21:34:12 2024 +0100 1.3 @@ -43,14 +43,14 @@ 1.4 1.5 $(BUILD_DIR)/context.o: context.c ascension/context.h \ 1.6 ascension/datatypes.h ascension/window.h ascension/primitives.h \ 1.7 - ascension/mesh.h ascension/font.h ascension/error.h ascension/utils.h \ 1.8 - ascension/shader.h 1.9 + ascension/mesh.h ascension/scene.h ascension/font.h ascension/error.h \ 1.10 + ascension/utils.h ascension/shader.h 1.11 @echo "Compiling $<" 1.12 $(CC) -o $@ $(CFLAGS) -c $< 1.13 1.14 $(BUILD_DIR)/error.o: error.c ascension/context.h ascension/datatypes.h \ 1.15 ascension/window.h ascension/primitives.h ascension/mesh.h \ 1.16 - ascension/font.h ascension/error.h ascension/utils.h 1.17 + ascension/scene.h ascension/font.h ascension/error.h ascension/utils.h 1.18 @echo "Compiling $<" 1.19 $(CC) -o $@ $(CFLAGS) -c $< 1.20 1.21 @@ -60,14 +60,14 @@ 1.22 1.23 $(BUILD_DIR)/font.o: font.c ascension/font.h ascension/context.h \ 1.24 ascension/datatypes.h ascension/window.h ascension/primitives.h \ 1.25 - ascension/mesh.h ascension/font.h ascension/error.h 1.26 + ascension/mesh.h ascension/scene.h ascension/font.h ascension/error.h 1.27 @echo "Compiling $<" 1.28 $(CC) -o $@ $(CFLAGS) -c $< 1.29 1.30 $(BUILD_DIR)/primitives.o: primitives.c ascension/primitives.h \ 1.31 ascension/mesh.h ascension/error.h ascension/context.h \ 1.32 ascension/datatypes.h ascension/window.h ascension/primitives.h \ 1.33 - ascension/font.h 1.34 + ascension/scene.h ascension/font.h 1.35 @echo "Compiling $<" 1.36 $(CC) -o $@ $(CFLAGS) -c $< 1.37 1.38 @@ -81,15 +81,16 @@ 1.39 $(CC) -o $@ $(CFLAGS) -c $< 1.40 1.41 $(BUILD_DIR)/text.o: text.c ascension/text.h ascension/font.h \ 1.42 - ascension/datatypes.h ascension/context.h ascension/window.h \ 1.43 - ascension/primitives.h ascension/mesh.h ascension/error.h \ 1.44 - ascension/shader.h 1.45 + ascension/datatypes.h ascension/scene.h ascension/context.h \ 1.46 + ascension/window.h ascension/primitives.h ascension/mesh.h \ 1.47 + ascension/error.h ascension/shader.h 1.48 @echo "Compiling $<" 1.49 $(CC) -o $@ $(CFLAGS) -c $< 1.50 1.51 $(BUILD_DIR)/window.o: window.c ascension/window.h ascension/datatypes.h \ 1.52 - ascension/primitives.h ascension/mesh.h ascension/context.h \ 1.53 - ascension/window.h ascension/font.h ascension/error.h ascension/utils.h 1.54 + ascension/primitives.h ascension/mesh.h ascension/scene.h \ 1.55 + ascension/context.h ascension/window.h ascension/font.h \ 1.56 + ascension/error.h ascension/utils.h 1.57 @echo "Compiling $<" 1.58 $(CC) -o $@ $(CFLAGS) -c $< 1.59
2.1 --- a/src/ascension/ascension.h Sun Jan 21 14:01:27 2024 +0100 2.2 +++ b/src/ascension/ascension.h Tue Jan 23 21:34:12 2024 +0100 2.3 @@ -32,6 +32,7 @@ 2.4 #include "context.h" 2.5 #include "shader.h" 2.6 #include "text.h" 2.7 +#include "scene.h" 2.8 2.9 #endif /* ASCENSION_H */ 2.10
3.1 --- a/src/ascension/scene.h Sun Jan 21 14:01:27 2024 +0100 3.2 +++ b/src/ascension/scene.h Tue Jan 23 21:34:12 2024 +0100 3.3 @@ -28,29 +28,60 @@ 3.4 #ifndef ASCENSION_SCENE_H 3.5 #define ASCENSION_SCENE_H 3.6 3.7 -typedef struct AscSceneNode { 3.8 - struct AscSceneNode *parent; 3.9 - struct AscSceneNode *prev; 3.10 - struct AscSceneNode *next; 3.11 - struct AscSceneNode *children; 3.12 - // TODO: add node contents 3.13 -} AscSceneNode; 3.14 +typedef struct AscSceneNode AscSceneNode; 3.15 + 3.16 +typedef void(*asc_scene_free_func)(AscSceneNode*); 3.17 +typedef void(*asc_scene_draw_func)(AscSceneNode const*); 3.18 + 3.19 +struct AscSceneNode { 3.20 + AscSceneNode *parent; 3.21 + AscSceneNode *prev; 3.22 + AscSceneNode *next; 3.23 + AscSceneNode *children; 3.24 + asc_scene_free_func free_func; 3.25 + asc_scene_draw_func draw_func; 3.26 + // TODO: add more node contents 3.27 +}; 3.28 + 3.29 +/** 3.30 + * Place this as first member of a structure that shall be usable as a scene node. 3.31 + */ 3.32 +#define extend_asc_scene_node AscSceneNode node 3.33 + 3.34 +#define asc_node(obj) (&((obj)->node)) 3.35 3.36 typedef struct AscScene { 3.37 AscSceneNode *root; 3.38 // TODO: add render groups for batching 3.39 } AscScene; 3.40 3.41 +__attribute__((__nonnull__)) 3.42 void asc_scene_init(AscScene *scene); 3.43 3.44 +__attribute__((__nonnull__)) 3.45 void asc_scene_destroy(AscScene *scene); 3.46 3.47 -AscSceneNode *asc_scene_node_create(AscSceneNode *parent); 3.48 +__attribute__((__nonnull__)) 3.49 +void asc_scene_draw(AscScene const *scene); 3.50 + 3.51 +__attribute__((__nonnull__)) 3.52 +void asc_scene_add(AscScene *scene, AscSceneNode *node); 3.53 + 3.54 +/** 3.55 + * Creates an empty node that may serve as a container for other nodes. 3.56 + * 3.57 + * The free_func of this node will be a simple free(). 3.58 + * 3.59 + * @return the new node 3.60 + */ 3.61 +AscSceneNode *asc_scene_node_empty(void); 3.62 3.63 void asc_scene_node_free(AscSceneNode *node); 3.64 3.65 -void asc_scene_node_link(AscSceneNode *node, AscSceneNode *parent); 3.66 +__attribute__((__nonnull__)) 3.67 +void asc_scene_node_link(AscSceneNode * restrict parent, AscSceneNode * restrict node); 3.68 3.69 +__attribute__((__nonnull__)) 3.70 void asc_scene_node_unlink(AscSceneNode *node); 3.71 3.72 #endif // ASCENSION_SCENE_H
4.1 --- a/src/ascension/text.h Sun Jan 21 14:01:27 2024 +0100 4.2 +++ b/src/ascension/text.h Tue Jan 23 21:34:12 2024 +0100 4.3 @@ -30,9 +30,10 @@ 4.4 4.5 #include "font.h" 4.6 #include "datatypes.h" 4.7 -#include <cx/buffer.h> 4.8 +#include "scene.h" 4.9 4.10 typedef struct AscText { 4.11 + extend_asc_scene_node; 4.12 char *text; 4.13 AscFont const *font; 4.14 asc_vec2i position; 4.15 @@ -74,13 +75,6 @@ 4.16 void asc_text_update(AscText *node); 4.17 4.18 /** 4.19 - * Draws the text node in the current frame. 4.20 - * 4.21 - * @param node the text node 4.22 - */ 4.23 -void asc_text_draw(AscText const *node); 4.24 - 4.25 -/** 4.26 * Releases all the memory of this node. 4.27 * 4.28 * @param node the text node
5.1 --- a/src/ascension/window.h Sun Jan 21 14:01:27 2024 +0100 5.2 +++ b/src/ascension/window.h Tue Jan 23 21:34:12 2024 +0100 5.3 @@ -32,6 +32,7 @@ 5.4 5.5 #include "datatypes.h" 5.6 #include "primitives.h" 5.7 +#include "scene.h" 5.8 5.9 #ifndef ASC_MAX_WINDOWS 5.10 /** The maximum number of windows that can exist simultaneously. */ 5.11 @@ -55,6 +56,7 @@ 5.12 AscPrimitives primitives; 5.13 asc_vec2i dimensions; 5.14 asc_mat4f projection; 5.15 + AscScene ui; 5.16 } AscWindow; 5.17 5.18 /**
6.1 --- a/src/scene.c Sun Jan 21 14:01:27 2024 +0100 6.2 +++ b/src/scene.c Tue Jan 23 21:34:12 2024 +0100 6.3 @@ -28,31 +28,63 @@ 6.4 #include "ascension/scene.h" 6.5 #include "ascension/error.h" 6.6 6.7 +#include <cx/tree.h> 6.8 + 6.9 #include <assert.h> 6.10 6.11 +#define asc_scene_node_layout \ 6.12 + offsetof(AscSceneNode, parent), offsetof(AscSceneNode, children), \ 6.13 + offsetof(AscSceneNode, prev), offsetof(AscSceneNode, next) 6.14 + 6.15 void asc_scene_init(AscScene *scene) { 6.16 if (scene->root != NULL) { 6.17 asc_error("Scene is already initialized."); 6.18 return; 6.19 } 6.20 - scene->root = asc_scene_node_create(NULL); 6.21 + scene->root = asc_scene_node_empty(); 6.22 } 6.23 6.24 void asc_scene_destroy(AscScene *scene) { 6.25 asc_scene_node_free(scene->root); 6.26 } 6.27 6.28 -AscSceneNode *asc_scene_node_create(AscSceneNode *parent) { 6.29 +void asc_scene_add(AscScene *scene, AscSceneNode *node) { 6.30 + asc_scene_node_link(scene->root, node); 6.31 +} 6.32 + 6.33 +static void asc_scene_draw_node(AscSceneNode *node) { 6.34 + if (node->draw_func != NULL) { 6.35 + node->draw_func(node); 6.36 + } 6.37 + if (node->children != NULL) { 6.38 + asc_scene_draw_node(node->children); 6.39 + } 6.40 + if (node->next != NULL) { 6.41 + asc_scene_draw_node(node->next); 6.42 + } 6.43 +} 6.44 + 6.45 +void asc_scene_draw(AscScene const *scene) { 6.46 + // TODO: replace with UCX tree visitor 6.47 + // TODO: don't visit the tree, visit the render groups 6.48 + // TODO: avoid recursion 6.49 + asc_scene_draw_node(scene->root); 6.50 +} 6.51 + 6.52 +AscSceneNode *asc_scene_node_empty(void) { 6.53 // TODO: check if this can remain a calloc or if it's too expensive 6.54 AscSceneNode *node = calloc(1, sizeof(AscSceneNode)); 6.55 assert(node != NULL); 6.56 - asc_scene_node_link(node, parent); 6.57 + node->free_func = (asc_scene_free_func) free; 6.58 return node; 6.59 } 6.60 6.61 void asc_scene_node_free(AscSceneNode *node) { 6.62 if (node == NULL) return; 6.63 6.64 + // TODO: replace with UCX tree visitor 6.65 + // TODO: avoid recursion 6.66 + 6.67 // free the children recursively 6.68 while (node->children != NULL) { 6.69 asc_scene_node_free(node->children); 6.70 @@ -62,36 +94,15 @@ 6.71 asc_scene_node_unlink(node); 6.72 6.73 // free the node 6.74 - free(node); 6.75 -} 6.76 - 6.77 -void asc_scene_node_link( 6.78 - AscSceneNode *node, 6.79 - AscSceneNode *parent 6.80 -) { 6.81 - if (node->parent == parent) return; 6.82 - if (node->parent != NULL || parent == NULL) asc_scene_node_unlink(node); 6.83 - if (parent != NULL) { 6.84 - if (parent->children == NULL) { 6.85 - parent->children = node; 6.86 - } else { 6.87 - parent->children->prev = node; 6.88 - node->next = parent->children; 6.89 - } 6.90 - node->parent = parent; 6.91 + if (node->free_func != NULL) { 6.92 + node->free_func(node); 6.93 } 6.94 } 6.95 6.96 +void asc_scene_node_link(AscSceneNode * restrict parent, AscSceneNode * restrict node) { 6.97 + cx_tree_link(parent, node, asc_scene_node_layout); 6.98 +} 6.99 + 6.100 void asc_scene_node_unlink(AscSceneNode *node) { 6.101 - if (node->parent == NULL) return; 6.102 - AscSceneNode *left = node->prev; 6.103 - AscSceneNode *right = node->next; 6.104 - assert(left == NULL || node->parent->children != node); 6.105 - if (left != NULL) { 6.106 - left->next = right; 6.107 - } else { 6.108 - node->parent->children = right; 6.109 - } 6.110 - if (right != NULL) right->prev = left; 6.111 - node->parent = node->prev = node->next = NULL; 6.112 + cx_tree_unlink(node, asc_scene_node_layout); 6.113 } 6.114 \ No newline at end of file
7.1 --- a/src/text.c Sun Jan 21 14:01:27 2024 +0100 7.2 +++ b/src/text.c Tue Jan 23 21:34:12 2024 +0100 7.3 @@ -32,6 +32,36 @@ 7.4 7.5 #include <GL/glew.h> 7.6 7.7 +static void asc_text_draw(AscText const *node) { 7.8 + if (node->color.alpha == 0 || node->hidden || node->internal.tex_id == 0) { 7.9 + return; 7.10 + } 7.11 + 7.12 + glUseProgram(ASC_SHADER_FONT.base.id); 7.13 + 7.14 + // Upload projection 7.15 + // TODO: when we group UI draw calls, we don't need this 7.16 + glUniformMatrix4fv(ASC_SHADER_FONT.base.projection, 1, 7.17 + GL_FALSE, asc_context.active_window->projection); 7.18 + 7.19 + // Upload model matrix 7.20 + asc_mat4f model = {0}; 7.21 + model[asc_mat4_index(0, 0)] = node->internal.dimension.width; 7.22 + model[asc_mat4_index(1, 1)] = node->internal.dimension.height; 7.23 + model[asc_mat4_index(3, 0)] = node->position.x; 7.24 + model[asc_mat4_index(3, 1)] = node->position.y; 7.25 + model[asc_mat4_index(3, 3)] = 1; 7.26 + glUniformMatrix4fv(ASC_SHADER_FONT.base.model, 1, GL_FALSE, model); 7.27 + 7.28 + // Upload surface 7.29 + glActiveTexture(GL_TEXTURE0); 7.30 + glBindTexture(GL_TEXTURE_RECTANGLE, node->internal.tex_id); 7.31 + glUniform1i(ASC_SHADER_FONT.surface, 0); 7.32 + 7.33 + // Draw mesh 7.34 + asc_primitives_draw_plane(); 7.35 +} 7.36 + 7.37 AscText *asc_text(int x, int y, char const *text) { 7.38 AscText *node = calloc(1, sizeof(AscText)); 7.39 if (node == NULL) { 7.40 @@ -39,6 +69,9 @@ 7.41 return NULL; 7.42 } 7.43 7.44 + node->node.free_func = (asc_scene_free_func) asc_text_free; 7.45 + node->node.draw_func = (asc_scene_draw_func) asc_text_draw; 7.46 + 7.47 node->position = (asc_vec2i) {x, y}; 7.48 node->font = asc_context.active_font; 7.49 node->color = asc_context.ink; 7.50 @@ -93,41 +126,6 @@ 7.51 SDL_FreeSurface(surface); 7.52 } 7.53 7.54 -void asc_text_draw(AscText const *node) { 7.55 - if (node->color.alpha == 0 || node->hidden) { 7.56 - return; 7.57 - } 7.58 - 7.59 - if (node->internal.tex_id == 0) { 7.60 - asc_error("Tried to redraw text node after destruction"); 7.61 - return; 7.62 - } 7.63 - 7.64 - glUseProgram(ASC_SHADER_FONT.base.id); 7.65 - 7.66 - // Upload projection 7.67 - // TODO: when we group UI draw calls, we don't need this 7.68 - glUniformMatrix4fv(ASC_SHADER_FONT.base.projection, 1, 7.69 - GL_FALSE, asc_context.active_window->projection); 7.70 - 7.71 - // Upload model matrix 7.72 - asc_mat4f model = {0}; 7.73 - model[asc_mat4_index(0, 0)] = node->internal.dimension.width; 7.74 - model[asc_mat4_index(1, 1)] = node->internal.dimension.height; 7.75 - model[asc_mat4_index(3, 0)] = node->position.x; 7.76 - model[asc_mat4_index(3, 1)] = node->position.y; 7.77 - model[asc_mat4_index(3, 3)] = 1; 7.78 - glUniformMatrix4fv(ASC_SHADER_FONT.base.model, 1, GL_FALSE, model); 7.79 - 7.80 - // Upload surface 7.81 - glActiveTexture(GL_TEXTURE0); 7.82 - glBindTexture(GL_TEXTURE_RECTANGLE, node->internal.tex_id); 7.83 - glUniform1i(ASC_SHADER_FONT.surface, 0); 7.84 - 7.85 - // Draw mesh 7.86 - asc_primitives_draw_plane(); 7.87 -} 7.88 - 7.89 void asc_text_free(AscText *node) { 7.90 asc_dprintf("Release text node texture: %u", node->internal.tex_id); 7.91 glDeleteTextures(1, &node->internal.tex_id);
8.1 --- a/src/window.c Sun Jan 21 14:01:27 2024 +0100 8.2 +++ b/src/window.c Tue Jan 23 21:34:12 2024 +0100 8.3 @@ -61,6 +61,10 @@ 8.4 settings->title = "Ascended Window"; 8.5 } 8.6 8.7 +static void asc_window_init_scenes(AscWindow *window) { 8.8 + asc_scene_init(&window->ui); 8.9 +} 8.10 + 8.11 AscWindow *asc_window_initialize(unsigned int index, AscWindowSettings const *settings) { 8.12 if (index >= ASC_MAX_WINDOWS) { 8.13 asc_error("Maximum number of windows exceeded."); 8.14 @@ -123,6 +127,7 @@ 8.15 8.16 asc_dprintf("Window %u initialized", window->id); 8.17 if (asc_primitives_init(&window->primitives)) { 8.18 + asc_window_init_scenes(window); 8.19 asc_context.active_window = window; 8.20 return window; 8.21 } else { 8.22 @@ -152,6 +157,9 @@ 8.23 asc_context.active_window = NULL; 8.24 } 8.25 8.26 + // destroy all scenes 8.27 + asc_scene_destroy(&window->ui); 8.28 + 8.29 // release context related data (we have to make the GL context current for this) 8.30 SDL_GL_MakeCurrent(window->window, window->glctx); 8.31 asc_primitives_destroy(&window->primitives); 8.32 @@ -180,10 +188,18 @@ 8.33 if (window != active_window) { 8.34 asc_window_activate(window); 8.35 } 8.36 - SDL_GL_SwapWindow(window->window); 8.37 + 8.38 + // Clear viewport for new frame 8.39 glViewport(0, 0, window->dimensions.width, window->dimensions.height); 8.40 glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 8.41 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 8.42 + 8.43 + // Draw the UI 8.44 + asc_scene_draw(&window->ui); 8.45 + 8.46 + // Swap Buffers 8.47 + SDL_GL_SwapWindow(window->window); 8.48 + 8.49 if (window != active_window) { 8.50 asc_window_activate(active_window); 8.51 }
9.1 --- a/test/Makefile Sun Jan 21 14:01:27 2024 +0100 9.2 +++ b/test/Makefile Tue Jan 23 21:34:12 2024 +0100 9.3 @@ -45,8 +45,8 @@ 9.4 ../src/ascension/error.h ../src/ascension/context.h \ 9.5 ../src/ascension/datatypes.h ../src/ascension/window.h \ 9.6 ../src/ascension/primitives.h ../src/ascension/mesh.h \ 9.7 - ../src/ascension/font.h ../src/ascension/shader.h \ 9.8 - ../src/ascension/text.h 9.9 + ../src/ascension/scene.h ../src/ascension/font.h \ 9.10 + ../src/ascension/shader.h ../src/ascension/text.h 9.11 @echo "Compiling $<" 9.12 $(CC) -o $@ $(CFLAGS) -c $< 9.13
10.1 --- a/test/sandbox.c Sun Jan 21 14:01:27 2024 +0100 10.2 +++ b/test/sandbox.c Tue Jan 23 21:34:12 2024 +0100 10.3 @@ -52,17 +52,18 @@ 10.4 AscWindow *window = asc_window_initialize(0, &settings); 10.5 asc_shader_initialize_predefined(); 10.6 10.7 + // create fps counter and add it to the UI 10.8 asc_set_font(asc_font(ASC_FONT_REGULAR, 24)); 10.9 asc_ink_rgb(255, 0, 0); 10.10 - AscText *fps_counter = asc_text(50, 50, "9999 FPS"); 10.11 + AscText *fps_counter = asc_text(50, 50, "XXXXX FPS"); 10.12 unsigned last_fps = 0; 10.13 + asc_scene_add(&window->ui, asc_node(fps_counter)); 10.14 10.15 - while (asc_loop_next()) { 10.16 + do { 10.17 // quit application on any error 10.18 if (show_message_box_on_error(window->window)) break; 10.19 10.20 - 10.21 - // fps counter 10.22 + // update fps counter 10.23 if (asc_context.elapsed_millis > 0) { 10.24 unsigned fps = 1000u / asc_context.elapsed_millis; 10.25 if (fps != last_fps) { 10.26 @@ -71,11 +72,7 @@ 10.27 asc_text_update(fps_counter); 10.28 } 10.29 } 10.30 - asc_text_draw(fps_counter); 10.31 - } 10.32 - 10.33 - // TODO: maybe nodes should also be "garbage collected" 10.34 - asc_text_free(fps_counter); 10.35 + } while (asc_loop_next()); 10.36 10.37 asc_context_destroy(); 10.38 return 0;