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.
 
 
 

749 lines
19 KiB

#include <cassert>
#include <cstddef>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iostream>
#include <string>
#include <glm/gtc/matrix_transform.hpp>
#include "asset.h"
#include "dumbLog.h"
#include "dummy_shader.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(MemoryArena* arena, GLContext* gl_ctx, ShaderProgram* s);
void loadDummyShader();
void initCTXSizes(GLContext* gl_ctx);
bool compileAndLinkShader(ShaderProgram* shader,
const char* vert_src,
const char* frag_src,
GLuint& vs_id,
GLuint& fs_id);
u32 getGLTypeSize(GLenum e);
GLTexture* getFreeGLTexture(GLContext* gl_ctx);
bool loadGLTexture(Texture* image, GLuint& tex_id);
void* getMeshData(const Mesh& m, const GLBufferToAttribMapping& mapping);
// NOTE: interface
GLContext*
initGLContext(MemoryArena* arena,
u32 max_shaders,
u32 max_textures,
u32 max_ubos)
{
GLContext* gl_ctx = ARENA_ALLOC(arena, GLContext, 1);
gl_ctx->max_shaders = max_shaders;
gl_ctx->shaders = ARENA_ALLOC(arena, ShaderProgram, max_shaders);
gl_ctx->max_ubos = max_ubos;
gl_ctx->uniform_buffers = ARENA_ALLOC(arena, GLBuffer, max_ubos);
// NOTE: initialize GLBuffer struct members to sane defaults
for (u32 i = 0; i < max_ubos; i++) {
GLBuffer& buf = gl_ctx->uniform_buffers[i];
buf.location = -1;
buf.binding_idx = -1;
}
gl_ctx->max_textures = max_textures;
gl_ctx->textures = ARENA_ALLOC(arena, GLTexture, max_textures);
// NOTE: load a dummy shader to avoid chicken and egg problem where we need
// GLContext info before we can parse a shader, which needs GLContext info
loadDummyShader();
initCTXSizes(gl_ctx);
return gl_ctx;
}
bool
addShaderProgram(MemoryArena* arena,
GLContext* gl_ctx,
const char* vs,
const char* fs,
const char* name)
{
LOGF(Info, "loading shader, %s\n", name);
const u32 max_len = 256;
char input_str[max_len];
snprintf(input_str, max_len, "%s%s", vs, fs);
u64 hash = utilFNV64a_str(input_str);
if (getShaderByHash(gl_ctx, hash)) {
LOGF(Error, "shader is already loaded\n");
return false;
}
ShaderProgram* s = getFreeShader(gl_ctx);
if (s) {
s->name = arenaCopyCStr(arena, name);
s->hash = hash;
// TODO: replace std::string w/ utilAllocateCStr() ?
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)) {
s->node_xform_id = glGetUniformLocation(s->prog_id, "node_xform");
// TODO: add sampler uniform detection in dynamic shader parsing
s->sampler_id = glGetUniformLocation(s->prog_id, "sampler");
glDetachShader(s->prog_id, vs_id);
glDetachShader(s->prog_id, fs_id);
glDeleteShader(vs_id);
glDeleteShader(fs_id);
return parseShader(arena, gl_ctx, s);
}
LOGF(Error, "Error linking shader\n");
return false;
}
LOGF(Error, "error loading shader\n");
return false;
}
ShaderProgram*
getFreeShader(GLContext* gl_ctx)
{
if (gl_ctx->num_shaders >= gl_ctx->max_shaders) {
LOGF(Error, "GLContext->shaders full\n");
return nullptr;
}
ShaderProgram* s = &gl_ctx->shaders[gl_ctx->num_shaders++];
return s;
}
ShaderProgram*
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;
}
ShaderProgram*
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];
}
LOGF(Error, "shader not found, %s\n", name);
return nullptr;
}
ShaderProgram*
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];
}
LOGF(Error, "shader not found, %d\n", prog_id);
return nullptr;
}
GLBuffer*
getFreeUBO(GLContext* gl_ctx)
{
if (gl_ctx->num_ubos < gl_ctx->max_ubos)
return &gl_ctx->uniform_buffers[gl_ctx->num_ubos++];
LOGF(Error, "no free Uniform Buffer Objects\n");
return nullptr;
}
GLBuffer*
getUBOByName(GLContext* gl_ctx, const char* name)
{
GLBuffer* ubo_out = nullptr;
for (u32 i = 0; i < gl_ctx->num_ubos; i++) {
GLBuffer* buf = &gl_ctx->uniform_buffers[i];
if (utilCStrMatch(name, buf->name))
ubo_out = buf;
}
if (ubo_out == nullptr)
LOGF(Error, "GLBuffer, \"%s\", not found\n", name);
return ubo_out;
}
GLTexture*
getGLTexture(GLContext* gl_ctx, Texture* diffuse_img)
{
u64 fp_hash = utilFNV64a_str(diffuse_img->file_path);
for (u32 i = 0; i < gl_ctx->num_textures; i++) {
GLTexture* glt = &gl_ctx->textures[i];
if (glt->filepath_hash == fp_hash)
return glt;
}
GLTexture* glt = getFreeGLTexture(gl_ctx);
if (!glt) return nullptr;
glt->pixel_format = (diffuse_img->num_channels == 3) ? GL_RGB : GL_RGBA;
glt->width = diffuse_img->w;
glt->height = diffuse_img->h;
glt->filepath_hash = diffuse_img->filepath_hash;
if (loadGLTexture(diffuse_img, glt->id))
return glt;
LOGF(Error, "Error, unable to load texture\n");
return nullptr;
}
void
updateCameraTransforms(Transforms* xforms, GLBuffer* xform_ubo)
{
glBindBuffer(xform_ubo->target, xform_ubo->id);
glBufferSubData(xform_ubo->target, 0, sizeof(*xforms), xforms);
}
void
renderVAO(GLMesh* glmesh,
glm::mat4* node_xform,
ShaderProgram* shader,
GLuint tex_id)
{
glUseProgram(shader->prog_id);
glBindVertexArray(glmesh->vao_id);
glUniformMatrix4fv(shader->node_xform_id, 1, GL_FALSE,
(float*) node_xform);
if (tex_id > 0) {
glBindTexture(GL_TEXTURE_2D, tex_id);
glUniform1i(shader->sampler_id, 0);
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, glmesh->element_buf->id);
glDrawElements(
glmesh->draw_mode, glmesh->num_indices, GL_UNSIGNED_SHORT, 0);
glBindVertexArray(0);
}
void
initTransforms(MemoryArena* arena,
Transforms* xforms,
GLBuffer* xform_ubo,
GLContext* gl_ctx,
float fov,
float near_clip_plane,
float aspect_ratio)
{
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");
xform_ubo->data_size = sizeof(*xforms);
glBindBuffer(xform_ubo->target, xform_ubo->id);
glBufferData(xform_ubo->target, xform_ubo->data_size, xforms,
GL_DYNAMIC_DRAW);
// NOTE: 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);
}
GLVertexAttrib*
getVertexAttribByName(ShaderProgram* shader, const char* name)
{
for (u32 i = 0; i < shader->num_vertex_attribs; i++) {
if (strncmp(shader->vertex_attribs[i].name, name, 256) == 0)
return &shader->vertex_attribs[i];
}
LOGF(Debug, "attribute: %s, not found on shader: %s\n", name, shader->name);
return nullptr;
}
GLMesh
initGLMesh(const Mesh& m, u32 num_mappings, GLenum draw_mode)
{
GLMesh glm = {0};
glm.num_indices = m.num_indices;
glm.draw_mode = draw_mode;
glm.usage = GL_STATIC_DRAW; // TODO: logic for updating dynamic meshes
glm.num_vertex_attrib_buffers = num_mappings;
glm.vertex_attrib_buffers = UTIL_ALLOC(num_mappings, GLBuffer);
glm.element_buf = UTIL_ALLOC(1, GLBuffer);
glm.node_xform = UTIL_ALLOC(1, glm::mat4);
*glm.node_xform = glm::mat4(1);
return glm;
}
void
initGLAttribBuffer(GLBuffer* buf, GLenum target, GLVertexAttrib* attrib)
{
glGenBuffers(1, &buf->id);
buf->target = target;
buf->data_type = attrib->data_type;
buf->location = attrib->location;
buf->name = utilAllocateCStr(attrib->name);
}
// FIXME: might as well pass in pointer to GLMesh, since that's how we're
// going to use this, can void copying GLMesh twice that way
GLMesh
loadGLMesh(const Mesh& m,
GLenum draw_mode,
GLuint diffuse_texture_id,
u32 num_mappings,
GLBufferToAttribMapping mappings[])
{
// NOTE: need to use freeGLMesh() when freeing because we're not storing
// the GLBuffers on a memory arean (yet)
GLMesh glm = initGLMesh(m, num_mappings, draw_mode);
glm.tex_id = diffuse_texture_id;
glGenVertexArrays(1, &glm.vao_id);
glBindVertexArray(glm.vao_id);
for (u32 i = 0; i < num_mappings; i++) {
GLBuffer& buf = glm.vertex_attrib_buffers[i];
GLVertexAttrib* attrib = mappings[i].attrib;
// FIXME: does it make sense to set 'buf_type' in a separate function?
attrib->buf_type = mappings[i].buf_type;
u32 type_size = getGLTypeSize(attrib->data_type);
assert(type_size > 0);
buf.data_size = m.num_vertices * type_size;
void* mesh_buf_data = getMeshData(m, mappings[i]);
assert(mesh_buf_data);
initGLAttribBuffer(&buf, GL_ARRAY_BUFFER, attrib);
glBindBuffer(buf.target, buf.id);
glBufferData(buf.target,
buf.data_size,
mesh_buf_data,
glm.usage);
glVertexAttribPointer(attrib->location, attrib->num_components,
attrib->component_type, GL_FALSE, 0, 0);
glEnableVertexAttribArray(attrib->location);
}
glGenBuffers(1, &glm.element_buf->id);
glm.element_buf->target = GL_ELEMENT_ARRAY_BUFFER;
glm.element_buf->data_type = GL_UNSIGNED_SHORT;
glm.element_buf->data_size = m.num_indices * sizeof(u16);
glBindBuffer(glm.element_buf->target, glm.element_buf->id);
glBufferData(glm.element_buf->target,
glm.element_buf->data_size,
m.indices,
glm.usage);
// TODO: many of these GL functions can set an error state
// TODO: return error status
glBindVertexArray(0);
return glm;
}
void
freeGLMesh(GLMesh* glm)
{
if (glm) {
for (u32 i = 0; i < glm->num_vertex_attrib_buffers; i++)
utilSafeFree(glm->vertex_attrib_buffers[i].name);
utilSafeFree(glm->vertex_attrib_buffers);
utilSafeFree(glm->element_buf);
// NOTE: don't free the GLMesh because it is part of a memory arena
//utilSafeFree(glm);
}
}
// NOTE: internal
void*
getMeshData(const Mesh& m, const GLBufferToAttribMapping& mapping)
{
switch (mapping.buf_type) {
case VERTEX: return m.vertices;
case NORMAL: return m.normals;
case UV: return m.uvs;
case COLOR: return m.colors;
default: return nullptr;
}
}
GLTexture*
getFreeGLTexture(GLContext* gl_ctx)
{
if (gl_ctx->num_textures < gl_ctx->max_textures)
return &gl_ctx->textures[gl_ctx->num_textures++];
LOGF(Error, "no free textures\n");
return nullptr;
}
bool
loadGLTexture(Texture* image, GLuint& tex_id)
{
glGenTextures(1, &tex_id);
glBindTexture(GL_TEXTURE_2D, tex_id);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
GLenum pixel_format = (image->num_channels == 3) ? GL_RGB : GL_RGBA;
glTexImage2D(GL_TEXTURE_2D, 0, pixel_format, image->w, image->h, 0,
pixel_format, GL_UNSIGNED_BYTE, image->pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
return (glGetError() == GL_NO_ERROR);
}
bool
compileAndLinkShader(ShaderProgram* 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);
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);
// NOTE: log shader linking errors
if (is_linked != GL_TRUE) {
const u32 max_len = 512;
char err_str[max_len];
i32 write_len;
glGetProgramInfoLog(
shader->prog_id, max_len, &write_len, &err_str[0]);
LOGF(Error, "Info Log: %s\n", err_str);
glDeleteProgram(shader->prog_id);
}
// FIXME: this works in libTangerine cleanUpShader(), but errors here?
#if 0
glDetachShader(shader->prog_id, vs_id);
glDetachShader(shader->prog_id, fs_id);
glDeleteShader(vs_id);
glDeleteShader(fs_id);
#endif
return (is_linked == GL_TRUE);
}
LOGF(Error, "empty shader source\n");
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;
}
void
loadDummyShader()
{
GLuint vs_id = 0, fs_id = 0;
ShaderProgram temp_shader = {0};
bool ret = compileAndLinkShader(&temp_shader,
DUMMY_VERTEX_SHADER,
DUMMY_FRAGMENT_SHADER,
vs_id,
fs_id);
assert(ret);
glDeleteProgram(temp_shader.prog_id);
}
u32
getGLTypeSize(GLenum e)
{
switch (e) {
case GL_FLOAT_VEC2: return 2 * sizeof(GLfloat);
case GL_FLOAT_VEC3: return 3 * sizeof(GLfloat);
case GL_FLOAT_VEC4: return 4 * sizeof(GLfloat);
case GL_FLOAT_MAT4: return 16 * sizeof(GLfloat);
default:
LOGF(Error, "unknown GLenum\n");
return 0;
}
}
// NOTE: returns sizes based on GLSL layout std140
// https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)#Memory_layout
u32
getGLTypeSizeStd140(GLenum e)
{
switch (e) {
case GL_FLOAT_VEC3: return 4 * sizeof(GLfloat);
case GL_FLOAT_VEC4: return 4 * sizeof(GLfloat);
case GL_FLOAT_MAT4: return 16 * sizeof(GLfloat);
default:
LOGF(Error, "unknown GLenum\n");
return 0;
}
}
const GLUniform
parseUniform(MemoryArena* arena, ShaderProgram* s, u32 uniform_idx)
{
GLUniform unif = {0};
GLchar unif_name[256] = {0};
GLsizei name_len = 0;
unif.idx = uniform_idx;
glGetActiveUniform(s->prog_id,
uniform_idx,
sizeof(unif_name),
&name_len,
&unif.num_elements,
&unif.uniform_type,
unif_name);
glGetActiveUniformsiv(s->prog_id, 1, &uniform_idx, GL_UNIFORM_BLOCK_INDEX,
&unif.block_idx);
glGetActiveUniformsiv(s->prog_id, 1, &uniform_idx, GL_UNIFORM_ARRAY_STRIDE,
&unif.array_stride);
glGetActiveUniformsiv(s->prog_id, 1, &uniform_idx, GL_UNIFORM_OFFSET,
&unif.uniform_offset);
unif.name = arenaCopyCStr(arena, unif_name);
unif.location = glGetUniformLocation(s->prog_id, unif.name);
return unif;
}
bool
parseShaderUniforms(MemoryArena* arena, ShaderProgram* 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, GLUniform, s->num_uniforms);
for (u32 i = 0; i < s->num_uniforms; i++) {
const GLUniform 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);
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &gl_ctx->max_vertex_attribs);
#if 1
LOGF(Debug, "context size info set\n");
LOGF(Debug, "GL_MAX_UNIFORM_BUFFER_BINDINGS: %d\n",
gl_ctx->max_binding_points);
LOGF(Debug, "GL_MAX_VERTEX_UNIFORM_BLOCKS: %d\n",
gl_ctx->max_vertex_blocks);
LOGF(Debug, "GL_MAX_FRAGMENT_UNIFORM_BLOCKS: %d\n",
gl_ctx->max_fragment_blocks);
LOGF(Debug, "GL_MAX_UNIFORM_BLOCK_SIZE: %d\n",
gl_ctx->max_ublock_size);
LOGF(Debug, "GL_MAX_VERTEX_ATTRIBS: %d\n", gl_ctx->max_vertex_attribs);
#endif
}
}
i32
ctxGetUniformBlockBinding(GLContext* gl_ctx, const char* name)
{
for (u32 i = 0; i < gl_ctx->num_ubos; i++) {
GLBuffer& ubo = gl_ctx->uniform_buffers[i];
if (std::strstr(ubo.name, name))
return ubo.binding_idx;
}
LOGF(Error, "no buffer found with name: %s\n", name);
return -1;
}
bool
parseUniformBlocks(MemoryArena* arena, ShaderProgram* 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, GLUniform, 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 GLUniform unif = parseUniform(arena, s, indices[j]);
std::memcpy(&ub.uniforms[j], &unif, sizeof(unif));
}
ub.binding_idx = ctxGetUniformBlockBinding(gl_ctx, ub.name);
if (ub.binding_idx < 0)
return false;
glUniformBlockBinding(s->prog_id, i, ub.binding_idx);
}
// TODO: would be helpful for debugging if we sort the uniforms in a block
// by their uniform_offset instead of their idx
return true;
}
u32
getNumAttribComponents(GLenum type)
{
switch (type) {
case GL_FLOAT_VEC3: return 3;
case GL_FLOAT_VEC2: return 2;
default:
LOGF(Error, "unknown GLenum\n");
return 0;
}
}
GLenum
getAttribComponentType(GLenum type)
{
switch (type) {
case GL_FLOAT_VEC3: return GL_FLOAT;
case GL_FLOAT_VEC2: return GL_FLOAT;
default:
LOGF(Error, "unknown GLenum\n");
return 0;
}
}
bool
parseAttributes(MemoryArena* arena, ShaderProgram* s, GLContext* gl_ctx)
{
GLint num_attribs;
glGetProgramiv(s->prog_id, GL_ACTIVE_ATTRIBUTES, &num_attribs);
s->num_vertex_attribs = num_attribs;
s->vertex_attribs = ARENA_ALLOC(arena, GLVertexAttrib, num_attribs);
s->attrib_mappings =
ARENA_ALLOC(arena, GLBufferToAttribMapping, 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);
GLVertexAttrib* attrib = &s->vertex_attribs[i];
attrib->data_type = type;
attrib->location = location;
attrib->num_components = getNumAttribComponents(type);
assert(attrib->num_components > 0);
attrib->component_type = getAttribComponentType(type);
assert(attrib->component_type > 0);
attrib->name = arenaCopyCStr(arena, attrib_name, sizeof(attrib_name));
}
return true;
}
bool
parseShader(MemoryArena* arena, GLContext* gl_ctx, ShaderProgram* s)
{
if (parseShaderUniforms(arena, s, gl_ctx)
&& parseUniformBlocks(arena, s, gl_ctx)
&& parseAttributes(arena, s, gl_ctx))
{
return true;
}
LOGF(Error, "Error parsing shader\n");
return false;
}