Sun, 08 Jun 2025 14:58:19 +0200
refactor shader management - resolves #684
src/Makefile | file | annotate | diff | comparison | revisions | |
src/ascension/2d/sprite.h | file | annotate | diff | comparison | revisions | |
src/ascension/constants.h | file | annotate | diff | comparison | revisions | |
src/ascension/datatypes.h | file | annotate | diff | comparison | revisions | |
src/ascension/glcontext.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 | |
test/snake/Makefile | file | annotate | diff | comparison | revisions |
--- a/src/Makefile Sun Jun 08 14:57:54 2025 +0200 +++ b/src/Makefile Sun Jun 08 14:58:19 2025 +0200 @@ -52,34 +52,24 @@ $(BUILD_DIR)/camera.o: camera.c ascension/error.h ascension/context.h \ ascension/datatypes.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/camera.h + ascension/scene.h ascension/scene_node.h ascension/transform.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/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/scene.h ascension/scene_node.h ascension/transform.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/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/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 ascension/error.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $< @@ -89,22 +79,17 @@ $(BUILD_DIR)/font.o: font.c ascension/error.h ascension/context.h \ ascension/datatypes.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/filesystem.h \ - ascension/ui/font.h + ascension/scene.h ascension/scene_node.h ascension/transform.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/2d/sprite.h ascension/2d/../scene_node.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 + ascension/2d/../shader.h ascension/2d/../camera.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $< @@ -115,36 +100,28 @@ $(BUILD_DIR)/scene.o: scene.c ascension/error.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 \ + ascension/scene.h ascension/behavior.h ascension/shader.h ascension/2d.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/../mesh.h ascension/2d/../datatypes.h \ + ascension/2d/../texture.h ascension/2d/../shader.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/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 + ascension/window.h ascension/glcontext.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/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 + 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 ascension/error.h \ + ascension/shader.h ascension/filesystem.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $< @@ -153,45 +130,37 @@ ascension/2d/../transform.h ascension/2d/../mesh.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/error.h ascension/mesh.h + ascension/window.h ascension/glcontext.h ascension/scene.h \ + ascension/scene_node.h ascension/camera.h ascension/input.h \ + ascension/ui/font.h ascension/glcontext.h ascension/error.h \ + ascension/mesh.h ascension/constants.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/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/scene.h ascension/scene_node.h ascension/transform.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/../2d/../shader.h ascension/ui/../2d/../camera.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/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 + ascension/scene.h ascension/scene_node.h ascension/transform.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/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 + ascension/datatypes.h ascension/glcontext.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 @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $<
--- a/src/ascension/2d/sprite.h Sun Jun 08 14:57:54 2025 +0200 +++ b/src/ascension/2d/sprite.h Sun Jun 08 14:58:19 2025 +0200 @@ -71,23 +71,20 @@ void asc_sprite_set_size(AscSceneNode *node, unsigned width, unsigned height); - - - -typedef struct AscShaderSprite { - AscShaderProgram program; - int tex; -} AscShaderSprite; +void asc_sprite_draw(const AscShaderProgram *shader, const AscSprite *node); /** - * Loads and initializes the sprite shader. + * Returns a shader program that can draw sprites with rectangle textures. * - * @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 + * @return the shader program */ -int asc_shader_sprite_init(AscShaderSprite *sprite, bool rect); +const AscShaderProgram *asc_sprite_shader_rect(void); -void asc_sprite_draw(const AscShaderSprite *shader, const AscSprite *node); +/** + * Returns a shader program that can draw sprites with 2D textures. + * + * @return the shader program + */ +const AscShaderProgram *asc_sprite_shader_uv(void); #endif //ASCENSION_SPRITE_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ascension/constants.h Sun Jun 08 14:58:19 2025 +0200 @@ -0,0 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * Copyright 2025 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 ASC_CONSTANTS_H +#define ASC_CONSTANTS_H + +// -------------------------------------- +// Internally used shader IDs. +// -------------------------------------- + +#define ASC_SHADER_INTERNAL_ID(id) (1000000000u+id) + +#define ASC_SHADER_SPRITE_RECT ASC_SHADER_INTERNAL_ID(1) +#define ASC_SHADER_SPRITE_UV ASC_SHADER_INTERNAL_ID(2) + + + +#endif // ASC_CONSTANTS_H
--- a/src/ascension/datatypes.h Sun Jun 08 14:57:54 2025 +0200 +++ b/src/ascension/datatypes.h Sun Jun 08 14:58:19 2025 +0200 @@ -59,6 +59,8 @@ #define asc_set_flag(reg, flag) (reg |= flag) #define asc_set_flag_masked(reg, mask, flag) (reg = (reg & ~(mask)) | flag) +#define asc_ptr_cast(type, lvalue, rvalue) type *lvalue = (type *)(rvalue); + // -------------------------------------------------------------------------- // Datatype Definitions // --------------------------------------------------------------------------
--- a/src/ascension/glcontext.h Sun Jun 08 14:57:54 2025 +0200 +++ b/src/ascension/glcontext.h Sun Jun 08 14:58:19 2025 +0200 @@ -32,9 +32,6 @@ #include <cx/list.h> -#include "2d/sprite.h" // TODO: this shouldn't be included here -#include "texture.h" - typedef struct AscGLContextSettings { int gl_major_version; int gl_minor_version; @@ -42,32 +39,22 @@ int depth_size; } AscGLContextSettings; -enum AscDefaultTextures2d { - ASC_TEXTURE_2D_EMPTY_1X1_IDX = 0, - ASC_TEXTURE_2D_COUNT -}; - -enum AscDefaultTexturesRect { - ASC_TEXTURE_RECT_EMPTY_1X1_IDX = 0, - ASC_TEXTURE_RECT_COUNT -}; - typedef struct AscGLContext { SDL_Window *window; SDL_GLContext glctx; + /** + * List of registered cleanup functions. + * Use the API to register functions, don't access manually. + */ CxList *cleanup_funcs; - // TODO: replace with something similar to the font cache - struct { - AscShaderSprite sprite_rect; - AscShaderSprite sprite_uv; - } shader; + /** + * List of pointers to AscShaderProgram. + */ + CxList *shaders; } AscGLContext; #define asc_active_glctx (&asc_active_window->glctx) -#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, SDL_Window *window,
--- a/src/ascension/shader.h Sun Jun 08 14:57:54 2025 +0200 +++ b/src/ascension/shader.h Sun Jun 08 14:58:19 2025 +0200 @@ -72,12 +72,23 @@ typedef struct AscShaderProgram { unsigned int id; + unsigned int gl_id; int model; int view; int projection; + cx_destructor_func destr_func; } AscShaderProgram; /** + * The maximum ID for a user defined shader. + * + * The IDs above this number are reserved for the engine. + */ +#define ASC_SHADER_ID_USER_MAX 1'000'000'000u + +typedef AscShaderProgram*(*asc_shader_create_func)(void); + +/** * Loads shader codes from files. * * @param info the structure containing the file names and preprocessing info @@ -96,18 +107,27 @@ /** * Creates a shader program. * + * @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. + * * @param codes the (zero-terminated) source codes - * @return the compiled and linked shader program + * @param mem_size the memory size required for the structure + * @return the compiled and linked shader program with @c AscShaderProgram as base struct */ -AscShaderProgram asc_shader_program_create(AscShaderCodes codes); +void *asc_shader_create(AscShaderCodes codes, size_t mem_size); /** - * Destroys a shader program. + * Frees a shader program. * * @param program the program */ -void asc_shader_program_destroy(AscShaderProgram *program); +void asc_shader_free(AscShaderProgram *program); + +void asc_shader_use(const AscShaderProgram *shader, const AscCamera *camera); -void asc_shader_program_use(const AscShaderProgram *shader, const AscCamera *camera); + +AscShaderProgram *asc_shader_register(unsigned int id, asc_shader_create_func create_func); +AscShaderProgram *asc_shader_lookup(unsigned int id); +AscShaderProgram *asc_shader_lookup_or_create(unsigned int id, asc_shader_create_func create_func); #endif //ASCENSION_SHADER_H
--- a/src/glcontext.c Sun Jun 08 14:57:54 2025 +0200 +++ b/src/glcontext.c Sun Jun 08 14:58:19 2025 +0200 @@ -47,20 +47,6 @@ } } -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_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_uv.program); - asc_shader_program_destroy(&ctx->shader.sprite_rect.program); -} - struct asc_gl_context_cleanup_data { int type; union { @@ -109,15 +95,14 @@ glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(asc_gl_debug_callback, NULL); - if (asc_shader_initialize_predefined(ctx)) { - asc_error("Initializing predefined shaders failed"); - SDL_GL_DeleteContext(ctx->glctx); - return false; - } - + // Create the cleanup functions array ctx->cleanup_funcs = cxArrayListCreateSimple(sizeof(struct asc_gl_context_cleanup_data), 8); cxDefineDestructor(ctx->cleanup_funcs, asc_gl_context_cleanup); + // Create the shaders array + ctx->shaders = cxArrayListCreateSimple(CX_STORE_POINTERS, 32); + cxDefineDestructor(ctx->shaders, asc_shader_free); + return true; } else { asc_error("glewInit failed: %s", glewGetErrorString(err)); @@ -130,8 +115,8 @@ if (ctx->glctx == NULL) return; SDL_GL_MakeCurrent(ctx->window, ctx->glctx); + cxListFree(ctx->shaders); cxListFree(ctx->cleanup_funcs); - asc_shader_destroy_predefined(ctx); // destroy the GL context and the window SDL_GL_DeleteContext(ctx->glctx);
--- a/src/scene.c Sun Jun 08 14:57:54 2025 +0200 +++ b/src/scene.c Sun Jun 08 14:58:19 2025 +0200 @@ -96,16 +96,16 @@ // 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); + 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); } } if (cxIteratorValid(iter_opaque_uv)) { glDisable(GL_BLEND); - AscShaderSprite *shader = ASC_SHADER_SPRITE_UV; - asc_shader_program_use(&shader->program, &scene->camera); + 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); } @@ -113,8 +113,8 @@ 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); + 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); } @@ -122,8 +122,8 @@ 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); + 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); }
--- a/src/shader.c Sun Jun 08 14:57:54 2025 +0200 +++ b/src/shader.c Sun Jun 08 14:58:19 2025 +0200 @@ -87,14 +87,16 @@ * * @param shader the shader IDs to link * @param n the number of shaders - * @return a compiled program + * @param prog the struct where to store the result + * @retval zero success + * @retval non-zero failure */ -static AscShaderProgram asc_shader_link(unsigned shader[], unsigned n) { +static int asc_shader_link(unsigned shader[], unsigned n, AscShaderProgram *prog) { GLint success; GLint id = glCreateProgram(); if (id <= 0) { asc_error("glCreateProgram failed: %s", glGetError()); - return (AscShaderProgram) {0}; + return -1; } for (unsigned i = 0; i < n; i++) { glAttachShader(id, shader[i]); @@ -106,33 +108,36 @@ } if (success) { asc_dprintf("Shader Program %u linked.", id); - AscShaderProgram prog; - prog.id = id; + prog->gl_id = id; // by convention every shader shall have MVP matrices - prog.model = glGetUniformLocation(id, "model"); - prog.view = glGetUniformLocation(id, "view"); - prog.projection = glGetUniformLocation(id, "projection"); - return prog; + prog->model = glGetUniformLocation(id, "model"); + prog->view = glGetUniformLocation(id, "view"); + prog->projection = glGetUniformLocation(id, "projection"); + return 0; } else { char *log = malloc(1024); glGetProgramInfoLog(id, 1024, NULL, log); glDeleteProgram(id); asc_error("Linking shader program %u failed.\n%s", id, log); free(log); - return (AscShaderProgram) {0}; + return -1; } } -void asc_shader_program_destroy(AscShaderProgram *program) { - if (program->id > 0) { - asc_dprintf("Delete Shader Program %u", program->id); - glDeleteProgram(program->id); +void asc_shader_free(AscShaderProgram *program) { + if (program->gl_id > 0) { + asc_dprintf("Delete Shader Program %u", program->gl_id); + glDeleteProgram(program->gl_id); } - program->id = 0; + if (program->destr_func) { + program->destr_func(program); + } + cxFreeDefault(program); } -AscShaderProgram asc_shader_program_create(AscShaderCodes codes) { - unsigned shader[4]; +void *asc_shader_create(AscShaderCodes codes, size_t mem_size) { + AscShaderProgram *prog = cxZallocDefault(mem_size); + unsigned shader[2]; unsigned n = 0; if (codes.vtx) { shader[n++] = asc_shader_compile(GL_VERTEX_SHADER, codes.vtx, codes.vtx_pp); @@ -140,15 +145,19 @@ if (codes.frag) { shader[n++] = asc_shader_compile(GL_FRAGMENT_SHADER, codes.frag, codes.frag_pp); } - const AscShaderProgram prog = asc_shader_link(shader, n); + if (asc_shader_link(shader, n, prog)) { + cxFreeDefault(prog); + prog = NULL; + } for (unsigned i = 0; i < n; i++) { + asc_dprintf("Delete shader: %u", shader[i]); glDeleteShader(shader[i]); } return prog; } -void asc_shader_program_use(const AscShaderProgram *shader, const AscCamera *camera) { - glUseProgram(shader->id); +void asc_shader_use(const AscShaderProgram *shader, const AscCamera *camera) { + glUseProgram(shader->gl_id); glUniformMatrix4fv(shader->projection, 1, GL_FALSE, camera->projection); glUniformMatrix4fv(shader->view, 1, GL_FALSE, camera->view); } @@ -185,3 +194,36 @@ cxFreeDefault(codes.vtx); cxFreeDefault(codes.frag); } + +AscShaderProgram *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; + } +#endif + prog = create_func(); + prog->id = id; + cxListAdd(glctx->shaders, prog); + return prog; +} + +AscShaderProgram *asc_shader_lookup(unsigned int id) { + CxIterator iter = cxListIterator(asc_active_glctx->shaders); + cx_foreach(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); + if (prog == NULL) { + return asc_shader_register(id, create_func); + } + return prog; +} \ No newline at end of file
--- a/src/sprite.c Sun Jun 08 14:57:54 2025 +0200 +++ b/src/sprite.c Sun Jun 08 14:58:19 2025 +0200 @@ -29,11 +29,48 @@ #include "ascension/context.h" #include "ascension/glcontext.h" +#include "ascension/error.h" +#include "ascension/mesh.h" +#include "ascension/constants.h" #include <GL/glew.h> -#include "ascension/error.h" -#include "ascension/mesh.h" + +struct asc_sprite_shader_s { + AscShaderProgram program; + int tex; +}; + +static void *asc_sprite_shader_create(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 NULL; + } + struct asc_sprite_shader_s *shader = asc_shader_create(codes, sizeof(*shader)); + if (asc_has_error()) { + asc_shader_free_codes(codes); + return NULL; + } + shader->tex = glGetUniformLocation(shader->program.gl_id, "tex"); + asc_shader_free_codes(codes); + + asc_error_catch_all_gl(); + + return shader; +} + +static AscShaderProgram *asc_sprite_shader_rect_create() { + return asc_sprite_shader_create(true); +} + +static AscShaderProgram *asc_sprite_shader_uv_create() { + return asc_sprite_shader_create(false); +} static void asc_sprite_destroy(AscSceneNode *node) { AscSprite *sprite = (AscSprite *) node; @@ -88,7 +125,17 @@ return node; } -void asc_sprite_draw(const AscShaderSprite *shader, const AscSprite *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); + // Upload model matrix glUniformMatrix4fv(shader->program.model, 1, GL_FALSE, node->data.world_transform); @@ -107,23 +154,3 @@ 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/test/snake/Makefile Sun Jun 08 14:57:54 2025 +0200 +++ b/test/snake/Makefile Sun Jun 08 14:58:19 2025 +0200 @@ -47,17 +47,18 @@ $(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/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/glcontext.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/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/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/../2d/../shader.h \ + ../../src/ascension/ui/../2d/../camera.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $<