#include #include #include #include #include #include #include #include #include "asset.h" #define GL_DEBUG_IMPLEMENTATION #include "GLDebug.h" #include "shader.h" // NOTE: forward declarations const std::string dumpTextFile(const char* filepath); bool parseShaderUniforms(memory_arena* arena, shader_program* s, GLContext* gl_ctx); bool parseUniformBlocks(memory_arena* arena, shader_program* s, GLContext* gl_ctx); bool parseAttributes(memory_arena* arena, shader_program* s, GLContext* gl_ctx); void initCTXSizes(GLContext* gl_ctx); // NOTE: interface // TODO: clean up this function bool addShaderProgram(memory_arena* arena, GLContext* gl_ctx, const char* vs, const char* fs, const char* name) { if (gl_ctx->num_shaders >= gl_ctx->max_shaders) { printf("%s(), GLContext->shaders full\n", __FUNCTION__); return false; } shader_program* s = &gl_ctx->shaders[gl_ctx->num_shaders]; gl_ctx->num_shaders++; s->name = arenaCopyCStr(arena, name); const u32 max_len = 256; std::string hash_str = vs; hash_str += fs; s->hash = utilFNV64a_str(hash_str.substr(0, max_len).c_str()); // FIXME: should probably check the hash here against other shaders loaded std::string vert = dumpTextFile(vs); std::string frag = dumpTextFile(fs); if (vert.size() > 0 && frag.size() > 0) { const char* vert_c = vert.c_str(); const char* frag_c = frag.c_str(); GLuint vs_id = glCreateShader(GL_VERTEX_SHADER); GLuint fs_id = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(vs_id, 1, &vert_c, NULL); glShaderSource(fs_id, 1, &frag_c, NULL); // TODO: both of these can fail glCompileShader(vs_id); glCompileShader(fs_id); s->prog_id = glCreateProgram(); glAttachShader(s->prog_id, vs_id); glAttachShader(s->prog_id, fs_id); glLinkProgram(s->prog_id); GLint is_linked = 0; glGetProgramiv(s->prog_id, GL_LINK_STATUS, &is_linked); if (is_linked) { // NOTE: need to set context maximums after at least one shader has // been linked initCTXSizes(gl_ctx); #if 0 dumpShader(s->prog_id); #endif 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); if (parseShaderUniforms(arena, s, gl_ctx) && parseUniformBlocks(arena, s, gl_ctx) && parseAttributes(arena, s, gl_ctx)) { return true; } else { printf("%s(), Error parsing shader component\n", __FUNCTION__); return false; } } printf("%s(), Error linking shader\n", __FUNCTION__); return false; } return false; } 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); } // NOTE: internal 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.data_size = getGLTypeSize(unif.data_type); assert(unif.data_size > 0); unif.name = arenaCopyCStr(arena, unif_name); unif.location = glGetUniformLocation(s->prog_id, unif.name); if (unif.data_size == 0) printf("%s(), error getting data_size\n", __FUNCTION__); 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 = (gl_uniform*) arenaAllocateBlock( arena, s->num_uniforms * sizeof(gl_uniform)); 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; } 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 = (GLUniformBlock*) arenaAllocateBlock(arena, s->num_blocks * sizeof(GLUniformBlock)); for (u32 i = 0; i < s->num_blocks; i++) { GLUniformBlock& ub = s->uniform_blocks[i]; ub.block_id = i; GLchar block_name[256] = {0}; glGetActiveUniformBlockName( s->prog_id, i, 256, &ub.name_len, block_name); ub.name_len++; // NOTE: space for null term ub.name = (char*) arenaAllocateBlock(arena, ub.name_len * sizeof(char)); std::strncpy(ub.name, block_name, ub.name_len); glGetActiveUniformBlockiv(s->prog_id, i, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, (GLint*) &ub.num_uniforms); ub.uniforms = (gl_uniform*) arenaAllocateBlock(arena, ub.num_uniforms * sizeof(gl_uniform)); 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) { // TODO: parse buffers (vert, normal, uv), glGetActiveAttrib() /// printf("%s(), FIXME:\n", __FUNCTION__); return true; } 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->data_size = sizeof(*xforms); 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); } 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); #endif } } 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; }