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.
 
 
 

393 lines
10 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"
// 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<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.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;
}