/** * 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)); #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"); shader->setTexture = def->setTexture; // 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(err.code != ERROR_OK) { glDeleteShader(shader->vertexShaderId); errorChain(err); } glCompileShader(shader->vertexShaderId); err = errorGLCheck(); if(err.code != ERROR_OK) { 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(err.code != ERROR_OK) { glDeleteShader(shader->vertexShaderId); errorChain(err); } glShaderSource(shader->fragmentShaderId, 1, &def->frag, NULL); err = errorGLCheck(); if(err.code != ERROR_OK) { glDeleteShader(shader->vertexShaderId); glDeleteShader(shader->fragmentShaderId); errorChain(err); } glCompileShader(shader->fragmentShaderId); err = errorGLCheck(); if(err.code != ERROR_OK) { 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(err.code != ERROR_OK) { glDeleteShader(shader->vertexShaderId); glDeleteShader(shader->fragmentShaderId); errorChain(err); } glAttachShader(shader->shaderProgramId, shader->vertexShaderId); err = errorGLCheck(); if(err.code != ERROR_OK) { glDeleteProgram(shader->shaderProgramId); glDeleteShader(shader->vertexShaderId); glDeleteShader(shader->fragmentShaderId); errorChain(err); } glAttachShader(shader->shaderProgramId, shader->fragmentShaderId); err = errorGLCheck(); if(err.code != ERROR_OK) { glDeleteProgram(shader->shaderProgramId); glDeleteShader(shader->vertexShaderId); glDeleteShader(shader->fragmentShaderId); errorChain(err); } glLinkProgram(shader->shaderProgramId); err = errorGLCheck(); if(err.code != ERROR_OK) { 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." ); if(stringCompare(name, SHADER_UNLIT_PROJECTION) == 0) { SHADER_LEGACY.dirty |= SHADER_LEGACY_DIRTY_PROJ; glm_mat4_copy(mat, shader->proj); } else if(stringCompare(name, SHADER_UNLIT_VIEW) == 0) { SHADER_LEGACY.dirty |= SHADER_LEGACY_DIRTY_VIEW; glm_mat4_copy(mat, shader->view); } else if(stringCompare(name, SHADER_UNLIT_MODEL) == 0) { SHADER_LEGACY.dirty |= SHADER_LEGACY_DIRTY_MODEL; glm_mat4_copy(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." ); if(texture == NULL) { glDisable(GL_TEXTURE_2D); errorChain(errorGLCheck()); errorOk(); } glEnable(GL_TEXTURE_2D); errorChain(errorGLCheck()); glBindTexture(GL_TEXTURE_2D, texture->id); errorChain(errorGLCheck()); #else if(shader->setTexture == NULL) { assertUnreachable("Shader does not support setting textures."); } errorChain(shader->setTexture(shader, name, texture)); #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) != 0) { glMatrixMode(GL_MODELVIEW); errorChain(errorGLCheck()); glLoadIdentity(); errorChain(errorGLCheck()); glMultMatrixf((const GLfloat *)SHADER_LEGACY.boundShader->view); errorChain(errorGLCheck()); } if((SHADER_LEGACY.dirty & SHADER_LEGACY_DIRTY_MODEL) != 0) { glMatrixMode(GL_MODELVIEW); errorChain(errorGLCheck()); glMultMatrixf((const GLfloat *)SHADER_LEGACY.boundShader->model); errorChain(errorGLCheck()); } SHADER_LEGACY.dirty = 0; errorOk(); } #endif