clean up the messy shader code specification

Fri, 25 Jul 2025 18:50:36 +0200

author
Mike Becker <universe@uap-core.de>
date
Fri, 25 Jul 2025 18:50:36 +0200
changeset 223
4f32c7755138
parent 222
2cb9a71df7a6
child 224
f72b80448413

clean up the messy shader code specification

src/2d.c 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	Thu Jul 24 20:58:00 2025 +0200
+++ b/src/2d.c	Fri Jul 25 18:50:36 2025 +0200
@@ -61,16 +61,17 @@
 }
 
 static AscShaderProgram *asc_rectangle_shader_create(int flags) {
-    const char * const defines[] = {
-        "#define FILL\n",
-        "#define ROUNDED_CORNERS\n",
-        "#define BORDER\n"
-    };
-    return asc_shader_create((AscShaderCodeInfo){
-        .files.vtx = "sprite_vtx.glsl",
-        .files.frag = "rectangle_frag.glsl",
-        .defines.frag_list = defines,
-        .defines.frag_list_select = flags
+    return asc_shader_create((AscShaderCodes){
+        .vtx = {.source_file = "sprite_vtx.glsl"},
+        .frag = {
+            .source_file = "rectangle_frag.glsl",
+            .preamble_code = (const char *[]) {
+                "#define FILL",
+                "#define ROUNDED_CORNERS",
+                "#define BORDER"
+            },
+            .preamble_code_flags = flags,
+        },
     }, sizeof(AscRectangleShader), asc_rectangle_shader_init, flags);
 }
 
@@ -215,15 +216,16 @@
 }
 
 static AscShaderProgram *asc_ellipsis_shader_create(int flags) {
-    const char * const defines[] = {
-        "#define FILL\n",
-        "#define BORDER\n"
-    };
-    return asc_shader_create((AscShaderCodeInfo){
-        .files.vtx = "sprite_vtx.glsl",
-        .files.frag = "ellipsis_frag.glsl",
-        .defines.frag_list = defines,
-        .defines.frag_list_select = flags
+    return asc_shader_create((AscShaderCodes){
+        .vtx = {.source_file = "sprite_vtx.glsl"},
+        .frag = {
+            .source_file = "ellipsis_frag.glsl",
+            .preamble_code = (const char *[]) {
+                "#define FILL",
+                "#define BORDER"
+            },
+            .preamble_code_flags = flags,
+        },
     }, sizeof(AscEllipsisShader), asc_ellipsis_shader_init, flags);
 }
 
--- a/src/ascension/shader.h	Thu Jul 24 20:58:00 2025 +0200
+++ b/src/ascension/shader.h	Fri Jul 25 18:50:36 2025 +0200
@@ -36,66 +36,29 @@
 
 typedef int asc_uniform_loc;
 
-struct asc_shader_code_files_s {
+struct asc_shader_code_s {
     /**
-     * File name of the vertex shader.
+     * File name of the shader.
+     * Set to @c NULL if the specific type of shader is not used in the program.
      */
-    const char *vtx;
+    const char *source_file;
     /**
-     * File name of the fragment shader.
+     * An array of code segments, usually preprocessor defines.
+     *
+     * The maximum number of elements in this array is 64.
+     * The preamble_code_flags integer decides which elements of this array shall be prepended to
+     * the shader code before compilation.
      */
-    const char *frag;
+    const char * const *preamble_code;
+    /**
+     * Flags for deciding which code segments shall be added to the preamble of the shader.
+     */
+    uint64_t preamble_code_flags;
 };
 
-struct asc_shader_code_defines_s {
-    /**
-     * Preprocessor code that shall be prepended to the vertex shader.
-     */
-    const char *vtx;
-    /**
-     * Preprocessor code that shall be prepended to the fragment shader.
-     */
-    const char *frag;
-    /**
-     * A list of flag-based preprocessor code for the vertex shader.
-     * Each code must end with a new-line.
-     */
-    const char * const *vtx_list;
-    /**
-     * A list of flag-based preprocessor code for the fragment shader.
-     * Each code must end with a new-line.
-     */
-    const char * const *frag_list;
-    /**
-     * Selection flags for the list of codes for the vertex shader.
-     */
-    unsigned short vtx_list_select;
-    /**
-     * Selection flags for the list of codes for the fragment shader.
-     */
-    unsigned short frag_list_select;
-};
-
-typedef struct asc_shader_code_info_s {
-    struct asc_shader_code_files_s files;
-    struct asc_shader_code_defines_s defines;
-} AscShaderCodeInfo;
-
 typedef struct asc_shader_codes_s {
-    char *vtx;
-    char *frag;
-    /**
-     * Optional preprocessor code for the vertex shader.
-     */
-    const char *vtx_pp;
-    /**
-     * Optional preprocessor code for the fragment shader.
-     */
-    const char *frag_pp;
-    const char * const *vtx_pp_list;
-    const char * const *frag_pp_list;
-    unsigned short vtx_pp_list_select;
-    unsigned short frag_pp_list_select;
+    struct asc_shader_code_s vtx;
+    struct asc_shader_code_s frag;
 } AscShaderCodes;
 
 typedef struct asc_shader_program_s {
@@ -137,22 +100,6 @@
 typedef void(*asc_shader_init_func)(AscShaderProgram*, int);
 
 /**
- * Loads shader codes from files.
- *
- * @param info the structure containing the file names and preprocessing info
- * @param codes the structure where to store the loaded codes
- * @return zero on success, non-zero on failure
- */
-int asc_shader_load_code_files(AscShaderCodeInfo info, AscShaderCodes *codes);
-
-/**
- * Deallocates the memory for the source codes.
- *
- * @param codes the structure containing pointers to the source codes
- */
-void asc_shader_free_codes(AscShaderCodes codes);
-
-/**
  * Creates a shader program.
  *
  * @param code_info the information about the code that should be loaded
@@ -162,7 +109,7 @@
  * @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(AscShaderCodeInfo code_info,
+AscShaderProgram *asc_shader_create(AscShaderCodes code_info,
     size_t mem_size, asc_shader_init_func init_func, int flags);
 
 /**
--- a/src/shader.c	Thu Jul 24 20:58:00 2025 +0200
+++ b/src/shader.c	Fri Jul 25 18:50:36 2025 +0200
@@ -37,62 +37,81 @@
 #include <cx/buffer.h>
 #include <cx/streams.h>
 
+static char *asc_shader_load_code_file(const char *filename) {
+    cxmutstr fpath = asc_filesystem_combine_paths(cx_strcast(asc_context.shader_path), cx_str(filename));
+    asc_dprintf("Load shader code from %" CX_PRIstr, CX_SFMT(fpath));
+    FILE *f = fopen(fpath.ptr, "r");
+    cx_strfree(&fpath);
+    CxBuffer buffer;
+    if (f == NULL || cxBufferInit(&buffer, NULL, 1024, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND)) {
+        return NULL;
+    }
+    cx_stream_copy(f, &buffer, (cx_read_func) fread, cxBufferWriteFunc);
+    cxBufferPut(&buffer, '\0');
+    cxBufferShrink(&buffer, 0);
+    return buffer.space;
+}
+
 /**
  * Compiles a shader from the given source code.
  *
  * The ID of the returned shader will be zero when something went wrong.
  *
  * @param type the shader type (use the GL enum)
- * @param code the source code
- * @param code_pp the optional preprocessor code
- * @param code_pp_list list of optional preprocessor code
- * @param code_pp_list_select selection flags for the preprocessor code list
+ * @param code the source code information
  * @return the compiled shader
  */
-static unsigned asc_shader_compile(unsigned int type, const char *code, const char *code_pp,
-    const char * const *code_pp_list, unsigned short code_pp_list_select) {
+static unsigned asc_shader_compile(unsigned int type, struct asc_shader_code_s code) {
+    // try to load the source code, first
+    char *source_text = asc_shader_load_code_file(code.source_file);
+    if (source_text == NULL) {
+        asc_error("Failed to load shader source code from %s", code.source_file);
+        return 0;
+    }
+
+    // create the shader
     GLuint id = glCreateShader(type);
     if (id == 0) {
         asc_error("glCreateShader failed: %s", glGetError());
         return 0;
     }
 
-    GLint success;
-    const char *code_array[20];
+    const unsigned max_code_parts = 128+3; // version + preamble + line 1 + source text
+    const char *code_array[max_code_parts];
     GLsizei code_count = 0;
     code_array[code_count++] = "#version 400 core\n";
-    if (code_pp != NULL) code_array[code_count++] = code_pp;
-    unsigned test_flag = 1;
+    uint64_t test_flag = 1;
     unsigned select_index = 0;
-    while (test_flag <= code_pp_list_select) {
-        if (asc_test_flag(code_pp_list_select, test_flag)) {
-            code_array[code_count++] = code_pp_list[select_index];
+    while (test_flag <= code.preamble_code_flags) {
+        if (asc_test_flag(code.preamble_code_flags, test_flag)) {
+            code_array[code_count++] = code.preamble_code[select_index];
+            code_array[code_count++] = "\n";
         }
         select_index++;
         test_flag <<= 1;
     }
-    code_array[code_count++] = "\n#line 1\n";
-    code_array[code_count++] = code;
-
-    // compute the lengths
-    GLint length_array[20];
-    for (int i = 0; i < code_count ; i++) {
-        length_array[i] = (GLint) strlen(code_array[i]);
-    }
+    code_array[code_count++] = "#line 1\n";
+    code_array[code_count++] = source_text;
 
     // compile
-    glShaderSource(id, code_count, code_array, length_array);
+    glShaderSource(id, code_count, code_array, NULL);
     glCompileShader(id);
+
+    // free the source text
+    cxFreeDefault(source_text);
+
+    // check the compilation result
+    GLint success;
     glGetShaderiv(id, GL_COMPILE_STATUS, &success);
     if (success) {
         asc_dprintf("Shader %u compiled", id);
         return id;
     } else {
-        char *log = malloc(1024);
+        char *log = cxMallocDefault(1024);
         glGetShaderInfoLog(id, 1024, NULL, log);
         glDeleteShader(id);
         asc_error("Shader %u compilation failed.\n%s", id, log);
-        free(log);
+        cxFreeDefault(log);
         return 0;
     }
 }
@@ -158,26 +177,34 @@
     return program == NULL || program->gl_id == 0;
 }
 
-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;
+AscShaderProgram *asc_shader_create(AscShaderCodes code_info, size_t mem_size, asc_shader_init_func init_func, int flags) {
+    unsigned shader[2];
+    unsigned n = 0;
+    if (code_info.vtx.source_file != NULL) {
+        shader[n++] = asc_shader_compile(GL_VERTEX_SHADER, code_info.vtx);
+    }
+    if (code_info.frag.source_file != NULL) {
+        shader[n++] = asc_shader_compile(GL_FRAGMENT_SHADER, code_info.frag);
     }
+#ifndef NDEBUG
+    // in debug mode, exit early when a shader compilation failed
+    for (unsigned i = 0 ; i < n ; i++) {
+        if (shader[i] == 0) {
+            asc_error("Shader %u compilation failed.", i);
+            for (unsigned j = 0; j < n; j++) {
+                if (shader[j] > 0) {
+                    asc_dprintf("Delete shader: %u", shader[j]);
+                    glDeleteShader(shader[j]);
+                }
+            }
+            return NULL;
+        }
+    }
+#endif
     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);
-        }
+    if (shader_program != NULL) {
         asc_shader_link(shader, n, shader_program);
     }
-    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;
@@ -209,43 +236,6 @@
     return asc_error_catch_gl("Activating shader");
 }
 
-static int asc_shader_load_code_file(const char *filename, char **code) {
-    if (filename == NULL) {
-        *code = NULL;
-        return 0;
-    }
-    cxmutstr fpath = asc_filesystem_combine_paths(cx_strcast(asc_context.shader_path), cx_str(filename));
-    asc_dprintf("Load shader code from %" CX_PRIstr, CX_SFMT(fpath));
-    FILE *f = fopen(fpath.ptr, "r");
-    cx_strfree(&fpath);
-    if (f == NULL) return -1;
-    CxBuffer buffer;
-    cxBufferInit(&buffer, NULL, 1024, NULL, CX_BUFFER_AUTO_EXTEND);
-    cx_stream_copy(f, &buffer, (cx_read_func) fread, cxBufferWriteFunc);
-    cxBufferPut(&buffer, '\0');
-    cxBufferShrink(&buffer, 0);
-    *code = buffer.space;
-    return *code == NULL ? -1 : 0;
-}
-
-int asc_shader_load_code_files(AscShaderCodeInfo info, AscShaderCodes *codes) {
-    int ret = 0;
-    ret |= asc_shader_load_code_file(info.files.vtx, &codes->vtx);
-    codes->vtx_pp = info.defines.vtx;
-    codes->vtx_pp_list = info.defines.vtx_list;
-    codes->vtx_pp_list_select = info.defines.vtx_list_select;
-    ret |= asc_shader_load_code_file(info.files.frag, &codes->frag);
-    codes->frag_pp = info.defines.frag;
-    codes->frag_pp_list = info.defines.frag_list;
-    codes->frag_pp_list_select = info.defines.frag_list_select;
-    return ret;
-}
-
-void asc_shader_free_codes(AscShaderCodes codes) {
-    cxFreeDefault(codes.vtx);
-    cxFreeDefault(codes.frag);
-}
-
 const AscShaderProgram *asc_shader_register(unsigned int id, asc_shader_create_func create_func, int create_flags) {
     AscGLContext *glctx = asc_active_glctx;
 #ifndef NDEBUG
--- a/src/sprite.c	Thu Jul 24 20:58:00 2025 +0200
+++ b/src/sprite.c	Fri Jul 25 18:50:36 2025 +0200
@@ -45,10 +45,13 @@
 }
 
 static AscShaderProgram *asc_sprite_shader_create(int rect) {
-    return asc_shader_create((AscShaderCodeInfo){
-        .files.vtx = "sprite_vtx.glsl",
-        .files.frag = "sprite_frag.glsl",
-        .defines.frag = rect ? "#define USE_RECT" : NULL,
+    return asc_shader_create((AscShaderCodes) {
+        .vtx = {.source_file = "sprite_vtx.glsl"},
+        .frag = {
+            .source_file = "sprite_frag.glsl",
+            .preamble_code = (const char*[]){"#define USE_RECT"},
+            .preamble_code_flags = (uint64_t) rect
+        },
     }, sizeof(AscSpriteShader), asc_sprite_shader_init, 0);
 }
 
--- a/src/text.c	Thu Jul 24 20:58:00 2025 +0200
+++ b/src/text.c	Fri Jul 25 18:50:36 2025 +0200
@@ -44,10 +44,13 @@
 }
 
 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",
+    return asc_shader_create((AscShaderCodes){
+        .vtx = {.source_file = "sprite_vtx.glsl"},
+        .frag = {
+            .source_file = "sprite_frag.glsl",
+            .preamble_code = (const char*[]){"#define USE_RECT"},
+            .preamble_code_flags = 1
+        },
     }, sizeof(AscTextShader), asc_text_shader_init, flags);
 }
 

mercurial