Browse Source

Revert most render pass API changes. The APIs need more work and iteration before they’re better to use than the old setCanvas APIs.

love.graphics.captureScreenshot has not been reverted.

--HG--
branch : minor
Alex Szpakowski 8 years ago
parent
commit
2a47a8c9e2

+ 3 - 1
changes.txt

@@ -16,6 +16,7 @@ Released: N/A
   * Added Shader:hasUniform.
   * Added support for non-square shader uniform matrices on desktop platforms.
   * Added Shader:send(matrixname, is_column_major, matrix, ...) which specifies how to interpret the matrix table arguments.
+  * Added love.graphics.captureScreenshot (replaces love.graphics.newScreenshot).
   * Added love.window.updateMode.
   * Added Object:release.
   * Added queueable audio sources.
@@ -30,15 +31,16 @@ Released: N/A
   * Changed all color values to be in the range 0-1, rather than 0-255.
   * Changed love.graphics.print and friends to ignore carriage returns.
   * Changed the 'multiply' blend mode to error if not used with the 'premultiplied' blend alpha mode, since the formula only works with that anyway.
+  * Changed some love.graphics, love.window, and love.event APIs to cause an error if a Canvas is active.
   * Changed the audio playback APIs drastically.
   * Changed enet to no longer set the 'enet' global, again matching luasocket.
   * Changed Source seeking behaviour, all kinds of Sources now behave similarly when seeking past the boundaries.
-  * Changed the rendering API to one based on render passes.
 
   * Removed the default source type for love.audio.newSource.
   * Removed variant of love.filesystem.newFileData which takes base64 data, use love.math.decode instead.
   * Removed the no-argument variant of Text:set, use Text:clear instead.
   * Removed Shader:getExternVariable, use Shader:hasUniform instead.
+  * Removed love.graphics.newScreenshot, use love.graphics.captureScreenshot instead.
   * Removed deprecated enet function host:socket_get_address.
   * Removed functions deprecated in LÖVE 0.10.2:
     * Removed Shader:sendInt, Shader:sendBoolean, Shader:sentFloat, Shader:sendMatrix, and Shader:sendTexture (use Shader:send instead).

+ 4 - 4
src/modules/event/sdl/Event.cpp

@@ -157,11 +157,11 @@ void Event::exceptionIfInRenderPass()
 {
 	// Some core OS graphics functionality (e.g. swap buffers on some platforms)
 	// happens inside SDL_PumpEvents - which is called by SDL_PollEvent and
-	// friends. It's probably a bad idea to call those functions while we're in
-	// a render pass.
+	// friends. It's probably a bad idea to call those functions while a Canvas
+	// is active.
 	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-	if (gfx != nullptr && gfx->isPassActive())
-		throw love::Exception("Cannot call this function while a render pass is active in love.graphics.");
+	if (gfx != nullptr && gfx->isCanvasActive())
+		throw love::Exception("Cannot call this function while a Canvas is active in love.graphics.");
 }
 
 Message *Event::convert(const SDL_Event &e)

+ 8 - 10
src/modules/graphics/Graphics.h

@@ -61,14 +61,6 @@ void gammaCorrectColor(Colorf &c);
  **/
 void unGammaCorrectColor(Colorf &c);
 
-class RenderOutsidePassException : public love::Exception
-{
-public:
-	RenderOutsidePassException()
-		: Exception("Cannot draw outside of a render pass!")
-	{}
-};
-
 class Graphics : public Module
 {
 public:
@@ -193,7 +185,7 @@ public:
 	struct Stats
 	{
 		int drawCalls;
-		int renderPasses;
+		int canvasSwitches;
 		int shaderSwitches;
 		int canvases;
 		int images;
@@ -224,6 +216,12 @@ public:
 		}
 	};
 
+	struct OptionalColorf
+	{
+		Colorf c;
+		bool enabled;
+	};
+
 	virtual ~Graphics();
 
 	// Implements Module.
@@ -261,7 +259,7 @@ public:
 	 **/
 	virtual bool isActive() const = 0;
 
-	virtual bool isPassActive() const = 0;
+	virtual bool isCanvasActive() const = 0;
 
 	static bool getConstant(const char *in, DrawMode &out);
 	static bool getConstant(DrawMode in, const char *&out);

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

@@ -253,15 +253,8 @@ void Canvas::unloadVolatile()
 void Canvas::drawv(const Matrix4 &t, const Vertex *v)
 {
 	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
-	if (gfx != nullptr)
-	{
-		const PassInfo &info = gfx->getActivePass();
-		for (const auto &attachment : info.colorAttachments)
-		{
-			if (attachment.canvas == this)
-				throw love::Exception("Cannot render a Canvas to itself!");
-		}
-	}
+	if (gfx != nullptr && gfx->isCanvasActive(this))
+		throw love::Exception("Cannot render a Canvas to itself!");
 
 	OpenGL::TempDebugGroup debuggroup("Canvas draw");
 
@@ -334,6 +327,59 @@ const void *Canvas::getHandle() const
 	return &texture;
 }
 
+love::image::ImageData *Canvas::newImageData(love::image::Image *module, int x, int y, int w, int h)
+{
+	if (x < 0 || y < 0 || w <= 0 || h <= 0 || (x + w) > getWidth() || (y + h) > getHeight())
+		throw love::Exception("Invalid rectangle dimensions.");
+
+	Graphics *gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
+
+	if (gfx != nullptr)
+	{
+		for (const auto &c : gfx->getCanvas())
+		{
+			if (c == this)
+				throw love::Exception("Canvas:newImageData cannot be called while that Canvas is currently active.");
+		}
+	}
+
+	PixelFormat dataformat;
+	switch (getPixelFormat())
+	{
+	case PIXELFORMAT_RGB10A2: // FIXME: Conversions aren't supported in GLES
+		dataformat = PIXELFORMAT_RGBA16;
+		break;
+	case PIXELFORMAT_R16F:
+	case PIXELFORMAT_RG16F:
+	case PIXELFORMAT_RGBA16F:
+	case PIXELFORMAT_RG11B10F: // FIXME: Conversions aren't supported in GLES
+		dataformat = PIXELFORMAT_RGBA16F;
+		break;
+	case PIXELFORMAT_R32F:
+	case PIXELFORMAT_RG32F:
+	case PIXELFORMAT_RGBA32F:
+		dataformat = PIXELFORMAT_RGBA32F;
+		break;
+	default:
+		dataformat = PIXELFORMAT_RGBA8;
+		break;
+	}
+
+	love::image::ImageData *imagedata = module->newImageData(w, h, dataformat);
+
+	bool isSRGB = false;
+	OpenGL::TextureFormat fmt = gl.convertPixelFormat(dataformat, false, isSRGB);
+
+	GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
+	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, getFBO());
+
+	glReadPixels(x, y, w, h, fmt.externalformat, fmt.type, imagedata->getData());
+
+	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
+
+	return imagedata;
+}
+
 PixelFormat Canvas::getSizedFormat(PixelFormat format)
 {
 	switch (format)

+ 2 - 0
src/modules/graphics/opengl/Canvas.h

@@ -61,6 +61,8 @@ public:
 	bool setWrap(const Texture::Wrap &w) override;
 	const void *getHandle() const override;
 
+	love::image::ImageData *newImageData(love::image::Image *module, int x, int y, int w, int h);
+
 	inline GLenum getStatus() const
 	{
 		return status;

+ 222 - 296
src/modules/graphics/opengl/Graphics.cpp

@@ -58,10 +58,8 @@ Graphics::Graphics()
 	, height(0)
 	, created(false)
 	, active(true)
-	, canCaptureScreenshot(true)
-	, currentPass()
 	, writingToStencil(false)
-	, renderPassCount(0)
+	, canvasSwitchCount(0)
 {
 	gl = OpenGL();
 
@@ -132,6 +130,7 @@ void Graphics::restoreState(const DisplayState &s)
 
 	setFont(s.font.get());
 	setShader(s.shader.get());
+	setCanvas(s.canvases);
 
 	setColorMask(s.colorMask);
 	setWireframe(s.wireframe);
@@ -174,6 +173,22 @@ void Graphics::restoreStateChecked(const DisplayState &s)
 	setFont(s.font.get());
 	setShader(s.shader.get());
 
+	bool canvaseschanged = s.canvases.size() != cur.canvases.size();
+	if (!canvaseschanged)
+	{
+		for (size_t i = 0; i < s.canvases.size() && i < cur.canvases.size(); i++)
+		{
+			if (s.canvases[i].get() != cur.canvases[i].get())
+			{
+				canvaseschanged = true;
+				break;
+			}
+		}
+	}
+
+	if (canvaseschanged)
+		setCanvas(s.canvases);
+
 	if (s.colorMask != cur.colorMask)
 		setColorMask(s.colorMask);
 
@@ -211,7 +226,7 @@ void Graphics::setViewportSize(int width, int height)
 	this->width = width;
 	this->height = height;
 
-	if (currentPass.active && currentPass.info.colorAttachmentCount == 0)
+	if (states.back().canvases.empty())
 	{
 		// Set the viewport to top-left corner.
 		gl.setViewport({0, 0, width, height}, false);
@@ -427,97 +442,72 @@ void Graphics::reset()
 	origin();
 }
 
-void Graphics::beginPass(PassInfo::BeginAction beginAction, Colorf clearColor)
+void Graphics::setCanvas(Canvas *canvas)
 {
-	if (currentPass.active)
-		throw love::Exception("Cannot call beginPass while another render pass is active!");
-
-	currentPass.active = true;
+	if (canvas == nullptr)
+		return setCanvas();
 
-	OpenGL::TempDebugGroup debuggroup("Render Pass begin");
-
-	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO());
+	std::vector<Canvas *> canvases = {canvas};
+	setCanvas(canvases);
+}
 
-	gl.setViewport({0, 0, width, height}, false);
+void Graphics::setCanvas(const std::vector<Canvas *> &canvases)
+{
+	DisplayState &state = states.back();
+	int ncanvases = (int) canvases.size();
 
-	// The projection matrix is flipped compared to rendering to a canvas, due
-	// to OpenGL considering (0,0) bottom-left instead of top-left.
-	gl.matrices.projection = Matrix4::ortho(0.0, (float) width, (float) height, 0.0);
+	if (ncanvases == 0)
+		return setCanvas();
 
-	if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control)
+	if (ncanvases == (int) state.canvases.size())
 	{
-		if (isGammaCorrect() && !gl.hasFramebufferSRGB())
-			gl.setFramebufferSRGB(true);
-		else if (!isGammaCorrect() && gl.hasFramebufferSRGB())
-			gl.setFramebufferSRGB(false);
-	}
-
-	// Always clear the stencil buffer, for now.
-	GLbitfield clearflags = GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT;
+		bool modified = false;
 
-	if (beginAction == PassInfo::BEGIN_CLEAR)
-	{
-		Colorf c = clearColor;
-		gammaCorrectColor(c);
+		for (int i = 0; i < ncanvases; i++)
+		{
+			if (canvases[i] != state.canvases[i].get())
+			{
+				modified = true;
+				break;
+			}
+		}
 
-		glClearColor(c.r, c.g, c.b, c.a);
-		clearflags |= GL_COLOR_BUFFER_BIT;
+		if (!modified)
+			return;
 	}
 
-	if (clearflags != 0)
-		glClear(clearflags);
+	if (ncanvases > gl.getMaxRenderTargets())
+		throw love::Exception("This system can't simultaneously render to %d canvases.", ncanvases);
 
-	PassInfo info;
-	info.colorAttachmentCount = 0;
-	info.stencil = true;
+	Canvas *firstcanvas = canvases[0];
 
-	currentPass.info = info;
-
-	renderPassCount++;
-
-	canCaptureScreenshot = false;
-}
-
-void Graphics::beginPass(const PassInfo &info)
-{
-	if (info.colorAttachmentCount == 0)
-		throw love::Exception("At least one Canvas must be specified for an off-screen render pass.");
-
-	if (currentPass.active)
-		throw love::Exception("Cannot call beginPass while another render pass is active!");
-
-	if (info.colorAttachmentCount > gl.getMaxRenderTargets())
-		throw love::Exception("This system can't simultaneously render to %d canvases.", info.colorAttachmentCount);
-
-	Canvas *firstcanvas = info.colorAttachments[0].canvas;
-
-	bool multiformatsupported = isSupported(Feature::FEATURE_MULTI_CANVAS_FORMATS);
+	bool multiformatsupported = Canvas::isMultiFormatMultiCanvasSupported();
 	PixelFormat firstformat = firstcanvas->getPixelFormat();
 
 	bool hasSRGBcanvas = firstformat == PIXELFORMAT_sRGBA8;
 
-	for (int i = 1; i < info.colorAttachmentCount; i++)
+	for (int i = 1; i < ncanvases; i++)
 	{
-		Canvas *c = info.colorAttachments[i].canvas;
+		Canvas *c = canvases[i];
 
 		if (c->getWidth() != firstcanvas->getWidth() || c->getHeight() != firstcanvas->getHeight())
-			throw love::Exception("All canvases in a render pass must have the same dimensions.");
+			throw love::Exception("All canvases in must have the same dimensions.");
 
 		if (!multiformatsupported && c->getPixelFormat() != firstformat)
 			throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats.");
 
 		if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA())
-			throw love::Exception("All Canvases in a render pass must have the same requested MSAA value.");
+			throw love::Exception("All Canvases in must have the same requested MSAA value.");
 
 		if (c->getPixelFormat() == PIXELFORMAT_sRGBA8)
 			hasSRGBcanvas = true;
 	}
 
-	OpenGL::TempDebugGroup debuggroup("Render Pass begin");
+	OpenGL::TempDebugGroup debuggroup("setCanvas(...)");
 
-	bindCachedFBOForPass(info);
+	endPass();
 
-	currentPass.active = true;
+	bindCachedFBO(canvases);
 
 	int w = firstcanvas->getWidth();
 	int h = firstcanvas->getHeight();
@@ -534,149 +524,88 @@ void Graphics::beginPass(const PassInfo &info)
 			gl.setFramebufferSRGB(false);
 	}
 
-	GLbitfield clearflags = 0;
+	std::vector<StrongRef<Canvas>> canvasrefs;
+	canvasrefs.reserve(canvases.size());
 
-	// Take a single-color clear codepath if there's only one specified Canvas.
-	// The multi-color codepath (in the loop below) assumes MRT functions are
-	// available, and also may call more expensive GL functions which are
-	// unnecessary if only one Canvas is used.
-	if (info.colorAttachmentCount <= 1)
-	{
-		if (info.colorAttachmentCount > 0 && info.colorAttachments[0].beginAction == PassInfo::BEGIN_CLEAR)
-		{
-			clearflags |= GL_COLOR_BUFFER_BIT;
-			Colorf c = info.colorAttachments[0].clearColor;
-			gammaCorrectColor(c);
-			glClearColor(c.r, c.g, c.b, c.a);
-		}
-	}
-	else
-	{
-		bool drawbuffermodified = false;
+	for (Canvas *c : canvases)
+		canvasrefs.push_back(c);
 
-		for (int i = 0; i < info.colorAttachmentCount; i++)
-		{
-			if (info.colorAttachments[i].beginAction == PassInfo::BEGIN_CLEAR)
-			{
-				Colorf c = info.colorAttachments[i].clearColor;
-				gammaCorrectColor(c);
+	std::swap(state.canvases, canvasrefs);
 
-				if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0)
-				{
-					const GLfloat carray[] = {c.r, c.g, c.b, c.a};
-					glClearBufferfv(GL_COLOR, i, carray);
-				}
-				else
-				{
-					glDrawBuffer(GL_COLOR_ATTACHMENT0 + i);
-					glClearColor(c.r, c.g, c.b, c.a);
-					glClear(GL_COLOR_BUFFER_BIT);
+	canvasSwitchCount++;
+}
 
-					drawbuffermodified = true;
-				}
-			}
-		}
+void Graphics::setCanvas(const std::vector<StrongRef<Canvas>> &canvases)
+{
+	std::vector<Canvas *> canvaslist;
+	canvaslist.reserve(canvases.size());
 
-		// Revert to the expected draw buffers once we're done, if glClearBuffer
-		// wasn't supported.
-		if (drawbuffermodified)
-		{
-			std::vector<GLenum> bufs;
+	for (const StrongRef<Canvas> &c : canvases)
+		canvaslist.push_back(c.get());
 
-			for (int i = 0; i < info.colorAttachmentCount; i++)
-				bufs.push_back(GL_COLOR_ATTACHMENT0 + i);
+	return setCanvas(canvaslist);
+}
 
-			glDrawBuffers((int) bufs.size(), &bufs[0]);
-		}
-	}
+void Graphics::setCanvas()
+{
+	DisplayState &state = states.back();
 
-	if (info.stencil)
-		clearflags |= GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT;
+	if (state.canvases.empty())
+		return;
 
-	if (clearflags != 0)
-		glClear(clearflags);
+	OpenGL::TempDebugGroup debuggroup("setCanvas()");
 
-	for (int i = 0; i < info.colorAttachmentCount; i++)
-		info.colorAttachments[i].canvas->retain();
+	endPass();
 
-	currentPass.info = info;
-	renderPassCount++;
+	state.canvases.clear();
 
-	if (gl.bugs.clearRequiresDriverTextureStateUpdate && Shader::current)
+	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO());
+	gl.setViewport({0, 0, width, height}, false);
+
+	// The projection matrix is flipped compared to rendering to a canvas, due
+	// to OpenGL considering (0,0) bottom-left instead of top-left.
+	gl.matrices.projection = Matrix4::ortho(0.0, (float) width, (float) height, 0.0);
+
+	if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control)
 	{
-		// This seems to be enough to fix the bug for me. Other methods I've
-		// tried (e.g. dummy draws) don't work in all cases.
-		gl.useProgram(0);
-		gl.useProgram(Shader::current->getProgram());
+		if (isGammaCorrect() && !gl.hasFramebufferSRGB())
+			gl.setFramebufferSRGB(true);
+		else if (!isGammaCorrect() && gl.hasFramebufferSRGB())
+			gl.setFramebufferSRGB(false);
 	}
-}
 
-void Graphics::endPass()
-{
-	endPass(0, 0, 0, 0, nullptr, nullptr);
+	canvasSwitchCount++;
 }
 
-void Graphics::endPass(int sX, int sY, int sW, int sH, const ScreenshotInfo *info, void *screenshotCallbackData)
+std::vector<Canvas *> Graphics::getCanvas() const
 {
-	if (!currentPass.active)
-		return; // Should this error instead?
-
-	StrongRef<love::image::ImageData> imagedata;
+	std::vector<Canvas *> canvases;
+	canvases.reserve(states.back().canvases.size());
 
-	auto &attachments = currentPass.info.colorAttachments;
-	int attachmentcount = currentPass.info.colorAttachmentCount;
+	for (const StrongRef<Canvas> &c : states.back().canvases)
+		canvases.push_back(c.get());
 
-	if (info != nullptr)
-	{
-		if (attachmentcount == 0)
-			throw love::Exception("Use captureScreenshot to capture the main screen's contents.");
-
-		if (sX < 0 || sY < 0 || sW <= 0 || sH <= 0 || (sX + sW) > getPassWidth() || (sY + sH) > getPassHeight())
-			throw love::Exception("Invalid rectangle dimensions.");
-
-		auto imagemodule = Module::getInstance<image::Image>(M_IMAGE);
-
-		if (imagemodule == nullptr)
-			throw love::Exception("The love.image module must be loaded to capture a Canvas' contents.");
-
-		PixelFormat format;
-		switch (attachments[0].canvas->getPixelFormat())
-		{
-		case PIXELFORMAT_RGB10A2: // FIXME: Conversions aren't supported in GLES
-			format = PIXELFORMAT_RGBA16;
-			break;
-		case PIXELFORMAT_R16F:
-		case PIXELFORMAT_RG16F:
-		case PIXELFORMAT_RGBA16F:
-		case PIXELFORMAT_RG11B10F: // FIXME: Conversions aren't supported in GLES
-			format = PIXELFORMAT_RGBA16F;
-			break;
-		case PIXELFORMAT_R32F:
-		case PIXELFORMAT_RG32F:
-		case PIXELFORMAT_RGBA32F:
-			format = PIXELFORMAT_RGBA32F;
-			break;
-		default:
-			format = PIXELFORMAT_RGBA8;
-			break;
-		}
+	return canvases;
+}
 
-		imagedata.set(imagemodule->newImageData(sW, sH, format), Acquire::NORETAIN);
-	}
+void Graphics::endPass()
+{
+	// Discard the stencil buffer.
+	discard({}, true);
 
-	if (currentPass.info.stencil)
-		discard(OpenGL::FRAMEBUFFER_ALL, {}, true);
+	auto &canvases = states.back().canvases;
 
-	if (attachmentcount > 0 && attachments[0].canvas->getMSAA() > 1)
+	// Resolve MSAA buffers.
+	if (canvases.size() > 0 && canvases[0]->getMSAA() > 1)
 	{
-		int w = attachments[0].canvas->getWidth();
-		int h = attachments[0].canvas->getHeight();
+		int w = canvases[0]->getWidth();
+		int h = canvases[0]->getHeight();
 
-		for (int i = 0; i < attachmentcount; i++)
+		for (int i = 0; i < (int) canvases.size(); i++)
 		{
 			glReadBuffer(GL_COLOR_ATTACHMENT0 + i);
 
-			gl.bindFramebuffer(OpenGL::FRAMEBUFFER_DRAW, attachments[i].canvas->getFBO());
+			gl.bindFramebuffer(OpenGL::FRAMEBUFFER_DRAW, canvases[i]->getFBO());
 
 			if (GLAD_APPLE_framebuffer_multisample)
 				glResolveMultisampleFramebufferAPPLE();
@@ -684,50 +613,106 @@ void Graphics::endPass(int sX, int sY, int sW, int sH, const ScreenshotInfo *inf
 				glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
 		}
 	}
+}
+
+void Graphics::clear(Colorf c)
+{
+	gammaCorrectColor(c);
+	glClearColor(c.r, c.g, c.b, c.a);
+	glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+	if (gl.bugs.clearRequiresDriverTextureStateUpdate && Shader::current)
+	{
+		// This seems to be enough to fix the bug for me. Other methods I've
+		// tried (e.g. dummy draws) don't work in all cases.
+		gl.useProgram(0);
+		gl.useProgram(Shader::current->getProgram());
+	}
+}
+
+void Graphics::clear(const std::vector<OptionalColorf> &colors)
+{
+	if (colors.size() == 0)
+		return;
+
+	int ncanvases = (int) states.back().canvases.size();
+	int ncolors = std::min((int) colors.size(), ncanvases);
+
+	if (ncolors <= 1 && ncanvases <= 1)
+	{
+		if (colors[0].enabled)
+			clear(colors[0].c);
+
+		return;
+	}
+
+	bool drawbuffersmodified = false;
 
-	if (info != nullptr)
+	for (int i = 0; i < ncolors; i++)
 	{
-		if (attachments[0].canvas->getMSAA() > 1)
-			gl.bindFramebuffer(OpenGL::FRAMEBUFFER_READ, attachments[0].canvas->getFBO());
-		else if (attachmentcount > 1)
-			glReadBuffer(GL_COLOR_ATTACHMENT0);
+		if (!colors[i].enabled)
+			continue;
 
-		GLenum datatype;
-		switch (imagedata->getFormat())
+		Colorf c = colors[i].c;
+		gammaCorrectColor(c);
+
+		if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0)
 		{
-		case PIXELFORMAT_RGBA16:
-			datatype = GL_UNSIGNED_SHORT;
-			break;
-		case PIXELFORMAT_RGBA16F:
-			datatype = GL_HALF_FLOAT;
-			break;
-		case PIXELFORMAT_RGBA32F:
-			datatype = GL_FLOAT;
-			break;
-		default:
-			datatype = GL_UNSIGNED_BYTE;
-			break;
+			const GLfloat carray[] = {c.r, c.g, c.b, c.a};
+			glClearBufferfv(GL_COLOR, i, carray);
 		}
+		else
+		{
+			glDrawBuffer(GL_COLOR_ATTACHMENT0 + i);
+			glClearColor(c.r, c.g, c.b, c.a);
+			glClear(GL_COLOR_BUFFER_BIT);
+
+			drawbuffersmodified = true;
+		}
+	}
+
+	// Revert to the expected draw buffers once we're done, if glClearBuffer
+	// wasn't supported.
+	if (drawbuffersmodified)
+	{
+		GLenum bufs[MAX_COLOR_RENDER_TARGETS];
 
-		glReadPixels(sX, sY, sW, sH, GL_RGBA, datatype, imagedata->getData());
+		for (int i = 0; i < ncanvases; i++)
+			bufs[i] = GL_COLOR_ATTACHMENT0 + i;
 
-		info->callback(imagedata, info->ref, screenshotCallbackData);
+		glDrawBuffers(ncanvases, bufs);
 	}
 
-	for (int i = 0; i < currentPass.info.colorAttachmentCount; i++)
-		currentPass.info.colorAttachments[i].canvas->release();
+	glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
-	currentPass.active = false;
+	if (gl.bugs.clearRequiresDriverTextureStateUpdate && Shader::current)
+	{
+		// This seems to be enough to fix the bug for me. Other methods I've
+		// tried (e.g. dummy draws) don't work in all cases.
+		gl.useProgram(0);
+		gl.useProgram(Shader::current->getProgram());
+	}
 }
 
-const PassInfo &Graphics::getActivePass() const
+bool Graphics::isCanvasActive() const
 {
-	return currentPass.info;
+	return !states.back().canvases.empty();
 }
 
-bool Graphics::isPassActive() const
+bool Graphics::isCanvasActive(Canvas *canvas) const
 {
-	return currentPass.active;
+	for (const auto &c : states.back().canvases)
+	{
+		if (c.get() == canvas)
+			return true;
+	}
+
+	return false;
+}
+
+void Graphics::discard(const std::vector<bool> &colorbuffers, bool depthstencil)
+{
+	discard(OpenGL::FRAMEBUFFER_ALL, colorbuffers, depthstencil);
 }
 
 void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector<bool> &colorbuffers, bool depthstencil)
@@ -745,7 +730,7 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector<bool>
 	attachments.reserve(colorbuffers.size());
 
 	// glDiscardFramebuffer uses different attachment enums for the default FBO.
-	if (currentPass.info.colorAttachmentCount == 0 && gl.getDefaultFBO() == 0)
+	if (states.back().canvases.empty() && gl.getDefaultFBO() == 0)
 	{
 		if (colorbuffers.size() > 0 && colorbuffers[0])
 			attachments.push_back(GL_COLOR);
@@ -758,7 +743,7 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector<bool>
 	}
 	else
 	{
-		int rendertargetcount = std::max(currentPass.info.colorAttachmentCount, 1);
+		int rendertargetcount = std::max((int) states.back().canvases.size(), 1);
 
 		for (int i = 0; i < (int) colorbuffers.size(); i++)
 		{
@@ -780,19 +765,11 @@ void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector<bool>
 		glDiscardFramebufferEXT(gltarget, (GLint) attachments.size(), &attachments[0]);
 }
 
-void Graphics::bindCachedFBOForPass(const PassInfo &pass)
+void Graphics::bindCachedFBO(const std::vector<Canvas *> &canvases)
 {
-	PassBufferInfo info;
-	memset(&info, 0, sizeof(PassBufferInfo));
-
-	info.stencil = pass.stencil;
-
-	int ncanvases = pass.colorAttachmentCount;
+	int ncanvases = (int) canvases.size();
 
-	for (int i = 0; i < ncanvases; i++)
-		info.canvases[i] = pass.colorAttachments[i].canvas;
-
-	uint32 hash = XXH32(&info, offsetof(PassBufferInfo, canvases) + ncanvases * sizeof(Canvas *), 0);
+	uint32 hash = XXH32(&canvases[0], sizeof(Canvas *) * ncanvases, 0);
 
 	GLuint fbo = framebufferObjects[hash];
 
@@ -802,36 +779,35 @@ void Graphics::bindCachedFBOForPass(const PassInfo &pass)
 	}
 	else
 	{
-		int w = info.canvases[0]->getWidth();
-		int h = info.canvases[0]->getHeight();
-		int msaa = std::max(info.canvases[0]->getMSAA(), 1);
+		int w = canvases[0]->getWidth();
+		int h = canvases[0]->getHeight();
+		int msaa = std::max(canvases[0]->getMSAA(), 1);
 
 		glGenFramebuffers(1, &fbo);
 		gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo);
 
-		std::vector<GLenum> drawbuffers;
-		drawbuffers.reserve(ncanvases);
+		GLenum drawbuffers[MAX_COLOR_RENDER_TARGETS];
 
 		for (int i = 0; i < ncanvases; i++)
 		{
-			drawbuffers.push_back(GL_COLOR_ATTACHMENT0 + i);
+			drawbuffers[i] = GL_COLOR_ATTACHMENT0 + i;
 
 			if (msaa > 1)
 			{
-				GLuint rbo = (GLuint) info.canvases[i]->getMSAAHandle();
+				GLuint rbo = (GLuint) canvases[i]->getMSAAHandle();
 				glFramebufferRenderbuffer(GL_FRAMEBUFFER, drawbuffers[i], GL_RENDERBUFFER, rbo);
 			}
 			else
 			{
-				GLuint tex = *(GLuint *) info.canvases[i]->getHandle();
+				GLuint tex = *(GLuint *) canvases[i]->getHandle();
 				glFramebufferTexture2D(GL_FRAMEBUFFER, drawbuffers[i], GL_TEXTURE_2D, tex, 0);
 			}
 		}
 
-		if (drawbuffers.size() > 1)
-			glDrawBuffers((int) drawbuffers.size(), &drawbuffers[0]);
+		if (ncanvases > 1)
+			glDrawBuffers(ncanvases, drawbuffers);
 
-		GLuint stencil = attachCachedStencilBuffer(w, h, info.canvases[0]->getRequestedMSAA());
+		GLuint stencil = attachCachedStencilBuffer(w, h, canvases[0]->getRequestedMSAA());
 
 		if (stencil == 0)
 		{
@@ -872,7 +848,7 @@ GLuint Graphics::attachCachedStencilBuffer(int w, int h, int samples)
 		}
 	}
 
-	OpenGL::TempDebugGroup debuggroup("Created cached stencil buffer");
+	OpenGL::TempDebugGroup debuggroup("Create cached stencil buffer");
 
 	CachedRenderbuffer rb;
 	rb.w = w;
@@ -921,16 +897,16 @@ GLuint Graphics::attachCachedStencilBuffer(int w, int h, int samples)
 	}
 
 	if (rb.renderbuffer != 0)
+	{
+		glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 		stencilBuffers.push_back(rb);
+	}
 
 	return rb.renderbuffer;
 }
 
 void Graphics::captureScreenshot(const ScreenshotInfo &info)
 {
-	if (!canCaptureScreenshot)
-		throw love::Exception("captureScreenshot cannot be called once rendering to the main screen has begun.");
-
 	pendingScreenshotCallbacks.push_back(info);
 }
 
@@ -939,8 +915,10 @@ void Graphics::present(void *screenshotCallbackData)
 	if (!isActive())
 		return;
 
-	if (currentPass.active)
-		throw love::Exception("present cannot be called while a render pass is active.");
+	if (!states.back().canvases.empty())
+		throw love::Exception("present cannot be called while a Canvas is active.");
+
+	endPass();
 
 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO());
 
@@ -1049,9 +1027,7 @@ void Graphics::present(void *screenshotCallbackData)
 	// Reset the per-frame stat counts.
 	gl.stats.drawCalls = 0;
 	gl.stats.shaderSwitches = 0;
-	renderPassCount = 0;
-
-	canCaptureScreenshot = true;
+	canvasSwitchCount = 0;
 }
 
 int Graphics::getWidth() const
@@ -1064,22 +1040,6 @@ int Graphics::getHeight() const
 	return height;
 }
 
-int Graphics::getPassWidth() const
-{
-	if (currentPass.active && currentPass.info.colorAttachmentCount > 0)
-		return currentPass.info.colorAttachments[0].canvas->getWidth();
-	else
-		return width;
-}
-
-int Graphics::getPassHeight() const
-{
-	if (currentPass.active && currentPass.info.colorAttachmentCount > 0)
-		return currentPass.info.colorAttachments[0].canvas->getHeight();
-	else
-		return height;
-}
-
 bool Graphics::isCreated() const
 {
 	return created;
@@ -1087,12 +1047,14 @@ bool Graphics::isCreated() const
 
 void Graphics::setScissor(const Rect &rect)
 {
+	DisplayState &state = states.back();
+
 	glEnable(GL_SCISSOR_TEST);
 	// OpenGL's reversed y-coordinate is compensated for in OpenGL::setScissor.
-	gl.setScissor({rect.x, rect.y, rect.w, rect.h}, currentPass.info.colorAttachmentCount > 0);
+	gl.setScissor(rect, !state.canvases.empty());
 
-	states.back().scissor = true;
-	states.back().scissorRect = rect;
+	state.scissor = true;
+	state.scissorRect = rect;
 }
 
 void Graphics::intersectScissor(const Rect &rect)
@@ -1132,9 +1094,6 @@ bool Graphics::getScissor(Rect &rect) const
 
 void Graphics::drawToStencilBuffer(StencilAction action, int value)
 {
-	if (!currentPass.active || !currentPass.info.stencil)
-		throw love::Exception("Stenciling must be enabled in the active render pass.");
-
 	writingToStencil = true;
 
 	// Disable color writes but don't save the state for it.
@@ -1189,9 +1148,6 @@ void Graphics::stopDrawToStencilBuffer()
 
 void Graphics::setStencilTest(CompareMode compare, int value)
 {
-	if (!currentPass.info.stencil && compare != COMPARE_ALWAYS)
-		throw love::Exception("Stenciling must be enabled in the active render pass.");
-
 	DisplayState &state = states.back();
 	state.stencilCompare = compare;
 	state.stencilTestValue = value;
@@ -1608,25 +1564,16 @@ bool Graphics::isWireframe() const
 
 void Graphics::draw(Drawable *drawable, const Matrix4 &m)
 {
-	if (!currentPass.active)
-		throw RenderOutsidePassException();
-
 	drawable->draw(m);
 }
 
 void Graphics::drawq(Texture *texture, Quad *quad, const Matrix4 &m)
 {
-	if (!currentPass.active)
-		throw RenderOutsidePassException();
-
 	texture->drawq(quad, m);
 }
 
 void Graphics::print(const std::vector<Font::ColoredString> &str, const Matrix4 &m)
 {
-	if (!currentPass.active)
-		throw RenderOutsidePassException();
-
 	checkSetDefaultFont();
 
 	DisplayState &state = states.back();
@@ -1637,9 +1584,6 @@ void Graphics::print(const std::vector<Font::ColoredString> &str, const Matrix4
 
 void Graphics::printf(const std::vector<Font::ColoredString> &str, float wrap, Font::AlignMode align, const Matrix4 &m)
 {
-	if (!currentPass.active)
-		throw RenderOutsidePassException();
-
 	checkSetDefaultFont();
 
 	DisplayState &state = states.back();
@@ -1654,9 +1598,6 @@ void Graphics::printf(const std::vector<Font::ColoredString> &str, float wrap, F
 
 void Graphics::points(const float *coords, const uint8 *colors, size_t numpoints)
 {
-	if (!currentPass.active)
-		throw RenderOutsidePassException();
-
 	OpenGL::TempDebugGroup debuggroup("Graphics points draw");
 
 	gl.prepareDraw();
@@ -1678,9 +1619,6 @@ void Graphics::points(const float *coords, const uint8 *colors, size_t numpoints
 
 void Graphics::polyline(const float *coords, size_t count)
 {
-	if (!currentPass.active)
-		throw RenderOutsidePassException();
-
 	const DisplayState &state = states.back();
 	float pixelsize = 1.0f / std::max((float) pixelScaleStack.back(), 0.000001f);
 
@@ -1712,9 +1650,6 @@ void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h)
 
 void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h, float rx, float ry, int points)
 {
-	if (!currentPass.active)
-		throw RenderOutsidePassException();
-
 	if (rx == 0 || ry == 0)
 	{
 		rectangle(mode, x, y, w, h);
@@ -1798,9 +1733,6 @@ void Graphics::circle(DrawMode mode, float x, float y, float radius)
 
 void Graphics::ellipse(DrawMode mode, float x, float y, float a, float b, int points)
 {
-	if (!currentPass.active)
-		throw RenderOutsidePassException();
-
 	float two_pi = static_cast<float>(LOVE_M_PI * 2);
 	if (points <= 0) points = 1;
 	float angle_shift = (two_pi / points);
@@ -1828,9 +1760,6 @@ void Graphics::ellipse(DrawMode mode, float x, float y, float a, float b)
 
 void Graphics::arc(DrawMode drawmode, ArcMode arcmode, float x, float y, float radius, float angle1, float angle2, int points)
 {
-	if (!currentPass.active)
-		throw RenderOutsidePassException();
-
 	// Nothing to display with no points or equal angles. (Or is there with line mode?)
 	if (points <= 0 || angle1 == angle2)
 		return;
@@ -1924,9 +1853,6 @@ void Graphics::arc(DrawMode drawmode, ArcMode arcmode, float x, float y, float r
 /// @param count   the number of coordinates/size of the array
 void Graphics::polygon(DrawMode mode, const float *coords, size_t count)
 {
-	if (!currentPass.active)
-		throw RenderOutsidePassException();
-
 	// coords is an array of a closed loop of vertices, i.e.
 	// coords[count-2] = coords[0], coords[count-1] = coords[1]
 	if (mode == DRAW_LINE)
@@ -1981,7 +1907,7 @@ Graphics::Stats Graphics::getStats() const
 	Stats stats;
 
 	stats.drawCalls = gl.stats.drawCalls;
-	stats.renderPasses = renderPassCount;
+	stats.canvasSwitches = canvasSwitchCount;
 	stats.shaderSwitches = gl.stats.shaderSwitches;
 	stats.canvases = Canvas::canvasCount;
 	stats.images = Image::imageCount;

+ 17 - 66
src/modules/graphics/opengl/Graphics.h

@@ -62,45 +62,6 @@ namespace graphics
 namespace opengl
 {
 
-struct PassInfo
-{
-	enum BeginAction
-	{
-		BEGIN_LOAD,
-		BEGIN_CLEAR,
-		BEGIN_DISCARD,
-	};
-
-	enum EndAction
-	{
-		END_STORE,
-		END_DISCARD,
-	};
-
-	struct ColorAttachment
-	{
-		Canvas *canvas = nullptr;
-		Colorf clearColor = Colorf(0.0f, 0.0f, 0.0f, 0.0f);
-		BeginAction beginAction = BEGIN_LOAD;
-	};
-
-	ColorAttachment colorAttachments[MAX_COLOR_RENDER_TARGETS];
-	int colorAttachmentCount = 0;
-
-	bool stencil = false;
-
-	bool addColorAttachment(const ColorAttachment &attachment)
-	{
-		if (colorAttachmentCount + 1 < MAX_COLOR_RENDER_TARGETS)
-		{
-			colorAttachments[colorAttachmentCount++] = attachment;
-			return true;
-		}
-
-		return false;
-	}
-};
-
 class Graphics : public love::graphics::Graphics
 {
 public:
@@ -135,14 +96,13 @@ public:
 	 **/
 	void reset();
 
-	void beginPass(PassInfo::BeginAction beginAction, Colorf clearColor);
-	void beginPass(const PassInfo &info);
+	void clear(Colorf color);
+	void clear(const std::vector<OptionalColorf> &colors);
 
-	void endPass();
-	void endPass(int sX, int sY, int sW, int sH, const ScreenshotInfo *info, void *screenshotCallbackData);
+	void discard(const std::vector<bool> &colorbuffers, bool depthstencil);
 
-	const PassInfo &getActivePass() const;
-	virtual bool isPassActive() const;
+	virtual bool isCanvasActive() const;
+	bool isCanvasActive(Canvas *canvas) const;
 
 	/**
 	 * Flips buffers. (Rendered geometry is presented on screen).
@@ -159,14 +119,18 @@ public:
 	 **/
 	int getHeight() const;
 
-	int getPassWidth() const;
-	int getPassHeight() const;
-
 	/**
 	 * True if a graphics viewport is set.
 	 **/
 	bool isCreated() const;
 
+	void setCanvas(Canvas *canvas);
+	void setCanvas(const std::vector<Canvas *> &canvases);
+	void setCanvas(const std::vector<StrongRef<Canvas>> &canvases);
+	void setCanvas();
+
+	std::vector<Canvas *> getCanvas() const;
+
 	/**
 	 * Scissor defines a box such that everything outside that box is discarded and not drawn.
 	 * Scissoring is automatically enabled.
@@ -519,6 +483,8 @@ private:
 		StrongRef<Font> font;
 		StrongRef<Shader> shader;
 
+		std::vector<StrongRef<Canvas>> canvases;
+
 		ColorMask colorMask = ColorMask(true, true, true, true);
 
 		bool wireframe = false;
@@ -529,18 +495,6 @@ private:
 		float defaultMipmapSharpness = 0.0f;
 	};
 
-	struct CurrentPass
-	{
-		PassInfo info;
-		bool active = false;
-	};
-
-	struct PassBufferInfo
-	{
-		bool stencil;
-		Canvas *canvases[MAX_COLOR_RENDER_TARGETS];
-	};
-
 	struct CachedRenderbuffer
 	{
 		int w;
@@ -553,7 +507,8 @@ private:
 	void restoreState(const DisplayState &s);
 	void restoreStateChecked(const DisplayState &s);
 
-	void bindCachedFBOForPass(const PassInfo &pass);
+	void endPass();
+	void bindCachedFBO(const std::vector<Canvas *> &canvases);
 	void discard(OpenGL::FramebufferTarget target, const std::vector<bool> &colorbuffers, bool depthstencil);
 	GLuint attachCachedStencilBuffer(int w, int h, int samples);
 
@@ -577,16 +532,12 @@ private:
 	bool created;
 	bool active;
 
-	bool canCaptureScreenshot;
-
-	CurrentPass currentPass;
-
 	bool writingToStencil;
 
 	std::vector<DisplayState> states;
 	std::vector<StackType> stackTypes; // Keeps track of the pushed stack types.
 
-	int renderPassCount;
+	int canvasSwitchCount;
 
 	static const size_t MAX_USER_STACK_DEPTH = 64;
 

+ 1 - 1
src/modules/graphics/opengl/Shader.cpp

@@ -806,7 +806,7 @@ void Shader::checkSetScreenParams()
 	Rect view = gl.getViewport();
 
 	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
-	bool canvasActive = gfx->getActivePass().colorAttachmentCount > 0;
+	bool canvasActive = gfx->isCanvasActive();
 
 	if (view == lastViewport && canvasWasActive == canvasActive)
 		return;

+ 51 - 0
src/modules/graphics/opengl/wrap_Canvas.cpp

@@ -52,10 +52,61 @@ int w_Canvas_getMSAA(lua_State *L)
 	return 1;
 }
 
+int w_Canvas_renderTo(lua_State *L)
+{
+	Canvas *canvas = luax_checkcanvas(L, 1);
+	luaL_checktype(L, 2, LUA_TFUNCTION);
+
+	auto graphics = Module::getInstance<Graphics>(Module::M_GRAPHICS);
+
+	if (graphics)
+	{
+		// Save the current Canvas so we can restore it when we're done.
+		std::vector<Canvas *> oldcanvases = graphics->getCanvas();
+
+		for (Canvas *c : oldcanvases)
+			c->retain();
+
+		luax_catchexcept(L, [&](){ graphics->setCanvas(canvas); });
+
+		lua_settop(L, 2); // make sure the function is on top of the stack
+		int status = lua_pcall(L, 0, 0, 0);
+
+		graphics->setCanvas(oldcanvases);
+
+		for (Canvas *c : oldcanvases)
+			c->release();
+
+		if (status != 0)
+			return lua_error(L);
+	}
+	
+	return 0;
+}
+
+int w_Canvas_newImageData(lua_State *L)
+{
+	Canvas *canvas = luax_checkcanvas(L, 1);
+	love::image::Image *image = luax_getmodule<love::image::Image>(L, love::image::Image::type);
+	int x = (int) luaL_optnumber(L, 2, 0);
+	int y = (int) luaL_optnumber(L, 3, 0);
+	int w = (int) luaL_optnumber(L, 4, canvas->getWidth());
+	int h = (int) luaL_optnumber(L, 5, canvas->getHeight());
+
+	love::image::ImageData *img = nullptr;
+	luax_catchexcept(L, [&](){ img = canvas->newImageData(image, x, y, w, h); });
+
+	luax_pushtype(L, img);
+	img->release();
+	return 1;
+}
+
 static const luaL_Reg w_Canvas_functions[] =
 {
 	{ "getFormat", w_Canvas_getFormat },
 	{ "getMSAA", w_Canvas_getMSAA },
+	{ "renderTo", w_Canvas_renderTo },
+	{ "newImageData", w_Canvas_newImageData },
 	{ 0, 0 }
 };
 

+ 117 - 226
src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -62,6 +62,76 @@ int w_reset(lua_State *)
 	return 0;
 }
 
+int w_clear(lua_State *L)
+{
+	Colorf color;
+
+	if (lua_isnoneornil(L, 1))
+		color.set(0.0, 0.0, 0.0, 0.0);
+	else if (lua_istable(L, 1))
+	{
+		std::vector<Graphics::OptionalColorf> colors((size_t) lua_gettop(L));
+
+		for (int i = 0; i < lua_gettop(L); i++)
+		{
+			if (lua_isnoneornil(L, i + 1) || luax_objlen(L, i + 1) == 0)
+			{
+				colors[i].enabled = false;
+				continue;
+			}
+
+			for (int j = 1; j <= 4; j++)
+				lua_rawgeti(L, i + 1, j);
+
+			colors[i].enabled = true;
+			colors[i].c.r = (float) luaL_checknumber(L, -4);
+			colors[i].c.g = (float) luaL_checknumber(L, -3);
+			colors[i].c.b = (float) luaL_checknumber(L, -2);
+			colors[i].c.a = (float) luaL_optnumber(L, -1, 1.0);
+
+			lua_pop(L, 4);
+		}
+
+		luax_catchexcept(L, [&]() { instance()->clear(colors); });
+		return 0;
+	}
+	else
+	{
+		color.r = (float) luaL_checknumber(L, 1);
+		color.g = (float) luaL_checknumber(L, 2);
+		color.b = (float) luaL_checknumber(L, 3);
+		color.a = (float) luaL_optnumber(L, 4, 1.0);
+	}
+
+	luax_catchexcept(L, [&]() { instance()->clear(color); });
+	return 0;
+}
+
+int w_discard(lua_State *L)
+{
+	std::vector<bool> colorbuffers;
+
+	if (lua_istable(L, 1))
+	{
+		for (size_t i = 1; i <= luax_objlen(L, 1); i++)
+		{
+			lua_rawgeti(L, 1, i);
+			colorbuffers.push_back(luax_optboolean(L, -1, true));
+			lua_pop(L, 1);
+		}
+	}
+	else
+	{
+		bool discardcolor = luax_optboolean(L, 1, true);
+		size_t numbuffers = std::max((size_t) 1, instance()->getCanvas().size());
+		colorbuffers = std::vector<bool>(numbuffers, discardcolor);
+	}
+
+	bool stencil = luax_optboolean(L, 2, true);
+	instance()->discard(colorbuffers, stencil);
+	return 0;
+}
+
 int w_present(lua_State *L)
 {
 	luax_catchexcept(L, [&]() { instance()->present(L); });
@@ -105,149 +175,64 @@ int w_getDimensions(lua_State *L)
 	return 2;
 }
 
-int w_getPassWidth(lua_State *L)
-{
-	lua_pushinteger(L, instance()->getPassWidth());
-	return 1;
-}
-
-int w_getPassHeight(lua_State *L)
-{
-	lua_pushinteger(L, instance()->getPassHeight());
-	return 1;
-}
-
-int w_getPassDimensions(lua_State *L)
-{
-	lua_pushinteger(L, instance()->getPassWidth());
-	lua_pushinteger(L, instance()->getPassHeight());
-	return 2;
-}
-
-static int w__beginPass(lua_State *L)
+int w_setCanvas(lua_State *L)
 {
-	int nextstartidx = 1;
+	// Disable stencil writes.
+	instance()->stopDrawToStencilBuffer();
 
+	// called with none -> reset to default buffer
 	if (lua_isnoneornil(L, 1))
 	{
-		luax_catchexcept(L, [&]() { instance()->beginPass(PassInfo::BEGIN_LOAD, Colorf()); });
-		nextstartidx = 1;
+		instance()->setCanvas();
+		return 0;
 	}
-	else if (lua_isnumber(L, 1))
-	{
-		Colorf c;
-		c.r = (float) luaL_checknumber(L, 1);
-		c.g = (float) luaL_checknumber(L, 2);
-		c.b = (float) luaL_checknumber(L, 3);
 
-		if (lua_isnumber(L, 4))
-		{
-			c.a = (float) lua_tonumber(L, 4);
-			nextstartidx = 5;
-		}
-		else
-		{
-			c.a = 1.0f;
-			nextstartidx = 4;
-		}
+	bool is_table = lua_istable(L, 1);
+	std::vector<Canvas *> canvases;
 
-		luax_catchexcept(L, [&]() { instance()->beginPass(PassInfo::BEGIN_CLEAR, c); });
-	}
-	else if (luax_istype(L, 1, Canvas::type))
+	if (is_table)
 	{
-		PassInfo::ColorAttachment attachment;
-		attachment.canvas = luax_checkcanvas(L, 1);
-		attachment.beginAction = PassInfo::BEGIN_LOAD;
-
-		if (lua_isnumber(L, 2))
-		{
-			attachment.beginAction = PassInfo::BEGIN_CLEAR;
-			attachment.clearColor.r = (float) luaL_checknumber(L, 2);
-			attachment.clearColor.g = (float) luaL_checknumber(L, 3);
-			attachment.clearColor.b = (float) luaL_checknumber(L, 4);
-
-			if (lua_isnumber(L, 5))
-			{
-				attachment.clearColor.a = (float) lua_tonumber(L, 5);
-				nextstartidx = 6;
-			}
-			else
-			{
-				attachment.clearColor.a = 1.0f;
-				nextstartidx = 5;
-			}
-		}
-		else
-			nextstartidx = 2;
-
-		PassInfo info;
-		info.addColorAttachment(attachment);
-
-		if (lua_isboolean(L, nextstartidx))
+		for (int i = 1; i <= (int) luax_objlen(L, 1); i++)
 		{
-			info.stencil = luax_toboolean(L, nextstartidx);
-			nextstartidx++;
+			lua_rawgeti(L, 1, i);
+			canvases.push_back(luax_checkcanvas(L, -1));
+			lua_pop(L, 1);
 		}
-		else
-			info.stencil = false;
-
-		luax_catchexcept(L, [&]() { instance()->beginPass(info); });
 	}
 	else
 	{
-		luaL_checktype(L, 1, LUA_TTABLE);
-
-		int nattachments = std::max((int) luax_objlen(L, 1), 1);
-
-		if (nattachments > MAX_COLOR_RENDER_TARGETS)
-			return luaL_error(L, "Cannot render to %d Canvases at once!", nattachments);
-
-		PassInfo info;
-
-		for (int i = 1; i <= nattachments; i++)
-		{
-			lua_rawgeti(L, 1, i);
-			luaL_checktype(L, -1, LUA_TTABLE);
-
-			PassInfo::ColorAttachment attachment;
-			attachment.beginAction = PassInfo::BEGIN_LOAD;
-
-			lua_rawgeti(L, -1, 1);
-			attachment.canvas = luax_checkcanvas(L, -1);
-
-			lua_rawgeti(L, -2, 2);
-			if (!lua_isnoneornil(L, -1))
-			{
-				attachment.beginAction = PassInfo::BEGIN_CLEAR;
-
-				for (int j = 3; j < 6; j++)
-					lua_rawgeti(L, -j, j);
-
-				attachment.clearColor.r = (float) luaL_checknumber(L, -4);
-				attachment.clearColor.g = (float) luaL_checknumber(L, -3);
-				attachment.clearColor.b = (float) luaL_checknumber(L, -2);
-				attachment.clearColor.a = (float) luaL_optnumber(L, -1, 1.0);
-			}
-
-			lua_pop(L, 2 + (attachment.beginAction == PassInfo::BEGIN_CLEAR ? 4 : 1));
-
-			info.addColorAttachment(attachment);
-		}
-
-		info.stencil = luax_boolflag(L, 1, "stencil", false);
-
-		luax_catchexcept(L, [&]() { instance()->beginPass(info); });
-
-		nextstartidx = 2;
+		for (int i = 1; i <= lua_gettop(L); i++)
+			canvases.push_back(luax_checkcanvas(L, i));
 	}
 
-	return nextstartidx;
+	luax_catchexcept(L, [&]() {
+		if (canvases.size() > 0)
+			instance()->setCanvas(canvases);
+		else
+			instance()->setCanvas();
+	});
+	
+	return 0;
 }
 
-int w_beginPass(lua_State *L)
+int w_getCanvas(lua_State *L)
 {
-	w__beginPass(L);
-	return 0;
+	const std::vector<Canvas *> canvases = instance()->getCanvas();
+	int n = 0;
+
+	for (Canvas *c : canvases)
+	{
+		luax_pushtype(L, c);
+		n++;
+	}
+
+	if (n == 0)
+	{
+		lua_pushnil(L);
+		n = 1;
+	}
+	
+	return n;
 }
 
 static void screenshotCallback(love::image::ImageData *i, Reference *ref, void *gd)
@@ -264,96 +249,6 @@ static void screenshotCallback(love::image::ImageData *i, Reference *ref, void *
 		delete ref;
 }
 
-static int w__endPass(lua_State *L, int startidx)
-{
-	if (lua_isnoneornil(L, startidx))
-	{
-		luax_catchexcept(L, []() { instance()->endPass(); });
-	}
-	else
-	{
-		int x, y, w, h;
-
-		if (lua_isnumber(L, startidx))
-		{
-			x = (int) luaL_checknumber(L, 1);
-			y = (int) luaL_checknumber(L, 2);
-			w = (int) luaL_checknumber(L, 3);
-			h = (int) luaL_checknumber(L, 4);
-			startidx += 4;
-		}
-		else
-		{
-			x = 0;
-			y = 0;
-			w = instance()->getPassWidth();
-			h = instance()->getPassHeight();
-		}
-
-		luaL_checktype(L, startidx, LUA_TFUNCTION);
-
-		Graphics::ScreenshotInfo info;
-		info.callback = screenshotCallback;
-
-		lua_pushvalue(L, startidx);
-		info.ref = luax_refif(L, LUA_TFUNCTION);
-		lua_pop(L, 1);
-
-		luax_catchexcept(L,
-			[&]() { instance()->endPass(x, y, w, h, &info, L); },
-			[&](bool except) { if (except) delete info.ref; }
-		);
-	}
-
-	return 0;
-}
-
-int w_endPass(lua_State *L)
-{
-	return w__endPass(L, 1);
-}
-
-int w_renderPass(lua_State *L)
-{
-	int startidx = w__beginPass(L);
-
-	if (lua_type(L, startidx) != LUA_TFUNCTION)
-	{
-		w__endPass(L, startidx + 1);
-		luaL_checktype(L, startidx, LUA_TFUNCTION);
-		return 0;
-	}
-
-	int nargs = lua_gettop(L) - startidx;
-	int status = lua_pcall(L, nargs, 0, 0);
-
-	w__endPass(L, startidx + 1);
-
-	if (status != 0)
-		return lua_error(L);
-
-	return 0;
-}
-
-int w_isPassActive(lua_State *L)
-{
-	luax_pushboolean(L, instance()->isPassActive());
-	return 1;
-}
-
-int w_getPassCanvases(lua_State *L)
-{
-	if (!instance()->isPassActive())
-		return 0;
-
-	const PassInfo &info = instance()->getActivePass();
-
-	for (const auto &attachment : info.colorAttachments)
-		luax_pushtype(L, attachment.canvas);
-
-	return info.colorAttachmentCount;
-}
-
 int w_captureScreenshot(lua_State *L)
 {
 	luaL_checktype(L, 1, LUA_TFUNCTION);
@@ -1600,8 +1495,8 @@ int w_getStats(lua_State *L)
 	lua_pushinteger(L, stats.drawCalls);
 	lua_setfield(L, -2, "drawcalls");
 
-	lua_pushinteger(L, stats.renderPasses);
-	lua_setfield(L, -2, "renderpasses");
+	lua_pushinteger(L, stats.canvasSwitches);
+	lua_setfield(L, -2, "canvasswitches");
 
 	lua_pushinteger(L, stats.shaderSwitches);
 	lua_setfield(L, -2, "shaderswitches");
@@ -2142,6 +2037,8 @@ int w_inverseTransformPoint(lua_State *L)
 static const luaL_Reg functions[] =
 {
 	{ "reset", w_reset },
+	{ "clear", w_clear },
+	{ "discard", w_discard },
 	{ "present", w_present },
 
 	{ "newImage", w_newImage },
@@ -2156,6 +2053,9 @@ static const luaL_Reg functions[] =
 	{ "newText", w_newText },
 	{ "_newVideo", w_newVideo },
 
+	{ "setCanvas", w_setCanvas },
+	{ "getCanvas", w_getCanvas },
+
 	{ "setColor", w_setColor },
 	{ "getColor", w_getColor },
 	{ "setBackgroundColor", w_setBackgroundColor },
@@ -2195,12 +2095,6 @@ static const luaL_Reg functions[] =
 	{ "getSystemLimits", w_getSystemLimits },
 	{ "getStats", w_getStats },
 
-	{ "beginPass", w_beginPass },
-	{ "endPass", w_endPass },
-	{ "renderPass", w_renderPass },
-	{ "isPassActive", w_isPassActive },
-	{ "getPassCanvases", w_getPassCanvases },
-
 	{ "captureScreenshot", w_captureScreenshot },
 
 	{ "draw", w_draw },
@@ -2214,9 +2108,6 @@ static const luaL_Reg functions[] =
 	{ "getWidth", w_getWidth },
 	{ "getHeight", w_getHeight },
 	{ "getDimensions", w_getDimensions },
-	{ "getPassWidth", w_getPassWidth },
-	{ "getPassHeight", w_getPassHeight },
-	{ "getPassDimensions", w_getPassDimensions },
 
 	{ "setScissor", w_setScissor },
 	{ "intersectScissor", w_intersectScissor },

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

@@ -411,8 +411,8 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 	if (!graphics.get())
 		graphics.set(Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS));
 
-	if (graphics.get() && graphics->isPassActive())
-		throw love::Exception("setMode cannot be called while a render pass is active in love.graphics.");
+	if (graphics.get() && graphics->isCanvasActive())
+		throw love::Exception("setMode cannot be called while a Canvas is active in love.graphics.");
 
 	WindowSettings f;
 
@@ -627,8 +627,8 @@ void Window::close()
 {
 	if (graphics.get())
 	{
-		if (graphics->isPassActive())
-			throw love::Exception("close cannot be called while a render pass is active in love.graphics.");
+		if (graphics->isCanvasActive())
+			throw love::Exception("close cannot be called while a Canvas is active in love.graphics.");
 
 		graphics->unSetMode();
 	}
@@ -657,8 +657,8 @@ bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype)
 	if (!window)
 		return false;
 
-	if (graphics.get() && graphics->isPassActive())
-		throw love::Exception("setFullscreen cannot be called while a render pass is active in love.graphics.");
+	if (graphics.get() && graphics->isCanvasActive())
+		throw love::Exception("setFullscreen cannot be called while a Canvas is active in love.graphics.");
 
 	WindowSettings newsettings = settings;
 	newsettings.fullscreen = fullscreen;

+ 4 - 9
src/scripts/boot.lua

@@ -547,12 +547,9 @@ function love.run()
 
 		if love.graphics and love.graphics.isActive() then
 			love.graphics.origin()
+			love.graphics.clear(love.graphics.getBackgroundColor())
 
-			if love.drawpasses then love.drawpasses() end
-
-			love.graphics.beginPass(love.graphics.getBackgroundColor())
 			if love.draw then love.draw() end
-			love.graphics.endPass()
 
 			love.graphics.present()
 		end
@@ -606,9 +603,6 @@ function love.errhand(msg)
 	if love.audio then love.audio.stop() end
 
 	love.graphics.reset()
-	if love.graphics.isPassActive() then
-		love.graphics.endPass()
-	end
 
 	local font = love.graphics.setNewFont(math.floor(love.window.toPixels(14)))
 
@@ -636,8 +630,10 @@ function love.errhand(msg)
 	p = string.gsub(p, "%[string \"(.-)\"%]", "%1")
 
 	local function draw()
+		love.graphics.clear(89/255, 157/255, 220/255)
 		local pos = love.window.toPixels(70)
 		love.graphics.printf(p, pos, pos, love.graphics.getWidth() - pos)
+		love.graphics.present()
 	end
 
 	while true do
@@ -659,8 +655,7 @@ function love.errhand(msg)
 			end
 		end
 
-		love.graphics.renderPass(89/255, 157/255, 220/255, draw)
-		love.graphics.present()
+		draw()
 
 		if love.timer then
 			love.timer.sleep(0.1)

+ 10 - 21
src/scripts/boot.lua.h

@@ -1010,17 +1010,12 @@ const unsigned char boot_lua[] =
 	0x2e, 0x69, 0x73, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x28, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
 	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6f, 
 	0x72, 0x69, 0x67, 0x69, 0x6e, 0x28, 0x29, 0x0a,
-	0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x70, 0x61, 0x73, 
-	0x73, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x64, 0x72, 0x61, 0x77, 
-	0x70, 0x61, 0x73, 0x73, 0x65, 0x73, 0x28, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a,
-	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x62, 
-	0x65, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 
-	0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 
-	0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x29, 0x29, 0x0a,
+	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x63, 
+	0x6c, 0x65, 0x61, 0x72, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 
+	0x2e, 0x67, 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 
+	0x72, 0x28, 0x29, 0x29, 0x0a,
 	0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x20, 0x74, 0x68, 
 	0x65, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a,
-	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x65, 
-	0x6e, 0x64, 0x50, 0x61, 0x73, 0x73, 0x28, 0x29, 0x0a,
 	0x09, 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, 0x0a,
 	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
@@ -1106,12 +1101,6 @@ const unsigned char boot_lua[] =
 	0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x72, 0x65, 0x73, 
 	0x65, 0x74, 0x28, 0x29, 0x0a,
-	0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 
-	0x69, 0x73, 0x50, 0x61, 0x73, 0x73, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x28, 0x29, 0x20, 0x74, 0x68, 0x65, 
-	0x6e, 0x0a,
-	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x65, 0x6e, 
-	0x64, 0x50, 0x61, 0x73, 0x73, 0x28, 0x29, 0x0a,
-	0x09, 0x65, 0x6e, 0x64, 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, 0x73, 0x65, 0x74, 0x4e, 0x65, 0x77, 0x46, 0x6f, 
 	0x6e, 0x74, 0x28, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x28, 0x6c, 0x6f, 0x76, 0x65, 
@@ -1151,6 +1140,9 @@ const unsigned char boot_lua[] =
 	0x5c, 0x22, 0x25, 0x5d, 0x22, 0x2c, 0x20, 0x22, 0x25, 0x31, 0x22, 0x29, 0x0a,
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x72, 
 	0x61, 0x77, 0x28, 0x29, 0x0a,
+	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x63, 0x6c, 
+	0x65, 0x61, 0x72, 0x28, 0x38, 0x39, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x31, 0x35, 0x37, 0x2f, 0x32, 0x35, 
+	0x35, 0x2c, 0x20, 0x32, 0x32, 0x30, 0x2f, 0x32, 0x35, 0x35, 0x29, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x70, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 
 	0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x74, 0x6f, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x28, 0x37, 
 	0x30, 0x29, 0x0a,
@@ -1158,6 +1150,8 @@ const unsigned char boot_lua[] =
 	0x69, 0x6e, 0x74, 0x66, 0x28, 0x70, 0x2c, 0x20, 0x70, 0x6f, 0x73, 0x2c, 0x20, 0x70, 0x6f, 0x73, 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, 0x70, 0x6f, 0x73, 0x29, 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, 0x0a,
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x77, 0x68, 0x69, 0x6c, 0x65, 0x20, 0x74, 0x72, 0x75, 0x65, 0x20, 0x64, 0x6f, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x75, 0x6d, 0x70, 0x28, 
@@ -1194,12 +1188,7 @@ const unsigned char boot_lua[] =
 	0x09, 0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
-	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x72, 0x65, 
-	0x6e, 0x64, 0x65, 0x72, 0x50, 0x61, 0x73, 0x73, 0x28, 0x38, 0x39, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x31, 
-	0x35, 0x37, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x32, 0x30, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x64, 
-	0x72, 0x61, 0x77, 0x29, 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, 0x0a,
+	0x09, 0x09, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x0a,
 	0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x20, 0x74, 0x68, 
 	0x65, 0x6e, 0x0a,
 	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x2e, 0x73, 0x6c, 0x65, 0x65,