Added instancing support to Meshes via Mesh:setInstanceCount. Added a new built-in variable to vertex shaders: int love_InstanceID.

love.graphics.draw(mesh) will draw the mesh instancecount times, using hardware instancing when available. The only way to draw individual instances differently from each other is to use the love_InstanceID variable in a vertex shader.

Added love.graphics.isSupported(“instancing”). Hardware instancing is supported if true, otherwise a (slower) pseudo-instancing fallback is used internally when drawing instanced meshes.
This commit is contained in:
Alex Szpakowski
2014-01-21 06:01:50 -04:00
parent 81d9810ed8
commit 73f1ce0d40
13 changed files with 217 additions and 13 deletions
+1
View File
@@ -175,6 +175,7 @@ StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM>::Entry Graphics::suppor
{ "mipmap", Graphics::SUPPORT_MIPMAP }, { "mipmap", Graphics::SUPPORT_MIPMAP },
{ "dxt", Graphics::SUPPORT_DXT }, { "dxt", Graphics::SUPPORT_DXT },
{ "bc5", Graphics::SUPPORT_BC5 }, { "bc5", Graphics::SUPPORT_BC5 },
{ "instancing", Graphics::SUPPORT_INSTANCING },
}; };
StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM> Graphics::support(Graphics::supportEntries, sizeof(Graphics::supportEntries)); StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM> Graphics::support(Graphics::supportEntries, sizeof(Graphics::supportEntries));
+1
View File
@@ -94,6 +94,7 @@ public:
SUPPORT_MIPMAP, SUPPORT_MIPMAP,
SUPPORT_DXT, SUPPORT_DXT,
SUPPORT_BC5, SUPPORT_BC5,
SUPPORT_INSTANCING,
SUPPORT_MAX_ENUM SUPPORT_MAX_ENUM
}; };
+28 -5
View File
@@ -35,6 +35,7 @@ Mesh::Mesh(const std::vector<Vertex> &verts, Mesh::DrawMode mode)
, vertex_count(0) , vertex_count(0)
, ibo(nullptr) , ibo(nullptr)
, element_count(0) , element_count(0)
, instance_count(1)
, draw_mode(mode) , draw_mode(mode)
, texture(nullptr) , texture(nullptr)
, colors_enabled(false) , colors_enabled(false)
@@ -51,8 +52,8 @@ Mesh::~Mesh()
void Mesh::setVertices(const std::vector<Vertex> &verts) void Mesh::setVertices(const std::vector<Vertex> &verts)
{ {
if (verts.size() < 3) if (verts.size() == 0)
throw love::Exception("At least 3 vertices are required."); throw love::Exception("At least one vertex is required.");
size_t size = sizeof(Vertex) * verts.size(); size_t size = sizeof(Vertex) * verts.size();
@@ -170,6 +171,19 @@ size_t Mesh::getVertexMapCount() const
return element_count; return element_count;
} }
void Mesh::setInstanceCount(int count)
{
if (count < 1)
count = 1;
instance_count = count;
}
int Mesh::getInstanceCount() const
{
return instance_count;
}
void Mesh::setTexture(Texture *tex) void Mesh::setTexture(Texture *tex)
{ {
tex->retain(); tex->retain();
@@ -270,18 +284,27 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo
if (ibo && element_count > 0) if (ibo && element_count > 0)
{ {
// Use the custom vertex map (index buffer) to draw the vertices.
VertexBuffer::Bind ibo_bind(*ibo); VertexBuffer::Bind ibo_bind(*ibo);
// Make sure the index buffer isn't mapped (sends data to GPU if needed.) // Make sure the index buffer isn't mapped (sends data to GPU if needed.)
ibo->unmap(); ibo->unmap();
// Use the custom vertex map to draw the vertices. const void *indices = ibo->getPointer(0);
glDrawElements(mode, element_count, GL_UNSIGNED_INT, ibo->getPointer(0)); const GLenum type = GL_UNSIGNED_INT;
if (instance_count > 1)
gl.drawElementsInstanced(mode, element_count, type, indices, instance_count);
else
glDrawElements(mode, element_count, type, indices);
} }
else else
{ {
// Normal non-indexed drawing (no custom vertex map.) // Normal non-indexed drawing (no custom vertex map.)
glDrawArrays(mode, 0, vertex_count); if (instance_count > 1)
gl.drawArraysInstanced(mode, 0, vertex_count, instance_count);
else
glDrawArrays(mode, 0, vertex_count);
} }
if (wireframe) if (wireframe)
+11
View File
@@ -109,6 +109,15 @@ public:
**/ **/
size_t getVertexMapCount() const; size_t getVertexMapCount() const;
/**
* Sets the number of instances of this Mesh to draw (uses hardware
* instancing when possible.)
* A custom vertex shader is necessary in order to introduce differences
* in each instance.
**/
void setInstanceCount(int count);
int getInstanceCount() const;
/** /**
* Sets the texture used when drawing the Mesh. * Sets the texture used when drawing the Mesh.
**/ **/
@@ -165,6 +174,8 @@ private:
VertexBuffer *ibo; VertexBuffer *ibo;
size_t element_count; size_t element_count;
int instance_count;
DrawMode draw_mode; DrawMode draw_mode;
Texture *texture; Texture *texture;
+65 -4
View File
@@ -113,6 +113,8 @@ void OpenGL::initContext()
initMaxValues(); initMaxValues();
createDefaultTexture(); createDefaultTexture();
state.lastPseudoInstanceID = -1;
contextInitialized = true; contextInitialized = true;
} }
@@ -214,10 +216,69 @@ void OpenGL::createDefaultTexture()
void OpenGL::prepareDraw() void OpenGL::prepareDraw()
{ {
// Make sure the active shader has the correct values for the built-in Shader *shader = Shader::current;
// screen params uniform. if (shader != nullptr)
if (Shader::current) {
Shader::current->checkSetScreenParams(); // Make sure the active shader has the correct values for its
// love-provided uniforms.
shader->checkSetScreenParams();
// Make sure the Instance ID variable is up-to-date when
// pseudo-instancing is used.
if (state.lastPseudoInstanceID != 0 && shader->hasVertexAttrib(ATTRIB_PSEUDO_INSTANCE_ID))
{
glVertexAttrib1f((GLuint) ATTRIB_PSEUDO_INSTANCE_ID, 0.0f);
state.lastPseudoInstanceID = 0;
}
}
}
void OpenGL::drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount)
{
Shader *shader = Shader::current;
if (GLEE_ARB_draw_instanced)
glDrawArraysInstancedARB(mode, first, count, primcount);
else
{
bool shaderHasID = shader && shader->hasVertexAttrib(ATTRIB_PSEUDO_INSTANCE_ID);
// Pseudo-instancing fallback.
for (int i = 0; i < primcount; i++)
{
if (shaderHasID)
glVertexAttrib1f((GLuint) ATTRIB_PSEUDO_INSTANCE_ID, (GLfloat) i);
glDrawArrays(mode, first, count);
}
if (shaderHasID)
state.lastPseudoInstanceID = primcount - 1;
}
}
void OpenGL::drawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount)
{
Shader *shader = Shader::current;
if (GLEE_ARB_draw_instanced)
glDrawElementsInstancedARB(mode, count, type, indices, primcount);
else
{
bool shaderHasID = shader && shader->hasVertexAttrib(ATTRIB_PSEUDO_INSTANCE_ID);
// Pseudo-instancing fallback.
for (int i = 0; i < primcount; i++)
{
if (shaderHasID)
glVertexAttrib1f((GLuint) ATTRIB_PSEUDO_INSTANCE_ID, (GLfloat) i);
glDrawElements(mode, count, type, indices);
}
if (shaderHasID)
state.lastPseudoInstanceID = primcount - 1;
}
} }
void OpenGL::setColor(const Color &c) void OpenGL::setColor(const Color &c)
+24
View File
@@ -63,6 +63,17 @@ public:
VENDOR_UNKNOWN VENDOR_UNKNOWN
}; };
// Vertex attributes used in shaders by LOVE. The values map to OpenGL
// generic vertex attribute indices, when applicable.
// LOVE uses the old hard-coded attribute APIs for positions, colors, etc.
// (for now.)
enum VertexAttrib
{
// Instance ID when pseudo-instancing is used.
ATTRIB_PSEUDO_INSTANCE_ID = 1,
ATTRIB_MAX_ENUM
};
// A rectangle representing an OpenGL viewport or a scissor box. // A rectangle representing an OpenGL viewport or a scissor box.
struct Viewport struct Viewport
{ {
@@ -99,6 +110,16 @@ public:
**/ **/
void prepareDraw(); void prepareDraw();
/**
* glDrawArraysInstanced with a pseudo-instancing fallback.
**/
void drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount);
/**
* glDrawElementsInstanced with a pseudo-instancing fallback.
**/
void drawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount);
/** /**
* Sets the current constant color. * Sets the current constant color.
**/ **/
@@ -230,6 +251,9 @@ private:
Viewport viewport; Viewport viewport;
Viewport scissor; Viewport scissor;
// The last ID value used for pseudo-instancing.
int lastPseudoInstanceID;
} state; } state;
}; // OpenGL }; // OpenGL
+30
View File
@@ -70,6 +70,7 @@ Shader::Shader(const ShaderSources &sources)
: shaderSources(sources) : shaderSources(sources)
, program(0) , program(0)
, builtinUniforms() , builtinUniforms()
, vertexAttributes()
, lastCanvas((Canvas *) -1) , lastCanvas((Canvas *) -1)
{ {
if (shaderSources.empty()) if (shaderSources.empty())
@@ -181,6 +182,14 @@ void Shader::createProgram(const std::vector<GLuint> &shaderids)
for (it = shaderids.begin(); it != shaderids.end(); ++it) for (it = shaderids.begin(); it != shaderids.end(); ++it)
glAttachShader(program, *it); glAttachShader(program, *it);
// Bind generic vertex attribute indices to names in the shader.
for (int i = 0; i < int(OpenGL::ATTRIB_MAX_ENUM); i++)
{
const char *name = nullptr;
if (attribNames.find((OpenGL::VertexAttrib) i, name))
glBindAttribLocation(program, i, (const GLchar *) name);
}
glLinkProgram(program); glLinkProgram(program);
// flag shaders for auto-deletion when the program object is deleted. // flag shaders for auto-deletion when the program object is deleted.
@@ -273,6 +282,15 @@ bool Shader::loadVolatile()
// Retreive all active uniform variables in this shader from OpenGL. // Retreive all active uniform variables in this shader from OpenGL.
mapActiveUniforms(); mapActiveUniforms();
for (int i = 0; i < int(OpenGL::ATTRIB_MAX_ENUM); i++)
{
const char *name = nullptr;
if (attribNames.find(OpenGL::VertexAttrib(i), name))
vertexAttributes[i] = glGetAttribLocation(program, name);
else
vertexAttributes[i] = -1;
}
if (current == this) if (current == this)
{ {
// make sure glUseProgram gets called. // make sure glUseProgram gets called.
@@ -633,6 +651,11 @@ int Shader::getTextureUnit(const std::string &name)
return texunit; return texunit;
} }
bool Shader::hasVertexAttrib(OpenGL::VertexAttrib attrib) const
{
return vertexAttributes[int(attrib)] != -1;
}
bool Shader::hasBuiltinExtern(BuiltinExtern builtin) const bool Shader::hasBuiltinExtern(BuiltinExtern builtin) const
{ {
return builtinUniforms[int(builtin)] != -1; return builtinUniforms[int(builtin)] != -1;
@@ -730,6 +753,13 @@ StringMap<Shader::ShaderType, Shader::TYPE_MAX_ENUM>::Entry Shader::typeNameEntr
StringMap<Shader::ShaderType, Shader::TYPE_MAX_ENUM> Shader::typeNames(Shader::typeNameEntries, sizeof(Shader::typeNameEntries)); StringMap<Shader::ShaderType, Shader::TYPE_MAX_ENUM> Shader::typeNames(Shader::typeNameEntries, sizeof(Shader::typeNameEntries));
StringMap<OpenGL::VertexAttrib, OpenGL::ATTRIB_MAX_ENUM>::Entry Shader::attribNameEntries[] =
{
{"love_PseudoInstanceID", OpenGL::ATTRIB_PSEUDO_INSTANCE_ID},
};
StringMap<OpenGL::VertexAttrib, OpenGL::ATTRIB_MAX_ENUM> Shader::attribNames(Shader::attribNameEntries, sizeof(Shader::attribNameEntries));
StringMap<Shader::BuiltinExtern, Shader::BUILTIN_MAX_ENUM>::Entry Shader::builtinNameEntries[] = StringMap<Shader::BuiltinExtern, Shader::BUILTIN_MAX_ENUM>::Entry Shader::builtinNameEntries[] =
{ {
{"love_ScreenParams", Shader::BUILTIN_SCREEN_PARAMS}, {"love_ScreenParams", Shader::BUILTIN_SCREEN_PARAMS},
+8
View File
@@ -138,6 +138,7 @@ public:
/** /**
* Internal use only. * Internal use only.
**/ **/
bool hasVertexAttrib(OpenGL::VertexAttrib attrib) const;
bool hasBuiltinExtern(BuiltinExtern builtin) const; bool hasBuiltinExtern(BuiltinExtern builtin) const;
bool sendBuiltinFloat(BuiltinExtern builtin, int size, const GLfloat *m, int count); bool sendBuiltinFloat(BuiltinExtern builtin, int size, const GLfloat *m, int count);
void checkSetScreenParams(); void checkSetScreenParams();
@@ -198,6 +199,9 @@ private:
// Location values for any built-in uniform variables. // Location values for any built-in uniform variables.
GLint builtinUniforms[BUILTIN_MAX_ENUM]; GLint builtinUniforms[BUILTIN_MAX_ENUM];
// Location values for any generic vertex attribute variables.
GLint vertexAttributes[OpenGL::ATTRIB_MAX_ENUM];
// Uniform location buffer map // Uniform location buffer map
std::map<std::string, Uniform> uniforms; std::map<std::string, Uniform> uniforms;
@@ -220,6 +224,10 @@ private:
static StringMap<ShaderType, TYPE_MAX_ENUM>::Entry typeNameEntries[]; static StringMap<ShaderType, TYPE_MAX_ENUM>::Entry typeNameEntries[];
static StringMap<ShaderType, TYPE_MAX_ENUM> typeNames; static StringMap<ShaderType, TYPE_MAX_ENUM> typeNames;
// Names for the generic vertex attributes used by love.
static StringMap<OpenGL::VertexAttrib, OpenGL::ATTRIB_MAX_ENUM>::Entry attribNameEntries[];
static StringMap<OpenGL::VertexAttrib, OpenGL::ATTRIB_MAX_ENUM> attribNames;
// Names for the built-in uniform variables. // Names for the built-in uniform variables.
static StringMap<BuiltinExtern, BUILTIN_MAX_ENUM>::Entry builtinNameEntries[]; static StringMap<BuiltinExtern, BUILTIN_MAX_ENUM>::Entry builtinNameEntries[];
static StringMap<BuiltinExtern, BUILTIN_MAX_ENUM> builtinNames; static StringMap<BuiltinExtern, BUILTIN_MAX_ENUM> builtinNames;
@@ -984,6 +984,10 @@ int w_isSupported(lua_State *L)
if (!Image::hasCompressedTextureSupport(image::CompressedData::FORMAT_BC5)) if (!Image::hasCompressedTextureSupport(image::CompressedData::FORMAT_BC5))
supported = false; supported = false;
break; break;
case Graphics::SUPPORT_INSTANCING:
if (!GLEE_ARB_draw_instanced)
supported = false;
break;
default: default:
supported = false; supported = false;
} }
+16
View File
@@ -236,6 +236,20 @@ int w_Mesh_getVertexMap(lua_State *L)
return 1; return 1;
} }
int w_Mesh_setInstanceCount(lua_State *L)
{
Mesh *t = luax_checkmesh(L, 1);
t->setInstanceCount(luaL_checkint(L, 2));
return 0;
}
int w_Mesh_getInstanceCount(lua_State *L)
{
Mesh *t = luax_checkmesh(L, 1);
lua_pushinteger(L, t->getInstanceCount());
return 1;
}
int w_Mesh_setTexture(lua_State *L) int w_Mesh_setTexture(lua_State *L)
{ {
Mesh *t = luax_checkmesh(L, 1); Mesh *t = luax_checkmesh(L, 1);
@@ -338,6 +352,8 @@ static const luaL_Reg functions[] =
{ "getVertexCount", w_Mesh_getVertexCount }, { "getVertexCount", w_Mesh_getVertexCount },
{ "setVertexMap", w_Mesh_setVertexMap }, { "setVertexMap", w_Mesh_setVertexMap },
{ "getVertexMap", w_Mesh_getVertexMap }, { "getVertexMap", w_Mesh_getVertexMap },
{ "setInstanceCount", w_Mesh_setInstanceCount },
{ "getInstanceCount", w_Mesh_getInstanceCount },
{ "setTexture", w_Mesh_setTexture }, { "setTexture", w_Mesh_setTexture },
{ "getTexture", w_Mesh_getTexture }, { "getTexture", w_Mesh_getTexture },
{ "setDrawMode", w_Mesh_setDrawMode }, { "setDrawMode", w_Mesh_setDrawMode },
+2
View File
@@ -41,6 +41,8 @@ int w_Mesh_getVertices(lua_State *L);
int w_Mesh_getVertexCount(lua_State *L); int w_Mesh_getVertexCount(lua_State *L);
int w_Mesh_setVertexMap(lua_State *L); int w_Mesh_setVertexMap(lua_State *L);
int w_Mesh_getVertexMap(lua_State *L); int w_Mesh_getVertexMap(lua_State *L);
int w_Mesh_setInstanceCount(lua_State *L);
int w_Mesh_getInstanceCount(lua_State *L);
int w_Mesh_setTexture(lua_State *L); int w_Mesh_setTexture(lua_State *L);
int w_Mesh_getTexture(lua_State *L); int w_Mesh_getTexture(lua_State *L);
int w_Mesh_setDrawMode(lua_State *L); int w_Mesh_setDrawMode(lua_State *L);
+9 -1
View File
@@ -1315,7 +1315,15 @@ uniform vec2 love_ScreenParams;]]
#define VertexColor gl_Color #define VertexColor gl_Color
#define VaryingTexCoord gl_TexCoord[0] #define VaryingTexCoord gl_TexCoord[0]
#define VaryingColor gl_FrontColor]], #define VaryingColor gl_FrontColor
#if defined(GL_ARB_draw_instanced)
#extension GL_ARB_draw_instanced : enable
#define love_InstanceID gl_InstanceIDARB
#else
attribute float love_PseudoInstanceID;
int love_InstanceID = int(love_PseudoInstanceID);
#endif]],
FOOTER = [[ FOOTER = [[
void main() { void main() {
+18 -3
View File
@@ -27,7 +27,7 @@ const unsigned char graphics_lua[] =
0x2d, 0x2d, 0x5b, 0x5b, 0x0a, 0x2d, 0x2d, 0x5b, 0x5b, 0x0a,
0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36,
0x2d, 0x32, 0x30, 0x31, 0x33, 0x20, 0x4c, 0x4f, 0x56, 0x45, 0x20, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x2d, 0x32, 0x30, 0x31, 0x34, 0x20, 0x4c, 0x4f, 0x56, 0x45, 0x20, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70,
0x6d, 0x65, 0x6e, 0x74, 0x20, 0x54, 0x65, 0x61, 0x6d, 0x0a, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x54, 0x65, 0x61, 0x6d, 0x0a,
0x54, 0x68, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 0x54, 0x68, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x27, 0x61, 0x73, 0x2d, 0x69, 0x73, 0x27, 0x2c, 0x20, 0x77, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x27, 0x61, 0x73, 0x2d, 0x69, 0x73, 0x27, 0x2c, 0x20, 0x77,
@@ -6312,8 +6312,23 @@ const unsigned char graphics_lua[] =
0x43, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x67, 0x6c, 0x5f, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x5b, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x67, 0x6c, 0x5f, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x5b,
0x30, 0x5d, 0x0a, 0x30, 0x5d, 0x0a,
0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6c, 0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6c,
0x6f, 0x72, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x5d, 0x5d, 0x6f, 0x72, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x0a,
0x2c, 0x0a, 0x23, 0x69, 0x66, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x28, 0x47, 0x4c, 0x5f, 0x41, 0x52, 0x42,
0x5f, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x64, 0x29, 0x0a,
0x09, 0x23, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x47, 0x4c, 0x5f, 0x41, 0x52, 0x42,
0x5f, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x64, 0x20, 0x3a, 0x20,
0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x0a,
0x09, 0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x49, 0x6e, 0x73, 0x74,
0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x20, 0x67, 0x6c, 0x5f, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65,
0x49, 0x44, 0x41, 0x52, 0x42, 0x0a,
0x23, 0x65, 0x6c, 0x73, 0x65, 0x0a,
0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6c,
0x6f, 0x76, 0x65, 0x5f, 0x50, 0x73, 0x65, 0x75, 0x64, 0x6f, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65,
0x49, 0x44, 0x3b, 0x0a,
0x09, 0x69, 0x6e, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65,
0x49, 0x44, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x50, 0x73, 0x65, 0x75,
0x64, 0x6f, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x29, 0x3b, 0x0a,
0x23, 0x65, 0x6e, 0x64, 0x69, 0x66, 0x5d, 0x5d, 0x2c, 0x0a,
0x09, 0x09, 0x46, 0x4f, 0x4f, 0x54, 0x45, 0x52, 0x20, 0x3d, 0x20, 0x5b, 0x5b, 0x0a, 0x09, 0x09, 0x46, 0x4f, 0x4f, 0x54, 0x45, 0x52, 0x20, 0x3d, 0x20, 0x5b, 0x5b, 0x0a,
0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a,
0x09, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x3d, 0x09, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x3d,