diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 68b71f379..4dd15669b 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/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 } -Shader *Graphics::newShader(const Shader::ShaderSources &sources) +Shader *Graphics::newShader(const std::vector &vertcode, const std::vector &pixelcode) { - return new Shader(sources); + return new Shader(vertcode, pixelcode); } Mesh *Graphics::newMesh(const std::vector &vertices, Mesh::DrawMode mode) diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index 7ab7560cd..7514dd7f4 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/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); - Shader *newShader(const Shader::ShaderSources &sources); + Shader *newShader(const std::vector &vertcode, const std::vector &pixelcode); Mesh *newMesh(const std::vector &vertices, Mesh::DrawMode mode = Mesh::DRAW_MODE_FAN); Mesh *newMesh(int vertexcount, Mesh::DrawMode mode = Mesh::DRAW_MODE_FAN); diff --git a/src/modules/graphics/opengl/Shader.cpp b/src/modules/graphics/opengl/Shader.cpp index b95a0d10b..f2beb3609 100644 --- a/src/modules/graphics/opengl/Shader.cpp +++ b/src/modules/graphics/opengl/Shader.cpp @@ -66,21 +66,25 @@ Shader *Shader::current = nullptr; GLint Shader::maxTexUnits = 0; std::vector Shader::textureCounters; -Shader::Shader(const ShaderSources &sources) - : shaderSources(sources) - , program(0) +Shader::Shader(const std::vector &vertcode, const std::vector &pixelcode) + : program(0) , builtinUniforms() , vertexAttributes() , lastCanvas((Canvas *) -1) , lastViewport() { - if (shaderSources.empty()) + if (vertcode.empty() && pixelcode.empty()) throw love::Exception("Cannot create shader: no source code!"); + shaderSources[TYPE_VERTEX] = vertcode; + shaderSources[TYPE_PIXEL] = pixelcode; + if (maxTexUnits <= 0) { GLint maxtexunits; glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxtexunits); + + // TU 0 is never used for stored Shader images. maxTexUnits = std::max(maxtexunits - 1, 0); } @@ -106,7 +110,7 @@ Shader::~Shader() unloadVolatile(); } -GLuint Shader::compileCode(ShaderType type, const std::string &code) +GLuint Shader::compileCode(ShaderType type, const std::vector &code) { GLenum glshadertype; 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); } - const char *src = code.c_str(); - size_t srclen = code.length(); - glShaderSource(shaderid, 1, (const GLchar **)&src, (GLint *)&srclen); + std::vector codelist; + std::vector 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); @@ -276,10 +289,12 @@ bool Shader::loadVolatile() std::vector 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); } @@ -288,7 +303,7 @@ bool Shader::loadVolatile() createProgram(shaderids); - // Retreive all active uniform variables in this shader from OpenGL. + // Get all active uniform variables in this shader from OpenGL. mapActiveUniforms(); for (int i = 0; i < int(OpenGL::ATTRIB_MAX_ENUM); i++) diff --git a/src/modules/graphics/opengl/Shader.h b/src/modules/graphics/opengl/Shader.h index 38c70a137..25e740543 100644 --- a/src/modules/graphics/opengl/Shader.h +++ b/src/modules/graphics/opengl/Shader.h @@ -74,14 +74,11 @@ public: UNIFORM_MAX_ENUM }; - // Type for a list of shader source codes in the form of sources[shadertype] = code - typedef std::map ShaderSources; - /** * 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 &vertcode, const std::vector &pixelcode); virtual ~Shader(); @@ -196,7 +193,7 @@ private: 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); + GLuint compileCode(ShaderType type, const std::vector &code); void createProgram(const std::vector &shaderids); int getTextureUnit(const std::string &name); @@ -206,8 +203,9 @@ private: // Get any warnings or errors generated only by the shader program object. 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 shaderSources[TYPE_MAX_ENUM]; // Shader compiler warning strings for individual shader stages. std::map shaderWarnings; diff --git a/src/modules/graphics/opengl/wrap_Graphics.cpp b/src/modules/graphics/opengl/wrap_Graphics.cpp index 243c9e908..d8a8cbae6 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.cpp +++ b/src/modules/graphics/opengl/wrap_Graphics.cpp @@ -353,16 +353,16 @@ int w_newShader(lua_State *L) if (!Shader::isSupported()) 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); - // read any filepath arguments + // Read any filepath arguments. for (int i = 1; i <= 2; i++) { if (!lua_isstring(L, i)) continue; - // call love.filesystem.isFile(arg_i) + // Call love.filesystem.isFile(arg_i) luax_getfunction(L, "filesystem", "isFile"); lua_pushvalue(L, i); lua_call(L, 1, 1); @@ -382,43 +382,76 @@ int w_newShader(lua_State *L) bool has_arg1 = lua_isstring(L, 1); bool has_arg2 = lua_isstring(L, 2); - // require at least one string argument + // Require at least one string argument. if (!(has_arg1 || has_arg2)) luaL_checkstring(L, 1); 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, 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) return luaL_error(L, "%s", lua_tostring(L, -1)); - Shader::ShaderSources sources; + // Each shader type might contain several source code strings. + std::vector 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 &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) 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 &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) 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++) { if (lua_isstring(L, i)) @@ -429,7 +462,7 @@ int w_newShader(lua_State *L) bool should_error = false; 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); } catch (love::Exception &e) diff --git a/src/scripts/graphics.lua b/src/scripts/graphics.lua index 0bb209420..6478e4d1f 100644 --- a/src/scripts/graphics.lua +++ b/src/scripts/graphics.lua @@ -1365,25 +1365,35 @@ void main() { } 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 = { - 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 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 = { - 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 local function isVertexCode(code) diff --git a/src/scripts/graphics.lua.h b/src/scripts/graphics.lua.h index 22912839e..868b739b1 100644 --- a/src/scripts/graphics.lua.h +++ b/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, 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, + 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, 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, 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, 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, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x73, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x61, 0x6e, 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, 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, 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, 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,