diff --git a/src/library/doc/doc.texi b/src/library/doc/doc.texi index 7c4b438..2f8ed57 100644 --- a/src/library/doc/doc.texi +++ b/src/library/doc/doc.texi @@ -2040,6 +2040,187 @@ myprogram:setattribute(0, 4, true, false, 3, 0, 12) @end verbatim @end example +@node shaderprogram-setuniform1i +@subsection setuniform1i + +Sets one integer as a uniform value. The first param is the "location" +specified in GLSL and the second is the value. This value will be +permanently associated with this location for this program, until it's +overwritten by another setuniform call. + +@example lua +@verbatim +myprogram:setuniform1i(location, myinteger) +@end verbatim +@end example + +@node shaderprogram-setuniform2i +@subsection setuniform2i + +Sets two integers as a uniform value. The first param is the "location" +specified in GLSL and the rest are the values. These values will be +permanently associated with this location for this program, until it's +overwritten by another setuniform call. + +@example lua +@verbatim +myprogram:setuniform2i(location, myint1, myint2) +@end verbatim +@end example + +@node shaderprogram-setuniform3i +@subsection setuniform3i + +Sets three integers as a uniform value. The first param is the +"location" specified in GLSL and the rest are the values. These values +will be permanently associated with this location for this program, +until it's overwritten by another setuniform call. + +@example lua +@verbatim +myprogram:setuniform3i(location, myint1, myint2, myint3) +@end verbatim +@end example + +@node shaderprogram-setuniform4i +@subsection setuniform4i + +Sets four integers as a uniform value. The first param is the "location" +specified in GLSL and the rest are the values. These values will be +permanently associated with this location for this program, until it's +overwritten by another setuniform call. + +@example lua +@verbatim +myprogram:setuniform4i(location, myint1, myint2, myint3, myint4) +@end verbatim +@end example + +@node shaderprogram-setuniform1f +@subsection setuniform1f + +Sets one float as a uniform value. The first param is the "location" +specified in GLSL and the second is the value. This value will be +permanently associated with this location for this program, until it's +overwritten by another setuniform call. + +@example lua +@verbatim +myprogram:setuniform1f(location, mynumber) +@end verbatim +@end example + +@node shaderprogram-setuniform2f +@subsection setuniform2f + +Sets two floats as a uniform value. The first param is the "location" +specified in GLSL and the rest are the values. These values will be +permanently associated with this location for this program, until it's +overwritten by another setuniform call. + +@example lua +@verbatim +myprogram:setuniform2f(location, mynum1, mynum2) +@end verbatim +@end example + +@node shaderprogram-setuniform3f +@subsection setuniform3f + +Sets three floats as a uniform value. The first param is the "location" +specified in GLSL and the rest are the values. These values will be +permanently associated with this location for this program, until it's +overwritten by another setuniform call. + +@example lua +@verbatim +myprogram:setuniform3f(location, mynum1, mynum2, mynum3) +@end verbatim +@end example + +@node shaderprogram-setuniform4f +@subsection setuniform4f + +Sets four floats as a uniform value. The first param is the "location" +specified in GLSL and the rest are the values. These values will be +permanently associated with this location for this program, until it's +overwritten by another setuniform call. + +@example lua +@verbatim +myprogram:setuniform4f(location, mynum1, mynum2, mynum3, mynum4) +@end verbatim +@end example + +@node shaderprogram-setuniformmatrix2f +@subsection setuniformmatrix2f + +Sets four floats as a uniform mat2x2 value. The first param is the +"location" specified in GLSL, the second is the "transpose" boolean, and +the rest are the values. Transposing a matrix turns it from row-major to +column-major or vice versa. These values will be permanently associated +with this location for this program, until it's overwritten by another +setuniform call. + +@example lua +@verbatim +myprogram:setuniformmatrix2f(location, false, n11, n21, n12, n22) +@end verbatim +@end example + +@node shaderprogram-setuniformmatrix3f +@subsection setuniformmatrix3f + +Sets nine floats as a uniform mat3x3 value. The first param is the +"location" specified in GLSL, the second is the "transpose" boolean, and +the rest are the values. Transposing a matrix turns it from row-major to +column-major or vice versa. These values will be permanently associated +with this location for this program, until it's overwritten by another +setuniform call. + +@example lua +@verbatim +myprogram:setuniformmatrix3f(location, false, n11, n21, n31, n12, n22, n32, n13, n23, n33) +@end verbatim +@end example + +@node shaderprogram-setuniformmatrix4f +@subsection setuniformmatrix4f + +Sets 16 floats as a uniform mat4x4 value. The first param is the +"location" specified in GLSL, the second is the "transpose" boolean, and +the rest are the values. Transposing a matrix turns it from row-major to +column-major or vice versa. These values will be permanently associated +with this location for this program, until it's overwritten by another +setuniform call. + +In the following example, the 16 values are returned from +@ref{transform-get}, taking advantage of the Lua feature where multiple +return values can be used implicitly as multiple function arguments: + +@example lua +@verbatim +myprogram:setuniformmatrix4f(location, false, mytransform:get()) +@end verbatim +@end example + +@node shaderprogram-setuniformsurface +@subsection setuniformsurface + +Sets a @ref{objects-surface} as a uniform value. This will usually be +used for uniform sampler2D variables. This value will be permanently +associated with this location for this program, until it's overwritten +by another setuniform call. Note that if the surface is destroyed before +being used for a render, the results will be undefined, so make sure to +keep your surface object in scope for as long as it remains bound to any +uniforms. + +@example lua +@verbatim +myprogram:setuniformsurface(location, mysurface) +@end verbatim +@end example + @node shaderprogram-drawtosurface @section drawtosurface @@ -3542,7 +3723,9 @@ the data will be 2 bytes. By the way, there's no way to query attribute locations by name, so using @code{layout(location=...)} in your GLSL code is required to be -able to pass data in. The same goes for uniform variables. +able to pass data in. The same goes for uniform variables. The behaviour +of attributes or uniforms with overlapping memory regions is undefined +in Bolt, so don't do it. You'll notice we defined the format of our data, but we don't actually have any data yet. The format of each attribute is defined in the shader diff --git a/src/library/gl.c b/src/library/gl.c index 317821b..c636687 100644 --- a/src/library/gl.c +++ b/src/library/gl.c @@ -45,6 +45,7 @@ static GLuint program_direct_vao; static GLuint buffer_vertices_square; #define GLSLHEADER "#version 330 core\n" +#define GLSLPLUGINEXTENSIONHEADER "#extension GL_ARB_explicit_uniform_location : require\n" // "direct" program is basically a blit but with transparency. // there are different vertex shaders for targeting the screen vs targeting a surface. @@ -143,6 +144,10 @@ static uint8_t _bolt_gl_plugin_shaderprogram_init(struct ShaderProgramFunctions* static void _bolt_gl_plugin_shader_destroy(void* userdata); static void _bolt_gl_plugin_shaderprogram_destroy(void* userdata); static uint8_t _bolt_gl_plugin_shaderprogram_set_attribute(void* userdata, uint8_t attribute, uint8_t type_width, uint8_t type_is_signed, uint8_t type_is_float, uint8_t size, uint32_t offset, uint32_t stride); +static void _bolt_gl_plugin_shaderprogram_set_uniform_floats(void* userdata, int location, uint8_t count, double* values); +static void _bolt_gl_plugin_shaderprogram_set_uniform_ints(void* userdata, int location, uint8_t count, int* values); +static void _bolt_gl_plugin_shaderprogram_set_uniform_matrix(void* userdata, int location, uint8_t transpose, uint8_t size, double* values); +static void _bolt_gl_plugin_shaderprogram_set_uniform_surface(void* userdata, int location, void* target); static void _bolt_gl_plugin_shaderprogram_drawtosurface(void* userdata, void* surface_, void* buffer_, uint32_t count); static void _bolt_gl_plugin_shaderbuffer_init(struct ShaderBufferFunctions* out, const void* data, uint32_t len); static void _bolt_gl_plugin_shaderbuffer_destroy(void* userdata); @@ -192,6 +197,7 @@ struct PluginProgramUserdata { GLuint program; GLuint vao; GLbitfield bindings_enabled; + struct hashmap* uniforms; struct PluginProgramAttrBinding bindings[MAX_PROGRAM_BINDINGS]; }; @@ -199,6 +205,69 @@ struct PluginShaderBufferUserdata { GLuint buffer; }; +struct PluginProgramUniform { + GLint location; + uint8_t count; + uint8_t type; // 0 = vec, 1 = matrix, 2 = sampler + union { + uint8_t is_float; // for type=0 + uint8_t transpose; // for type=1 + GLuint sampler; // for type=2 + } sub; + union { + GLint i[4]; // for type=0 && !is_float + GLfloat f[16]; // for everything else + } values; +}; + +static int uniform_compare(const void* a, const void* b, void* udata) { + return (*(GLuint*)a) - (*(GLuint*)b); +} + +static uint64_t uniform_hash(const void* item, uint64_t seed0, uint64_t seed1) { + const GLint* const id = item; + return hashmap_sip(id, sizeof *id, seed0, seed1); +} + +#define UCASE(N,TYPE) case N: gl.Uniform##N##TYPE##v(uniform->location, 1, uniform->values.TYPE); break; +#define UMATCASE(N) case N: gl.UniformMatrix##N##fv(uniform->location, 1, uniform->sub.transpose, uniform->values.f); break; +static void uniform_upload(const struct PluginProgramUniform* uniform, uint32_t* active_texture) { + switch (uniform->type) { + case 0: + if (uniform->sub.is_float) { + switch (uniform->count) { + UCASE(1,f) + UCASE(2,f) + UCASE(3,f) + UCASE(4,f) + } + } else { + switch (uniform->count) { + UCASE(1,i) + UCASE(2,i) + UCASE(3,i) + UCASE(4,i) + } + } + break; + case 1: + switch (uniform->count) { + UMATCASE(2) + UMATCASE(3) + UMATCASE(4) + } + break; + case 2: + gl.ActiveTexture(GL_TEXTURE0 + *active_texture); + lgl->BindTexture(GL_TEXTURE_2D, uniform->sub.sampler); + gl.Uniform1i(uniform->location, *active_texture); + *active_texture += 1; + break; + } +} +#undef UCASE +#undef UMATCASE + struct GLContext* _bolt_context() { #if defined(_WIN32) return (struct GLContext*)TlsGetValue(current_context_tls); @@ -644,6 +713,8 @@ static void _bolt_gl_load(void* (*GetProcAddress)(const char*)) { INIT_GL_FUNC(Uniform2iv) INIT_GL_FUNC(Uniform3iv) INIT_GL_FUNC(Uniform4iv) + INIT_GL_FUNC(UniformMatrix2fv) + INIT_GL_FUNC(UniformMatrix3fv) INIT_GL_FUNC(UniformMatrix4fv) INIT_GL_FUNC(UnmapBuffer) INIT_GL_FUNC(UseProgram) @@ -2319,9 +2390,9 @@ static void _bolt_gl_plugin_game_view_rect(int* x, int* y, int* w, int* h) { static uint8_t _bolt_gl_plugin_vertex_shader_init(struct ShaderFunctions* out, const char* source, int len, char* output, int output_len) { const GLuint shader = gl.CreateShader(GL_VERTEX_SHADER); - const GLchar* sources[] = {GLSLHEADER, source}; - const GLint lengths[] = {sizeof(GLSLHEADER) - sizeof(*GLSLHEADER), len}; - gl.ShaderSource(shader, 2, sources, lengths); + const GLchar* sources[] = {GLSLHEADER, GLSLPLUGINEXTENSIONHEADER, source}; + const GLint lengths[] = {sizeof(GLSLHEADER) - sizeof(*GLSLHEADER), sizeof(GLSLPLUGINEXTENSIONHEADER) - sizeof(*GLSLPLUGINEXTENSIONHEADER), len}; + gl.ShaderSource(shader, 3, sources, lengths); gl.CompileShader(shader); GLint status; gl.GetShaderiv(shader, GL_COMPILE_STATUS, &status); @@ -2336,9 +2407,9 @@ static uint8_t _bolt_gl_plugin_vertex_shader_init(struct ShaderFunctions* out, c static uint8_t _bolt_gl_plugin_fragment_shader_init(struct ShaderFunctions* out, const char* source, int len, char* output, int output_len) { const GLuint shader = gl.CreateShader(GL_FRAGMENT_SHADER); - const GLchar* sources[] = {GLSLHEADER, source}; - const GLint lengths[] = {sizeof(GLSLHEADER) - sizeof(*GLSLHEADER), len}; - gl.ShaderSource(shader, 2, sources, lengths); + const GLchar* sources[] = {GLSLHEADER, GLSLPLUGINEXTENSIONHEADER, source}; + const GLint lengths[] = {sizeof(GLSLHEADER) - sizeof(*GLSLHEADER), sizeof(GLSLPLUGINEXTENSIONHEADER) - sizeof(*GLSLPLUGINEXTENSIONHEADER), len}; + gl.ShaderSource(shader, 3, sources, lengths); gl.CompileShader(shader); GLint status; gl.GetShaderiv(shader, GL_COMPILE_STATUS, &status); @@ -2368,11 +2439,16 @@ static uint8_t _bolt_gl_plugin_shaderprogram_init(struct ShaderProgramFunctions* gl.DetachShader(program, fs); out->userdata = malloc(sizeof(struct PluginProgramUserdata)); struct PluginProgramUserdata* userdata = (struct PluginProgramUserdata*)out->userdata; + userdata->uniforms = hashmap_new(sizeof(struct PluginProgramUniform), 16, 0, 0, uniform_hash, uniform_compare, NULL, NULL); userdata->program = program; userdata->bindings_enabled = 0; gl.GenVertexArrays(1, &userdata->vao); out->set_attribute = _bolt_gl_plugin_shaderprogram_set_attribute; out->draw_to_surface = _bolt_gl_plugin_shaderprogram_drawtosurface; + out->set_uniform_floats = _bolt_gl_plugin_shaderprogram_set_uniform_floats; + out->set_uniform_ints = _bolt_gl_plugin_shaderprogram_set_uniform_ints; + out->set_uniform_matrix = _bolt_gl_plugin_shaderprogram_set_uniform_matrix; + out->set_uniform_surface = _bolt_gl_plugin_shaderprogram_set_uniform_surface; return true; } @@ -2383,6 +2459,7 @@ static void _bolt_gl_plugin_shader_destroy(void* userdata) { static void _bolt_gl_plugin_shaderprogram_destroy(void* userdata) { struct PluginProgramUserdata* program = (struct PluginProgramUserdata*)userdata; + hashmap_free(program->uniforms); gl.DeleteProgram(program->program); gl.DeleteVertexArrays(1, &program->vao); free(userdata); @@ -2434,6 +2511,60 @@ static uint8_t _bolt_gl_plugin_shaderprogram_set_attribute(void* userdata, uint8 return true; } +static void _bolt_gl_plugin_shaderprogram_set_uniform_floats(void* userdata, int location, uint8_t count, double* values) { + const struct PluginProgramUserdata* program = (struct PluginProgramUserdata*)userdata; + struct PluginProgramUniform uniform = { + .location = location, + .count = count, + .type = 0, + .sub.is_float = true, + }; + for (size_t i = 0; i < count; i += 1) { + uniform.values.f[i] = (GLfloat)values[i]; + } + hashmap_set(program->uniforms, &uniform); +} + +static void _bolt_gl_plugin_shaderprogram_set_uniform_ints(void* userdata, int location, uint8_t count, int* values) { + const struct PluginProgramUserdata* program = (struct PluginProgramUserdata*)userdata; + struct PluginProgramUniform uniform = { + .location = location, + .count = count, + .type = 0, + .sub.is_float = false, + }; + for (size_t i = 0; i < count; i += 1) { + uniform.values.i[i] = (GLint)values[i]; + } + hashmap_set(program->uniforms, &uniform); +} + +static void _bolt_gl_plugin_shaderprogram_set_uniform_matrix(void* userdata, int location, uint8_t transpose, uint8_t size, double* values) { + const struct PluginProgramUserdata* program = (struct PluginProgramUserdata*)userdata; + struct PluginProgramUniform uniform = { + .location = location, + .count = size, + .type = 1, + .sub.transpose = transpose, + }; + for (size_t i = 0; i < (size * size); i += 1) { + uniform.values.f[i] = (GLfloat)values[i]; + } + hashmap_set(program->uniforms, &uniform); +} + +static void _bolt_gl_plugin_shaderprogram_set_uniform_surface(void* userdata, int location, void* target) { + const struct PluginProgramUserdata* program = (struct PluginProgramUserdata*)userdata; + const struct PluginSurfaceUserdata* surface = (struct PluginSurfaceUserdata*)target; + struct PluginProgramUniform uniform = { + .location = location, + .count = 1, + .type = 2, + .sub.sampler = surface->renderbuffer, + }; + hashmap_set(program->uniforms, &uniform); +} + static void _bolt_gl_plugin_shaderprogram_drawtosurface(void* userdata, void* surface_, void* buffer_, uint32_t count) { const struct PluginProgramUserdata* program = (struct PluginProgramUserdata*)userdata; const struct PluginSurfaceUserdata* surface = surface_; @@ -2462,6 +2593,12 @@ static void _bolt_gl_plugin_shaderprogram_drawtosurface(void* userdata, void* su gl.DisableVertexAttribArray(i); } } + uint32_t active_texture = 0; + size_t iter = 0; + void* item; + while (hashmap_iter(program->uniforms, &iter, &item)) { + uniform_upload(item, &active_texture); + } gl.BindFramebuffer(GL_DRAW_FRAMEBUFFER, surface->framebuffer); lgl->Viewport(0, 0, surface->width, surface->height); lgl->Disable(GL_DEPTH_TEST); @@ -2469,6 +2606,13 @@ static void _bolt_gl_plugin_shaderprogram_drawtosurface(void* userdata, void* su lgl->Disable(GL_CULL_FACE); lgl->DrawArrays(GL_TRIANGLES, 0, count); + if (active_texture > 0) { + for (size_t i = 0; i < active_texture; i += 1) { + gl.ActiveTexture(GL_TEXTURE0 + i); + lgl->BindTexture(GL_TEXTURE_2D, c->texture_units[i]->id); + } + gl.ActiveTexture(GL_TEXTURE0 + c->active_texture); + } if (depth_test) lgl->Enable(GL_DEPTH_TEST); if (scissor_test) lgl->Enable(GL_SCISSOR_TEST); if (cull_face) lgl->Enable(GL_CULL_FACE); diff --git a/src/library/gl.h b/src/library/gl.h index 1cb79b3..5d6164f 100644 --- a/src/library/gl.h +++ b/src/library/gl.h @@ -97,6 +97,8 @@ struct GLProcFunctions { void (*Uniform2iv)(GLint, GLsizei, const GLint*); void (*Uniform3iv)(GLint, GLsizei, const GLint*); void (*Uniform4iv)(GLint, GLsizei, const GLint*); + void (*UniformMatrix2fv)(GLint, GLsizei, GLboolean, const GLfloat*); + void (*UniformMatrix3fv)(GLint, GLsizei, GLboolean, const GLfloat*); void (*UniformMatrix4fv)(GLint, GLsizei, GLboolean, const GLfloat*); GLboolean (*UnmapBuffer)(GLenum); void (*UseProgram)(GLuint); diff --git a/src/library/plugin/plugin.h b/src/library/plugin/plugin.h index a56447b..5c98093 100644 --- a/src/library/plugin/plugin.h +++ b/src/library/plugin/plugin.h @@ -186,6 +186,19 @@ struct ShaderProgramFunctions { /// Draws to a surface using this shader program. void (*draw_to_surface)(void* userdata, void* surface_, void* buffer_, uint32_t count); + + /// Associates some values with a uniform in this shader program by its location. + void (*set_uniform_floats)(void* userdata, int location, uint8_t count, double* values); + + /// Associates some values with a uniform in this shader program by its location. + void (*set_uniform_ints)(void* userdata, int location, uint8_t count, int* values); + + /// Associates some values with a uniform in this shader program by its location. + void (*set_uniform_matrix)(void* userdata, int location, uint8_t transpose, uint8_t size, double* values); + + /// Associates a surface userdata object with a uniform in this shader program by its location. + /// Typically this will be used for a sampler2D variable. + void (*set_uniform_surface)(void* userdata, int location, void* target); }; /// Struct containing "vtable" callback information for shader buffers diff --git a/src/library/plugin/plugin_api.c b/src/library/plugin/plugin_api.c index 6bea86f..cf1a888 100644 --- a/src/library/plugin/plugin_api.c +++ b/src/library/plugin/plugin_api.c @@ -1648,6 +1648,60 @@ static int api_shaderprogram_setattribute(lua_State* state) { return 0; } +#define DEFSETUNIFORM(N) \ +static int api_shaderprogram_setuniform##N##i(lua_State* state) { \ + struct ShaderProgramFunctions* program = require_self_userdata(state, "setuniform" #N "i"); \ + const lua_Integer location = luaL_checkinteger(state, 2); \ + int values[N]; \ + for (size_t i = 0; i < N; i += 1) { \ + values[i] = (int)luaL_checkinteger(state, i + 3); \ + } \ + program->set_uniform_ints(program->userdata, location, N, values); \ + return 0; \ +} \ +static int api_shaderprogram_setuniform##N##f(lua_State* state) { \ + struct ShaderProgramFunctions* program = require_self_userdata(state, "setuniform" #N "f"); \ + const lua_Integer location = luaL_checkinteger(state, 2); \ + double values[N]; \ + for (size_t i = 0; i < N; i += 1) { \ + values[i] = (double)luaL_checknumber(state, i + 3); \ + } \ + program->set_uniform_floats(program->userdata, location, N, values); \ + return 0; \ +} + +DEFSETUNIFORM(1) +DEFSETUNIFORM(2) +DEFSETUNIFORM(3) +DEFSETUNIFORM(4) +#undef DEFSETUNIFORM + +#define DEFSETUNIFORMMATRIX(N) \ +static int api_shaderprogram_setuniformmatrix##N##f(lua_State* state) { \ + struct ShaderProgramFunctions* program = require_self_userdata(state, "setuniformmatrix" #N "f"); \ + const lua_Integer location = luaL_checkinteger(state, 2); \ + const uint8_t transpose = lua_toboolean(state, 3); \ + double values[N * N]; \ + for (size_t i = 0; i < (N * N); i += 1) { \ + values[i] = (double)luaL_checknumber(state, i + 4); \ + } \ + program->set_uniform_matrix(program->userdata, location, transpose, N, values); \ + return 0; \ +} + +DEFSETUNIFORMMATRIX(2) +DEFSETUNIFORMMATRIX(3) +DEFSETUNIFORMMATRIX(4) +#undef DEFSETUNIFORMMATRIX + +static int api_shaderprogram_setuniformsurface(lua_State* state) { + struct ShaderProgramFunctions* program = require_self_userdata(state, "setuniformsurface"); + const lua_Integer location = luaL_checkinteger(state, 2); + const struct SurfaceFunctions* surface = require_userdata(state, 3, "setuniformsurface"); + program->set_uniform_surface(program->userdata, location, surface->userdata); + return 0; +} + static int api_shaderprogram_drawtosurface(lua_State* state) { const struct ShaderProgramFunctions* program = require_self_userdata(state, "drawtosurface"); const struct SurfaceFunctions* surface = require_userdata(state, 2, "drawtosurface"); @@ -1848,6 +1902,18 @@ static struct ApiFuncTemplate buffer_functions[] = { static struct ApiFuncTemplate shaderprogram_functions[] = { BOLTFUNC(setattribute, shaderprogram), + BOLTFUNC(setuniform1i, shaderprogram), + BOLTFUNC(setuniform2i, shaderprogram), + BOLTFUNC(setuniform3i, shaderprogram), + BOLTFUNC(setuniform4i, shaderprogram), + BOLTFUNC(setuniform1f, shaderprogram), + BOLTFUNC(setuniform2f, shaderprogram), + BOLTFUNC(setuniform3f, shaderprogram), + BOLTFUNC(setuniform4f, shaderprogram), + BOLTFUNC(setuniformmatrix2f, shaderprogram), + BOLTFUNC(setuniformmatrix3f, shaderprogram), + BOLTFUNC(setuniformmatrix4f, shaderprogram), + BOLTFUNC(setuniformsurface, shaderprogram), BOLTFUNC(drawtosurface, shaderprogram), };