Browse Source

Fix conflicts with default

--HG--
branch : sounddata-samplecount
darthfodder 12 years ago
parent
commit
f55a282f34
39 changed files with 24049 additions and 21318 deletions
  1. 8 0
      changes.txt
  2. 0 2
      src/common/types.h
  3. 1 1
      src/modules/event/Event.cpp
  4. 1 1
      src/modules/event/Event.h
  5. 1 1
      src/modules/filesystem/physfs/File.cpp
  6. 1 1
      src/modules/filesystem/physfs/File.h
  7. 3 14
      src/modules/filesystem/physfs/Filesystem.cpp
  8. 1 0
      src/modules/graphics/Graphics.cpp
  9. 1 0
      src/modules/graphics/Graphics.h
  10. 185 9
      src/modules/graphics/opengl/Canvas.cpp
  11. 19 3
      src/modules/graphics/opengl/Canvas.h
  12. 25 25
      src/modules/graphics/opengl/Font.cpp
  13. 10 6
      src/modules/graphics/opengl/Font.h
  14. 22312 20148
      src/modules/graphics/opengl/GLee.c
  15. 947 881
      src/modules/graphics/opengl/GLee.h
  16. 32 14
      src/modules/graphics/opengl/Graphics.cpp
  17. 16 0
      src/modules/graphics/opengl/Graphics.h
  18. 14 0
      src/modules/graphics/opengl/ParticleSystem.cpp
  19. 6 0
      src/modules/graphics/opengl/ParticleSystem.h
  20. 59 22
      src/modules/graphics/opengl/Shader.cpp
  21. 16 3
      src/modules/graphics/opengl/Shader.h
  22. 16 13
      src/modules/graphics/opengl/wrap_Canvas.cpp
  23. 124 40
      src/modules/graphics/opengl/wrap_Graphics.cpp
  24. 3 0
      src/modules/graphics/opengl/wrap_Graphics.h
  25. 11 6
      src/modules/graphics/opengl/wrap_ParticleSystem.cpp
  26. 1 0
      src/modules/graphics/opengl/wrap_ParticleSystem.h
  27. 8 11
      src/modules/graphics/opengl/wrap_SpriteBatch.cpp
  28. 11 11
      src/modules/image/devil/ImageData.cpp
  29. 3 3
      src/modules/image/wrap_ImageData.cpp
  30. 2 2
      src/modules/sound/SoundData.cpp
  31. 1 1
      src/modules/sound/SoundData.h
  32. 1 1
      src/modules/sound/wrap_SoundData.cpp
  33. 4 0
      src/modules/thread/wrap_Channel.cpp
  34. 2 0
      src/modules/timer/sdl/Timer.cpp
  35. 2 2
      src/modules/window/Window.h
  36. 5 6
      src/modules/window/sdl/Window.cpp
  37. 2 2
      src/modules/window/sdl/Window.h
  38. 63 25
      src/scripts/graphics.lua
  39. 132 64
      src/scripts/graphics.lua.h

+ 8 - 0
changes.txt

@@ -20,6 +20,7 @@ LOVE 0.9.0 []
   * Added support for UTF-8 ImageFonts.
   * Added SoundData:getDuration.
   * Added new Channels api for love.thread.
+  * Added Thread:getError.
   * Added flags to setMode.
   * Added support for resizable, borderless, and non-centered windows.
   * Added resize event.
@@ -35,6 +36,11 @@ LOVE 0.9.0 []
   * Added love.graphics.getDimensions and Image/Canvas/ImageData:getDimensions.
   * Added love.filesystem.append.
   * Added love.filesystem.getSize.
+  * Added angle, scale, and shear parameters to love.graphics.printf.
+  * Added anisotropic filtering support for Images, Canvases, and Fonts.
+  * Added love.graphics.setCanvases.
+  * Added ParticleSystem:emit.
+  * Added love.graphics.setColorMask.
   * OPTIONAL: Added support for GME.
 
   * Fixed crashes with font drawing on some ATI cards.
@@ -65,6 +71,7 @@ LOVE 0.9.0 []
 
   * Renamed love's boot script to 'love.boot', which can be required.
   * Renamed PixelEffect to Shader (but now with vertex shaders).
+  * Renamed love.graphics.setDefaultImageFilter to love.graphics.setDefaultFilter.
 
   * Removed love.joystick.open and friends.
   * Removed love.graphics.drawTest.
@@ -86,6 +93,7 @@ LOVE 0.9.0 []
   * Updated Canvas code to support more systems.
   * Updated love.timer.getFPS to be microsecond-accurate.
   * Updated love.graphics.newScreenshot to create a fully opaque image by default.
+  * Updated ImageData:setPixel's alpha parameter to default to 255.
 
 LOVE 0.8.0 [Rubber Piggy]
 -------------------------

+ 0 - 2
src/common/types.h

@@ -56,7 +56,6 @@ enum Type
 
 	// Image
 	IMAGE_IMAGE_DATA_ID,
-	IMAGE_ENCODED_IMAGE_DATA_ID,
 
 	// Audio
 	AUDIO_SOURCE_ID,
@@ -128,7 +127,6 @@ const bits GRAPHICS_SHADER_T = (bits(1) << GRAPHICS_SHADER_ID) | OBJECT_T;
 
 // Image.
 const bits IMAGE_IMAGE_DATA_T = (bits(1) << IMAGE_IMAGE_DATA_ID) | DATA_T;
-const bits IMAGE_ENCODED_IMAGE_DATA_T = (bits(1) << IMAGE_ENCODED_IMAGE_DATA_ID) | DATA_T;
 
 // Audio.
 const bits AUDIO_SOURCE_T = (bits(1) << AUDIO_SOURCE_ID) | OBJECT_T;

+ 1 - 1
src/modules/event/Event.cpp

@@ -28,7 +28,7 @@ namespace love
 namespace event
 {
 
-Message::Message(std::string name, Variant *a, Variant *b, Variant *c, Variant *d)
+Message::Message(const std::string &name, Variant *a, Variant *b, Variant *c, Variant *d)
 	: name(name)
 	, nargs(0)
 {

+ 1 - 1
src/modules/event/Event.h

@@ -45,7 +45,7 @@ private:
 	int nargs;
 
 public:
-	Message(std::string name, Variant *a = NULL, Variant *b = NULL, Variant *c = NULL, Variant *d = NULL);
+	Message(const std::string &name, Variant *a = NULL, Variant *b = NULL, Variant *c = NULL, Variant *d = NULL);
 	~Message();
 
 	int toLua(lua_State *L);

+ 1 - 1
src/modules/filesystem/physfs/File.cpp

@@ -36,7 +36,7 @@ namespace physfs
 
 extern bool hack_setupWriteDirectory();
 
-File::File(std::string filename)
+File::File(const std::string &filename)
 	: filename(filename)
 	, file(0)
 	, mode(filesystem::File::CLOSED)

+ 1 - 1
src/modules/filesystem/physfs/File.h

@@ -61,7 +61,7 @@ public:
 	 * @param source The source from which to load the file. (Archive or directory)
 	 * @param filename The relative filepath of the file to load from the source.
 	 **/
-	File(std::string filename);
+	File(const std::string &filename);
 
 	virtual ~File();
 

+ 3 - 14
src/modules/filesystem/physfs/Filesystem.cpp

@@ -343,28 +343,17 @@ void Filesystem::append(const char *filename, const void *data, int64 size) cons
 
 int Filesystem::enumerate(lua_State *L)
 {
-	int n = lua_gettop(L);
+	const char *dir = luaL_checkstring(L, 1);
 
-	if (n != 1)
-		return luaL_error(L, "Function requires a single parameter.");
-
-	int type = lua_type(L, 1);
-
-	if (type != LUA_TSTRING)
-		return luaL_error(L, "Function requires parameter of type string.");
-
-	const char *dir = lua_tostring(L, 1);
 	char **rc = PHYSFS_enumerateFiles(dir);
-	char **i;
 	int index = 1;
 
 	lua_newtable(L);
 
-	for (i = rc; *i != 0; i++)
+	for (char **i = rc; *i != 0; i++)
 	{
-		lua_pushinteger(L, index);
 		lua_pushstring(L, *i);
-		lua_settable(L, -3);
+		lua_rawseti(L, -2, index);
 		index++;
 	}
 

+ 1 - 0
src/modules/graphics/Graphics.cpp

@@ -158,6 +158,7 @@ StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM>::Entry Graphics::suppor
 {
 	{ "canvas", Graphics::SUPPORT_CANVAS },
 	{ "hdrcanvas", Graphics::SUPPORT_HDR_CANVAS },
+	{ "multicanvas", Graphics::SUPPORT_MULTI_CANVAS },
 	{ "shader", Graphics::SUPPORT_SHADER },
 	{ "npot", Graphics::SUPPORT_NPOT },
 	{ "subtractive", Graphics::SUPPORT_SUBTRACTIVE },

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

@@ -87,6 +87,7 @@ public:
 	{
 		SUPPORT_CANVAS = 1,
 		SUPPORT_HDR_CANVAS,
+		SUPPORT_MULTI_CANVAS,
 		SUPPORT_SHADER,
 		SUPPORT_NPOT,
 		SUPPORT_SUBTRACTIVE,

+ 185 - 9
src/modules/graphics/opengl/Canvas.cpp

@@ -59,6 +59,15 @@ struct FramebufferStrategy
 	 */
 	virtual void deleteFBO(GLuint, GLuint, GLuint) {}
 	virtual void bindFBO(GLuint) {}
+
+	/// attach additional canvases to the active framebuffer for rendering
+	/**
+	 * @param[in] canvases List of canvases to attach
+	 **/
+	virtual void setAttachments(const std::vector<Canvas *> &canvases) {}
+
+	/// stop using all additional attached canvases
+	virtual void setAttachments() {}
 };
 
 struct FramebufferStrategyGL3 : public FramebufferStrategy
@@ -127,6 +136,40 @@ struct FramebufferStrategyGL3 : public FramebufferStrategy
 	{
 		glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
 	}
+
+	virtual void setAttachments()
+	{
+		// set a single render target
+		glDrawBuffer(GL_COLOR_ATTACHMENT0);
+	}
+
+	virtual void setAttachments(const std::vector<Canvas *> &canvases)
+	{
+		if (canvases.size() == 0)
+		{
+			setAttachments();
+			return;
+		}
+
+		std::vector<GLenum> drawbuffers;
+		drawbuffers.push_back(GL_COLOR_ATTACHMENT0);
+
+		// attach the canvas framebuffer textures to the currently bound framebuffer
+		for (int i = 0; i < canvases.size(); i++)
+		{
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1 + i,
+				GL_TEXTURE_2D, canvases[i]->getTextureName(), 0);
+			drawbuffers.push_back(GL_COLOR_ATTACHMENT1 + i);
+		}
+
+		// set up multiple render targets
+		if (GLEE_VERSION_2_0)
+			glDrawBuffers(drawbuffers.size(), &drawbuffers[0]);
+		else if (GLEE_ARB_draw_buffers)
+			glDrawBuffersARB(drawbuffers.size(), &drawbuffers[0]);
+		else if (GLEE_ATI_draw_buffers)
+			glDrawBuffersATI(drawbuffers.size(), &drawbuffers[0]);
+	}
 };
 
 struct FramebufferStrategyPackedEXT : public FramebufferStrategy
@@ -196,6 +239,40 @@ struct FramebufferStrategyPackedEXT : public FramebufferStrategy
 	{
 		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer);
 	}
+
+	virtual void setAttachments()
+	{
+		// set a single render target
+		glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
+	}
+
+	virtual void setAttachments(const std::vector<Canvas *> &canvases)
+	{
+		if (canvases.size() == 0)
+		{
+			setAttachments();
+			return;
+		}
+
+		std::vector<GLenum> drawbuffers;
+		drawbuffers.push_back(GL_COLOR_ATTACHMENT0_EXT);
+
+		// attach the canvas framebuffer textures to the currently bound framebuffer
+		for (int i = 0; i < canvases.size(); i++)
+		{
+			glFramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1_EXT + i,
+								   GL_TEXTURE_2D, canvases[i]->getTextureName(), 0);
+			drawbuffers.push_back(GL_COLOR_ATTACHMENT1_EXT + i);
+		}
+
+		// set up multiple render targets
+		if (GLEE_VERSION_2_0)
+			glDrawBuffers(drawbuffers.size(), &drawbuffers[0]);
+		else if (GLEE_ARB_draw_buffers)
+			glDrawBuffersARB(drawbuffers.size(), &drawbuffers[0]);
+		else if (GLEE_ATI_draw_buffers)
+			glDrawBuffersATI(drawbuffers.size(), &drawbuffers[0]);
+	}
 };
 
 struct FramebufferStrategyEXT : public FramebufferStrategyPackedEXT
@@ -288,6 +365,9 @@ static void getStrategy()
 	}
 }
 
+static int maxFBOColorAttachments = 0;
+static int maxDrawBuffers = 0;
+
 Canvas::Canvas(int width, int height, TextureType texture_type)
 	: width(width)
 	, height(height)
@@ -338,18 +418,33 @@ bool Canvas::isSupported()
 	return (strategy != &strategyNone);
 }
 
-bool Canvas::isHdrSupported()
+bool Canvas::isHDRSupported()
 {
 	return GLEE_VERSION_3_0 || GLEE_ARB_texture_float;
 }
 
+bool Canvas::isMultiCanvasSupported()
+{
+	if (!(isSupported() && (GLEE_VERSION_2_0 || GLEE_ARB_draw_buffers || GLEE_ATI_draw_buffers)))
+		return false;
+
+	if (maxFBOColorAttachments == 0 || maxDrawBuffers == 0)
+	{
+		glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxFBOColorAttachments);
+		glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
+	}
+
+	// system must support at least 4 simultanious active canvases
+	return maxFBOColorAttachments >= 4 && maxDrawBuffers >= 4;
+}
+
 void Canvas::bindDefaultCanvas()
 {
 	if (current != NULL)
 		current->stopGrab();
 }
 
-void Canvas::startGrab()
+void Canvas::setupGrab()
 {
 	// already grabbing
 	if (current == this)
@@ -379,6 +474,63 @@ void Canvas::startGrab()
 	current = this;
 }
 
+void Canvas::startGrab(const std::vector<Canvas *> &canvases)
+{
+	if (canvases.size() > 0)
+	{
+		if (!isMultiCanvasSupported())
+			throw love::Exception("Multi-canvas rendering is not supported on this system.");
+
+		if (canvases.size()+1 > maxDrawBuffers || canvases.size()+1 > maxFBOColorAttachments)
+			throw love::Exception("This system can't simultaniously render to %d canvases.", canvases.size()+1);
+	}
+
+	for (size_t i = 0; i < canvases.size(); i++)
+	{
+		if (canvases[i]->getWidth() != width || canvases[i]->getHeight() != height)
+			throw love::Exception("All canvas arguments must have the same dimensions.");
+
+		if (canvases[i]->getTextureType() != texture_type)
+			throw love::Exception("All canvas arguments must have the same texture type.");
+	}
+
+	setupGrab();
+
+	// don't attach anything if there's nothing to attach/detach
+	if (canvases.size() == 0 && attachedCanvases.size() == 0)
+		return;
+
+	// attach the canvas textures to the active FBO and set up multiple render targets
+	strategy->setAttachments(canvases);
+
+	// retain newly attached canvases
+	for (size_t i = 0; i < canvases.size(); i++)
+		canvases[i]->retain();
+
+	// release any old canvases
+	for (size_t i = 0; i < attachedCanvases.size(); i++)
+		attachedCanvases[i]->release();
+
+	attachedCanvases = canvases;
+}
+
+void Canvas::startGrab()
+{
+	setupGrab();
+
+	if (attachedCanvases.size() == 0)
+		return;
+
+	// make sure the FBO is only using a single canvas
+	strategy->setAttachments();
+
+	// release any previously attached canvases
+	for (size_t i = 0; i < attachedCanvases.size(); i++)
+		attachedCanvases[i]->release();
+
+	attachedCanvases.clear();
+}
+
 void Canvas::stopGrab()
 {
 	// i am not grabbing. leave me alone
@@ -393,20 +545,34 @@ void Canvas::stopGrab()
 	current = NULL;
 }
 
-
 void Canvas::clear(const Color &c)
 {
 	GLuint previous = 0;
-	if (current != NULL)
-		previous = current->fbo;
 
-	strategy->bindFBO(fbo);
-	glPushAttrib(GL_COLOR_BUFFER_BIT);
+	if (current != this)
+	{
+		if (current != NULL)
+			previous = current->fbo;
+
+		strategy->bindFBO(fbo);
+		glPushAttrib(GL_COLOR_BUFFER_BIT);
+	}
+
+	// Make sure only this canvas is cleared when multi-canvas rendering is set
+	if (attachedCanvases.size() > 0)
+		strategy->setAttachments();
+
 	glClearColor((float)c.r/255.0f, (float)c.g/255.0f, (float)c.b/255.0f, (float)c.a/255.0f);
 	glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
-	glPopAttrib();
 
-	strategy->bindFBO(previous);
+	if (attachedCanvases.size() > 0)
+		strategy->setAttachments(attachedCanvases);
+
+	if (current != this)
+	{
+		glPopAttrib();
+		strategy->bindFBO(previous);
+	}
 }
 
 void Canvas::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const
@@ -472,6 +638,11 @@ void Canvas::getPixel(unsigned char* pixel_rgba, int x, int y)
 		strategy->bindFBO( 0 );
 }
 
+const std::vector<Canvas *> &Canvas::getAttachedCanvases() const
+{
+	return attachedCanvases;
+}
+
 void Canvas::setFilter(const Image::Filter &f)
 {
 	bindTexture(img);
@@ -515,6 +686,11 @@ void Canvas::unloadVolatile()
 	settings.filter = getFilter();
 	settings.wrap   = getWrap();
 	strategy->deleteFBO(fbo, depth_stencil, img);
+
+	for (size_t i = 0; i < attachedCanvases.size(); i++)
+		attachedCanvases[i]->release();
+
+	attachedCanvases.clear();
 }
 
 int Canvas::getWidth()

+ 19 - 3
src/modules/graphics/opengl/Canvas.h

@@ -21,6 +21,7 @@
 #ifndef LOVE_GRAPHICS_OPENGL_CANVAS_H
 #define LOVE_GRAPHICS_OPENGL_CANVAS_H
 
+// LOVE
 #include "graphics/DrawQable.h"
 #include "graphics/Volatile.h"
 #include "graphics/Image.h"
@@ -31,6 +32,9 @@
 #include "common/Matrix.h"
 #include "OpenGL.h"
 
+// STL
+#include <vector>
+
 namespace love
 {
 namespace graphics
@@ -50,6 +54,11 @@ public:
 	Canvas(int width, int height, TextureType texture_type = TYPE_NORMAL);
 	virtual ~Canvas();
 
+	/**
+	 * @param canvases A list of other canvases to temporarily attach to this one,
+	 * to allow drawing to multiple canvases at once.
+	 **/
+	void startGrab(const std::vector<Canvas *> &canvases);
 	void startGrab();
 	void stopGrab();
 
@@ -66,6 +75,8 @@ public:
 
 	void getPixel(unsigned char* pixel_rgba, int x, int y);
 
+	const std::vector<Canvas *> &getAttachedCanvases() const;
+
 	void setFilter(const Image::Filter &f);
 	Image::Filter getFilter() const;
 
@@ -89,20 +100,22 @@ public:
 	void unloadVolatile();
 
 	static bool isSupported();
-	static bool isHdrSupported();
+	static bool isHDRSupported();
+	static bool isMultiCanvasSupported();
 	static bool getConstant(const char *in, TextureType &out);
 	static bool getConstant(TextureType in, const char *&out);
 
 	static Canvas *current;
 	static void bindDefaultCanvas();
 
-private:
-	friend class Shader;
 	GLuint getTextureName() const
 	{
 		return img;
 	}
 
+private:
+	friend class Shader;
+
 	GLsizei width;
 	GLsizei height;
 	GLuint fbo;
@@ -121,6 +134,9 @@ private:
 		Image::Wrap   wrap;
 	} settings;
 
+	std::vector<Canvas *> attachedCanvases;
+
+	void setupGrab();
 	void drawv(const Matrix &t, const vertex *v) const;
 
 	static StringMap<TextureType, TYPE_MAX_ENUM>::Entry textureTypeEntries[];

+ 25 - 25
src/modules/graphics/opengl/Font.cpp

@@ -51,20 +51,20 @@ Font::Font(love::font::Rasterizer *r, const Image::Filter &filter)
 {
 	// try to find the best texture size match for the font size
 	// default to the largest texture size if no rough match is found
-	texture_size_index = NUM_TEXTURE_SIZES - 1;
+	textureSizeIndex = NUM_TEXTURE_SIZES - 1;
 	for (int i = 0; i < NUM_TEXTURE_SIZES; i++)
 	{
 		// base our chosen texture width/height on a very rough guess of the total size taken up by the font's used glyphs
 		// the estimate is likely larger than the actual total size taken up, which is good since texture changes are expensive
 		if ((height * 0.8) * height * 95 <= TEXTURE_WIDTHS[i] * TEXTURE_HEIGHTS[i])
 		{
-			texture_size_index = i;
+			textureSizeIndex = i;
 			break;
 		}
 	}
 
-	texture_width = TEXTURE_WIDTHS[texture_size_index];
-	texture_height = TEXTURE_HEIGHTS[texture_size_index];
+	textureWidth = TEXTURE_WIDTHS[textureSizeIndex];
+	textureHeight = TEXTURE_HEIGHTS[textureSizeIndex];
 
 	love::font::GlyphData *gd = 0;
 
@@ -102,8 +102,8 @@ bool Font::initializeTexture(GLint format)
 	glTexImage2D(GL_TEXTURE_2D,
 				 0,
 				 internalformat,
-				 (GLsizei)texture_width,
-				 (GLsizei)texture_height,
+				 (GLsizei)textureWidth,
+				 (GLsizei)textureHeight,
 				 0,
 				 format,
 				 GL_UNSIGNED_BYTE,
@@ -114,7 +114,7 @@ bool Font::initializeTexture(GLint format)
 
 void Font::createTexture()
 {
-	texture_x = texture_y = rowHeight = TEXTURE_PADDING;
+	textureX = textureY = rowHeight = TEXTURE_PADDING;
 
 	GLuint t;
 	glGenTextures(1, &t);
@@ -132,17 +132,17 @@ void Font::createTexture()
 
 	// try to initialize the texture, attempting smaller sizes if initialization fails
 	bool initialized = false;
-	while (texture_size_index >= 0)
+	while (textureSizeIndex >= 0)
 	{
-		texture_width = TEXTURE_WIDTHS[texture_size_index];
-		texture_height = TEXTURE_HEIGHTS[texture_size_index];
+		textureWidth = TEXTURE_WIDTHS[textureSizeIndex];
+		textureHeight = TEXTURE_HEIGHTS[textureSizeIndex];
 
 		initialized = initializeTexture(format);
 
-		if (initialized || texture_size_index <= 0)
+		if (initialized || textureSizeIndex <= 0)
 			break;
 
-		--texture_size_index;
+		--textureSizeIndex;
 	}
 
 	if (!initialized)
@@ -156,12 +156,12 @@ void Font::createTexture()
 	}
 	
 	// Fill the texture with transparent black
-	std::vector<GLubyte> emptyData(texture_width * texture_height * (type == FONT_TRUETYPE ? 2 : 4), 0);
+	std::vector<GLubyte> emptyData(textureWidth * textureHeight * (type == FONT_TRUETYPE ? 2 : 4), 0);
 	glTexSubImage2D(GL_TEXTURE_2D,
 					0,
 					0, 0,
-					(GLsizei)texture_width,
-					(GLsizei)texture_height,
+					(GLsizei)textureWidth,
+					(GLsizei)textureHeight,
 					format,
 					GL_UNSIGNED_BYTE,
 					&emptyData[0]);
@@ -175,14 +175,14 @@ Font::Glyph *Font::addGlyph(unsigned int glyph)
 	int w = gd->getWidth();
 	int h = gd->getHeight();
 
-	if (texture_x + w + TEXTURE_PADDING > texture_width)
+	if (textureX + w + TEXTURE_PADDING > textureWidth)
 	{
 		// out of space - new row!
-		texture_x = TEXTURE_PADDING;
-		texture_y += rowHeight;
+		textureX = TEXTURE_PADDING;
+		textureY += rowHeight;
 		rowHeight = TEXTURE_PADDING;
 	}
-	if (texture_y + h + TEXTURE_PADDING > texture_height)
+	if (textureY + h + TEXTURE_PADDING > textureHeight)
 	{
 		// totally out of space - new texture!
 		createTexture();
@@ -203,8 +203,8 @@ Font::Glyph *Font::addGlyph(unsigned int glyph)
 		bindTexture(t);
 		glTexSubImage2D(GL_TEXTURE_2D,
 						0,
-						texture_x,
-						texture_y,
+						textureX,
+						textureY,
 						w, h,
 						(type == FONT_TRUETYPE ? GL_LUMINANCE_ALPHA : GL_RGBA),
 						GL_UNSIGNED_BYTE,
@@ -213,12 +213,12 @@ Font::Glyph *Font::addGlyph(unsigned int glyph)
 		g->texture = t;
 
 		Quad::Viewport v;
-		v.x = (float) texture_x;
-		v.y = (float) texture_y;
+		v.x = (float) textureX;
+		v.y = (float) textureY;
 		v.w = (float) w;
 		v.h = (float) h;
 
-		Quad q = Quad(v, (const float) texture_width, (const float) texture_height);
+		Quad q = Quad(v, (const float) textureWidth, (const float) textureHeight);
 		const vertex *verts = q.getVertices();
 
 		// copy vertex data to the glyph and set proper bearing
@@ -231,7 +231,7 @@ Font::Glyph *Font::addGlyph(unsigned int glyph)
 	}
 
 	if (w > 0)
-		texture_x += (w + TEXTURE_PADDING);
+		textureX += (w + TEXTURE_PADDING);
 	if (h > 0)
 		rowHeight = std::max(rowHeight, h + TEXTURE_PADDING);
 

+ 10 - 6
src/modules/graphics/opengl/Font.h

@@ -183,12 +183,16 @@ private:
 	float lineHeight;
 	float mSpacing; // modifies the spacing by multiplying it with this value
 
-	int texture_size_index;
-	int texture_width;
-	int texture_height;
+	int textureSizeIndex;
+	int textureWidth;
+	int textureHeight;
+
+	// vector of packed textures
+	std::vector<GLuint> textures;
+
+	// maps glyphs to glyph texture information
+	std::map<unsigned int, Glyph *> glyphs;
 
-	std::vector<GLuint> textures; // vector of packed textures
-	std::map<unsigned int, Glyph *> glyphs; // maps glyphs to quad information
 	FontType type;
 	Image::Filter filter;
 
@@ -198,7 +202,7 @@ private:
 
 	static const int TEXTURE_PADDING = 1;
 
-	int texture_x, texture_y;
+	int textureX, textureY;
 	int rowHeight;
 
 	bool initializeTexture(GLint format);

File diff suppressed because it is too large
+ 22312 - 20148
src/modules/graphics/opengl/GLee.c


File diff suppressed because it is too large
+ 947 - 881
src/modules/graphics/opengl/GLee.h


+ 32 - 14
src/modules/graphics/opengl/Graphics.cpp

@@ -87,6 +87,9 @@ DisplayState Graphics::saveState()
 	if (s.scissor)
 		glGetIntegerv(GL_SCISSOR_BOX, s.scissorBox);
 
+	for (int i = 0; i < 4; i++)
+		s.colorMask[i] = colorMask[i];
+
 	return s;
 }
 
@@ -101,6 +104,7 @@ void Graphics::restoreState(const DisplayState &s)
 		setScissor(s.scissorBox[0], s.scissorBox[1], s.scissorBox[2], s.scissorBox[3]);
 	else
 		setScissor();
+	setColorMask(s.colorMask[0], s.colorMask[1], s.colorMask[2], s.colorMask[3]);
 }
 
 bool Graphics::setMode(int width, int height, WindowFlags *flags)
@@ -135,6 +139,9 @@ bool Graphics::setMode(int width, int height, WindowFlags *flags)
 	// "Normal" blending
 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
+	// Enable all color component writes.
+	setColorMask(true, true, true, true);
+
 	// Enable line/point smoothing.
 	setLineStyle(LINE_SMOOTH);
 	glEnable(GL_POINT_SMOOTH);
@@ -265,33 +272,29 @@ bool Graphics::isCreated() const
 int Graphics::getModes(lua_State *L) const
 {
 	int n;
-	love::window::Window::WindowSize **modes = currentWindow->getFullscreenSizes(n);
+	love::window::Window::WindowSize *modes = currentWindow->getFullscreenSizes(n);
 
 	if (modes == 0)
 		return 0;
 
-	lua_newtable(L);
+	lua_createtable(L, n, 0);
 
 	for (int i = 0; i < n ; i++)
 	{
 		lua_pushinteger(L, i+1);
-		lua_newtable(L);
+		lua_createtable(L, 0, 2);
 
 		// Inner table attribs.
 
-		lua_pushstring(L, "width");
-		lua_pushinteger(L, modes[i]->width);
-		lua_settable(L, -3);
+		lua_pushinteger(L, modes[i].width);
+		lua_setfield(L, -2, "width");
 
-		lua_pushstring(L, "height");
-		lua_pushinteger(L, modes[i]->height);
-		lua_settable(L, -3);
+		lua_pushinteger(L, modes[i].height);
+		lua_setfield(L, -2, "height");
 
 		// Inner table attribs end.
 
 		lua_settable(L, -3);
-
-		delete modes[i];
 	}
 
 	delete[] modes;
@@ -338,12 +341,12 @@ void Graphics::useStencil(bool invert)
 {
 	glStencilFunc(GL_EQUAL, (int)(!invert), 1); // invert ? 0 : 1
 	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
-	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+	setColorMask(colorMask[0], colorMask[1], colorMask[2], colorMask[3]);
 }
 
 void Graphics::discardStencil()
 {
-	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+	setColorMask(colorMask[0], colorMask[1], colorMask[2], colorMask[3]);
 	glDisable(GL_STENCIL_TEST);
 }
 
@@ -397,7 +400,7 @@ ParticleSystem *Graphics::newParticleSystem(Image *image, int size)
 
 Canvas *Graphics::newCanvas(int width, int height, Canvas::TextureType texture_type)
 {
-	if (texture_type == Canvas::TYPE_HDR && !Canvas::isHdrSupported())
+	if (texture_type == Canvas::TYPE_HDR && !Canvas::isHDRSupported())
 		throw Exception("HDR Canvases are not supported by your OpenGL implementation");
 
 	while (GL_NO_ERROR != glGetError())
@@ -507,6 +510,21 @@ Font *Graphics::getFont() const
 	return currentFont;
 }
 
+void Graphics::setColorMask(bool r, bool g, bool b, bool a)
+{
+	colorMask[0] = r;
+	colorMask[1] = g;
+	colorMask[2] = b;
+	colorMask[3] = a;
+
+	glColorMask((GLboolean) r, (GLboolean) g, (GLboolean) b, (GLboolean) a);
+}
+
+const bool *Graphics::getColorMask() const
+{
+	return colorMask;
+}
+
 void Graphics::setBlendMode(Graphics::BlendMode mode)
 {
 	const int gl_1_4 = GLEE_VERSION_1_4;

+ 16 - 0
src/modules/graphics/opengl/Graphics.h

@@ -77,6 +77,9 @@ struct DisplayState
 	bool scissor;
 	GLint scissorBox[4];
 
+	// Color mask.
+	bool colorMask[4];
+
 	// Window info.
 	std::string caption;
 	bool mouseVisible;
@@ -94,6 +97,7 @@ struct DisplayState
 		pointSize = 1.0f;
 		pointStyle = Graphics::POINT_SMOOTH;
 		scissor = false;
+		colorMask[0] = colorMask[1] = colorMask[2] = colorMask[3] = true;
 		caption = "";
 		mouseVisible = true;
 	}
@@ -299,6 +303,17 @@ public:
 	 **/
 	Font *getFont() const;
 
+	/**
+	 * Sets the enabled color components when rendering.
+	 **/
+	void setColorMask(bool r, bool g, bool b, bool a);
+
+	/**
+	 * Gets the current color mask.
+	 * Returns an array of 4 booleans representing the mask.
+	 **/
+	const bool *getColorMask() const;
+
 	/**
 	 * Sets the current blend mode.
 	 **/
@@ -495,6 +510,7 @@ private:
 	float lineWidth;
 	GLint matrixLimit;
 	GLint userMatrices;
+	bool colorMask[4];
 
 	int getRenderWidth() const;
 	int getRenderHeight() const;

+ 14 - 0
src/modules/graphics/opengl/ParticleSystem.cpp

@@ -478,6 +478,20 @@ void ParticleSystem::reset()
 	emitCounter = 0;
 }
 
+void ParticleSystem::emit(int num)
+{
+	if (!active)
+		return;
+
+	for (int i = 0; i < num; i++)
+	{
+		if (isFull())
+			return;
+
+		add();
+	}
+}
+
 bool ParticleSystem::isActive() const
 {
 	return active;

+ 6 - 0
src/modules/graphics/opengl/ParticleSystem.h

@@ -377,6 +377,12 @@ public:
 	 **/
 	void reset();
 
+	/**
+	 * Instantly emits a number of particles.
+	 * @param num The number of particles to emit.
+	 **/
+	void emit(int num);
+
 	/**
 	 * Returns whether the particle emitter is active.
 	 **/

+ 59 - 22
src/modules/graphics/opengl/Shader.cpp

@@ -71,9 +71,12 @@ Shader::Shader(const ShaderSources &sources)
 	if (shaderSources.empty())
 		throw love::Exception("Cannot create shader: no source code!");
 
-	GLint maxtexunits;
-	glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxtexunits);
-	maxTextureUnits = std::max(maxtexunits - 1, 0);
+	if (maxTextureUnits <= 0)
+	{
+		GLint maxtexunits;
+		glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxtexunits);
+		maxTextureUnits = std::max(maxtexunits - 1, 0);
+	}
 
 	// initialize global texture id counters if needed
 	if (textureCounters.size() < (size_t) maxTextureUnits)
@@ -94,17 +97,18 @@ Shader::~Shader()
 GLuint Shader::compileCode(ShaderType type, const std::string &code)
 {
 	GLenum glshadertype;
-	const char *shadertypename = NULL;
+	const char *typestr;
+
+	if (!typeNames.find(type, typestr))
+		typestr = "";
 
 	switch (type)
 	{
 	case TYPE_VERTEX:
 		glshadertype = GL_VERTEX_SHADER;
-		shadertypename = "vertex";
 		break;
 	case TYPE_PIXEL:
 		glshadertype = GL_FRAGMENT_SHADER;
-		shadertypename = "pixel";
 		break;
 	default:
 		throw love::Exception("Cannot create shader object: unknown shader type.");
@@ -121,9 +125,9 @@ GLuint Shader::compileCode(ShaderType type, const std::string &code)
 		GLenum err = glGetError();
 
 		if (err == GL_INVALID_ENUM) // invalid or unsupported shader type
-			throw love::Exception("Cannot create %s shader object: %s shaders not supported.", shadertypename, shadertypename);
+			throw love::Exception("Cannot create %s shader object: %s shaders not supported.", typestr, typestr);
 		else // other errors should only happen between glBegin() and glEnd()
-			throw love::Exception("Cannot create %s shader object.", shadertypename);
+			throw love::Exception("Cannot create %s shader object.", typestr);
 	}
 
 	const char *src = code.c_str();
@@ -132,23 +136,26 @@ GLuint Shader::compileCode(ShaderType type, const std::string &code)
 
 	glCompileShader(shaderid);
 
-	GLint status;
-	glGetShaderiv(shaderid, GL_COMPILE_STATUS, &status);
+	// Get any warnings the shader compiler may have produced
+	GLint infologlen;
+	glGetShaderiv(shaderid, GL_INFO_LOG_LENGTH, &infologlen);
 
-	if (status == GL_FALSE)
-	{
-		GLint infologlen;
-		glGetShaderiv(shaderid, GL_INFO_LOG_LENGTH, &infologlen);
+	GLchar *infolog = new GLchar[infologlen + 1];
+	glGetShaderInfoLog(shaderid, infologlen, NULL, infolog);
 
-		GLchar *errorlog = new GLchar[infologlen + 1];
-		glGetShaderInfoLog(shaderid, infologlen, NULL, errorlog);
+	// Save any warnings for later querying
+	if (infologlen > 0)
+		shaderWarnings[type] = infolog;
 
-		std::string tmp(errorlog);
+	delete[] infolog;
 
-		delete[] errorlog;
-		glDeleteShader(shaderid);
+	GLint status;
+	glGetShaderiv(shaderid, GL_COMPILE_STATUS, &status);
 
-		throw love::Exception("Cannot compile %s shader code:\n%s", shadertypename, tmp.c_str());
+	if (status == GL_FALSE)
+	{
+		throw love::Exception("Cannot compile %s shader code:\n%s",
+		                      typestr, shaderWarnings[type].c_str());
 	}
 
 	return shaderid;
@@ -174,7 +181,7 @@ void Shader::createProgram(const std::vector<GLuint> &shaderids)
 
 	if (status == GL_FALSE)
 	{
-		const std::string warnings = getWarnings();
+		std::string warnings = getProgramWarnings();
 		glDeleteProgram(program);
 
 		throw love::Exception("Cannot link shader program object:\n%s", warnings.c_str());
@@ -233,12 +240,15 @@ void Shader::unloadVolatile()
 
 	// same with uniform location list
 	uniforms.clear();
+
+	shaderWarnings.clear();
 }
 
-std::string Shader::getWarnings() const
+std::string Shader::getProgramWarnings() const
 {
 	GLint strlen, nullpos;
 	glGetProgramiv(program, GL_INFO_LOG_LENGTH, &strlen);
+
 	char *tempstr = new char[strlen+1];
 	// be extra sure that the error string will be 0-terminated
 	memset(tempstr, '\0', strlen+1);
@@ -247,6 +257,25 @@ std::string Shader::getWarnings() const
 
 	std::string warnings(tempstr);
 	delete[] tempstr;
+
+	return warnings;
+}
+
+std::string Shader::getWarnings() const
+{
+	std::string warnings;
+	const char *typestr;
+
+	// Get the individual shader stage warnings
+	std::map<ShaderType, std::string>::const_iterator it;
+	for (it = shaderWarnings.begin(); it != shaderWarnings.end(); ++it)
+	{
+		if (typeNames.find(it->first, typestr))
+			warnings += std::string(typestr) + std::string(" shader:\n") + it->second;
+	}
+
+	warnings += getProgramWarnings();
+
 	return warnings;
 }
 
@@ -452,6 +481,14 @@ bool Shader::isSupported()
 	return GLEE_VERSION_2_0 && getGLSLVersion() >= "1.2";
 }
 
+StringMap<Shader::ShaderType, Shader::TYPE_MAX_ENUM>::Entry Shader::typeNameEntries[] =
+{
+	{"vertex", Shader::TYPE_VERTEX},
+	{"pixel", Shader::TYPE_PIXEL},
+};
+
+StringMap<Shader::ShaderType, Shader::TYPE_MAX_ENUM> Shader::typeNames(Shader::typeNameEntries, sizeof(Shader::typeNameEntries));
+
 } // opengl
 } // graphics
 } // love

+ 16 - 3
src/modules/graphics/opengl/Shader.h

@@ -21,14 +21,18 @@
 #ifndef LOVE_GRAPHICS_SHADER_H
 #define LOVE_GRAPHICS_SHADER_H
 
+// LOVE
 #include "common/Object.h"
-#include <string>
-#include <map>
-#include <vector>
+#include "common/StringMap.h"
 #include "OpenGL.h"
 #include "Image.h"
 #include "Canvas.h"
 
+// STL
+#include <string>
+#include <map>
+#include <vector>
+
 namespace love
 {
 namespace graphics
@@ -133,9 +137,15 @@ private:
 
 	void sendTexture(const std::string &name, GLuint texture);
 
+	// 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;
 
+	// Shader compiler warning strings for individual shader stages.
+	std::map<ShaderType, std::string> shaderWarnings;
+
 	GLuint program; // volatile
 
 	// Uniform location buffer map
@@ -150,6 +160,9 @@ private:
 
 	// Counts total number of textures bound to each texture unit in all shaders
 	static std::vector<int> textureCounters;
+
+	static StringMap<ShaderType, TYPE_MAX_ENUM>::Entry typeNameEntries[];
+	static StringMap<ShaderType, TYPE_MAX_ENUM> typeNames;
 };
 
 } // opengl

+ 16 - 13
src/modules/graphics/opengl/wrap_Canvas.cpp

@@ -47,7 +47,14 @@ int w_Canvas_renderTo(lua_State *L)
 	if (!lua_isfunction(L, 2))
 		return luaL_error(L, "Need a function to render to canvas.");
 
-	canvas->startGrab();
+	try
+	{
+		canvas->startGrab();
+	}
+	catch (love::Exception &e)
+	{
+		return luaL_error(L, "%s", e.what());
+	}
 	lua_settop(L, 2); // make sure the function is on top of the stack
 	lua_call(L, 0, 0);
 	canvas->stopGrab();
@@ -172,18 +179,14 @@ int w_Canvas_clear(lua_State *L)
 	}
 	else if (lua_istable(L, 2))
 	{
-		lua_pushinteger(L, 1);
-		lua_gettable(L, 2);
-		c.r = (unsigned char)luaL_checkint(L, -1);
-		lua_pushinteger(L, 2);
-		lua_gettable(L, 2);
-		c.g = (unsigned char)luaL_checkint(L, -1);
-		lua_pushinteger(L, 3);
-		lua_gettable(L, 2);
-		c.b = (unsigned char)luaL_checkint(L, -1);
-		lua_pushinteger(L, 4);
-		lua_gettable(L, 2);
-		c.g = (unsigned char)luaL_optint(L, -1, 255);
+		for (int i = 1; i <= 4; i++)
+			lua_rawgeti(L, 2, i);
+
+		c.r = (unsigned char)luaL_checkint(L, -4);
+		c.g = (unsigned char)luaL_checkint(L, -3);
+		c.b = (unsigned char)luaL_checkint(L, -2);
+		c.a = (unsigned char)luaL_optint(L, -1, 255);
+
 		lua_pop(L, 4);
 	}
 	else

+ 124 - 40
src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -601,22 +601,15 @@ int w_setColor(lua_State *L)
 	Color c;
 	if (lua_istable(L, 1))
 	{
-		lua_pushinteger(L, 1);
-		lua_gettable(L, -2);
-		c.r = (unsigned char)luaL_checkint(L, -1);
-		lua_pop(L, 1);
-		lua_pushinteger(L, 2);
-		lua_gettable(L, -2);
-		c.g = (unsigned char)luaL_checkint(L, -1);
-		lua_pop(L, 1);
-		lua_pushinteger(L, 3);
-		lua_gettable(L, -2);
-		c.b = (unsigned char)luaL_checkint(L, -1);
-		lua_pop(L, 1);
-		lua_pushinteger(L, 4);
-		lua_gettable(L, -2);
+		for (int i = 1; i <= 4; i++)
+			lua_rawgeti(L, 1, i);
+
+		c.r = (unsigned char)luaL_checkint(L, -4);
+		c.g = (unsigned char)luaL_checkint(L, -3);
+		c.b = (unsigned char)luaL_checkint(L, -2);
 		c.a = (unsigned char)luaL_optint(L, -1, 255);
-		lua_pop(L, 1);
+
+		lua_pop(L, 4);
 	}
 	else
 	{
@@ -644,22 +637,15 @@ int w_setBackgroundColor(lua_State *L)
 	Color c;
 	if (lua_istable(L, 1))
 	{
-		lua_pushinteger(L, 1);
-		lua_gettable(L, -2);
-		c.r = (unsigned char)luaL_checkint(L, -1);
-		lua_pop(L, 1);
-		lua_pushinteger(L, 2);
-		lua_gettable(L, -2);
-		c.g = (unsigned char)luaL_checkint(L, -1);
-		lua_pop(L, 1);
-		lua_pushinteger(L, 3);
-		lua_gettable(L, -2);
-		c.b = (unsigned char)luaL_checkint(L, -1);
-		lua_pop(L, 1);
-		lua_pushinteger(L, 4);
-		lua_gettable(L, -2);
+		for (int i = 1; i <= 4; i++)
+			lua_rawgeti(L, 1, i);
+
+		c.r = (unsigned char)luaL_checkint(L, -4);
+		c.g = (unsigned char)luaL_checkint(L, -3);
+		c.b = (unsigned char)luaL_checkint(L, -2);
 		c.a = (unsigned char)luaL_optint(L, -1, 255);
-		lua_pop(L, 1);
+
+		lua_pop(L, 4);
 	}
 	else
 	{
@@ -701,6 +687,28 @@ int w_getFont(lua_State *L)
 	return 1;
 }
 
+int w_setColorMask(lua_State *L)
+{
+	bool mask[4];
+	for (int i = 0; i < 4; i++)
+		mask[i] = luax_toboolean(L, i + 1);
+
+	// r, g, b, a
+	instance->setColorMask(mask[0], mask[1], mask[2], mask[3]);
+
+	return 0;
+}
+
+int w_getColorMask(lua_State *L)
+{
+	const bool *mask = instance->getColorMask();
+
+	for (int i = 0; i < 4; i++)
+		luax_pushboolean(L, mask[i]);
+
+	return 4;
+}
+
 int w_setBlendMode(lua_State *L)
 {
 	Graphics::BlendMode mode;
@@ -943,8 +951,67 @@ int w_setCanvas(lua_State *L)
 	}
 
 	Canvas *canvas = luax_checkcanvas(L, 1);
-	// this unbinds the previous fbo
-	canvas->startGrab();
+
+	try
+	{
+		// this unbinds the previous fbo
+		canvas->startGrab();
+	}
+	catch (love::Exception &e)
+	{
+		return luaL_error(L, "%s", e.what());
+	}
+
+	return 0;
+}
+
+int w_setCanvases(lua_State *L)
+{
+	// discard stencil testing
+	instance->discardStencil();
+
+	// called with none -> reset to default buffer
+	// nil is an error, to help people with typoes
+	if (lua_isnone(L,1))
+	{
+		Canvas::bindDefaultCanvas();
+		return 0;
+	}
+
+	bool is_table = lua_istable(L, 1);
+	std::vector<Canvas *> attachments;
+
+	Canvas *canvas = 0;
+
+	if (is_table)
+	{
+		// grab the first canvas in the array and attach the rest
+		lua_rawgeti(L, 1, 1);
+		canvas = luax_checkcanvas(L, -1);
+		lua_pop(L, 1);
+
+		for (int i = 2; i <= lua_objlen(L, 1); i++)
+		{
+			lua_rawgeti(L, 1, i);
+			attachments.push_back(luax_checkcanvas(L, -1));
+			lua_pop(L, 1);
+		}
+	}
+	else
+	{
+		canvas = luax_checkcanvas(L, 1);
+		for (int i = 2; i <= lua_gettop(L); i++)
+			attachments.push_back(luax_checkcanvas(L, i));
+	}
+
+	try
+	{
+		canvas->startGrab(attachments);
+	}
+	catch (love::Exception &e)
+	{
+		return luaL_error(L, "%s", e.what());
+	}
 
 	return 0;
 }
@@ -952,14 +1019,25 @@ int w_setCanvas(lua_State *L)
 int w_getCanvas(lua_State *L)
 {
 	Canvas *canvas = Canvas::current;
+	int n = 1;
+
 	if (canvas)
 	{
 		canvas->retain();
 		luax_newtype(L, "Canvas", GRAPHICS_CANVAS_T, (void *) canvas);
+
+		const std::vector<Canvas *> &attachments = canvas->getAttachedCanvases();
+		for (size_t i = 0; i < attachments.size(); i++)
+		{
+			attachments[i]->retain();
+			luax_newtype(L, "Canvas", GRAPHICS_CANVAS_T, (void *) attachments[i]);
+			n++;
+		}
 	}
 	else
 		lua_pushnil(L);
-	return 1;
+
+	return n;
 }
 
 int w_setShader(lua_State *L)
@@ -1009,7 +1087,11 @@ int w_isSupported(lua_State *L)
 				supported = false;
 			break;
 		case Graphics::SUPPORT_HDR_CANVAS:
-			if (!Canvas::isHdrSupported())
+			if (!Canvas::isHDRSupported())
+				supported = false;
+			break;
+		case Graphics::SUPPORT_MULTI_CANVAS:
+			if (!Canvas::isMultiCanvasSupported())
 				supported = false;
 			break;
 		case Graphics::SUPPORT_SHADER:
@@ -1086,8 +1168,8 @@ int w_drawq(lua_State *L)
 {
 	DrawQable *dq = luax_checktype<DrawQable>(L, 1, "DrawQable", GRAPHICS_DRAWQABLE_T);
 	Quad *q = luax_checkquad(L, 2);
-	float x = (float)luaL_checknumber(L, 3);
-	float y = (float)luaL_checknumber(L, 4);
+	float x = (float)luaL_optnumber(L, 3, 0.0f);
+	float y = (float)luaL_optnumber(L, 4, 0.0f);
 	float angle = (float)luaL_optnumber(L, 5, 0);
 	float sx = (float)luaL_optnumber(L, 6, 1);
 	float sy = (float)luaL_optnumber(L, 7, sx);
@@ -1192,8 +1274,7 @@ int w_line(lua_State *L)
 	{
 		for (int i = 0; i < args; ++i)
 		{
-			lua_pushnumber(L, i + 1);
-			lua_rawget(L, 1);
+			lua_rawgeti(L, 1, i + 1);
 			coords[i] = luax_tofloat(L, -1);
 			lua_pop(L, 1);
 		}
@@ -1295,8 +1376,7 @@ int w_polygon(lua_State *L)
 	{
 		for (int i = 0; i < args; ++i)
 		{
-			lua_pushnumber(L, i + 1);
-			lua_rawget(L, 2);
+			lua_rawgeti(L, 2, i + 1);
 			coords[i] = luax_tofloat(L, -1);
 			lua_pop(L, 1);
 		}
@@ -1408,6 +1488,8 @@ static const luaL_Reg functions[] =
 	{ "setFont", w_setFont },
 	{ "getFont", w_getFont },
 
+	{ "setColorMask", w_setColorMask },
+	{ "getColorMask", w_getColorMask },
 	{ "setBlendMode", w_setBlendMode },
 	{ "getBlendMode", w_getBlendMode },
 	{ "setDefaultFilter", w_setDefaultFilter },
@@ -1427,7 +1509,9 @@ static const luaL_Reg functions[] =
 	{ "getMaxPointSize", w_getMaxPointSize },
 	{ "newScreenshot", w_newScreenshot },
 	{ "setCanvas", w_setCanvas },
+	{ "setCanvases", w_setCanvases },
 	{ "getCanvas", w_getCanvas },
+	{ "getCanvases", w_getCanvas },
 
 	{ "setShader", w_setShader },
 	{ "getShader", w_getShader },

+ 3 - 0
src/modules/graphics/opengl/wrap_Graphics.h

@@ -71,6 +71,8 @@ int w_setBackgroundColor(lua_State *L);
 int w_getBackgroundColor(lua_State *L);
 int w_setFont(lua_State *L);
 int w_getFont(lua_State *L);
+int w_setColorMask(lua_State *L);
+int w_getColorMask(lua_State *L);
 int w_setBlendMode(lua_State *L);
 int w_getBlendMode(lua_State *L);
 int w_setDefaultFilter(lua_State *L);
@@ -90,6 +92,7 @@ int w_getPointStyle(lua_State *L);
 int w_getMaxPointSize(lua_State *L);
 int w_newScreenshot(lua_State *L);
 int w_setCanvas(lua_State *L);
+int w_setCanvases(lua_State *L);
 int w_getCanvas(lua_State *L);
 int w_setShader(lua_State *L);
 int w_getShader(lua_State *L);

+ 11 - 6
src/modules/graphics/opengl/wrap_ParticleSystem.cpp

@@ -248,11 +248,8 @@ int w_ParticleSystem_setColors(lua_State *L)
 				return luaL_argerror(L, i + 2, "expected 4 color components");
 
 			for (int j = 0; j < 4; j++)
-			{
 				// push args[i+2][j+1] onto the stack
-				lua_pushnumber(L, j + 1);
-				lua_gettable(L, i + 2);
-			}
+				lua_rawgeti(L, i + 2, j + 1);
 
 			int r = luaL_checkint(L, -4);
 			int g = luaL_checkint(L, -3);
@@ -325,8 +322,7 @@ int w_ParticleSystem_setQuads(lua_State *L)
 	{
 		for (int i = 0; i < nQuads; i++)
 		{
-			lua_pushnumber(L, i + 1); // array index
-			lua_gettable(L, 2);
+			lua_rawgeti(L, 2, i + 1); // array index
 			quads[i] = luax_checkquad(L, -1);
 			lua_pop(L, 1);
 		}
@@ -452,6 +448,14 @@ int w_ParticleSystem_reset(lua_State *L)
 	return 0;
 }
 
+int w_ParticleSystem_emit(lua_State *L)
+{
+	ParticleSystem *t = luax_checkparticlesystem(L, 1);
+	int num = luaL_checkint(L, 2);
+	t->emit(num);
+	return 0;
+}
+
 int w_ParticleSystem_isActive(lua_State *L)
 {
 	ParticleSystem *t = luax_checkparticlesystem(L, 1);
@@ -518,6 +522,7 @@ static const luaL_Reg functions[] =
 	{ "stop", w_ParticleSystem_stop },
 	{ "pause", w_ParticleSystem_pause },
 	{ "reset", w_ParticleSystem_reset },
+	{ "emit", w_ParticleSystem_emit },
 	{ "isActive", w_ParticleSystem_isActive },
 	{ "isEmpty", w_ParticleSystem_isEmpty },
 	{ "isFull", w_ParticleSystem_isFull },

+ 1 - 0
src/modules/graphics/opengl/wrap_ParticleSystem.h

@@ -67,6 +67,7 @@ int w_ParticleSystem_start(lua_State *L);
 int w_ParticleSystem_stop(lua_State *L);
 int w_ParticleSystem_pause(lua_State *L);
 int w_ParticleSystem_reset(lua_State *L);
+int w_ParticleSystem_emit(lua_State *L);
 int w_ParticleSystem_isActive(lua_State *L);
 int w_ParticleSystem_isEmpty(lua_State *L);
 int w_ParticleSystem_isFull(lua_State *L);

+ 8 - 11
src/modules/graphics/opengl/wrap_SpriteBatch.cpp

@@ -158,18 +158,15 @@ int w_SpriteBatch_setColor(lua_State *L)
 	}
 	else if (lua_istable(L, 2))
 	{
-		lua_rawgeti(L, 2, 1);
-		c.r = (unsigned char) luaL_checkint(L, -1);
-		lua_pop(L, 1);
-		lua_rawgeti(L, 2, 2);
-		c.g = (unsigned char) luaL_checkint(L, -1);
-		lua_pop(L, 1);
-		lua_rawgeti(L, 2, 3);
-		c.b = (unsigned char) luaL_checkint(L, -1);
-		lua_pop(L, 1);
-		lua_rawgeti(L, 2, 4);
+		for (int i = 1; i <= 4; i++)
+			lua_rawgeti(L, 2, i);
+
+		c.r = (unsigned char) luaL_checkint(L, -4);
+		c.g = (unsigned char) luaL_checkint(L, -3);
+		c.b = (unsigned char) luaL_checkint(L, -2);
 		c.a = (unsigned char) luaL_optint(L, -1, 255);
-		lua_pop(L, 1);
+
+		lua_pop(L, 4);
 	}
 	else
 	{

+ 11 - 11
src/modules/image/devil/ImageData.cpp

@@ -108,8 +108,7 @@ void ImageData::load(Data *data)
 		devilMutex = thread::newMutex();
 
 	Lock lock(devilMutex);
-	ILuint image;
-	ilGenImages(1, &image);
+	ILuint image = ilGenImage();
 	ilBindImage(image);
 
 	try
@@ -132,13 +131,14 @@ void ImageData::load(Data *data)
 
 		create(width, height, ilGetData());
 	}
-	catch(std::exception &e)
+	catch (std::exception &e)
 	{
-		ilDeleteImages(1, &image);
+		// catches love and std exceptions
+		ilDeleteImage(image);
 		throw love::Exception("%s", e.what());
 	}
 
-	ilDeleteImages(1, &image);
+	ilDeleteImage(image);
 }
 
 void ImageData::encode(love::filesystem::File *f, ImageData::Format format)
@@ -149,8 +149,7 @@ void ImageData::encode(love::filesystem::File *f, ImageData::Format format)
 	Lock lock1(devilMutex);
 	Lock lock2(mutex);
 
-	ILuint tempimage;
-	ilGenImages(1, &tempimage);
+	ILuint tempimage = ilGenImage();
 	ilBindImage(tempimage);
 	ilxClearErrors();
 
@@ -222,14 +221,15 @@ void ImageData::encode(love::filesystem::File *f, ImageData::Format format)
 		f->write(encoded_data, size);
 		f->close();
 	}
-	catch(std::exception &)
+	catch (std::exception &e)
 	{
-		ilDeleteImages(1, &tempimage);
+		// catches love and std exceptions
+		ilDeleteImage(tempimage);
 		delete[] encoded_data;
-		throw;
+		throw love::Exception("%s", e.what());
 	}
 
-	ilDeleteImages(1, &tempimage);
+	ilDeleteImage(tempimage);
 	delete[] encoded_data;
 }
 

+ 3 - 3
src/modules/image/wrap_ImageData.cpp

@@ -167,13 +167,14 @@ int w_ImageData_encode(lua_State *L)
 	{
 		ext = file->getExtension();
 		fmt = ext.c_str();
-		ImageData::getConstant(fmt, format);
+		if (!ImageData::getConstant(fmt, format))
+			return luaL_error(L, "Invalid image format '%s'.", fmt);
 	}
 	else
 	{
 		fmt = luaL_checkstring(L, 3);
 		if (!ImageData::getConstant(fmt, format))
-			luaL_error(L, "Invalid image format.");
+			return luaL_error(L, "Invalid image format '%s'.", fmt);
 	}
 
 	try
@@ -189,7 +190,6 @@ int w_ImageData_encode(lua_State *L)
 
 static const luaL_Reg functions[] =
 {
-
 	// Data
 	{ "getPointer", w_Data_getPointer },
 	{ "getSize", w_Data_getSize },

+ 2 - 2
src/modules/sound/SoundData.cpp

@@ -152,9 +152,9 @@ int SoundData::getSampleCount() const
 	return (size/channels)/(bits/8);
 }
 
-int SoundData::getDuration() const
+float SoundData::getDuration() const
 {
-	return size/(channels*sampleRate*bits/8);
+	return float(size) /(channels*sampleRate*bits/8);
 }
 
 void SoundData::setSample(int i, float sample)

+ 1 - 1
src/modules/sound/SoundData.h

@@ -50,7 +50,7 @@ public:
 	virtual int getSampleRate() const;
 	virtual int getSampleCount() const;
 
-	virtual int getDuration() const;
+	virtual float getDuration() const;
 
 	void setSample(int i, float sample);
 	float getSample(int i) const;

+ 1 - 1
src/modules/sound/wrap_SoundData.cpp

@@ -63,7 +63,7 @@ int w_SoundData_getSampleCount(lua_State *L)
 int w_SoundData_getDuration(lua_State *L)
 {
 	SoundData *t = luax_checksounddata(L, 1);
-	lua_pushinteger(L, t->getDuration());
+	lua_pushnumber(L, t->getDuration());
 	return 1;
 }
 

+ 4 - 0
src/modules/thread/wrap_Channel.cpp

@@ -47,6 +47,8 @@ int w_Channel_push(lua_State *L)
 {
 	Channel *c = luax_checkchannel(L, 1);
 	Variant *var = Variant::fromLua(L, 2);
+	if (!var)
+		return luaL_argerror(L, 2, "boolean, number, string, or love userdata expected");
 	c->push(var);
 	releaseVariant(c, var);
 	return 0;
@@ -56,6 +58,8 @@ int w_Channel_supply(lua_State *L)
 {
 	Channel *c = luax_checkchannel(L, 1);
 	Variant *var = Variant::fromLua(L, 2);
+	if (!var)
+		return luaL_argerror(L, 2, "boolean, number, string, or love userdata expected");
 	c->supply(var);
 	releaseVariant(c, var);
 	return 0;

+ 2 - 0
src/modules/timer/sdl/Timer.cpp

@@ -49,6 +49,8 @@ Timer::Timer()
 	// Init the SDL timer system.
 	if (SDL_InitSubSystem(SDL_INIT_TIMER) < 0)
 		throw Exception(SDL_GetError());
+
+	prevFpsUpdate = currTime = getMicroTime();
 }
 
 Timer::~Timer()

+ 2 - 2
src/modules/window/Window.h

@@ -59,14 +59,14 @@ public:
 	virtual void getWindow(int &width, int &height, WindowFlags &flags) const = 0;
 
 	virtual bool checkWindowSize(int width, int height, bool fullscreen) const = 0;
-	virtual WindowSize **getFullscreenSizes(int &n) const = 0;
+	virtual WindowSize *getFullscreenSizes(int &n) const = 0;
 
 	virtual int getWidth() const = 0;
 	virtual int getHeight() const = 0;
 
 	virtual bool isCreated() const = 0;
 
-	virtual void setWindowTitle(std::string &title) = 0;
+	virtual void setWindowTitle(const std::string &title) = 0;
 	virtual std::string getWindowTitle() const = 0;
 
 	virtual bool setIcon(love::image::ImageData *imgd) = 0;

+ 5 - 6
src/modules/window/sdl/Window.cpp

@@ -211,7 +211,7 @@ bool Window::checkWindowSize(int width, int height, bool fullscreen) const
 
 typedef Window::WindowSize WindowSize;
 
-WindowSize **Window::getFullscreenSizes(int &n) const
+WindowSize *Window::getFullscreenSizes(int &n) const
 {
 	SDL_Rect **modes = SDL_ListModes(0, SDL_OPENGL | SDL_FULLSCREEN);
 
@@ -225,13 +225,12 @@ WindowSize **Window::getFullscreenSizes(int &n) const
 	for (int i = 0; modes[i]; i++)
 		n++;
 
-	WindowSize **sizes = new WindowSize*[n];
+	WindowSize *sizes = new WindowSize[n];
 
 	for (int i = 0; i < n; i++)
 	{
-		sizes[i] = new WindowSize;
-		sizes[i]->width = modes[i]->w;
-		sizes[i]->height = modes[i]->h;
+		WindowSize w = {modes[i]->w, modes[i]->h};
+		sizes[i] = w;
 	}
 	return sizes;
 }
@@ -251,7 +250,7 @@ bool Window::isCreated() const
 	return created;
 }
 
-void Window::setWindowTitle(std::string &title)
+void Window::setWindowTitle(const std::string &title)
 {
 	windowTitle = title;
 	SDL_WM_SetCaption(windowTitle.c_str(), 0);

+ 2 - 2
src/modules/window/sdl/Window.h

@@ -41,14 +41,14 @@ public:
 	void getWindow(int &width, int &height, WindowFlags &flags) const;
 
 	bool checkWindowSize(int width, int height, bool fullscreen) const;
-	WindowSize **getFullscreenSizes(int &n) const;
+	WindowSize *getFullscreenSizes(int &n) const;
 
 	int getWidth() const;
 	int getHeight() const;
 
 	bool isCreated() const;
 
-	void setWindowTitle(std::string &title);
+	void setWindowTitle(const std::string &title);
 	std::string getWindowTitle() const;
 
 	bool setIcon(love::image::ImageData *imgd);

+ 63 - 25
src/scripts/graphics.lua

@@ -1295,7 +1295,8 @@ do
 #define number float
 #define Image sampler2D
 #define extern uniform
-#define Texel texture2D]]
+#define Texel texture2D
+#define love_Canvases gl_FragData]]
 
 	local GLSL_UNIFORMS = [[
 #define ModelViewMatrix gl_ModelViewMatrix
@@ -1332,10 +1333,17 @@ void main() {
 
 		FOOTER = [[
 void main() {
-	// fix weird crashing issue in OSX when _tex0_ is unused within effect()
+	// fix crashing issue in OSX when _tex0_ is unused within effect()
 	float dummy = texture2D(_tex0_, vec2(.5)).r;
 	gl_FragColor = effect(VaryingColor, _tex0_, VaryingTexCoord.st, gl_FragCoord.xy);
 }]],
+
+		FOOTER_MULTI_CANVAS = [[
+void main() {
+	// fix crashing issue in OSX when _tex0_ is unused within effect()
+	float dummy = texture2D(_tex0_, vec2(.5)).r;
+	effects(VaryingColor, _tex0_, VaryingTexCoord.st, gl_FragCoord.xy);
+}]],
 	}
 
 	local function createVertexCode(vertexcode)
@@ -1349,36 +1357,62 @@ void main() {
 		return table_concat(vertexcodes, "\n")
 	end
 
-	local function createPixelCode(pixelcode)
+	local function createPixelCode(pixelcode, is_multicanvas)
 		local pixelcodes = {
 			GLSL_VERSION,
 			GLSL_SYNTAX, GLSL_PIXEL.HEADER, GLSL_UNIFORMS,
 			"#line 0",
 			pixelcode,
-			GLSL_PIXEL.FOOTER
+			is_multicanvas and GLSL_PIXEL.FOOTER_MULTI_CANVAS or GLSL_PIXEL.FOOTER,
 		}
 		return table_concat(pixelcodes, "\n")
 	end
 
-	function love.graphics._shaderCodeToGLSL(vertexcode, pixelcode)
-		if vertexcode then
-			local s = vertexcode:gsub("\r\n\t", " ")
-			s = s:gsub("(%w+)(%s+)%(", "%1(")
-			if s:match("vec4%s*effect%(") then
-				pixelcode = vertexcode -- first argument contains pixel shader code
+	local function isVertexCode(code)
+		return code:match("vec4%s*position%(") ~= nil
+	end
+
+	local function isPixelCode(code)
+		if code:match("vec4%s*effect%(") then
+			return true
+		elseif code:match("void%s*effects%(") then -- render to multiple canvases simultaniously
+			return true, true
+		else
+			return false
+		end
+	end
+
+	function love.graphics._shaderCodeToGLSL(arg1, arg2)
+		local vertexcode, pixelcode
+		local is_multicanvas = false -- whether pixel code has "effects" function instead of "effect"
+		
+		if arg1 then
+			local s = arg1:gsub("\r\n\t", " ") -- convert whitespace to spaces for parsing
+			s = s:gsub("(%w+)(%s+)%(", "%1(") -- convert "func ()" to "func()"
+
+			if isVertexCode(s) then
+				vertexcode = arg1 -- first arg contains vertex shader code
 			end
-			if not s:match("vec4%s*position%(") then
-				vertexcode = nil -- first argument doesn't contain vertex shader code
+
+			local ispixel, isMultiCanvas = isPixelCode(s)
+			if ispixel then
+				pixelcode = arg1 -- first arg contains pixel shader code
+				is_multicanvas = isMultiCanvas
 			end
 		end
-		if pixelcode then
-			local s = pixelcode:gsub("\r\n\t", " ")
-			s = s:gsub("(%w+)(%s+)%(", "%1(")
-			if s:match("vec4%s*position%(") then
-				vertexcode = pixelcode -- second argument contains vertex shader code
+		
+		if arg2 then
+			local s = arg2:gsub("\r\n\t", " ") -- convert whitespace to spaces for parsing
+			s = s:gsub("(%w+)(%s+)%(", "%1(") -- convert "func ()" to "func()"
+
+			if isVertexCode(s) then
+				vertexcode = arg2 -- second arg contains vertex shader code
 			end
-			if not s:match("vec4%s*effect%(") then
-				pixelcode = nil -- second argument doesn't contain pixel shader code
+
+			local ispixel, isMultiCanvas = isPixelCode(s)
+			if ispixel then
+				pixelcode = arg2 -- second arg contains pixel shader code
+				is_multicanvas = isMultiCanvas
 			end
 		end
 
@@ -1386,7 +1420,7 @@ void main() {
 			vertexcode = createVertexCode(vertexcode)
 		end
 		if pixelcode then
-			pixelcode = createPixelCode(pixelcode)
+			pixelcode = createPixelCode(pixelcode, is_multicanvas)
 		end
 
 		return vertexcode, pixelcode
@@ -1466,10 +1500,14 @@ void main() {
 	function love.graphics.newShader(vertexcode, pixelcode)
 		love.graphics.newShader = newShader
 
-		local shader = newShader(vertexcode, pixelcode)
-		local meta = getmetatable(shader)
-		meta.send = shader_dispatch_send
-		meta.sendBoolean = meta.sendFloat
-		return shader
+		local success, shader = pcall(love.graphics.newShader, vertexcode, pixelcode)
+		if success then
+			local meta = getmetatable(shader)
+			meta.send = shader_dispatch_send
+			meta.sendBoolean = meta.sendFloat
+			return shader
+		else
+			return error(shader, 2)
+		end
 	end
 end

+ 132 - 64
src/scripts/graphics.lua.h

@@ -6276,7 +6276,9 @@ const unsigned char graphics_lua[] =
 	0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x75, 0x6e, 0x69, 
 	0x66, 0x6f, 0x72, 0x6d, 0x0a,
 	0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x54, 0x65, 0x78, 0x65, 0x6c, 0x20, 0x74, 0x65, 0x78, 0x74, 
-	0x75, 0x72, 0x65, 0x32, 0x44, 0x5d, 0x5d, 0x0a,
+	0x75, 0x72, 0x65, 0x32, 0x44, 0x0a,
+	0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x43, 0x61, 0x6e, 0x76, 0x61, 
+	0x73, 0x65, 0x73, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x44, 0x61, 0x74, 0x61, 0x5d, 0x5d, 0x0a,
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x55, 0x4e, 0x49, 0x46, 0x4f, 0x52, 
 	0x4d, 0x53, 0x20, 0x3d, 0x20, 0x5b, 0x5b, 0x0a,
 	0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x56, 0x69, 0x65, 0x77, 0x4d, 
@@ -6333,11 +6335,10 @@ const unsigned char graphics_lua[] =
 	0x6f, 0x72, 0x20, 0x67, 0x6c, 0x5f, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x5d, 0x5d, 0x2c, 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,
-	0x09, 0x2f, 0x2f, 0x20, 0x66, 0x69, 0x78, 0x20, 0x77, 0x65, 0x69, 0x72, 0x64, 0x20, 0x63, 0x72, 0x61, 0x73, 
-	0x68, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x73, 0x73, 0x75, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x4f, 0x53, 0x58, 0x20, 
-	0x77, 0x68, 0x65, 0x6e, 0x20, 0x5f, 0x74, 0x65, 0x78, 0x30, 0x5f, 0x20, 0x69, 0x73, 0x20, 0x75, 0x6e, 0x75, 
-	0x73, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x20, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x28, 
-	0x29, 0x0a,
+	0x09, 0x2f, 0x2f, 0x20, 0x66, 0x69, 0x78, 0x20, 0x63, 0x72, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x69, 
+	0x73, 0x73, 0x75, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x4f, 0x53, 0x58, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x5f, 
+	0x74, 0x65, 0x78, 0x30, 0x5f, 0x20, 0x69, 0x73, 0x20, 0x75, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x20, 0x77, 0x69, 
+	0x74, 0x68, 0x69, 0x6e, 0x20, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x28, 0x29, 0x0a,
 	0x09, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x64, 0x75, 0x6d, 0x6d, 0x79, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 
 	0x74, 0x75, 0x72, 0x65, 0x32, 0x44, 0x28, 0x5f, 0x74, 0x65, 0x78, 0x30, 0x5f, 0x2c, 0x20, 0x76, 0x65, 0x63, 
 	0x32, 0x28, 0x2e, 0x35, 0x29, 0x29, 0x2e, 0x72, 0x3b, 0x0a,
@@ -6347,6 +6348,21 @@ const unsigned char graphics_lua[] =
 	0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x73, 0x74, 0x2c, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 
 	0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x78, 0x79, 0x29, 0x3b, 0x0a,
 	0x7d, 0x5d, 0x5d, 0x2c, 0x0a,
+	0x09, 0x09, 0x46, 0x4f, 0x4f, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x5f, 0x43, 0x41, 0x4e, 
+	0x56, 0x41, 0x53, 0x20, 0x3d, 0x20, 0x5b, 0x5b, 0x0a,
+	0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a,
+	0x09, 0x2f, 0x2f, 0x20, 0x66, 0x69, 0x78, 0x20, 0x63, 0x72, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x69, 
+	0x73, 0x73, 0x75, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x4f, 0x53, 0x58, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x5f, 
+	0x74, 0x65, 0x78, 0x30, 0x5f, 0x20, 0x69, 0x73, 0x20, 0x75, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x20, 0x77, 0x69, 
+	0x74, 0x68, 0x69, 0x6e, 0x20, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x28, 0x29, 0x0a,
+	0x09, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x64, 0x75, 0x6d, 0x6d, 0x79, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 
+	0x74, 0x75, 0x72, 0x65, 0x32, 0x44, 0x28, 0x5f, 0x74, 0x65, 0x78, 0x30, 0x5f, 0x2c, 0x20, 0x76, 0x65, 0x63, 
+	0x32, 0x28, 0x2e, 0x35, 0x29, 0x29, 0x2e, 0x72, 0x3b, 0x0a,
+	0x09, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x73, 0x28, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x43, 0x6f, 
+	0x6c, 0x6f, 0x72, 0x2c, 0x20, 0x5f, 0x74, 0x65, 0x78, 0x30, 0x5f, 0x2c, 0x20, 0x56, 0x61, 0x72, 0x79, 0x69, 
+	0x6e, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x73, 0x74, 0x2c, 0x20, 0x67, 0x6c, 0x5f, 
+	0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x78, 0x79, 0x29, 0x3b, 0x0a,
+	0x7d, 0x5d, 0x5d, 0x2c, 0x0a,
 	0x09, 0x7d, 0x0a,
 	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, 
@@ -6368,7 +6384,8 @@ const unsigned char graphics_lua[] =
 	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, 0x29, 0x0a,
+	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, 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,
@@ -6377,65 +6394,108 @@ const unsigned char graphics_lua[] =
 	0x4c, 0x53, 0x4c, 0x5f, 0x55, 0x4e, 0x49, 0x46, 0x4f, 0x52, 0x4d, 0x53, 0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x22, 0x23, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x30, 0x22, 0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x0a,
-	0x09, 0x09, 0x09, 0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x50, 0x49, 0x58, 0x45, 0x4c, 0x2e, 0x46, 0x4f, 0x4f, 0x54, 
-	0x45, 0x52, 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, 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, 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,
+	0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x6d, 0x61, 0x74, 0x63, 
+	0x68, 0x28, 0x22, 0x76, 0x65, 0x63, 0x34, 0x25, 0x73, 0x2a, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 
+	0x25, 0x28, 0x22, 0x29, 0x20, 0x7e, 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x0a,
+	0x09, 0x65, 0x6e, 0x64, 0x0a,
+	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 
+	0x50, 0x69, 0x78, 0x65, 0x6c, 0x43, 0x6f, 0x64, 0x65, 0x28, 0x63, 0x6f, 0x64, 0x65, 0x29, 0x0a,
+	0x09, 0x09, 0x69, 0x66, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x28, 0x22, 0x76, 
+	0x65, 0x63, 0x34, 0x25, 0x73, 0x2a, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x25, 0x28, 0x22, 0x29, 0x20, 0x74, 
+	0x68, 0x65, 0x6e, 0x0a,
+	0x09, 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a,
+	0x09, 0x09, 0x65, 0x6c, 0x73, 0x65, 0x69, 0x66, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x6d, 0x61, 0x74, 0x63, 
+	0x68, 0x28, 0x22, 0x76, 0x6f, 0x69, 0x64, 0x25, 0x73, 0x2a, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x73, 0x25, 
+	0x28, 0x22, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x2d, 0x2d, 0x20, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 
+	0x20, 0x74, 0x6f, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x63, 0x61, 0x6e, 0x76, 0x61, 
+	0x73, 0x65, 0x73, 0x20, 0x73, 0x69, 0x6d, 0x75, 0x6c, 0x74, 0x61, 0x6e, 0x69, 0x6f, 0x75, 0x73, 0x6c, 0x79, 0x0a,
+	0x09, 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x74, 0x72, 
+	0x75, 0x65, 0x0a,
+	0x09, 0x09, 0x65, 0x6c, 0x73, 0x65, 0x0a,
+	0x09, 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x0a,
+	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
+	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 
 	0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x5f, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x54, 
-	0x6f, 0x47, 0x4c, 0x53, 0x4c, 0x28, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x20, 
-	0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x29, 0x0a,
-	0x09, 0x09, 0x69, 0x66, 0x20, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x74, 0x68, 
-	0x65, 0x6e, 0x0a,
-	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x72, 0x74, 0x65, 
-	0x78, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x5c, 0x72, 0x5c, 0x6e, 0x5c, 0x74, 
-	0x22, 0x2c, 0x20, 0x22, 0x20, 0x22, 0x29, 0x0a,
+	0x6f, 0x47, 0x4c, 0x53, 0x4c, 0x28, 0x61, 0x72, 0x67, 0x31, 0x2c, 0x20, 0x61, 0x72, 0x67, 0x32, 0x29, 0x0a,
+	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 
+	0x2c, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x0a,
+	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x69, 0x73, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x61, 
+	0x6e, 0x76, 0x61, 0x73, 0x20, 0x3d, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x20, 0x2d, 0x2d, 0x20, 0x77, 0x68, 
+	0x65, 0x74, 0x68, 0x65, 0x72, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x68, 
+	0x61, 0x73, 0x20, 0x22, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x73, 0x22, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 
+	0x69, 0x6f, 0x6e, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x65, 0x61, 0x64, 0x20, 0x6f, 0x66, 0x20, 0x22, 0x65, 0x66, 
+	0x66, 0x65, 0x63, 0x74, 0x22, 0x0a,
+	0x09, 0x09, 0x0a,
+	0x09, 0x09, 0x69, 0x66, 0x20, 0x61, 0x72, 0x67, 0x31, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
+	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x20, 0x3d, 0x20, 0x61, 0x72, 0x67, 0x31, 0x3a, 
+	0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x5c, 0x72, 0x5c, 0x6e, 0x5c, 0x74, 0x22, 0x2c, 0x20, 0x22, 0x20, 0x22, 
+	0x29, 0x20, 0x2d, 0x2d, 0x20, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 
+	0x73, 0x70, 0x61, 0x63, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x20, 0x66, 0x6f, 
+	0x72, 0x20, 0x70, 0x61, 0x72, 0x73, 0x69, 0x6e, 0x67, 0x0a,
 	0x09, 0x09, 0x09, 0x73, 0x20, 0x3d, 0x20, 0x73, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x28, 0x25, 0x77, 
-	0x2b, 0x29, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x25, 0x28, 0x22, 0x2c, 0x20, 0x22, 0x25, 0x31, 0x28, 0x22, 0x29, 0x0a,
-	0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x73, 0x3a, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x28, 0x22, 0x76, 0x65, 0x63, 
-	0x34, 0x25, 0x73, 0x2a, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x25, 0x28, 0x22, 0x29, 0x20, 0x74, 0x68, 0x65, 
-	0x6e, 0x0a,
-	0x09, 0x09, 0x09, 0x09, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x76, 0x65, 
-	0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x2d, 0x2d, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 
-	0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x20, 
-	0x70, 0x69, 0x78, 0x65, 0x6c, 0x20, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x0a,
+	0x2b, 0x29, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x25, 0x28, 0x22, 0x2c, 0x20, 0x22, 0x25, 0x31, 0x28, 0x22, 0x29, 
+	0x20, 0x2d, 0x2d, 0x20, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x20, 0x22, 0x66, 0x75, 0x6e, 0x63, 0x20, 
+	0x28, 0x29, 0x22, 0x20, 0x74, 0x6f, 0x20, 0x22, 0x66, 0x75, 0x6e, 0x63, 0x28, 0x29, 0x22, 0x0a,
+	0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x69, 0x73, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x43, 0x6f, 0x64, 0x65, 
+	0x28, 0x73, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
+	0x09, 0x09, 0x09, 0x09, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x61, 
+	0x72, 0x67, 0x31, 0x20, 0x2d, 0x2d, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x61, 0x72, 0x67, 0x20, 0x63, 
+	0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x20, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x20, 0x73, 0x68, 0x61, 
+	0x64, 0x65, 0x72, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x0a,
 	0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
-	0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x3a, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x28, 
-	0x22, 0x76, 0x65, 0x63, 0x34, 0x25, 0x73, 0x2a, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x25, 0x28, 
-	0x22, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
-	0x09, 0x09, 0x09, 0x09, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x6e, 
-	0x69, 0x6c, 0x20, 0x2d, 0x2d, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 
-	0x6e, 0x74, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x6e, 0x27, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 
-	0x20, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x20, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x63, 0x6f, 0x64, 
-	0x65, 0x0a,
+	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x69, 0x73, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x2c, 0x20, 
+	0x69, 0x73, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x20, 0x3d, 0x20, 0x69, 0x73, 
+	0x50, 0x69, 0x78, 0x65, 0x6c, 0x43, 0x6f, 0x64, 0x65, 0x28, 0x73, 0x29, 0x0a,
+	0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x69, 0x73, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
+	0x09, 0x09, 0x09, 0x09, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x61, 0x72, 
+	0x67, 0x31, 0x20, 0x2d, 0x2d, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x61, 0x72, 0x67, 0x20, 0x63, 0x6f, 
+	0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x20, 0x73, 0x68, 0x61, 0x64, 0x65, 
+	0x72, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x0a,
+	0x09, 0x09, 0x09, 0x09, 0x69, 0x73, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 
+	0x20, 0x3d, 0x20, 0x69, 0x73, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x0a,
 	0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
-	0x09, 0x09, 0x69, 0x66, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x74, 0x68, 0x65, 
-	0x6e, 0x0a,
-	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x20, 0x3d, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 
-	0x63, 0x6f, 0x64, 0x65, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x5c, 0x72, 0x5c, 0x6e, 0x5c, 0x74, 0x22, 
-	0x2c, 0x20, 0x22, 0x20, 0x22, 0x29, 0x0a,
+	0x09, 0x09, 0x0a,
+	0x09, 0x09, 0x69, 0x66, 0x20, 0x61, 0x72, 0x67, 0x32, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
+	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x20, 0x3d, 0x20, 0x61, 0x72, 0x67, 0x32, 0x3a, 
+	0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x5c, 0x72, 0x5c, 0x6e, 0x5c, 0x74, 0x22, 0x2c, 0x20, 0x22, 0x20, 0x22, 
+	0x29, 0x20, 0x2d, 0x2d, 0x20, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 
+	0x73, 0x70, 0x61, 0x63, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x20, 0x66, 0x6f, 
+	0x72, 0x20, 0x70, 0x61, 0x72, 0x73, 0x69, 0x6e, 0x67, 0x0a,
 	0x09, 0x09, 0x09, 0x73, 0x20, 0x3d, 0x20, 0x73, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x28, 0x25, 0x77, 
-	0x2b, 0x29, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x25, 0x28, 0x22, 0x2c, 0x20, 0x22, 0x25, 0x31, 0x28, 0x22, 0x29, 0x0a,
-	0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x73, 0x3a, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x28, 0x22, 0x76, 0x65, 0x63, 
-	0x34, 0x25, 0x73, 0x2a, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x25, 0x28, 0x22, 0x29, 0x20, 0x74, 
-	0x68, 0x65, 0x6e, 0x0a,
-	0x09, 0x09, 0x09, 0x09, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x70, 
-	0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x2d, 0x2d, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 
-	0x20, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 
-	0x20, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x20, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x63, 0x6f, 0x64, 
-	0x65, 0x0a,
+	0x2b, 0x29, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x25, 0x28, 0x22, 0x2c, 0x20, 0x22, 0x25, 0x31, 0x28, 0x22, 0x29, 
+	0x20, 0x2d, 0x2d, 0x20, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x20, 0x22, 0x66, 0x75, 0x6e, 0x63, 0x20, 
+	0x28, 0x29, 0x22, 0x20, 0x74, 0x6f, 0x20, 0x22, 0x66, 0x75, 0x6e, 0x63, 0x28, 0x29, 0x22, 0x0a,
+	0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x69, 0x73, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x43, 0x6f, 0x64, 0x65, 
+	0x28, 0x73, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
+	0x09, 0x09, 0x09, 0x09, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x61, 
+	0x72, 0x67, 0x32, 0x20, 0x2d, 0x2d, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x61, 0x72, 0x67, 0x20, 
+	0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x20, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x20, 0x73, 0x68, 
+	0x61, 0x64, 0x65, 0x72, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x0a,
 	0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
-	0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x3a, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x28, 
-	0x22, 0x76, 0x65, 0x63, 0x34, 0x25, 0x73, 0x2a, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x25, 0x28, 0x22, 0x29, 
-	0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
-	0x09, 0x09, 0x09, 0x09, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x6e, 0x69, 
-	0x6c, 0x20, 0x2d, 0x2d, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 
-	0x6e, 0x74, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x6e, 0x27, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 
-	0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x20, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x0a,
+	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x69, 0x73, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x2c, 0x20, 
+	0x69, 0x73, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x20, 0x3d, 0x20, 0x69, 0x73, 
+	0x50, 0x69, 0x78, 0x65, 0x6c, 0x43, 0x6f, 0x64, 0x65, 0x28, 0x73, 0x29, 0x0a,
+	0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x69, 0x73, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
+	0x09, 0x09, 0x09, 0x09, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x61, 0x72, 
+	0x67, 0x32, 0x20, 0x2d, 0x2d, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x61, 0x72, 0x67, 0x20, 0x63, 
+	0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x20, 0x73, 0x68, 0x61, 0x64, 
+	0x65, 0x72, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x0a,
+	0x09, 0x09, 0x09, 0x09, 0x69, 0x73, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 
+	0x20, 0x3d, 0x20, 0x69, 0x73, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x0a,
 	0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x09, 0x69, 0x66, 0x20, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x74, 0x68, 
@@ -6448,7 +6508,8 @@ const unsigned char graphics_lua[] =
 	0x6e, 0x0a,
 	0x09, 0x09, 0x09, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 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, 0x29, 0x0a,
+	0x63, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x73, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x61, 0x6e, 0x76, 
+	0x61, 0x73, 0x29, 0x0a,
 	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 
 	0x65, 0x2c, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x0a,
@@ -6628,16 +6689,23 @@ const unsigned char graphics_lua[] =
 	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 
 	0x77, 0x53, 0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x3d, 0x20, 0x6e, 0x65, 0x77, 0x53, 0x68, 0x61, 0x64, 0x65, 
 	0x72, 0x0a,
-	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x3d, 0x20, 0x6e, 
-	0x65, 0x77, 0x53, 0x68, 0x61, 0x64, 0x65, 0x72, 0x28, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 
-	0x65, 0x2c, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x29, 0x0a,
-	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, 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,
-	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, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x73, 0x68, 0x61, 0x64, 0x65, 0x72, 0x0a,
+	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x2c, 0x20, 0x73, 
+	0x68, 0x61, 0x64, 0x65, 0x72, 0x20, 0x3d, 0x20, 0x70, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x6c, 0x6f, 0x76, 0x65, 
+	0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x53, 0x68, 0x61, 0x64, 0x65, 
+	0x72, 0x2c, 0x20, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x70, 0x69, 0x78, 
+	0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x29, 0x0a,
+	0x09, 0x09, 0x69, 0x66, 0x20, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x20, 0x74, 0x68, 0x65, 0x6e, 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,
+	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, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x65, 0x6e, 0x64, 0x0a,
 }; // [graphics.lua]

Some files were not shown because too many files changed in this diff