Commits

Alex Szpakowski  committed 8d3a640

Improved error messages when sending bad values to a shader (also issue #587)

  • Participants
  • Parent commits 717156c

Comments (0)

Files changed (4)

File src/modules/graphics/opengl/Shader.cpp

 			if (prevShader != NULL)
 				prevShader->attach();
 			else
-				Shader::detach();
+				curShader->detach();
 		}
 
 		Shader *curShader;
 	}
 }
 
+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
 
 	createProgram(shaderids);
 
+	mapActiveUniforms();
+
 	if (current == this)
 	{
 		current = NULL; // make sure glUseProgram gets called
 	current = NULL;
 }
 
+const Shader::Uniform &Shader::getUniform(const std::string &name) const
+{
+	std::map<std::string, Uniform>::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)
 	{
 							  "(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];
 	sendTexture(name, canvas.getTextureName());
 }
 
-GLint Shader::getUniformLocation(const std::string &name)
-{
-	std::map<std::string, GLint>::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<std::string, GLint>::const_iterator it = textureUnitPool.find(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.

File src/modules/graphics/opengl/Shader.h

 
 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<GLuint> &shaderids);
 	// Shader compiler warning strings for individual shader stages.
 	std::map<ShaderType, std::string> shaderWarnings;
 
-	GLuint program; // volatile
+	// volatile
+	GLuint program;
 
 	// Uniform location buffer map
-	std::map<std::string, GLint> uniforms;
+	std::map<std::string, Uniform> uniforms;
 
 	// Texture unit pool for setting images
 	std::map<std::string, GLint> textureUnitPool; // textureUnitPool[name] = textureunit

File src/scripts/graphics.lua

 		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
 		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

File src/scripts/graphics.lua.h

 	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,
 	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,