Wed, 02 Jul 2025 23:21:17 +0200
resolve TODOs regarding input.h
a) mouse position must be integer, because it can be negative (though rarely)
b) we should not trade "access complexity" for space in the scancodes array
/* * 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> /** * 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 * @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) { GLuint id = glCreateShader(type); if (id == 0) { asc_error("glCreateShader failed: %s", glGetError()); return 0; } GLint success; const char *code_array[20]; 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; 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]; } 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]); } // compile glShaderSource(id, code_count, code_array, length_array); glCompileShader(id); glGetShaderiv(id, GL_COMPILE_STATUS, &success); if (success) { asc_dprintf("Shader %u compiled", id); return id; } else { char *log = malloc(1024); glGetShaderInfoLog(id, 1024, NULL, log); glDeleteShader(id); asc_error("Shader %u compilation failed.\n%s", id, log); free(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); } 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); } 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, prog); return prog; } int asc_shader_use(const AscShaderProgram *shader, const AscCamera *camera) { if (shader == NULL) { asc_active_glctx->active_program = 0; glUseProgram(0); return 0; } if (asc_active_glctx->active_program == shader->gl_id) return 0; if (asc_shader_invalid(shader)) return -1; 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_all_gl(); } 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 { 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) return NULL; 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_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); }