do not try to use one distinct render group for each different shader

Wed, 11 Jun 2025 23:38:55 +0200

author
Mike Becker <universe@uap-core.de>
date
Wed, 11 Jun 2025 23:38:55 +0200
changeset 144
43636d6a6e25
parent 143
4db4f00493ad
child 145
a3231310d66d

do not try to use one distinct render group for each different shader

src/Makefile 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/ascension/sprite.h file | annotate | diff | comparison | revisions
src/ascension/texture.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
test/snake/snake.c file | annotate | diff | comparison | revisions
--- a/src/Makefile	Tue Jun 10 19:29:07 2025 +0200
+++ b/src/Makefile	Wed Jun 11 23:38:55 2025 +0200
@@ -105,8 +105,7 @@
  ascension/scene.h ascension/scene_node.h ascension/transform.h \
  ascension/camera.h ascension/input.h ascension/ui/font.h \
  ascension/scene.h ascension/behavior.h ascension/shader.h \
- ascension/sprite.h ascension/mesh.h ascension/texture.h \
- ascension/shader.h
+ ascension/sprite.h ascension/mesh.h ascension/texture.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
@@ -128,10 +127,10 @@
 
 $(BUILD_DIR)/sprite.o: sprite.c ascension/sprite.h ascension/scene_node.h \
  ascension/datatypes.h ascension/transform.h ascension/mesh.h \
- ascension/texture.h ascension/shader.h ascension/camera.h \
- ascension/context.h ascension/window.h ascension/glcontext.h \
- ascension/scene.h ascension/input.h ascension/ui/font.h \
- ascension/glcontext.h ascension/error.h ascension/mesh.h \
+ ascension/texture.h ascension/camera.h ascension/context.h \
+ ascension/window.h ascension/glcontext.h ascension/scene.h \
+ ascension/input.h ascension/ui/font.h ascension/glcontext.h \
+ ascension/error.h ascension/mesh.h ascension/shader.h \
  ascension/constants.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
@@ -143,7 +142,7 @@
  ascension/ui/text.h ascension/ui/font.h ascension/ui/../sprite.h \
  ascension/ui/../scene_node.h ascension/ui/../mesh.h \
  ascension/ui/../datatypes.h ascension/ui/../texture.h \
- ascension/ui/../shader.h ascension/ui/../camera.h
+ ascension/ui/../camera.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
--- a/src/ascension/glcontext.h	Tue Jun 10 19:29:07 2025 +0200
+++ b/src/ascension/glcontext.h	Wed Jun 11 23:38:55 2025 +0200
@@ -51,6 +51,7 @@
      * List of pointers to AscShaderProgram.
      */
     CxList *shaders;
+    unsigned active_program;
 } AscGLContext;
 
 #define asc_active_glctx (&asc_active_window->glctx)
--- a/src/ascension/scene.h	Tue Jun 10 19:29:07 2025 +0200
+++ b/src/ascension/scene.h	Wed Jun 11 23:38:55 2025 +0200
@@ -71,7 +71,7 @@
  *
  * @param scene the scene graph
  */
-static inline AscCamera *asc_scene_camera(AscScene *scene) {
+static inline const AscCamera *asc_scene_camera(const AscScene *scene) {
     return &scene->camera;
 }
 
--- a/src/ascension/scene_node.h	Tue Jun 10 19:29:07 2025 +0200
+++ b/src/ascension/scene_node.h	Wed Jun 11 23:38:55 2025 +0200
@@ -39,12 +39,11 @@
 typedef void(*asc_scene_node_destroy_func)(AscSceneNode*);
 typedef void(*asc_scene_node_update_func)(AscSceneNode*);
 
+// TODO: rework the concept of render groups, because currently it is only half abstract, half hard-coded
 enum AscRenderGroup {
     ASC_RENDER_GROUP_NONE = -1,
-    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_SPRITE_OPAQUE,
+    ASC_RENDER_GROUP_SPRITE_BLEND,
     ASC_RENDER_GROUP_COUNT
 };
 
--- a/src/ascension/shader.h	Tue Jun 10 19:29:07 2025 +0200
+++ b/src/ascension/shader.h	Wed Jun 11 23:38:55 2025 +0200
@@ -126,7 +126,9 @@
 /**
  * Activates a shader for use.
  *
- * @param shader the shader program to use
+ * Does nothing when the shader is already active.
+ *
+ * @param shader the shader program to use or @c NULL to deactivate any shader
  * @param camera the camera matrices (view/projection) to upload
  */
 void asc_shader_use(const AscShaderProgram *shader, const AscCamera *camera);
@@ -135,34 +137,46 @@
 /**
  * Registers a shader under a certain ID.
  *
+ * @note This function intentionally has an unspecified pointer return type
+ * so that you can assign the return value to your shader struct pointer without casts.
+ * All shader structs must be based on @c AscShaderProgram.
+ *
  * @param id the custom ID of the shader
  * @param create_func the function that creates the shader
  * @return the shader created by the @c create_func
  * @see asc_shader_lookup()
  */
-AscShaderProgram *asc_shader_register(unsigned int id, asc_shader_create_func create_func);
+const void *asc_shader_register(unsigned int id, asc_shader_create_func create_func);
 
 /**
  * Looks up a shader by ID.
  *
+ * @note This function intentionally has an unspecified pointer return type
+ * so that you can assign the return value to your shader struct pointer without casts.
+ * All shader structs must be based on @c AscShaderProgram.
+ *
  * @param id the ID of the shader to look up
  * @return the shader or @c NULL if no shader with such ID exists
  * @see asc_shader_register()
  */
-AscShaderProgram *asc_shader_lookup(unsigned int id);
+const void *asc_shader_lookup(unsigned int id);
 
 /**
  * Looks up a shader by ID or registers a new one if no shader exists with the specified ID.
  *
  * This function can be used for lazy initialization of shaders.
  *
+ * @note This function intentionally has an unspecified pointer return type
+ * so that you can assign the return value to your shader struct pointer without casts.
+ * All shader structs must be based on @c AscShaderProgram.
+ *
  * @param id the custom ID of the shader
  * @param create_func the function to create the shader
  * @return the found shader or the newly created shader
  * @see asc_shader_lookup()
  * @see asc_shader_register()
  */
-AscShaderProgram *asc_shader_lookup_or_create(unsigned int id, asc_shader_create_func create_func);
+const void *asc_shader_lookup_or_create(unsigned int id, asc_shader_create_func create_func);
 
 
 /**
--- a/src/ascension/sprite.h	Tue Jun 10 19:29:07 2025 +0200
+++ b/src/ascension/sprite.h	Wed Jun 11 23:38:55 2025 +0200
@@ -31,7 +31,7 @@
 #include "scene_node.h"
 #include "mesh.h"
 #include "texture.h"
-#include "shader.h"
+#include "camera.h"
 
 typedef struct AscSprite {
     AscSceneNode data;
@@ -71,20 +71,6 @@
 
 void asc_sprite_set_size(AscSceneNode *node, unsigned width, unsigned height);
 
-void asc_sprite_draw(const AscShaderProgram *shader, const AscSprite *node);
-
-/**
- * Returns a shader program that can draw sprites with rectangle textures.
- *
- * @return the shader program
- */
-const AscShaderProgram *asc_sprite_shader_rect(void);
-
-/**
- * Returns a shader program that can draw sprites with 2D textures.
- *
- * @return the shader program
- */
-const AscShaderProgram *asc_sprite_shader_uv(void);
+void asc_sprite_draw(const AscCamera *camera, const AscSprite *node);
 
 #endif //ASCENSION_SPRITE_H
--- a/src/ascension/texture.h	Tue Jun 10 19:29:07 2025 +0200
+++ b/src/ascension/texture.h	Wed Jun 11 23:38:55 2025 +0200
@@ -35,6 +35,10 @@
 typedef struct AscTexture AscTexture;
 
 struct AscTexture {
+    /**
+     * OpenGL texture target.
+     * Not to be confused with asc_texture_target!
+     */
     unsigned target;
     unsigned tex_id;
     unsigned width;
--- a/src/glcontext.c	Tue Jun 10 19:29:07 2025 +0200
+++ b/src/glcontext.c	Wed Jun 11 23:38:55 2025 +0200
@@ -101,6 +101,7 @@
         // Create the shaders array
         ctx->shaders = cxArrayListCreateSimple(CX_STORE_POINTERS, 32);
         cxDefineDestructor(ctx->shaders, asc_shader_free);
+        ctx->active_program = 0;
 
         return true;
     } else {
--- a/src/scene.c	Tue Jun 10 19:29:07 2025 +0200
+++ b/src/scene.c	Wed Jun 11 23:38:55 2025 +0200
@@ -78,55 +78,34 @@
     }
 }
 
-void asc_scene_draw_sprites(
+static void asc_scene_draw_sprites(
         const AscScene *scene,
-        const CxList *opaque_rect,
-        const CxList *opaque_uv,
-        const CxList *blend_rect,
-        const CxList *blend_uv
+        const CxList *opaque,
+        const CxList *blend
 ) {
+    glEnable(GL_DEPTH_TEST);
+    glClear(GL_DEPTH_BUFFER_BIT);
+    const AscCamera *camera = asc_scene_camera(scene);
+
     // render opaque sprites from front to back
-    CxIterator iter_opaque_rect = cxListBackwardsIterator(opaque_rect);
-    CxIterator iter_opaque_uv = cxListBackwardsIterator(opaque_uv);
+    CxIterator iter_opaque = cxListBackwardsIterator(opaque);
 
     // render sprites with alpha value from back to front
-    CxIterator iter_blend_rect = cxListIterator(blend_rect);
-    CxIterator iter_blend_uv = cxListIterator(blend_uv);
+    CxIterator iter_blend = cxListIterator(blend);
 
     // TODO: implement interleaving by depth
-    if (cxIteratorValid(iter_opaque_rect)) {
-        // TODO: add abstraction, because otherwise this will explode really fast when we start adding primitive shaders
+    // TODO: implement sorting by shader ID to reduce shader switches
+    if (cxIteratorValid(iter_opaque)) {
         glDisable(GL_BLEND);
-        const AscShaderProgram *shader = asc_sprite_shader_rect();
-        asc_shader_use(shader, &scene->camera);
-        cx_foreach(const AscSprite*, node, iter_opaque_rect) {
-            asc_sprite_draw(shader, node);
+        cx_foreach(const AscSprite*, node, iter_opaque) {
+            asc_sprite_draw(camera, node);
         }
     }
-    if (cxIteratorValid(iter_opaque_uv)) {
-        glDisable(GL_BLEND);
-        const AscShaderProgram *shader = asc_sprite_shader_uv();
-        asc_shader_use(shader, &scene->camera);
-        cx_foreach(const AscSprite*, node, iter_opaque_uv) {
-            asc_sprite_draw(shader, node);
-        }
-    }
-    if (cxIteratorValid(iter_blend_rect)) {
+    if (cxIteratorValid(iter_blend)) {
         glEnable(GL_BLEND);
         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-        const AscShaderProgram *shader = asc_sprite_shader_rect();
-        asc_shader_use(shader, &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);
-        const AscShaderProgram *shader = asc_sprite_shader_uv();
-        asc_shader_use(shader, &scene->camera);
-        cx_foreach(const AscSprite*, node, iter_blend_uv) {
-            asc_sprite_draw(shader, node);
+        cx_foreach(const AscSprite*, node, iter_blend) {
+            asc_sprite_draw(camera, node);
         }
     }
 }
@@ -229,18 +208,17 @@
     // process the render groups
     // -------------------------
 
+    // clear any previously active shader / camera combination
+    asc_shader_use(NULL, NULL);
+
     // 2D Elements
     // ===========
-    glEnable(GL_DEPTH_TEST);
-    glClear(GL_DEPTH_BUFFER_BIT);
 
     // Sprites
     // -------
     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]
+        render_group[ASC_RENDER_GROUP_SPRITE_OPAQUE],
+        render_group[ASC_RENDER_GROUP_SPRITE_BLEND]
     );
 }
 
--- a/src/shader.c	Tue Jun 10 19:29:07 2025 +0200
+++ b/src/shader.c	Wed Jun 11 23:38:55 2025 +0200
@@ -157,6 +157,13 @@
 }
 
 void asc_shader_use(const AscShaderProgram *shader, const AscCamera *camera) {
+    if (shader == NULL) {
+        asc_active_glctx->active_program = 0;
+        glUseProgram(0);
+        return;
+    }
+    if (asc_active_glctx->active_program == shader->gl_id) return;
+    asc_active_glctx->active_program = shader->gl_id;
     glUseProgram(shader->gl_id);
     glUniformMatrix4fv(shader->projection, 1, GL_FALSE, camera->projection);
     glUniformMatrix4fv(shader->view, 1, GL_FALSE, camera->view);
@@ -195,33 +202,34 @@
     cxFreeDefault(codes.frag);
 }
 
-AscShaderProgram *asc_shader_register(unsigned int id, asc_shader_create_func create_func) {
+const void *asc_shader_register(unsigned int id, asc_shader_create_func create_func) {
     AscGLContext *glctx = asc_active_glctx;
-    AscShaderProgram *prog = NULL;
 #ifndef NDEBUG
-    prog = asc_shader_lookup(id);
-    if (prog != NULL) {
-        asc_error("Shader program %u already exists. This is a bug!", id);
-        // still return it, so that the caller does not die immediately
-        return prog;
+    {
+        const AscShaderProgram *prog = asc_shader_lookup(id);
+        if (prog != NULL) {
+            asc_error("Shader program %u already exists. This is a bug!", id);
+            // still return it, so that the caller does not die immediately
+            return prog;
+        }
     }
 #endif
-    prog = create_func();
+    AscShaderProgram *prog = create_func();
     prog->id = id;
     cxListAdd(glctx->shaders, prog);
     return prog;
 }
 
-AscShaderProgram *asc_shader_lookup(unsigned int id) {
+const void *asc_shader_lookup(unsigned int id) {
     CxIterator iter = cxListIterator(asc_active_glctx->shaders);
-    cx_foreach(AscShaderProgram *, prog, iter) {
+    cx_foreach(const AscShaderProgram *, prog, iter) {
         if (prog->id == id) return prog;
     }
     return NULL;
 }
 
-AscShaderProgram *asc_shader_lookup_or_create(unsigned int id, asc_shader_create_func create_func) {
-    AscShaderProgram *prog = asc_shader_lookup(id);
+const void *asc_shader_lookup_or_create(unsigned int id, asc_shader_create_func create_func) {
+    const AscShaderProgram *prog = asc_shader_lookup(id);
     if (prog == NULL) {
         return asc_shader_register(id, create_func);
     }
@@ -230,4 +238,6 @@
 
 void asc_shader_clear_registry(void) {
     cxListClear(asc_active_glctx->shaders);
+    // also clear the active program to avoid accidental matches with newly created shaders
+    asc_shader_use(NULL, NULL);
 }
--- a/src/sprite.c	Tue Jun 10 19:29:07 2025 +0200
+++ b/src/sprite.c	Wed Jun 11 23:38:55 2025 +0200
@@ -31,6 +31,7 @@
 #include "ascension/glcontext.h"
 #include "ascension/error.h"
 #include "ascension/mesh.h"
+#include "ascension/shader.h"
 #include "ascension/constants.h"
 
 #include <GL/glew.h>
@@ -41,6 +42,8 @@
     int tex;
 };
 
+typedef struct asc_sprite_shader_s AscSpriteShader;
+
 static void *asc_sprite_shader_create(bool rect) {
     AscShaderCodes codes;
     if (asc_shader_load_code_files((AscShaderCodeInfo){
@@ -51,7 +54,7 @@
         asc_error("Loading sprite shader failed.");
         return NULL;
     }
-    struct asc_sprite_shader_s *shader = asc_shader_create(codes, sizeof(*shader));
+    AscSpriteShader *shader = asc_shader_create(codes, sizeof(*shader));
     if (asc_has_error()) {
         asc_shader_free_codes(codes);
         return NULL;
@@ -72,6 +75,14 @@
     return asc_sprite_shader_create(false);
 }
 
+static const AscSpriteShader *asc_sprite_shader_rect(void) {
+    return asc_shader_lookup_or_create(ASC_SHADER_SPRITE_RECT, asc_sprite_shader_rect_create);
+}
+
+static const AscSpriteShader *asc_sprite_shader_uv(void) {
+    return asc_shader_lookup_or_create(ASC_SHADER_SPRITE_UV, asc_sprite_shader_uv_create);
+}
+
 static void asc_sprite_destroy(AscSceneNode *node) {
     AscSprite *sprite = (AscSprite *) node;
     asc_mesh_destroy(&sprite->mesh);
@@ -113,8 +124,8 @@
     AscSceneNode *node = (AscSceneNode *) sprite;
     asc_scene_node_name(node, args.name);
     node->render_group = args.opaque
-                             ? ASC_RENDER_GROUP_SPRITE_OPAQUE_UV
-                             : ASC_RENDER_GROUP_SPRITE_BLEND_UV;
+                             ? ASC_RENDER_GROUP_SPRITE_OPAQUE
+                             : ASC_RENDER_GROUP_SPRITE_BLEND;
     node->update_func = asc_sprite_update;
     node->destroy_func = asc_sprite_destroy;
 
@@ -125,16 +136,13 @@
     return node;
 }
 
-const AscShaderProgram *asc_sprite_shader_rect(void) {
-    return asc_shader_lookup_or_create(ASC_SHADER_SPRITE_RECT, asc_sprite_shader_rect_create);
-}
-
-const AscShaderProgram *asc_sprite_shader_uv(void) {
-    return asc_shader_lookup_or_create(ASC_SHADER_SPRITE_UV, asc_sprite_shader_uv_create);
-}
-
-void asc_sprite_draw(const AscShaderProgram *program, const AscSprite *node) {
-    asc_ptr_cast(const struct asc_sprite_shader_s, shader, program);
+void asc_sprite_draw(const AscCamera *camera, const AscSprite *node) {
+    // Activate shader
+    const AscSpriteShader *shader =
+            node->texture->target == GL_TEXTURE_RECTANGLE
+                ? asc_sprite_shader_rect()
+                : asc_sprite_shader_uv();
+    asc_shader_use(&shader->program, camera);
 
     // Upload model matrix
     glUniformMatrix4fv(shader->program.model, 1,
--- a/src/text.c	Tue Jun 10 19:29:07 2025 +0200
+++ b/src/text.c	Wed Jun 11 23:38:55 2025 +0200
@@ -97,7 +97,7 @@
 
     // node properties
     asc_scene_node_name(node, args.name);
-    node->render_group = ASC_RENDER_GROUP_SPRITE_BLEND_RECT;
+    node->render_group = ASC_RENDER_GROUP_SPRITE_BLEND;
     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	Tue Jun 10 19:29:07 2025 +0200
+++ b/test/snake/Makefile	Wed Jun 11 23:38:55 2025 +0200
@@ -55,9 +55,8 @@
  ../../src/ascension/ui/font.h ../../src/ascension/ui/../sprite.h \
  ../../src/ascension/ui/../scene_node.h ../../src/ascension/ui/../mesh.h \
  ../../src/ascension/ui/../datatypes.h \
- ../../src/ascension/ui/../texture.h ../../src/ascension/ui/../shader.h \
- ../../src/ascension/ui/../camera.h ../../src/ascension/sprite.h \
- ../../src/ascension/2d.h
+ ../../src/ascension/ui/../texture.h ../../src/ascension/ui/../camera.h \
+ ../../src/ascension/sprite.h ../../src/ascension/shader.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS) -c $<
 
--- a/test/snake/snake.c	Tue Jun 10 19:29:07 2025 +0200
+++ b/test/snake/snake.c	Wed Jun 11 23:38:55 2025 +0200
@@ -28,7 +28,7 @@
 #include <ascension/core.h>
 #include <ascension/ui.h>
 #include <ascension/sprite.h>
-#include <ascension/2d.h>
+#include <ascension/shader.h>
 
 #include <cx/printf.h>
 

mercurial