You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
493 lines
13 KiB
493 lines
13 KiB
|
|
#include <cassert> |
|
#include <cstddef> |
|
#include <cstdlib> |
|
#include <cstring> |
|
#include <string> |
|
#include <fstream> |
|
#include <iostream> |
|
|
|
#include <glm/gtc/matrix_transform.hpp> |
|
|
|
#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<char>(fs), |
|
std::istreambuf_iterator<char>()}; |
|
|
|
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; |
|
} |
|
|
|
|