Thu, 24 Apr 2025 19:53:40 +0200
in preparation of more scenes, bring back AscScene struct
--- a/src/Makefile Thu Apr 24 18:41:42 2025 +0200 +++ b/src/Makefile Thu Apr 24 19:53:40 2025 +0200 @@ -27,11 +27,11 @@ BUILD_DIR=../build/lib -SRC = context.c glcontext.c error.c filesystem.c window.c \ +SRC = context.c glcontext.c filesystem.c window.c \ shader.c \ mesh.c primitives.c texture.c \ sprite.c \ - camera.c scene.c \ + camera.c scene.c scene_node.c \ font.c text.c OBJ = $(SRC:%.c=$(BUILD_DIR)/%.o) @@ -54,16 +54,7 @@ ascension/primitives.h ascension/mesh.h ascension/shader.h \ ascension/texture.h ascension/scene.h ascension/scene_node.h \ ascension/transform.h ascension/camera.h ascension/input.h \ - ascension/ui/font.h ascension/error.h ascension/utils.h - @echo "Compiling $<" - $(CC) -o $@ $(CFLAGS) -c $< - -$(BUILD_DIR)/error.o: error.c ascension/context.h ascension/datatypes.h \ - ascension/window.h ascension/glcontext.h ascension/primitives.h \ - ascension/mesh.h ascension/shader.h ascension/texture.h \ - ascension/scene.h ascension/scene_node.h ascension/transform.h \ - ascension/camera.h ascension/input.h ascension/ui/font.h \ - ascension/error.h ascension/utils.h + ascension/ui/font.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $< @@ -76,26 +67,34 @@ ascension/mesh.h ascension/shader.h ascension/texture.h \ ascension/scene.h ascension/scene_node.h ascension/transform.h \ ascension/camera.h ascension/input.h ascension/ui/font.h \ - ascension/error.h ascension/filesystem.h ascension/ui/font.h + ascension/filesystem.h ascension/ui/font.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $< $(BUILD_DIR)/glcontext.o: glcontext.c ascension/glcontext.h \ ascension/primitives.h ascension/mesh.h ascension/shader.h \ - ascension/texture.h ascension/error.h + ascension/texture.h ascension/context.h ascension/datatypes.h \ + ascension/window.h ascension/glcontext.h ascension/scene.h \ + ascension/scene_node.h ascension/transform.h ascension/camera.h \ + ascension/input.h ascension/ui/font.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $< -$(BUILD_DIR)/mesh.o: mesh.c ascension/mesh.h ascension/error.h +$(BUILD_DIR)/mesh.o: mesh.c ascension/mesh.h ascension/context.h \ + ascension/datatypes.h ascension/window.h ascension/glcontext.h \ + ascension/primitives.h ascension/mesh.h ascension/shader.h \ + ascension/texture.h ascension/scene.h ascension/scene_node.h \ + ascension/transform.h ascension/camera.h ascension/input.h \ + ascension/ui/font.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/glcontext.h \ - ascension/primitives.h ascension/shader.h ascension/texture.h \ - ascension/scene.h ascension/scene_node.h ascension/transform.h \ - ascension/camera.h ascension/input.h ascension/ui/font.h + ascension/mesh.h ascension/context.h ascension/datatypes.h \ + ascension/window.h ascension/glcontext.h ascension/primitives.h \ + ascension/shader.h ascension/texture.h ascension/scene.h \ + ascension/scene_node.h ascension/transform.h ascension/camera.h \ + ascension/input.h ascension/ui/font.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $< @@ -104,9 +103,17 @@ ascension/context.h ascension/window.h ascension/glcontext.h \ ascension/primitives.h ascension/mesh.h ascension/shader.h \ ascension/texture.h ascension/scene.h ascension/input.h \ - ascension/ui/font.h ascension/utils.h ascension/2d.h \ - ascension/2d/sprite.h ascension/2d/../scene_node.h \ - ascension/2d/../texture.h + ascension/ui/font.h ascension/2d.h ascension/2d/sprite.h \ + ascension/2d/../scene_node.h ascension/2d/../texture.h + @echo "Compiling $<" + $(CC) -o $@ $(CFLAGS) -c $< + +$(BUILD_DIR)/scene_node.o: scene_node.c ascension/scene_node.h \ + ascension/datatypes.h ascension/transform.h ascension/context.h \ + ascension/window.h ascension/glcontext.h ascension/primitives.h \ + ascension/mesh.h ascension/shader.h ascension/texture.h \ + ascension/scene.h ascension/scene_node.h ascension/camera.h \ + ascension/input.h ascension/ui/font.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $< @@ -115,7 +122,7 @@ ascension/mesh.h ascension/shader.h ascension/texture.h \ ascension/scene.h ascension/scene_node.h ascension/transform.h \ ascension/camera.h ascension/input.h ascension/ui/font.h \ - ascension/shader.h ascension/error.h ascension/filesystem.h + ascension/shader.h ascension/filesystem.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $< @@ -135,9 +142,8 @@ ascension/mesh.h ascension/shader.h ascension/texture.h \ ascension/scene.h ascension/scene_node.h ascension/transform.h \ ascension/camera.h ascension/input.h ascension/ui/font.h \ - ascension/error.h ascension/ui/text.h ascension/ui/font.h \ - ascension/ui/../2d/sprite.h ascension/ui/../2d/../scene_node.h \ - ascension/ui/../2d/../texture.h ascension/ui/../utils.h + ascension/ui/text.h ascension/ui/font.h ascension/ui/../2d/sprite.h \ + ascension/ui/../2d/../scene_node.h ascension/ui/../2d/../texture.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $< @@ -146,8 +152,7 @@ ascension/primitives.h ascension/mesh.h ascension/shader.h \ ascension/texture.h ascension/scene.h ascension/scene_node.h \ ascension/transform.h ascension/camera.h ascension/input.h \ - ascension/ui/font.h ascension/texture.h ascension/error.h \ - ascension/filesystem.h + ascension/ui/font.h ascension/texture.h ascension/filesystem.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $< @@ -156,7 +161,7 @@ ascension/shader.h ascension/texture.h ascension/scene.h \ ascension/scene_node.h ascension/transform.h ascension/camera.h \ ascension/context.h ascension/window.h ascension/input.h \ - ascension/ui/font.h ascension/error.h ascension/utils.h + ascension/ui/font.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $<
--- a/src/ascension/context.h Thu Apr 24 18:41:42 2025 +0200 +++ b/src/ascension/context.h Thu Apr 24 19:53:40 2025 +0200 @@ -36,6 +36,8 @@ #include <cx/buffer.h> #include <cx/string.h> +#include <stdio.h> + /** The flag for the overall initialized state. */ #define ASC_FLAG_INITILIZED 0x01u @@ -45,6 +47,34 @@ /** Flag is set when SDL wants to quit the application. */ #define ASC_FLAG_QUIT 0x80000000u + +#define asc_test_flag(reg, flag) ((reg & flag) == flag) +#define asc_test_flag_masked(reg, mask, flag) ((reg & mask) == flag) +#define asc_clear_flag(reg, flag) (reg &= ~(flag)) +#define asc_set_flag(reg, flag) (reg |= flag) +#define asc_set_flag_masked(reg, mask, flag) (reg = (reg & ~(mask)) | flag) + + + +void asc_error_impl(const char* file, unsigned line, char const* fmt, ...); +#define asc_error(...) asc_error_impl(__FILE__, __LINE__, __VA_ARGS__) + +void asc_error_gl(unsigned code, const char *message); + +int asc_error_catch_all_gl(void); + +bool asc_has_error(void); +char const* asc_get_error(void); +void asc_clear_error(void); + +#define asc_wprintf(...) printf("[WARNING %s %u] ", __FILE__, __LINE__); printf(__VA_ARGS__); putchar('\n') +#ifdef NDEBUG +#define asc_dprintf(...) +#else +#define asc_dprintf(...) printf("[DEBUG %s %u] ", __FILE__, __LINE__); printf(__VA_ARGS__); putchar('\n') +#endif + + /** * The global ascension context. */
--- a/src/ascension/core.h Thu Apr 24 18:41:42 2025 +0200 +++ b/src/ascension/core.h Thu Apr 24 19:53:40 2025 +0200 @@ -28,7 +28,6 @@ #ifndef ASCENSION_CORE_H #define ASCENSION_CORE_H -#include "error.h" #include "context.h" #include "shader.h" #include "scene.h"
--- a/src/ascension/error.h Thu Apr 24 18:41:42 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -/* - * 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_ERROR_H -#define ASCENSION_ERROR_H - -#include <cx/string.h> -#include <stdio.h> - -void asc_error_impl(const char* file, unsigned line, char const* fmt, ...); -#define asc_error(...) asc_error_impl(__FILE__, __LINE__, __VA_ARGS__) - -void asc_error_gl(unsigned code, cxstring message); - -int asc_error_catch_all_gl(void); - -bool asc_has_error(void); -char const* asc_get_error(void); -void asc_clear_error(void); - -#define asc_wprintf(...) printf("[WARNING %s %u] ", __FILE__, __LINE__); printf(__VA_ARGS__); putchar('\n') -#ifdef NDEBUG -#define asc_dprintf(...) -#else -#define asc_dprintf(...) printf("[DEBUG %s %u] ", __FILE__, __LINE__); printf(__VA_ARGS__); putchar('\n') -#endif - -#endif /* ASCENSION_ERROR_H */ -
--- a/src/ascension/scene.h Thu Apr 24 18:41:42 2025 +0200 +++ b/src/ascension/scene.h Thu Apr 24 19:53:40 2025 +0200 @@ -32,136 +32,41 @@ #include "scene_node.h" #include "camera.h" +typedef struct { + // TODO: should a scene always have an own camera? + // yes: that would enable efficient frustum culling that does not need to be recalculated + // no: if the same scene needs to be drawn from different cameras, we must duplicate the scene graph + AscCamera camera; + AscSceneNode *root; +} AscScene; + +/** + * Initializes a scene graph. + * + * @param scene the scene graph + */ +__attribute__((__nonnull__)) +void asc_scene_init(AscScene *scene); + +/** + * Destroys a scene graph. + * + * @param scene the scene graph + */ +__attribute__((__nonnull__)) +void asc_scene_destroy(AscScene *scene); + /** * Draws the scene with the specified root node. * - * @param root the root node of the scene graph + * @param scene 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_draw(AscSceneNode *root, asc_recti viewport, const AscCamera *camera); - -/** - * 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); - -/** - * 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__)) -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( - AscSceneNode *node, - asc_scene_update_func behavior -); - -__attribute__((__nonnull__)) -void asc_node_update(AscSceneNode *node); +void asc_scene_draw(AscScene *scene, asc_recti viewport); __attribute__((__nonnull__)) -void asc_node_update_transform(AscSceneNode *node); - - -__attribute__((__nonnull__)) static inline -void asc_set_position(AscSceneNode *node, float x, float y, float z) { - node->position.x = x; - node->position.y = y; - node->position.z = z; - asc_node_update_transform(node); -} - -__attribute__((__nonnull__)) static inline -void asc_set_position2d(AscSceneNode *node, int x, int y) { - node->position.x = (float)x; - node->position.y = (float)y; - node->position.z = 0.f; - asc_node_update_transform(node); -} - -__attribute__((__nonnull__)) static inline -asc_vec2i asc_get_position2d(AscSceneNode *node) { - return (asc_vec2i) {(int) node->position.x, (int) node->position.y}; -} - -__attribute__((__nonnull__)) static inline -void asc_set_scale(AscSceneNode *node, float width, float height, float depth) { - node->scale.width = width; - node->scale.height = height; - node->scale.depth = depth; - asc_node_update_transform(node); -} - -__attribute__((__nonnull__)) static inline -void asc_set_scale2d(AscSceneNode *node, int width, int height) { - node->scale.width = (float)width; - node->scale.height = (float)height; - node->scale.depth = 1.f; - asc_node_update_transform(node); -} - -__attribute__((__nonnull__)) static inline -asc_vec2i asc_get_scale2d(AscSceneNode *node) { - return (asc_vec2i) {(int) node->scale.width, (int) node->scale.height}; -} +void asc_scene_add_node(AscScene *scene, AscSceneNode *node); #endif // ASCENSION_SCENE_H
--- a/src/ascension/scene_node.h Thu Apr 24 18:41:42 2025 +0200 +++ b/src/ascension/scene_node.h Thu Apr 24 19:53:40 2025 +0200 @@ -39,6 +39,7 @@ typedef void(*asc_scene_update_func)(AscSceneNode*); enum AscRenderGroup { + ASC_RENDER_GROUP_NONE = -1, ASC_RENDER_GROUP_SPRITE_OPAQUE, ASC_RENDER_GROUP_SPRITE_BLEND, ASC_RENDER_GROUP_COUNT @@ -94,4 +95,126 @@ #define ASC_SCENE_NODE_HIDDEN 0x80000000 + +/** + * 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); + +/** + * 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__)) +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( + AscSceneNode *node, + asc_scene_update_func behavior +); + +__attribute__((__nonnull__)) +void asc_node_update(AscSceneNode *node); + +__attribute__((__nonnull__)) +void asc_node_update_transform(AscSceneNode *node); + + +__attribute__((__nonnull__)) static inline +void asc_set_position(AscSceneNode *node, float x, float y, float z) { + node->position.x = x; + node->position.y = y; + node->position.z = z; + asc_node_update_transform(node); +} + +__attribute__((__nonnull__)) static inline +void asc_set_position2d(AscSceneNode *node, int x, int y) { + node->position.x = (float)x; + node->position.y = (float)y; + node->position.z = 0.f; + asc_node_update_transform(node); +} + +__attribute__((__nonnull__)) static inline +asc_vec2i asc_get_position2d(AscSceneNode *node) { + return (asc_vec2i) {(int) node->position.x, (int) node->position.y}; +} + +__attribute__((__nonnull__)) static inline +void asc_set_scale(AscSceneNode *node, float width, float height, float depth) { + node->scale.width = width; + node->scale.height = height; + node->scale.depth = depth; + asc_node_update_transform(node); +} + +__attribute__((__nonnull__)) static inline +void asc_set_scale2d(AscSceneNode *node, int width, int height) { + node->scale.width = (float)width; + node->scale.height = (float)height; + node->scale.depth = 1.f; + asc_node_update_transform(node); +} + +__attribute__((__nonnull__)) static inline +asc_vec2i asc_get_scale2d(AscSceneNode *node) { + return (asc_vec2i) {(int) node->scale.width, (int) node->scale.height}; +} + #endif
--- a/src/ascension/ui.h Thu Apr 24 18:41:42 2025 +0200 +++ b/src/ascension/ui.h Thu Apr 24 19:53:40 2025 +0200 @@ -30,8 +30,5 @@ #include "ui/text.h" -#define asc_add_ui_node(node) \ - asc_scene_node_link(asc_active_window->ui, node) - #endif /* ASCENSION_UI_H */
--- a/src/ascension/ui/text.h Thu Apr 24 18:41:42 2025 +0200 +++ b/src/ascension/ui/text.h Thu Apr 24 19:53:40 2025 +0200 @@ -30,7 +30,6 @@ #include "font.h" #include "../2d/sprite.h" -#include "../utils.h" #include <cx/string.h>
--- a/src/ascension/utils.h Thu Apr 24 18:41:42 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -/* - * 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_UTILS_H -#define ASCENSION_UTILS_H - -#include <stdbool.h> - -#define asc_test_flag(reg, flag) ((reg & flag) == flag) -#define asc_test_flag_masked(reg, mask, flag) ((reg & mask) == flag) -#define asc_clear_flag(reg, flag) (reg &= ~(flag)) -#define asc_set_flag(reg, flag) (reg |= flag) -#define asc_set_flag_masked(reg, mask, flag) (reg = (reg & ~(mask)) | flag) - -#endif /* ASCENSION_UTILS_H */ -
--- a/src/ascension/window.h Thu Apr 24 18:41:42 2025 +0200 +++ b/src/ascension/window.h Thu Apr 24 19:53:40 2025 +0200 @@ -53,7 +53,7 @@ SDL_Window* window; asc_vec2i dimensions; AscGLContext glctx; - AscSceneNode *ui; + AscScene ui; } AscWindow; /** @@ -122,5 +122,9 @@ */ unsigned int asc_window_index(Uint32 id); + +__attribute__((__nonnull__)) +void asc_add_ui_node(AscSceneNode *node); + #endif /* ASCENSION_WINDOW_H */
--- a/src/context.c Thu Apr 24 18:41:42 2025 +0200 +++ b/src/context.c Thu Apr 24 19:53:40 2025 +0200 @@ -26,17 +26,96 @@ */ #include "ascension/context.h" -#include "ascension/error.h" -#include "ascension/utils.h" #include <SDL2/SDL.h> #include <SDL2/SDL_ttf.h> #include <SDL2/SDL_image.h> +#include <GL/gl.h> + +#include <cx/printf.h> + #include <time.h> AscContext asc_context; +void asc_error_impl(const char* file, unsigned line, char const* fmt, ...) { + asc_set_flag(asc_context.flags, ASC_FLAG_HAS_ERROR); + + // write to error buffer + va_list args; + va_start(args, fmt); + CxBuffer* buf = &asc_context.error_buffer; + size_t bufpos = buf->pos; + int written = cx_vfprintf(buf, cxBufferWriteFunc, fmt, args); + cxBufferPut(buf, '\n'); + va_end(args); + + // also print to stderr + // avoid double-formatting, get it directly from the buffer + fprintf(stderr, "[ERROR %s %u] %.*s\n", + file, line, written, buf->space+bufpos); +} + +bool asc_has_error(void) { + return asc_test_flag(asc_context.flags, ASC_FLAG_HAS_ERROR); +} + +char const* asc_get_error(void) { + // we zero-terminate the current buffer contents before providing them + cxBufferPut(&asc_context.error_buffer, 0); + --asc_context.error_buffer.pos; + --asc_context.error_buffer.size; + return asc_context.error_buffer.space; +} + +void asc_clear_error(void) { + cxBufferClear(&asc_context.error_buffer); + asc_clear_flag(asc_context.flags, ASC_FLAG_HAS_ERROR); +} + +void asc_error_gl(unsigned code, const char *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"; + } + asc_error("%s\nGL Error: %s", message, glerr); +} + +int asc_error_catch_all_gl(void) { + GLenum error; + int ret = 0; + while ((error = glGetError()) != GL_NO_ERROR) { + asc_error_gl(error, "Uncaught"); + ret = 1; + } + return ret; +} + static uint64_t asc_nanos(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);
--- a/src/error.c Thu Apr 24 18:41:42 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,111 +0,0 @@ -/* - * 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/context.h" -#include "ascension/error.h" -#include "ascension/utils.h" - -#include <cx/buffer.h> -#include <cx/printf.h> -#include <GL/gl.h> - -void asc_error_impl(const char* file, unsigned line, char const* fmt, ...) { - asc_set_flag(asc_context.flags, ASC_FLAG_HAS_ERROR); - - // write to error buffer - va_list args; - va_start(args, fmt); - CxBuffer* buf = &asc_context.error_buffer; - size_t bufpos = buf->pos; - int written = cx_vfprintf(buf, cxBufferWriteFunc, fmt, args); - cxBufferPut(buf, '\n'); - va_end(args); - - // also print to stderr - // avoid double-formatting, get it directly from the buffer - fprintf(stderr, "[ERROR %s %u] %.*s\n", - file, line, written, buf->space+bufpos); -} - -bool asc_has_error(void) { - return asc_test_flag(asc_context.flags, ASC_FLAG_HAS_ERROR); -} - -char const* asc_get_error(void) { - // we zero-terminate the current buffer contents before providing them - cxBufferPut(&asc_context.error_buffer, 0); - --asc_context.error_buffer.pos; - --asc_context.error_buffer.size; - return asc_context.error_buffer.space; -} - -void asc_clear_error(void) { - 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"; - } - asc_error("%s\nGL Error: %s", message, glerr); -} - -int asc_error_catch_all_gl(void) { - GLenum error; - int ret = 0; - while ((error = glGetError()) != GL_NO_ERROR) { - asc_error_gl(error, CX_STR("Uncaught")); - ret = 1; - } - return ret; -}
--- a/src/font.c Thu Apr 24 18:41:42 2025 +0200 +++ b/src/font.c Thu Apr 24 19:53:40 2025 +0200 @@ -26,7 +26,6 @@ */ #include "ascension/context.h" -#include "ascension/error.h" #include "ascension/filesystem.h" #include "ascension/ui/font.h"
--- a/src/glcontext.c Thu Apr 24 18:41:42 2025 +0200 +++ b/src/glcontext.c Thu Apr 24 19:53:40 2025 +0200 @@ -26,7 +26,7 @@ */ #include "ascension/glcontext.h" -#include "ascension/error.h" +#include "ascension/context.h" #include <cx/printf.h>
--- a/src/mesh.c Thu Apr 24 18:41:42 2025 +0200 +++ b/src/mesh.c Thu Apr 24 19:53:40 2025 +0200 @@ -26,7 +26,7 @@ */ #include "ascension/mesh.h" -#include "ascension/error.h" +#include "ascension/context.h" #include <GL/glew.h> #include <assert.h>
--- a/src/primitives.c Thu Apr 24 18:41:42 2025 +0200 +++ b/src/primitives.c Thu Apr 24 19:53:40 2025 +0200 @@ -26,7 +26,6 @@ */ #include "ascension/primitives.h" -#include "ascension/error.h" #include "ascension/context.h" #include <GL/glew.h>
--- a/src/scene.c Thu Apr 24 18:41:42 2025 +0200 +++ b/src/scene.c Thu Apr 24 19:53:40 2025 +0200 @@ -28,46 +28,41 @@ #include "ascension/scene.h" #include "ascension/context.h" -#include "ascension/utils.h" #include "ascension/2d.h" +#include <cx/tree.h> #include <cx/linked_list.h> #include <cx/array_list.h> -#include <cx/tree.h> #include <cx/utils.h> #include <GL/glew.h> #include <assert.h> -static CxTreeIterator asc_scene_node_iterator( - AscSceneNode *node, - bool visit_on_exit -) { - return cx_tree_iterator( - node, visit_on_exit, - offsetof(AscSceneNode, children), - offsetof(AscSceneNode, next) - ); +void asc_scene_init(AscScene *scene) { + asc_dprintf("Initialized scene %"PRIxPTR, (uintptr_t) scene); + // TODO: how should we initialize the camera? + scene->root = asc_scene_node_empty(); } -static CxTreeVisitor asc_scene_node_visitor(AscSceneNode *node) { - return cx_tree_visitor(node, - offsetof(AscSceneNode, children), - offsetof(AscSceneNode, next) - ); +void asc_scene_destroy(AscScene *scene) { + asc_dprintf("Destroyed scene %"PRIxPTR, (uintptr_t) scene); + asc_scene_node_free(scene->root); } -void asc_scene_draw(AscSceneNode *root, asc_recti viewport, const AscCamera *camera) { +void asc_scene_draw(AscScene *scene, asc_recti viewport) { // create render groups CxList *render_group[ASC_RENDER_GROUP_COUNT]; cx_for_n(i, ASC_RENDER_GROUP_COUNT) { render_group[i] = cxArrayListCreateSimple(CX_STORE_POINTERS, 32); } - // skip the root node deliberately, we know it's just the container - CxTreeVisitor iter = asc_scene_node_visitor(root); + // skip the root node deliberately; we know it's just the container + CxTreeVisitor iter = cx_tree_visitor(scene->root, + offsetof(AscSceneNode, children), + offsetof(AscSceneNode, next) + ); cxIteratorNext(iter); // update the children and add them to the render groups @@ -116,7 +111,9 @@ } // add to render group - cxListAdd(render_group[node->render_group], node); + if (node->render_group >= 0) { + cxListAdd(render_group[node->render_group], node); + } } // set the viewport (in OpenGL we need to invert the Y axis) @@ -145,9 +142,9 @@ AscShaderProgram *shader = &ASC_SHADER_SPRITE->program; glUseProgram(shader->id); glUniformMatrix4fv(shader->projection, 1, - GL_FALSE, camera->projection); + GL_FALSE, scene->camera.projection); glUniformMatrix4fv(shader->view, 1, - GL_FALSE, camera->view); + GL_FALSE, scene->camera.view); // render opaque sprites from front to back glDisable(GL_BLEND); @@ -171,92 +168,10 @@ } } -AscSceneNode *asc_scene_node_empty(void) { - AscSceneNode *node = calloc(1, sizeof(AscSceneNode)); - node->free_func = (asc_scene_free_func) free; - node->scale.x = node->scale.y = node->scale.z = 1; - asc_transform_identity(node->transform); - asc_transform_identity(node->world_transform); - return node; -} - -void asc_scene_node_free(AscSceneNode *node) { - if (node == NULL) return; - - // remove this node from its parent - asc_scene_node_unlink(node); - - // free the entire subtree - CxTreeIterator iter = asc_scene_node_iterator(node, true); - cx_foreach(AscSceneNode*, child, iter) { - if (!iter.exiting) continue; - if (child->behaviors != NULL) { - cxListFree(child->behaviors); - } - if (child->free_func != NULL) { - child->free_func(child); - } else { - free(child); - } - } -} - -void asc_scene_node_link(AscSceneNode * restrict parent, AscSceneNode * restrict node) { - cx_tree_link( - parent, node, - offsetof(AscSceneNode, parent), - offsetof(AscSceneNode, children), - offsetof(AscSceneNode, last_child), - offsetof(AscSceneNode, prev), - offsetof(AscSceneNode, next) - ); +void asc_scene_add_node(AscScene *scene, AscSceneNode *node) { + asc_scene_node_link(scene->root, node); } -void asc_scene_node_unlink(AscSceneNode *node) { - cx_tree_unlink( - node, - offsetof(AscSceneNode, parent), - offsetof(AscSceneNode, children), - offsetof(AscSceneNode, last_child), - offsetof(AscSceneNode, prev), - offsetof(AscSceneNode, next) - ); -} - -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_node(AscSceneNode *node) { + asc_scene_node_unlink(node); } - -void asc_scene_remove_behavior( - AscSceneNode *node, - asc_scene_update_func behavior -) { - if (node->behaviors != NULL) { - cxListFindRemove(node->behaviors, behavior); - } -} - -void asc_node_update(AscSceneNode *node) { - asc_set_flag(node->flags, ASC_SCENE_NODE_UPDATE_GRAPHICS); -} - -void asc_node_update_transform(AscSceneNode *node) { - // fast skip if node is already marked - if (asc_test_flag(node->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM)) { - return; - } - - CxTreeIterator iter = asc_scene_node_iterator(node, false); - cx_foreach(AscSceneNode*, n, iter) { - if (asc_test_flag(n->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM)) { - cxTreeIteratorContinue(iter); - } - asc_set_flag(n->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM); - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/scene_node.c Thu Apr 24 19:53:40 2025 +0200 @@ -0,0 +1,134 @@ +/* +* 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/scene_node.h" +#include "ascension/context.h" + +#include <cx/tree.h> +#include <cx/linked_list.h> + +static CxTreeIterator asc_scene_node_iterator( + AscSceneNode *node, + bool visit_on_exit +) { + return cx_tree_iterator( + node, visit_on_exit, + offsetof(AscSceneNode, children), + offsetof(AscSceneNode, next) + ); +} + +AscSceneNode *asc_scene_node_empty(void) { + AscSceneNode *node = calloc(1, sizeof(AscSceneNode)); + node->render_group = ASC_RENDER_GROUP_NONE; + node->free_func = (asc_scene_free_func) free; + node->scale.x = node->scale.y = node->scale.z = 1; + asc_transform_identity(node->transform); + asc_transform_identity(node->world_transform); + return node; +} + +void asc_scene_node_free(AscSceneNode *node) { + if (node == NULL) return; + + // remove this node from its parent + asc_scene_node_unlink(node); + + // free the entire subtree + CxTreeIterator iter = asc_scene_node_iterator(node, true); + cx_foreach(AscSceneNode*, child, iter) { + if (!iter.exiting) continue; + if (child->behaviors != NULL) { + cxListFree(child->behaviors); + } + if (child->free_func != NULL) { + child->free_func(child); + } else { + free(child); + } + } +} + +void asc_scene_node_link(AscSceneNode * restrict parent, AscSceneNode * restrict node) { + cx_tree_link( + parent, node, + offsetof(AscSceneNode, parent), + offsetof(AscSceneNode, children), + offsetof(AscSceneNode, last_child), + offsetof(AscSceneNode, prev), + offsetof(AscSceneNode, next) + ); +} + +void asc_scene_node_unlink(AscSceneNode *node) { + cx_tree_unlink( + node, + offsetof(AscSceneNode, parent), + offsetof(AscSceneNode, children), + offsetof(AscSceneNode, last_child), + offsetof(AscSceneNode, prev), + offsetof(AscSceneNode, next) + ); +} + +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( + AscSceneNode *node, + asc_scene_update_func behavior +) { + if (node->behaviors != NULL) { + cxListFindRemove(node->behaviors, behavior); + } +} + +void asc_node_update(AscSceneNode *node) { + asc_set_flag(node->flags, ASC_SCENE_NODE_UPDATE_GRAPHICS); +} + +void asc_node_update_transform(AscSceneNode *node) { + // fast skip if node is already marked + if (asc_test_flag(node->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM)) { + return; + } + + CxTreeIterator iter = asc_scene_node_iterator(node, false); + cx_foreach(AscSceneNode*, n, iter) { + if (asc_test_flag(n->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM)) { + cxTreeIteratorContinue(iter); + } + asc_set_flag(n->flags, ASC_SCENE_NODE_UPDATE_TRANSFORM); + } +}
--- a/src/shader.c Thu Apr 24 18:41:42 2025 +0200 +++ b/src/shader.c Thu Apr 24 19:53:40 2025 +0200 @@ -27,7 +27,6 @@ #include "ascension/context.h" #include "ascension/shader.h" -#include "ascension/error.h" #include "ascension/filesystem.h" #include <GL/glew.h>
--- a/src/text.c Thu Apr 24 18:41:42 2025 +0200 +++ b/src/text.c Thu Apr 24 19:53:40 2025 +0200 @@ -26,7 +26,6 @@ */ #include "ascension/context.h" -#include "ascension/error.h" #include "ascension/ui/text.h" #include <assert.h>
--- a/src/texture.c Thu Apr 24 18:41:42 2025 +0200 +++ b/src/texture.c Thu Apr 24 19:53:40 2025 +0200 @@ -27,7 +27,6 @@ #include "ascension/context.h" #include "ascension/texture.h" -#include "ascension/error.h" #include "ascension/filesystem.h" #include <assert.h>
--- a/src/window.c Thu Apr 24 18:41:42 2025 +0200 +++ b/src/window.c Thu Apr 24 19:53:40 2025 +0200 @@ -27,8 +27,6 @@ #include "ascension/window.h" #include "ascension/context.h" -#include "ascension/error.h" -#include "ascension/utils.h" #include <GL/glew.h> @@ -53,10 +51,6 @@ asc_error("Cannot create window - slot %u already occupied.", index); return; } - if (window->ui != NULL) { - asc_wprintf("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; @@ -82,7 +76,7 @@ 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_scene_init(&window->ui); asc_dprintf("Window %u initialized", window->id); asc_context.active_window = index; } else { @@ -110,8 +104,7 @@ asc_gl_context_activate(&window->glctx); // destroy all scenes - asc_scene_node_free(window->ui); - window->ui = NULL; + asc_scene_destroy(&window->ui); // release context related data asc_gl_context_destroy(&window->glctx); @@ -146,12 +139,12 @@ int window_height = asc_active_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 - AscCamera ui_camera; - asc_camera_ortho(&ui_camera, viewport); - asc_scene_draw(asc_active_window->ui, viewport, &ui_camera); + AscScene *ui = &asc_active_window->ui; + asc_recti viewport = {0, 0, window_width, window_height}; + asc_camera_ortho(&ui->camera, viewport); + asc_scene_draw(ui, viewport); // Swap Buffers SDL_GL_SwapWindow(asc_active_window->window); @@ -176,3 +169,7 @@ } return i; } + +void asc_add_ui_node(AscSceneNode *node) { + asc_scene_add_node(&asc_active_window->ui, node); +}
--- a/test/snake/Makefile Thu Apr 24 18:41:42 2025 +0200 +++ b/test/snake/Makefile Thu Apr 24 19:53:40 2025 +0200 @@ -45,19 +45,17 @@ FORCE: $(BUILD_DIR)/snake.o: snake.c ../../src/ascension/core.h \ - ../../src/ascension/error.h ../../src/ascension/context.h \ - ../../src/ascension/datatypes.h ../../src/ascension/window.h \ - ../../src/ascension/glcontext.h ../../src/ascension/primitives.h \ - ../../src/ascension/mesh.h ../../src/ascension/shader.h \ - ../../src/ascension/texture.h ../../src/ascension/scene.h \ - ../../src/ascension/scene_node.h ../../src/ascension/transform.h \ - ../../src/ascension/camera.h ../../src/ascension/input.h \ - ../../src/ascension/ui/font.h ../../src/ascension/ui.h \ - ../../src/ascension/ui/text.h ../../src/ascension/ui/font.h \ - ../../src/ascension/ui/../2d/sprite.h \ + ../../src/ascension/context.h ../../src/ascension/datatypes.h \ + ../../src/ascension/window.h ../../src/ascension/glcontext.h \ + ../../src/ascension/primitives.h ../../src/ascension/mesh.h \ + ../../src/ascension/shader.h ../../src/ascension/texture.h \ + ../../src/ascension/scene.h ../../src/ascension/scene_node.h \ + ../../src/ascension/transform.h ../../src/ascension/camera.h \ + ../../src/ascension/input.h ../../src/ascension/ui/font.h \ + ../../src/ascension/ui.h ../../src/ascension/ui/text.h \ + ../../src/ascension/ui/font.h ../../src/ascension/ui/../2d/sprite.h \ ../../src/ascension/ui/../2d/../scene_node.h \ - ../../src/ascension/ui/../2d/../texture.h \ - ../../src/ascension/ui/../utils.h + ../../src/ascension/ui/../2d/../texture.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $<