vastly deduplicate shader creation code

Thu, 24 Jul 2025 20:58:00 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 24 Jul 2025 20:58:00 +0200
changeset 222
2cb9a71df7a6
parent 221
14eddd43b3f7
child 223
4f32c7755138

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) {

mercurial