add camera and render groups

Fri, 15 Mar 2024 00:06:59 +0100

author
Mike Becker <universe@uap-core.de>
date
Fri, 15 Mar 2024 00:06:59 +0100
changeset 37
8a8cc6725b48
parent 36
e26b4ac1661c
child 38
6e5629ea4c5c

add camera and render groups

src/Makefile file | annotate | diff | comparison | revisions
src/ascension/camera.h file | annotate | diff | comparison | revisions
src/ascension/datatypes.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/camera.c file | annotate | diff | comparison | revisions
src/context.c 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/snake.c file | annotate | diff | comparison | revisions
     1.1 --- a/src/Makefile	Wed Mar 06 23:38:17 2024 +0100
     1.2 +++ b/src/Makefile	Fri Mar 15 00:06:59 2024 +0100
     1.3 @@ -27,8 +27,8 @@
     1.4  
     1.5  BUILD_DIR=../build/lib
     1.6  
     1.7 -SRC = context.c error.c window.c files.c shader.c font.c text.c scene.c \
     1.8 -	  primitives.c
     1.9 +SRC = context.c error.c window.c files.c shader.c font.c text.c \
    1.10 +      scene.c camera.c primitives.c
    1.11  
    1.12  OBJ = $(SRC:%.c=$(BUILD_DIR)/%.o)
    1.13  
    1.14 @@ -41,17 +41,22 @@
    1.15  
    1.16  FORCE:
    1.17  
    1.18 +$(BUILD_DIR)/camera.o: camera.c ascension/camera.h ascension/datatypes.h
    1.19 +	@echo "Compiling $<"
    1.20 +	$(CC) -o $@ $(CFLAGS) -c $<
    1.21 +
    1.22  $(BUILD_DIR)/context.o: context.c ascension/context.h \
    1.23   ascension/datatypes.h ascension/window.h ascension/primitives.h \
    1.24   ascension/mesh.h ascension/scene.h ascension/transform.h \
    1.25 - ascension/font.h ascension/error.h ascension/utils.h ascension/shader.h
    1.26 + ascension/camera.h ascension/font.h ascension/error.h ascension/utils.h \
    1.27 + ascension/shader.h
    1.28  	@echo "Compiling $<"
    1.29  	$(CC) -o $@ $(CFLAGS) -c $<
    1.30  
    1.31  $(BUILD_DIR)/error.o: error.c ascension/context.h ascension/datatypes.h \
    1.32   ascension/window.h ascension/primitives.h ascension/mesh.h \
    1.33 - ascension/scene.h ascension/transform.h ascension/font.h \
    1.34 - ascension/error.h ascension/utils.h
    1.35 + ascension/scene.h ascension/transform.h ascension/camera.h \
    1.36 + ascension/font.h ascension/error.h ascension/utils.h
    1.37  	@echo "Compiling $<"
    1.38  	$(CC) -o $@ $(CFLAGS) -c $<
    1.39  
    1.40 @@ -62,19 +67,22 @@
    1.41  $(BUILD_DIR)/font.o: font.c ascension/font.h ascension/context.h \
    1.42   ascension/datatypes.h ascension/window.h ascension/primitives.h \
    1.43   ascension/mesh.h ascension/scene.h ascension/transform.h \
    1.44 - ascension/font.h ascension/error.h
    1.45 + ascension/camera.h ascension/font.h ascension/error.h
    1.46  	@echo "Compiling $<"
    1.47  	$(CC) -o $@ $(CFLAGS) -c $<
    1.48  
    1.49  $(BUILD_DIR)/primitives.o: primitives.c ascension/primitives.h \
    1.50   ascension/mesh.h ascension/error.h ascension/context.h \
    1.51   ascension/datatypes.h ascension/window.h ascension/primitives.h \
    1.52 - ascension/scene.h ascension/transform.h ascension/font.h
    1.53 + ascension/scene.h ascension/transform.h ascension/camera.h \
    1.54 + ascension/font.h
    1.55  	@echo "Compiling $<"
    1.56  	$(CC) -o $@ $(CFLAGS) -c $<
    1.57  
    1.58 -$(BUILD_DIR)/scene.o: scene.c ascension/scene.h ascension/transform.h \
    1.59 - ascension/datatypes.h ascension/error.h
    1.60 +$(BUILD_DIR)/scene.o: scene.c ascension/scene.h ascension/datatypes.h \
    1.61 + ascension/transform.h ascension/camera.h ascension/error.h \
    1.62 + ascension/context.h ascension/window.h ascension/primitives.h \
    1.63 + ascension/mesh.h ascension/scene.h ascension/font.h ascension/shader.h
    1.64  	@echo "Compiling $<"
    1.65  	$(CC) -o $@ $(CFLAGS) -c $<
    1.66  
    1.67 @@ -84,16 +92,17 @@
    1.68  	$(CC) -o $@ $(CFLAGS) -c $<
    1.69  
    1.70  $(BUILD_DIR)/text.o: text.c ascension/text.h ascension/font.h \
    1.71 - ascension/scene.h ascension/transform.h ascension/datatypes.h \
    1.72 - ascension/context.h ascension/window.h ascension/primitives.h \
    1.73 - ascension/mesh.h ascension/error.h ascension/shader.h
    1.74 + ascension/scene.h ascension/datatypes.h ascension/transform.h \
    1.75 + ascension/camera.h ascension/context.h ascension/window.h \
    1.76 + ascension/primitives.h ascension/mesh.h ascension/error.h \
    1.77 + ascension/shader.h
    1.78  	@echo "Compiling $<"
    1.79  	$(CC) -o $@ $(CFLAGS) -c $<
    1.80  
    1.81  $(BUILD_DIR)/window.o: window.c ascension/window.h ascension/datatypes.h \
    1.82   ascension/primitives.h ascension/mesh.h ascension/scene.h \
    1.83 - ascension/transform.h ascension/context.h ascension/window.h \
    1.84 - ascension/font.h ascension/error.h ascension/utils.h
    1.85 + ascension/transform.h ascension/camera.h ascension/context.h \
    1.86 + ascension/window.h ascension/font.h ascension/error.h ascension/utils.h
    1.87  	@echo "Compiling $<"
    1.88  	$(CC) -o $@ $(CFLAGS) -c $<
    1.89  
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/src/ascension/camera.h	Fri Mar 15 00:06:59 2024 +0100
     2.3 @@ -0,0 +1,59 @@
     2.4 +/*
     2.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     2.6 + * Copyright 2023 Mike Becker. All rights reserved.
     2.7 + *
     2.8 + * Redistribution and use in source and binary forms, with or without
     2.9 + * modification, are permitted provided that the following conditions are met:
    2.10 + *
    2.11 + *   1. Redistributions of source code must retain the above copyright
    2.12 + *      notice, this list of conditions and the following disclaimer.
    2.13 + *
    2.14 + *   2. Redistributions in binary form must reproduce the above copyright
    2.15 + *      notice, this list of conditions and the following disclaimer in the
    2.16 + *      documentation and/or other materials provided with the distribution.
    2.17 + *
    2.18 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    2.19 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    2.20 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    2.21 + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    2.22 + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    2.23 + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    2.24 + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    2.25 + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    2.26 + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    2.27 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    2.28 + * POSSIBILITY OF SUCH DAMAGE.
    2.29 + */
    2.30 +
    2.31 +#ifndef ASCENSION_CAMERA_H
    2.32 +#define ASCENSION_CAMERA_H
    2.33 +
    2.34 +#include "datatypes.h"
    2.35 +
    2.36 +enum AscCameraType {
    2.37 +    ASC_CAMERA_DISABLED,
    2.38 +    ASC_CAMERA_ORTHO,
    2.39 +    ASC_CAMERA_PERSPECTIVE,
    2.40 +};
    2.41 +
    2.42 +typedef struct AscCamera AscCamera;
    2.43 +typedef void(*asc_camera_update_func)(AscCamera*);
    2.44 +
    2.45 +struct AscCamera {
    2.46 +    asc_camera_update_func update;
    2.47 +    asc_mat4f projection;
    2.48 +    asc_mat4f view;
    2.49 +    asc_vec3f right;
    2.50 +    asc_vec3f up;
    2.51 +    asc_vec3f direction;
    2.52 +    asc_recti rect;
    2.53 +    // TODO: fov?
    2.54 +};
    2.55 +
    2.56 +__attribute__((__nonnull__))
    2.57 +void asc_camera_ortho(AscCamera *camera, asc_recti rect);
    2.58 +
    2.59 +__attribute__((__nonnull__))
    2.60 +void asc_camera_disable(AscCamera *camera);
    2.61 +
    2.62 +#endif //ASCENSION_CAMERA_H
     3.1 --- a/src/ascension/datatypes.h	Wed Mar 06 23:38:17 2024 +0100
     3.2 +++ b/src/ascension/datatypes.h	Fri Mar 15 00:06:59 2024 +0100
     3.3 @@ -44,11 +44,22 @@
     3.4  typedef signed char asc_sbyte;
     3.5  
     3.6  typedef union asc_vec2i {
     3.7 -    int data[2];
     3.8      struct { int x, y; };
     3.9      struct { int width, height; };
    3.10 +    int data[2];
    3.11  } asc_vec2i;
    3.12  
    3.13 +typedef struct asc_recti {
    3.14 +    asc_vec2i pos;
    3.15 +    asc_vec2i size;
    3.16 +} asc_recti;
    3.17 +
    3.18 +typedef union asc_vec3f {
    3.19 +    struct { float x, y, z; };
    3.20 +    struct { float width, height, depth; };
    3.21 +    float data[3];
    3.22 +} asc_vec3f;
    3.23 +
    3.24  typedef struct asc_col4i {
    3.25      asc_ubyte red, green, blue, alpha;
    3.26  } asc_col4i;
    3.27 @@ -114,6 +125,14 @@
    3.28   */
    3.29  #define asc_mat4_index(col, row) asc_mat_index(col, row, 4)
    3.30  
    3.31 +static inline void asc_mat4f_unit(asc_mat4f mat) {
    3.32 +    memset(mat, 0, sizeof(float) * 16);
    3.33 +    mat[asc_mat4_index(0,0)] = 1;
    3.34 +    mat[asc_mat4_index(1,1)] = 1;
    3.35 +    mat[asc_mat4_index(2,2)] = 1;
    3.36 +    mat[asc_mat4_index(3,3)] = 1;
    3.37 +}
    3.38 +
    3.39  static inline void asc_mat4f_ortho(
    3.40          asc_mat4f mat,
    3.41          float left,
     4.1 --- a/src/ascension/scene.h	Wed Mar 06 23:38:17 2024 +0100
     4.2 +++ b/src/ascension/scene.h	Fri Mar 15 00:06:59 2024 +0100
     4.3 @@ -28,7 +28,11 @@
     4.4  #ifndef ASCENSION_SCENE_H
     4.5  #define ASCENSION_SCENE_H
     4.6  
     4.7 +#include "datatypes.h"
     4.8  #include "transform.h"
     4.9 +#include "camera.h"
    4.10 +
    4.11 +#include <cx/array_list.h>
    4.12  
    4.13  typedef struct AscSceneNode AscSceneNode;
    4.14  typedef struct AscBehaviorNode AscBehaviorNode;
    4.15 @@ -44,6 +48,11 @@
    4.16      asc_scene_update_func func;
    4.17  };
    4.18  
    4.19 +enum AscRenderGroup {
    4.20 +    ASC_RENDER_GROUP_NONE,
    4.21 +    ASC_RENDER_GROUP_FONTS
    4.22 +};
    4.23 +
    4.24  struct AscSceneNode {
    4.25      AscSceneNode *parent;
    4.26      AscSceneNode *prev;
    4.27 @@ -53,9 +62,12 @@
    4.28      void *data;
    4.29      asc_scene_free_func free_func;
    4.30      asc_scene_update_func update_func;
    4.31 +    asc_scene_update_func transform_update_func;
    4.32      asc_scene_draw_func draw_func;
    4.33      asc_transform transform;
    4.34 -    bool need_update;
    4.35 +    enum AscRenderGroup render_group;
    4.36 +    bool need_full_update;
    4.37 +    bool need_transform_update;
    4.38  };
    4.39  
    4.40  /**
    4.41 @@ -65,13 +77,32 @@
    4.42  
    4.43  #define asc_node(obj) ((AscSceneNode*)obj)
    4.44  
    4.45 -#define asc_node_update(node) ((AscSceneNode*)node)->need_update = true
    4.46 +#define asc_node_update(node) \
    4.47 +    ((AscSceneNode*)node)->need_full_update = true
    4.48 +#define asc_node_update_transform(node) \
    4.49 +    ((AscSceneNode*)node)->need_transform_update = true
    4.50 +
    4.51 +struct asc_render_group_entry {
    4.52 +    asc_scene_draw_func draw;
    4.53 +    AscSceneNode const *node;
    4.54 +};
    4.55 +
    4.56 +#define ASC_SCENE_CAMERAS_MAX 4
    4.57  
    4.58  typedef struct AscScene {
    4.59      AscSceneNode *root;
    4.60 -    // TODO: add render groups for batching
    4.61 +    asc_recti viewport;
    4.62 +    AscCamera cameras[ASC_SCENE_CAMERAS_MAX];
    4.63 +    CX_ARRAY_DECLARE(struct asc_render_group_entry, rg_none);
    4.64 +    CX_ARRAY_DECLARE(struct asc_render_group_entry, rg_fonts);
    4.65  } AscScene;
    4.66  
    4.67 +/**
    4.68 + * Initializes the scene using the active window as reference.
    4.69 + *
    4.70 + * @param scene the scene to initialize
    4.71 + * @param type determines the type of camera to use
    4.72 + */
    4.73  __attribute__((__nonnull__))
    4.74  void asc_scene_init(AscScene *scene);
    4.75  
    4.76 @@ -79,7 +110,7 @@
    4.77  void asc_scene_destroy(AscScene *scene);
    4.78  
    4.79  __attribute__((__nonnull__))
    4.80 -void asc_scene_draw(AscScene const *scene);
    4.81 +void asc_scene_draw(AscScene *scene);
    4.82  
    4.83  __attribute__((__nonnull__))
    4.84  void asc_scene_add(AscScene *scene, AscSceneNode *node);
     5.1 --- a/src/ascension/text.h	Wed Mar 06 23:38:17 2024 +0100
     5.2 +++ b/src/ascension/text.h	Fri Mar 15 00:06:59 2024 +0100
     5.3 @@ -40,9 +40,8 @@
     5.4      unsigned max_width;
     5.5      bool hidden;
     5.6      bool centered;
     5.7 -    struct {
     5.8 -        unsigned tex_id;
     5.9 -    } internal;
    5.10 +    asc_vec2i dimension;
    5.11 +    unsigned tex_id;
    5.12  } AscText;
    5.13  
    5.14  
     6.1 --- a/src/ascension/window.h	Wed Mar 06 23:38:17 2024 +0100
     6.2 +++ b/src/ascension/window.h	Fri Mar 15 00:06:59 2024 +0100
     6.3 @@ -55,11 +55,11 @@
     6.4      SDL_GLContext glctx;
     6.5      AscPrimitives primitives;
     6.6      asc_vec2i dimensions;
     6.7 -    asc_mat4f projection;
     6.8 +    bool resized;
     6.9      AscScene ui;
    6.10  } AscWindow;
    6.11  
    6.12 -#define asc_window_active_ui &(asc_context.active_window->ui)
    6.13 +#define asc_window_active asc_context.active_window
    6.14  
    6.15  /**
    6.16   * Initializes the settings structure with default values.
    6.17 @@ -102,7 +102,7 @@
    6.18   *
    6.19   * @param window the window
    6.20   */
    6.21 -void asc_window_sync(AscWindow const *window);
    6.22 +void asc_window_sync(AscWindow *window);
    6.23  
    6.24  /**
    6.25   * Switches the active window.
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/src/camera.c	Fri Mar 15 00:06:59 2024 +0100
     7.3 @@ -0,0 +1,46 @@
     7.4 +/*
     7.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     7.6 + * Copyright 2023 Mike Becker. All rights reserved.
     7.7 + *
     7.8 + * Redistribution and use in source and binary forms, with or without
     7.9 + * modification, are permitted provided that the following conditions are met:
    7.10 + *
    7.11 + *   1. Redistributions of source code must retain the above copyright
    7.12 + *      notice, this list of conditions and the following disclaimer.
    7.13 + *
    7.14 + *   2. Redistributions in binary form must reproduce the above copyright
    7.15 + *      notice, this list of conditions and the following disclaimer in the
    7.16 + *      documentation and/or other materials provided with the distribution.
    7.17 + *
    7.18 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    7.19 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    7.20 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    7.21 + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    7.22 + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    7.23 + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    7.24 + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    7.25 + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    7.26 + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    7.27 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    7.28 + * POSSIBILITY OF SUCH DAMAGE.
    7.29 + */
    7.30 +
    7.31 +#include "ascension/camera.h"
    7.32 +
    7.33 +static void asc_camera_update_ortho(AscCamera *camera) {
    7.34 +    float left = (float) camera->rect.pos.x;
    7.35 +    float right = left + (float) camera->rect.size.width;
    7.36 +    float top = (float) camera->rect.pos.y;
    7.37 +    float bottom = top + (float) camera->rect.size.height;
    7.38 +    asc_mat4f_ortho(camera->projection, left, right, bottom, top);
    7.39 +}
    7.40 +
    7.41 +void asc_camera_ortho(AscCamera *camera, asc_recti rect) {
    7.42 +    asc_mat4f_unit(camera->view);
    7.43 +    camera->update = asc_camera_update_ortho;
    7.44 +    camera->rect = rect;
    7.45 +}
    7.46 +
    7.47 +void asc_camera_disable(AscCamera *camera) {
    7.48 +    camera->update = NULL;
    7.49 +}
     8.1 --- a/src/context.c	Wed Mar 06 23:38:17 2024 +0100
     8.2 +++ b/src/context.c	Fri Mar 15 00:06:59 2024 +0100
     8.3 @@ -83,9 +83,11 @@
     8.4  static void asc_event_window_resized(Uint32 id, Sint32 width, Sint32 height) {
     8.5      for (unsigned int i = 0 ; i < ASC_MAX_WINDOWS ; i++) {
     8.6          if (asc_context.windows[i].id == id) {
     8.7 -            asc_context.windows[i].dimensions.width = width;
     8.8 -            asc_context.windows[i].dimensions.height = height;
     8.9 -            asc_mat4f_ortho(asc_context.windows[i].projection, 0, (float) width, (float) height, 0);
    8.10 +            asc_vec2i dimensions = (asc_vec2i) {width, height};
    8.11 +            asc_context.windows[i].resized = true;
    8.12 +            asc_context.windows[i].dimensions = dimensions;
    8.13 +            asc_context.windows[i].ui.viewport.size = dimensions;
    8.14 +            asc_context.windows[i].ui.cameras[0].rect.size = dimensions;
    8.15              return;
    8.16          }
    8.17      }
     9.1 --- a/src/scene.c	Wed Mar 06 23:38:17 2024 +0100
     9.2 +++ b/src/scene.c	Fri Mar 15 00:06:59 2024 +0100
     9.3 @@ -28,19 +28,34 @@
     9.4  #include "ascension/scene.h"
     9.5  #include "ascension/error.h"
     9.6  
     9.7 +#include "ascension/context.h"
     9.8 +
     9.9  #include <cx/tree.h>
    9.10 +#include <cx/utils.h>
    9.11 +
    9.12 +#include "ascension/shader.h"
    9.13 +#include <GL/glew.h>
    9.14  
    9.15  #include <assert.h>
    9.16  
    9.17 -#define child_list_off_ \
    9.18 -    offsetof(AscSceneNode, children), offsetof(AscSceneNode, next)
    9.19 -
    9.20  void asc_scene_init(AscScene *scene) {
    9.21      if (scene->root != NULL) {
    9.22          asc_error("Scene is already initialized.");
    9.23          return;
    9.24      }
    9.25 +
    9.26 +    // zero everything, first
    9.27 +    memset(scene, 0, sizeof(AscScene));
    9.28 +
    9.29 +    // default viewport is the entire viewport of the active window
    9.30 +    scene->viewport.size = asc_context.active_window->dimensions;
    9.31 +
    9.32 +    // create the root node
    9.33      scene->root = asc_scene_node_empty();
    9.34 +
    9.35 +    // initialize the render groups
    9.36 +    cx_array_initialize(scene->rg_none, 8);
    9.37 +    cx_array_initialize(scene->rg_fonts, 8);
    9.38  }
    9.39  
    9.40  void asc_scene_destroy(AscScene *scene) {
    9.41 @@ -52,13 +67,25 @@
    9.42      asc_node_update(node);
    9.43  }
    9.44  
    9.45 -void asc_scene_draw(AscScene const *scene) {
    9.46 -    CxTreeIterator iter = cx_tree_iterator(scene->root, false, child_list_off_);
    9.47 +#define asc_scene_draw_render_group(rg) \
    9.48 +    cx_for_n(i, rg##_size) { \
    9.49 +        rg[i].draw(rg[i].node); \
    9.50 +    } (void)0
    9.51 +
    9.52 +void asc_scene_draw(AscScene *scene) {
    9.53 +    // reset render groups
    9.54 +    scene->rg_none_size = 0;
    9.55 +    scene->rg_fonts_size = 0;
    9.56  
    9.57      // skip the root node deliberately, we know it's just the container
    9.58 +    CxTreeIterator iter = cx_tree_iterator(
    9.59 +            scene->root, false,
    9.60 +            offsetof(AscSceneNode, children),
    9.61 +            offsetof(AscSceneNode, next)
    9.62 +    );
    9.63      cxIteratorNext(iter);
    9.64  
    9.65 -    // update the children
    9.66 +    // update the children and add them to the render groups
    9.67      cx_foreach(AscSceneNode*, node, iter) {
    9.68          // execute behaviors, first
    9.69          AscBehaviorNode *behavior = node->behaviors;
    9.70 @@ -68,22 +95,65 @@
    9.71          }
    9.72  
    9.73          // check if geometry needs update
    9.74 -        if (node->need_update && node->update_func != NULL) {
    9.75 -            node->need_update = false;
    9.76 -            asc_transform_copy(node->transform, node->parent->transform);
    9.77 +        if (node->need_full_update) {
    9.78 +            assert(node->update_func != NULL);
    9.79 +            node->need_full_update = false;
    9.80              node->update_func(node);
    9.81          }
    9.82 +        if (node->need_transform_update) {
    9.83 +            assert(node->transform_update_func != NULL);
    9.84 +            node->need_transform_update = false;
    9.85 +            asc_transform_identity(node->transform);
    9.86 +            node->transform_update_func(node);
    9.87 +        }
    9.88  
    9.89 -        // TODO: don't visit the tree for drawing, visit the render groups
    9.90 +        // add to render group
    9.91          if (node->draw_func != NULL) {
    9.92 -            node->draw_func(node);
    9.93 +            struct asc_render_group_entry entry = {
    9.94 +                    node->draw_func, node
    9.95 +            };
    9.96 +            switch (node->render_group) {
    9.97 +                case ASC_RENDER_GROUP_NONE:
    9.98 +                    cx_array_simple_add(scene->rg_none, entry);
    9.99 +                    break;
   9.100 +                case ASC_RENDER_GROUP_FONTS:
   9.101 +                    cx_array_simple_add(scene->rg_fonts, entry);
   9.102 +                    break;
   9.103 +            }
   9.104          }
   9.105      }
   9.106 +
   9.107 +    // set the viewport (in OpenGL we need to invert the Y axis)
   9.108 +    glViewport(
   9.109 +            scene->viewport.pos.x,
   9.110 +            -scene->viewport.pos.y,
   9.111 +            scene->viewport.size.width,
   9.112 +            scene->viewport.size.height
   9.113 +    );
   9.114 +
   9.115 +    // -----------------------------------------
   9.116 +    // process the render groups for each camera
   9.117 +    // -----------------------------------------
   9.118 +    cx_for_n(cam_id, ASC_SCENE_CAMERAS_MAX) {
   9.119 +        // update camera parameters, first
   9.120 +        AscCamera *camera = &scene->cameras[cam_id];
   9.121 +        if (camera->update == NULL) continue;
   9.122 +        camera->update(camera);
   9.123 +
   9.124 +        // for the NONE group, the draw func is expected to do everything
   9.125 +        asc_scene_draw_render_group(scene->rg_none);
   9.126 +
   9.127 +        // draw the FONTS group
   9.128 +        // TODO: see if we can really always ignore the view matrix
   9.129 +        glUseProgram(ASC_SHADER_FONT.base.id);
   9.130 +        glUniformMatrix4fv(ASC_SHADER_FONT.base.projection, 1,
   9.131 +                           GL_FALSE, camera->projection);
   9.132 +        asc_scene_draw_render_group(scene->rg_fonts);
   9.133 +    }
   9.134  }
   9.135  
   9.136  AscSceneNode *asc_scene_node_empty(void) {
   9.137      AscSceneNode *node = calloc(1, sizeof(AscSceneNode));
   9.138 -    assert(node != NULL);
   9.139      node->free_func = (asc_scene_free_func) free;
   9.140      asc_transform_identity(node->transform);
   9.141      return node;
   9.142 @@ -96,7 +166,11 @@
   9.143      asc_scene_node_unlink(node);
   9.144  
   9.145      // free the entire subtree
   9.146 -    CxTreeIterator iter = cx_tree_iterator(node, true, child_list_off_);
   9.147 +    CxTreeIterator iter = cx_tree_iterator(
   9.148 +            node, true,
   9.149 +            offsetof(AscSceneNode, children),
   9.150 +            offsetof(AscSceneNode, next)
   9.151 +    );
   9.152      cx_foreach(AscSceneNode*, child, iter) {
   9.153          if (!iter.exiting) continue;
   9.154          if (child->free_func != NULL) {
    10.1 --- a/src/text.c	Wed Mar 06 23:38:17 2024 +0100
    10.2 +++ b/src/text.c	Fri Mar 15 00:06:59 2024 +0100
    10.3 @@ -33,29 +33,28 @@
    10.4  #include <GL/glew.h>
    10.5  
    10.6  static void asc_text_draw(AscText const *node) {
    10.7 -    if (node->color.alpha == 0 || node->hidden || node->internal.tex_id == 0) {
    10.8 +    if (node->color.alpha == 0 || node->hidden || node->tex_id == 0) {
    10.9          return;
   10.10      }
   10.11  
   10.12 -    // TODO: when we group draw calls, we don't need to activate shader here
   10.13 -    glUseProgram(ASC_SHADER_FONT.base.id);
   10.14 -
   10.15 -    // TODO: when we group UI draw calls, we don't need to upload matrices here
   10.16 -    // Upload matrices
   10.17 -    glUniformMatrix4fv(ASC_SHADER_FONT.base.projection, 1,
   10.18 -                       GL_FALSE, asc_context.active_window->projection);
   10.19 +    // Upload model matrix
   10.20      glUniformMatrix4fv(ASC_SHADER_FONT.base.model, 1,
   10.21                         GL_FALSE, node->base.transform);
   10.22  
   10.23      // Upload surface
   10.24      glActiveTexture(GL_TEXTURE0);
   10.25 -    glBindTexture(GL_TEXTURE_RECTANGLE, node->internal.tex_id);
   10.26 +    glBindTexture(GL_TEXTURE_RECTANGLE, node->tex_id);
   10.27      glUniform1i(ASC_SHADER_FONT.surface, 0);
   10.28  
   10.29      // Draw mesh
   10.30      asc_primitives_draw_plane();
   10.31  }
   10.32  
   10.33 +static void asc_text_update_transform(AscText *node) {
   10.34 +    asc_transform_scale(node->base.transform, (float) node->dimension.width, (float) node->dimension.height, 0);
   10.35 +    asc_transform_translate2i(node->base.transform, node->position);
   10.36 +}
   10.37 +
   10.38  static void asc_text_update(AscText *node) {
   10.39      // short circuit if fully transparent or hidden, we don't need anything
   10.40      if (node->color.alpha == 0 || node->hidden) {
   10.41 @@ -63,12 +62,12 @@
   10.42      }
   10.43  
   10.44      // Generate new texture, if required
   10.45 -    if (node->internal.tex_id == 0) {
   10.46 -        glGenTextures(1, &node->internal.tex_id);
   10.47 -        glBindTexture(GL_TEXTURE_RECTANGLE, node->internal.tex_id);
   10.48 +    if (node->tex_id == 0) {
   10.49 +        glGenTextures(1, &node->tex_id);
   10.50 +        glBindTexture(GL_TEXTURE_RECTANGLE, node->tex_id);
   10.51          glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
   10.52          glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   10.53 -        asc_dprintf("Generated new texture for text node: %u", node->internal.tex_id);
   10.54 +        asc_dprintf("Generated new texture for text node: %u", node->tex_id);
   10.55      }
   10.56  
   10.57      // Render text onto a surface
   10.58 @@ -82,14 +81,13 @@
   10.59          asc_error(SDL_GetError());
   10.60          return;
   10.61      }
   10.62 -
   10.63 -    // Transform
   10.64 -    asc_transform_scale(node->base.transform, (float) surface->w, (float) surface->h, 0);
   10.65 -    asc_transform_translate2i(node->base.transform, node->position);
   10.66 +    node->dimension.width = surface->w;
   10.67 +    node->dimension.height = surface->h;
   10.68 +    asc_node_update_transform(node);
   10.69  
   10.70      // Transfer Image Data
   10.71      // TODO: move the image data transfer to a separate function - we will need it more often
   10.72 -    glBindTexture(GL_TEXTURE_RECTANGLE, node->internal.tex_id);
   10.73 +    glBindTexture(GL_TEXTURE_RECTANGLE, node->tex_id);
   10.74      glPixelStorei(GL_UNPACK_ROW_LENGTH, surface->pitch / surface->format->BytesPerPixel);
   10.75      glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA,
   10.76                   surface->w, surface->h,
   10.77 @@ -106,8 +104,10 @@
   10.78          return NULL;
   10.79      }
   10.80  
   10.81 +    node->base.render_group = ASC_RENDER_GROUP_FONTS;
   10.82      node->base.free_func = (asc_scene_free_func) asc_text_free;
   10.83      node->base.update_func = (asc_scene_update_func) asc_text_update;
   10.84 +    node->base.transform_update_func = (asc_scene_update_func) asc_text_update_transform;
   10.85      node->base.draw_func = (asc_scene_draw_func) asc_text_draw;
   10.86  
   10.87      node->position.x = x;
   10.88 @@ -118,12 +118,16 @@
   10.89          node->text = strdup(text);
   10.90      }
   10.91  
   10.92 +    // initialize
   10.93 +    asc_text_update(node);
   10.94 +    asc_text_update_transform(node);
   10.95 +
   10.96      return &node->base;
   10.97  }
   10.98  
   10.99  void asc_text_free(AscText *node) {
  10.100 -    asc_dprintf("Release text node texture: %u", node->internal.tex_id);
  10.101 -    glDeleteTextures(1, &node->internal.tex_id);
  10.102 +    asc_dprintf("Release text node texture: %u", node->tex_id);
  10.103 +    glDeleteTextures(1, &node->tex_id);
  10.104      free(node->text);
  10.105      free(node);
  10.106  }
  10.107 \ No newline at end of file
    11.1 --- a/src/window.c	Wed Mar 06 23:38:17 2024 +0100
    11.2 +++ b/src/window.c	Fri Mar 15 00:06:59 2024 +0100
    11.3 @@ -63,6 +63,9 @@
    11.4  
    11.5  static void asc_window_init_scenes(AscWindow *window) {
    11.6      asc_scene_init(&window->ui);
    11.7 +    asc_camera_ortho(&window->ui.cameras[0], (asc_recti){
    11.8 +        0, 0, window->dimensions
    11.9 +    });
   11.10  }
   11.11  
   11.12  AscWindow *asc_window_initialize(unsigned int index, AscWindowSettings const *settings) {
   11.13 @@ -98,13 +101,7 @@
   11.14              &window->dimensions.width,
   11.15              &window->dimensions.height
   11.16      );
   11.17 -    asc_mat4f_ortho(
   11.18 -            window->projection,
   11.19 -            0,
   11.20 -            (float) window->dimensions.width,
   11.21 -            (float) window->dimensions.height,
   11.22 -            0
   11.23 -    );
   11.24 +    window->resized = true; // count initial sizing as resize
   11.25  
   11.26      SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
   11.27      SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, settings->gl_major_version);
   11.28 @@ -127,8 +124,8 @@
   11.29  
   11.30              asc_dprintf("Window %u initialized", window->id);
   11.31              if (asc_primitives_init(&window->primitives)) {
   11.32 +                asc_context.active_window = window;
   11.33                  asc_window_init_scenes(window);
   11.34 -                asc_context.active_window = window;
   11.35                  return window;
   11.36              } else {
   11.37                  asc_dprintf("!!! Creating primitives for window %u failed !!!", window->id);
   11.38 @@ -183,14 +180,13 @@
   11.39      memset(window, 0, sizeof(AscWindow));
   11.40  }
   11.41  
   11.42 -void asc_window_sync(AscWindow const* window) {
   11.43 +void asc_window_sync(AscWindow* window) {
   11.44      AscWindow const *active_window = asc_context.active_window;
   11.45      if (window != active_window) {
   11.46          asc_window_activate(window);
   11.47      }
   11.48  
   11.49 -    // Clear viewport for new frame
   11.50 -    glViewport(0, 0, window->dimensions.width, window->dimensions.height);
   11.51 +    // Clear for new frame
   11.52      glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
   11.53      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   11.54  
   11.55 @@ -200,6 +196,9 @@
   11.56      // Swap Buffers
   11.57      SDL_GL_SwapWindow(window->window);
   11.58  
   11.59 +    // Clear Flags
   11.60 +    window->resized = false;
   11.61 +
   11.62      if (window != active_window) {
   11.63          asc_window_activate(active_window);
   11.64      }
    12.1 --- a/test/Makefile	Wed Mar 06 23:38:17 2024 +0100
    12.2 +++ b/test/Makefile	Fri Mar 15 00:06:59 2024 +0100
    12.3 @@ -46,8 +46,8 @@
    12.4   ../src/ascension/datatypes.h ../src/ascension/window.h \
    12.5   ../src/ascension/primitives.h ../src/ascension/mesh.h \
    12.6   ../src/ascension/scene.h ../src/ascension/transform.h \
    12.7 - ../src/ascension/font.h ../src/ascension/shader.h \
    12.8 - ../src/ascension/text.h
    12.9 + ../src/ascension/camera.h ../src/ascension/font.h \
   12.10 + ../src/ascension/shader.h ../src/ascension/text.h
   12.11  	@echo "Compiling $<"
   12.12  	$(CC) -o $@ $(CFLAGS) -c $<
   12.13  
    13.1 --- a/test/snake.c	Wed Mar 06 23:38:17 2024 +0100
    13.2 +++ b/test/snake.c	Fri Mar 15 00:06:59 2024 +0100
    13.3 @@ -30,7 +30,7 @@
    13.4  
    13.5  static void update_fps_counter(AscSceneNode *node) {
    13.6      // addition and multiplication is more efficient testing for zero
    13.7 -    // at an unnoticible cost of imprecision
    13.8 +    // at an unnoticeable cost of imprecision
    13.9      static unsigned last_fps = 0u;
   13.10      static unsigned debounce = 999u;
   13.11      unsigned fps = 1000u;
   13.12 @@ -51,7 +51,27 @@
   13.13      asc_ink_rgb(255, 0, 0);
   13.14      AscSceneNode* node = asc_text(10, 10, "XXXXX FPS");
   13.15      asc_scene_add_behavior(node, update_fps_counter);
   13.16 -    asc_scene_add(asc_window_active_ui, node);
   13.17 +    asc_scene_add(&asc_window_active->ui, node);
   13.18 +}
   13.19 +
   13.20 +static void update_score_counter(AscSceneNode *node) {
   13.21 +    AscText *text = asc_text_data(node);
   13.22 +
   13.23 +    // tie to bottom left of the screen
   13.24 +    if (asc_window_active->resized) {
   13.25 +        asc_vec2i bottom_left = asc_window_active->dimensions;
   13.26 +        text->position.x = bottom_left.x - text->dimension.width - 10;
   13.27 +        text->position.y = bottom_left.y - text->dimension.height - 10;
   13.28 +        asc_node_update_transform(text);
   13.29 +    }
   13.30 +}
   13.31 +
   13.32 +static void create_score_counter(void) {
   13.33 +    asc_set_font(asc_font(ASC_FONT_BOLD, 14));
   13.34 +    asc_ink_rgb(0, 255, 0);
   13.35 +    AscSceneNode* node = asc_text(0, 0, "Score: 0");
   13.36 +    asc_scene_add_behavior(node, update_score_counter);
   13.37 +    asc_scene_add(&asc_window_active->ui, node);
   13.38  }
   13.39  
   13.40  int main(int argc, char** argv) {
   13.41 @@ -69,8 +89,10 @@
   13.42      AscWindow *window = asc_window_initialize(0, &settings);
   13.43      asc_shader_initialize_predefined();
   13.44  
   13.45 -    // create fps counter
   13.46 +    // create UI elements
   13.47      create_fps_counter();
   13.48 +    create_score_counter();
   13.49 +
   13.50  
   13.51      // Main Loop
   13.52      do {

mercurial