#include #include #include #include #include #include #include #include "asset.h" #include "dumbLog.h" #include "dummy_shader.h" #define GL_DEBUG_IMPLEMENTATION #include "GLDebug.h" #include "shader.h" #include "util.h" // NOTE: forward declarations bool parseShader(MemoryArena* arena, GLContext* gl_ctx, ShaderProgram* s); void loadDummyShader(); void initCTXSizes(GLContext* gl_ctx); bool compileAndLinkShader(ShaderProgram* shader, const char* vert_src, const char* frag_src, GLuint& vs_id, GLuint& fs_id); u32 getGLTypeSize(GLenum e); GLTexture* getFreeGLTexture(GLContext* gl_ctx); bool loadGLTexture(Texture* image, GLuint& tex_id); void* getMeshData(const Mesh& m, const GLBufferToAttribMapping& mapping); // NOTE: interface GLContext* initGLContext(MemoryArena* arena, u32 max_shaders, u32 max_textures, u32 max_ubos) { GLContext* gl_ctx = ARENA_ALLOC(arena, GLContext, 1); gl_ctx->max_shaders = max_shaders; gl_ctx->shaders = ARENA_ALLOC(arena, ShaderProgram, max_shaders); gl_ctx->max_ubos = max_ubos; gl_ctx->uniform_buffers = ARENA_ALLOC(arena, GLBuffer, max_ubos); // NOTE: initialize GLBuffer struct members to sane defaults for (u32 i = 0; i < max_ubos; i++) { GLBuffer& buf = gl_ctx->uniform_buffers[i]; buf.location = -1; buf.binding_idx = -1; } gl_ctx->max_textures = max_textures; gl_ctx->textures = ARENA_ALLOC(arena, GLTexture, max_textures); // NOTE: load a dummy shader to avoid chicken and egg problem where we need // GLContext info before we can parse a shader, which needs GLContext info loadDummyShader(); initCTXSizes(gl_ctx); return gl_ctx; } bool addShaderProgram(MemoryArena* arena, GLContext* gl_ctx, const char* vs, const char* fs, const char* name) { LOGF(Info, "loading shader, %s\n", name); const u32 max_len = 256; char input_str[max_len]; snprintf(input_str, max_len, "%s%s", vs, fs); u64 hash = utilFNV64a_str(input_str); if (getShaderByHash(gl_ctx, hash)) { LOGF(Error, "shader is already loaded\n"); return false; } ShaderProgram* s = getFreeShader(gl_ctx); if (s) { s->name = arenaCopyCStr(arena, name); s->hash = hash; size_t vs_size, fs_size; const char* v_str = (const char*) SDL_LoadFile(vs, &vs_size); const char* f_str = (const char*) SDL_LoadFile(fs, &fs_size); GLuint vs_id, fs_id; if (compileAndLinkShader(s, v_str, f_str, vs_id, fs_id)) { glDetachShader(s->prog_id, vs_id); glDetachShader(s->prog_id, fs_id); glDeleteShader(vs_id); glDeleteShader(fs_id); return parseShader(arena, gl_ctx, s); } LOGF(Error, "Error linking shader\n"); return false; } LOGF(Error, "error loading shader\n"); return false; } ShaderProgram* getFreeShader(GLContext* gl_ctx) { if (gl_ctx->num_shaders >= gl_ctx->max_shaders) { LOGF(Error, "GLContext->shaders full\n"); return nullptr; } ShaderProgram* s = &gl_ctx->shaders[gl_ctx->num_shaders++]; return s; } ShaderProgram* getShaderByHash(GLContext* gl_ctx, u64 hash) { for (u32 i; i < gl_ctx->num_shaders; i++) { if (gl_ctx->shaders[i].hash == hash) return &gl_ctx->shaders[i]; } return nullptr; } ShaderProgram* getShaderByName(const char* name, GLContext* gl_ctx) { for (u32 i = 0; i < gl_ctx->num_shaders; i++) { if (utilCStrMatch(name, gl_ctx->shaders[i].name)) return &gl_ctx->shaders[i]; } LOGF(Error, "shader not found, %s\n", name); return nullptr; } ShaderProgram* getShaderByID(GLContext* gl_ctx, GLuint prog_id) { for (u32 i = 0; i < gl_ctx->num_shaders; i++) { if (gl_ctx->shaders[i].prog_id) return &gl_ctx->shaders[i]; } LOGF(Error, "shader not found, %d\n", prog_id); return nullptr; } GLBuffer* getFreeUBO(GLContext* gl_ctx) { if (gl_ctx->num_ubos < gl_ctx->max_ubos) return &gl_ctx->uniform_buffers[gl_ctx->num_ubos++]; LOGF(Error, "no free Uniform Buffer Objects\n"); return nullptr; } GLBuffer* getUBOByName(GLContext* gl_ctx, const char* name) { GLBuffer* ubo_out = nullptr; for (u32 i = 0; i < gl_ctx->num_ubos; i++) { GLBuffer* buf = &gl_ctx->uniform_buffers[i]; if (utilCStrMatch(name, buf->name)) ubo_out = buf; } if (ubo_out == nullptr) LOGF(Error, "GLBuffer, \"%s\", not found\n", name); return ubo_out; } GLTexture* getGLTexture(GLContext* gl_ctx, Texture* diffuse_img) { u64 fp_hash = utilFNV64a_str(diffuse_img->file_path); for (u32 i = 0; i < gl_ctx->num_textures; i++) { GLTexture* glt = &gl_ctx->textures[i]; if (glt->filepath_hash == fp_hash) return glt; } GLTexture* glt = getFreeGLTexture(gl_ctx); if (!glt) return nullptr; glt->pixel_format = (diffuse_img->num_channels == 3) ? GL_RGB : GL_RGBA; glt->width = diffuse_img->w; glt->height = diffuse_img->h; glt->filepath_hash = diffuse_img->filepath_hash; if (loadGLTexture(diffuse_img, glt->id)) return glt; LOGF(Error, "Error, unable to load texture\n"); return nullptr; } void updateGLBuffer(GLBuffer* gl_buf, void* data) { assert(gl_buf && data); glBindBuffer(gl_buf->target, gl_buf->id); glBufferSubData(gl_buf->target, 0, gl_buf->data_size, data); } void renderVAO(GLMesh* glmesh, glm::mat4* node_xform, ShaderProgram* shader, GLTexture* gl_tex) { glUseProgram(shader->prog_id); glBindVertexArray(glmesh->vao_id); for (u32 i = 0; i < shader->num_uniforms; i++) { const GLUniform& uniform = shader->uniforms[i]; if (uniform.uniform_type == UNIFORM_NODE_XFORM) { glUniformMatrix4fv(uniform.location, 1, GL_FALSE, (float*) node_xform); } else if (glmesh->has_texture && uniform.uniform_type == UNIFORM_SAMPLER) { glBindTexture(GL_TEXTURE_2D, gl_tex->id); glUniform1i(uniform.location, 0); } } glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, glmesh->element_buf->id); glDrawElements( glmesh->draw_mode, glmesh->num_indices, GL_UNSIGNED_SHORT, 0); glBindVertexArray(0); } void initTransforms(MemoryArena* arena, Transforms* xforms, GLBuffer* xform_ubo, GLContext* gl_ctx, float fov, float near_clip_plane, float aspect_ratio) { xforms->proj_xform = glm::infinitePerspective( glm::radians(fov), aspect_ratio, near_clip_plane); glGenBuffers(1, &xform_ubo->id); xform_ubo->target = GL_UNIFORM_BUFFER; xform_ubo->data_type = GL_FLOAT; xform_ubo->name = arenaCopyCStr(arena, "matrices"); xform_ubo->data_size = sizeof(*xforms); glBindBuffer(xform_ubo->target, xform_ubo->id); glBufferData(xform_ubo->target, xform_ubo->data_size, xforms, GL_DYNAMIC_DRAW); // NOTE: bindbufferbase xform_ubo->binding_idx = gl_ctx->binding_count++; glBindBufferBase(xform_ubo->target, xform_ubo->binding_idx, xform_ubo->id); glBindBuffer(xform_ubo->target, 0); } GLVertexAttrib* getVertexAttribByName(ShaderProgram* shader, const char* name) { for (u32 i = 0; i < shader->num_vertex_attribs; i++) { if (strncmp(shader->vertex_attribs[i].name, name, 256) == 0) return &shader->vertex_attribs[i]; } LOGF(Debug, "attribute: %s, not found on shader: %s\n", name, shader->name); return nullptr; } GLMesh initGLMesh(const Mesh& m, u32 num_mappings, GLenum draw_mode) { GLMesh glm = {0}; glm.num_indices = m.num_indices; glm.draw_mode = draw_mode; glm.usage = GL_STATIC_DRAW; // TODO: logic for updating dynamic meshes glm.num_vertex_attrib_buffers = num_mappings; glm.vertex_attrib_buffers = UTIL_ALLOC(num_mappings, GLBuffer); glm.element_buf = UTIL_ALLOC(1, GLBuffer); glm.node_xform = UTIL_ALLOC(1, glm::mat4); *glm.node_xform = glm::mat4(1); return glm; } void initGLAttribBuffer(GLBuffer* buf, GLenum target, GLVertexAttrib* attrib) { glGenBuffers(1, &buf->id); buf->target = target; buf->data_type = attrib->data_type; buf->location = attrib->location; buf->name = utilAllocateCStr(attrib->name); } // FIXME: might as well pass in pointer to GLMesh, since that's how we're // going to use this, can void copying GLMesh twice that way GLMesh loadGLMesh(const Mesh& m, GLenum draw_mode, GLTexture* diffuse_texture, u32 num_mappings, GLBufferToAttribMapping mappings[]) { // NOTE: need to use freeGLMesh() when freeing because we're not storing // the GLBuffers on a memory arean (yet) GLMesh glm = initGLMesh(m, num_mappings, draw_mode); if (diffuse_texture) { glm.has_texture = true; glm.tex_id = diffuse_texture->id; } glGenVertexArrays(1, &glm.vao_id); glBindVertexArray(glm.vao_id); for (u32 i = 0; i < num_mappings; i++) { GLBuffer& buf = glm.vertex_attrib_buffers[i]; GLVertexAttrib* attrib = mappings[i].attrib; attrib->buf_type = mappings[i].buf_type; u32 type_size = getGLTypeSize(attrib->data_type); assert(type_size > 0); buf.data_size = m.num_vertices * type_size; void* mesh_buf_data = getMeshData(m, mappings[i]); assert(mesh_buf_data); initGLAttribBuffer(&buf, GL_ARRAY_BUFFER, attrib); glBindBuffer(buf.target, buf.id); glBufferData(buf.target, buf.data_size, mesh_buf_data, glm.usage); glVertexAttribPointer(attrib->location, attrib->num_components, attrib->component_type, GL_FALSE, 0, 0); glEnableVertexAttribArray(attrib->location); } glGenBuffers(1, &glm.element_buf->id); glm.element_buf->target = GL_ELEMENT_ARRAY_BUFFER; glm.element_buf->data_type = GL_UNSIGNED_SHORT; glm.element_buf->data_size = m.num_indices * sizeof(u16); glBindBuffer(glm.element_buf->target, glm.element_buf->id); glBufferData(glm.element_buf->target, glm.element_buf->data_size, m.indices, glm.usage); // TODO: many of these GL functions can set an error state // TODO: return error status glBindVertexArray(0); return glm; } void freeGLMesh(GLMesh* glm) { if (glm) { for (u32 i = 0; i < glm->num_vertex_attrib_buffers; i++) utilSafeFree(glm->vertex_attrib_buffers[i].name); utilSafeFree(glm->vertex_attrib_buffers); utilSafeFree(glm->element_buf); // NOTE: don't free the GLMesh because it is part of a memory arena //utilSafeFree(glm); } } // NOTE: internal void* getMeshData(const Mesh& m, const GLBufferToAttribMapping& mapping) { switch (mapping.buf_type) { case VERTEX: return m.vertices; case NORMAL: return m.normals; case UV: return m.uvs; case COLOR: return m.colors; default: return nullptr; } } GLTexture* getFreeGLTexture(GLContext* gl_ctx) { if (gl_ctx->num_textures < gl_ctx->max_textures) return &gl_ctx->textures[gl_ctx->num_textures++]; LOGF(Error, "no free textures\n"); return nullptr; } bool loadGLTexture(Texture* image, GLuint& tex_id) { glGenTextures(1, &tex_id); glBindTexture(GL_TEXTURE_2D, tex_id); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); GLenum pixel_format = (image->num_channels == 3) ? GL_RGB : GL_RGBA; glTexImage2D(GL_TEXTURE_2D, 0, pixel_format, image->w, image->h, 0, pixel_format, GL_UNSIGNED_BYTE, image->pixels); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); return (glGetError() == GL_NO_ERROR); } bool compileAndLinkShader(ShaderProgram* shader, const char* vert_src, const char* frag_src, GLuint& vs_id, GLuint& fs_id) { if (vert_src && frag_src && strlen(vert_src) > 0 && strlen(frag_src) > 0) { vs_id = glCreateShader(GL_VERTEX_SHADER); fs_id = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(vs_id, 1, &vert_src, NULL); glShaderSource(fs_id, 1, &frag_src, NULL); glCompileShader(vs_id); assert(glGetError() == GL_NO_ERROR); glCompileShader(fs_id); assert(glGetError() == GL_NO_ERROR); shader->prog_id = glCreateProgram(); glAttachShader(shader->prog_id, vs_id); glAttachShader(shader->prog_id, fs_id); glLinkProgram(shader->prog_id); GLint is_linked = 0; glGetProgramiv(shader->prog_id, GL_LINK_STATUS, &is_linked); // NOTE: log shader linking errors if (is_linked != GL_TRUE) { const u32 max_len = 512; char err_str[max_len]; i32 write_len; glGetProgramInfoLog( shader->prog_id, max_len, &write_len, &err_str[0]); LOGF(Error, "Info Log: %s\n", err_str); glDeleteProgram(shader->prog_id); } return (is_linked == GL_TRUE); } LOGF(Error, "empty shader source\n"); return false; } void loadDummyShader() { GLuint vs_id = 0, fs_id = 0; ShaderProgram temp_shader = {0}; bool ret = compileAndLinkShader(&temp_shader, DUMMY_VERTEX_SHADER, DUMMY_FRAGMENT_SHADER, vs_id, fs_id); assert(ret); glDeleteProgram(temp_shader.prog_id); } u32 getGLTypeSize(GLenum e) { switch (e) { case GL_FLOAT_VEC2: return 2 * sizeof(GLfloat); case GL_FLOAT_VEC3: return 3 * sizeof(GLfloat); case GL_FLOAT_VEC4: return 4 * sizeof(GLfloat); case GL_FLOAT_MAT4: return 16 * sizeof(GLfloat); default: LOGF(Error, "unknown GLenum\n"); return 0; } } // NOTE: returns sizes based on GLSL layout std140 // https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)#Memory_layout u32 getGLTypeSizeStd140(GLenum e) { switch (e) { case GL_FLOAT_VEC3: return 4 * sizeof(GLfloat); case GL_FLOAT_VEC4: return 4 * sizeof(GLfloat); case GL_FLOAT_MAT4: return 16 * sizeof(GLfloat); default: LOGF(Error, "unknown GLenum\n"); return 0; } } UniformType getUniformType(const char* name) { if (utilCStrMatch(name, "sampler")) return UNIFORM_SAMPLER; else if (utilCStrMatch(name, "node_xform")) return UNIFORM_NODE_XFORM; else if (utilCStrMatch(name, "normal_xform")) return UNIFORM_NORMAL_XFORM; else if (utilCStrMatch(name, "view_xform")) return UNIFORM_VIEW_XFORM; else if (utilCStrMatch(name, "proj_xform")) return UNIFORM_PROJECTION_XFORM; else if (utilCStrMatch(name, "matrices")) return UNIFORM_BLOCK_XFORMS; else if (utilCStrMatch(name, "lights")) return UNIFORM_BLOCK_LIGHTS; else return UNIFORM_UNKNOWN; } const GLUniform parseUniform(MemoryArena* arena, ShaderProgram* s, u32 uniform_idx) { GLUniform unif = {0}; GLchar unif_name[256] = {0}; GLsizei name_len = 0; unif.idx = uniform_idx; glGetActiveUniform(s->prog_id, uniform_idx, sizeof(unif_name), &name_len, &unif.num_elements, &unif.gl_type, unif_name); glGetActiveUniformsiv(s->prog_id, 1, &uniform_idx, GL_UNIFORM_BLOCK_INDEX, &unif.block_idx); glGetActiveUniformsiv(s->prog_id, 1, &uniform_idx, GL_UNIFORM_ARRAY_STRIDE, &unif.array_stride); glGetActiveUniformsiv(s->prog_id, 1, &uniform_idx, GL_UNIFORM_OFFSET, &unif.uniform_offset); unif.name = arenaCopyCStr(arena, unif_name); unif.uniform_type = getUniformType(unif.name); unif.location = glGetUniformLocation(s->prog_id, unif.name); return unif; } bool parseShaderUniforms(MemoryArena* arena, ShaderProgram* s, GLContext* gl_ctx) { // NOTE: only add uniforms in the default block to the base uniform array GLint num_uniforms_total = 0; glGetProgramiv(s->prog_id, GL_ACTIVE_UNIFORMS, &num_uniforms_total); GLint indices[num_uniforms_total]; for (u32 i = 0; i < (u32) num_uniforms_total; i++) { GLint block_idx = 0; glGetActiveUniformsiv(s->prog_id, 1, &i, GL_UNIFORM_BLOCK_INDEX, &block_idx); if (block_idx == -1) { indices[s->num_uniforms] = i; s->num_uniforms++; } } s->uniforms = ARENA_ALLOC(arena, GLUniform, s->num_uniforms); for (u32 i = 0; i < s->num_uniforms; i++) { const GLUniform unif = parseUniform(arena, s, indices[i]); std::memcpy(&s->uniforms[i], &unif, sizeof(unif)); } return true; } void initCTXSizes(GLContext* gl_ctx) { // NOTE: see https://docs.gl/gl3/glGet for other useful context info if (gl_ctx->max_binding_points == 0) { glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &gl_ctx->max_binding_points); glGetIntegerv(GL_MAX_VERTEX_UNIFORM_BLOCKS, &gl_ctx->max_vertex_blocks); glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_BLOCKS, &gl_ctx->max_fragment_blocks); glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &gl_ctx->max_ublock_size); glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &gl_ctx->max_vertex_attribs); #if 1 LOGF(Debug, "context size info set\n"); LOGF(Debug, "GL_MAX_UNIFORM_BUFFER_BINDINGS: %d\n", gl_ctx->max_binding_points); LOGF(Debug, "GL_MAX_VERTEX_UNIFORM_BLOCKS: %d\n", gl_ctx->max_vertex_blocks); LOGF(Debug, "GL_MAX_FRAGMENT_UNIFORM_BLOCKS: %d\n", gl_ctx->max_fragment_blocks); LOGF(Debug, "GL_MAX_UNIFORM_BLOCK_SIZE: %d\n", gl_ctx->max_ublock_size); LOGF(Debug, "GL_MAX_VERTEX_ATTRIBS: %d\n", gl_ctx->max_vertex_attribs); #endif } } i32 ctxGetUniformBlockBinding(GLContext* gl_ctx, const char* name) { for (u32 i = 0; i < gl_ctx->num_ubos; i++) { GLBuffer& ubo = gl_ctx->uniform_buffers[i]; if (std::strstr(ubo.name, name)) return ubo.binding_idx; } LOGF(Error, "no buffer found with name: %s\n", name); return -1; } bool parseUniformBlocks(MemoryArena* arena, ShaderProgram* s, GLContext* gl_ctx) { glGetProgramiv(s->prog_id, GL_ACTIVE_UNIFORM_BLOCKS, (GLint*) &s->num_blocks); s->uniform_blocks = ARENA_ALLOC(arena, GLUniformBlock, s->num_blocks); for (u32 i = 0; i < s->num_blocks; i++) { GLUniformBlock& ub = s->uniform_blocks[i]; ub.block_id = i; GLchar block_name[256] = {0}; GLsizei name_len = 0; glGetActiveUniformBlockName( s->prog_id, i, 256, &name_len, block_name); ub.name = arenaCopyCStr(arena, block_name); ub.uniform_type = getUniformType(ub.name); glGetActiveUniformBlockiv(s->prog_id, i, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, (GLint*) &ub.num_uniforms); ub.uniforms = ARENA_ALLOC(arena, GLUniform, ub.num_uniforms); GLint indices[ub.num_uniforms] = {0}; glGetActiveUniformBlockiv(s->prog_id, i, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, (GLint*) &indices); for (u32 j = 0; j < ub.num_uniforms; j++) { const GLUniform unif = parseUniform(arena, s, indices[j]); std::memcpy(&ub.uniforms[j], &unif, sizeof(unif)); } ub.binding_idx = ctxGetUniformBlockBinding(gl_ctx, ub.name); if (ub.binding_idx < 0) return false; glUniformBlockBinding(s->prog_id, i, ub.binding_idx); } // TODO: would be helpful for debugging if we sort the uniforms in a block // by their uniform_offset instead of their idx return true; } u32 getNumAttribComponents(GLenum type) { switch (type) { case GL_FLOAT_VEC3: return 3; case GL_FLOAT_VEC2: return 2; default: LOGF(Error, "unknown GLenum\n"); return 0; } } GLenum getAttribComponentType(GLenum type) { switch (type) { case GL_FLOAT_VEC3: return GL_FLOAT; case GL_FLOAT_VEC2: return GL_FLOAT; default: LOGF(Error, "unknown GLenum\n"); return 0; } } bool parseAttributes(MemoryArena* arena, ShaderProgram* s, GLContext* gl_ctx) { GLint num_attribs; glGetProgramiv(s->prog_id, GL_ACTIVE_ATTRIBUTES, &num_attribs); s->num_vertex_attribs = num_attribs; s->vertex_attribs = ARENA_ALLOC(arena, GLVertexAttrib, num_attribs); s->attrib_mappings = ARENA_ALLOC(arena, GLBufferToAttribMapping, num_attribs); GLchar attrib_name[256] = {0}; GLsizei length; GLint size; GLenum type; for (int i = 0; i < num_attribs; i++) { glGetActiveAttrib(s->prog_id, i, sizeof(attrib_name), &length, &size, &type, attrib_name); GLint location = glGetAttribLocation(s->prog_id, attrib_name); GLVertexAttrib* attrib = &s->vertex_attribs[i]; attrib->data_type = type; attrib->location = location; attrib->num_components = getNumAttribComponents(type); assert(attrib->num_components > 0); attrib->component_type = getAttribComponentType(type); assert(attrib->component_type > 0); attrib->name = arenaCopyCStr(arena, attrib_name, sizeof(attrib_name)); } return true; } bool parseShader(MemoryArena* arena, GLContext* gl_ctx, ShaderProgram* s) { if (parseShaderUniforms(arena, s, gl_ctx) && parseUniformBlocks(arena, s, gl_ctx) && parseAttributes(arena, s, gl_ctx)) { return true; } LOGF(Error, "Error parsing shader\n"); return false; }