# HG changeset patch # User Mike Becker # Date 1711990459 -7200 # Node ID 44457f6cb0a2046608f31b284d1e3d61872dff7c # Parent d3285aed65b39a3f0dd32be892bd34ac39b84ae2 remove unnecessary scene container diff -r d3285aed65b3 -r 44457f6cb0a2 src/ascension/camera.h --- a/src/ascension/camera.h Fri Mar 29 00:03:25 2024 +0100 +++ b/src/ascension/camera.h Mon Apr 01 18:54:19 2024 +0200 @@ -30,30 +30,14 @@ #include "datatypes.h" -enum AscCameraType { - ASC_CAMERA_DISABLED, - ASC_CAMERA_ORTHO, - ASC_CAMERA_PERSPECTIVE, -}; - typedef struct AscCamera AscCamera; -typedef void(*asc_camera_update_func)(AscCamera*); struct AscCamera { - asc_camera_update_func update; asc_mat4f projection; asc_mat4f view; - asc_vec3f right; - asc_vec3f up; - asc_vec3f direction; - asc_recti rect; - // TODO: fov? }; __attribute__((__nonnull__)) void asc_camera_ortho(AscCamera *camera, asc_recti rect); -__attribute__((__nonnull__)) -void asc_camera_disable(AscCamera *camera); - #endif //ASCENSION_CAMERA_H diff -r d3285aed65b3 -r 44457f6cb0a2 src/ascension/scene.h --- a/src/ascension/scene.h Fri Mar 29 00:03:25 2024 +0100 +++ b/src/ascension/scene.h Mon Apr 01 18:54:19 2024 +0200 @@ -32,25 +32,18 @@ #include "transform.h" #include "camera.h" -#include +#include typedef struct AscSceneNode AscSceneNode; -typedef struct AscBehaviorNode AscBehaviorNode; typedef void(*asc_scene_free_func)(AscSceneNode*); typedef void(*asc_scene_update_func)(AscSceneNode*); typedef void(*asc_scene_draw_func)(AscSceneNode const*); -struct AscBehaviorNode { - AscSceneNode *parent; - AscBehaviorNode *prev; - AscBehaviorNode *next; - asc_scene_update_func func; -}; - enum AscRenderGroup { ASC_RENDER_GROUP_SPRITE_OPAQUE, - ASC_RENDER_GROUP_SPRITE_BLEND + ASC_RENDER_GROUP_SPRITE_BLEND, + ASC_RENDER_GROUP_COUNT }; struct AscSceneNode { @@ -58,7 +51,7 @@ AscSceneNode *prev; AscSceneNode *next; AscSceneNode *children; - AscBehaviorNode *behaviors; + CxList *behaviors; asc_scene_free_func free_func; asc_scene_update_func update_func; asc_scene_draw_func draw_func; @@ -74,42 +67,19 @@ }; /** - * Place this as first member of a structure that shall be usable as a scene node. + * Place this as first member of a structure that shall be used as a scene node. */ #define extend_asc_scene_node AscSceneNode base -struct asc_render_group_entry { - asc_scene_draw_func draw; - AscSceneNode const *node; -}; - -#define ASC_SCENE_CAMERAS_MAX 4 - -typedef struct AscScene { - AscSceneNode *root; - asc_recti viewport; - AscCamera cameras[ASC_SCENE_CAMERAS_MAX]; - CX_ARRAY_DECLARE(struct asc_render_group_entry, rg_sprites_opaque); - CX_ARRAY_DECLARE(struct asc_render_group_entry, rg_sprites_blended); -} AscScene; - /** - * Initializes the scene using the active window as reference. + * Draws the scene with the specified root node. * - * @param scene the scene to initialize - * @param type determines the type of camera to use + * @param root the root node of the scene graph + * @param viewport the window viewport the scene shall be drawn to + * @param camera the camera to obtain the view and projection matrix from */ __attribute__((__nonnull__)) -void asc_scene_init(AscScene *scene); - -__attribute__((__nonnull__)) -void asc_scene_destroy(AscScene *scene); - -__attribute__((__nonnull__)) -void asc_scene_draw(AscScene *scene); - -__attribute__((__nonnull__)) -void asc_scene_add(AscScene *scene, AscSceneNode *node); +void asc_scene_draw(AscSceneNode *root, asc_recti viewport, AscCamera *camera); /** * Creates an empty node that may serve as a container for other nodes. @@ -120,25 +90,68 @@ */ AscSceneNode *asc_scene_node_empty(void); +/** + * Unlinks the node from its parent and frees the entire subtree. + * + * The free_func of this node and all child nodes is called, starting + * with the leaf nodes and terminating with \p node. + * + * @param node the node to unlink + */ void asc_scene_node_free(AscSceneNode *node); +/** + * Links a node to a (new) parent. + * + * @param parent the (new) parent + * @param node the node to link + */ __attribute__((__nonnull__)) void asc_scene_node_link( AscSceneNode *restrict parent, AscSceneNode *restrict node ); +/** + * Unlinks a node from its parent. + * + * This might be useful to temporarily remove a subtree from a scene. + * To permanently remove the node use asc_scene_node_free(). + * + * @param node the node to unlink + */ __attribute__((__nonnull__)) void asc_scene_node_unlink(AscSceneNode *node); +/** + * Adds a behavior function to the node. + * + * A behavior function MUST NOT be added more than once to the same node. + * This will not be checked. + * + * @param node the node + * @param behavior the behavior function + */ __attribute__((__nonnull__)) -AscBehaviorNode *asc_scene_add_behavior( +void asc_scene_add_behavior( AscSceneNode *node, asc_scene_update_func behavior ); +/** + * Removes a behavior function from the node. + * + * If the behavior function is not attached to this node, this function + * does nothing. + * + * @param node the node + * @param behavior the behavior function + */ __attribute__((__nonnull__)) -void asc_scene_remove_behavior(AscBehaviorNode *node); +void asc_scene_remove_behavior( + AscSceneNode *node, + asc_scene_update_func behavior +); #define asc_node_update(node) \ ((AscSceneNode*)node)->need_graphics_update = true diff -r d3285aed65b3 -r 44457f6cb0a2 src/ascension/window.h --- a/src/ascension/window.h Fri Mar 29 00:03:25 2024 +0100 +++ b/src/ascension/window.h Mon Apr 01 18:54:19 2024 +0200 @@ -52,7 +52,7 @@ asc_vec2i dimensions; bool resized; AscGLContext glctx; - AscScene ui; + AscSceneNode *ui; } AscWindow; #define asc_window_active asc_context.active_window diff -r d3285aed65b3 -r 44457f6cb0a2 src/camera.c --- a/src/camera.c Fri Mar 29 00:03:25 2024 +0100 +++ b/src/camera.c Mon Apr 01 18:54:19 2024 +0200 @@ -27,20 +27,11 @@ #include "ascension/camera.h" -static void asc_camera_update_ortho(AscCamera *camera) { - float left = (float) camera->rect.pos.x; - float right = left + (float) camera->rect.size.width; - float top = (float) camera->rect.pos.y; - float bottom = top + (float) camera->rect.size.height; +void asc_camera_ortho(AscCamera *camera, asc_recti rect) { + asc_mat4f_unit(camera->view); + float left = (float) rect.pos.x; + float right = left + (float) rect.size.width; + float top = (float) rect.pos.y; + float bottom = top + (float) rect.size.height; asc_mat4f_ortho(camera->projection, left, right, bottom, top, -1, 1); } - -void asc_camera_ortho(AscCamera *camera, asc_recti rect) { - asc_mat4f_unit(camera->view); - camera->update = asc_camera_update_ortho; - camera->rect = rect; -} - -void asc_camera_disable(AscCamera *camera) { - camera->update = NULL; -} diff -r d3285aed65b3 -r 44457f6cb0a2 src/context.c --- a/src/context.c Fri Mar 29 00:03:25 2024 +0100 +++ b/src/context.c Mon Apr 01 18:54:19 2024 +0200 @@ -94,8 +94,6 @@ asc_vec2i dimensions = (asc_vec2i) {width, height}; asc_context.windows[i].resized = true; asc_context.windows[i].dimensions = dimensions; - asc_context.windows[i].ui.viewport.size = dimensions; - asc_context.windows[i].ui.cameras[0].rect.size = dimensions; return; } } diff -r d3285aed65b3 -r 44457f6cb0a2 src/scene.c --- a/src/scene.c Fri Mar 29 00:03:25 2024 +0100 +++ b/src/scene.c Mon Apr 01 18:54:19 2024 +0200 @@ -30,6 +30,8 @@ #include "ascension/context.h" +#include +#include #include #include @@ -56,53 +58,26 @@ ); } -void asc_scene_init(AscScene *scene) { - if (scene->root != NULL) { - asc_error("Scene is already initialized."); - return; +struct asc_render_group_entry { + asc_scene_draw_func draw; + AscSceneNode const *node; +}; + +#define asc_draw_render_group(iter) \ + cx_foreach(struct asc_render_group_entry*, entry, iter) { \ + entry->draw(entry->node); \ } - // zero everything, first - memset(scene, 0, sizeof(AscScene)); - - // default viewport is the entire viewport of the active window - scene->viewport.size = asc_context.active_window->dimensions; - - // create the root node - scene->root = asc_scene_node_empty(); - - // initialize the render groups - cx_array_initialize(scene->rg_sprites_opaque, 8); - cx_array_initialize(scene->rg_sprites_blended, 8); -} - -void asc_scene_destroy(AscScene *scene) { - asc_scene_node_free(scene->root); -} - -void asc_scene_add(AscScene *scene, AscSceneNode *node) { - asc_scene_node_link(scene->root, node); - asc_node_update(node); -} - -#define asc_scene_draw_render_group(rg) \ - cx_for_n(i, rg##_size) { \ - rg[i].draw(rg[i].node); \ - } (void)0 - -#define asc_scene_draw_render_group_reversed(rg) \ - for(size_t i = rg##_size ; i > 0 ; i--) { \ - rg[i-1].draw(rg[i-1].node); \ - } (void)0 - -void asc_scene_draw(AscScene *scene) { - // reset render groups - // TODO: avoid recalculating the groups, if possible - scene->rg_sprites_opaque_size = 0; - scene->rg_sprites_blended_size = 0; +void asc_scene_draw(AscSceneNode *root, asc_recti viewport, AscCamera *camera) { + // create render groups + CxList *render_group[ASC_RENDER_GROUP_COUNT]; + cx_for_n(i, ASC_RENDER_GROUP_COUNT) { + render_group[i] = cxArrayListCreateSimple( + sizeof(struct asc_render_group_entry), 32); + } // skip the root node deliberately, we know it's just the container - CxTreeVisitor iter = asc_scene_node_visitor(scene->root); + CxTreeVisitor iter = asc_scene_node_visitor(root); cxIteratorNext(iter); // update the children and add them to the render groups @@ -110,12 +85,16 @@ node->depth = iter.depth; // execute behaviors, first - AscBehaviorNode *behavior = node->behaviors; - while (behavior) { - behavior->func(node); - behavior = behavior->next; + if (node->behaviors != NULL) { + CxIterator behavior_iter = cxListIterator(node->behaviors); + cx_foreach(asc_scene_update_func, behavior, behavior_iter) { + behavior(node); + } } + // TODO: implement culling + // TODO: implement a hidden flag (requires UCX tree-continue function) + // check if geometry needs update if (node->need_graphics_update) { assert(node->update_func != NULL); @@ -142,56 +121,51 @@ struct asc_render_group_entry entry = { node->draw_func, node }; - switch (node->render_group) { - case ASC_RENDER_GROUP_SPRITE_OPAQUE: - cx_array_simple_add(scene->rg_sprites_opaque, entry); - break; - case ASC_RENDER_GROUP_SPRITE_BLEND: - cx_array_simple_add(scene->rg_sprites_blended, entry); - break; - } + cxListAdd(render_group[node->render_group], &entry); } } // set the viewport (in OpenGL we need to invert the Y axis) glViewport( - scene->viewport.pos.x, - -scene->viewport.pos.y, - scene->viewport.size.width, - scene->viewport.size.height + viewport.pos.x, + -viewport.pos.y, + viewport.size.width, + viewport.size.height ); - glClear(GL_COLOR_BUFFER_BIT); - // ----------------------------------------- - // process the render groups for each camera - // ----------------------------------------- + // ------------------------- + // process the render groups + // ------------------------- AscShaderProgram *shader; - cx_for_n(cam_id, ASC_SCENE_CAMERAS_MAX) { - // update camera parameters, first - AscCamera *camera = &scene->cameras[cam_id]; - if (camera->update == NULL) continue; - camera->update(camera); + CxIterator render_iter; - // 2D Elements - // =========== - glEnable(GL_DEPTH_TEST); - glClear(GL_DEPTH_BUFFER_BIT); + // 2D Elements + // =========== + glEnable(GL_DEPTH_TEST); + glClear(GL_DEPTH_BUFFER_BIT); - // Sprites - // ------- - // TODO: see if we can really always ignore the view matrix - shader = &asc_context.active_window->glctx.shader.sprite.base; - glUseProgram(shader->id); - glUniformMatrix4fv(shader->projection, 1, - GL_FALSE, camera->projection); + // Sprites + // ------- + // TODO: implement view matrix for 2D worlds + shader = &asc_context.active_window->glctx.shader.sprite.base; + glUseProgram(shader->id); + glUniformMatrix4fv(shader->projection, 1, + GL_FALSE, camera->projection); - // render opaque sprites from front to back - glDisable(GL_BLEND); - asc_scene_draw_render_group_reversed(scene->rg_sprites_opaque); - // render sprites with alpha value from back to front - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - asc_scene_draw_render_group(scene->rg_sprites_blended); + // render opaque sprites from front to back + glDisable(GL_BLEND); + render_iter = cxListBackwardsIterator(render_group[ASC_RENDER_GROUP_SPRITE_OPAQUE]); + asc_draw_render_group(render_iter); + + // render sprites with alpha value from back to front + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + render_iter = cxListIterator(render_group[ASC_RENDER_GROUP_SPRITE_BLEND]); + asc_draw_render_group(render_iter); + + // destroy render groups + cx_for_n(i, ASC_RENDER_GROUP_COUNT) { + cxListDestroy(render_group[i]); } } @@ -213,6 +187,9 @@ CxTreeIterator iter = asc_scene_node_iterator(node, true); cx_foreach(AscSceneNode*, child, iter) { if (!iter.exiting) continue; + if (child->behaviors != NULL) { + cxListDestroy(child->behaviors); + } if (child->free_func != NULL) { child->free_func(child); } else { @@ -241,28 +218,23 @@ ); } -AscBehaviorNode *asc_scene_add_behavior(AscSceneNode *node, asc_scene_update_func behavior) { - AscBehaviorNode *behavior_node = calloc(1, sizeof(AscBehaviorNode)); - behavior_node->func = behavior; - cx_tree_link( - node, - behavior_node, - offsetof(AscBehaviorNode, parent), - offsetof(AscSceneNode, behaviors), - offsetof(AscBehaviorNode, prev), - offsetof(AscBehaviorNode, next) - ); - return behavior_node; +void asc_scene_add_behavior( + AscSceneNode *node, + asc_scene_update_func behavior +) { + if (node->behaviors == NULL) { + node->behaviors = cxLinkedListCreateSimple(CX_STORE_POINTERS); + } + cxListAdd(node->behaviors, behavior); } -void asc_scene_remove_behavior(AscBehaviorNode *node) { - cx_tree_unlink( - node, - offsetof(AscBehaviorNode, parent), - offsetof(AscSceneNode, behaviors), - offsetof(AscBehaviorNode, prev), - offsetof(AscBehaviorNode, next) - ); +void asc_scene_remove_behavior( + AscSceneNode *node, + asc_scene_update_func behavior +) { + if (node->behaviors != NULL) { + cxListFindRemove(node->behaviors, behavior); + } } void asc_update_transform(AscSceneNode *node) { diff -r d3285aed65b3 -r 44457f6cb0a2 src/window.c --- a/src/window.c Fri Mar 29 00:03:25 2024 +0100 +++ b/src/window.c Mon Apr 01 18:54:19 2024 +0200 @@ -43,13 +43,6 @@ settings->title = "Ascended Window"; } -static void asc_window_init_scenes(AscWindow *window) { - asc_scene_init(&window->ui); - asc_camera_ortho(&window->ui.cameras[0], (asc_recti){ - 0, 0, window->dimensions - }); -} - AscWindow *asc_window_initialize(unsigned int index, AscWindowSettings const *settings) { if (index >= ASC_MAX_WINDOWS) { asc_error("Maximum number of windows exceeded."); @@ -58,9 +51,13 @@ AscWindow *window = &asc_context.windows[index]; if (window->id > 0) { asc_error("Cannot create window - slot already occupied."); - asc_dprintf("Tried to create window with index %u", index); + asc_dprintf("Tried to create window with index %u twice", index); return NULL; } + if (window->ui != NULL) { + asc_dprintf("Window with index %u has a dangling UI pointer", index); + asc_scene_node_free(window->ui); + } Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN; flags |= settings->fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_RESIZABLE; @@ -86,9 +83,9 @@ window->resized = true; // count initial sizing as resize if (asc_gl_context_initialize(&window->glctx, window->window, &settings->glsettings)) { + window->ui = asc_scene_node_empty(); asc_dprintf("Window %u initialized", window->id); asc_context.active_window = window; - asc_window_init_scenes(window); return window; } else { asc_dprintf("Creating GL context failed for window %u", window->id); @@ -110,7 +107,8 @@ } // destroy all scenes - asc_scene_destroy(&window->ui); + asc_scene_node_free(window->ui); + window->ui = NULL; // release context related data asc_gl_context_destroy(&window->glctx); @@ -136,8 +134,17 @@ asc_window_activate(window); } + // Clear the color buffer for the window frame + int window_width = window->dimensions.width; + int window_height = window->dimensions.height; + glViewport(0, 0, window_width, window_height); + glClear(GL_COLOR_BUFFER_BIT); + asc_recti viewport = {0, 0, window_width, window_height}; + // Draw the UI - asc_scene_draw(&window->ui); + AscCamera ui_camera; + asc_camera_ortho(&ui_camera, viewport); + asc_scene_draw(window->ui, viewport, &ui_camera); // Swap Buffers SDL_GL_SwapWindow(window->window); diff -r d3285aed65b3 -r 44457f6cb0a2 test/snake.c --- a/test/snake.c Fri Mar 29 00:03:25 2024 +0100 +++ b/test/snake.c Mon Apr 01 18:54:19 2024 +0200 @@ -50,7 +50,7 @@ asc_ink_rgb(255, 0, 0); AscSceneNode* node = asc_text(10, 10, "XXXXXXX FPS"); asc_scene_add_behavior(node, update_fps_counter); - asc_scene_add(&asc_window_active->ui, node); + asc_scene_node_link(asc_window_active->ui, node); } static void update_score_counter(AscSceneNode *node) { @@ -71,7 +71,7 @@ asc_ink_rgb(0, 255, 0); AscSceneNode* node = asc_text(0, 0, "Score: 0"); asc_scene_add_behavior(node, update_score_counter); - asc_scene_add(&asc_window_active->ui, node); + asc_scene_node_link(asc_window_active->ui, node); } int main(int argc, char** argv) {