Browse Source

Added antialiasing (MSAA) support to Canvases via a new optional parameter. Added Canvas:getFSAA.

Alex Szpakowski 11 years ago
parent
commit
7556fde5e6

+ 1 - 1
src/modules/graphics/Drawable.h

@@ -55,7 +55,7 @@ public:
 	 * @param kx Shear along the x-axis.
 	 * @param ky Shear along the y-axis.
 	 **/
-	virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const = 0;
+	virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) = 0;
 };
 
 } // graphics

+ 1 - 1
src/modules/graphics/Texture.h

@@ -88,7 +88,7 @@ public:
 	 * @param kx Shear along the x-axis.
 	 * @param ky Shear along the y-axis.
 	 **/
-	virtual void drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const = 0;
+	virtual void drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) = 0;
 
 	virtual int getWidth() const;
 	virtual int getHeight() const;

+ 246 - 23
src/modules/graphics/opengl/Canvas.cpp

@@ -63,12 +63,26 @@ struct FramebufferStrategy
 		return false;
 	}
 
+	/// Create a MSAA renderbuffer and attach it to the active FBO.
+	/**
+	 * @param[in]    width Width of the MSAA buffer
+	 * @param[in]    height Height of the MSAA buffer
+	 * @param[inout] samples Number of samples to use
+	 * @param[in]    internalformat The internal format to use for the buffer
+	 * @param[out]   buffer Name for the MSAA buffer
+	 * @return Whether the MSAA buffer was successfully created and attached
+	 **/
+	virtual bool createMSAABuffer(int, int, int &, GLenum, GLuint &)
+	{
+		return false;
+	}
+
 	/// remove objects
 	/**
 	 * @param[in] framebuffer   Framebuffer name
 	 * @param[in] depth_stencil Name for packed depth and stencil buffer
 	 */
-	virtual void deleteFBO(GLuint, GLuint) {}
+	virtual void deleteFBO(GLuint, GLuint, GLuint) {}
 	virtual void bindFBO(GLuint) {}
 
 	/// attach additional canvases to the active framebuffer for rendering
@@ -93,8 +107,11 @@ struct FramebufferStrategyGL3 : public FramebufferStrategy
 		glGenFramebuffers(1, &framebuffer);
 		glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
 
-		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
-			GL_TEXTURE_2D, texture, 0);
+		if (texture != 0)
+		{
+			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+			                       GL_TEXTURE_2D, texture, 0);
+		}
 
 		// check status
 		GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
@@ -118,13 +135,44 @@ struct FramebufferStrategyGL3 : public FramebufferStrategy
 		glBindRenderbuffer(GL_RENDERBUFFER, 0);
 
 		// check status
-		return glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
+		if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+		{
+			glDeleteRenderbuffers(1, &stencil);
+			stencil = 0;
+			return false;
+		}
+
+		return true;
+	}
+
+	virtual bool createMSAABuffer(int width, int height, int &samples, GLenum internalformat, GLuint &buffer)
+	{
+		glGenRenderbuffers(1, &buffer);
+		glBindRenderbuffer(GL_RENDERBUFFER, buffer);
+		glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, internalformat,
+		                                 width, height);
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+		                          GL_RENDERBUFFER, buffer);
+		glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples);
+
+		glBindRenderbuffer(GL_RENDERBUFFER, 0);
+
+		if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+		{
+			glDeleteRenderbuffers(1, &buffer);
+			buffer = 0;
+			return false;
+		}
+
+		return true;
 	}
 
-	virtual void deleteFBO(GLuint framebuffer, GLuint depth_stencil)
+	virtual void deleteFBO(GLuint framebuffer, GLuint depth_stencil, GLuint msaa_buffer)
 	{
 		if (depth_stencil != 0)
 			glDeleteRenderbuffers(1, &depth_stencil);
+		if (msaa_buffer != 0)
+			glDeleteRenderbuffers(1, &msaa_buffer);
 		if (framebuffer != 0)
 			glDeleteFramebuffers(1, &framebuffer);
 	}
@@ -192,7 +240,7 @@ struct FramebufferStrategyPackedEXT : public FramebufferStrategy
 	virtual bool createStencil(int width, int height, GLuint &stencil)
 	{
 		// create combined depth/stencil buffer
-		glDeleteRenderbuffers(1, &stencil);
+		glDeleteRenderbuffersEXT(1, &stencil);
 		glGenRenderbuffersEXT(1, &stencil);
 		glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, stencil);
 		glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_STENCIL_EXT,
@@ -203,13 +251,47 @@ struct FramebufferStrategyPackedEXT : public FramebufferStrategy
 		glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
 
 		// check status
-		return glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT;
+		if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+		{
+			glDeleteRenderbuffersEXT(1, &stencil);
+			stencil = 0;
+			return false;
+		}
+
+		return true;
 	}
 
-	virtual void deleteFBO(GLuint framebuffer, GLuint depth_stencil)
+	virtual bool createMSAABuffer(int width, int height, int &samples, GLenum internalformat, GLuint &buffer)
+	{
+		if (!GLEE_EXT_framebuffer_multisample)
+			return false;
+
+		glGenRenderbuffersEXT(1, &buffer);
+		glBindRenderbufferEXT(GL_RENDERBUFFER, buffer);
+		glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, samples,
+		                                    internalformat, width, height);
+		glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+		                             GL_RENDERBUFFER, buffer);
+		glGetRenderbufferParameterivEXT(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples);
+
+		glBindRenderbufferEXT(GL_RENDERBUFFER, 0);
+
+		if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+		{
+			glDeleteRenderbuffersEXT(1, &buffer);
+			buffer = 0;
+			return false;
+		}
+
+		return true;
+	}
+
+	virtual void deleteFBO(GLuint framebuffer, GLuint depth_stencil, GLuint msaa_buffer)
 	{
 		if (depth_stencil != 0)
 			glDeleteRenderbuffersEXT(1, &depth_stencil);
+		if (msaa_buffer != 0)
+			glDeleteRenderbuffersEXT(1, &msaa_buffer);
 		if (framebuffer != 0)
 			glDeleteFramebuffersEXT(1, &framebuffer);
 	}
@@ -257,7 +339,7 @@ struct FramebufferStrategyEXT : public FramebufferStrategyPackedEXT
 	virtual bool createStencil(int width, int height, GLuint &stencil)
 	{
 		// create stencil buffer
-		glDeleteRenderbuffers(1, &stencil);
+		glDeleteRenderbuffersEXT(1, &stencil);
 		glGenRenderbuffersEXT(1, &stencil);
 		glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, stencil);
 		glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_STENCIL_INDEX,
@@ -268,14 +350,21 @@ struct FramebufferStrategyEXT : public FramebufferStrategyPackedEXT
 		glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
 
 		// check status
-		return glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT;
+		if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+		{
+			glDeleteRenderbuffersEXT(1, &stencil);
+			stencil = 0;
+			return false;
+		}
+
+		return true;
 	}
 
 	bool isSupported()
 	{
 		GLuint fb = 0, stencil = 0;
 		GLenum status = createFBO(fb, 0);
-		deleteFBO(fb, stencil);
+		deleteFBO(fb, stencil, 0);
 		return status == GL_FRAMEBUFFER_COMPLETE;
 	}
 };
@@ -311,11 +400,15 @@ static void getStrategy()
 static int maxFBOColorAttachments = 0;
 static int maxDrawBuffers = 0;
 
-Canvas::Canvas(int width, int height, TextureType texture_type)
+Canvas::Canvas(int width, int height, TextureType texture_type, int fsaa)
 	: fbo(0)
+    , resolve_fbo(0)
 	, texture(0)
+    , fsaa_buffer(0)
 	, depth_stencil(0)
 	, texture_type(texture_type)
+    , fsaa_samples(fsaa)
+	, fsaa_dirty(false)
 {
 	this->width = width;
 	this->height = height;
@@ -357,9 +450,50 @@ Canvas::~Canvas()
 	unloadVolatile();
 }
 
+bool Canvas::createFSAAFBO(GLenum internalformat)
+{
+	// Create our FBO without a texture.
+	status = strategy->createFBO(fbo, 0);
+
+	GLuint previous = 0;
+	if (current != this)
+	{
+		if (current != nullptr)
+			previous = current->fbo;
+
+		strategy->bindFBO(fbo);
+	}
+
+	// Create and attach the MSAA buffer for our FBO.
+	if (strategy->createMSAABuffer(width, height, fsaa_samples, internalformat, fsaa_buffer))
+		status = GL_FRAMEBUFFER_COMPLETE;
+	else
+		status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
+
+	// Create the FBO used for the MSAA resolve, and attach the texture.
+	if (status == GL_FRAMEBUFFER_COMPLETE)
+		status = strategy->createFBO(resolve_fbo, texture);
+
+	if (status != GL_FRAMEBUFFER_COMPLETE)
+	{
+		// Clean up.
+		strategy->deleteFBO(fbo, 0, fsaa_buffer);
+		strategy->deleteFBO(resolve_fbo, 0, 0);
+		fbo = fsaa_buffer = resolve_fbo = 0;
+		fsaa_samples = 0;
+	}
+
+	if (current != this)
+		strategy->bindFBO(previous);
+
+	return status == GL_FRAMEBUFFER_COMPLETE;
+}
+
 bool Canvas::loadVolatile()
 {
 	fbo = depth_stencil = texture = 0;
+	resolve_fbo = fsaa_buffer = 0;
+	status = GL_FRAMEBUFFER_COMPLETE;
 
 	// glTexImage2D is guaranteed to error in this case.
 	if (width > gl.getMaxTextureSize() || height > gl.getMaxTextureSize())
@@ -406,7 +540,25 @@ bool Canvas::loadVolatile()
 		return false;
 	}
 
-	status = strategy->createFBO(fbo, texture);
+	int max_samples = 0;
+	if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object
+		|| GLEE_EXT_framebuffer_multisample)
+	{
+		glGetIntegerv(GL_MAX_SAMPLES, &max_samples);
+	}
+
+	if (fsaa_samples > max_samples)
+		fsaa_samples = max_samples;
+
+	// Try to create a FSAA FBO if requested.
+	bool fsaasuccess = false;
+	if (fsaa_samples > 1)
+		fsaasuccess = createFSAAFBO(internalformat);
+
+	// On failure (or no requested FSAA), fall back to a regular FBO.
+	if (!fsaasuccess)
+		status = strategy->createFBO(fbo, texture);
+
 	if (status != GL_FRAMEBUFFER_COMPLETE)
 		return false;
 
@@ -416,11 +568,13 @@ bool Canvas::loadVolatile()
 
 void Canvas::unloadVolatile()
 {
-	strategy->deleteFBO(fbo, depth_stencil);
+	strategy->deleteFBO(fbo, depth_stencil, fsaa_buffer);
+	strategy->deleteFBO(resolve_fbo, 0, 0);
 
 	gl.deleteTexture(texture);
 
 	fbo = depth_stencil = texture = 0;
+	resolve_fbo = fsaa_buffer = 0;
 
 	for (size_t i = 0; i < attachedCanvases.size(); i++)
 		attachedCanvases[i]->release();
@@ -428,13 +582,12 @@ void Canvas::unloadVolatile()
 	attachedCanvases.clear();
 }
 
-void Canvas::drawv(const Matrix &t, const Vertex *v) const
+void Canvas::drawv(const Matrix &t, const Vertex *v)
 {
 	glPushMatrix();
-
 	glMultMatrixf((const GLfloat *)t.getElements());
 
-	gl.bindTexture(texture);
+	predraw();
 
 	glEnableClientState(GL_VERTEX_ARRAY);
 	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
@@ -447,11 +600,13 @@ void Canvas::drawv(const Matrix &t, const Vertex *v) const
 
 	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 	glDisableClientState(GL_VERTEX_ARRAY);
+
+	postdraw();
 	
 	glPopMatrix();
 }
 
-void Canvas::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const
+void Canvas::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 	static Matrix t;
 	t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
@@ -459,7 +614,7 @@ void Canvas::draw(float x, float y, float angle, float sx, float sy, float ox, f
 	drawv(t, vertices);
 }
 
-void Canvas::drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const
+void Canvas::drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 	static Matrix t;
 	t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
@@ -487,8 +642,12 @@ GLuint Canvas::getGLTexture() const
 	return texture;
 }
 
-void Canvas::predraw() const
+void Canvas::predraw()
 {
+	// We need to make sure the texture is up-to-date by resolving the MSAA
+	// buffer (which we render to when the canvas is active) to it.
+	resolveMSAA();
+
 	gl.bindTexture(texture);
 }
 
@@ -520,6 +679,9 @@ void Canvas::setupGrab()
 
 	// indicate we are using this fbo
 	current = this;
+
+	if (fsaa_buffer != 0)
+		fsaa_dirty = true;
 }
 
 void Canvas::startGrab(const std::vector<Canvas *> &canvases)
@@ -535,6 +697,9 @@ void Canvas::startGrab(const std::vector<Canvas *> &canvases)
 
 		if (canvases.size()+1 > size_t(maxDrawBuffers) || canvases.size()+1 > size_t(maxFBOColorAttachments))
 			throw love::Exception("This system can't simultaniously render to %d canvases.", canvases.size()+1);
+
+		if (fsaa_samples != 0)
+			throw love::Exception("Multi-canvas rendering is not supported with FSAA.");
 	}
 
 	for (size_t i = 0; i < canvases.size(); i++)
@@ -545,6 +710,9 @@ void Canvas::startGrab(const std::vector<Canvas *> &canvases)
 		if (canvases[i]->getTextureType() != texture_type)
 			throw love::Exception("All canvas arguments must have the same texture type.");
 
+		if (canvases[i]->getFSAA() != 0)
+			throw love::Exception("Multi-canvas rendering is not supported with FSAA.");
+
 		if (!canvaseschanged && canvases[i] != attachedCanvases[i])
 			canvaseschanged = true;
 	}
@@ -674,12 +842,22 @@ bool Canvas::checkCreateStencil()
 
 love::image::ImageData *Canvas::getImageData(love::image::Image *image)
 {
+	resolveMSAA();
+
 	int row = 4 * width;
 	int size = row * height;
 	GLubyte *pixels  = new GLubyte[size];
 
-	strategy->bindFBO(fbo);
+	// Our texture is attached to 'resolve_fbo' when we use MSAA.
+	if (fsaa_samples > 1 && (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object))
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo);
+	else if (fsaa_samples > 1 && GLEE_EXT_framebuffer_multisample)
+		glBindFramebufferEXT(GL_READ_FRAMEBUFFER, resolve_fbo);
+	else
+		strategy->bindFBO(fbo);
+
 	glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+
 	if (current)
 		strategy->bindFBO(current->fbo);
 	else
@@ -692,10 +870,17 @@ love::image::ImageData *Canvas::getImageData(love::image::Image *image)
 
 void Canvas::getPixel(unsigned char* pixel_rgba, int x, int y)
 {
-	if (current != this)
+	resolveMSAA();
+
+	// Our texture is attached to 'resolve_fbo' when we use MSAA.
+	if (fsaa_samples > 1 && (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object))
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo);
+	else if (fsaa_samples > 1 && GLEE_EXT_framebuffer_multisample)
+		glBindFramebufferEXT(GL_READ_FRAMEBUFFER, resolve_fbo);
+	else if (current != this)
 		strategy->bindFBO(fbo);
 
-	glReadPixels(x, height - y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel_rgba);
+	glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel_rgba);
 
 	if (current && current != this)
 		strategy->bindFBO(current->fbo);
@@ -703,6 +888,44 @@ void Canvas::getPixel(unsigned char* pixel_rgba, int x, int y)
 		strategy->bindFBO(0);
 }
 
+bool Canvas::resolveMSAA()
+{
+	if (resolve_fbo == 0 || fsaa_buffer == 0)
+		return false;
+
+	if (!fsaa_dirty)
+		return true;
+
+	GLuint previous = 0;
+	if (current != nullptr)
+		previous = current->fbo;
+
+	// Do the MSAA resolve by blitting the MSAA renderbuffer to the texture.
+	if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object)
+	{
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
+		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo);
+		glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
+						  GL_COLOR_BUFFER_BIT, GL_NEAREST);
+	}
+	else if (GLEE_EXT_framebuffer_multisample && GLEE_EXT_framebuffer_blit)
+	{
+		glBindFramebufferEXT(GL_READ_FRAMEBUFFER, fbo);
+		glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, resolve_fbo);
+		glBlitFramebufferEXT(0, 0, width, height, 0, 0, width, height,
+							 GL_COLOR_BUFFER_BIT, GL_NEAREST);
+	}
+	else
+		return false;
+
+	strategy->bindFBO(previous);
+
+	if (current != this)
+		fsaa_dirty = false;
+
+	return true;
+}
+
 bool Canvas::isSupported()
 {
 	getStrategy();

+ 20 - 5
src/modules/graphics/opengl/Canvas.h

@@ -46,7 +46,7 @@ public:
 		TYPE_MAX_ENUM
 	};
 
-	Canvas(int width, int height, TextureType texture_type = TYPE_NORMAL);
+	Canvas(int width, int height, TextureType texture_type = TYPE_NORMAL, int fsaa = 0);
 	virtual ~Canvas();
 
 	// Implements Volatile.
@@ -54,14 +54,14 @@ public:
 	virtual void unloadVolatile();
 
 	// Implements Drawable.
-	virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const;
+	virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 
 	// Implements Texture.
-	virtual void drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const;
+	virtual void drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 	virtual void setFilter(const Texture::Filter &f);
 	virtual void setWrap(const Texture::Wrap &w);
 	virtual GLuint getGLTexture() const;
-	virtual void predraw() const;
+	virtual void predraw();
 
 	/**
 	 * @param canvases A list of other canvases to temporarily attach to this one,
@@ -97,6 +97,13 @@ public:
 		return texture_type;
 	}
 
+	inline int getFSAA() const
+	{
+		return fsaa_samples;
+	}
+
+	bool resolveMSAA();
+
 	static bool isSupported();
 	static bool isHDRSupported();
 	static bool isMultiCanvasSupported();
@@ -112,8 +119,13 @@ public:
 
 private:
 
+	bool createFSAAFBO(GLenum internalformat);
+
 	GLuint fbo;
+	GLuint resolve_fbo;
+
 	GLuint texture;
+	GLuint fsaa_buffer;
 	GLuint depth_stencil;
 
 	TextureType texture_type;
@@ -122,8 +134,11 @@ private:
 
 	std::vector<Canvas *> attachedCanvases;
 
+	int fsaa_samples;
+	bool fsaa_dirty;
+
 	void setupGrab();
-	void drawv(const Matrix &t, const Vertex *v) const;
+	void drawv(const Matrix &t, const Vertex *v);
 
 	static StringMap<TextureType, TYPE_MAX_ENUM>::Entry textureTypeEntries[];
 	static StringMap<TextureType, TYPE_MAX_ENUM> textureTypes;

+ 2 - 2
src/modules/graphics/opengl/Graphics.cpp

@@ -392,7 +392,7 @@ ParticleSystem *Graphics::newParticleSystem(Texture *texture, int size)
 	return new ParticleSystem(texture, size);
 }
 
-Canvas *Graphics::newCanvas(int width, int height, Canvas::TextureType texture_type)
+Canvas *Graphics::newCanvas(int width, int height, Canvas::TextureType texture_type, int fsaa)
 {
 	if (texture_type == Canvas::TYPE_HDR && !Canvas::isHDRSupported())
 		throw Exception("HDR Canvases are not supported by your OpenGL implementation");
@@ -405,7 +405,7 @@ Canvas *Graphics::newCanvas(int width, int height, Canvas::TextureType texture_t
 	while (GL_NO_ERROR != glGetError())
 		/* clear opengl error flag */;
 
-	Canvas *canvas = new Canvas(width, height, texture_type);
+	Canvas *canvas = new Canvas(width, height, texture_type, fsaa);
 	GLenum err = canvas->getStatus();
 
 	// everything ok, return canvas (early out)

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

@@ -208,7 +208,7 @@ public:
 
 	ParticleSystem *newParticleSystem(Texture *texture, int size);
 
-	Canvas *newCanvas(int width, int height, Canvas::TextureType texture_type = Canvas::TYPE_NORMAL);
+	Canvas *newCanvas(int width, int height, Canvas::TextureType texture_type = Canvas::TYPE_NORMAL, int fsaa = 0);
 
 	Shader *newShader(const Shader::ShaderSources &sources);
 

+ 5 - 5
src/modules/graphics/opengl/Image.cpp

@@ -91,7 +91,7 @@ love::image::CompressedData *Image::getCompressedData() const
 	return cdata;
 }
 
-void Image::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const
+void Image::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 	Matrix t;
 	t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
@@ -99,7 +99,7 @@ void Image::draw(float x, float y, float angle, float sx, float sy, float ox, fl
 	drawv(t, vertices);
 }
 
-void Image::drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const
+void Image::drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 	Matrix t;
 	t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
@@ -107,7 +107,7 @@ void Image::drawq(Quad *quad, float x, float y, float angle, float sx, float sy,
 	drawv(t, quad->getVertices());
 }
 
-void Image::predraw() const
+void Image::predraw()
 {
 	bind();
 
@@ -121,7 +121,7 @@ void Image::predraw() const
 	}
 }
 
-void Image::postdraw() const
+void Image::postdraw()
 {
 	if (width != paddedWidth || height != paddedHeight)
 	{
@@ -511,7 +511,7 @@ void Image::uploadDefaultTexture()
 	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, px);
 }
 
-void Image::drawv(const Matrix &t, const Vertex *v) const
+void Image::drawv(const Matrix &t, const Vertex *v)
 {
 	predraw();
 

+ 5 - 5
src/modules/graphics/opengl/Image.h

@@ -76,20 +76,20 @@ public:
 	/**
 	 * @copydoc Drawable::draw()
 	 **/
-	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const;
+	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 
 	/**
 	 * @copydoc Texture::drawq()
 	 **/
-	void drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const;
+	void drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 
 	/**
 	 * Call before using this Image's texture to draw. Binds the texture,
 	 * globally scales texture coordinates if the Image has NPOT dimensions and
 	 * NPOT isn't supported, etc.
 	 **/
-	virtual void predraw() const;
-	virtual void postdraw() const;
+	virtual void predraw();
+	virtual void postdraw();
 
 	virtual GLuint getGLTexture() const;
 
@@ -137,7 +137,7 @@ private:
 
 	void uploadDefaultTexture();
 
-	void drawv(const Matrix &t, const Vertex *v) const;
+	void drawv(const Matrix &t, const Vertex *v);
 
 	// The ImageData from which the texture is created. May be null if
 	// Compressed image data was used to create the texture.

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

@@ -288,7 +288,7 @@ bool Mesh::isWireframe() const
 	return wireframe;
 }
 
-void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const
+void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 	const size_t pos_offset   = offsetof(Vertex, x);
 	const size_t tex_offset   = offsetof(Vertex, s);

+ 1 - 1
src/modules/graphics/opengl/Mesh.h

@@ -172,7 +172,7 @@ public:
 	bool isWireframe() const;
 
 	// Implements Drawable.
-	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const;
+	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 
 	static bool getConstant(const char *in, DrawMode &out);
 	static bool getConstant(DrawMode in, const char *&out);

+ 12 - 0
src/modules/graphics/opengl/OpenGL.cpp

@@ -230,6 +230,18 @@ void OpenGL::prepareDraw()
 			glVertexAttrib1f((GLuint) ATTRIB_PSEUDO_INSTANCE_ID, 0.0f);
 			state.lastPseudoInstanceID = 0;
 		}
+
+		// We need to make sure antialiased Canvases are properly resolved
+		// before sampling from their textures in a shader.
+		// This is kind of a big hack. :(
+		const std::map<std::string, Object *> &r = shader->getBoundRetainables();
+		for (auto it = r.begin(); it != r.end(); ++it)
+		{
+			// Even bigger hack! D:
+			Canvas *canvas = dynamic_cast<Canvas *>(it->second);
+			if (canvas != nullptr)
+				canvas->resolveMSAA();
+		}
 	}
 }
 

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

@@ -780,7 +780,7 @@ bool ParticleSystem::isFull() const
 	return activeParticles == maxParticles;
 }
 
-void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const
+void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 	uint32 pCount = getCount();
 	if (pCount == 0 || texture == nullptr || pMem == nullptr || particleVerts == nullptr)

+ 1 - 1
src/modules/graphics/opengl/ParticleSystem.h

@@ -470,7 +470,7 @@ public:
 	 * @param x The x-coordinate.
 	 * @param y The y-coordinate.
 	 **/
-	virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const;
+	virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 
 	/**
 	 * Updates the particle system.

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

@@ -719,6 +719,11 @@ void Shader::checkSetScreenParams()
 	lastCanvas = Canvas::current;
 }
 
+const std::map<std::string, Object *> &Shader::getBoundRetainables() const
+{
+	return boundRetainables;
+}
+
 std::string Shader::getGLSLVersion()
 {
 	const char *tmp = nullptr;

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

@@ -143,6 +143,8 @@ public:
 	bool sendBuiltinFloat(BuiltinExtern builtin, int size, const GLfloat *m, int count);
 	void checkSetScreenParams();
 
+	const std::map<std::string, Object *> &getBoundRetainables() const;
+
 	static std::string getGLSLVersion();
 	static bool isSupported();
 

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

@@ -263,7 +263,7 @@ int SpriteBatch::getBufferSize() const
 	return size;
 }
 
-void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const
+void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 	const size_t vertex_offset = offsetof(Vertex, x);
 	const size_t texel_offset = offsetof(Vertex, s);

+ 1 - 1
src/modules/graphics/opengl/SpriteBatch.h

@@ -109,7 +109,7 @@ public:
 	int getBufferSize() const;
 
 	// Implements Drawable.
-	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const;
+	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 
 	static bool getConstant(const char *in, UsageHint &out);
 	static bool getConstant(UsageHint in, const char *&out);

+ 2 - 2
src/modules/graphics/opengl/Texture.h

@@ -48,12 +48,12 @@ public:
 	 * Any setup the texture might need to do before drawing, e.g. binding
 	 * the OpenGL texture for use.
 	 **/
-	virtual void predraw() const {}
+	virtual void predraw() {}
 
 	/**
 	 * Any cleanup the texture might need to do directly after drawing.
 	 **/
-	virtual void postdraw() const {}
+	virtual void postdraw() {}
 
 
 }; // Texture

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

@@ -120,6 +120,13 @@ int w_Canvas_getType(lua_State *L)
 	return 1;
 }
 
+int w_Canvas_getFSAA(lua_State *L)
+{
+	Canvas *canvas = luax_checkcanvas(L, 1);
+	lua_pushinteger(L, canvas->getFSAA());
+	return 1;
+}
+
 static const luaL_Reg functions[] =
 {
 	// From wrap_Texture.
@@ -136,6 +143,7 @@ static const luaL_Reg functions[] =
 	{ "getPixel", w_Canvas_getPixel },
 	{ "clear", w_Canvas_clear },
 	{ "getType", w_Canvas_getType },
+	{ "getFSAA", w_Canvas_getFSAA },
 	{ 0, 0 }
 };
 

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

@@ -40,6 +40,7 @@ int w_Canvas_getImageData(lua_State *L);
 int w_Canvas_getPixel(lua_State * L);
 int w_Canvas_clear(lua_State *L);
 int w_Canvas_getType(lua_State *L);
+int w_Canvas_getFSAA(lua_State *L);
 extern "C" int luaopen_canvas(lua_State *L);
 
 } // opengl

+ 4 - 3
src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -323,15 +323,16 @@ int w_newCanvas(lua_State *L)
 	int width       = luaL_optint(L, 1, instance->getWidth());
 	int height      = luaL_optint(L, 2, instance->getHeight());
 	const char *str = luaL_optstring(L, 3, "normal");
+	int fsaa        = luaL_optint(L, 4, 0);
 
 	Canvas::TextureType texture_type;
 	if (!Canvas::getConstant(str, texture_type))
 		return luaL_error(L, "Invalid canvas type: %s", str);
 
-	Canvas *canvas = 0;
-	EXCEPT_GUARD(canvas = instance->newCanvas(width, height, texture_type);)
+	Canvas *canvas = nullptr;
+	EXCEPT_GUARD(canvas = instance->newCanvas(width, height, texture_type, fsaa);)
 
-	if (canvas == 0)
+	if (canvas == nullptr)
 		return luaL_error(L, "Canvas not created, but no error thrown. I don't even...");
 
 	luax_pushtype(L, "Canvas", GRAPHICS_CANVAS_T, canvas);