Fri, 25 Jul 2025 18:50:36 +0200
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); }