Browse Source

Internally compose and pass an array of shader source codes to OpenGL, instead of relying on #line to make sure the line number is correct for shader error messages (resolves issue #882)

Alex Szpakowski 11 năm trước cách đây
mục cha
commit
4bc96f1bf6

+ 2 - 2
src/modules/graphics/opengl/Graphics.cpp

@@ -544,9 +544,9 @@ Canvas *Graphics::newCanvas(int width, int height, Texture::Format format, int f
 	return NULL; // never reached
 	return NULL; // never reached
 }
 }
 
 
-Shader *Graphics::newShader(const Shader::ShaderSources &sources)
+Shader *Graphics::newShader(const std::vector<std::string> &vertcode, const std::vector<std::string> &pixelcode)
 {
 {
-	return new Shader(sources);
+	return new Shader(vertcode, pixelcode);
 }
 }
 
 
 Mesh *Graphics::newMesh(const std::vector<Vertex> &vertices, Mesh::DrawMode mode)
 Mesh *Graphics::newMesh(const std::vector<Vertex> &vertices, Mesh::DrawMode mode)

+ 1 - 1
src/modules/graphics/opengl/Graphics.h

@@ -210,7 +210,7 @@ public:
 
 
 	Canvas *newCanvas(int width, int height, Texture::Format format = Texture::FORMAT_NORMAL, int fsaa = 0);
 	Canvas *newCanvas(int width, int height, Texture::Format format = Texture::FORMAT_NORMAL, int fsaa = 0);
 
 
-	Shader *newShader(const Shader::ShaderSources &sources);
+	Shader *newShader(const std::vector<std::string> &vertcode, const std::vector<std::string> &pixelcode);
 
 
 	Mesh *newMesh(const std::vector<Vertex> &vertices, Mesh::DrawMode mode = Mesh::DRAW_MODE_FAN);
 	Mesh *newMesh(const std::vector<Vertex> &vertices, Mesh::DrawMode mode = Mesh::DRAW_MODE_FAN);
 	Mesh *newMesh(int vertexcount, Mesh::DrawMode mode = Mesh::DRAW_MODE_FAN);
 	Mesh *newMesh(int vertexcount, Mesh::DrawMode mode = Mesh::DRAW_MODE_FAN);

+ 27 - 12
src/modules/graphics/opengl/Shader.cpp

@@ -66,21 +66,25 @@ Shader *Shader::current = nullptr;
 GLint Shader::maxTexUnits = 0;
 GLint Shader::maxTexUnits = 0;
 std::vector<int> Shader::textureCounters;
 std::vector<int> Shader::textureCounters;
 
 
-Shader::Shader(const ShaderSources &sources)
-	: shaderSources(sources)
-	, program(0)
+Shader::Shader(const std::vector<std::string> &vertcode, const std::vector<std::string> &pixelcode)
+	: program(0)
 	, builtinUniforms()
 	, builtinUniforms()
 	, vertexAttributes()
 	, vertexAttributes()
 	, lastCanvas((Canvas *) -1)
 	, lastCanvas((Canvas *) -1)
 	, lastViewport()
 	, lastViewport()
 {
 {
-	if (shaderSources.empty())
+	if (vertcode.empty() && pixelcode.empty())
 		throw love::Exception("Cannot create shader: no source code!");
 		throw love::Exception("Cannot create shader: no source code!");
 
 
+	shaderSources[TYPE_VERTEX] = vertcode;
+	shaderSources[TYPE_PIXEL] = pixelcode;
+
 	if (maxTexUnits <= 0)
 	if (maxTexUnits <= 0)
 	{
 	{
 		GLint maxtexunits;
 		GLint maxtexunits;
 		glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxtexunits);
 		glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxtexunits);
+
+		// TU 0 is never used for stored Shader images.
 		maxTexUnits = std::max(maxtexunits - 1, 0);
 		maxTexUnits = std::max(maxtexunits - 1, 0);
 	}
 	}
 
 
@@ -106,7 +110,7 @@ Shader::~Shader()
 	unloadVolatile();
 	unloadVolatile();
 }
 }
 
 
-GLuint Shader::compileCode(ShaderType type, const std::string &code)
+GLuint Shader::compileCode(ShaderType type, const std::vector<std::string> &code)
 {
 {
 	GLenum glshadertype;
 	GLenum glshadertype;
 	const char *typestr;
 	const char *typestr;
@@ -142,9 +146,18 @@ GLuint Shader::compileCode(ShaderType type, const std::string &code)
 			throw love::Exception("Cannot create %s shader object.", typestr);
 			throw love::Exception("Cannot create %s shader object.", typestr);
 	}
 	}
 
 
-	const char *src = code.c_str();
-	size_t srclen = code.length();
-	glShaderSource(shaderid, 1, (const GLchar **)&src, (GLint *)&srclen);
+	std::vector<const GLchar *> codelist;
+	std::vector<GLint> lengthlist;
+
+	for (size_t i = 0; i < code.size(); i++)
+	{
+		codelist.push_back((const GLchar *) code[i].c_str());
+		lengthlist.push_back((GLint) code[i].length());
+	}
+
+	// The code parameter is a list of source code "files." We can hand them
+	// all to OpenGL at once using glShaderSource.
+	glShaderSource(shaderid, codelist.size(), &codelist[0], &lengthlist[0]);
 
 
 	glCompileShader(shaderid);
 	glCompileShader(shaderid);
 
 
@@ -276,10 +289,12 @@ bool Shader::loadVolatile()
 
 
 	std::vector<GLuint> shaderids;
 	std::vector<GLuint> shaderids;
 
 
-	ShaderSources::const_iterator source;
-	for (source = shaderSources.begin(); source != shaderSources.end(); ++source)
+	for (int i = 0; i < (int) TYPE_MAX_ENUM; i++)
 	{
 	{
-		GLuint shaderid = compileCode(source->first, source->second);
+		if (shaderSources[i].empty())
+			continue;
+
+		GLuint shaderid = compileCode((ShaderType) i, shaderSources[i]);
 		shaderids.push_back(shaderid);
 		shaderids.push_back(shaderid);
 	}
 	}
 
 
@@ -288,7 +303,7 @@ bool Shader::loadVolatile()
 
 
 	createProgram(shaderids);
 	createProgram(shaderids);
 
 
-	// Retreive all active uniform variables in this shader from OpenGL.
+	// Get all active uniform variables in this shader from OpenGL.
 	mapActiveUniforms();
 	mapActiveUniforms();
 
 
 	for (int i = 0; i < int(OpenGL::ATTRIB_MAX_ENUM); i++)
 	for (int i = 0; i < int(OpenGL::ATTRIB_MAX_ENUM); i++)

+ 6 - 8
src/modules/graphics/opengl/Shader.h

@@ -74,14 +74,11 @@ public:
 		UNIFORM_MAX_ENUM
 		UNIFORM_MAX_ENUM
 	};
 	};
 
 
-	// Type for a list of shader source codes in the form of sources[shadertype] = code
-	typedef std::map<ShaderType, std::string> ShaderSources;
-
 	/**
 	/**
 	 * Creates a new Shader using a list of source codes.
 	 * Creates a new Shader using a list of source codes.
-	 * Sources must contain either vertex or pixel shader code, or both.
+	 * The sources must contain either vertex or pixel shader code, or both.
 	 **/
 	 **/
-	Shader(const ShaderSources &sources);
+	Shader(const std::vector<std::string> &vertcode, const std::vector<std::string> &pixelcode);
 
 
 	virtual ~Shader();
 	virtual ~Shader();
 
 
@@ -196,7 +193,7 @@ private:
 	UniformType getUniformBaseType(GLenum type) const;
 	UniformType getUniformBaseType(GLenum type) const;
 	void checkSetUniformError(const Uniform &u, int size, int count, UniformType sendtype) const;
 	void checkSetUniformError(const Uniform &u, int size, int count, UniformType sendtype) const;
 
 
-	GLuint compileCode(ShaderType type, const std::string &code);
+	GLuint compileCode(ShaderType type, const std::vector<std::string> &code);
 	void createProgram(const std::vector<GLuint> &shaderids);
 	void createProgram(const std::vector<GLuint> &shaderids);
 
 
 	int getTextureUnit(const std::string &name);
 	int getTextureUnit(const std::string &name);
@@ -206,8 +203,9 @@ private:
 	// Get any warnings or errors generated only by the shader program object.
 	// Get any warnings or errors generated only by the shader program object.
 	std::string getProgramWarnings() const;
 	std::string getProgramWarnings() const;
 
 
-	// List of all shader code attached to this Shader
-	ShaderSources shaderSources;
+	// List of all shader code attached to this Shader. Each shader type has its
+	// own list, which represents separate "files".
+	std::vector<std::string> shaderSources[TYPE_MAX_ENUM];
 
 
 	// Shader compiler warning strings for individual shader stages.
 	// Shader compiler warning strings for individual shader stages.
 	std::map<ShaderType, std::string> shaderWarnings;
 	std::map<ShaderType, std::string> shaderWarnings;

+ 51 - 18
src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -353,16 +353,16 @@ int w_newShader(lua_State *L)
 	if (!Shader::isSupported())
 	if (!Shader::isSupported())
 		return luaL_error(L, "Sorry, your graphics card does not support shaders.");
 		return luaL_error(L, "Sorry, your graphics card does not support shaders.");
 
 
-	// clamp stack to 2 elements
+	// Clamp stack to 2 elements.
 	lua_settop(L, 2);
 	lua_settop(L, 2);
 
 
-	// read any filepath arguments
+	// Read any filepath arguments.
 	for (int i = 1; i <= 2; i++)
 	for (int i = 1; i <= 2; i++)
 	{
 	{
 		if (!lua_isstring(L, i))
 		if (!lua_isstring(L, i))
 			continue;
 			continue;
 
 
-		// call love.filesystem.isFile(arg_i)
+		// Call love.filesystem.isFile(arg_i)
 		luax_getfunction(L, "filesystem", "isFile");
 		luax_getfunction(L, "filesystem", "isFile");
 		lua_pushvalue(L, i);
 		lua_pushvalue(L, i);
 		lua_call(L, 1, 1);
 		lua_call(L, 1, 1);
@@ -382,43 +382,76 @@ int w_newShader(lua_State *L)
 	bool has_arg1 = lua_isstring(L, 1);
 	bool has_arg1 = lua_isstring(L, 1);
 	bool has_arg2 = lua_isstring(L, 2);
 	bool has_arg2 = lua_isstring(L, 2);
 
 
-	// require at least one string argument
+	// Require at least one string argument.
 	if (!(has_arg1 || has_arg2))
 	if (!(has_arg1 || has_arg2))
 		luaL_checkstring(L, 1);
 		luaL_checkstring(L, 1);
 
 
 	luax_getfunction(L, "graphics", "_shaderCodeToGLSL");
 	luax_getfunction(L, "graphics", "_shaderCodeToGLSL");
 
 
-	// push vertexcode and pixelcode strings to the top of the stack
+	// Push vertexcode and pixelcode strings to the top of the stack.
 	lua_pushvalue(L, 1);
 	lua_pushvalue(L, 1);
 	lua_pushvalue(L, 2);
 	lua_pushvalue(L, 2);
 
 
-	// call effectCodeToGLSL, returned values will be at the top of the stack
+	// Call shaderCodeToGLSL, returned values will be at the top of the stack.
 	if (lua_pcall(L, 2, 2, 0) != 0)
 	if (lua_pcall(L, 2, 2, 0) != 0)
 		return luaL_error(L, "%s", lua_tostring(L, -1));
 		return luaL_error(L, "%s", lua_tostring(L, -1));
 
 
-	Shader::ShaderSources sources;
+	// Each shader type might contain several source code strings.
+	std::vector<std::string> sources[Shader::TYPE_MAX_ENUM];
 
 
-	// vertex shader code
-	if (lua_isstring(L, -2))
+	// Vertex shader code.
+	if (!lua_isnoneornil(L, -2))
 	{
 	{
-		std::string vertexcode(luaL_checkstring(L, -2));
-		sources[Shader::TYPE_VERTEX] = vertexcode;
+		std::vector<std::string> &source = sources[Shader::TYPE_VERTEX];
+
+		// The argument might be a Lua array containing strings for the code.
+		if (lua_istable(L, -2))
+		{
+			// Convert table index to absolute.
+			int idx = lua_gettop(L) + 1 - 2;
+
+			// Get all the shader code strings from the Lua array.
+			for (size_t i = 1; i <= lua_objlen(L, idx); i++)
+			{
+				lua_rawgeti(L, idx, i);
+				source.push_back(luax_checkstring(L, -1));
+				lua_pop(L, 1);
+			}
+		}
+		else
+			source.push_back(luax_checkstring(L, -2));
 	}
 	}
 	else if (has_arg1 && has_arg2)
 	else if (has_arg1 && has_arg2)
 		return luaL_error(L, "Could not parse vertex shader code (missing 'position' function?)");
 		return luaL_error(L, "Could not parse vertex shader code (missing 'position' function?)");
 
 
-	// pixel shader code
-	if (lua_isstring(L, -1))
+	// Pixel shader code.
+	if (!lua_isnoneornil(L, -1))
 	{
 	{
-		std::string pixelcode(luaL_checkstring(L, -1));
-		sources[Shader::TYPE_PIXEL] = pixelcode;
+		std::vector<std::string> &source = sources[Shader::TYPE_PIXEL];
+
+		// The argument might be a Lua array containing strings for the code.
+		if (lua_istable(L, -1))
+		{
+			// Convert table index to absolute.
+			int idx = lua_gettop(L) + 1 - 1;
+
+			// Get all the shader code strings from the Lua array.
+			for (size_t i = 1; i <= lua_objlen(L, idx); i++)
+			{
+				lua_rawgeti(L, idx, i);
+				source.push_back(luax_checkstring(L, -1));
+				lua_pop(L, 1);
+			}
+		}
+		else
+			source.push_back(luax_checkstring(L, -1));
 	}
 	}
 	else if (has_arg1 && has_arg2)
 	else if (has_arg1 && has_arg2)
 		return luaL_error(L, "Could not parse pixel shader code (missing 'effect' function?)");
 		return luaL_error(L, "Could not parse pixel shader code (missing 'effect' function?)");
 
 
-	if (sources.empty())
+	if (sources[Shader::TYPE_VERTEX].empty() && sources[Shader::TYPE_PIXEL].empty())
 	{
 	{
-		// Original args had source code, but effectCodeToGLSL couldn't translate it
+		// Original args had source code, but shaderCodeToGLSL couldn't translate it
 		for (int i = 1; i <= 2; i++)
 		for (int i = 1; i <= 2; i++)
 		{
 		{
 			if (lua_isstring(L, i))
 			if (lua_isstring(L, i))
@@ -429,7 +462,7 @@ int w_newShader(lua_State *L)
 	bool should_error = false;
 	bool should_error = false;
 	try
 	try
 	{
 	{
-		Shader *shader = instance->newShader(sources);
+		Shader *shader = instance->newShader(sources[Shader::TYPE_VERTEX], sources[Shader::TYPE_PIXEL]);
 		luax_pushtype(L, "Shader", GRAPHICS_SHADER_T, shader);
 		luax_pushtype(L, "Shader", GRAPHICS_SHADER_T, shader);
 	}
 	}
 	catch (love::Exception &e)
 	catch (love::Exception &e)

+ 22 - 12
src/scripts/graphics.lua

@@ -1365,25 +1365,35 @@ void main() {
 	}
 	}
 
 
 	local function createVertexCode(vertexcode)
 	local function createVertexCode(vertexcode)
+		-- If we return an array, each string in the array is considered separate to GLSL,
+		-- and the line numbers in GLSL errors/warnings maintain this.
 		local vertexcodes = {
 		local vertexcodes = {
-			GLSL_VERSION,
-			GLSL_SYNTAX, GLSL_VERTEX.HEADER, GLSL_UNIFORMS,
-			"#line 1",
-			vertexcode,
-			GLSL_VERTEX.FOOTER,
+			table_concat({
+				GLSL_VERSION,
+				GLSL_SYNTAX, GLSL_VERTEX.HEADER, GLSL_UNIFORMS,
+			}, "\n") .. "\n",
+			table_concat({
+				vertexcode,
+				GLSL_VERTEX.FOOTER
+			}, "\n"),
 		}
 		}
-		return table_concat(vertexcodes, "\n")
+		return vertexcodes
 	end
 	end
 
 
 	local function createPixelCode(pixelcode, is_multicanvas)
 	local function createPixelCode(pixelcode, is_multicanvas)
+		-- If we return an array, each string in the array is considered separate to GLSL,
+		-- and the line numbers in GLSL errors/warnings maintain this.
 		local pixelcodes = {
 		local pixelcodes = {
-			GLSL_VERSION,
-			GLSL_SYNTAX, GLSL_PIXEL.HEADER, GLSL_UNIFORMS,
-			"#line 1",
-			pixelcode,
-			is_multicanvas and GLSL_PIXEL.FOOTER_MULTI_CANVAS or GLSL_PIXEL.FOOTER,
+			table_concat({
+				GLSL_VERSION,
+				GLSL_SYNTAX, GLSL_PIXEL.HEADER, GLSL_UNIFORMS,
+			}, "\n") .. "\n",
+			table_concat({
+				pixelcode,
+				is_multicanvas and GLSL_PIXEL.FOOTER_MULTI_CANVAS or GLSL_PIXEL.FOOTER,
+			}, "\n"),
 		}
 		}
-		return table_concat(pixelcodes, "\n")
+		return pixelcodes
 	end
 	end
 
 
 	local function isVertexCode(code)
 	local function isVertexCode(code)

+ 49 - 25
src/scripts/graphics.lua.h

@@ -6404,42 +6404,66 @@ const unsigned char graphics_lua[] =
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x72, 
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x72, 
 	0x65, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x43, 0x6f, 0x64, 0x65, 0x28, 0x76, 0x65, 0x72, 
 	0x65, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x43, 0x6f, 0x64, 0x65, 0x28, 0x76, 0x65, 0x72, 
 	0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x29, 0x0a,
 	0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x29, 0x0a,
+	0x09, 0x09, 0x2d, 0x2d, 0x20, 0x49, 0x66, 0x20, 0x77, 0x65, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 
+	0x61, 0x6e, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x2c, 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x73, 0x74, 0x72, 
+	0x69, 0x6e, 0x67, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x20, 0x69, 
+	0x73, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, 0x65, 0x64, 0x20, 0x73, 0x65, 0x70, 0x61, 0x72, 
+	0x61, 0x74, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x2c, 0x0a,
+	0x09, 0x09, 0x2d, 0x2d, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x20, 
+	0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x20, 0x65, 0x72, 
+	0x72, 0x6f, 0x72, 0x73, 0x2f, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x20, 0x6d, 0x61, 0x69, 0x6e, 
+	0x74, 0x61, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 
 	0x73, 0x20, 0x3d, 0x20, 0x7b, 0x0a,
 	0x73, 0x20, 0x3d, 0x20, 0x7b, 0x0a,
-	0x09, 0x09, 0x09, 0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x2c, 0x0a,
-	0x09, 0x09, 0x09, 0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x53, 0x59, 0x4e, 0x54, 0x41, 0x58, 0x2c, 0x20, 0x47, 0x4c, 
-	0x53, 0x4c, 0x5f, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58, 0x2e, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x2c, 0x20, 
-	0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x55, 0x4e, 0x49, 0x46, 0x4f, 0x52, 0x4d, 0x53, 0x2c, 0x0a,
-	0x09, 0x09, 0x09, 0x22, 0x23, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x31, 0x22, 0x2c, 0x0a,
-	0x09, 0x09, 0x09, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x0a,
-	0x09, 0x09, 0x09, 0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58, 0x2e, 0x46, 0x4f, 0x4f, 
-	0x54, 0x45, 0x52, 0x2c, 0x0a,
+	0x09, 0x09, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x63, 0x61, 0x74, 0x28, 0x7b, 0x0a,
+	0x09, 0x09, 0x09, 0x09, 0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x2c, 0x0a,
+	0x09, 0x09, 0x09, 0x09, 0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x53, 0x59, 0x4e, 0x54, 0x41, 0x58, 0x2c, 0x20, 0x47, 
+	0x4c, 0x53, 0x4c, 0x5f, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58, 0x2e, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x2c, 
+	0x20, 0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x55, 0x4e, 0x49, 0x46, 0x4f, 0x52, 0x4d, 0x53, 0x2c, 0x0a,
+	0x09, 0x09, 0x09, 0x7d, 0x2c, 0x20, 0x22, 0x5c, 0x6e, 0x22, 0x29, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x5c, 0x6e, 
+	0x22, 0x2c, 0x0a,
+	0x09, 0x09, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x63, 0x61, 0x74, 0x28, 0x7b, 0x0a,
+	0x09, 0x09, 0x09, 0x09, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x0a,
+	0x09, 0x09, 0x09, 0x09, 0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58, 0x2e, 0x46, 0x4f, 
+	0x4f, 0x54, 0x45, 0x52, 0x0a,
+	0x09, 0x09, 0x09, 0x7d, 0x2c, 0x20, 0x22, 0x5c, 0x6e, 0x22, 0x29, 0x2c, 0x0a,
 	0x09, 0x09, 0x7d, 0x0a,
 	0x09, 0x09, 0x7d, 0x0a,
-	0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 
-	0x63, 0x61, 0x74, 0x28, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x2c, 0x20, 0x22, 
-	0x5c, 0x6e, 0x22, 0x29, 0x0a,
+	0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 
+	0x65, 0x73, 0x0a,
 	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, 0x63, 0x72, 
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x72, 
 	0x65, 0x61, 0x74, 0x65, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x43, 0x6f, 0x64, 0x65, 0x28, 0x70, 0x69, 0x78, 0x65, 
 	0x65, 0x61, 0x74, 0x65, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x43, 0x6f, 0x64, 0x65, 0x28, 0x70, 0x69, 0x78, 0x65, 
 	0x6c, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x73, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x61, 0x6e, 
 	0x6c, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x73, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x61, 0x6e, 
 	0x76, 0x61, 0x73, 0x29, 0x0a,
 	0x76, 0x61, 0x73, 0x29, 0x0a,
+	0x09, 0x09, 0x2d, 0x2d, 0x20, 0x49, 0x66, 0x20, 0x77, 0x65, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 
+	0x61, 0x6e, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x2c, 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x73, 0x74, 0x72, 
+	0x69, 0x6e, 0x67, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x20, 0x69, 
+	0x73, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, 0x65, 0x64, 0x20, 0x73, 0x65, 0x70, 0x61, 0x72, 
+	0x61, 0x74, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x2c, 0x0a,
+	0x09, 0x09, 0x2d, 0x2d, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x20, 
+	0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x20, 0x65, 0x72, 
+	0x72, 0x6f, 0x72, 0x73, 0x2f, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x20, 0x6d, 0x61, 0x69, 0x6e, 
+	0x74, 0x61, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x73, 
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x73, 
 	0x20, 0x3d, 0x20, 0x7b, 0x0a,
 	0x20, 0x3d, 0x20, 0x7b, 0x0a,
-	0x09, 0x09, 0x09, 0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x2c, 0x0a,
-	0x09, 0x09, 0x09, 0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x53, 0x59, 0x4e, 0x54, 0x41, 0x58, 0x2c, 0x20, 0x47, 0x4c, 
-	0x53, 0x4c, 0x5f, 0x50, 0x49, 0x58, 0x45, 0x4c, 0x2e, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x2c, 0x20, 0x47, 
-	0x4c, 0x53, 0x4c, 0x5f, 0x55, 0x4e, 0x49, 0x46, 0x4f, 0x52, 0x4d, 0x53, 0x2c, 0x0a,
-	0x09, 0x09, 0x09, 0x22, 0x23, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x31, 0x22, 0x2c, 0x0a,
-	0x09, 0x09, 0x09, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x0a,
-	0x09, 0x09, 0x09, 0x69, 0x73, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x20, 
-	0x61, 0x6e, 0x64, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x50, 0x49, 0x58, 0x45, 0x4c, 0x2e, 0x46, 0x4f, 0x4f, 
-	0x54, 0x45, 0x52, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x5f, 0x43, 0x41, 0x4e, 0x56, 0x41, 0x53, 0x20, 0x6f, 
-	0x72, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x50, 0x49, 0x58, 0x45, 0x4c, 0x2e, 0x46, 0x4f, 0x4f, 0x54, 0x45, 
-	0x52, 0x2c, 0x0a,
+	0x09, 0x09, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x63, 0x61, 0x74, 0x28, 0x7b, 0x0a,
+	0x09, 0x09, 0x09, 0x09, 0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x2c, 0x0a,
+	0x09, 0x09, 0x09, 0x09, 0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x53, 0x59, 0x4e, 0x54, 0x41, 0x58, 0x2c, 0x20, 0x47, 
+	0x4c, 0x53, 0x4c, 0x5f, 0x50, 0x49, 0x58, 0x45, 0x4c, 0x2e, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x2c, 0x20, 
+	0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x55, 0x4e, 0x49, 0x46, 0x4f, 0x52, 0x4d, 0x53, 0x2c, 0x0a,
+	0x09, 0x09, 0x09, 0x7d, 0x2c, 0x20, 0x22, 0x5c, 0x6e, 0x22, 0x29, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x5c, 0x6e, 
+	0x22, 0x2c, 0x0a,
+	0x09, 0x09, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x63, 0x61, 0x74, 0x28, 0x7b, 0x0a,
+	0x09, 0x09, 0x09, 0x09, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x0a,
+	0x09, 0x09, 0x09, 0x09, 0x69, 0x73, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 
+	0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x50, 0x49, 0x58, 0x45, 0x4c, 0x2e, 0x46, 0x4f, 
+	0x4f, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x5f, 0x43, 0x41, 0x4e, 0x56, 0x41, 0x53, 0x20, 
+	0x6f, 0x72, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x50, 0x49, 0x58, 0x45, 0x4c, 0x2e, 0x46, 0x4f, 0x4f, 0x54, 
+	0x45, 0x52, 0x2c, 0x0a,
+	0x09, 0x09, 0x09, 0x7d, 0x2c, 0x20, 0x22, 0x5c, 0x6e, 0x22, 0x29, 0x2c, 0x0a,
 	0x09, 0x09, 0x7d, 0x0a,
 	0x09, 0x09, 0x7d, 0x0a,
-	0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 
-	0x63, 0x61, 0x74, 0x28, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x2c, 0x20, 0x22, 0x5c, 
-	0x6e, 0x22, 0x29, 0x0a,
+	0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 
+	0x73, 0x0a,
 	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, 0x69, 0x73, 
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 
 	0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x43, 0x6f, 0x64, 0x65, 0x28, 0x63, 0x6f, 0x64, 0x65, 0x29, 0x0a,
 	0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x43, 0x6f, 0x64, 0x65, 0x28, 0x63, 0x6f, 0x64, 0x65, 0x29, 0x0a,