Parcourir la 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 il y a 8 ans
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,