src/shader.c

Fri, 25 Jul 2025 19:19:54 +0200

author
Mike Becker <universe@uap-core.de>
date
Fri, 25 Jul 2025 19:19:54 +0200
changeset 226
18327d2df79d
parent 225
c42c7d1a3c34
permissions
-rw-r--r--

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);
}

mercurial