Generalize Shader to support more types of uniforms;

This commit is contained in:
bjorn 2017-10-21 13:39:50 -07:00
parent b82ed7fd56
commit 54533351bb
4 changed files with 373 additions and 266 deletions

View File

@ -3,163 +3,134 @@
#include "graphics/shader.h"
#include "math/transform.h"
struct TempData {
void* data;
size_t size;
};
static struct TempData tempData;
int l_lovrShaderSend(lua_State* L) {
Shader* shader = luax_checktype(L, 1, Shader);
const char* name = luaL_checkstring(L, 2);
lua_settop(L, 3);
int id = lovrShaderGetUniformId(shader, name);
if (id == -1) {
Uniform* uniform = lovrShaderGetUniform(shader, name);
if (!uniform) {
return luaL_error(L, "Unknown shader variable '%s'", name);
}
GLenum type;
int size;
lovrShaderGetUniformType(shader, name, &type, &size);
lovrGraphicsBindProgram(shader->id);
float data[16];
int n;
vec_float_t values;
vec_init(&values);
switch (type) {
case GL_SAMPLER_2D:
case GL_SAMPLER_CUBE:
case GL_INT:
lovrShaderSendInt(shader, id, luaL_checkinteger(L, 3));
break;
case GL_FLOAT:
lovrShaderSendFloat(shader, id, luaL_checknumber(L, 3));
break;
case GL_FLOAT_VEC2:
luaL_checktype(L, 3, LUA_TTABLE);
lua_rawgeti(L, 3, 1);
if (!lua_istable(L, -1)) {
lua_newtable(L);
lua_pushvalue(L, 3);
lua_rawseti(L, -2, 1);
} else {
lua_pop(L, 1);
}
n = lua_objlen(L, -1);
if (n < size) {
return luaL_error(L, "Expected %d vec3s, got %d", size, n);
}
for (int i = 0; i < size; i++) {
lua_rawgeti(L, -1, i + 1);
for (int j = 0; j < 2; j++) {
lua_rawgeti(L, -1, j + 1);
vec_push(&values, lua_tonumber(L, -1));
lua_pop(L, 1);
}
lua_pop(L, 1);
}
lovrShaderSendFloatVec2(shader, id, size, values.data);
break;
case GL_FLOAT_VEC3:
luaL_checktype(L, 3, LUA_TTABLE);
lua_rawgeti(L, 3, 1);
if (!lua_istable(L, -1)) {
lua_newtable(L);
lua_pushvalue(L, 3);
lua_rawseti(L, -2, 1);
} else {
lua_pop(L, 1);
}
n = lua_objlen(L, -1);
if (n < size) {
return luaL_error(L, "Expected %d vec3s, got %d", size, n);
}
for (int i = 0; i < size; i++) {
lua_rawgeti(L, -1, i + 1);
for (int j = 0; j < 3; j++) {
lua_rawgeti(L, -1, j + 1);
vec_push(&values, lua_tonumber(L, -1));
lua_pop(L, 1);
}
lua_pop(L, 1);
}
lovrShaderSendFloatVec3(shader, id, size, values.data);
break;
case GL_FLOAT_VEC4:
luaL_checktype(L, 3, LUA_TTABLE);
lua_rawgeti(L, 3, 1);
if (!lua_istable(L, -1)) {
lua_newtable(L);
lua_pushvalue(L, 3);
lua_rawseti(L, -2, 1);
} else {
lua_pop(L, 1);
}
n = lua_objlen(L, -1);
if (n < size) {
return luaL_error(L, "Expected %d vec3s, got %d", size, n);
}
for (int i = 0; i < size; i++) {
lua_rawgeti(L, -1, i + 1);
for (int j = 0; j < 4; j++) {
lua_rawgeti(L, -1, j + 1);
vec_push(&values, lua_tonumber(L, -1));
lua_pop(L, 1);
}
lua_pop(L, 1);
}
lovrShaderSendFloatVec4(shader, id, size, values.data);
break;
case GL_FLOAT_MAT2:
luaL_checktype(L, 3, LUA_TTABLE);
for (int i = 0; i < 4; i++) {
lua_rawgeti(L, 3, i + 1);
data[i] = lua_tonumber(L, -1);
lua_pop(L, 1);
}
lovrShaderSendFloatMat2(shader, id, data);
break;
case GL_FLOAT_MAT3:
luaL_checktype(L, 3, LUA_TTABLE);
for (int i = 0; i < 9; i++) {
lua_rawgeti(L, 3, i + 1);
data[i] = lua_tonumber(L, -1);
lua_pop(L, 1);
}
lovrShaderSendFloatMat3(shader, id, data);
break;
case GL_FLOAT_MAT4:
if (lua_isuserdata(L, 3)) {
Transform* transform = luax_checktype(L, 3, Transform);
memcpy(data, transform->matrix, 16 * sizeof(float));
} else {
luaL_checktype(L, 3, LUA_TTABLE);
for (int i = 0; i < 16; i++) {
lua_rawgeti(L, 3, i + 1);
data[i] = lua_tonumber(L, -1);
lua_pop(L, 1);
}
}
lovrShaderSendFloatMat4(shader, id, data);
break;
default:
return luaL_error(L, "Unknown uniform type %d", type);
if (!tempData.data) {
tempData.data = malloc(uniform->size);
} else if (tempData.size < uniform->size) {
tempData.size = uniform->size;
tempData.data = realloc(tempData.data, tempData.size);
}
vec_deinit(&values);
int* ints = (int*) tempData.data;
float* floats = (float*) tempData.data;
Texture** textures = (Texture**) tempData.data;
int n = 1;
int components = uniform->components;
if (components > 1) {
luaL_checktype(L, 3, LUA_TTABLE);
lua_rawgeti(L, 3, 1);
if (!lua_istable(L, -1)) {
lua_newtable(L);
lua_pushvalue(L, 3);
lua_rawseti(L, -2, 1);
} else {
lua_pop(L, 1);
}
n = lua_objlen(L, -1);
if (n != uniform->count) {
const char* elements = uniform->count == 1 ? "element" : "elements";
return luaL_error(L, "Expected %d %s for array '%s', got %d", uniform->count, elements, uniform->name, n);
}
}
switch (uniform->type) {
case UNIFORM_FLOAT:
if (components == 1) {
floats[0] = luaL_checkinteger(L, 3);
} else {
luaL_checktype(L, 3, LUA_TTABLE);
for (int i = 0; i < n; i++) {
lua_rawgeti(L, -1, i + 1);
if (lua_type(L, -1) != LUA_TTABLE || (int) lua_objlen(L, -1) != components) {
return luaL_error(L, "Expected %d components for uniform '%s' #%d, got %d", components, uniform->name, lua_objlen(L, -1));
}
for (int j = 0; j < components; j++) {
lua_rawgeti(L, -1, j + 1);
floats[i * components + j] = luaL_checknumber(L, -1);
lua_pop(L, 1);
}
lua_pop(L, 1);
}
}
lovrShaderSetFloat(shader, name, floats, n);
break;
case UNIFORM_INT:
if (components == 1) {
ints[0] = luaL_checkinteger(L, 3);
} else {
luaL_checktype(L, 3, LUA_TTABLE);
for (int i = 0; i < n; i++) {
lua_rawgeti(L, -1, i + 1);
if (lua_type(L, -1) != LUA_TTABLE || (int) lua_objlen(L, -1) != components) {
return luaL_error(L, "Expected %d components for uniform '%s' #%d, got %d", components, uniform->name, lua_objlen(L, -1));
}
for (int j = 0; j < components; j++) {
lua_rawgeti(L, -1, j + 1);
ints[i * components + j] = luaL_checkinteger(L, -1);
lua_pop(L, 1);
}
lua_pop(L, 1);
}
}
lovrShaderSetInt(shader, name, ints, n);
break;
case UNIFORM_MATRIX:
if (components == 4 && lua_isuserdata(L, 3)) {
Transform* transform = luax_checktype(L, 3, Transform);
memcpy(floats, transform->matrix, 16 * sizeof(float));
} else {
luaL_checktype(L, 3, LUA_TTABLE);
for (int i = 0; i < n; i++) {
lua_rawgeti(L, -1, i + 1);
for (int j = 0; j < components * components; j++) {
lua_rawgeti(L, -1, j + 1);
floats[i * components * components + j] = luaL_checknumber(L, -1);
lua_pop(L, 1);
}
lua_pop(L, 1);
}
}
lovrShaderSetMatrix(shader, name, floats, n);
break;
case UNIFORM_SAMPLER:
if (components == 1) {
textures[0] = luax_checktype(L, 3, Texture);
} else {
luaL_checktype(L, 3, LUA_TTABLE);
for (int i = 0; i < n; i++) {
lua_rawgeti(L, -1, i + 1);
textures[i] = luax_checktype(L, -1, Texture);
lua_pop(L, 1);
}
}
lovrShaderSetTexture(shader, name, textures, n);
break;
default: break;
}
return 0;
}

View File

@ -87,8 +87,36 @@ void lovrGraphicsPrepare() {
mat4 model = state.transforms[state.transform][MATRIX_MODEL];
mat4 view = state.transforms[state.transform][MATRIX_VIEW];
mat4 projection = state.canvases[state.canvas].projection;
lovrShaderSetMatrix(shader, "lovrModel", model, 16);
lovrShaderSetMatrix(shader, "lovrView", view, 16);
lovrShaderSetMatrix(shader, "lovrProjection", projection, 16);
float transform[16];
mat4_multiply(mat4_set(transform, view), model);
lovrShaderSetMatrix(shader, "lovrTransform", transform, 16);
if (lovrShaderGetUniform(shader, "lovrNormalMatrix")) {
if (mat4_invert(transform)) {
mat4_transpose(transform);
} else {
mat4_identity(transform);
}
float normalMatrix[9] = {
transform[0], transform[1], transform[2],
transform[4], transform[5], transform[6],
transform[8], transform[9], transform[10]
};
lovrShaderSetMatrix(shader, "lovrNormalMatrix", normalMatrix, 9);
}
// Color
float color[4] = { state.color.r / 255., state.color.g / 255., state.color.b / 255., state.color.a / 255. };
lovrShaderSetFloat(shader, "lovrColor", color, 4);
lovrGraphicsBindProgram(shader->id);
lovrShaderBind(shader, model, view, projection, state.color, 0);
lovrShaderBind(shader);
}
void lovrGraphicsCreateWindow(int w, int h, int fullscreen, int msaa, const char* title, const char* icon) {

View File

@ -5,6 +5,63 @@
#include <stdio.h>
#include <stdlib.h>
static UniformType getUniformType(GLenum type, const char* debug) {
switch (type) {
case GL_FLOAT:
case GL_FLOAT_VEC2:
case GL_FLOAT_VEC3:
case GL_FLOAT_VEC4:
return UNIFORM_FLOAT;
case GL_INT:
case GL_INT_VEC2:
case GL_INT_VEC3:
case GL_INT_VEC4:
return UNIFORM_INT;
case GL_FLOAT_MAT2:
case GL_FLOAT_MAT3:
case GL_FLOAT_MAT4:
return UNIFORM_MATRIX;
case GL_SAMPLER_2D:
case GL_SAMPLER_CUBE:
return UNIFORM_SAMPLER;
default:
lovrThrow("Unsupported type for uniform '%s'", debug);
return UNIFORM_FLOAT;
}
}
static int getUniformComponents(GLenum type) {
switch (type) {
case GL_FLOAT:
case GL_INT:
case GL_SAMPLER_2D:
case GL_SAMPLER_CUBE:
return 1;
case GL_FLOAT_VEC2:
case GL_INT_VEC2:
case GL_FLOAT_MAT2:
return 2;
case GL_FLOAT_VEC3:
case GL_INT_VEC3:
case GL_FLOAT_MAT3:
return 3;
case GL_FLOAT_VEC4:
case GL_INT_VEC4:
case GL_FLOAT_MAT4:
return 4;
default:
return 1;
}
}
static GLuint compileShader(GLenum type, const char* source) {
GLuint shader = glCreateShader(type);
@ -79,46 +136,103 @@ Shader* lovrShaderCreate(const char* vertexSource, const char* fragmentSource) {
// Link
GLuint id = linkShaders(vertexShader, fragmentShader);
shader->id = id;
// Compute information about uniforms
lovrGraphicsBindProgram(id);
// Uniform introspection
GLint uniformCount;
int textureSlot = 0;
GLsizei bufferSize = LOVR_MAX_UNIFORM_LENGTH / sizeof(GLchar);
map_init(&shader->uniforms);
glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &uniformCount);
for (int i = 0; i < uniformCount; i++) {
Uniform uniform;
glGetActiveUniform(id, i, bufferSize, NULL, &uniform.count, &uniform.type, uniform.name);
glGetActiveUniform(id, i, bufferSize, NULL, &uniform.count, &uniform.glType, uniform.name);
char* subscript = strchr(uniform.name, '[');
if (subscript) {
*subscript = '\0';
}
uniform.location = glGetUniformLocation(id, uniform.name);
uniform.index = i;
uniform.location = glGetUniformLocation(id, uniform.name);
uniform.type = getUniformType(uniform.glType, uniform.name);
uniform.components = getUniformComponents(uniform.glType);
uniform.baseTextureSlot = (uniform.type == UNIFORM_SAMPLER) ? textureSlot : -1;
switch (uniform.type) {
case UNIFORM_FLOAT:
uniform.size = uniform.components * uniform.count * sizeof(float);
uniform.value.data = malloc(uniform.size);
break;
case UNIFORM_INT:
uniform.size = uniform.components * uniform.count * sizeof(int);
uniform.value.data = malloc(uniform.size);
break;
case UNIFORM_MATRIX:
uniform.size = uniform.components * uniform.components * uniform.count * sizeof(int);
uniform.value.data = malloc(uniform.size);
break;
case UNIFORM_SAMPLER:
uniform.size = uniform.components * uniform.count * MAX(sizeof(Texture*), sizeof(int));
uniform.value.data = malloc(uniform.size);
// Use the value for ints to bind texture slots, but use the value for textures afterwards.
for (int i = 0; i < uniform.count; i++) {
uniform.value.ints[i] = uniform.baseTextureSlot + i;
}
glUniform1iv(uniform.location, uniform.count, uniform.value.ints);
break;
}
memset(uniform.value.data, 0, uniform.size);
size_t offset = 0;
for (int j = 0; j < uniform.count; j++) {
int location = uniform.location;
if (uniform.count > 1) {
char name[LOVR_MAX_UNIFORM_LENGTH];
snprintf(name, LOVR_MAX_UNIFORM_LENGTH, "%s[%d]", uniform.name, j);
location = glGetUniformLocation(id, name);
}
switch (uniform.type) {
case UNIFORM_FLOAT:
glGetUniformfv(id, location, &uniform.value.floats[offset]);
offset += uniform.components;
break;
case UNIFORM_INT:
glGetUniformiv(id, location, &uniform.value.ints[offset]);
offset += uniform.components;
break;
case UNIFORM_MATRIX:
glGetUniformfv(id, location, &uniform.value.floats[offset]);
offset += uniform.components * uniform.components;
break;
default:
break;
}
}
map_set(&shader->uniforms, uniform.name, uniform);
textureSlot += (uniform.type == UNIFORM_SAMPLER) ? uniform.count : 0;
}
// Initial state
shader->id = id;
mat4_identity(shader->model);
mat4_identity(shader->view);
mat4_identity(shader->projection);
shader->color = (Color) { 0, 0, 0, 0 };
// Send initial uniform values to shader
lovrGraphicsBindProgram(id);
lovrShaderBind(shader, shader->model, shader->view, shader->projection, shader->color, 1);
return shader;
}
Shader* lovrShaderCreateDefault(DefaultShader type) {
switch (type) {
case SHADER_DEFAULT: return lovrShaderCreate(NULL, NULL);
case SHADER_SKYBOX: {
Shader* shader = lovrShaderCreate(lovrSkyboxVertexShader, lovrSkyboxFragmentShader);
lovrShaderSendInt(shader, lovrShaderGetUniformId(shader, "cube"), 1);
return shader;
}
case SHADER_SKYBOX: return lovrShaderCreate(lovrSkyboxVertexShader, lovrSkyboxFragmentShader); break;
case SHADER_FONT: return lovrShaderCreate(NULL, lovrFontFragmentShader);
case SHADER_FULLSCREEN: return lovrShaderCreate(lovrNoopVertexShader, NULL);
default: lovrThrow("Unknown default shader type");
@ -132,64 +246,54 @@ void lovrShaderDestroy(const Ref* ref) {
free(shader);
}
void lovrShaderBind(Shader* shader, mat4 model, mat4 view, mat4 projection, Color color, int force) {
int dirtyModel = force || memcmp(shader->model, model, 16 * sizeof(float));
int dirtyView = force || memcmp(shader->view, view, 16 * sizeof(float));
int dirtyTransform = dirtyModel || dirtyView;
int dirtyProjection = force || memcmp(shader->projection, projection, 16 * sizeof(float));
int dirtyColor = force || memcmp(&shader->color, &color, sizeof(Color));
void lovrShaderBind(Shader* shader) {
map_iter_t iter = map_iter(&shader->uniforms);
const char* key;
while ((key = map_next(&shader->uniforms, &iter)) != NULL) {
Uniform* uniform = map_get(&shader->uniforms, key);
if (dirtyModel) {
int uniformId = lovrShaderGetUniformId(shader, "lovrModel");
lovrShaderSendFloatMat4(shader, uniformId, model);
memcpy(shader->model, model, 16 * sizeof(float));
}
if (dirtyView) {
int uniformId = lovrShaderGetUniformId(shader, "lovrView");
lovrShaderSendFloatMat4(shader, uniformId, view);
memcpy(shader->view, view, 16 * sizeof(float));
}
if (dirtyTransform) {
float matrix[16];
int uniformId = lovrShaderGetUniformId(shader, "lovrTransform");
mat4_multiply(mat4_set(matrix, view), model);
if (uniformId > -1) {
lovrShaderSendFloatMat4(shader, uniformId, matrix);
if (uniform->type != UNIFORM_SAMPLER && !uniform->dirty) {
continue;
}
if (mat4_invert(matrix)) {
mat4_transpose(matrix);
} else {
mat4_identity(matrix);
uniform->dirty = 0;
int count = uniform->count;
void* data = uniform->value.data;
switch (uniform->type) {
case UNIFORM_FLOAT:
switch (uniform->components) {
case 1: glUniform1fv(uniform->location, count, data); break;
case 2: glUniform2fv(uniform->location, count, data); break;
case 3: glUniform3fv(uniform->location, count, data); break;
case 4: glUniform4fv(uniform->location, count, data); break;
}
break;
case UNIFORM_INT:
switch (uniform->components) {
case 1: glUniform1iv(uniform->location, count, data); break;
case 2: glUniform2iv(uniform->location, count, data); break;
case 3: glUniform3iv(uniform->location, count, data); break;
case 4: glUniform4iv(uniform->location, count, data); break;
}
break;
case UNIFORM_MATRIX:
switch (uniform->components) {
case 2: glUniformMatrix2fv(uniform->location, count, GL_FALSE, data); break;
case 3: glUniformMatrix3fv(uniform->location, count, GL_FALSE, data); break;
case 4: glUniformMatrix4fv(uniform->location, count, GL_FALSE, data); break;
}
break;
case UNIFORM_SAMPLER:
for (int i = 0; i < count; i++) {
TextureType type = uniform->glType == GL_SAMPLER_2D ? TEXTURE_2D : TEXTURE_CUBE;
lovrGraphicsBindTexture(uniform->value.textures[i], type, uniform->baseTextureSlot + i);
}
break;
}
float normalMatrix[9] = {
matrix[0], matrix[1], matrix[2],
matrix[4], matrix[5], matrix[6],
matrix[8], matrix[9], matrix[10]
};
uniformId = lovrShaderGetUniformId(shader, "lovrNormalMatrix");
if (uniformId > -1) {
lovrShaderSendFloatMat3(shader, uniformId, normalMatrix);
}
}
if (dirtyProjection) {
int uniformId = lovrShaderGetUniformId(shader, "lovrProjection");
lovrShaderSendFloatMat4(shader, uniformId, projection);
memcpy(shader->projection, projection, 16 * sizeof(float));
}
if (dirtyColor) {
int uniformId = lovrShaderGetUniformId(shader, "lovrColor");
float c[4] = { color.r / 255., color.g / 255., color.b / 255., color.a / 255. };
lovrShaderSendFloatVec4(shader, uniformId, 1, c);
shader->color = color;
}
}
@ -201,51 +305,39 @@ int lovrShaderGetAttributeId(Shader* shader, const char* name) {
return glGetAttribLocation(shader->id, name);
}
int lovrShaderGetUniformId(Shader* shader, const char* name) {
Uniform* uniform = map_get(&shader->uniforms, name);
return uniform ? uniform->location : -1;
Uniform* lovrShaderGetUniform(Shader* shader, const char* name) {
return map_get(&shader->uniforms, name);
}
int lovrShaderGetUniformType(Shader* shader, const char* name, GLenum* type, int* count) {
static void lovrShaderSetUniform(Shader* shader, const char* name, UniformType type, void* data, int count, size_t size, const char* debug) {
Uniform* uniform = map_get(&shader->uniforms, name);
if (!uniform) {
return 1;
return;
}
*type = uniform->type;
*count = uniform->count;
return 0;
lovrAssert(uniform->type == type, "Unable to send %ss to uniform %s", debug, uniform->name);
lovrAssert(count * size == uniform->size, "Expected %d %ss for uniform %s, got %d", uniform->count, debug, uniform->name, count);
if (!uniform->dirty && !memcmp(uniform->value.data, data, count * size)) {
return;
}
memcpy(uniform->value.data, data, count * size);
uniform->dirty = 1;
}
void lovrShaderSendInt(Shader* shader, int id, int value) {
glUniform1i(id, value);
void lovrShaderSetFloat(Shader* shader, const char* name, float* data, int count) {
lovrShaderSetUniform(shader, name, UNIFORM_FLOAT, data, count, sizeof(float), "float");
}
void lovrShaderSendFloat(Shader* shader, int id, float value) {
glUniform1f(id, value);
void lovrShaderSetInt(Shader* shader, const char* name, int* data, int count) {
lovrShaderSetUniform(shader, name, UNIFORM_INT, data, count, sizeof(int), "int");
}
void lovrShaderSendFloatVec2(Shader* shader, int id, int count, float* vector) {
glUniform2fv(id, count, vector);
void lovrShaderSetMatrix(Shader* shader, const char* name, float* data, int count) {
lovrShaderSetUniform(shader, name, UNIFORM_MATRIX, data, count, sizeof(float), "float");
}
void lovrShaderSendFloatVec3(Shader* shader, int id, int count, float* vector) {
glUniform3fv(id, count, vector);
}
void lovrShaderSendFloatVec4(Shader* shader, int id, int count, float* vector) {
glUniform4fv(id, count, vector);
}
void lovrShaderSendFloatMat2(Shader* shader, int id, float* matrix) {
glUniformMatrix2fv(id, 1, GL_FALSE, matrix);
}
void lovrShaderSendFloatMat3(Shader* shader, int id, float* matrix) {
glUniformMatrix3fv(id, 1, GL_FALSE, matrix);
}
void lovrShaderSendFloatMat4(Shader* shader, int id, float* matrix) {
glUniformMatrix4fv(id, 1, GL_FALSE, matrix);
void lovrShaderSetTexture(Shader* shader, const char* name, Texture** data, int count) {
lovrShaderSetUniform(shader, name, UNIFORM_SAMPLER, data, count, sizeof(Texture*), "texture");
}

View File

@ -1,3 +1,4 @@
#include "graphics/texture.h"
#include "math/math.h"
#include "lib/map/map.h"
#include "lib/glfw.h"
@ -10,6 +11,20 @@
#define LOVR_SHADER_TEX_COORD 2
#define LOVR_MAX_UNIFORM_LENGTH 256
typedef enum {
UNIFORM_FLOAT,
UNIFORM_MATRIX,
UNIFORM_INT,
UNIFORM_SAMPLER
} UniformType;
typedef union {
void* data;
int* ints;
float* floats;
Texture** textures;
} UniformValue;
typedef enum {
SHADER_DEFAULT,
SHADER_SKYBOX,
@ -19,10 +34,16 @@ typedef enum {
typedef struct {
GLchar name[LOVR_MAX_UNIFORM_LENGTH];
GLenum glType;
int index;
int location;
GLenum type;
int count;
int components;
size_t size;
UniformType type;
UniformValue value;
int baseTextureSlot;
int dirty;
} Uniform;
typedef map_t(Uniform) map_uniform_t;
@ -40,15 +61,10 @@ typedef struct {
Shader* lovrShaderCreate(const char* vertexSource, const char* fragmentSource);
Shader* lovrShaderCreateDefault(DefaultShader type);
void lovrShaderDestroy(const Ref* ref);
void lovrShaderBind(Shader* shader, mat4 model, mat4 view, mat4 projection, Color color, int force);
void lovrShaderBind(Shader* shader);
int lovrShaderGetAttributeId(Shader* shader, const char* name);
int lovrShaderGetUniformId(Shader* shader, const char* name);
int lovrShaderGetUniformType(Shader* shader, const char* name, GLenum* type, int* count);
void lovrShaderSendInt(Shader* shader, int id, int value);
void lovrShaderSendFloat(Shader* shader, int id, float value);
void lovrShaderSendFloatVec2(Shader* shader, int id, int count, float* vector);
void lovrShaderSendFloatVec3(Shader* shader, int id, int count,float* vector);
void lovrShaderSendFloatVec4(Shader* shader, int id, int count, float* vector);
void lovrShaderSendFloatMat2(Shader* shader, int id, float* matrix);
void lovrShaderSendFloatMat3(Shader* shader, int id, float* matrix);
void lovrShaderSendFloatMat4(Shader* shader, int id, float* matrix);
Uniform* lovrShaderGetUniform(Shader* shader, const char* name);
void lovrShaderSetFloat(Shader* shader, const char* name, float* data, int count);
void lovrShaderSetInt(Shader* shader, const char* name, int* data, int count);
void lovrShaderSetMatrix(Shader* shader, const char* name, float* data, int count);
void lovrShaderSetTexture(Shader* shader, const char* name, Texture** data, int count);