Browse Source

Merged in fbos

Bart van Strien 15 years ago
parent
commit
26d856a9ce

+ 4 - 0
.hgignore

@@ -7,6 +7,10 @@ glob:extra/reshax/Debug/
 glob:extra/reshax/resources.h
 glob:extra/reshax/resources.cpp
 glob:*.obj
+glob:*.o
+glob:*.dirstamp
+glob:*.m4
+glob:*.Po
 glob:*.dll
 glob:*.user
 glob:*.suo

+ 12 - 0
platform/macosx/love.xcodeproj/project.pbxproj

@@ -187,6 +187,8 @@
 		A98D914410507C97008E03F2 /* EncodedImageData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A98D914310507C97008E03F2 /* EncodedImageData.cpp */; };
 		A9B4BA9C1045937F001DBC80 /* ParticleSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9B4BA9A1045937F001DBC80 /* ParticleSystem.cpp */; };
 		A9B4BA9D1045937F001DBC80 /* wrap_ParticleSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9B4BA9B1045937F001DBC80 /* wrap_ParticleSystem.cpp */; };
+		A9BD60741226C988007DEC63 /* Framebuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9BD60701226C988007DEC63 /* Framebuffer.cpp */; };
+		A9BD60751226C988007DEC63 /* wrap_Framebuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9BD60721226C988007DEC63 /* wrap_Framebuffer.cpp */; };
 		A9CF0E8610B9EB1000E6F37E /* utf8.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9CF0E8510B9EB1000E6F37E /* utf8.cpp */; };
 		A9D307EA106635C3004FEDF8 /* physfs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9D307E9106635C3004FEDF8 /* physfs.framework */; };
 		A9D307F2106635D3004FEDF8 /* physfs.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A9D307E9106635C3004FEDF8 /* physfs.framework */; };
@@ -598,6 +600,10 @@
 		A9B4BA991045937F001DBC80 /* wrap_ParticleSystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_ParticleSystem.h; sourceTree = "<group>"; };
 		A9B4BA9A1045937F001DBC80 /* ParticleSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ParticleSystem.cpp; sourceTree = "<group>"; };
 		A9B4BA9B1045937F001DBC80 /* wrap_ParticleSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_ParticleSystem.cpp; sourceTree = "<group>"; };
+		A9BD60701226C988007DEC63 /* Framebuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Framebuffer.cpp; sourceTree = "<group>"; };
+		A9BD60711226C988007DEC63 /* Framebuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Framebuffer.h; sourceTree = "<group>"; };
+		A9BD60721226C988007DEC63 /* wrap_Framebuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Framebuffer.cpp; sourceTree = "<group>"; };
+		A9BD60731226C988007DEC63 /* wrap_Framebuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Framebuffer.h; sourceTree = "<group>"; };
 		A9BFAA851137C1CE005FE0AD /* ThreadModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThreadModule.h; sourceTree = "<group>"; };
 		A9CF0E8410B9EB1000E6F37E /* utf8.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = utf8.h; sourceTree = "<group>"; };
 		A9CF0E8510B9EB1000E6F37E /* utf8.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = utf8.cpp; sourceTree = "<group>"; };
@@ -985,6 +991,8 @@
 			children = (
 				A93E6A8710420AC2007D418B /* Font.cpp */,
 				A93E6A8810420AC2007D418B /* Font.h */,
+				A9BD60701226C988007DEC63 /* Framebuffer.cpp */,
+				A9BD60711226C988007DEC63 /* Framebuffer.h */,
 				A93E6A8910420AC2007D418B /* GLee.c */,
 				A93E6A8A10420AC2007D418B /* GLee.h */,
 				A93E6A8B10420AC2007D418B /* Glyph.cpp */,
@@ -1001,6 +1009,8 @@
 				A93E6A9610420AC2007D418B /* SpriteBatch.h */,
 				A93E6A9910420AC2007D418B /* wrap_Font.cpp */,
 				A93E6A9A10420AC2007D418B /* wrap_Font.h */,
+				A9BD60721226C988007DEC63 /* wrap_Framebuffer.cpp */,
+				A9BD60731226C988007DEC63 /* wrap_Framebuffer.h */,
 				A93E6A9B10420AC2007D418B /* wrap_Glyph.cpp */,
 				A93E6A9C10420AC2007D418B /* wrap_Glyph.h */,
 				A93E6A9D10420AC3007D418B /* wrap_Graphics.cpp */,
@@ -1660,6 +1670,8 @@
 				A946CE821172BCD7005E1462 /* wrap_EncodedImageData.cpp in Sources */,
 				A946D3BB117681BD005E1462 /* FontData.cpp in Sources */,
 				A946D3C611768D69005E1462 /* wrap_FontData.cpp in Sources */,
+				A9BD60741226C988007DEC63 /* Framebuffer.cpp in Sources */,
+				A9BD60751226C988007DEC63 /* wrap_Framebuffer.cpp in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 2 - 0
src/common/types.h

@@ -52,6 +52,7 @@ namespace love
 		GRAPHICS_PARTICLE_SYSTEM_ID,
 		GRAPHICS_SPRITE_BATCH_ID,
 		GRAPHICS_VERTEX_BUFFER_ID,
+		GRAPHICS_FRAMEBUFFER_ID,
 
 		// Image
 		IMAGE_IMAGE_DATA_ID,
@@ -116,6 +117,7 @@ namespace love
 	const bits GRAPHICS_PARTICLE_SYSTEM_T = (bits(1) << GRAPHICS_PARTICLE_SYSTEM_ID) | GRAPHICS_DRAWABLE_T;
 	const bits GRAPHICS_SPRITE_BATCH_T = (bits(1) << GRAPHICS_SPRITE_BATCH_ID) | GRAPHICS_DRAWABLE_T;
 	const bits GRAPHICS_VERTEX_BUFFER_T = (bits(1) << GRAPHICS_VERTEX_BUFFER_ID) | GRAPHICS_DRAWABLE_T;
+	const bits GRAPHICS_FRAMEBUFFER_T = (bits(1) << GRAPHICS_FRAMEBUFFER_ID) | GRAPHICS_DRAWABLE_T;
 
 	// Image.
 	const bits IMAGE_IMAGE_DATA_T = (bits(1) << IMAGE_IMAGE_DATA_ID) | DATA_T;

+ 324 - 0
src/modules/graphics/opengl/Framebuffer.cpp

@@ -0,0 +1,324 @@
+#include "Framebuffer.h"
+#include <common/Matrix.h>
+
+#include <string.h>
+
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <vector>
+#include <algorithm>
+#include <iterator>
+using namespace std;
+
+namespace {
+
+	// functions to get opengl capabilities at runtime
+	vector<string> tokenize(const string& str)
+	{
+		vector<string> tokens;
+
+		istringstream iss( str );
+		copy(istream_iterator<string>(iss), istream_iterator<string>(),
+				back_inserter< vector<string> >(tokens));
+		return tokens;
+	}
+
+	float getOpenGLVersionNumber()
+	{
+		vector<string> tokens = tokenize( (const char*)glGetString(GL_VERSION) );
+		stringstream toNumber( tokens.at(0) );
+
+		double version;
+		toNumber >> version;
+		return version;
+	}
+
+	bool hasFramebufferExtension()
+	{
+		vector<string> ext = tokenize( (const char*)glGetString(GL_EXTENSIONS) );
+		return find(ext.begin(), ext.end(), "GL_EXT_framebuffer_object") != ext.end();
+	}
+
+
+	// strategy for fbo creation, interchangable at runtime:
+	// none, opengl >= 3.0, extensions
+	struct FramebufferStrategy {
+		/// create a new framebuffer, depthbuffer and texture
+		/**
+		 * @param[out] framebuffer Framebuffer name
+		 * @param[out] depthbuffer Depthbuffer name
+		 * @param[out] img         Texture name
+		 * @param[in]  width       Width of framebuffer
+		 * @param[in]  height      Height of framebuffer
+		 * @return Creation status
+		 */
+		virtual GLenum createFBO(GLuint& framebuffer, GLuint& depthbuffer, GLuint& img, int width, int height)
+		{ return GL_FRAMEBUFFER_UNSUPPORTED; }
+		/// remove objects
+		/**
+		 * @param[in] framebuffer Framebuffer name
+		 * @param[in] depthbuffer Depthbuffer name
+		 * @param[in] img         Texture name
+		 */
+		virtual void deleteFBO(GLuint framebuffer, GLuint depthbuffer, GLuint img) {}
+		virtual void bindFBO(GLuint framebuffer) {}
+	};
+
+#ifdef GL_VERSION_3_0
+	struct FramebufferStrategyGL3 : public FramebufferStrategy {
+		virtual GLenum createFBO(GLuint& framebuffer, GLuint& depthbuffer, GLuint& img, int width, int height)
+		{
+			// generate depth buffer
+			glGenRenderbuffers(1, &depthbuffer);
+			glBindRenderbuffer(GL_RENDERBUFFER, depthbuffer);
+			glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
+			glBindRenderbuffer(GL_RENDERBUFFER, 0);
+
+			// generate texture save target
+			glGenTextures(1, &img);
+			glBindTexture(GL_TEXTURE_2D, img);
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height,
+					0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+			glBindTexture(GL_TEXTURE_2D, 0);
+
+			// create framebuffer
+			glGenFramebuffers(1, &framebuffer);
+			glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+					GL_TEXTURE_2D, img, 0);
+			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+					GL_RENDERBUFFER, depthbuffer);
+			GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+
+			// unbind buffers and texture
+			glBindFramebuffer(GL_FRAMEBUFFER, 0);
+			return status;
+		}
+		virtual void deleteFBO(GLuint framebuffer, GLuint depthbuffer, GLuint img)
+		{
+			glDeleteTextures(1, &img);
+			glDeleteRenderbuffers(1, &depthbuffer);
+			glDeleteFramebuffers(1, &framebuffer);
+		}
+
+		virtual void bindFBO(GLuint framebuffer)
+		{
+			glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
+		}
+	};
+#endif
+
+	struct FramebufferStrategyEXT : public FramebufferStrategy {
+
+		virtual GLenum createFBO(GLuint& framebuffer, GLuint& depthbuffer, GLuint& img, int width, int height)
+		{
+			// generate depth buffer
+			glGenRenderbuffersEXT(1, &depthbuffer);
+			glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthbuffer);
+			glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT16, width, height);
+			glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
+
+			// generate texture save target
+			glGenTextures(1, &img);
+			glBindTexture(GL_TEXTURE_2D, img);
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height,
+					0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+			glBindTexture(GL_TEXTURE_2D, 0);
+
+			// create framebuffer
+			glGenFramebuffersEXT(1, &framebuffer);
+			glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer);
+			glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+					GL_TEXTURE_2D, img, 0);
+			glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
+					GL_RENDERBUFFER_EXT, depthbuffer);
+			GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
+
+			// unbind buffers and texture
+			glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+			return status;
+		}
+
+		virtual void deleteFBO(GLuint framebuffer, GLuint depthbuffer, GLuint img)
+		{
+			glDeleteTextures(1, &img);
+			glDeleteRenderbuffersEXT(1, &depthbuffer);
+			glDeleteFramebuffersEXT(1, &framebuffer);
+		}
+
+		virtual void bindFBO(GLuint framebuffer)
+		{
+			glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer);
+		}
+	};
+
+	FramebufferStrategy* strategy = NULL;
+
+	FramebufferStrategy    strategyNone;
+#ifdef GL_VERSION_3_0
+	FramebufferStrategyGL3 strategyGL3;
+#endif
+	FramebufferStrategyEXT strategyEXT;
+
+};
+
+namespace love
+{
+namespace graphics
+{
+namespace opengl
+{
+	Framebuffer* Framebuffer::current = NULL;
+
+	Framebuffer::Framebuffer(int width, int height) :
+		width(width), height(height)
+	{
+
+		// world coordinates
+		vertices[0].x = 0;     vertices[0].y = 0;
+		vertices[1].x = 0;     vertices[1].y = height;
+		vertices[2].x = width; vertices[2].y = height;
+		vertices[3].x = width; vertices[3].y = 0;
+
+		// texture coordinates
+		vertices[0].s = 0;     vertices[0].t = 1;
+		vertices[1].s = 0;     vertices[1].t = 0;
+		vertices[2].s = 1;     vertices[2].t = 0;
+		vertices[3].s = 1;     vertices[3].t = 1;
+
+		if (!strategy) {
+#ifdef GL_VERSION_3_0
+			if (getOpenGLVersionNumber() >= 3.0)
+				strategy = &strategyGL3;
+			else if (hasFramebufferExtension())
+#else
+			if (hasFramebufferExtension())
+#endif
+			  strategy = &strategyEXT;
+			else
+			  strategy = &strategyNone;
+			
+		}
+
+		loadVolatile();
+	}
+
+	Framebuffer::~Framebuffer()
+	{
+		// reset framebuffer if still using this one
+		if (current == this)
+			stopGrab();
+
+		unloadVolatile();
+	}
+
+	void Framebuffer::bindDefaultBuffer()
+	{
+		if (current != NULL)
+			current->stopGrab();
+	}
+
+	void Framebuffer::startGrab()
+	{
+		// already grabbing
+		if (current == this)
+			return;
+
+		// cleanup after previous fbo
+		if (current != NULL)
+			glPopAttrib();
+
+		// bind buffer and clear screen
+		glPushAttrib(GL_VIEWPORT_BIT | GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		strategy->bindFBO(fbo);
+		glClearColor(.0f, .0f, .0f, .0f);
+		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+		glViewport(0, 0, width, height);
+
+		// indicate we are using this fbo
+		current = this;
+	}
+
+	void Framebuffer::stopGrab()
+	{
+		// i am not grabbing. leave me alone
+		if (current != this)
+			return;
+
+		// bind default
+		strategy->bindFBO( 0 );
+		glPopAttrib();
+		current = NULL;
+	}
+
+	void Framebuffer::draw(float x, float y, float angle, float sx, float sy, float ox, float oy) const
+	{
+		static Matrix t;
+		t.setTransformation(x, y, angle, sx, sy, ox, oy);
+
+		glPushMatrix();
+		glMultMatrixf((const GLfloat*)t.getElements());
+
+		glBindTexture(GL_TEXTURE_2D, img);
+
+		glEnableClientState(GL_VERTEX_ARRAY);
+		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+		glVertexPointer(2, GL_FLOAT, sizeof(vertex), (GLvoid*)&vertices[0].x);
+		glTexCoordPointer(2, GL_FLOAT, sizeof(vertex), (GLvoid*)&vertices[0].s);
+		glDrawArrays(GL_QUADS, 0, 4);
+		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+		glDisableClientState(GL_VERTEX_ARRAY);
+
+		glPopMatrix();
+	}
+
+	love::image::ImageData * Framebuffer::getImageData(love::image::Image * image)
+	{
+		int row = 4 * width;
+		int size = row * height;
+
+		// see Graphics::newScreenshot. OpenGL reads from lower-left,
+		// but we need the pixels from upper-left.
+		GLubyte* pixels = new GLubyte[size];
+		GLubyte* screenshot = new GLubyte[size];
+
+		strategy->bindFBO( fbo );
+		glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+		if (current)
+			strategy->bindFBO( current->fbo );
+		else
+			strategy->bindFBO( 0 );
+
+		GLubyte* src = pixels - row; // second line of buffer
+		GLubyte* dst = screenshot + size; // end of buffer
+
+		for (int i = 0; i < height; ++i)
+			memcpy(dst -= row, src += row, row);
+
+		love::image::ImageData * img = image->newImageData(width, height, (void*)screenshot);
+
+		delete[] screenshot;
+		delete[] pixels;
+
+		return img;
+	}
+	
+	bool Framebuffer::loadVolatile()
+	{
+		status = strategy->createFBO(fbo, depthbuffer, img, width, height);
+		return (status == GL_FRAMEBUFFER_COMPLETE);
+	}
+	
+	void Framebuffer::unloadVolatile()
+	{
+		strategy->deleteFBO(fbo, depthbuffer, img);
+	}
+
+} // opengl
+} // graphics
+} // love

+ 55 - 0
src/modules/graphics/opengl/Framebuffer.h

@@ -0,0 +1,55 @@
+#ifndef LOVE_GRAPHICS_FRAMEBUFFER_H
+#define LOVE_GRAPHICS_FRAMEBUFFER_H
+
+#include <graphics/Drawable.h>
+#include <graphics/Volatile.h>
+#include <image/Image.h>
+#include <image/ImageData.h>
+#include <common/math.h>
+#include <map>
+#include "GLee.h"
+
+namespace love
+{
+namespace graphics
+{
+namespace opengl
+{
+
+	class Framebuffer : public Drawable, public Volatile
+	{
+	public:
+		Framebuffer(int width, int height);
+		virtual ~Framebuffer();
+
+		unsigned int getStatus() const { return status; }
+
+		static Framebuffer* current;
+		static void bindDefaultBuffer();
+
+		void startGrab();
+		void stopGrab();
+
+		virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy) const;
+		love::image::ImageData * getImageData(love::image::Image * image);
+		
+		bool loadVolatile();
+		void unloadVolatile();
+
+	private:
+		GLsizei width;
+		GLsizei height;
+		GLuint fbo;
+		GLuint depthbuffer;
+		GLuint img;
+
+		vertex vertices[4];
+
+		GLenum status;
+	};
+
+} // opengl
+} // graphics
+} // love
+
+#endif // LOVE_GRAPHICS_FRAMEBUFFER_H

+ 5 - 0
src/modules/graphics/opengl/Graphics.cpp

@@ -477,6 +477,11 @@ namespace opengl
 		return new ParticleSystem(image, size);
 	}
 
+	Framebuffer * Graphics::newFramebuffer(int width, int height)
+	{
+		return new Framebuffer(width, height);
+	}
+
 	void Graphics::setColor(Color c)
 	{
 		glColor4ubv(&c.r);

+ 28 - 25
src/modules/graphics/opengl/Graphics.h

@@ -1,14 +1,14 @@
 /**
 * Copyright (c) 2006-2010 LOVE Development Team
-* 
+*
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
-* 
+*
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
-* 
+*
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
@@ -43,6 +43,7 @@
 #include "Quad.h"
 #include "SpriteBatch.h"
 #include "ParticleSystem.h"
+#include "Framebuffer.h"
 
 namespace love
 {
@@ -95,13 +96,13 @@ namespace opengl
 		// Default values.
 		DisplayState()
 		{
-			color.r = 255; 
-			color.g = 255; 
-			color.b = 255; 
+			color.r = 255;
+			color.g = 255;
+			color.b = 255;
 			color.a = 255;
-			backgroundColor.r = 0; 
-			backgroundColor.g = 0; 
-			backgroundColor.b = 0; 
+			backgroundColor.r = 0;
+			backgroundColor.g = 0;
+			backgroundColor.b = 0;
 			backgroundColor.a = 255;
 			blendMode = Graphics::BLEND_ALPHA;
 			colorMode = Graphics::COLOR_MODULATE;
@@ -126,7 +127,7 @@ namespace opengl
 
 		Graphics();
 		virtual ~Graphics();
-		
+
 		// Implements Module.
 		const char * getName() const;
 
@@ -165,18 +166,18 @@ namespace opengl
 		* when the game reloads.
 		**/
 		void reset();
-			
+
 		/**
 		* Clears the screen.
 		**/
 		void clear();
 
 		/**
-		* Flips buffers. (Rendered geometry is 
+		* Flips buffers. (Rendered geometry is
 		* presented on screen).
 		**/
 		void present();
-		
+
 		/**
 		* Sets the window's icon.
 		**/
@@ -207,13 +208,13 @@ namespace opengl
 		/**
 		* This native Lua function gets available modes
 		* from SDL and returns them as a table on the following format:
-		* 
-		* { 
-		*   { width = 800, height = 600 }, 
+		*
+		* {
+		*   { width = 800, height = 600 },
 		*   { width = 1024, height = 768 },
 		*   ...
 		* }
-		* 
+		*
 		* Only fullscreen modes are returned here, as all
 		* window sizes are supported (normally).
 		**/
@@ -245,7 +246,7 @@ namespace opengl
 		**/
 		Image * newImage(love::filesystem::File * file);
 		Image * newImage(love::image::ImageData * data);
-		
+
 		/**
 		* Creates a Frame
 		**/
@@ -255,11 +256,13 @@ namespace opengl
 		* Creates a Font object.
 		**/
 		Font * newFont(love::font::FontData * data);
-		
+
 		SpriteBatch * newSpriteBatch(Image * image, int size, int usage);
 
 		ParticleSystem * newParticleSystem(Image * image, int size);
-			
+
+		Framebuffer * newFramebuffer(int width, int height);
+
 		/**
 		* Sets the foreground color.
 		**/
@@ -271,7 +274,7 @@ namespace opengl
 		Color getColor();
 
 		/**
-		* Sets the background Color. 
+		* Sets the background Color.
 		**/
 		void setBackgroundColor(Color c);
 
@@ -383,7 +386,7 @@ namespace opengl
 		PointStyle getPointStyle();
 
 		/**
-		* Gets the maximum point size supported. 
+		* Gets the maximum point size supported.
 		* This may vary from computer to computer.
 		**/
 		int getMaxPointSize();
@@ -406,7 +409,7 @@ namespace opengl
 		void print(const char * str, float x, float y , float angle);
 
 		/**
-		* Draws text at the specified coordinates, with rotation and 
+		* Draws text at the specified coordinates, with rotation and
 		* scaling.
 		* @param x The x-coordinate.
 		* @param y The y-coordinate.
@@ -416,7 +419,7 @@ namespace opengl
 		void print(const char * str, float x, float y , float angle, float s);
 
 		/**
-		* Draws text at the specified coordinates, with rotation and 
+		* Draws text at the specified coordinates, with rotation and
 		* scaling along both axes.
 		* @param x The x-coordinate.
 		* @param y The y-coordinate.
@@ -452,7 +455,7 @@ namespace opengl
 		* @param y2 Second y-coordinate.
 		**/
 		void line(float x1, float y1, float x2, float y2);
-		
+
 		/**
 		* Draws a series of lines connecting the given vertices.
 		* @param ... Vertex components (x1, y1, x2, y2, etc.)

+ 57 - 0
src/modules/graphics/opengl/wrap_Framebuffer.cpp

@@ -0,0 +1,57 @@
+#include "wrap_Framebuffer.h"
+
+namespace love
+{
+namespace graphics
+{
+namespace opengl
+{
+	Framebuffer * luax_checkfbo(lua_State * L, int idx)
+	{
+		return luax_checktype<Framebuffer>(L, idx, "Framebuffer", GRAPHICS_FRAMEBUFFER_T);
+	}
+
+	int w_Framebuffer_renderTo(lua_State * L)
+	{
+		// As startGrab() clears the framebuffer, better not allow
+		// grabbing inside another grabbing
+		if (Framebuffer::current != NULL) {
+			Framebuffer::bindDefaultBuffer();
+			return luaL_error(L, "Current render target not the default framebuffer!");
+		}
+
+		Framebuffer * fbo = luax_checkfbo(L, 1);
+		if (!lua_isfunction(L, 2))
+			return luaL_error(L, "Need a function to render to fbo");
+
+		fbo->startGrab();
+		lua_settop(L, 2); // make sure the function is on top of the stack
+		lua_call(L, 0, 0);
+		fbo->stopGrab();
+
+		return 0;
+	}
+
+	int w_Framebuffer_getImageData(lua_State * L)
+	{
+		Framebuffer * fbo = luax_checkfbo(L, 1);
+		love::image::Image * image = luax_getmodule<love::image::Image>(L, "image", MODULE_IMAGE_T);
+		love::image::ImageData * img = fbo->getImageData( image );
+		luax_newtype(L, "ImageData", IMAGE_IMAGE_DATA_T, (void *)img);
+		return 1;
+	}
+
+	static const luaL_Reg functions[] = {
+		{ "renderTo", w_Framebuffer_renderTo },
+		{ "getImageData", w_Framebuffer_getImageData },
+		{ 0, 0 }
+	};
+
+	int luaopen_framebuffer(lua_State * L)
+	{
+		return luax_register_type(L, "Framebuffer", functions);
+	}
+
+} // opengl
+} // graphics
+} // love

+ 24 - 0
src/modules/graphics/opengl/wrap_Framebuffer.h

@@ -0,0 +1,24 @@
+#ifndef LOVE_GRAPHICS_OPENGL_WRAP_FBO_H
+#define LOVE_GRAPHICS_OPENGL_WRAP_FBO_H
+
+// LOVE
+#include <common/runtime.h>
+#include "Framebuffer.h"
+
+namespace love
+{
+namespace graphics
+{
+namespace opengl
+{
+	//see Framebuffer.h
+	Framebuffer * luax_checkfbo(lua_State * L, int idx);
+	int w_Framebuffer_renderTo(lua_State * L);
+	int w_Framebuffer_getImageData(lua_State * L);
+	int luaopen_framebuffer(lua_State * L);
+
+} // opengl
+} // graphics
+} // love
+
+#endif // LOVE_GRAPHICS_OPENGL_WRAP_FBO_H

+ 60 - 2
src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -84,7 +84,7 @@ namespace opengl
 		instance->setIcon(image);
 		return 0;
 	}
-	
+
 	int w_setCaption(lua_State * L)
 	{
 		const char * str = luaL_checkstring(L, 1);
@@ -292,6 +292,46 @@ namespace opengl
 		return 1;
 	}
 
+	int w_newFramebuffer(lua_State * L)
+	{
+		// check if width and height are given. else default to screen dimensions.
+		int width  = luaL_optint(L, 1, instance->getWidth());
+		int height = luaL_optint(L, 2, instance->getHeight());
+		glGetError(); // clear opengl error flag
+		Framebuffer * framebuffer = instance->newFramebuffer(width, height);
+
+		//and there we go with the status... still disliked
+		if (framebuffer->getStatus() != GL_FRAMEBUFFER_COMPLETE) {
+			switch (framebuffer->getStatus()) {
+				case GL_FRAMEBUFFER_UNSUPPORTED:
+					return luaL_error(L, "Cannot create Framebuffer: "
+							"Not supported by your OpenGL implementation");
+				// remaining error codes are highly unlikely:
+				case GL_FRAMEBUFFER_UNDEFINED:
+				case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
+				case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
+				case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
+				case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
+				case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
+					return luaL_error(L, "Cannot create Framebuffer: "
+							"Error in implementation (please inform the love devs)");
+				default:
+					// my intel hda card wrongly returns 0 to glCheckFramebufferStatus() but sets
+					// no error flag. I think it meant to return GL_FRAMEBUFFER_UNSUPPORTED, but who
+					// knows.
+					if (glGetError() == GL_NO_ERROR)
+						return luaL_error(L, "Cannot create Framebuffer: "
+								"May not be supported by your OpenGL implementation.");
+					// the remaining error is an indication of a serious fuckup since it should
+					// only be returned if glCheckFramebufferStatus() was called with the wrong
+					// arguments.
+					return luaL_error(L, "Cannot create Framebuffer: Aliens did it (OpenGL error code: %d)", glGetError());
+			}
+		}
+		luax_newtype(L, "Framebuffer", GRAPHICS_FRAMEBUFFER_T, (void*)framebuffer);
+		return 1;
+	}
+
 	int w_setColor(lua_State * L)
 	{
 		Color c;
@@ -567,6 +607,21 @@ namespace opengl
 		return 1;
 	}
 
+	int w_setRenderTarget(lua_State * L)
+	{
+		// called with nil or none -> reset to default buffer
+		if (lua_isnoneornil(L,1)) {
+			Framebuffer::bindDefaultBuffer();
+			return 0;
+		}
+
+		Framebuffer * fbo = luax_checkfbo(L, 1);
+		// this unbinds the previous fbo
+		fbo->startGrab();
+
+		return 0;
+	}
+
 	/**
 	* Draws an Image at the specified coordinates, with rotation and
 	* scaling along both axes.
@@ -829,6 +884,7 @@ namespace opengl
 		{ "newImageFont", w_newImageFont },
 		{ "newSpriteBatch", w_newSpriteBatch },
 		{ "newParticleSystem", w_newParticleSystem },
+		{ "newFramebuffer", w_newFramebuffer },
 
 		{ "setColor", w_setColor },
 		{ "getColor", w_getColor },
@@ -856,6 +912,7 @@ namespace opengl
 		{ "getPointStyle", w_getPointStyle },
 		{ "getMaxPointSize", w_getMaxPointSize },
 		{ "newScreenshot", w_newScreenshot },
+		{ "setRenderTarget", w_setRenderTarget },
 
 		{ "draw", w_draw },
 		{ "drawq", w_drawq },
@@ -866,7 +923,7 @@ namespace opengl
 
 		{ "setCaption", w_setCaption },
 		{ "getCaption", w_getCaption },
-		
+
 		{ "setIcon", w_setIcon },
 
 		{ "getWidth", w_getWidth },
@@ -906,6 +963,7 @@ namespace opengl
 		luaopen_frame,
 		luaopen_spritebatch,
 		luaopen_particlesystem,
+		luaopen_framebuffer,
 		0
 	};
 

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

@@ -1,14 +1,14 @@
 /**
 * Copyright (c) 2006-2010 LOVE Development Team
-* 
+*
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
-* 
+*
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
-* 
+*
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
@@ -28,6 +28,7 @@
 #include "wrap_Quad.h"
 #include "wrap_SpriteBatch.h"
 #include "wrap_ParticleSystem.h"
+#include "wrap_Framebuffer.h"
 #include "Graphics.h"
 
 namespace love
@@ -58,6 +59,7 @@ namespace opengl
 	int w_newImageFont(lua_State * L);
 	int w_newSpriteBatch(lua_State * L);
 	int w_newParticleSystem(lua_State * L);
+	int w_newFramebuffer(lua_State * L); // commetns in function
 	int w_setColor(lua_State * L);
 	int w_getColor(lua_State * L);
 	int w_setBackgroundColor(lua_State * L);
@@ -82,6 +84,7 @@ namespace opengl
 	int w_getPointStyle(lua_State * L);
 	int w_getMaxPointSize(lua_State * L);
 	int w_newScreenshot(lua_State * L);
+	int w_setRenderTarget(lua_State * L);
 	int w_draw(lua_State * L);
 	int w_drawq(lua_State * L);
 	int w_drawTest(lua_State * L);

+ 2 - 0
src/scripts/boot.lua

@@ -787,6 +787,8 @@ function love.errhand(msg)
 		return
 	end
 
+	love.graphics.setRenderTarget()
+
 	-- Load.
 	love.graphics.setScissor()
 	love.graphics.setBackgroundColor(89, 157, 220)

+ 91 - 89
src/scripts/boot.lua.h

@@ -1157,102 +1157,104 @@ const unsigned char boot_lua[] =
 	0x74,0x20,0x6C,0x6F,0x76,0x65,0x2E,0x67,0x72,0x61,0x70,0x68,0x69,0x63,0x73,
 	0x2E,0x69,0x73,0x43,0x72,0x65,0x61,0x74,0x65,0x64,0x28,0x29,0x20,0x74,0x68,
 	0x65,0x6E,0x0D,0x0A,0x09,0x09,0x72,0x65,0x74,0x75,0x72,0x6E,0x0D,0x0A,0x09,
-	0x65,0x6E,0x64,0x0D,0x0A,0x0D,0x0A,0x09,0x2D,0x2D,0x20,0x4C,0x6F,0x61,0x64,
-	0x2E,0x0D,0x0A,0x09,0x6C,0x6F,0x76,0x65,0x2E,0x67,0x72,0x61,0x70,0x68,0x69,
-	0x63,0x73,0x2E,0x73,0x65,0x74,0x53,0x63,0x69,0x73,0x73,0x6F,0x72,0x28,0x29,
-	0x0D,0x0A,0x09,0x6C,0x6F,0x76,0x65,0x2E,0x67,0x72,0x61,0x70,0x68,0x69,0x63,
-	0x73,0x2E,0x73,0x65,0x74,0x42,0x61,0x63,0x6B,0x67,0x72,0x6F,0x75,0x6E,0x64,
-	0x43,0x6F,0x6C,0x6F,0x72,0x28,0x38,0x39,0x2C,0x20,0x31,0x35,0x37,0x2C,0x20,
-	0x32,0x32,0x30,0x29,0x0D,0x0A,0x09,0x6C,0x6F,0x63,0x61,0x6C,0x20,0x66,0x6F,
-	0x6E,0x74,0x20,0x3D,0x20,0x6C,0x6F,0x76,0x65,0x2E,0x67,0x72,0x61,0x70,0x68,
-	0x69,0x63,0x73,0x2E,0x6E,0x65,0x77,0x46,0x6F,0x6E,0x74,0x28,0x6C,0x6F,0x76,
-	0x65,0x2E,0x5F,0x76,0x65,0x72,0x61,0x5F,0x74,0x74,0x66,0x2C,0x20,0x31,0x34,
-	0x29,0x0D,0x0A,0x09,0x6C,0x6F,0x76,0x65,0x2E,0x67,0x72,0x61,0x70,0x68,0x69,
-	0x63,0x73,0x2E,0x73,0x65,0x74,0x46,0x6F,0x6E,0x74,0x28,0x66,0x6F,0x6E,0x74,
-	0x29,0x0D,0x0A,0x0D,0x0A,0x09,0x6C,0x6F,0x76,0x65,0x2E,0x67,0x72,0x61,0x70,
-	0x68,0x69,0x63,0x73,0x2E,0x73,0x65,0x74,0x43,0x6F,0x6C,0x6F,0x72,0x28,0x32,
-	0x35,0x35,0x2C,0x20,0x32,0x35,0x35,0x2C,0x20,0x32,0x35,0x35,0x2C,0x20,0x32,
-	0x35,0x35,0x29,0x0D,0x0A,0x0D,0x0A,0x09,0x6C,0x6F,0x63,0x61,0x6C,0x20,0x74,
-	0x72,0x61,0x63,0x65,0x20,0x3D,0x20,0x64,0x65,0x62,0x75,0x67,0x2E,0x74,0x72,
-	0x61,0x63,0x65,0x62,0x61,0x63,0x6B,0x28,0x29,0x0D,0x0A,0x0D,0x0A,0x09,0x6C,
-	0x6F,0x76,0x65,0x2E,0x67,0x72,0x61,0x70,0x68,0x69,0x63,0x73,0x2E,0x63,0x6C,
-	0x65,0x61,0x72,0x28,0x29,0x0D,0x0A,0x0D,0x0A,0x09,0x6C,0x6F,0x63,0x61,0x6C,
-	0x20,0x65,0x72,0x72,0x20,0x3D,0x20,0x7B,0x7D,0x0D,0x0A,0x0D,0x0A,0x09,0x74,
-	0x61,0x62,0x6C,0x65,0x2E,0x69,0x6E,0x73,0x65,0x72,0x74,0x28,0x65,0x72,0x72,
-	0x2C,0x20,0x22,0x45,0x72,0x72,0x6F,0x72,0x5C,0x6E,0x22,0x29,0x0D,0x0A,0x09,
-	0x74,0x61,0x62,0x6C,0x65,0x2E,0x69,0x6E,0x73,0x65,0x72,0x74,0x28,0x65,0x72,
-	0x72,0x2C,0x20,0x6D,0x73,0x67,0x2E,0x2E,0x22,0x5C,0x6E,0x5C,0x6E,0x22,0x29,
-	0x0D,0x0A,0x0D,0x0A,0x09,0x66,0x6F,0x72,0x20,0x6C,0x20,0x69,0x6E,0x20,0x73,
-	0x74,0x72,0x69,0x6E,0x67,0x2E,0x67,0x6D,0x61,0x74,0x63,0x68,0x28,0x74,0x72,
-	0x61,0x63,0x65,0x2C,0x20,0x22,0x28,0x2E,0x2D,0x29,0x5C,0x6E,0x22,0x29,0x20,
-	0x64,0x6F,0x0D,0x0A,0x09,0x09,0x69,0x66,0x20,0x6E,0x6F,0x74,0x20,0x73,0x74,
-	0x72,0x69,0x6E,0x67,0x2E,0x6D,0x61,0x74,0x63,0x68,0x28,0x6C,0x2C,0x20,0x22,
-	0x62,0x6F,0x6F,0x74,0x2E,0x6C,0x75,0x61,0x22,0x29,0x20,0x74,0x68,0x65,0x6E,
-	0x0D,0x0A,0x09,0x09,0x09,0x6C,0x20,0x3D,0x20,0x73,0x74,0x72,0x69,0x6E,0x67,
-	0x2E,0x67,0x73,0x75,0x62,0x28,0x6C,0x2C,0x20,0x22,0x73,0x74,0x61,0x63,0x6B,
-	0x20,0x74,0x72,0x61,0x63,0x65,0x62,0x61,0x63,0x6B,0x3A,0x22,0x2C,0x20,0x22,
-	0x54,0x72,0x61,0x63,0x65,0x62,0x61,0x63,0x6B,0x5C,0x6E,0x22,0x29,0x0D,0x0A,
-	0x09,0x09,0x09,0x74,0x61,0x62,0x6C,0x65,0x2E,0x69,0x6E,0x73,0x65,0x72,0x74,
-	0x28,0x65,0x72,0x72,0x2C,0x20,0x6C,0x29,0x0D,0x0A,0x09,0x09,0x65,0x6E,0x64,
-	0x0D,0x0A,0x09,0x65,0x6E,0x64,0x0D,0x0A,0x0D,0x0A,0x09,0x6C,0x6F,0x63,0x61,
-	0x6C,0x20,0x70,0x20,0x3D,0x20,0x74,0x61,0x62,0x6C,0x65,0x2E,0x63,0x6F,0x6E,
-	0x63,0x61,0x74,0x28,0x65,0x72,0x72,0x2C,0x20,0x22,0x5C,0x6E,0x22,0x29,0x0D,
-	0x0A,0x0D,0x0A,0x09,0x70,0x20,0x3D,0x20,0x73,0x74,0x72,0x69,0x6E,0x67,0x2E,
-	0x67,0x73,0x75,0x62,0x28,0x70,0x2C,0x20,0x22,0x5C,0x74,0x22,0x2C,0x20,0x22,
-	0x22,0x29,0x0D,0x0A,0x09,0x70,0x20,0x3D,0x20,0x73,0x74,0x72,0x69,0x6E,0x67,
-	0x2E,0x67,0x73,0x75,0x62,0x28,0x70,0x2C,0x20,0x22,0x25,0x5B,0x73,0x74,0x72,
-	0x69,0x6E,0x67,0x20,0x5C,0x22,0x28,0x2E,0x2D,0x29,0x5C,0x22,0x25,0x5D,0x22,
-	0x2C,0x20,0x22,0x25,0x31,0x22,0x29,0x0D,0x0A,0x0D,0x0A,0x09,0x6C,0x6F,0x63,
-	0x61,0x6C,0x20,0x66,0x75,0x6E,0x63,0x74,0x69,0x6F,0x6E,0x20,0x64,0x72,0x61,
-	0x77,0x28,0x29,0x0D,0x0A,0x09,0x09,0x6C,0x6F,0x76,0x65,0x2E,0x67,0x72,0x61,
-	0x70,0x68,0x69,0x63,0x73,0x2E,0x63,0x6C,0x65,0x61,0x72,0x28,0x29,0x0D,0x0A,
-	0x09,0x09,0x6C,0x6F,0x76,0x65,0x2E,0x67,0x72,0x61,0x70,0x68,0x69,0x63,0x73,
-	0x2E,0x70,0x72,0x69,0x6E,0x74,0x66,0x28,0x70,0x2C,0x20,0x37,0x30,0x2C,0x20,
-	0x37,0x30,0x2C,0x20,0x6C,0x6F,0x76,0x65,0x2E,0x67,0x72,0x61,0x70,0x68,0x69,
-	0x63,0x73,0x2E,0x67,0x65,0x74,0x57,0x69,0x64,0x74,0x68,0x28,0x29,0x20,0x2D,
-	0x20,0x37,0x30,0x29,0x0D,0x0A,0x09,0x09,0x6C,0x6F,0x76,0x65,0x2E,0x67,0x72,
-	0x61,0x70,0x68,0x69,0x63,0x73,0x2E,0x70,0x72,0x65,0x73,0x65,0x6E,0x74,0x28,
-	0x29,0x0D,0x0A,0x09,0x65,0x6E,0x64,0x0D,0x0A,0x0D,0x0A,0x09,0x64,0x72,0x61,
-	0x77,0x28,0x29,0x0D,0x0A,0x0D,0x0A,0x09,0x6C,0x6F,0x63,0x61,0x6C,0x20,0x65,
-	0x2C,0x20,0x61,0x2C,0x20,0x62,0x2C,0x20,0x63,0x0D,0x0A,0x09,0x77,0x68,0x69,
-	0x6C,0x65,0x20,0x74,0x72,0x75,0x65,0x20,0x64,0x6F,0x0D,0x0A,0x09,0x09,0x65,
-	0x2C,0x20,0x61,0x2C,0x20,0x62,0x2C,0x20,0x63,0x20,0x3D,0x20,0x6C,0x6F,0x76,
-	0x65,0x2E,0x65,0x76,0x65,0x6E,0x74,0x2E,0x77,0x61,0x69,0x74,0x28,0x29,0x0D,
-	0x0A,0x0D,0x0A,0x09,0x09,0x69,0x66,0x20,0x65,0x20,0x3D,0x3D,0x20,0x22,0x71,
-	0x22,0x20,0x74,0x68,0x65,0x6E,0x0D,0x0A,0x09,0x09,0x09,0x72,0x65,0x74,0x75,
-	0x72,0x6E,0x0D,0x0A,0x09,0x09,0x65,0x6E,0x64,0x0D,0x0A,0x09,0x09,0x69,0x66,
-	0x20,0x65,0x20,0x3D,0x3D,0x20,0x22,0x6B,0x70,0x22,0x20,0x61,0x6E,0x64,0x20,
-	0x61,0x20,0x3D,0x3D,0x20,0x22,0x65,0x73,0x63,0x61,0x70,0x65,0x22,0x20,0x74,
-	0x68,0x65,0x6E,0x0D,0x0A,0x09,0x09,0x09,0x72,0x65,0x74,0x75,0x72,0x6E,0x0D,
-	0x0A,0x09,0x09,0x65,0x6E,0x64,0x0D,0x0A,0x0D,0x0A,0x09,0x09,0x64,0x72,0x61,
-	0x77,0x28,0x29,0x0D,0x0A,0x0D,0x0A,0x09,0x65,0x6E,0x64,0x0D,0x0A,0x0D,0x0A,
-	0x65,0x6E,0x64,0x0D,0x0A,0x0D,0x0A,0x0D,0x0A,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+	0x65,0x6E,0x64,0x0D,0x0A,0x0D,0x0A,0x09,0x6C,0x6F,0x76,0x65,0x2E,0x67,0x72,
+	0x61,0x70,0x68,0x69,0x63,0x73,0x2E,0x73,0x65,0x74,0x52,0x65,0x6E,0x64,0x65,
+	0x72,0x54,0x61,0x72,0x67,0x65,0x74,0x28,0x29,0x0D,0x0A,0x0D,0x0A,0x09,0x2D,
+	0x2D,0x20,0x4C,0x6F,0x61,0x64,0x2E,0x0D,0x0A,0x09,0x6C,0x6F,0x76,0x65,0x2E,
+	0x67,0x72,0x61,0x70,0x68,0x69,0x63,0x73,0x2E,0x73,0x65,0x74,0x53,0x63,0x69,
+	0x73,0x73,0x6F,0x72,0x28,0x29,0x0D,0x0A,0x09,0x6C,0x6F,0x76,0x65,0x2E,0x67,
+	0x72,0x61,0x70,0x68,0x69,0x63,0x73,0x2E,0x73,0x65,0x74,0x42,0x61,0x63,0x6B,
+	0x67,0x72,0x6F,0x75,0x6E,0x64,0x43,0x6F,0x6C,0x6F,0x72,0x28,0x38,0x39,0x2C,
+	0x20,0x31,0x35,0x37,0x2C,0x20,0x32,0x32,0x30,0x29,0x0D,0x0A,0x09,0x6C,0x6F,
+	0x63,0x61,0x6C,0x20,0x66,0x6F,0x6E,0x74,0x20,0x3D,0x20,0x6C,0x6F,0x76,0x65,
+	0x2E,0x67,0x72,0x61,0x70,0x68,0x69,0x63,0x73,0x2E,0x6E,0x65,0x77,0x46,0x6F,
+	0x6E,0x74,0x28,0x6C,0x6F,0x76,0x65,0x2E,0x5F,0x76,0x65,0x72,0x61,0x5F,0x74,
+	0x74,0x66,0x2C,0x20,0x31,0x34,0x29,0x0D,0x0A,0x09,0x6C,0x6F,0x76,0x65,0x2E,
+	0x67,0x72,0x61,0x70,0x68,0x69,0x63,0x73,0x2E,0x73,0x65,0x74,0x46,0x6F,0x6E,
+	0x74,0x28,0x66,0x6F,0x6E,0x74,0x29,0x0D,0x0A,0x0D,0x0A,0x09,0x6C,0x6F,0x76,
+	0x65,0x2E,0x67,0x72,0x61,0x70,0x68,0x69,0x63,0x73,0x2E,0x73,0x65,0x74,0x43,
+	0x6F,0x6C,0x6F,0x72,0x28,0x32,0x35,0x35,0x2C,0x20,0x32,0x35,0x35,0x2C,0x20,
+	0x32,0x35,0x35,0x2C,0x20,0x32,0x35,0x35,0x29,0x0D,0x0A,0x0D,0x0A,0x09,0x6C,
+	0x6F,0x63,0x61,0x6C,0x20,0x74,0x72,0x61,0x63,0x65,0x20,0x3D,0x20,0x64,0x65,
+	0x62,0x75,0x67,0x2E,0x74,0x72,0x61,0x63,0x65,0x62,0x61,0x63,0x6B,0x28,0x29,
+	0x0D,0x0A,0x0D,0x0A,0x09,0x6C,0x6F,0x76,0x65,0x2E,0x67,0x72,0x61,0x70,0x68,
+	0x69,0x63,0x73,0x2E,0x63,0x6C,0x65,0x61,0x72,0x28,0x29,0x0D,0x0A,0x0D,0x0A,
+	0x09,0x6C,0x6F,0x63,0x61,0x6C,0x20,0x65,0x72,0x72,0x20,0x3D,0x20,0x7B,0x7D,
+	0x0D,0x0A,0x0D,0x0A,0x09,0x74,0x61,0x62,0x6C,0x65,0x2E,0x69,0x6E,0x73,0x65,
+	0x72,0x74,0x28,0x65,0x72,0x72,0x2C,0x20,0x22,0x45,0x72,0x72,0x6F,0x72,0x5C,
+	0x6E,0x22,0x29,0x0D,0x0A,0x09,0x74,0x61,0x62,0x6C,0x65,0x2E,0x69,0x6E,0x73,
+	0x65,0x72,0x74,0x28,0x65,0x72,0x72,0x2C,0x20,0x6D,0x73,0x67,0x2E,0x2E,0x22,
+	0x5C,0x6E,0x5C,0x6E,0x22,0x29,0x0D,0x0A,0x0D,0x0A,0x09,0x66,0x6F,0x72,0x20,
+	0x6C,0x20,0x69,0x6E,0x20,0x73,0x74,0x72,0x69,0x6E,0x67,0x2E,0x67,0x6D,0x61,
+	0x74,0x63,0x68,0x28,0x74,0x72,0x61,0x63,0x65,0x2C,0x20,0x22,0x28,0x2E,0x2D,
+	0x29,0x5C,0x6E,0x22,0x29,0x20,0x64,0x6F,0x0D,0x0A,0x09,0x09,0x69,0x66,0x20,
+	0x6E,0x6F,0x74,0x20,0x73,0x74,0x72,0x69,0x6E,0x67,0x2E,0x6D,0x61,0x74,0x63,
+	0x68,0x28,0x6C,0x2C,0x20,0x22,0x62,0x6F,0x6F,0x74,0x2E,0x6C,0x75,0x61,0x22,
+	0x29,0x20,0x74,0x68,0x65,0x6E,0x0D,0x0A,0x09,0x09,0x09,0x6C,0x20,0x3D,0x20,
+	0x73,0x74,0x72,0x69,0x6E,0x67,0x2E,0x67,0x73,0x75,0x62,0x28,0x6C,0x2C,0x20,
+	0x22,0x73,0x74,0x61,0x63,0x6B,0x20,0x74,0x72,0x61,0x63,0x65,0x62,0x61,0x63,
+	0x6B,0x3A,0x22,0x2C,0x20,0x22,0x54,0x72,0x61,0x63,0x65,0x62,0x61,0x63,0x6B,
+	0x5C,0x6E,0x22,0x29,0x0D,0x0A,0x09,0x09,0x09,0x74,0x61,0x62,0x6C,0x65,0x2E,
+	0x69,0x6E,0x73,0x65,0x72,0x74,0x28,0x65,0x72,0x72,0x2C,0x20,0x6C,0x29,0x0D,
+	0x0A,0x09,0x09,0x65,0x6E,0x64,0x0D,0x0A,0x09,0x65,0x6E,0x64,0x0D,0x0A,0x0D,
+	0x0A,0x09,0x6C,0x6F,0x63,0x61,0x6C,0x20,0x70,0x20,0x3D,0x20,0x74,0x61,0x62,
+	0x6C,0x65,0x2E,0x63,0x6F,0x6E,0x63,0x61,0x74,0x28,0x65,0x72,0x72,0x2C,0x20,
+	0x22,0x5C,0x6E,0x22,0x29,0x0D,0x0A,0x0D,0x0A,0x09,0x70,0x20,0x3D,0x20,0x73,
+	0x74,0x72,0x69,0x6E,0x67,0x2E,0x67,0x73,0x75,0x62,0x28,0x70,0x2C,0x20,0x22,
+	0x5C,0x74,0x22,0x2C,0x20,0x22,0x22,0x29,0x0D,0x0A,0x09,0x70,0x20,0x3D,0x20,
+	0x73,0x74,0x72,0x69,0x6E,0x67,0x2E,0x67,0x73,0x75,0x62,0x28,0x70,0x2C,0x20,
+	0x22,0x25,0x5B,0x73,0x74,0x72,0x69,0x6E,0x67,0x20,0x5C,0x22,0x28,0x2E,0x2D,
+	0x29,0x5C,0x22,0x25,0x5D,0x22,0x2C,0x20,0x22,0x25,0x31,0x22,0x29,0x0D,0x0A,
+	0x0D,0x0A,0x09,0x6C,0x6F,0x63,0x61,0x6C,0x20,0x66,0x75,0x6E,0x63,0x74,0x69,
+	0x6F,0x6E,0x20,0x64,0x72,0x61,0x77,0x28,0x29,0x0D,0x0A,0x09,0x09,0x6C,0x6F,
+	0x76,0x65,0x2E,0x67,0x72,0x61,0x70,0x68,0x69,0x63,0x73,0x2E,0x63,0x6C,0x65,
+	0x61,0x72,0x28,0x29,0x0D,0x0A,0x09,0x09,0x6C,0x6F,0x76,0x65,0x2E,0x67,0x72,
+	0x61,0x70,0x68,0x69,0x63,0x73,0x2E,0x70,0x72,0x69,0x6E,0x74,0x66,0x28,0x70,
+	0x2C,0x20,0x37,0x30,0x2C,0x20,0x37,0x30,0x2C,0x20,0x6C,0x6F,0x76,0x65,0x2E,
+	0x67,0x72,0x61,0x70,0x68,0x69,0x63,0x73,0x2E,0x67,0x65,0x74,0x57,0x69,0x64,
+	0x74,0x68,0x28,0x29,0x20,0x2D,0x20,0x37,0x30,0x29,0x0D,0x0A,0x09,0x09,0x6C,
+	0x6F,0x76,0x65,0x2E,0x67,0x72,0x61,0x70,0x68,0x69,0x63,0x73,0x2E,0x70,0x72,
+	0x65,0x73,0x65,0x6E,0x74,0x28,0x29,0x0D,0x0A,0x09,0x65,0x6E,0x64,0x0D,0x0A,
+	0x0D,0x0A,0x09,0x64,0x72,0x61,0x77,0x28,0x29,0x0D,0x0A,0x0D,0x0A,0x09,0x6C,
+	0x6F,0x63,0x61,0x6C,0x20,0x65,0x2C,0x20,0x61,0x2C,0x20,0x62,0x2C,0x20,0x63,
+	0x0D,0x0A,0x09,0x77,0x68,0x69,0x6C,0x65,0x20,0x74,0x72,0x75,0x65,0x20,0x64,
+	0x6F,0x0D,0x0A,0x09,0x09,0x65,0x2C,0x20,0x61,0x2C,0x20,0x62,0x2C,0x20,0x63,
+	0x20,0x3D,0x20,0x6C,0x6F,0x76,0x65,0x2E,0x65,0x76,0x65,0x6E,0x74,0x2E,0x77,
+	0x61,0x69,0x74,0x28,0x29,0x0D,0x0A,0x0D,0x0A,0x09,0x09,0x69,0x66,0x20,0x65,
+	0x20,0x3D,0x3D,0x20,0x22,0x71,0x22,0x20,0x74,0x68,0x65,0x6E,0x0D,0x0A,0x09,
+	0x09,0x09,0x72,0x65,0x74,0x75,0x72,0x6E,0x0D,0x0A,0x09,0x09,0x65,0x6E,0x64,
+	0x0D,0x0A,0x09,0x09,0x69,0x66,0x20,0x65,0x20,0x3D,0x3D,0x20,0x22,0x6B,0x70,
+	0x22,0x20,0x61,0x6E,0x64,0x20,0x61,0x20,0x3D,0x3D,0x20,0x22,0x65,0x73,0x63,
+	0x61,0x70,0x65,0x22,0x20,0x74,0x68,0x65,0x6E,0x0D,0x0A,0x09,0x09,0x09,0x72,
+	0x65,0x74,0x75,0x72,0x6E,0x0D,0x0A,0x09,0x09,0x65,0x6E,0x64,0x0D,0x0A,0x0D,
+	0x0A,0x09,0x09,0x64,0x72,0x61,0x77,0x28,0x29,0x0D,0x0A,0x0D,0x0A,0x09,0x65,
+	0x6E,0x64,0x0D,0x0A,0x0D,0x0A,0x65,0x6E,0x64,0x0D,0x0A,0x0D,0x0A,0x0D,0x0A,
 	0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
 	0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
 	0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
-	0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x0D,0x0A,0x2D,0x2D,0x20,0x54,0x68,
-	0x65,0x20,0x72,0x6F,0x6F,0x74,0x20,0x6F,0x66,0x20,0x61,0x6C,0x6C,0x20,0x63,
-	0x61,0x6C,0x6C,0x73,0x2E,0x0D,0x0A,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
+	0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x0D,
+	0x0A,0x2D,0x2D,0x20,0x54,0x68,0x65,0x20,0x72,0x6F,0x6F,0x74,0x20,0x6F,0x66,
+	0x20,0x61,0x6C,0x6C,0x20,0x63,0x61,0x6C,0x6C,0x73,0x2E,0x0D,0x0A,0x2D,0x2D,
 	0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
 	0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
 	0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
-	0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x0D,0x0A,0x0D,0x0A,0x6C,0x6F,0x63,0x61,0x6C,
+	0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x0D,0x0A,0x0D,
+	0x0A,0x6C,0x6F,0x63,0x61,0x6C,0x20,0x72,0x65,0x73,0x75,0x6C,0x74,0x20,0x3D,
+	0x20,0x78,0x70,0x63,0x61,0x6C,0x6C,0x28,0x6C,0x6F,0x76,0x65,0x2E,0x62,0x6F,
+	0x6F,0x74,0x2C,0x20,0x65,0x72,0x72,0x6F,0x72,0x5F,0x70,0x72,0x69,0x6E,0x74,
+	0x65,0x72,0x29,0x0D,0x0A,0x69,0x66,0x20,0x6E,0x6F,0x74,0x20,0x72,0x65,0x73,
+	0x75,0x6C,0x74,0x20,0x74,0x68,0x65,0x6E,0x20,0x72,0x65,0x74,0x75,0x72,0x6E,
+	0x20,0x65,0x6E,0x64,0x0D,0x0A,0x6C,0x6F,0x63,0x61,0x6C,0x20,0x72,0x65,0x73,
+	0x75,0x6C,0x74,0x20,0x3D,0x20,0x78,0x70,0x63,0x61,0x6C,0x6C,0x28,0x6C,0x6F,
+	0x76,0x65,0x2E,0x69,0x6E,0x69,0x74,0x2C,0x20,0x6C,0x6F,0x76,0x65,0x2E,0x65,
+	0x72,0x72,0x68,0x61,0x6E,0x64,0x29,0x0D,0x0A,0x69,0x66,0x20,0x6E,0x6F,0x74,
+	0x20,0x72,0x65,0x73,0x75,0x6C,0x74,0x20,0x74,0x68,0x65,0x6E,0x20,0x72,0x65,
+	0x74,0x75,0x72,0x6E,0x20,0x65,0x6E,0x64,0x0D,0x0A,0x6C,0x6F,0x63,0x61,0x6C,
 	0x20,0x72,0x65,0x73,0x75,0x6C,0x74,0x20,0x3D,0x20,0x78,0x70,0x63,0x61,0x6C,
-	0x6C,0x28,0x6C,0x6F,0x76,0x65,0x2E,0x62,0x6F,0x6F,0x74,0x2C,0x20,0x65,0x72,
-	0x72,0x6F,0x72,0x5F,0x70,0x72,0x69,0x6E,0x74,0x65,0x72,0x29,0x0D,0x0A,0x69,
-	0x66,0x20,0x6E,0x6F,0x74,0x20,0x72,0x65,0x73,0x75,0x6C,0x74,0x20,0x74,0x68,
-	0x65,0x6E,0x20,0x72,0x65,0x74,0x75,0x72,0x6E,0x20,0x65,0x6E,0x64,0x0D,0x0A,
-	0x6C,0x6F,0x63,0x61,0x6C,0x20,0x72,0x65,0x73,0x75,0x6C,0x74,0x20,0x3D,0x20,
-	0x78,0x70,0x63,0x61,0x6C,0x6C,0x28,0x6C,0x6F,0x76,0x65,0x2E,0x69,0x6E,0x69,
-	0x74,0x2C,0x20,0x6C,0x6F,0x76,0x65,0x2E,0x65,0x72,0x72,0x68,0x61,0x6E,0x64,
-	0x29,0x0D,0x0A,0x69,0x66,0x20,0x6E,0x6F,0x74,0x20,0x72,0x65,0x73,0x75,0x6C,
-	0x74,0x20,0x74,0x68,0x65,0x6E,0x20,0x72,0x65,0x74,0x75,0x72,0x6E,0x20,0x65,
-	0x6E,0x64,0x0D,0x0A,0x6C,0x6F,0x63,0x61,0x6C,0x20,0x72,0x65,0x73,0x75,0x6C,
-	0x74,0x20,0x3D,0x20,0x78,0x70,0x63,0x61,0x6C,0x6C,0x28,0x6C,0x6F,0x76,0x65,
-	0x2E,0x72,0x75,0x6E,0x2C,0x20,0x6C,0x6F,0x76,0x65,0x2E,0x65,0x72,0x72,0x68,
-	0x61,0x6E,0x64,0x29,0x0D,0x0A,0x69,0x66,0x20,0x6E,0x6F,0x74,0x20,0x72,0x65,
-	0x73,0x75,0x6C,0x74,0x20,0x74,0x68,0x65,0x6E,0x20,0x72,0x65,0x74,0x75,0x72,
-	0x6E,0x20,0x65,0x6E,0x64,0x0D,0x0A,
+	0x6C,0x28,0x6C,0x6F,0x76,0x65,0x2E,0x72,0x75,0x6E,0x2C,0x20,0x6C,0x6F,0x76,
+	0x65,0x2E,0x65,0x72,0x72,0x68,0x61,0x6E,0x64,0x29,0x0D,0x0A,0x69,0x66,0x20,
+	0x6E,0x6F,0x74,0x20,0x72,0x65,0x73,0x75,0x6C,0x74,0x20,0x74,0x68,0x65,0x6E,
+	0x20,0x72,0x65,0x74,0x75,0x72,0x6E,0x20,0x65,0x6E,0x64,0x0D,0x0A,
 };
 // [/boot.lua]