/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "shadergl.h" #include "util/memory.h" #include "util/string.h" #include "assert/assertgl.h" #include "display/shader/shaderunlit.h" #ifdef DUSK_OPENGL_LEGACY shaderlegacygl_t SHADER_LEGACY = { 0 }; #endif errorret_t shaderInitGL(shadergl_t *shader, const shaderdefinitiongl_t *def) { assertNotNull(shader, "Shader cannot be null"); assertNotNull(def, "Shader definition cannot be null"); memoryZero(shader, sizeof(shadergl_t)); shader->definition = def; #ifdef DUSK_OPENGL_LEGACY glm_mat4_identity(shader->view); glm_mat4_identity(shader->proj); glm_mat4_identity(shader->model); SHADER_LEGACY.boundShader = NULL; errorOk(); #else assertNotNull(def->vert, "Vertex shader source cannot be null"); assertNotNull(def->frag, "Fragment shader source cannot be null"); // Create vertex shader shader->vertexShaderId = glCreateShader(GL_VERTEX_SHADER); errorret_t err = errorGLCheck(); errorChain(err); glShaderSource(shader->vertexShaderId, 1, &def->vert, NULL); err = errorGLCheck(); if(errorIsNotOk(err)) { glDeleteShader(shader->vertexShaderId); errorChain(err); } glCompileShader(shader->vertexShaderId); err = errorGLCheck(); if(errorIsNotOk(err)) { glDeleteShader(shader->vertexShaderId); errorChain(err); } GLint ok = 0; glGetShaderiv(shader->vertexShaderId, GL_COMPILE_STATUS, &ok); if(!ok) { GLchar log[1024]; glGetShaderInfoLog(shader->vertexShaderId, sizeof(log), NULL, log); glDeleteShader(shader->vertexShaderId); errorThrow("Vertex shader compilation failed: %s", log); } // Create fragment shader shader->fragmentShaderId = glCreateShader(GL_FRAGMENT_SHADER); err = errorGLCheck(); if(errorIsNotOk(err)) { glDeleteShader(shader->vertexShaderId); errorChain(err); } glShaderSource(shader->fragmentShaderId, 1, &def->frag, NULL); err = errorGLCheck(); if(errorIsNotOk(err)) { glDeleteShader(shader->vertexShaderId); glDeleteShader(shader->fragmentShaderId); errorChain(err); } glCompileShader(shader->fragmentShaderId); err = errorGLCheck(); if(errorIsNotOk(err)) { glDeleteShader(shader->vertexShaderId); glDeleteShader(shader->fragmentShaderId); errorChain(err); } glGetShaderiv(shader->fragmentShaderId, GL_COMPILE_STATUS, &ok); if(!ok) { GLchar log[1024]; glGetShaderInfoLog(shader->fragmentShaderId, sizeof(log), NULL, log); glDeleteShader(shader->vertexShaderId); glDeleteShader(shader->fragmentShaderId); errorThrow("Fragment shader compilation failed: %s", log); } // Create shader program shader->shaderProgramId = glCreateProgram(); err = errorGLCheck(); if(errorIsNotOk(err)) { glDeleteShader(shader->vertexShaderId); glDeleteShader(shader->fragmentShaderId); errorChain(err); } glAttachShader(shader->shaderProgramId, shader->vertexShaderId); err = errorGLCheck(); if(errorIsNotOk(err)) { glDeleteProgram(shader->shaderProgramId); glDeleteShader(shader->vertexShaderId); glDeleteShader(shader->fragmentShaderId); errorChain(err); } glAttachShader(shader->shaderProgramId, shader->fragmentShaderId); err = errorGLCheck(); if(errorIsNotOk(err)) { glDeleteProgram(shader->shaderProgramId); glDeleteShader(shader->vertexShaderId); glDeleteShader(shader->fragmentShaderId); errorChain(err); } glLinkProgram(shader->shaderProgramId); err = errorGLCheck(); if(errorIsNotOk(err)) { glDeleteProgram(shader->shaderProgramId); glDeleteShader(shader->vertexShaderId); glDeleteShader(shader->fragmentShaderId); errorChain(err); } ok = 0; glGetProgramiv(shader->shaderProgramId, GL_LINK_STATUS, &ok); if(!ok) { GLchar log[1024]; glGetProgramInfoLog(shader->shaderProgramId, sizeof(log), NULL, log); glDeleteProgram(shader->shaderProgramId); glDeleteShader(shader->vertexShaderId); glDeleteShader(shader->fragmentShaderId); errorThrow("Shader program linking failed: %s", log); } #endif errorOk(); } errorret_t shaderParamGetLocationGL( shadergl_t *shader, const char_t *name, GLint *location ) { assertNotNull(shader, "Shader cannot be null"); assertStrLenMin(name, 1, "Uniform name cannot be empty"); assertNotNull(location, "Location cannot be null"); #ifdef DUSK_OPENGL_LEGACY assertUnreachable("Cannot get uniform locations on legacy opengl."); #else *location = glGetUniformLocation(shader->shaderProgramId, name); errorChain(errorGLCheck()); if(*location == -1) { errorThrow("Uniform '%s' not found in shader.", name); } #endif errorOk(); } errorret_t shaderSetMatrixGL( shadergl_t *shader, const char_t *name, mat4 mat ) { assertNotNull(shader, "Shader cannot be null"); assertStrLenMin(name, 1, "Uniform name cannot be empty"); assertNotNull(mat, "Matrix data cannot be null"); #ifdef DUSK_OPENGL_LEGACY assertTrue( SHADER_LEGACY.boundShader == shader, "Shader must be bound to set legacy matrices." ); // Use unaligned copy to safely handle possibly unaligned input matrices if(stringCompare(name, SHADER_UNLIT_PROJECTION) == 0) { SHADER_LEGACY.dirty |= SHADER_LEGACY_DIRTY_PROJ; glm_mat4_ucopy(mat, shader->proj); } else if(stringCompare(name, SHADER_UNLIT_VIEW) == 0) { SHADER_LEGACY.dirty |= SHADER_LEGACY_DIRTY_VIEW; glm_mat4_ucopy(mat, shader->view); } else if(stringCompare(name, SHADER_UNLIT_MODEL) == 0) { SHADER_LEGACY.dirty |= SHADER_LEGACY_DIRTY_MODEL; glm_mat4_ucopy(mat, shader->model); } else { assertUnreachable("Cannot use a custom matrix on legacy opengl."); } #else GLint location; errorChain(shaderParamGetLocationGL(shader, name, &location)); glUniformMatrix4fv(location, 1, GL_FALSE, (const GLfloat *)mat); errorChain(errorGLCheck()); #endif errorOk(); } errorret_t shaderSetTextureGL( shadergl_t *shader, const char_t *name, texture_t *texture ) { assertNotNull(shader, "Shader cannot be null"); assertStrLenMin(name, 1, "Uniform name cannot be empty"); #ifdef DUSK_OPENGL_LEGACY assertStringEqual( name, SHADER_UNLIT_TEXTURE, "Only one texture supported in legacy opengl." ); // glActiveTexture(GL_TEXTURE0); errorChain(errorGLCheck()); if(texture == NULL) { glDisable(GL_TEXTURE_2D); errorChain(errorGLCheck()); errorOk(); } glEnable(GL_TEXTURE_2D); errorChain(errorGLCheck()); glBindTexture(GL_TEXTURE_2D, texture->id); errorChain(errorGLCheck()); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); errorChain(errorGLCheck()); // glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); // errorChain(errorGLCheck()); // glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); // errorChain(errorGLCheck()); // glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE); // errorChain(errorGLCheck()); // glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); // errorChain(errorGLCheck()); // glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PRIMARY_COLOR); // errorChain(errorGLCheck()); // glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); // errorChain(errorGLCheck()); #else assertNotNull(shader->definition, "Shader definition cannot be null"); assertNotNull(shader->definition->setTexture, "Shader cannot do textures."); errorChain(shader->definition->setTexture(shader, name, texture)); #endif errorOk(); } errorret_t shaderSetColorGL( shadergl_t *shader, const char_t *name, color_t color ) { assertNotNull(shader, "Shader cannot be null"); assertStrLenMin(name, 1, "Uniform name cannot be empty"); #ifdef DUSK_OPENGL_LEGACY // if(color.a == 0) { // glDisable(GL_TEXTURE_2D); // errorChain(errorGLCheck()); // errorOk(); // } // glActiveTexture(GL_TEXTURE1); // errorChain(errorGLCheck()); // if(color.r == 255 && color.g == 255 && color.b == 255) { // glDisable(GL_TEXTURE_2D); // errorChain(errorGLCheck()); // errorOk(); // } // glEnable(GL_TEXTURE_2D); // errorChain(errorGLCheck()); // glBindTexture(GL_TEXTURE_2D, TEXTURE_WHITE.id); // errorChain(errorGLCheck()); // GLfloat tint[4] = { // ((float_t)color.r) / 255.0f, // ((float_t)color.g) / 255.0f, // ((float_t)color.b) / 255.0f, // ((float_t)color.a) / 255.0f // }; // glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, tint); // errorChain(errorGLCheck()); // glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); // errorChain(errorGLCheck()); // glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); // errorChain(errorGLCheck()); // glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS); // errorChain(errorGLCheck()); // glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); // errorChain(errorGLCheck()); // glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT); // errorChain(errorGLCheck()); // glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); // errorChain(errorGLCheck()); glColor4f( (float_t)color.r / 255.0f, (float_t)color.g / 255.0f, (float_t)color.b / 255.0f, (float_t)color.a / 255.0f ); errorChain(errorGLCheck()); #else GLint location; errorChain(shaderParamGetLocationGL(shader, name, &location)); glUniform4f( location, color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f ); errorChain(errorGLCheck()); #endif errorOk(); } errorret_t shaderBindGL(shadergl_t *shader) { #ifdef DUSK_OPENGL_LEGACY assertNotNull(shader, "Cannot bind a null shader."); SHADER_LEGACY.boundShader = shader; SHADER_LEGACY.dirty = ( SHADER_LEGACY_DIRTY_MODEL | SHADER_LEGACY_DIRTY_PROJ | SHADER_LEGACY_DIRTY_VIEW ); #else assertNotNull(shader, "Shader cannot be null"); glUseProgram(shader->shaderProgramId); errorChain(errorGLCheck()); #endif errorOk(); } errorret_t shaderDisposeGL(shadergl_t *shader) { assertNotNull(shader, "Shader cannot be null"); #ifdef DUSK_OPENGL_LEGACY SHADER_LEGACY.boundShader = NULL; #else if(shader->shaderProgramId != 0) { glDeleteProgram(shader->shaderProgramId); } if(shader->vertexShaderId != 0) { glDeleteShader(shader->vertexShaderId); } if(shader->fragmentShaderId != 0) { glDeleteShader(shader->fragmentShaderId); } assertNoGLError("Failed disposing shader"); #endif memoryZero(shader, sizeof(shadergl_t)); errorOk(); } #ifdef DUSK_OPENGL_LEGACY errorret_t shaderLegacyMatrixUpdate() { assertNotNull(SHADER_LEGACY.boundShader, "No shader is currently bound."); if((SHADER_LEGACY.dirty & SHADER_LEGACY_DIRTY_PROJ) != 0) { glMatrixMode(GL_PROJECTION); errorChain(errorGLCheck()); glLoadIdentity(); errorChain(errorGLCheck()); glMultMatrixf((const GLfloat *)SHADER_LEGACY.boundShader->proj); errorChain(errorGLCheck()); } if( (SHADER_LEGACY.dirty & (SHADER_LEGACY_DIRTY_VIEW | SHADER_LEGACY_DIRTY_MODEL)) != 0 ) { glMatrixMode(GL_MODELVIEW); errorChain(errorGLCheck()); mat4 viewModel; glm_mat4_mul( SHADER_LEGACY.boundShader->view, SHADER_LEGACY.boundShader->model, viewModel ); glLoadMatrixf((const GLfloat *)viewModel); errorChain(errorGLCheck()); } SHADER_LEGACY.dirty = 0; errorOk(); } #endif