remove pre-defined dummy textures by introducing conditional compilation for shaders default tip

Sun, 01 Jun 2025 16:35:23 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 01 Jun 2025 16:35:23 +0200
changeset 137
f8e6e0ae61a8
parent 136
768e6eac1ab0

remove pre-defined dummy textures by introducing conditional compilation for shaders

and by the way resolves #645

shader/sprite_frag.glsl file | annotate | diff | comparison | revisions
shader/sprite_vtx.glsl file | annotate | diff | comparison | revisions
src/Makefile file | annotate | diff | comparison | revisions
src/ascension/2d/sprite.h file | annotate | diff | comparison | revisions
src/ascension/glcontext.h file | annotate | diff | comparison | revisions
src/ascension/scene.h file | annotate | diff | comparison | revisions
src/ascension/scene_node.h file | annotate | diff | comparison | revisions
src/ascension/shader.h file | annotate | diff | comparison | revisions
src/glcontext.c file | annotate | diff | comparison | revisions
src/scene.c file | annotate | diff | comparison | revisions
src/shader.c file | annotate | diff | comparison | revisions
src/sprite.c file | annotate | diff | comparison | revisions
src/text.c file | annotate | diff | comparison | revisions
test/snake/Makefile file | annotate | diff | comparison | revisions
--- a/shader/sprite_frag.glsl	Sun Jun 01 14:59:40 2025 +0200
+++ b/shader/sprite_frag.glsl	Sun Jun 01 16:35:23 2025 +0200
@@ -1,11 +1,12 @@
-#version 400 core
-
 layout(location = 0) out vec4 diffuse;
 in vec2 uvcoord;
 
-uniform sampler2D uv_tex;
-uniform sampler2DRect rect_tex;
+#ifdef USE_RECT
+uniform sampler2DRect tex;
+#else
+uniform sampler2D tex;
+#endif
 
 void main(void) {
-    diffuse = texture(rect_tex, uvcoord) * texture(uv_tex, uvcoord);
+    diffuse = texture(tex, uvcoord);
 }
--- a/shader/sprite_vtx.glsl	Sun Jun 01 14:59:40 2025 +0200
+++ b/shader/sprite_vtx.glsl	Sun Jun 01 16:35:23 2025 +0200
@@ -1,5 +1,3 @@
-#version 400 core
-
 layout(location = 0) in vec2 in_pos;
 layout(location = 1) in vec2 in_uv;
 out vec2 uvcoord;
--- a/src/Makefile	Sun Jun 01 14:59:40 2025 +0200
+++ b/src/Makefile	Sun Jun 01 16:35:23 2025 +0200
@@ -52,24 +52,33 @@
 
 $(BUILD_DIR)/camera.o: camera.c ascension/error.h ascension/context.h \
  ascension/datatypes.h ascension/window.h ascension/glcontext.h \
- ascension/shader.h ascension/texture.h ascension/scene.h \
- ascension/scene_node.h ascension/transform.h ascension/camera.h \
+ ascension/2d/sprite.h ascension/2d/../scene_node.h \
+ ascension/2d/../datatypes.h ascension/2d/../transform.h \
+ ascension/2d/../mesh.h ascension/2d/../texture.h \
+ ascension/2d/../shader.h ascension/2d/../camera.h ascension/texture.h \
+ ascension/scene.h ascension/scene_node.h ascension/camera.h \
  ascension/input.h ascension/ui/font.h ascension/camera.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
 $(BUILD_DIR)/context.o: context.c ascension/context.h \
  ascension/datatypes.h ascension/window.h ascension/glcontext.h \
- ascension/shader.h ascension/texture.h ascension/scene.h \
- ascension/scene_node.h ascension/transform.h ascension/camera.h \
+ ascension/2d/sprite.h ascension/2d/../scene_node.h \
+ ascension/2d/../datatypes.h ascension/2d/../transform.h \
+ ascension/2d/../mesh.h ascension/2d/../texture.h \
+ ascension/2d/../shader.h ascension/2d/../camera.h ascension/texture.h \
+ ascension/scene.h ascension/scene_node.h ascension/camera.h \
  ascension/input.h ascension/ui/font.h ascension/error.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/shader.h \
- ascension/texture.h ascension/scene.h ascension/scene_node.h \
- ascension/transform.h ascension/camera.h ascension/input.h \
+ ascension/window.h ascension/glcontext.h ascension/2d/sprite.h \
+ ascension/2d/../scene_node.h ascension/2d/../datatypes.h \
+ ascension/2d/../transform.h ascension/2d/../mesh.h \
+ ascension/2d/../texture.h ascension/2d/../shader.h \
+ ascension/2d/../camera.h ascension/texture.h ascension/scene.h \
+ ascension/scene_node.h ascension/camera.h ascension/input.h \
  ascension/ui/font.h ascension/error.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
@@ -80,16 +89,22 @@
 
 $(BUILD_DIR)/font.o: font.c ascension/error.h ascension/context.h \
  ascension/datatypes.h ascension/window.h ascension/glcontext.h \
- ascension/shader.h ascension/texture.h ascension/scene.h \
- ascension/scene_node.h ascension/transform.h ascension/camera.h \
+ ascension/2d/sprite.h ascension/2d/../scene_node.h \
+ ascension/2d/../datatypes.h ascension/2d/../transform.h \
+ ascension/2d/../mesh.h ascension/2d/../texture.h \
+ ascension/2d/../shader.h ascension/2d/../camera.h ascension/texture.h \
+ ascension/scene.h ascension/scene_node.h ascension/camera.h \
  ascension/input.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/shader.h ascension/texture.h ascension/datatypes.h \
- ascension/error.h
+ ascension/2d/sprite.h ascension/2d/../scene_node.h \
+ ascension/2d/../datatypes.h ascension/2d/../transform.h \
+ ascension/2d/../mesh.h ascension/2d/../texture.h \
+ ascension/2d/../shader.h ascension/2d/../camera.h ascension/texture.h \
+ ascension/error.h ascension/2d/sprite.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
@@ -100,29 +115,34 @@
 
 $(BUILD_DIR)/scene.o: scene.c ascension/error.h ascension/context.h \
  ascension/datatypes.h ascension/window.h ascension/glcontext.h \
- ascension/shader.h ascension/texture.h ascension/scene.h \
- ascension/scene_node.h ascension/transform.h ascension/camera.h \
+ ascension/2d/sprite.h ascension/2d/../scene_node.h \
+ ascension/2d/../datatypes.h ascension/2d/../transform.h \
+ ascension/2d/../mesh.h ascension/2d/../texture.h \
+ ascension/2d/../shader.h ascension/2d/../camera.h ascension/texture.h \
+ ascension/scene.h ascension/scene_node.h ascension/camera.h \
  ascension/input.h ascension/ui/font.h ascension/scene.h \
- ascension/behavior.h ascension/shader.h ascension/2d.h \
- ascension/2d/sprite.h ascension/2d/../scene_node.h \
- ascension/2d/../mesh.h ascension/2d/../datatypes.h \
- ascension/2d/../texture.h
+ ascension/behavior.h ascension/shader.h ascension/2d.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/shader.h \
- ascension/texture.h ascension/scene.h ascension/scene_node.h \
- ascension/camera.h ascension/input.h ascension/ui/font.h \
- ascension/error.h
+ ascension/window.h ascension/glcontext.h ascension/2d/sprite.h \
+ ascension/2d/../scene_node.h ascension/2d/../mesh.h \
+ ascension/2d/../datatypes.h ascension/2d/../texture.h \
+ ascension/2d/../shader.h ascension/2d/../camera.h ascension/texture.h \
+ ascension/scene.h ascension/scene_node.h ascension/camera.h \
+ ascension/input.h ascension/ui/font.h ascension/error.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
 $(BUILD_DIR)/shader.o: shader.c ascension/context.h ascension/datatypes.h \
- ascension/window.h ascension/glcontext.h ascension/shader.h \
- ascension/texture.h ascension/scene.h ascension/scene_node.h \
- ascension/transform.h ascension/camera.h ascension/input.h \
+ ascension/window.h ascension/glcontext.h ascension/2d/sprite.h \
+ ascension/2d/../scene_node.h ascension/2d/../datatypes.h \
+ ascension/2d/../transform.h ascension/2d/../mesh.h \
+ ascension/2d/../texture.h ascension/2d/../shader.h \
+ ascension/2d/../camera.h ascension/texture.h ascension/scene.h \
+ ascension/scene_node.h ascension/camera.h ascension/input.h \
  ascension/ui/font.h ascension/error.h ascension/shader.h \
  ascension/filesystem.h
 	@echo "Compiling $<"
@@ -131,38 +151,46 @@
 $(BUILD_DIR)/sprite.o: sprite.c ascension/2d/sprite.h \
  ascension/2d/../scene_node.h ascension/2d/../datatypes.h \
  ascension/2d/../transform.h ascension/2d/../mesh.h \
- ascension/2d/../texture.h ascension/context.h ascension/datatypes.h \
- ascension/window.h ascension/glcontext.h ascension/shader.h \
+ ascension/2d/../texture.h ascension/2d/../shader.h \
+ ascension/2d/../camera.h ascension/context.h ascension/datatypes.h \
+ ascension/window.h ascension/glcontext.h ascension/2d/sprite.h \
  ascension/texture.h ascension/scene.h ascension/scene_node.h \
  ascension/camera.h ascension/input.h ascension/ui/font.h \
- ascension/glcontext.h ascension/mesh.h
+ ascension/glcontext.h ascension/error.h ascension/mesh.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
 $(BUILD_DIR)/text.o: text.c ascension/error.h ascension/context.h \
  ascension/datatypes.h ascension/window.h ascension/glcontext.h \
- ascension/shader.h ascension/texture.h ascension/scene.h \
- ascension/scene_node.h ascension/transform.h ascension/camera.h \
+ ascension/2d/sprite.h ascension/2d/../scene_node.h \
+ ascension/2d/../datatypes.h ascension/2d/../transform.h \
+ ascension/2d/../mesh.h ascension/2d/../texture.h \
+ ascension/2d/../shader.h ascension/2d/../camera.h ascension/texture.h \
+ ascension/scene.h ascension/scene_node.h ascension/camera.h \
  ascension/input.h ascension/ui/font.h ascension/ui/text.h \
- ascension/ui/font.h ascension/ui/../2d/sprite.h \
- ascension/ui/../2d/../scene_node.h ascension/ui/../2d/../mesh.h \
- ascension/ui/../2d/../datatypes.h ascension/ui/../2d/../texture.h
+ ascension/ui/font.h ascension/ui/../2d/sprite.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
 $(BUILD_DIR)/texture.o: texture.c ascension/error.h ascension/context.h \
  ascension/datatypes.h ascension/window.h ascension/glcontext.h \
- ascension/shader.h ascension/texture.h ascension/scene.h \
- ascension/scene_node.h ascension/transform.h ascension/camera.h \
+ ascension/2d/sprite.h ascension/2d/../scene_node.h \
+ ascension/2d/../datatypes.h ascension/2d/../transform.h \
+ ascension/2d/../mesh.h ascension/2d/../texture.h \
+ ascension/2d/../shader.h ascension/2d/../camera.h ascension/texture.h \
+ ascension/scene.h ascension/scene_node.h ascension/camera.h \
  ascension/input.h ascension/ui/font.h ascension/texture.h \
  ascension/filesystem.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
 $(BUILD_DIR)/window.o: window.c ascension/error.h ascension/window.h \
- ascension/datatypes.h ascension/glcontext.h ascension/shader.h \
- ascension/texture.h ascension/scene.h ascension/scene_node.h \
- ascension/transform.h ascension/camera.h ascension/context.h \
+ ascension/datatypes.h ascension/glcontext.h ascension/2d/sprite.h \
+ ascension/2d/../scene_node.h ascension/2d/../datatypes.h \
+ ascension/2d/../transform.h ascension/2d/../mesh.h \
+ ascension/2d/../texture.h ascension/2d/../shader.h \
+ ascension/2d/../camera.h ascension/texture.h ascension/scene.h \
+ ascension/scene_node.h ascension/camera.h ascension/context.h \
  ascension/window.h ascension/input.h ascension/ui/font.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
--- a/src/ascension/2d/sprite.h	Sun Jun 01 14:59:40 2025 +0200
+++ b/src/ascension/2d/sprite.h	Sun Jun 01 16:35:23 2025 +0200
@@ -31,6 +31,7 @@
 #include "../scene_node.h"
 #include "../mesh.h"
 #include "../texture.h"
+#include "../shader.h"
 
 typedef struct AscSprite {
     AscSceneNode data;
@@ -68,8 +69,25 @@
 #define asc_sprite(...) \
     asc_sprite_create((struct asc_sprite_create_args) { __VA_ARGS__ })
 
-void asc_sprite_draw(AscSprite const *sprite);
-
 void asc_sprite_set_size(AscSceneNode *node, unsigned width, unsigned height);
 
+
+
+
+typedef struct AscShaderSprite {
+    AscShaderProgram program;
+    int tex;
+} AscShaderSprite;
+
+/**
+ * Loads and initializes the sprite shader.
+ *
+ * @param sprite the structure to initialize
+ * @param rect true if the version for rectangular textures shall be compiled
+ * @return zero on success, non-zero on failure
+ */
+int asc_shader_sprite_init(AscShaderSprite *sprite, bool rect);
+
+void asc_sprite_draw(const AscShaderSprite *shader, const AscSprite *node);
+
 #endif //ASCENSION_SPRITE_H
--- a/src/ascension/glcontext.h	Sun Jun 01 14:59:40 2025 +0200
+++ b/src/ascension/glcontext.h	Sun Jun 01 16:35:23 2025 +0200
@@ -32,7 +32,7 @@
 
 #include <cx/list.h>
 
-#include "shader.h"
+#include "2d/sprite.h" // TODO: this shouldn't be included here
 #include "texture.h"
 
 typedef struct AscGLContextSettings {
@@ -56,18 +56,17 @@
     SDL_Window *window;
     SDL_GLContext glctx;
     CxList *cleanup_funcs;
-    // TODO: find a better way to store the dummy textures
-    AscTexture textures_2d[ASC_TEXTURE_2D_COUNT];
-    AscTexture textures_rect[ASC_TEXTURE_RECT_COUNT];
+    // TODO: replace with something similar to the font cache
     struct {
-        AscShaderSprite sprite;
+        AscShaderSprite sprite_rect;
+        AscShaderSprite sprite_uv;
     } shader;
 } AscGLContext;
 
 #define asc_active_glctx (&asc_active_window->glctx)
-#define ASC_TEXTURE_2D_EMPTY_1X1 (&asc_active_glctx->textures_2d[ASC_TEXTURE_2D_EMPTY_1X1_IDX])
-#define ASC_TEXTURE_RECT_EMPTY_1X1 (&asc_active_glctx->textures_rect[ASC_TEXTURE_RECT_EMPTY_1X1_IDX])
-#define ASC_SHADER_SPRITE (&asc_active_glctx->shader.sprite)
+
+#define ASC_SHADER_SPRITE_RECT (&asc_active_glctx->shader.sprite_rect)
+#define ASC_SHADER_SPRITE_UV (&asc_active_glctx->shader.sprite_uv)
 
 bool asc_gl_context_initialize(
         AscGLContext *ctx,
--- a/src/ascension/scene.h	Sun Jun 01 14:59:40 2025 +0200
+++ b/src/ascension/scene.h	Sun Jun 01 16:35:23 2025 +0200
@@ -31,9 +31,14 @@
 #include "scene_node.h"
 #include "camera.h"
 
+#include <cx/list.h>
+
 typedef struct {
     AscCamera camera;
     AscSceneNode *root;
+    struct {
+        CxList *render_groups[ASC_RENDER_GROUP_COUNT];
+    } internal;
 } AscScene;
 
 /**
--- a/src/ascension/scene_node.h	Sun Jun 01 14:59:40 2025 +0200
+++ b/src/ascension/scene_node.h	Sun Jun 01 16:35:23 2025 +0200
@@ -41,8 +41,10 @@
 
 enum AscRenderGroup {
     ASC_RENDER_GROUP_NONE = -1,
-    ASC_RENDER_GROUP_SPRITE_OPAQUE,
-    ASC_RENDER_GROUP_SPRITE_BLEND,
+    ASC_RENDER_GROUP_SPRITE_OPAQUE_UV,
+    ASC_RENDER_GROUP_SPRITE_OPAQUE_RECT,
+    ASC_RENDER_GROUP_SPRITE_BLEND_UV,
+    ASC_RENDER_GROUP_SPRITE_BLEND_RECT,
     ASC_RENDER_GROUP_COUNT
 };
 
--- a/src/ascension/shader.h	Sun Jun 01 14:59:40 2025 +0200
+++ b/src/ascension/shader.h	Sun Jun 01 16:35:23 2025 +0200
@@ -28,14 +28,46 @@
 #ifndef ASCENSION_SHADER_H
 #define ASCENSION_SHADER_H
 
-typedef struct AscShaderCodeFiles {
+#include "camera.h"
+
+struct asc_shader_code_files_s {
+    /**
+     * File name of the vertex shader.
+     */
     const char *vtx;
+    /**
+     * File name of the fragment shader.
+     */
     const char *frag;
-} AscShaderCodeFiles;
+};
+
+struct asc_shader_code_defines_s {
+    /**
+     * Preprocessor code that shall be prepended to the vertex shader.
+     */
+    const char *vtx;
+    /**
+     * Preprocessor code that shall be prepended to the fragment shader.
+     */
+    const char *frag;
+};
+
+typedef struct asc_shader_code_info_s {
+    struct asc_shader_code_files_s files;
+    struct asc_shader_code_defines_s defines;
+} AscShaderCodeInfo;
 
 typedef struct AscShaderCodes {
     char *vtx;
     char *frag;
+    /**
+     * Optional preprocessor code for the vertex shader.
+     */
+    const char *vtx_pp;
+    /**
+     * Optional preprocessor code for the fragment shader.
+     */
+    const char *frag_pp;
 } AscShaderCodes;
 
 typedef struct AscShaderProgram {
@@ -45,21 +77,14 @@
     int projection;
 } AscShaderProgram;
 
-typedef struct AscShaderSprite {
-    AscShaderProgram program;
-    int depth;
-    int rect_tex;
-    int uv_tex;
-} AscShaderSprite;
-
 /**
  * Loads shader codes from files.
  *
- * @param files the structure containing the file names
+ * @param info the structure containing the file names and preprocessing info
  * @param codes the structure where to store the loaded codes
  * @return zero on success, non-zero on failure
  */
-int asc_shader_load_code_files(AscShaderCodeFiles files, AscShaderCodes *codes);
+int asc_shader_load_code_files(AscShaderCodeInfo info, AscShaderCodes *codes);
 
 /**
  * Deallocates the memory for the source codes.
@@ -83,12 +108,6 @@
  */
 void asc_shader_program_destroy(AscShaderProgram *program);
 
-/**
- * Loads and initializes the sprite shader.
- *
- * @param sprite the structure to initialize
- * @return zero on success, non-zero on failure
- */
-int asc_shader_sprite_init(AscShaderSprite *sprite);
+void asc_shader_program_use(const AscShaderProgram *shader, const AscCamera *camera);
 
 #endif //ASCENSION_SHADER_H
--- a/src/glcontext.c	Sun Jun 01 14:59:40 2025 +0200
+++ b/src/glcontext.c	Sun Jun 01 16:35:23 2025 +0200
@@ -28,6 +28,8 @@
 #include "ascension/glcontext.h"
 #include "ascension/error.h"
 
+#include "ascension/2d/sprite.h"
+
 #include <GL/glew.h>
 #include <cx/array_list.h>
 
@@ -46,40 +48,17 @@
 }
 
 static int asc_shader_initialize_predefined(AscGLContext *ctx) {
+    // TODO: check if we can replace that by lazy loading shaders just like fonts
     int ret = 0;
-    ret |= asc_shader_sprite_init(&ctx->shader.sprite);
+    ret |= asc_shader_sprite_init(&ctx->shader.sprite_uv, false);
+    ret |= asc_shader_sprite_init(&ctx->shader.sprite_rect, true);
     ret |= asc_error_catch_all_gl();
     return ret;
 }
 
 static void asc_shader_destroy_predefined(AscGLContext *ctx) {
-    asc_shader_program_destroy(&ctx->shader.sprite.program);
-}
-
-static int asc_texture_initialize_predefined(AscGLContext *ctx) {
-    asc_texture_init_rectangle(ctx->textures_rect, ASC_TEXTURE_RECT_COUNT);
-    asc_texture_init_2d(ctx->textures_2d, ASC_TEXTURE_2D_COUNT);
-
-    // Create a 1x1 surface with 32-bit RGBA format
-    SDL_Surface* white1x1 = SDL_CreateRGBSurface(
-        0, 1, 1, 32,
-        0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF);
-    if (white1x1 == NULL) {
-        asc_error("Failed to create surface: %s", SDL_GetError());
-        return 1;
-    }
-    SDL_FillRect(white1x1, NULL, 0xFFFFFFFF);
-
-    // Initialize the empty textures with the white surface
-    asc_texture_from_surface(&ctx->textures_rect[ASC_TEXTURE_RECT_EMPTY_1X1_IDX], white1x1);
-    asc_texture_from_surface(&ctx->textures_2d[ASC_TEXTURE_2D_EMPTY_1X1_IDX], white1x1);
-
-    return asc_error_catch_all_gl();
-}
-
-static void asc_texture_destroy_predefined(AscGLContext *ctx) {
-    asc_texture_destroy(ctx->textures_rect, ASC_TEXTURE_RECT_COUNT);
-    asc_texture_destroy(ctx->textures_2d, ASC_TEXTURE_2D_COUNT);
+    asc_shader_program_destroy(&ctx->shader.sprite_uv.program);
+    asc_shader_program_destroy(&ctx->shader.sprite_rect.program);
 }
 
 struct asc_gl_context_cleanup_data {
@@ -136,11 +115,6 @@
             return false;
         }
 
-        if (asc_texture_initialize_predefined(ctx)) {
-            asc_error("Initializing predefined textures failed");
-            return false;
-        }
-
         ctx->cleanup_funcs = cxArrayListCreateSimple(sizeof(struct asc_gl_context_cleanup_data), 8);
         cxDefineDestructor(ctx->cleanup_funcs, asc_gl_context_cleanup);
 
@@ -157,7 +131,6 @@
     SDL_GL_MakeCurrent(ctx->window, ctx->glctx);
 
     cxListFree(ctx->cleanup_funcs);
-    asc_texture_destroy_predefined(ctx);
     asc_shader_destroy_predefined(ctx);
 
     // destroy the GL context and the window
--- a/src/scene.c	Sun Jun 01 14:59:40 2025 +0200
+++ b/src/scene.c	Sun Jun 01 16:35:23 2025 +0200
@@ -33,7 +33,6 @@
 #include "ascension/2d.h"
 
 #include <cx/tree.h>
-#include <cx/linked_list.h>
 #include <cx/array_list.h>
 
 #include <GL/glew.h>
@@ -47,13 +46,19 @@
     }
     asc_camera_init_(&scene->camera, args);
     scene->root = asc_scene_node_empty();
+    for (unsigned i = 0 ; i < ASC_RENDER_GROUP_COUNT ; i++) {
+        scene->internal.render_groups[i] = cxArrayListCreateSimple(CX_STORE_POINTERS, 32);
+    }
 
     asc_dprintf("Initialized scene %"PRIxPTR, (uintptr_t) scene);
 }
 
 void asc_scene_destroy(AscScene *scene) {
     if (scene->root == NULL) return;
-
+    for (unsigned i = 0 ; i < ASC_RENDER_GROUP_COUNT ; i++) {
+        cxListFree(scene->internal.render_groups[i]);
+        scene->internal.render_groups[i] = NULL;
+    }
     asc_dprintf("Destroyed scene %"PRIxPTR, (uintptr_t) scene);
     asc_scene_node_free(scene->root);
 }
@@ -73,6 +78,58 @@
     }
 }
 
+void asc_scene_draw_sprites(
+        const AscScene *scene,
+        const CxList *opaque_rect,
+        const CxList *opaque_uv,
+        const CxList *blend_rect,
+        const CxList *blend_uv
+) {
+    // render opaque sprites from front to back
+    CxIterator iter_opaque_rect = cxListBackwardsIterator(opaque_rect);
+    CxIterator iter_opaque_uv = cxListBackwardsIterator(opaque_uv);
+
+    // render sprites with alpha value from back to front
+    CxIterator iter_blend_rect = cxListIterator(blend_rect);
+    CxIterator iter_blend_uv = cxListIterator(blend_uv);
+
+    // TODO: implement interleaving by depth
+    if (cxIteratorValid(iter_opaque_rect)) {
+        glDisable(GL_BLEND);
+        AscShaderSprite *shader = ASC_SHADER_SPRITE_RECT;
+        asc_shader_program_use(&shader->program, &scene->camera);
+        cx_foreach(const AscSprite*, node, iter_opaque_rect) {
+            asc_sprite_draw(shader, node);
+        }
+    }
+    if (cxIteratorValid(iter_opaque_uv)) {
+        glDisable(GL_BLEND);
+        AscShaderSprite *shader = ASC_SHADER_SPRITE_UV;
+        asc_shader_program_use(&shader->program, &scene->camera);
+        cx_foreach(const AscSprite*, node, iter_opaque_uv) {
+            asc_sprite_draw(shader, node);
+        }
+    }
+    if (cxIteratorValid(iter_blend_rect)) {
+        glEnable(GL_BLEND);
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+        AscShaderSprite *shader = ASC_SHADER_SPRITE_RECT;
+        asc_shader_program_use(&shader->program, &scene->camera);
+        cx_foreach(const AscSprite*, node, iter_blend_rect) {
+            asc_sprite_draw(shader, node);
+        }
+    }
+    if (cxIteratorValid(iter_blend_uv)) {
+        glEnable(GL_BLEND);
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+        AscShaderSprite *shader = ASC_SHADER_SPRITE_UV;
+        asc_shader_program_use(&shader->program, &scene->camera);
+        cx_foreach(const AscSprite*, node, iter_blend_uv) {
+            asc_sprite_draw(shader, node);
+        }
+    }
+}
+
 void asc_scene_draw(AscScene *scene) {
     if (scene->root == NULL) return;
 
@@ -90,10 +147,10 @@
         }
     }
 
-    // create render groups
-    CxList *render_group[ASC_RENDER_GROUP_COUNT];
+    // reset render groups
+    CxList **render_group = scene->internal.render_groups;
     for (unsigned i = 0 ; i < ASC_RENDER_GROUP_COUNT ; i++) {
-        render_group[i] = cxArrayListCreateSimple(CX_STORE_POINTERS, 32);
+        cxListClear(render_group[i]);
     }
 
     // update the scene graph and add nodes to their render groups
@@ -170,7 +227,6 @@
     // -------------------------
     // process the render groups
     // -------------------------
-    CxIterator render_iter;
 
     // 2D Elements
     // ===========
@@ -179,36 +235,12 @@
 
     // Sprites
     // -------
-    size_t sprite_count = cxListSize(render_group[ASC_RENDER_GROUP_SPRITE_OPAQUE])
-        + cxListSize(render_group[ASC_RENDER_GROUP_SPRITE_BLEND]);
-    if (sprite_count > 0) {
-        AscShaderProgram *shader = &ASC_SHADER_SPRITE->program;
-        glUseProgram(shader->id);
-        glUniformMatrix4fv(shader->projection, 1,
-                           GL_FALSE, scene->camera.projection);
-        glUniformMatrix4fv(shader->view, 1,
-                               GL_FALSE, scene->camera.view);
-
-        // render opaque sprites from front to back
-        glDisable(GL_BLEND);
-        render_iter = cxListBackwardsIterator(render_group[ASC_RENDER_GROUP_SPRITE_OPAQUE]);
-        cx_foreach(AscSprite const *, node, render_iter) {
-            asc_sprite_draw(node);
-        }
-
-        // 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]);
-        cx_foreach(AscSprite const *, node, render_iter) {
-            asc_sprite_draw(node);
-        }
-    }
-
-    // deallocate render groups
-    for (unsigned i = 0 ; i < ASC_RENDER_GROUP_COUNT ; i++) {
-        cxListFree(render_group[i]);
-    }
+    asc_scene_draw_sprites(scene,
+        render_group[ASC_RENDER_GROUP_SPRITE_OPAQUE_RECT],
+        render_group[ASC_RENDER_GROUP_SPRITE_OPAQUE_UV],
+        render_group[ASC_RENDER_GROUP_SPRITE_BLEND_RECT],
+        render_group[ASC_RENDER_GROUP_SPRITE_BLEND_UV]
+    );
 }
 
 void asc_scene_add_node(AscScene *scene, AscSceneNode *node) {
--- a/src/shader.c	Sun Jun 01 14:59:40 2025 +0200
+++ b/src/shader.c	Sun Jun 01 16:35:23 2025 +0200
@@ -43,9 +43,10 @@
  *
  * @param type the shader type (use the GL enum)
  * @param code the source code
+ * @param code_pp the optional preprocessor code
  * @return the compiled shader
  */
-static unsigned asc_shader_compile(unsigned int type, char const *code) {
+static unsigned asc_shader_compile(unsigned int type, const char *code, const char *code_pp) {
     GLuint id = glCreateShader(type);
     if (id == 0) {
         asc_error("glCreateShader failed: %s", glGetError());
@@ -53,8 +54,17 @@
     }
 
     GLint success;
-    int length = (int) strlen(code); // must be int because of OpenGL API
-    glShaderSource(id, 1, &code, &length);
+    const char *code_array[4];
+    GLint length_array[4];
+#define store_str(i, s) do {cxstring cxs = cx_str(s); code_array[i] = cxs.ptr; length_array[i] = (GLint) cxs.length;} while(0)
+    store_str(0, "#version 400 core\n");
+    store_str(1, code_pp);
+    store_str(2, "\n#line 1\n");
+    store_str(3, code);
+#undef store_str
+
+    // compile
+    glShaderSource(id, cx_nmemb(length_array), code_array, length_array);
     glCompileShader(id);
     glGetShaderiv(id, GL_COMPILE_STATUS, &success);
     if (success) {
@@ -121,35 +131,14 @@
     program->id = 0;
 }
 
-int asc_shader_sprite_init(AscShaderSprite *sprite) {
-    AscShaderCodes codes;
-    if (asc_shader_load_code_files((AscShaderCodeFiles){
-        .vtx = "sprite_vtx.glsl",
-        .frag = "sprite_frag.glsl"
-    }, &codes)) {
-        asc_error("Loading sprite shader failed.");
-        return 1;
-    }
-    sprite->program = asc_shader_program_create(codes);
-    if (asc_has_error()) {
-        asc_shader_free_codes(codes);
-        return 1;
-    }
-    sprite->depth = glGetUniformLocation(sprite->program.id, "depth");
-    sprite->rect_tex = glGetUniformLocation(sprite->program.id, "rect_tex");
-    sprite->uv_tex = glGetUniformLocation(sprite->program.id, "uv_tex");
-    asc_shader_free_codes(codes);
-    return 0;
-}
-
 AscShaderProgram asc_shader_program_create(AscShaderCodes codes) {
     unsigned shader[4];
     unsigned n = 0;
     if (codes.vtx) {
-        shader[n++] = asc_shader_compile(GL_VERTEX_SHADER, codes.vtx);
+        shader[n++] = asc_shader_compile(GL_VERTEX_SHADER, codes.vtx, codes.vtx_pp);
     }
     if (codes.frag) {
-        shader[n++] = asc_shader_compile(GL_FRAGMENT_SHADER, codes.frag);
+        shader[n++] = asc_shader_compile(GL_FRAGMENT_SHADER, codes.frag, codes.frag_pp);
     }
     const AscShaderProgram prog = asc_shader_link(shader, n);
     for (unsigned i = 0; i < n; i++) {
@@ -158,6 +147,12 @@
     return prog;
 }
 
+void asc_shader_program_use(const AscShaderProgram *shader, const AscCamera *camera) {
+    glUseProgram(shader->id);
+    glUniformMatrix4fv(shader->projection, 1, GL_FALSE, camera->projection);
+    glUniformMatrix4fv(shader->view, 1, GL_FALSE, camera->view);
+}
+
 static int asc_shader_load_code_file(const char *filename, char **code) {
     if (filename == NULL) {
         *code = NULL;
@@ -177,10 +172,12 @@
     return *code == NULL ? -1 : 0;
 }
 
-int asc_shader_load_code_files(AscShaderCodeFiles files, AscShaderCodes *codes) {
+int asc_shader_load_code_files(AscShaderCodeInfo info, AscShaderCodes *codes) {
     int ret = 0;
-    ret |= asc_shader_load_code_file(files.vtx, &codes->vtx);
-    ret |= asc_shader_load_code_file(files.frag, &codes->frag);
+    ret |= asc_shader_load_code_file(info.files.vtx, &codes->vtx);
+    codes->vtx_pp = info.defines.vtx;
+    ret |= asc_shader_load_code_file(info.files.frag, &codes->frag);
+    codes->frag_pp = info.defines.frag;
     return ret;
 }
 
--- a/src/sprite.c	Sun Jun 01 14:59:40 2025 +0200
+++ b/src/sprite.c	Sun Jun 01 16:35:23 2025 +0200
@@ -32,6 +32,7 @@
 
 #include <GL/glew.h>
 
+#include "ascension/error.h"
 #include "ascension/mesh.h"
 
 static void asc_sprite_destroy(AscSceneNode *node) {
@@ -75,8 +76,8 @@
     AscSceneNode *node = (AscSceneNode *) sprite;
     asc_scene_node_name(node, args.name);
     node->render_group = args.opaque
-                             ? ASC_RENDER_GROUP_SPRITE_OPAQUE
-                             : ASC_RENDER_GROUP_SPRITE_BLEND;
+                             ? ASC_RENDER_GROUP_SPRITE_OPAQUE_UV
+                             : ASC_RENDER_GROUP_SPRITE_BLEND_UV;
     node->update_func = asc_sprite_update;
     node->destroy_func = asc_sprite_destroy;
 
@@ -87,22 +88,13 @@
     return node;
 }
 
-void asc_sprite_draw(AscSprite const *node) {
-    // Obtain shader
-    AscShaderSprite *shader = ASC_SHADER_SPRITE;
-
+void asc_sprite_draw(const AscShaderSprite *shader, const AscSprite *node) {
     // Upload model matrix
     glUniformMatrix4fv(shader->program.model, 1,
                        GL_FALSE, node->data.world_transform);
 
     // Bind texture
-    if (node->texture->target == GL_TEXTURE_RECTANGLE) {
-        asc_texture_bind(node->texture, shader->rect_tex, 0);
-        asc_texture_bind(ASC_TEXTURE_2D_EMPTY_1X1, shader->uv_tex, 1);
-    } else {
-        asc_texture_bind(ASC_TEXTURE_RECT_EMPTY_1X1, shader->rect_tex, 0);
-        asc_texture_bind(node->texture, shader->uv_tex, 1);
-    }
+    asc_texture_bind(node->texture, shader->tex, 0);
 
     // Draw mesh
     asc_mesh_draw_triangle_strip(&node->mesh);
@@ -114,3 +106,24 @@
     sprite->height = height;
     asc_node_update(node);
 }
+
+int asc_shader_sprite_init(AscShaderSprite *sprite, bool rect) {
+    AscShaderCodes codes;
+    if (asc_shader_load_code_files((AscShaderCodeInfo){
+        .files.vtx = "sprite_vtx.glsl",
+        .files.frag = "sprite_frag.glsl",
+        .defines.frag = rect ? "#define USE_RECT" : NULL,
+    }, &codes)) {
+        asc_error("Loading sprite shader failed.");
+        return 1;
+    }
+    sprite->program = asc_shader_program_create(codes);
+    if (asc_has_error()) {
+        asc_shader_free_codes(codes);
+        return 1;
+    }
+    sprite->tex = glGetUniformLocation(sprite->program.id, "tex");
+    asc_shader_free_codes(codes);
+
+    return asc_error_catch_all_gl();
+}
--- a/src/text.c	Sun Jun 01 14:59:40 2025 +0200
+++ b/src/text.c	Sun Jun 01 16:35:23 2025 +0200
@@ -97,7 +97,7 @@
 
     // node properties
     asc_scene_node_name(node, args.name);
-    node->render_group = ASC_RENDER_GROUP_SPRITE_BLEND;
+    node->render_group = ASC_RENDER_GROUP_SPRITE_BLEND_RECT;
     node->destroy_func = asc_text_destroy;
     node->update_func = asc_text_update;
     node->position = asc_vec3f_new(args.x, args.y, ASC_SCENE_2D_DEPTH_OFFSET);
--- a/test/snake/Makefile	Sun Jun 01 14:59:40 2025 +0200
+++ b/test/snake/Makefile	Sun Jun 01 16:35:23 2025 +0200
@@ -47,17 +47,17 @@
 $(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/shader.h \
- ../../src/ascension/texture.h ../../src/ascension/scene.h \
- ../../src/ascension/scene_node.h ../../src/ascension/transform.h \
+ ../../src/ascension/glcontext.h ../../src/ascension/2d/sprite.h \
+ ../../src/ascension/2d/../scene_node.h \
+ ../../src/ascension/2d/../datatypes.h \
+ ../../src/ascension/2d/../transform.h ../../src/ascension/2d/../mesh.h \
+ ../../src/ascension/2d/../texture.h ../../src/ascension/2d/../shader.h \
+ ../../src/ascension/2d/../camera.h ../../src/ascension/texture.h \
+ ../../src/ascension/scene.h ../../src/ascension/scene_node.h \
  ../../src/ascension/camera.h ../../src/ascension/input.h \
  ../../src/ascension/ui/font.h ../../src/ascension/behavior.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/../mesh.h \
- ../../src/ascension/ui/../2d/../datatypes.h \
- ../../src/ascension/ui/../2d/../texture.h
+ ../../src/ascension/ui/font.h ../../src/ascension/ui/../2d/sprite.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 

mercurial