#include #include #include #include #include #include #include #include #include "asset.h" #include "dumbLog.h" #define GL_DEBUG_IMPLEMENTATION #include "GLDebug.h" #include "shader.h" #include "util.h" // NOTE: forward declarations const std::string dumpTextFile(const char* filepath); bool parseShader(MemoryArena* arena, GLContext* gl_ctx, ShaderProgram* s); 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 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; // TODO: replace std::string w/ utilAllocateCStr() ? std::string vert = dumpTextFile(vs); std::string frag = dumpTextFile(fs); GLuint vs_id, fs_id; if (compileAndLinkShader(s, vert.c_str(), frag.c_str(), vs_id, fs_id)) { // NOTE: we call this here because we can only make the GL_MAX* // queries after at least one shader has been linked initCTXSizes(gl_ctx); s->node_xform_id = glGetUniformLocation(s->prog_id, "node_xform"); // TODO: add sampler uniform detection in dynamic shader parsing s->sampler_id = glGetUniformLocation(s->prog_id, "sampler"); 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) { // FIXME: no reason to use hashes here. should use std::strncmp instead u64 hash = utilFNV64a_str(name); for (u32 i = 0; i < gl_ctx->num_shaders; i++) { if (utilFNV64a_str(gl_ctx->shaders[i].name) == hash) 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; } 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 renderVAO(GLmesh* glmesh, glm::mat4* node_xform, ShaderProgram* shader, GLuint tex_id) { glUseProgram(shader->prog_id); glBindVertexArray(glmesh->vao_id); glUniformMatrix4fv(shader->node_xform_id, 1, GL_FALSE, (float*) node_xform); if (tex_id > 0) { glBindTexture(GL_TEXTURE_2D, tex_id); glUniform1i(shader->sampler_id, 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, glm::vec3 cam_pos, glm::vec3 look_pos) { xforms->view_xform = glm::lookAt(cam_pos, look_pos, glm::vec3(0, 1, 0)); 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"); glBindBuffer(xform_ubo->target, xform_ubo->id); glBufferData(xform_ubo->target, sizeof(*xforms), xforms, GL_DYNAMIC_DRAW); // 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, GLuint diffuse_texture_id, 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); 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; u32 type_size = getGLTypeSize(attrib->data_type); assert(type_size > 0); 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, m.num_vertices * type_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; glBindBuffer(glm.element_buf->target, glm.element_buf->id); glBufferData(glm.element_buf->target, m.num_indices * sizeof(u16), 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 (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); // TODO: add a glError to string function in GLDebug.h 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); return (is_linked == GL_TRUE); } LOGF(Error, "empty shader source\n"); return false; } const std::string dumpTextFile(const char* filepath) { std::ifstream fs { filepath }; std::string s { std::istreambuf_iterator(fs), std::istreambuf_iterator()}; if (!fs || !fs.good()) std::cout << "error reading file, " << filepath << "\n"; return s; } 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; } } const GLUniform parseUniform(MemoryArena* arena, ShaderProgram* s, u32 uniform_idx) { GLUniform unif = {0}; GLchar unif_name[256] = {0}; GLsizei name_len = 0; glGetActiveUniform(s->prog_id, uniform_idx, sizeof(unif_name), &name_len, &unif.num_elements, &unif.data_type, unif_name); glGetActiveUniformsiv(s->prog_id, 1, &uniform_idx, GL_UNIFORM_BLOCK_INDEX, &unif.block_idx); unif.idx = uniform_idx; unif.name = arenaCopyCStr(arena, 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); 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 0 if (ub.binding_idx < 0) return false; glUniformBlockBinding(s->prog_id, i, ub.binding_idx); #endif } 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; }