diff --git a/src/modules/graphics/opengl/Shader.cpp b/src/modules/graphics/opengl/Shader.cpp index 1fc9fd94f..150d42719 100644 --- a/src/modules/graphics/opengl/Shader.cpp +++ b/src/modules/graphics/opengl/Shader.cpp @@ -50,7 +50,7 @@ namespace if (prevShader != NULL) prevShader->attach(); else - Shader::detach(); + curShader->detach(); } Shader *curShader; @@ -188,6 +188,39 @@ void Shader::createProgram(const std::vector &shaderids) } } +void Shader::mapActiveUniforms() +{ + uniforms.clear(); + + GLint numuniforms; + glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &numuniforms); + + GLsizei bufsize; + glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, (GLint *) &bufsize); + + for (int i = 0; i < numuniforms; i++) + { + GLchar *cname = new GLchar[bufsize]; + GLsizei namelength; + + Uniform u; + + glGetActiveUniform(program, (GLuint) i, bufsize, &namelength, &u.count, &u.type, cname); + + u.name = std::string(cname, (size_t) namelength); + u.location = glGetUniformLocation(program, u.name.c_str()); + + delete[] cname; + + // glGetActiveUniform appends "[0]" to the end of array uniform names... + if (u.name.find("[0]") == u.name.length() - 3) + u.name.erase(u.name.length() - 3); + + if (u.location != -1) + uniforms[u.name] = u; + } +} + bool Shader::loadVolatile() { // zero out active texture list @@ -208,6 +241,8 @@ bool Shader::loadVolatile() createProgram(shaderids); + mapActiveUniforms(); + if (current == this) { current = NULL; // make sure glUseProgram gets called @@ -307,39 +342,127 @@ void Shader::detach() current = NULL; } +const Shader::Uniform &Shader::getUniform(const std::string &name) const +{ + std::map::const_iterator it = uniforms.find(name); + + if (it == uniforms.end()) + throw love::Exception("Variable '%s' does not exist.\n" + "A common error is to define but not use the variable.", name.c_str()); + + return it->second; +} + +int Shader::getUniformTypeSize(GLenum type) const +{ + switch (type) + { + case GL_INT: + case GL_FLOAT: + case GL_BOOL: + case GL_SAMPLER_1D: + case GL_SAMPLER_2D: + case GL_SAMPLER_3D: + return 1; + case GL_INT_VEC2: + case GL_FLOAT_VEC2: + case GL_BOOL_VEC2: + return 2; + case GL_INT_VEC3: + case GL_FLOAT_VEC3: + case GL_BOOL_VEC3: + return 3; + case GL_INT_VEC4: + case GL_FLOAT_VEC4: + case GL_BOOL_VEC4: + return 4; + default: + break; + } + + return 1; +} + +Shader::UniformType Shader::getUniformBaseType(GLenum type) const +{ + switch (type) + { + case GL_INT: + case GL_INT_VEC2: + case GL_INT_VEC3: + case GL_INT_VEC4: + return UNIFORM_INT; + case GL_FLOAT: + case GL_FLOAT_VEC2: + case GL_FLOAT_VEC3: + case GL_FLOAT_VEC4: + return UNIFORM_FLOAT; + case GL_BOOL: + case GL_BOOL_VEC2: + case GL_BOOL_VEC3: + case GL_BOOL_VEC4: + return UNIFORM_BOOL; + case GL_SAMPLER_1D: + case GL_SAMPLER_2D: + case GL_SAMPLER_3D: + return UNIFORM_SAMPLER; + default: + break; + } + + return UNIFORM_UNKNOWN; +} + +void Shader::checkSetUniformError(const Uniform &u, int size, int count, UniformType sendtype) const +{ + if (!program) + throw love::Exception("No active shader program."); + + int realsize = getUniformTypeSize(u.type); + + if (size != realsize) + throw love::Exception("Value size of %d does not match variable size of %d.", size, realsize); + + if ((u.count == 1 && count > 1) || count < 0) + throw love::Exception("Invalid number of values (expected %d, got %d).", u.count, count); + + UniformType basetype = getUniformBaseType(u.type); + + if (basetype == UNIFORM_SAMPLER && sendtype != UNIFORM_SAMPLER) + throw love::Exception("Cannot send a value of this type to an Image variable."); + + if (sendtype == UNIFORM_FLOAT && basetype == UNIFORM_INT) + throw love::Exception("Cannot convert between float and int."); +} + void Shader::sendFloat(const std::string &name, int size, const GLfloat *vec, int count) { TemporaryAttacher attacher(this); - GLint location = getUniformLocation(name); - if (size < 1 || size > 4) - throw love::Exception("Invalid variable size: %d (expected 1-4).", size); + const Uniform &u = getUniform(name); + checkSetUniformError(u, size, count, UNIFORM_FLOAT); switch (size) { case 4: - glUniform4fv(location, count, vec); + glUniform4fv(u.location, count, vec); break; case 3: - glUniform3fv(location, count, vec); + glUniform3fv(u.location, count, vec); break; case 2: - glUniform2fv(location, count, vec); + glUniform2fv(u.location, count, vec); break; case 1: default: - glUniform1fv(location, count, vec); + glUniform1fv(u.location, count, vec); break; } - - // throw error if needed - checkSetUniformError(); } void Shader::sendMatrix(const std::string &name, int size, const GLfloat *m, int count) { TemporaryAttacher attacher(this); - GLint location = getUniformLocation(name); if (size < 2 || size > 4) { @@ -347,40 +470,41 @@ void Shader::sendMatrix(const std::string &name, int size, const GLfloat *m, int "(can only set 2x2, 3x3 or 4x4 matrices).", size,size); } + const Uniform &u = getUniform(name); + checkSetUniformError(u, size, count, UNIFORM_FLOAT); + switch (size) { case 4: - glUniformMatrix4fv(location, count, GL_FALSE, m); + glUniformMatrix4fv(u.location, count, GL_FALSE, m); break; case 3: - glUniformMatrix3fv(location, count, GL_FALSE, m); + glUniformMatrix3fv(u.location, count, GL_FALSE, m); break; case 2: default: - glUniformMatrix2fv(location, count, GL_FALSE, m); + glUniformMatrix2fv(u.location, count, GL_FALSE, m); break; } - - // throw error if needed - checkSetUniformError(); } void Shader::sendTexture(const std::string &name, GLuint texture) { TemporaryAttacher attacher(this); - GLint location = getUniformLocation(name); + int textureunit = getTextureUnit(name); + const Uniform &u = getUniform(name); + checkSetUniformError(u, 1, 1, UNIFORM_SAMPLER); + // bind texture to assigned texture unit and send uniform to shader program gl.bindTextureToUnit(texture, textureunit, false); - glUniform1i(location, textureunit); + + glUniform1i(u.location, textureunit); // reset texture unit gl.setActiveTextureUnit(0); - // throw error if needed - checkSetUniformError(); - // increment global shader texture id counter for this texture unit, if we haven't already if (activeTextureUnits[textureunit-1] == 0) ++textureCounters[textureunit-1]; @@ -399,24 +523,6 @@ void Shader::sendCanvas(const std::string &name, const Canvas &canvas) sendTexture(name, canvas.getTextureName()); } -GLint Shader::getUniformLocation(const std::string &name) -{ - std::map::const_iterator it = uniforms.find(name); - if (it != uniforms.end()) - return it->second; - - GLint location = glGetUniformLocation(program, name.c_str()); - if (location == -1) - { - throw love::Exception( - "Cannot get location of shader variable `%s'.\n" - "A common error is to define but not use the variable.", name.c_str()); - } - - uniforms[name] = location; - return location; -} - int Shader::getTextureUnit(const std::string &name) { std::map::const_iterator it = textureUnitPool.find(name); @@ -446,19 +552,6 @@ int Shader::getTextureUnit(const std::string &name) return textureunit; } -void Shader::checkSetUniformError() -{ - GLenum error_code = glGetError(); - if (GL_INVALID_OPERATION == error_code) - { - throw love::Exception( - "Invalid operation:\n" - "- Trying to send the wrong value type to shader variable, or\n" - "- Trying to send array values with wrong dimension, or\n" - "- Invalid variable name."); - } -} - std::string Shader::getGLSLVersion() { // GL_SHADING_LANGUAGE_VERSION may not be available in OpenGL < 2.0. diff --git a/src/modules/graphics/opengl/Shader.h b/src/modules/graphics/opengl/Shader.h index c5d944989..cc7a31556 100644 --- a/src/modules/graphics/opengl/Shader.h +++ b/src/modules/graphics/opengl/Shader.h @@ -127,8 +127,33 @@ public: private: - GLint getUniformLocation(const std::string &name); - void checkSetUniformError(); + // Represents a single uniform/extern shader variable. + struct Uniform + { + GLint location; + GLint count; + GLenum type; + std::string name; + }; + + // Types of potential uniform variables used in love's shaders. + enum UniformType + { + UNIFORM_FLOAT, + UNIFORM_INT, + UNIFORM_BOOL, + UNIFORM_SAMPLER, + UNIFORM_UNKNOWN + }; + + // Map active uniform names to their locations. + void mapActiveUniforms(); + + const Uniform &getUniform(const std::string &name) const; + + int getUniformTypeSize(GLenum type) const; + UniformType getUniformBaseType(GLenum type) const; + void checkSetUniformError(const Uniform &u, int size, int count, UniformType sendtype) const; GLuint compileCode(ShaderType type, const std::string &code); void createProgram(const std::vector &shaderids); @@ -146,10 +171,11 @@ private: // Shader compiler warning strings for individual shader stages. std::map shaderWarnings; - GLuint program; // volatile + // volatile + GLuint program; // Uniform location buffer map - std::map uniforms; + std::map uniforms; // Texture unit pool for setting images std::map textureUnitPool; // textureUnitPool[name] = textureunit diff --git a/src/scripts/graphics.lua b/src/scripts/graphics.lua index 64782ffee..2db1a923c 100644 --- a/src/scripts/graphics.lua +++ b/src/scripts/graphics.lua @@ -1496,6 +1496,16 @@ void main() { end end + local function shader_send_protected(self, name, value, ...) + local success, err = pcall(shader_dispatch_send, self, name, value, ...) + if not success then + if err and type(err) == "string" then + err = err:gsub("^(.-):(%d+): ", "") + end + error(err, 2) + end + end + local newShader = love.graphics.newShader function love.graphics.newShader(vertexcode, pixelcode) love.graphics.newShader = newShader @@ -1503,7 +1513,7 @@ void main() { local success, shader = pcall(love.graphics.newShader, vertexcode, pixelcode) if success then local meta = getmetatable(shader) - meta.send = shader_dispatch_send + meta.send = shader_send_protected meta.sendBoolean = meta.sendFloat return shader else diff --git a/src/scripts/graphics.lua.h b/src/scripts/graphics.lua.h index f2f8ec2e1..d17f0211c 100644 --- a/src/scripts/graphics.lua.h +++ b/src/scripts/graphics.lua.h @@ -6679,6 +6679,27 @@ const unsigned char graphics_lua[] = 0x65, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x2e, 0x22, 0x29, 0x2e, 0x22, 0x29, 0x0a, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x73, 0x68, + 0x61, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, + 0x64, 0x28, 0x73, 0x65, 0x6c, 0x66, 0x2c, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x20, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x2c, 0x20, 0x2e, 0x2e, 0x2e, 0x29, 0x0a, + 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x2c, 0x20, 0x65, + 0x72, 0x72, 0x20, 0x3d, 0x20, 0x70, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x5f, + 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x2c, 0x20, 0x73, 0x65, 0x6c, + 0x66, 0x2c, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2c, 0x20, 0x2e, 0x2e, + 0x2e, 0x29, 0x0a, + 0x09, 0x09, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x65, 0x72, 0x72, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x79, 0x70, 0x65, + 0x28, 0x65, 0x72, 0x72, 0x29, 0x20, 0x3d, 0x3d, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x20, + 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x65, 0x72, 0x72, 0x20, 0x3d, 0x20, 0x65, 0x72, 0x72, 0x3a, 0x67, 0x73, 0x75, 0x62, + 0x28, 0x22, 0x5e, 0x28, 0x2e, 0x2d, 0x29, 0x3a, 0x28, 0x25, 0x64, 0x2b, 0x29, 0x3a, 0x20, 0x22, 0x2c, 0x20, + 0x22, 0x22, 0x29, 0x0a, + 0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x09, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x65, 0x72, 0x72, 0x2c, 0x20, 0x32, 0x29, 0x0a, + 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6e, 0x65, 0x77, 0x53, 0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x53, 0x68, 0x61, 0x64, 0x65, 0x72, 0x0a, @@ -6698,13 +6719,13 @@ const unsigned char graphics_lua[] = 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x3d, 0x20, 0x67, 0x65, 0x74, 0x6d, 0x65, 0x74, 0x61, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x28, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x29, 0x0a, 0x09, 0x09, 0x09, 0x6d, 0x65, 0x74, 0x61, 0x2e, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x3d, 0x20, 0x73, 0x68, 0x61, - 0x64, 0x65, 0x72, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x0a, + 0x64, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x0a, 0x09, 0x09, 0x09, 0x6d, 0x65, 0x74, 0x61, 0x2e, 0x73, 0x65, 0x6e, 0x64, 0x42, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x20, 0x3d, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x2e, 0x73, 0x65, 0x6e, 0x64, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x0a, 0x09, 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x0a, 0x09, 0x09, 0x65, 0x6c, 0x73, 0x65, 0x0a, - 0x09, 0x09, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x2c, 0x20, 0x32, - 0x29, 0x0a, + 0x09, 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x73, 0x68, + 0x61, 0x64, 0x65, 0x72, 0x2c, 0x20, 0x32, 0x29, 0x0a, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x65, 0x6e, 0x64, 0x0a,