Small program to quickly test OpenGL GLSL shaders.
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

#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;
}