Thu, 24 Jul 2025 20:58:00 +0200
vastly deduplicate shader creation code
src/2d.c | file | annotate | diff | comparison | revisions | |
src/Makefile | file | annotate | diff | comparison | revisions | |
src/ascension/shader.h | 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 |
--- a/src/2d.c Wed Jul 23 00:27:46 2025 +0200 +++ b/src/2d.c Thu Jul 24 20:58:00 2025 +0200 @@ -46,53 +46,32 @@ #define ASC_RECTANGLE_SHADER_FLAG_ROUND 2 #define ASC_RECTANGLE_SHADER_FLAG_BORDER 4 +static void asc_rectangle_shader_init(AscShaderProgram *shader, int flags) { + asc_shader_init_uniform_loc_nice(shader, AscRectangleShader, size); + if (asc_test_flag(flags, ASC_RECTANGLE_SHADER_FLAG_FILL)) { + asc_shader_init_uniform_loc_nice(shader, AscRectangleShader, color); + } + if (asc_test_flag(flags, ASC_RECTANGLE_SHADER_FLAG_BORDER)) { + asc_shader_init_uniform_loc_nice(shader, AscRectangleShader, border_color); + asc_shader_init_uniform_loc_nice(shader, AscRectangleShader, thickness); + } + if (asc_test_flag(flags, ASC_RECTANGLE_SHADER_FLAG_ROUND)) { + asc_shader_init_uniform_loc_nice(shader, AscRectangleShader, radius); + } +} + static AscShaderProgram *asc_rectangle_shader_create(int flags) { - AscShaderCodes codes; const char * const defines[] = { "#define FILL\n", "#define ROUNDED_CORNERS\n", "#define BORDER\n" }; - if (asc_shader_load_code_files((AscShaderCodeInfo){ + return asc_shader_create((AscShaderCodeInfo){ .files.vtx = "sprite_vtx.glsl", .files.frag = "rectangle_frag.glsl", .defines.frag_list = defines, .defines.frag_list_select = flags - }, &codes)) { - asc_error("Loading rectangle shader failed."); - return NULL; - } - AscShaderProgram *shader = asc_shader_create(codes, sizeof(AscRectangleShader)); - if (asc_shader_invalid(shader)) { - asc_shader_free_codes(codes); - return shader; - } - asc_ptr_cast(AscRectangleShader, rect_shader, shader); - rect_shader->size = asc_shader_get_uniform_loc(shader, "size"); - if (asc_test_flag(flags, ASC_RECTANGLE_SHADER_FLAG_FILL)) { - rect_shader->color = asc_shader_get_uniform_loc(shader, "color"); - } else { - rect_shader->color = -1; - } - if (asc_test_flag(flags, ASC_RECTANGLE_SHADER_FLAG_BORDER)) { - rect_shader->thickness = asc_shader_get_uniform_loc(shader, "thickness"); - rect_shader->border_color = asc_shader_get_uniform_loc(shader, "border_color"); - } else { - rect_shader->thickness = -1; - rect_shader->border_color = -1; - } - if (asc_test_flag(flags, ASC_RECTANGLE_SHADER_FLAG_ROUND)) { - rect_shader->radius = asc_shader_get_uniform_loc(shader, "radius"); - } else { - rect_shader->radius = -1; - } - asc_shader_free_codes(codes); - - if (asc_error_catch_gl("Creating rectangle shader")) { - // TODO: error handling - } - - return shader; + }, sizeof(AscRectangleShader), asc_rectangle_shader_init, flags); } static void asc_rectangle_destroy(AscSceneNode *node) { @@ -224,47 +203,28 @@ #define ASC_ELLIPSIS_SHADER_FLAG_FILL 1 #define ASC_ELLIPSIS_SHADER_FLAG_BORDER 2 +static void asc_ellipsis_shader_init(AscShaderProgram *shader, int flags) { + asc_shader_init_uniform_loc_nice(shader, AscEllipsisShader, radii); + if (asc_test_flag(flags, ASC_ELLIPSIS_SHADER_FLAG_FILL)) { + asc_shader_init_uniform_loc_nice(shader, AscEllipsisShader, color); + } + if (asc_test_flag(flags, ASC_ELLIPSIS_SHADER_FLAG_BORDER)) { + asc_shader_init_uniform_loc_nice(shader, AscEllipsisShader, thickness); + asc_shader_init_uniform_loc_nice(shader, AscEllipsisShader, border_color); + } +} + static AscShaderProgram *asc_ellipsis_shader_create(int flags) { - AscShaderCodes codes; const char * const defines[] = { "#define FILL\n", "#define BORDER\n" }; - if (asc_shader_load_code_files((AscShaderCodeInfo){ + return asc_shader_create((AscShaderCodeInfo){ .files.vtx = "sprite_vtx.glsl", .files.frag = "ellipsis_frag.glsl", .defines.frag_list = defines, .defines.frag_list_select = flags - }, &codes)) { - asc_error("Loading ellipsis shader failed."); - return NULL; - } - AscShaderProgram *shader = asc_shader_create(codes, sizeof(AscRectangleShader)); - if (asc_shader_invalid(shader)) { - asc_shader_free_codes(codes); - return shader; - } - asc_ptr_cast(AscEllipsisShader, ellipsis_shader, shader); - ellipsis_shader->radii = asc_shader_get_uniform_loc(shader, "radii"); - if (asc_test_flag(flags, ASC_ELLIPSIS_SHADER_FLAG_FILL)) { - ellipsis_shader->color = asc_shader_get_uniform_loc(shader, "color"); - } else { - ellipsis_shader->color = -1; - } - if (asc_test_flag(flags, ASC_ELLIPSIS_SHADER_FLAG_BORDER)) { - ellipsis_shader->thickness = asc_shader_get_uniform_loc(shader, "thickness"); - ellipsis_shader->border_color = asc_shader_get_uniform_loc(shader, "border_color"); - } else { - ellipsis_shader->thickness = -1; - ellipsis_shader->border_color = -1; - } - asc_shader_free_codes(codes); - - if (asc_error_catch_gl("Creating ellipsis shader")) { - // TODO: error handling - } - - return shader; + }, sizeof(AscEllipsisShader), asc_ellipsis_shader_init, flags); } static void asc_ellipsis_draw(const AscCamera *camera, const AscSceneNode *node) {
--- a/src/Makefile Wed Jul 23 00:27:46 2025 +0200 +++ b/src/Makefile Thu Jul 24 20:58:00 2025 +0200 @@ -107,7 +107,7 @@ 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/font.h ascension/scene.h \ - ascension/behavior.h ascension/shader.h + ascension/behavior.h ascension/shader.h ascension/util.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $<
--- a/src/ascension/shader.h Wed Jul 23 00:27:46 2025 +0200 +++ b/src/ascension/shader.h Thu Jul 24 20:58:00 2025 +0200 @@ -116,14 +116,27 @@ /** * A function to create a shader program. - * This is only allowed to return @c NULL when loading the code - * files from disk fails. In all other cases, this must - * at least return a valid pointer to a program for which - * asc_shader_invalid() returns @c true. + * Should return @c NULL when creation fails. + * + * Usually this function calls asc_shader_create() with + * a configuration that depends on the flags passed as parameter. + * + * @see asc_shader_create() */ typedef AscShaderProgram*(*asc_shader_create_func)(int); /** + * Initialization function for a shader. + * + * This usually gets all uniform locations and stores them as integers in the struct. + * + * To be used with asc_shader_create(). + * @see asc_shader_create() + * @see asc_shader_init_uniform_loc() + */ +typedef void(*asc_shader_init_func)(AscShaderProgram*, int); + +/** * Loads shader codes from files. * * @param info the structure containing the file names and preprocessing info @@ -140,23 +153,17 @@ void asc_shader_free_codes(AscShaderCodes codes); /** - * Checks if the shader received a correct Open GL ID. - * - * If it did not, there is something wrong (compilation or linking failed). - * - * @param program the program - * @return true if the shader was not created successfully - */ -bool asc_shader_invalid(const AscShaderProgram *program); - -/** * Creates a shader program. * - * @param codes the (zero-terminated) source codes - * @param mem_size the memory size required for the structure - * @return the compiled and linked shader program with @c AscShaderProgram as base struct + * @param code_info the information about the code that should be loaded + * @param mem_size the memory size required for the target structure + * @param init_func the initialization function + * @param flags the creation flags + * @return the compiled and linked shader program as a structure of size @p mem_size + * that contains @c AscShaderProgram as base struct */ -AscShaderProgram *asc_shader_create(AscShaderCodes codes, size_t mem_size); +AscShaderProgram *asc_shader_create(AscShaderCodeInfo code_info, + size_t mem_size, asc_shader_init_func init_func, int flags); /** * Frees a shader program. @@ -230,6 +237,29 @@ asc_uniform_loc asc_shader_get_uniform_loc(const AscShaderProgram *shader, const char *name); +/** + * Gets the location of a uniform as an integer and stores it in the struct. + * + * This is a convenient wrapper for asc_shader_get_uniform_loc(). + * + * @param shader the shader program struct + * @param mem_offset the offset of the int field in the shader program struct + * @param name the name of the uniform + */ +void asc_shader_init_uniform_loc(AscShaderProgram *shader, off_t mem_offset, const char *name); + +/** + * Gets the location of a uniform as an integer and stores it in the struct. + * + * This is a nicer, but less flexible, wrapper for asc_shader_get_uniform_loc(). + * + * @param s a pointer to the shader struct + * @param type_name the type name of the shader struct + * @param field_name the field name which must match the uniform's name + */ +#define asc_shader_init_uniform_loc_nice(s, type_name, field_name) \ + asc_shader_init_uniform_loc(s, offsetof(type_name, field_name), #field_name) + void asc_shader_upload_model_matrix(const AscShaderProgram *shader, const AscSceneNode *node); void asc_shader_upload_col4f(int uniform_id, asc_col4f color);
--- a/src/shader.c Wed Jul 23 00:27:46 2025 +0200 +++ b/src/shader.c Thu Jul 24 20:58:00 2025 +0200 @@ -154,27 +154,48 @@ cxFreeDefault(program); } -bool asc_shader_invalid(const AscShaderProgram *program) { +static bool asc_shader_invalid(const AscShaderProgram *program) { return program == NULL || program->gl_id == 0; } -AscShaderProgram *asc_shader_create(AscShaderCodes codes, size_t mem_size) { - AscShaderProgram *prog = cxZallocDefault(mem_size); - unsigned shader[2]; - unsigned n = 0; - // TODO: clean up this pp mess by introducing proper nested structs - if (codes.vtx) { - shader[n++] = asc_shader_compile(GL_VERTEX_SHADER, codes.vtx, codes.vtx_pp, codes.vtx_pp_list, codes.vtx_pp_list_select); +AscShaderProgram *asc_shader_create(AscShaderCodeInfo code_info, size_t mem_size, asc_shader_init_func init_func, int flags) { + AscShaderCodes codes; + if (asc_shader_load_code_files(code_info, &codes)) { + asc_error("Loading shader failed."); + return NULL; } - if (codes.frag) { - shader[n++] = asc_shader_compile(GL_FRAGMENT_SHADER, codes.frag, codes.frag_pp, codes.frag_pp_list, codes.frag_pp_list_select); + AscShaderProgram *shader_program = cxZallocDefault(mem_size); + { + unsigned shader[2]; + unsigned n = 0; + // TODO: clean up this pp mess by introducing proper nested structs + if (codes.vtx) { + shader[n++] = asc_shader_compile(GL_VERTEX_SHADER, codes.vtx, codes.vtx_pp, codes.vtx_pp_list, codes.vtx_pp_list_select); + } + if (codes.frag) { + shader[n++] = asc_shader_compile(GL_FRAGMENT_SHADER, codes.frag, codes.frag_pp, codes.frag_pp_list, codes.frag_pp_list_select); + } + asc_shader_link(shader, n, shader_program); } - asc_shader_link(shader, n, prog); - return prog; + asc_shader_free_codes(codes); + if (asc_error_catch_gl("Compile and link shader") || asc_shader_invalid(shader_program)) { + asc_shader_free(shader_program); + return NULL; + } + + init_func(shader_program, flags); + + if (asc_error_catch_gl("Initializing shader")) { + asc_shader_free(shader_program); + return NULL; + } + + return shader_program; } + int asc_shader_use(const AscShaderProgram *shader, const AscCamera *camera) { - if (shader == NULL) { + if (asc_shader_invalid(shader)) { asc_active_glctx->active_program = 0; glUseProgram(0); return 0; @@ -270,6 +291,10 @@ return glGetUniformLocation(shader->gl_id, name); } +void asc_shader_init_uniform_loc(AscShaderProgram *shader, off_t mem_offset, const char *name) { + *((asc_uniform_loc*)((char *) shader + mem_offset)) = glGetUniformLocation(shader->gl_id, name); +} + void asc_shader_upload_model_matrix(const AscShaderProgram *shader, const AscSceneNode *node) { glUniformMatrix4fv(shader->model, 1,GL_FALSE, node->world_transform); }
--- a/src/sprite.c Wed Jul 23 00:27:46 2025 +0200 +++ b/src/sprite.c Thu Jul 24 20:58:00 2025 +0200 @@ -40,30 +40,16 @@ asc_uniform_loc tex; } AscSpriteShader; +static void asc_sprite_shader_init(AscShaderProgram *p, cx_attr_unused int flags) { + asc_shader_init_uniform_loc_nice(p, AscSpriteShader, tex); +} + static AscShaderProgram *asc_sprite_shader_create(int rect) { - AscShaderCodes codes; - if (asc_shader_load_code_files((AscShaderCodeInfo){ + return asc_shader_create((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; - } - AscShaderProgram *shader = asc_shader_create(codes, sizeof(AscSpriteShader)); - if (asc_shader_invalid(shader)) { - asc_shader_free_codes(codes); - return shader; - } - asc_ptr_cast(AscSpriteShader, sprite_shader, shader); - sprite_shader->tex = asc_shader_get_uniform_loc(shader, "tex"); - asc_shader_free_codes(codes); - - if (asc_error_catch_gl("Creating sprite shader")) { - // TODO: error handling - } - - return shader; + }, sizeof(AscSpriteShader), asc_sprite_shader_init, 0); } static void asc_sprite_destroy(AscSceneNode *node) {
--- a/src/text.c Wed Jul 23 00:27:46 2025 +0200 +++ b/src/text.c Thu Jul 24 20:58:00 2025 +0200 @@ -39,30 +39,16 @@ asc_uniform_loc tex; } AscTextShader; -AscShaderProgram *asc_text_shader_create(cx_attr_unused int unused) { - AscShaderCodes codes; - if (asc_shader_load_code_files((AscShaderCodeInfo){ +static void asc_text_shader_init(AscShaderProgram *p, cx_attr_unused int flags) { + asc_shader_init_uniform_loc_nice(p, AscTextShader, tex); +} + +static AscShaderProgram *asc_text_shader_create(int flags) { + return asc_shader_create((AscShaderCodeInfo){ .files.vtx = "sprite_vtx.glsl", .files.frag = "sprite_frag.glsl", .defines.frag = "#define USE_RECT", - }, &codes)) { - asc_error("Loading text shader failed."); - return NULL; - } - AscShaderProgram *shader = asc_shader_create(codes, sizeof(AscTextShader)); - if (asc_shader_invalid(shader)) { - asc_shader_free_codes(codes); - return shader; - } - asc_ptr_cast(AscTextShader, text_shader, shader); - text_shader->tex = asc_shader_get_uniform_loc(shader, "tex"); - asc_shader_free_codes(codes); - - if (asc_error_catch_gl("Creating text shader")) { - // TODO: error handling - } - - return shader; + }, sizeof(AscTextShader), asc_text_shader_init, flags); } static void asc_text_update(AscSceneNode *node) {