Fri, 25 Jul 2025 19:19:54 +0200
simplify how pre-defined shader IDs work
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * Copyright 2023 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. */ #include "ascension/shader.h" #include "ascension/context.h" #include "ascension/error.h" #include "ascension/filesystem.h" #include <string.h> #include <stdio.h> #include <GL/glew.h> #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 information * @return the compiled shader */ 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; } 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"; uint64_t test_flag = 1; unsigned select_index = 0; 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++] = "#line 1\n"; code_array[code_count++] = source_text; // compile 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 = cxMallocDefault(1024); glGetShaderInfoLog(id, 1024, NULL, log); glDeleteShader(id); asc_error("Shader %u compilation failed.\n%s", id, log); cxFreeDefault(log); return 0; } } /** * This function links shaders into a program. * * The OpenGL ID of the program will be zero when something went wrong. * * @param shader the shader IDs to link * @param n the number of shaders * @param prog the struct where to store the result */ static void asc_shader_link(unsigned shader[], unsigned n, AscShaderProgram *prog) { prog->gl_id = 0; GLint success; GLint id = glCreateProgram(); if (id <= 0) { for (unsigned i = 0; i < n; i++) { asc_dprintf("Delete shader: %u", shader[i]); glDeleteShader(shader[i]); } asc_error("glCreateProgram failed: %s", glGetError()); return; } for (unsigned i = 0; i < n; i++) { glAttachShader(id, shader[i]); } glLinkProgram(id); glGetProgramiv(id, GL_LINK_STATUS, &success); for (unsigned i = 0; i < n; i++) { asc_dprintf("Delete shader: %u", shader[i]); glDeleteShader(shader[i]); } if (success) { asc_dprintf("Shader Program %u linked.", 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"); } 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); } } void asc_shader_free(AscShaderProgram *program) { if (program->gl_id > 0) { asc_dprintf("Delete Shader Program %u", program->gl_id); glDeleteProgram(program->gl_id); } if (program->destr_func) { program->destr_func(program); } cxFreeDefault(program); } static bool asc_shader_invalid(const AscShaderProgram *program) { return program == NULL || program->gl_id == 0; } 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) { 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); if (shader_program != NULL) { asc_shader_link(shader, n, shader_program); } 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 (asc_shader_invalid(shader)) { asc_active_glctx->active_program = 0; glUseProgram(0); return -1; } if (asc_active_glctx->active_program == shader->gl_id) { // already using this shader - continue return 0; } asc_active_glctx->active_program = shader->gl_id; glUseProgram(shader->gl_id); glUniformMatrix4fv(shader->projection, 1, GL_FALSE, camera->projection); glUniformMatrix4fv(shader->view, 1, GL_FALSE, camera->view); return asc_error_catch_gl("Activating shader"); } const AscShaderProgram *asc_shader_register(unsigned int id, asc_shader_create_func create_func, int create_flags) { AscGLContext *glctx = asc_active_glctx; #ifndef NDEBUG { const AscShaderProgram *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 AscShaderProgram *prog = create_func(create_flags); if (prog == NULL) { // create an empty program to prevent future loading attempts prog = cxZallocDefault(sizeof(AscShaderProgram)); } prog->id = id; cxListAdd(glctx->shaders, prog); return prog; } const AscShaderProgram *asc_shader_lookup(unsigned int id) { CxIterator iter = cxListIterator(asc_active_glctx->shaders); cx_foreach(const AscShaderProgram *, prog, iter) { if (prog->id == id) return prog; } return NULL; } const AscShaderProgram *asc_shader_lookup_or_create(unsigned int id, asc_shader_create_func create_func, int create_flags) { const AscShaderProgram *prog = asc_shader_lookup(id); if (prog == NULL) { return asc_shader_register(id, create_func, create_flags); } return prog; } void asc_shader_clear_registry(void) { cxListClear(asc_active_glctx->shaders); // also clear the active program to avoid accidental matches with newly created shaders asc_shader_use(NULL, NULL); } asc_uniform_loc asc_shader_get_uniform_loc(const AscShaderProgram *shader, const char *name) { 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); } void asc_shader_upload_col4f(int uniform_id, asc_col4f color) { glUniform4f(uniform_id, color.red, color.green, color.blue, color.alpha); } void asc_shader_upload_float(int uniform_id, float value) { glUniform1f(uniform_id, value); } void asc_shader_upload_vec2f(int uniform_id, asc_vec2f value) { glUniform2f(uniform_id, value.x, value.y); }