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.
748 lines
19 KiB
748 lines
19 KiB
|
|
#include <cassert> |
|
#include <cstddef> |
|
#include <cstdlib> |
|
#include <cstdio> |
|
#include <cstring> |
|
#include <fstream> |
|
|
|
#define GLM_FORCE_XYZW_ONLY |
|
#include <glm/gtc/matrix_transform.hpp> |
|
|
|
#include "asset.h" |
|
#include "dumbLog.h" |
|
#include "dummy_shader.h" |
|
#include "GLDebug.h" |
|
#include "shader.h" |
|
#include "util.h" |
|
|
|
|
|
// NOTE: forward declarations |
|
|
|
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); |
|
|
|
// FIXME: replace with utilCStrMatch, and remove getShaderByHash, we don't |
|
// need a hashing function if we're not storing in a hash table |
|
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; |
|
|
|
size_t vs_size, fs_size; |
|
const char* v_str = (const char*) SDL_LoadFile(vs, &vs_size); |
|
const char* f_str = (const char*) SDL_LoadFile(fs, &fs_size); |
|
GLuint vs_id, fs_id; |
|
|
|
if (compileAndLinkShader(s, v_str, f_str, vs_id, fs_id)) { |
|
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 = 0; 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) |
|
{ |
|
for (u32 i = 0; i < gl_ctx->num_shaders; i++) { |
|
if (utilCStrMatch(name, gl_ctx->shaders[i].name)) |
|
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; |
|
} |
|
|
|
GLBuffer* initGLBackingBuffer(GLContext* gl_ctx, |
|
MemoryArena* arena, |
|
const char* name, |
|
GLenum data_type, |
|
u32 buf_size, |
|
void* data) |
|
{ |
|
GLBuffer* buf = getFreeUBO(gl_ctx); |
|
glGenBuffers(1, &buf->id); |
|
buf->target = GL_UNIFORM_BUFFER; |
|
buf->data_type = data_type; |
|
buf->data_size = buf_size; |
|
buf->name = arenaCopyCStr(arena, name); |
|
|
|
glBindBuffer(buf->target, buf->id); |
|
glBufferData(buf->target, buf->data_size, data, GL_DYNAMIC_DRAW); |
|
buf->binding_idx = gl_ctx->binding_count++; |
|
glBindBufferBase(buf->target, buf->binding_idx, buf->id); |
|
|
|
return buf; |
|
} |
|
|
|
void |
|
updateGLBuffer(GLBuffer* gl_buf, void* data) |
|
{ |
|
assert(gl_buf && data); |
|
glBindBuffer(gl_buf->target, gl_buf->id); |
|
glBufferSubData(gl_buf->target, 0, gl_buf->data_size, data); |
|
} |
|
|
|
void |
|
renderVAO(GLMesh* glmesh, |
|
mat4* node_xform, |
|
ShaderProgram* shader, |
|
GLTexture* gl_tex, |
|
GLenum draw_mode) |
|
{ |
|
glUseProgram(shader->prog_id); |
|
glBindVertexArray(glmesh->vao_id); |
|
|
|
for (u32 i = 0; i < shader->num_uniforms; i++) { |
|
const GLUniform& uniform = shader->uniforms[i]; |
|
|
|
if (uniform.uniform_type == UNIFORM_NODE_XFORM) { |
|
glUniformMatrix4fv(uniform.location, 1, GL_FALSE, |
|
(float*) node_xform); |
|
} |
|
|
|
else if (glmesh->has_texture && |
|
uniform.uniform_type == UNIFORM_SAMPLER) |
|
{ |
|
glBindTexture(GL_TEXTURE_2D, gl_tex->id); |
|
glUniform1i(uniform.location, 0); |
|
} |
|
} |
|
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, glmesh->element_buf->id); |
|
glDrawElements(draw_mode, glmesh->num_indices, GL_UNSIGNED_SHORT, 0); |
|
glBindVertexArray(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(MemoryArena* arena, |
|
const Mesh& m, |
|
u32 num_mappings) |
|
{ |
|
GLMesh glm = {0}; |
|
glm.num_indices = m.num_indices; |
|
glm.num_vertex_attrib_buffers = num_mappings; |
|
glm.vertex_attrib_buffers = ARENA_ALLOC(arena, GLBuffer, num_mappings); |
|
glm.element_buf = ARENA_ALLOC(arena, GLBuffer, 1); |
|
glm.node_xform = ARENA_ALLOC(arena, mat4, 1); |
|
*glm.node_xform = 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; |
|
// FIXME: this should be allocated on the same arena as the parent GLBuffer |
|
// with utilAllocateCStr |
|
buf->name = utilAllocateCStr(attrib->name); |
|
} |
|
|
|
// TODO: might as well pass in pointer to GLMesh, since that's how we're |
|
// going to use this, can avoid copying GLMesh twice that way |
|
GLMesh |
|
loadGLMesh(MemoryArena* arena, |
|
const Mesh& m, |
|
GLenum draw_mode, |
|
GLenum usage, |
|
GLTexture* diffuse_texture, |
|
u32 num_mappings, |
|
GLBufferToAttribMapping mappings[]) |
|
{ |
|
GLMesh glm = initGLMesh(arena, m, num_mappings); |
|
|
|
if (diffuse_texture) { |
|
glm.has_texture = true; |
|
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; |
|
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, |
|
usage); |
|
glVertexAttribPointer(attrib->location, attrib->num_components, |
|
attrib->component_type, GL_FALSE, 0, 0); |
|
glEnableVertexAttribArray(attrib->location); |
|
} |
|
|
|
// FIXME: should we be re-using initGLAttribBuffer here? |
|
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, |
|
usage); |
|
|
|
// TODO: many of these GL functions can set an error state |
|
// TODO: return error status |
|
glBindVertexArray(0); |
|
|
|
return 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 (vert_src && frag_src && 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); |
|
} |
|
|
|
return (is_linked == GL_TRUE); |
|
} |
|
|
|
LOGF(Error, "empty shader source\n"); |
|
return false; |
|
} |
|
|
|
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; |
|
} |
|
} |
|
|
|
UniformType |
|
getUniformType(const char* name) |
|
{ |
|
if (utilCStrMatch(name, "sampler")) |
|
return UNIFORM_SAMPLER; |
|
else if (utilCStrMatch(name, "node_xform")) |
|
return UNIFORM_NODE_XFORM; |
|
else if (utilCStrMatch(name, "normal_xform")) |
|
return UNIFORM_NORMAL_XFORM; |
|
else if (utilCStrMatch(name, "view_xform")) |
|
return UNIFORM_VIEW_XFORM; |
|
else if (utilCStrMatch(name, "proj_xform")) |
|
return UNIFORM_PROJECTION_XFORM; |
|
else if (utilCStrMatch(name, "matrices")) |
|
return UNIFORM_BLOCK_XFORMS; |
|
else if (utilCStrMatch(name, "lights")) |
|
return UNIFORM_BLOCK_LIGHTS; |
|
else |
|
return UNIFORM_UNKNOWN; |
|
} |
|
|
|
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.gl_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.uniform_type = getUniformType(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 (utilCStrMatch(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); |
|
ub.uniform_type = getUniformType(ub.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; |
|
} |
|
|
|
|