bring first scene graph to live

Tue, 23 Jan 2024 21:34:12 +0100

author
Mike Becker <universe@uap-core.de>
date
Tue, 23 Jan 2024 21:34:12 +0100
changeset 29
1d001eb694dc
parent 28
8acde7d27904
child 30
fceda550ebcb

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
--- a/src/Makefile	Sun Jan 21 14:01:27 2024 +0100
+++ b/src/Makefile	Tue Jan 23 21:34:12 2024 +0100
@@ -43,14 +43,14 @@
 
 $(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
+ ascension/mesh.h ascension/scene.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/datatypes.h \
  ascension/window.h ascension/primitives.h ascension/mesh.h \
- ascension/font.h ascension/error.h ascension/utils.h
+ ascension/scene.h ascension/font.h ascension/error.h ascension/utils.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
@@ -60,14 +60,14 @@
 
 $(BUILD_DIR)/font.o: font.c ascension/font.h ascension/context.h \
  ascension/datatypes.h ascension/window.h ascension/primitives.h \
- ascension/mesh.h ascension/font.h ascension/error.h
+ ascension/mesh.h ascension/scene.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
+ ascension/scene.h ascension/font.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
@@ -81,15 +81,16 @@
 	$(CC) -o $@ $(CFLAGS) -c $<
 
 $(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
+ ascension/datatypes.h ascension/scene.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
+ ascension/primitives.h ascension/mesh.h ascension/scene.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	Sun Jan 21 14:01:27 2024 +0100
+++ b/src/ascension/ascension.h	Tue Jan 23 21:34:12 2024 +0100
@@ -32,6 +32,7 @@
 #include "context.h"
 #include "shader.h"
 #include "text.h"
+#include "scene.h"
 
 #endif /* ASCENSION_H */
 
--- a/src/ascension/scene.h	Sun Jan 21 14:01:27 2024 +0100
+++ b/src/ascension/scene.h	Tue Jan 23 21:34:12 2024 +0100
@@ -28,29 +28,60 @@
 #ifndef ASCENSION_SCENE_H
 #define ASCENSION_SCENE_H
 
-typedef struct AscSceneNode {
-    struct AscSceneNode *parent;
-    struct AscSceneNode *prev;
-    struct AscSceneNode *next;
-    struct AscSceneNode *children;
-    // TODO: add node contents
-} AscSceneNode;
+typedef struct AscSceneNode AscSceneNode;
+
+typedef void(*asc_scene_free_func)(AscSceneNode*);
+typedef void(*asc_scene_draw_func)(AscSceneNode const*);
+
+struct AscSceneNode {
+    AscSceneNode *parent;
+    AscSceneNode *prev;
+    AscSceneNode *next;
+    AscSceneNode *children;
+    asc_scene_free_func free_func;
+    asc_scene_draw_func draw_func;
+    // TODO: add more node contents
+};
+
+/**
+ * Place this as first member of a structure that shall be usable as a scene node.
+ */
+#define extend_asc_scene_node AscSceneNode node
+
+#define asc_node(obj) (&((obj)->node))
 
 typedef struct AscScene {
     AscSceneNode *root;
     // TODO: add render groups for batching
 } AscScene;
 
+__attribute__((__nonnull__))
 void asc_scene_init(AscScene *scene);
 
+__attribute__((__nonnull__))
 void asc_scene_destroy(AscScene *scene);
 
-AscSceneNode *asc_scene_node_create(AscSceneNode *parent);
+__attribute__((__nonnull__))
+void asc_scene_draw(AscScene const *scene);
+
+__attribute__((__nonnull__))
+void asc_scene_add(AscScene *scene, AscSceneNode *node);
+
+/**
+ * Creates an empty node that may serve as a container for other nodes.
+ *
+ * The free_func of this node will be a simple free().
+ *
+ * @return the new node
+ */
+AscSceneNode *asc_scene_node_empty(void);
 
 void asc_scene_node_free(AscSceneNode *node);
 
-void asc_scene_node_link(AscSceneNode *node, AscSceneNode *parent);
+__attribute__((__nonnull__))
+void asc_scene_node_link(AscSceneNode * restrict parent, AscSceneNode * restrict node);
 
+__attribute__((__nonnull__))
 void asc_scene_node_unlink(AscSceneNode *node);
 
 #endif // ASCENSION_SCENE_H
--- a/src/ascension/text.h	Sun Jan 21 14:01:27 2024 +0100
+++ b/src/ascension/text.h	Tue Jan 23 21:34:12 2024 +0100
@@ -30,9 +30,10 @@
 
 #include "font.h"
 #include "datatypes.h"
-#include <cx/buffer.h>
+#include "scene.h"
 
 typedef struct AscText {
+    extend_asc_scene_node;
     char *text;
     AscFont const *font;
     asc_vec2i position;
@@ -74,13 +75,6 @@
 void asc_text_update(AscText *node);
 
 /**
- * Draws the text node in the current frame.
- *
- * @param node the text node
- */
-void asc_text_draw(AscText const *node);
-
-/**
  * Releases all the memory of this node.
  *
  * @param node the text node
--- a/src/ascension/window.h	Sun Jan 21 14:01:27 2024 +0100
+++ b/src/ascension/window.h	Tue Jan 23 21:34:12 2024 +0100
@@ -32,6 +32,7 @@
 
 #include "datatypes.h"
 #include "primitives.h"
+#include "scene.h"
 
 #ifndef ASC_MAX_WINDOWS
 /** The maximum number of windows that can exist simultaneously. */
@@ -55,6 +56,7 @@
     AscPrimitives primitives;
     asc_vec2i dimensions;
     asc_mat4f projection;
+    AscScene ui;
 } AscWindow;
 
 /**
--- a/src/scene.c	Sun Jan 21 14:01:27 2024 +0100
+++ b/src/scene.c	Tue Jan 23 21:34:12 2024 +0100
@@ -28,31 +28,63 @@
 #include "ascension/scene.h"
 #include "ascension/error.h"
 
+#include <cx/tree.h>
+
 #include <assert.h>
 
+#define asc_scene_node_layout \
+    offsetof(AscSceneNode, parent), offsetof(AscSceneNode, children), \
+    offsetof(AscSceneNode, prev), offsetof(AscSceneNode, next)
+
 void asc_scene_init(AscScene *scene) {
     if (scene->root != NULL) {
         asc_error("Scene is already initialized.");
         return;
     }
-    scene->root = asc_scene_node_create(NULL);
+    scene->root = asc_scene_node_empty();
 }
 
 void asc_scene_destroy(AscScene *scene) {
     asc_scene_node_free(scene->root);
 }
 
-AscSceneNode *asc_scene_node_create(AscSceneNode *parent) {
+void asc_scene_add(AscScene *scene, AscSceneNode *node) {
+    asc_scene_node_link(scene->root, node);
+}
+
+static void asc_scene_draw_node(AscSceneNode *node) {
+    if (node->draw_func != NULL) {
+        node->draw_func(node);
+    }
+    if (node->children != NULL) {
+        asc_scene_draw_node(node->children);
+    }
+    if (node->next != NULL) {
+        asc_scene_draw_node(node->next);
+    }
+}
+
+void asc_scene_draw(AscScene const *scene) {
+    // TODO: replace with UCX tree visitor
+    // TODO: don't visit the tree, visit the render groups
+    // TODO: avoid recursion
+    asc_scene_draw_node(scene->root);
+}
+
+AscSceneNode *asc_scene_node_empty(void) {
     // TODO: check if this can remain a calloc or if it's too expensive
     AscSceneNode *node = calloc(1, sizeof(AscSceneNode));
     assert(node != NULL);
-    asc_scene_node_link(node, parent);
+    node->free_func = (asc_scene_free_func) free;
     return node;
 }
 
 void asc_scene_node_free(AscSceneNode *node) {
     if (node == NULL) return;
 
+    // TODO: replace with UCX tree visitor
+    // TODO: avoid recursion
+
     // free the children recursively
     while (node->children != NULL) {
         asc_scene_node_free(node->children);
@@ -62,36 +94,15 @@
     asc_scene_node_unlink(node);
 
     // free the node
-    free(node);
-}
-
-void asc_scene_node_link(
-        AscSceneNode *node,
-        AscSceneNode *parent
-) {
-    if (node->parent == parent) return;
-    if (node->parent != NULL || parent == NULL) asc_scene_node_unlink(node);
-    if (parent != NULL) {
-        if (parent->children == NULL) {
-            parent->children = node;
-        } else {
-            parent->children->prev = node;
-            node->next = parent->children;
-        }
-        node->parent = parent;
+    if (node->free_func != NULL) {
+        node->free_func(node);
     }
 }
 
+void asc_scene_node_link(AscSceneNode * restrict parent, AscSceneNode * restrict node) {
+    cx_tree_link(parent, node, asc_scene_node_layout);
+}
+
 void asc_scene_node_unlink(AscSceneNode *node) {
-    if (node->parent == NULL) return;
-    AscSceneNode *left = node->prev;
-    AscSceneNode *right = node->next;
-    assert(left == NULL || node->parent->children != node);
-    if (left != NULL) {
-        left->next = right;
-    } else {
-        node->parent->children = right;
-    }
-    if (right != NULL) right->prev = left;
-    node->parent = node->prev = node->next = NULL;
+    cx_tree_unlink(node, asc_scene_node_layout);
 }
\ No newline at end of file
--- a/src/text.c	Sun Jan 21 14:01:27 2024 +0100
+++ b/src/text.c	Tue Jan 23 21:34:12 2024 +0100
@@ -32,6 +32,36 @@
 
 #include <GL/glew.h>
 
+static void asc_text_draw(AscText const *node) {
+    if (node->color.alpha == 0 || node->hidden || node->internal.tex_id == 0) {
+        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->internal.dimension.width;
+    model[asc_mat4_index(1, 1)] = node->internal.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->internal.tex_id);
+    glUniform1i(ASC_SHADER_FONT.surface, 0);
+
+    // Draw mesh
+    asc_primitives_draw_plane();
+}
+
 AscText *asc_text(int x, int y, char const *text) {
     AscText *node = calloc(1, sizeof(AscText));
     if (node == NULL) {
@@ -39,6 +69,9 @@
         return NULL;
     }
 
+    node->node.free_func = (asc_scene_free_func) asc_text_free;
+    node->node.draw_func = (asc_scene_draw_func) asc_text_draw;
+
     node->position = (asc_vec2i) {x, y};
     node->font = asc_context.active_font;
     node->color = asc_context.ink;
@@ -93,41 +126,6 @@
     SDL_FreeSurface(surface);
 }
 
-void asc_text_draw(AscText const *node) {
-    if (node->color.alpha == 0 || node->hidden) {
-        return;
-    }
-
-    if (node->internal.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->internal.dimension.width;
-    model[asc_mat4_index(1, 1)] = node->internal.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->internal.tex_id);
-    glUniform1i(ASC_SHADER_FONT.surface, 0);
-
-    // Draw mesh
-    asc_primitives_draw_plane();
-}
-
 void asc_text_free(AscText *node) {
     asc_dprintf("Release text node texture: %u", node->internal.tex_id);
     glDeleteTextures(1, &node->internal.tex_id);
--- a/src/window.c	Sun Jan 21 14:01:27 2024 +0100
+++ b/src/window.c	Tue Jan 23 21:34:12 2024 +0100
@@ -61,6 +61,10 @@
     settings->title = "Ascended Window";
 }
 
+static void asc_window_init_scenes(AscWindow *window) {
+    asc_scene_init(&window->ui);
+}
+
 AscWindow *asc_window_initialize(unsigned int index, AscWindowSettings const *settings) {
     if (index >= ASC_MAX_WINDOWS) {
         asc_error("Maximum number of windows exceeded.");
@@ -123,6 +127,7 @@
 
             asc_dprintf("Window %u initialized", window->id);
             if (asc_primitives_init(&window->primitives)) {
+                asc_window_init_scenes(window);
                 asc_context.active_window = window;
                 return window;
             } else {
@@ -152,6 +157,9 @@
         asc_context.active_window = NULL;
     }
 
+    // destroy all scenes
+    asc_scene_destroy(&window->ui);
+
     // 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);
@@ -180,10 +188,18 @@
     if (window != active_window) {
         asc_window_activate(window);
     }
-    SDL_GL_SwapWindow(window->window);
+
+    // Clear viewport for new frame
     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);
+
+    // Draw the UI
+    asc_scene_draw(&window->ui);
+
+    // Swap Buffers
+    SDL_GL_SwapWindow(window->window);
+
     if (window != active_window) {
         asc_window_activate(active_window);
     }
--- a/test/Makefile	Sun Jan 21 14:01:27 2024 +0100
+++ b/test/Makefile	Tue Jan 23 21:34:12 2024 +0100
@@ -45,8 +45,8 @@
  ../src/ascension/error.h ../src/ascension/context.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
+ ../src/ascension/scene.h ../src/ascension/font.h \
+ ../src/ascension/shader.h ../src/ascension/text.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
--- a/test/sandbox.c	Sun Jan 21 14:01:27 2024 +0100
+++ b/test/sandbox.c	Tue Jan 23 21:34:12 2024 +0100
@@ -52,17 +52,18 @@
     AscWindow *window = asc_window_initialize(0, &settings);
     asc_shader_initialize_predefined();
 
+    // create fps counter and add it to the UI
     asc_set_font(asc_font(ASC_FONT_REGULAR, 24));
     asc_ink_rgb(255, 0, 0);
-    AscText *fps_counter = asc_text(50, 50, "9999 FPS");
+    AscText *fps_counter = asc_text(50, 50, "XXXXX FPS");
     unsigned last_fps = 0;
+    asc_scene_add(&window->ui, asc_node(fps_counter));
 
-    while (asc_loop_next()) {
+    do {
         // quit application on any error
         if (show_message_box_on_error(window->window)) break;
 
-
-        // fps counter
+        // update fps counter
         if (asc_context.elapsed_millis > 0) {
             unsigned fps = 1000u / asc_context.elapsed_millis;
             if (fps != last_fps) {
@@ -71,11 +72,7 @@
                 asc_text_update(fps_counter);
             }
         }
-        asc_text_draw(fps_counter);
-    }
-
-    // TODO: maybe nodes should also be "garbage collected"
-    asc_text_free(fps_counter);
+    } while (asc_loop_next());
 
     asc_context_destroy();
     return 0;

mercurial