/* * Copyright (C) 2018-2020 - Vito Caputo - * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3 as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include "glad.h" #include "macros.h" #include "shader.h" typedef struct shader_t { unsigned program, refcnt; unsigned n_uniforms, n_attributes; const char **uniforms, **attributes; int *uniform_locations, *attribute_locations; const char *vs_path, *fs_path; struct timespec vs_mtime, fs_mtime; shader_uniform_t *active_uniforms; int n_active_uniforms; int locations[]; } shader_t; unsigned int shader_pair_new_bare(const char *vs_src, const char *fs_src) { unsigned int vs, fs, shader; int shader_success; char shader_info[4096]; vs = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vs, 1, &vs_src, NULL); glCompileShader(vs); glGetShaderiv(vs, GL_COMPILE_STATUS, &shader_success); if (!shader_success) { glGetShaderInfoLog(vs, sizeof(shader_info), NULL, shader_info); warn_if(1, "Error compiling vertex shader: \"%s\"", shader_info); glDeleteShader(vs); return 0; } fs = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fs, 1, &fs_src, NULL); glCompileShader(fs); glGetShaderiv(fs, GL_COMPILE_STATUS, &shader_success); if (!shader_success) { glGetShaderInfoLog(fs, sizeof(shader_info), NULL, shader_info); warn_if(1, "Error compiling fragment shader: \"%s\"", shader_info); glDeleteShader(fs); return 0; } shader = glCreateProgram(); glAttachShader(shader, vs); glAttachShader(shader, fs); glLinkProgram(shader); glGetProgramiv(shader, GL_LINK_STATUS, &shader_success); if (!shader_success) { glGetProgramInfoLog(shader, sizeof(shader_info), NULL, shader_info); warn_if(1, "Error linking shader program: \"%s\"", shader_info); glDeleteProgram(shader); shader = 0; } glDeleteShader(vs); glDeleteShader(fs); return shader; } /* query a shader for its active uniforms, returns results in res_uniforms * the returned results are owned by the shader, do not free them, they will be * cleaned up with shader_free() or reallocated by shader_reload_files() */ void shader_active_uniforms(shader_t *shader, int *res_n_uniforms, const shader_uniform_t **res_uniforms) { assert(shader); assert(res_n_uniforms); assert(res_uniforms); *res_n_uniforms = shader->n_active_uniforms; *res_uniforms = shader->active_uniforms; } static void refresh_active_uniforms(shader_t *shader) { int n, maxlen; assert(shader); if (shader->active_uniforms) { for (int i = 0; i < shader->n_active_uniforms; i++) free(shader->active_uniforms[i].name); free(shader->active_uniforms); shader->active_uniforms = NULL; shader->n_active_uniforms = 0; } glGetProgramiv(shader->program, GL_ACTIVE_UNIFORMS, &n); glGetProgramiv(shader->program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxlen); /* FIXME: this is currently written such that allocation failures just cut the * active uniforms short instead of throwing an error, but should probably * be rewritten to fail and report the ENOMEM errors. */ shader->active_uniforms = calloc(n, sizeof(shader_uniform_t)); if (shader->active_uniforms) { for (int i = 0; i < n; i++) { shader->active_uniforms[i].name = calloc(maxlen, sizeof(char)); if (!shader->active_uniforms[i].name) break; glGetActiveUniform(shader->program, i, maxlen, /* length */ NULL, /* size */ NULL, &shader->active_uniforms[i].type, shader->active_uniforms[i].name); shader->active_uniforms[i].location = glGetUniformLocation(shader->program, shader->active_uniforms[i].name); shader->n_active_uniforms++; } } } static void get_locations(shader_t *shader) { for (unsigned i = 0; i < shader->n_uniforms; i++) shader->uniform_locations[i] = glGetUniformLocation(shader->program, shader->uniforms[i]); for (unsigned i = 0; i < shader->n_attributes; i++) shader->attribute_locations[i] = glGetAttribLocation(shader->program, shader->attributes[i]); } shader_t * shader_pair_new(const char *vs_src, const char *fs_src, unsigned n_uniforms, const char **uniforms, unsigned n_attributes, const char **attributes) { shader_t *shader; assert(vs_src); assert(fs_src); assert(uniforms || !n_uniforms); assert(attributes || !n_attributes); shader = calloc(1, sizeof(shader_t) + (n_uniforms + n_attributes) * sizeof(int)); fatal_if(!shader, "Unable to allocate shader"); shader->program = shader_pair_new_bare(vs_src, fs_src); shader->refcnt++; shader->uniforms = uniforms; shader->n_uniforms = n_uniforms; shader->attributes = attributes; shader->n_attributes = n_attributes; shader->uniform_locations = shader->locations; shader->attribute_locations = &shader->locations[n_uniforms]; get_locations(shader); refresh_active_uniforms(shader); return shader; } static char * load_file(const char *path) { char *buf = NULL; FILE *f; size_t size = 0, used = 0; f = fopen(path, "r"); if (!f) { fprintf(stderr, "Unable to open \"%s\"", path); return NULL; } do { size += 8192; buf = realloc(buf, size); fatal_if(!buf, "unable to enlarge buf"); used += fread(&buf[used], 1, 8192, f); } while (used == size); buf[used] = '\0'; fclose(f); return buf; } /* replace the shader program if the paths have been modified */ /* returns -1 on failure */ int shader_reload_files(shader_t *shader) { char *vs_src = NULL, *fs_src = NULL; int ret = -1; assert(shader); assert(shader->vs_path); assert(shader->fs_path); vs_src = load_file(shader->vs_path); fs_src = load_file(shader->fs_path); if (vs_src && fs_src) { unsigned program; program = shader_pair_new_bare(vs_src, fs_src); if (program) { glDeleteProgram(shader->program); shader->program = program; ret = 1; } } free(vs_src); free(fs_src); get_locations(shader); refresh_active_uniforms(shader); return ret; } shader_t * shader_pair_new_files(const char *vs_path, const char *fs_path, unsigned n_uniforms, const char **uniforms, unsigned n_attributes, const char **attributes) { shader_t *shader; assert(vs_path); assert(fs_path); assert(uniforms || !n_uniforms); assert(attributes || !n_attributes); shader = calloc(1, sizeof(shader_t) + (n_uniforms + n_attributes) * sizeof(int)); fatal_if(!shader, "Unable to allocate shader"); shader->vs_path = strdup(vs_path); fatal_if(!shader->vs_path, "unable to dup vs_path \"%s\"", vs_path); shader->fs_path = strdup(fs_path); fatal_if(!shader->fs_path, "unable to dup fs_path \"%s\"", fs_path); shader->n_uniforms = n_uniforms; shader->uniforms = uniforms; shader->n_attributes = n_attributes; shader->attributes = attributes; shader->uniform_locations = shader->locations; shader->attribute_locations = &shader->locations[n_uniforms]; shader->refcnt++; (void) shader_reload_files(shader); return shader; } void shader_ref(shader_t *shader) { assert(shader); shader->refcnt++; } shader_t * shader_free(shader_t *shader) { assert(shader); shader->refcnt--; if (shader->refcnt > 0) return shader; glDeleteProgram(shader->program); free(shader); return NULL; } void shader_use(shader_t *shader, unsigned *res_n_uniforms, int **res_uniform_locations, unsigned *res_n_attributes, int **res_attribute_locations) { assert(shader); if (res_n_uniforms) *res_n_uniforms = shader->n_uniforms; if (res_uniform_locations) *res_uniform_locations = shader->uniform_locations; if (res_n_attributes) *res_n_attributes = shader->n_attributes; if (res_attribute_locations) *res_attribute_locations = shader->attribute_locations; glUseProgram(shader->program); }