Wed, 11 Jun 2025 23:38:55 +0200
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 $<