#include #include #include #include #include #include #include #include #include "asset.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(memory_arena* arena, GLContext* gl_ctx, shader_program* s); void initCTXSizes(GLContext* gl_ctx); bool compileAndLinkShader(shader_program* shader, const char* vert_src, const char* frag_src, GLuint& vs_id, GLuint& fs_id); // NOTE: interface shader_program* getFreeShader(GLContext* gl_ctx) { if (gl_ctx->num_shaders >= gl_ctx->max_shaders) { printf("%s(), GLContext->shaders full\n", __FUNCTION__); return nullptr; } shader_program* s = &gl_ctx->shaders[gl_ctx->num_shaders++]; return s; } shader_program* 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; } bool addShaderProgram(memory_arena* arena, GLContext* gl_ctx, const char* vs, const char* fs, const char* name) { // TODO: replace std::string w/ utilAllocCStr() ? std::string hash_str = std::string(vs) + std::string(fs); const u32 max_len = 256; u64 hash = utilFNV64a_str(hash_str.substr(0, max_len).c_str()); if (getShaderByHash(gl_ctx, hash)) { printf("%s(), shader is already loaded\n", __FUNCTION__); return false; } shader_program* s = getFreeShader(gl_ctx); if (s) { s->name = arenaCopyCStr(arena, name); s->hash = hash; // TODO: replace std::string w/ utilAllocCStr() ? 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->model_xform_id = glGetUniformLocation(s->prog_id, "model_xform"); glDetachShader(s->prog_id, vs_id); glDetachShader(s->prog_id, fs_id); glDeleteShader(vs_id); glDeleteShader(fs_id); return parseShader(arena, gl_ctx, s); } printf("%s(), Error linking shader\n", __FUNCTION__); return false; } printf("%s(), error loading shader\n", __FUNCTION__); return false; } shader_program* 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]; } printf("%s(), shader not found, %s\n", __FUNCTION__, name); return nullptr; } shader_program* 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]; } printf("%s(), shader not found, %d\n", __FUNCTION__, prog_id); return nullptr; } void renderVAO(GLmesh* glmesh) { glUseProgram(glmesh->shader->prog_id); glBindVertexArray(glmesh->vao_id); glUniformMatrix4fv(glmesh->shader->model_xform_id, 1, GL_FALSE, (float*) glmesh->model_xform); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, glmesh->idx_buf_id); glDrawElements( glmesh->draw_mode, glmesh->num_indices, GL_UNSIGNED_SHORT, 0); glBindVertexArray(0); } void initTransforms(memory_arena* arena, transforms* xforms, gl_buffer* 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); } GLmesh loadGLMesh(shader_program* s, const mesh& m, GLenum draw_mode, const glm::vec3& pos) { GLmesh gm = {0}; gm.num_indices = m.num_indices; gm.draw_mode = draw_mode; // NOTE: okay to store shader_program pointer on GLmesh here because we // won't ever delete the shader gm.shader = s; glUseProgram(s->prog_id); glGenVertexArrays(1, &gm.vao_id); glBindVertexArray(gm.vao_id); gm.model_xform = (glm::mat4*) std::calloc(1, sizeof(glm::mat4)); *gm.model_xform = glm::translate(glm::mat4(1), pos); glUniformMatrix4fv( s->model_xform_id, 1, GL_FALSE, (float*) gm.model_xform); glGenBuffers(1, &gm.vert_buf_id); glBindBuffer(GL_ARRAY_BUFFER, gm.vert_buf_id); glBufferData(GL_ARRAY_BUFFER, m.num_vertices * 3 * sizeof(GLfloat), m.vertices, GL_STATIC_DRAW); glVertexAttribPointer(POSITION, 3, GL_FLOAT, GL_FALSE, 0, 0); glEnableVertexAttribArray(POSITION); if (m.normals != nullptr) { glGenBuffers(1, &gm.norm_buf_id); glBindBuffer(GL_ARRAY_BUFFER, gm.norm_buf_id); glBufferData(GL_ARRAY_BUFFER, m.num_vertices * 3 * sizeof(GLfloat), m.normals, GL_STATIC_DRAW); glVertexAttribPointer(NORMAL, 3, GL_FLOAT, GL_FALSE, 0, 0); glEnableVertexAttribArray(NORMAL); } glGenBuffers(1, &gm.idx_buf_id); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gm.idx_buf_id); glBufferData(GL_ELEMENT_ARRAY_BUFFER, m.num_indices * sizeof(u16), m.indices, GL_STATIC_DRAW); glBindVertexArray(0); glUseProgram(0); return gm; } // NOTE: internal bool compileAndLinkShader(shader_program* 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); } // TODO: make another logging macro for printf style logging printf("%s(), empty shader source\n", __FUNCTION__); 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; } // NOTE: returns sizes based on GLSL layout std140 // https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)#Memory_layout u32 getGLTypeSize(GLenum e) { switch (e) { case 0x8b51: return 4 * sizeof(GLfloat); // GL_FLOAT_VEC3 case 0x8b52: return 4 * sizeof(GLfloat); // GL_FLOAT_VEC4 case 0x8b5c: return 16 * sizeof(GLfloat); // GL_FLOAT_MAT4 default: printf("%s(), unknown GLenum\n", __FUNCTION__); return 0; } } const gl_uniform parseUniform(memory_arena* arena, shader_program* s, u32 uniform_idx) { gl_uniform 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(memory_arena* arena, shader_program* 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, gl_uniform, s->num_uniforms); for (u32 i = 0; i < s->num_uniforms; i++) { const gl_uniform 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); #if 1 printf("%s(), context size info set\n", __FUNCTION__); printf("GL_MAX_UNIFORM_BUFFER_BINDINGS: %d\n", gl_ctx->max_binding_points); printf("GL_MAX_VERTEX_UNIFORM_BLOCKS: %d\n", gl_ctx->max_vertex_blocks); printf("GL_MAX_FRAGMENT_UNIFORM_BLOCKS: %d\n", gl_ctx->max_fragment_blocks); printf("GL_MAX_UNIFORM_BLOCK_SIZE: %d\n", gl_ctx->max_ublock_size); GLint max_vertex_attribs = 0; glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &max_vertex_attribs); printf("GL_MAX_VERTEX_ATTRIBS: %d\n", max_vertex_attribs); #endif } } i32 ctxGetUniformBlockBinding(GLContext* gl_ctx, const char* name) { for (u32 i = 0; i < gl_ctx->num_ubos; i++) { gl_buffer& ubo = gl_ctx->uniform_buffers[i]; if (std::strstr(ubo.name, name)) return ubo.binding_idx; } printf("%s(), no buffer found with name: %s\n", __FUNCTION__, name); return -1; } bool parseUniformBlocks(memory_arena* arena, shader_program* 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, gl_uniform, 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 gl_uniform 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; } bool parseAttributes(memory_arena* arena, shader_program* s, GLContext* gl_ctx) { printf("\n---------------------------------\n"); printf("%s(), FIXME: dumping attrib info for programe: %s\n", __FUNCTION__, s->name); GLint num_attribs; glGetProgramiv(s->prog_id, GL_ACTIVE_ATTRIBUTES, &num_attribs); printf("active attributes: %d\n", num_attribs); s->num_vertex_attribs = num_attribs; s->vertex_attribs = ARENA_ALLOC(arena, GLVertexAttrib, 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); printf("attrib idx: %d, location: %d, type: %s, name: %s \n", i, location, glEnumToString(type), attrib_name); GLVertexAttrib* attrib = &s->vertex_attribs[i]; attrib->data_type = type; attrib->location = location; attrib->name = arenaCopyCStr(arena, attrib_name, sizeof(attrib_name)); // TODO: replace POSITION, NORMAL, UV... etc enums in loadGLMesh() // with extra logic to detect mesh to shader mapping // TODO: we still need a way to map the shader attribute name to a // vertex buffer object... could maybe pass in an array of mappings to // loadGLMesh? ie) [{ "position", gm.vert_buf_id }, ...] } printf("---------------------------------\n\n"); return true; } bool parseShader(memory_arena* arena, GLContext* gl_ctx, shader_program* s) { if (parseShaderUniforms(arena, s, gl_ctx) && parseUniformBlocks(arena, s, gl_ctx) && parseAttributes(arena, s, gl_ctx)) { return true; } printf("%s(), Error parsing shader\n", __FUNCTION__); return false; }