Browse Source

Merged default into minor

--HG--
branch : minor
Alex Szpakowski 11 years ago
parent
commit
53e63d48c9
52 changed files with 860 additions and 578 deletions
  1. 14 0
      src/common/Matrix.cpp
  2. 6 0
      src/common/Matrix.h
  3. 2 0
      src/common/Object.cpp
  4. 62 0
      src/common/Object.h
  5. 8 13
      src/modules/audio/openal/Source.cpp
  6. 2 2
      src/modules/audio/openal/Source.h
  7. 17 4
      src/modules/filesystem/physfs/File.cpp
  8. 0 2
      src/modules/font/ImageRasterizer.cpp
  9. 1 1
      src/modules/font/ImageRasterizer.h
  10. 0 3
      src/modules/font/freetype/TrueTypeRasterizer.cpp
  11. 1 1
      src/modules/font/freetype/TrueTypeRasterizer.h
  12. 18 0
      src/modules/graphics/Graphics.cpp
  13. 13 0
      src/modules/graphics/Graphics.h
  14. 7 35
      src/modules/graphics/opengl/Canvas.cpp
  15. 0 1
      src/modules/graphics/opengl/Canvas.h
  16. 3 8
      src/modules/graphics/opengl/Font.cpp
  17. 1 1
      src/modules/graphics/opengl/Font.h
  18. 387 229
      src/modules/graphics/opengl/Graphics.cpp
  19. 60 59
      src/modules/graphics/opengl/Graphics.h
  20. 8 19
      src/modules/graphics/opengl/Image.cpp
  21. 2 2
      src/modules/graphics/opengl/Image.h
  22. 7 20
      src/modules/graphics/opengl/Mesh.cpp
  23. 1 1
      src/modules/graphics/opengl/Mesh.h
  24. 62 3
      src/modules/graphics/opengl/OpenGL.cpp
  25. 40 0
      src/modules/graphics/opengl/OpenGL.h
  26. 20 37
      src/modules/graphics/opengl/ParticleSystem.cpp
  27. 3 3
      src/modules/graphics/opengl/ParticleSystem.h
  28. 12 19
      src/modules/graphics/opengl/Shader.cpp
  29. 5 15
      src/modules/graphics/opengl/SpriteBatch.cpp
  30. 1 1
      src/modules/graphics/opengl/SpriteBatch.h
  31. 18 9
      src/modules/graphics/opengl/wrap_Canvas.cpp
  32. 29 34
      src/modules/graphics/opengl/wrap_Graphics.cpp
  33. 7 13
      src/modules/mouse/sdl/Mouse.cpp
  34. 1 1
      src/modules/mouse/sdl/Mouse.h
  35. 2 5
      src/modules/physics/box2d/Body.cpp
  36. 1 1
      src/modules/physics/box2d/Body.h
  37. 2 2
      src/modules/physics/box2d/Joint.cpp
  38. 3 6
      src/modules/physics/box2d/World.cpp
  39. 0 3
      src/modules/sound/lullaby/Decoder.cpp
  40. 1 1
      src/modules/sound/lullaby/Decoder.h
  41. 1 1
      src/modules/sound/lullaby/FLACDecoder.cpp
  42. 1 1
      src/modules/sound/lullaby/GmeDecoder.cpp
  43. 1 1
      src/modules/sound/lullaby/ModPlugDecoder.cpp
  44. 1 1
      src/modules/sound/lullaby/Mpg123Decoder.cpp
  45. 1 1
      src/modules/sound/lullaby/VorbisDecoder.cpp
  46. 1 1
      src/modules/sound/lullaby/WaveDecoder.cpp
  47. 20 3
      src/modules/system/System.cpp
  48. 1 0
      src/modules/system/System.h
  49. 0 3
      src/modules/thread/LuaThread.cpp
  50. 1 1
      src/modules/thread/LuaThread.h
  51. 4 10
      src/modules/window/sdl/Window.cpp
  52. 1 1
      src/modules/window/sdl/Window.h

+ 14 - 0
src/common/Matrix.cpp

@@ -194,5 +194,19 @@ void Matrix::transform(Vertex *dst, const Vertex *src, int size) const
 	}
 }
 
+Matrix Matrix::ortho(float left, float right, float bottom, float top)
+{
+	Matrix m;
+
+	m.e[0] = 2.0f / (right - left);
+	m.e[5] = 2.0f / (top - bottom);
+	m.e[10] = -1.0;
+
+	m.e[12] = -(right + left) / (right - left);
+	m.e[13] = -(top + bottom) / (top - bottom);
+
+	return m;
+}
+
 
 } // love

+ 6 - 0
src/common/Matrix.h

@@ -150,6 +150,12 @@ public:
 	 **/
 	void transform(Vertex *dst, const Vertex *src, int size) const;
 
+	/**
+	 * Creates a new orthographic projection matrix with depth in the range of
+	 * [-1, 1].
+	 **/
+	static Matrix ortho(float left, float right, float bottom, float top);
+
 private:
 
 	/**

+ 2 - 0
src/common/Object.cpp

@@ -21,6 +21,8 @@
 // LOVE
 #include "Object.h"
 
+#include <stdio.h>
+
 namespace love
 {
 

+ 62 - 0
src/common/Object.h

@@ -92,11 +92,73 @@ public:
 
 	}; // AutoRelease
 
+	/**
+	 * Partial re-implementation + specialization of std::shared_ptr. We can't
+	 * use C++11's stdlib yet...
+	 **/
+	template <typename T>
+	class StrongRef
+	{
+	public:
+
+		StrongRef()
+			: object(nullptr)
+		{
+		}
+
+		StrongRef(T *obj)
+			: object(obj)
+		{
+			if (object) object->retain();
+		}
+
+		StrongRef(const StrongRef &other)
+			: object(other.get())
+		{
+			if (object) object->retain();
+		}
+
+		~StrongRef()
+		{
+			if (object) object->release();
+		}
+
+		StrongRef &operator = (const StrongRef &other)
+		{
+			set(other.get());
+			return *this;
+		}
+
+		T *operator->() const
+		{
+			return object;
+		}
+
+		void set(T *obj)
+		{
+			if (obj) obj->retain();
+			if (object) object->release();
+			object = obj;
+		}
+
+		T *get() const
+		{
+			return object;
+		}
+
+	private:
+
+		T *object;
+
+	}; // StrongRef
+
 private:
 
 	// The reference count.
 	int count;
+
 }; // Object
+
 } // love
 
 #endif // LOVE_OBJECT_H

+ 8 - 13
src/modules/audio/openal/Source.cpp

@@ -102,7 +102,6 @@ Source::Source(Pool *pool, love::sound::Decoder *decoder)
 	, decoder(decoder)
 	, toLoop(0)
 {
-	decoder->retain();
 	alGenBuffers(MAX_BUFFERS, streamBuffers);
 
 	float z[3] = {0, 0, 0};
@@ -136,13 +135,15 @@ Source::Source(const Source &s)
 {
 	if (type == TYPE_STREAM)
 	{
-		if (s.decoder)
-			decoder = s.decoder->clone();
+		if (s.decoder.get())
+		{
+			love::sound::Decoder *dec = s.decoder->clone();
+			decoder.set(dec);
+			dec->release();
+		}
 
 		alGenBuffers(MAX_BUFFERS, streamBuffers);
 	}
-	else
-		staticBuffer->retain();
 
 	setFloatv(position, s.position);
 	setFloatv(velocity, s.velocity);
@@ -156,12 +157,6 @@ Source::~Source()
 
 	if (type == TYPE_STREAM)
 		alDeleteBuffers(MAX_BUFFERS, streamBuffers);
-
-	if (staticBuffer)
-		staticBuffer->release();
-
-	if (decoder)
-		decoder->release();
 }
 
 love::audio::Source *Source::clone()
@@ -273,7 +268,7 @@ bool Source::update()
 			offsetSamples += (curOffsetSamples - newOffsetSamples);
 			offsetSeconds += (curOffsetSecs - newOffsetSecs);
 
-			streamAtomic(buffer, decoder);
+			streamAtomic(buffer, decoder.get());
 			alSourceQueueBuffers(source, 1, &buffer);
 		}
 		return true;
@@ -544,7 +539,7 @@ bool Source::playAtomic()
 
 		for (unsigned int i = 0; i < MAX_BUFFERS; i++)
 		{
-			streamAtomic(streamBuffers[i], decoder);
+			streamAtomic(streamBuffers[i], decoder.get());
 			++usedBuffers;
 			if (decoder->isFinished())
 				break;

+ 2 - 2
src/modules/audio/openal/Source.h

@@ -147,7 +147,7 @@ private:
 	static const unsigned int MAX_BUFFERS = 32;
 	ALuint streamBuffers[MAX_BUFFERS];
 
-	StaticDataBuffer *staticBuffer;
+	Object::StrongRef<StaticDataBuffer> staticBuffer;
 
 	float pitch;
 	float volume;
@@ -181,7 +181,7 @@ private:
 
 	int channels;
 
-	love::sound::Decoder *decoder;
+	Object::StrongRef<love::sound::Decoder> decoder;
 
 	unsigned int toLoop;
 

+ 17 - 4
src/modules/filesystem/physfs/File.cpp

@@ -69,23 +69,36 @@ bool File::open(Mode mode)
 	if (file != 0)
 		return false;
 
-	this->mode = mode;
+	PHYSFS_getLastError(); // Clear the error buffer.
+	PHYSFS_File *handle = nullptr;
 
 	switch (mode)
 	{
 	case READ:
-		file = PHYSFS_openRead(filename.c_str());
+		handle = PHYSFS_openRead(filename.c_str());
 		break;
 	case APPEND:
-		file = PHYSFS_openAppend(filename.c_str());
+		handle = PHYSFS_openAppend(filename.c_str());
 		break;
 	case WRITE:
-		file = PHYSFS_openWrite(filename.c_str());
+		handle = PHYSFS_openWrite(filename.c_str());
 		break;
 	default:
 		break;
 	}
 
+	if (handle == nullptr)
+	{
+		const char *err = PHYSFS_getLastError();
+		if (err == nullptr)
+			err = "unknown error";
+		throw love::Exception("Could not open file %s (%s)", filename.c_str(), err);
+	}
+
+	file = handle;
+
+	this->mode = mode;
+
 	if (file != 0 && !setBuffer(bufferMode, bufferSize))
 	{
 		// Revert to buffer defaults if we don't successfully set the buffer.

+ 0 - 2
src/modules/font/ImageRasterizer.cpp

@@ -39,13 +39,11 @@ ImageRasterizer::ImageRasterizer(love::image::ImageData *data, uint32 *glyphs, i
 	, glyphs(glyphs)
 	, numglyphs(numglyphs)
 {
-	imageData->retain();
 	load();
 }
 
 ImageRasterizer::~ImageRasterizer()
 {
-	imageData->release();
 }
 
 int ImageRasterizer::getLineHeight() const

+ 1 - 1
src/modules/font/ImageRasterizer.h

@@ -53,7 +53,7 @@ private:
 	void load();
 
 	// The image data
-	love::image::ImageData *imageData;
+	Object::StrongRef<love::image::ImageData> imageData;
 
 	// The glyphs in the font
 	uint32 *glyphs;

+ 0 - 3
src/modules/font/freetype/TrueTypeRasterizer.cpp

@@ -51,14 +51,11 @@ TrueTypeRasterizer::TrueTypeRasterizer(FT_Library library, Data *data, int size)
 	metrics.ascent = s.ascender >> 6;
 	metrics.descent = s.descender >> 6;
 	metrics.height = s.height >> 6;
-
-	data->retain();
 }
 
 TrueTypeRasterizer::~TrueTypeRasterizer()
 {
 	FT_Done_Face(face);
-	data->release();
 }
 
 int TrueTypeRasterizer::getLineHeight() const

+ 1 - 1
src/modules/font/freetype/TrueTypeRasterizer.h

@@ -58,7 +58,7 @@ private:
 	FT_Face face;
 
 	// File data
-	Data *data;
+	Object::StrongRef<Data> data;
 }; // FreetypeRasterizer
 
 } // freetype

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

@@ -99,6 +99,16 @@ bool Graphics::getConstant(SystemLimit in, const char *&out)
 	return systemLimits.find(in, out);
 }
 
+bool Graphics::getConstant(const char *in, StackType &out)
+{
+	return stackTypes.find(in, out);
+}
+
+bool Graphics::getConstant(StackType in, const char *&out)
+{
+	return stackTypes.find(in, out);
+}
+
 StringMap<Graphics::DrawMode, Graphics::DRAW_MAX_ENUM>::Entry Graphics::drawModeEntries[] =
 {
 	{ "line", Graphics::DRAW_LINE },
@@ -165,5 +175,13 @@ StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM>::Entry Graphics::syst
 
 StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM> Graphics::systemLimits(Graphics::systemLimitEntries, sizeof(Graphics::systemLimitEntries));
 
+StringMap<Graphics::StackType, Graphics::STACK_MAX_ENUM>::Entry Graphics::stackTypeEntries[] =
+{
+	{"all", Graphics::STACK_ALL},
+	{"transform", Graphics::STACK_TRANSFORM},
+};
+
+StringMap<Graphics::StackType, Graphics::STACK_MAX_ENUM> Graphics::stackTypes(Graphics::stackTypeEntries, sizeof(Graphics::stackTypeEntries));
+
 } // graphics
 } // love

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

@@ -96,6 +96,13 @@ public:
 		LIMIT_MAX_ENUM
 	};
 
+	enum StackType
+	{
+		STACK_ALL,
+		STACK_TRANSFORM,
+		STACK_MAX_ENUM
+	};
+
 	struct RendererInfo
 	{
 		std::string name;
@@ -148,6 +155,9 @@ public:
 	static bool getConstant(const char *in, SystemLimit &out);
 	static bool getConstant(SystemLimit in, const char *&out);
 
+	static bool getConstant(const char *in, StackType &out);
+	static bool getConstant(StackType in, const char *&out);
+
 private:
 
 	static StringMap<DrawMode, DRAW_MAX_ENUM>::Entry drawModeEntries[];
@@ -171,6 +181,9 @@ private:
 	static StringMap<SystemLimit, LIMIT_MAX_ENUM>::Entry systemLimitEntries[];
 	static StringMap<SystemLimit, LIMIT_MAX_ENUM> systemLimits;
 
+	static StringMap<StackType, STACK_MAX_ENUM>::Entry stackTypeEntries[];
+	static StringMap<StackType, STACK_MAX_ENUM> stackTypes;
+
 }; // Graphics
 
 } // graphics

+ 7 - 35
src/modules/graphics/opengl/Canvas.cpp

@@ -588,16 +588,13 @@ void Canvas::unloadVolatile()
 	fbo = depth_stencil = texture = 0;
 	resolve_fbo = msaa_buffer = 0;
 
-	for (size_t i = 0; i < attachedCanvases.size(); i++)
-		attachedCanvases[i]->release();
-
 	attachedCanvases.clear();
 }
 
 void Canvas::drawv(const Matrix &t, const Vertex *v)
 {
-	glPushMatrix();
-	glMultMatrixf((const GLfloat *)t.getElements());
+	OpenGL::TempTransform transform(gl);
+	transform.get() *= t;
 
 	predraw();
 
@@ -614,8 +611,6 @@ void Canvas::drawv(const Matrix &t, const Vertex *v)
 	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)
@@ -688,16 +683,8 @@ void Canvas::setupGrab()
 	strategy->bindFBO(fbo);
 	gl.setViewport(OpenGL::Viewport(0, 0, width, height));
 
-	// Reset the projection matrix
-	glMatrixMode(GL_PROJECTION);
-	glPushMatrix();
-	glLoadIdentity();
-
-	// Set up orthographic view (no depth)
-	glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
-
-	// Switch back to modelview matrix
-	glMatrixMode(GL_MODELVIEW);
+	// Set up the projection matrix
+	gl.matrices.projection.push_back(Matrix::ortho(0.0, width, 0.0, height));
 
 	// Make sure the correct sRGB setting is used when drawing to the canvas.
 	if (format == FORMAT_SRGB)
@@ -751,11 +738,8 @@ void Canvas::startGrab(const std::vector<Canvas *> &canvases)
 	// Attach the canvas textures to the active FBO and set up MRTs.
 	strategy->setAttachments(canvases);
 
-	for (size_t i = 0; i < canvases.size(); i++)
-		canvases[i]->retain();
-
-	for (size_t i = 0; i < attachedCanvases.size(); i++)
-		attachedCanvases[i]->release();
+	// We want to avoid reference cycles, so we don't retain the attached
+	// Canvases here. The code in Graphics::setCanvas retains them.
 
 	attachedCanvases = canvases;
 }
@@ -770,10 +754,6 @@ void Canvas::startGrab()
 	// make sure the FBO is only using a single canvas
 	strategy->setAttachments();
 
-	// release any previously attached canvases
-	for (size_t i = 0; i < attachedCanvases.size(); i++)
-		attachedCanvases[i]->release();
-
 	attachedCanvases.clear();
 }
 
@@ -783,9 +763,7 @@ void Canvas::stopGrab(bool switchingToOtherCanvas)
 	if (current != this)
 		return;
 
-	glMatrixMode(GL_PROJECTION);
-	glPopMatrix();
-	glMatrixMode(GL_MODELVIEW);
+	gl.matrices.projection.pop_back();
 
 	if (switchingToOtherCanvas)
 	{
@@ -1120,12 +1098,6 @@ bool Canvas::isFormatSupported(Canvas::Format format)
 	return supported;
 }
 
-void Canvas::bindDefaultCanvas()
-{
-	if (current != nullptr)
-		current->stopGrab();
-}
-
 bool Canvas::getConstant(const char *in, Format &out)
 {
 	return formats.find(in, out);

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

@@ -120,7 +120,6 @@ public:
 	static bool isFormatSupported(Format format);
 
 	static Canvas *current;
-	static void bindDefaultCanvas();
 
 	// The viewport dimensions of the system (default) framebuffer.
 	static OpenGL::Viewport systemViewport;

+ 3 - 8
src/modules/graphics/opengl/Font.cpp

@@ -87,13 +87,10 @@ Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter)
 	}
 
 	delete gd;
-
-	rasterizer->retain();
 }
 
 Font::~Font()
 {
-	rasterizer->release();
 	unloadVolatile();
 }
 
@@ -368,11 +365,11 @@ void Font::print(const std::string &text, float x, float y, float extra_spacing,
 	// second (using the struct's < operator).
 	std::sort(glyphinfolist.begin(), glyphinfolist.end());
 
-	glPushMatrix();
-
 	Matrix t;
 	t.setTransformation(ceilf(x), ceilf(y), angle, sx, sy, ox, oy, kx, ky);
-	glMultMatrixf((const GLfloat *)t.getElements());
+
+	OpenGL::TempTransform transform(gl);
+	transform.get() *= t;
 
 	glEnableClientState(GL_VERTEX_ARRAY);
 	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
@@ -393,8 +390,6 @@ void Font::print(const std::string &text, float x, float y, float extra_spacing,
 
 	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 	glDisableClientState(GL_VERTEX_ARRAY);
-
-	glPopMatrix();
 }
 
 int Font::getWidth(const std::string &str)

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

@@ -183,7 +183,7 @@ private:
 	Glyph *addGlyph(uint32 glyph);
 	Glyph *findGlyph(uint32 glyph);
 
-	love::font::Rasterizer *rasterizer;
+	Object::StrongRef<love::font::Rasterizer> rasterizer;
 
 	int height;
 	float lineHeight;

+ 387 - 229
src/modules/graphics/opengl/Graphics.cpp

@@ -44,20 +44,15 @@ namespace opengl
 {
 
 Graphics::Graphics()
-	: currentFont(0)
-	, lineStyle(LINE_SMOOTH)
-	, lineJoin(LINE_JOIN_MITER)
-	, lineWidth(1)
-	, matrixLimit(0)
-	, userMatrices(0)
-	, colorMask()
-	, width(0)
+	: width(0)
 	, height(0)
 	, created(false)
 	, activeStencil(false)
-	, savedState()
 	, displayedMinReqWarning(false)
 {
+	states.reserve(10);
+	states.push_back(DisplayState());
+
 	currentWindow = love::window::sdl::Window::createSingleton();
 
 	int w, h;
@@ -71,8 +66,8 @@ Graphics::Graphics()
 
 Graphics::~Graphics()
 {
-	if (currentFont != 0)
-		currentFont->release();
+	// We do this manually so the love objects get released before the window.
+	states.clear();
 
 	currentWindow->release();
 }
@@ -82,50 +77,86 @@ const char *Graphics::getName() const
 	return "love.graphics.opengl";
 }
 
-DisplayState Graphics::saveState()
-{
-	DisplayState s;
-
-	s.color = getColor();
-	s.backgroundColor = getBackgroundColor();
-
-	s.blendMode = getBlendMode();
-	//get line style
-	s.lineStyle = getLineStyle();
-	s.lineJoin = getLineJoin();
-	//get the point size
-	glGetFloatv(GL_POINT_SIZE, &s.pointSize);
-	//get scissor status
-	s.scissor = (glIsEnabled(GL_SCISSOR_TEST) == GL_TRUE);
-	//do we have scissor, if so, store the box
-	if (s.scissor)
-		s.scissorBox = gl.getScissor();
-
-	for (int i = 0; i < 4; i++)
-		s.colorMask[i] = colorMask[i];
-
-	wireframe = isWireframe();
-
-	return s;
-}
-
 void Graphics::restoreState(const DisplayState &s)
 {
 	setColor(s.color);
 	setBackgroundColor(s.backgroundColor);
+
 	setBlendMode(s.blendMode);
-	setLineWidth(lineWidth);
+
+	setLineWidth(s.lineWidth);
 	setLineStyle(s.lineStyle);
 	setLineJoin(s.lineJoin);
+
 	setPointSize(s.pointSize);
+
 	if (s.scissor)
 		setScissor(s.scissorBox.x, s.scissorBox.y, s.scissorBox.w, s.scissorBox.h);
 	else
 		setScissor();
-	setColorMask(s.colorMask[0], s.colorMask[1], s.colorMask[2], s.colorMask[3]);
+
+	setFont(s.font.get());
+	setShader(s.shader.get());
+	setCanvas(s.canvases);
+
+	setColorMask(s.colorMask);
 	setWireframe(s.wireframe);
 }
 
+void Graphics::restoreStateChecked(const DisplayState &s)
+{
+	const DisplayState &cur = states.back();
+
+	if (*(uint32 *) &s.color.r != *(uint32 *) &cur.color.r)
+		setColor(s.color);
+
+	if (*(uint32 *) &s.backgroundColor.r != *(uint32 *) &cur.backgroundColor.r)
+		setBackgroundColor(s.backgroundColor);
+
+	if (s.blendMode != cur.blendMode)
+		setBlendMode(s.blendMode);
+
+	// These are just simple assignments.
+	setLineWidth(s.lineWidth);
+	setLineStyle(s.lineStyle);
+	setLineJoin(s.lineJoin);
+
+	if (s.pointSize != cur.pointSize)
+		setPointSize(s.pointSize);
+
+	if (s.scissor != cur.scissor || (s.scissor && !(s.scissorBox == cur.scissorBox)))
+	{
+		if (s.scissor)
+			setScissor(s.scissorBox.x, s.scissorBox.y, s.scissorBox.w, s.scissorBox.h);
+		else
+			setScissor();
+	}
+
+	setFont(s.font.get());
+	setShader(s.shader.get());
+
+	for (size_t i = 0; i < s.canvases.size() && i < cur.canvases.size(); i++)
+	{
+		if (s.canvases[i].get() != cur.canvases[i].get())
+		{
+			setCanvas(s.canvases);
+			break;
+		}
+	}
+
+	for (int i = 0; i < 4; i++)
+	{
+		if (s.colorMask[i] != cur.colorMask[i])
+		{
+			setColorMask(s.colorMask);
+			break;
+		}
+	}
+
+	if (s.wireframe != cur.wireframe)
+		setWireframe(s.wireframe);
+}
+
 void Graphics::setViewportSize(int width, int height)
 {
 	this->width = width;
@@ -136,8 +167,8 @@ void Graphics::setViewportSize(int width, int height)
 
 	// We want to affect the main screen, not any Canvas that's currently active
 	// (not that any *should* be active when this is called.)
-	Canvas *c = Canvas::current;
-	Canvas::bindDefaultCanvas();
+	std::vector<Canvas *> canvases = getCanvas();
+	setCanvas();
 
 	// Set the viewport to top-left corner.
 	gl.setViewport(OpenGL::Viewport(0, 0, width, height));
@@ -146,18 +177,11 @@ void Graphics::setViewportSize(int width, int height)
 	// made aware of the new system viewport size.
 	Canvas::systemViewport = gl.getViewport();
 
-	// Reset the projection matrix
-	glMatrixMode(GL_PROJECTION);
-	glLoadIdentity();
-
-	// Set up orthographic view (no depth)
-	glOrtho(0.0, width, height, 0.0, -1.0, 1.0);
-
-	glMatrixMode(GL_MODELVIEW);
+	// Set up the projection matrix
+	gl.matrices.projection.back() = Matrix::ortho(0.0, width, height, 0.0);
 
 	// Restore the previously active Canvas.
-	if (c != nullptr)
-		c->startGrab(c->getAttachedCanvases());
+	setCanvas(canvases);
 }
 
 bool Graphics::setMode(int width, int height, bool &sRGB)
@@ -202,7 +226,8 @@ bool Graphics::setMode(int width, int height, bool &sRGB)
 	glEnable(GL_BLEND);
 
 	// Enable all color component writes.
-	setColorMask(true, true, true, true);
+	bool colormask[] = {true, true, true, true};
+	setColorMask(colormask);
 
 	// Auto-generated mipmaps should be the best quality possible
 	glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
@@ -211,28 +236,9 @@ bool Graphics::setMode(int width, int height, bool &sRGB)
 	glEnable(GL_TEXTURE_2D);
 	gl.setTextureUnit(0);
 
-	// Reset modelview matrix
-	glMatrixMode(GL_MODELVIEW);
-	glLoadIdentity();
-
 	// Set pixel row alignment
 	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
 
-	// Reload all volatile objects.
-	if (!Volatile::loadAll())
-		std::cerr << "Could not reload all volatile objects." << std::endl;
-
-	// Restore the display state.
-	restoreState(savedState);
-	pixel_size_stack.clear();
-	pixel_size_stack.reserve(5);
-	pixel_size_stack.push_back(1);
-
-	// Get the maximum number of matrices
-	// subtract a few to give the engine some room.
-	glGetIntegerv(GL_MAX_MODELVIEW_STACK_DEPTH, &matrixLimit);
-	matrixLimit -= 5;
-
 	// Set whether drawing converts input from linear -> sRGB colorspace.
 	if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_sRGB || GLEE_EXT_framebuffer_sRGB)
 	{
@@ -258,6 +264,17 @@ bool Graphics::setMode(int width, int height, bool &sRGB)
 
 	setDebug(enabledebug);
 
+	// Reload all volatile objects.
+	if (!Volatile::loadAll())
+		std::cerr << "Could not reload all volatile objects." << std::endl;
+
+	// Restore the graphics state.
+	restoreState(states.back());
+
+	pixel_size_stack.clear();
+	pixel_size_stack.reserve(5);
+	pixel_size_stack.push_back(1);
+
 	return true;
 }
 
@@ -266,9 +283,6 @@ void Graphics::unSetMode()
 	if (!isCreated())
 		return;
 
-	// Window re-creation may destroy the GL context, so we must save the state.
-	savedState = saveState();
-
 	// Unload all volatile objects. These must be reloaded after the display
 	// mode change.
 	Volatile::unloadAll();
@@ -337,8 +351,6 @@ void Graphics::reset()
 {
 	DisplayState s;
 	discardStencil();
-	Canvas::bindDefaultCanvas();
-	Shader::detach();
 	origin();
 	restoreState(s);
 }
@@ -370,13 +382,18 @@ bool Graphics::isCreated() const
 
 void Graphics::setScissor(int x, int y, int width, int height)
 {
+	OpenGL::Viewport box(x, y, width, height);
+
+	states.back().scissor = true;
 	glEnable(GL_SCISSOR_TEST);
 	// OpenGL's reversed y-coordinate is compensated for in OpenGL::setScissor.
-	gl.setScissor(OpenGL::Viewport(x, y, width, height));
+	gl.setScissor(box);
+	states.back().scissorBox = box;
 }
 
 void Graphics::setScissor()
 {
+	states.back().scissor = false;
 	glDisable(GL_SCISSOR_TEST);
 }
 
@@ -389,7 +406,7 @@ bool Graphics::getScissor(int &x, int &y, int &width, int &height) const
 	width = scissor.w;
 	height = scissor.h;
 
-	return glIsEnabled(GL_SCISSOR_TEST) == GL_TRUE;
+	return states.back().scissor;
 }
 
 void Graphics::defineStencil()
@@ -413,7 +430,7 @@ void Graphics::useStencil(bool invert)
 {
 	glStencilFunc(GL_EQUAL, (GLint)(!invert), 1); // invert ? 0 : 1
 	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
-	setColorMask(colorMask[0], colorMask[1], colorMask[2], colorMask[3]);
+	setColorMask(states.back().colorMask);
 }
 
 void Graphics::discardStencil()
@@ -421,7 +438,7 @@ void Graphics::discardStencil()
 	if (!activeStencil)
 		return;
 
-	setColorMask(colorMask[0], colorMask[1], colorMask[2], colorMask[3]);
+	setColorMask(states.back().colorMask);
 	glDisable(GL_STENCIL_TEST);
 	activeStencil = false;
 }
@@ -580,119 +597,186 @@ Mesh *Graphics::newMesh(int vertexcount, Mesh::DrawMode mode)
 void Graphics::setColor(const Color &c)
 {
 	gl.setColor(c);
+	states.back().color = c;
 }
 
 Color Graphics::getColor() const
 {
-	return gl.getColor();
+	return states.back().color;
 }
 
 void Graphics::setBackgroundColor(const Color &c)
 {
 	gl.setClearColor(c);
+	states.back().backgroundColor = c;
 }
 
 Color Graphics::getBackgroundColor() const
 {
-	return gl.getClearColor();
+	return states.back().backgroundColor;
 }
 
 void Graphics::setFont(Font *font)
 {
-	Object::AutoRelease fontrelease(currentFont);
+	DisplayState &state = states.back();
+	state.font.set(font);
+}
+
+Font *Graphics::getFont() const
+{
+	return states.back().font.get();
+}
+
+void Graphics::setShader(Shader *shader)
+{
+	if (shader == nullptr)
+		return setShader();
+
+	DisplayState &state = states.back();
 
-	currentFont = font;
+	shader->attach();
 
-	if (font != 0)
-		currentFont->retain();
+	state.shader.set(shader);
 }
 
-Font *Graphics::getFont() const
+void Graphics::setShader()
+{
+	DisplayState &state = states.back();
+
+	Shader::detach();
+
+	state.shader.set(nullptr);
+}
+
+Shader *Graphics::getShader() const
+{
+	return states.back().shader.get();
+}
+
+void Graphics::setCanvas(Canvas *canvas)
+{
+	if (canvas == nullptr)
+		return setCanvas();
+
+	DisplayState &state = states.back();
+
+	canvas->startGrab();
+
+	std::vector<Object::StrongRef<Canvas>> canvasref;
+	canvasref.push_back(canvas);
+
+	std::swap(state.canvases, canvasref);
+}
+
+void Graphics::setCanvas(const std::vector<Canvas *> &canvases)
+{
+	if (canvases.size() == 0)
+		return setCanvas();
+	else if (canvases.size() == 1)
+		return setCanvas(canvases[0]);
+
+	DisplayState &state = states.back();
+
+	auto attachments = std::vector<Canvas *>(canvases.begin() + 1, canvases.end());
+	canvases[0]->startGrab(attachments);
+
+	std::vector<Object::StrongRef<Canvas>> canvasrefs;
+	canvasrefs.reserve(canvases.size());
+
+	for (Canvas *c : canvases)
+		canvasrefs.push_back(c);
+
+	std::swap(state.canvases, canvasrefs);
+}
+
+void Graphics::setCanvas(const std::vector<Object::StrongRef<Canvas>> &canvases)
+{
+	std::vector<Canvas *> canvaslist;
+	canvaslist.reserve(canvases.size());
+
+	for (const Object::StrongRef<Canvas> &c : canvases)
+		canvaslist.push_back(c.get());
+
+	return setCanvas(canvaslist);
+}
+
+void Graphics::setCanvas()
 {
-	return currentFont;
+	DisplayState &state = states.back();
+
+	if (Canvas::current != nullptr)
+		Canvas::current->stopGrab();
+
+	state.canvases.clear();
 }
 
-void Graphics::setColorMask(bool r, bool g, bool b, bool a)
+std::vector<Canvas *> Graphics::getCanvas() const
 {
-	colorMask[0] = r;
-	colorMask[1] = g;
-	colorMask[2] = b;
-	colorMask[3] = a;
+	std::vector<Canvas *> canvases;
+	canvases.reserve(states.back().canvases.size());
 
-	glColorMask((GLboolean) r, (GLboolean) g, (GLboolean) b, (GLboolean) a);
+	for (const Object::StrongRef<Canvas> &c : states.back().canvases)
+		canvases.push_back(c.get());
+
+	return canvases;
+}
+
+void Graphics::setColorMask(const bool mask[4])
+{
+	for (int i = 0; i < 4; i++)
+		states.back().colorMask[i] = mask[i];
+
+	glColorMask(mask[0], mask[1], mask[2], mask[3]);
 }
 
 const bool *Graphics::getColorMask() const
 {
-	return colorMask;
+	return states.back().colorMask;
 }
 
 void Graphics::setBlendMode(Graphics::BlendMode mode)
 {
-	OpenGL::BlendState state = {GL_ONE, GL_ONE, GL_ZERO, GL_ZERO, GL_FUNC_ADD};
+	OpenGL::BlendState blend = {GL_ONE, GL_ONE, GL_ZERO, GL_ZERO, GL_FUNC_ADD};
 
 	switch (mode)
 	{
 	case BLEND_ALPHA:
-		state.srcRGB = GL_SRC_ALPHA;
-		state.srcA = GL_ONE;
-		state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_ALPHA;
+		blend.srcRGB = GL_SRC_ALPHA;
+		blend.srcA = GL_ONE;
+		blend.dstRGB = blend.dstA = GL_ONE_MINUS_SRC_ALPHA;
 		break;
 	case BLEND_MULTIPLY:
-		state.srcRGB = state.srcA = GL_DST_COLOR;
-		state.dstRGB = state.dstA = GL_ZERO;
+		blend.srcRGB = blend.srcA = GL_DST_COLOR;
+		blend.dstRGB = blend.dstA = GL_ZERO;
 		break;
 	case BLEND_PREMULTIPLIED:
-		state.srcRGB = state.srcA = GL_ONE;
-		state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_ALPHA;
+		blend.srcRGB = blend.srcA = GL_ONE;
+		blend.dstRGB = blend.dstA = GL_ONE_MINUS_SRC_ALPHA;
 		break;
 	case BLEND_SUBTRACT:
-		state.func = GL_FUNC_REVERSE_SUBTRACT;
+		blend.func = GL_FUNC_REVERSE_SUBTRACT;
 	case BLEND_ADD:
-		state.srcRGB = state.srcA = GL_SRC_ALPHA;
-		state.dstRGB = state.dstA = GL_ONE;
+		blend.srcRGB = blend.srcA = GL_SRC_ALPHA;
+		blend.dstRGB = blend.dstA = GL_ONE;
 		break;
 	case BLEND_SCREEN:
-		state.srcRGB = state.srcA = GL_ONE;
-		state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_COLOR;
+		blend.srcRGB = blend.srcA = GL_ONE;
+		blend.dstRGB = blend.dstA = GL_ONE_MINUS_SRC_COLOR;
 		break;
 	case BLEND_REPLACE:
 	default:
-		state.srcRGB = state.srcA = GL_ONE;
-		state.dstRGB = state.dstA = GL_ZERO;
+		blend.srcRGB = blend.srcA = GL_ONE;
+		blend.dstRGB = blend.dstA = GL_ZERO;
 		break;
 	}
 
-	gl.setBlendState(state);
+	gl.setBlendState(blend);
+	states.back().blendMode = mode;
 }
 
 Graphics::BlendMode Graphics::getBlendMode() const
 {
-	OpenGL::BlendState state = gl.getBlendState();
-
-	if (state.func == GL_FUNC_REVERSE_SUBTRACT)  // && src == GL_SRC_ALPHA && dst == GL_ONE
-		return BLEND_SUBTRACT;
-	// Everything else has equation == GL_FUNC_ADD.
-	else if (state.srcRGB == state.srcA && state.dstRGB == state.dstA)
-	{
-		if (state.srcRGB == GL_SRC_ALPHA && state.dstRGB == GL_ONE)
-			return BLEND_ADD;
-		else if (state.srcRGB == GL_SRC_ALPHA && state.dstRGB == GL_ONE_MINUS_SRC_ALPHA)
-			return BLEND_ALPHA; // alpha blend mode fallback for very old OpenGL versions.
-		else if (state.srcRGB == GL_DST_COLOR && state.dstRGB == GL_ZERO)
-			return BLEND_MULTIPLY;
-		else if (state.srcRGB == GL_ONE && state.dstRGB == GL_ONE_MINUS_SRC_ALPHA)
-			return BLEND_PREMULTIPLIED;
-		else if (state.srcRGB == GL_ONE && state.dstRGB == GL_ONE_MINUS_SRC_COLOR)
-			return BLEND_SCREEN;
-		else if (state.srcRGB == GL_ONE && state.dstRGB == GL_ZERO)
-			return BLEND_REPLACE;
-	}
-	else if (state.srcRGB == GL_SRC_ALPHA && state.srcA == GL_ONE &&
-		state.dstRGB == GL_ONE_MINUS_SRC_ALPHA && state.dstA == GL_ONE_MINUS_SRC_ALPHA)
-		return BLEND_ALPHA;
-
-	throw Exception("Unknown blend mode");
+	return states.back().blendMode;
 }
 
 void Graphics::setDefaultFilter(const Texture::Filter &f)
@@ -719,66 +803,69 @@ void Graphics::getDefaultMipmapFilter(Texture::FilterMode *filter, float *sharpn
 
 void Graphics::setLineWidth(float width)
 {
-	lineWidth = width;
+	states.back().lineWidth = width;
 }
 
 void Graphics::setLineStyle(Graphics::LineStyle style)
 {
-	lineStyle = style;
+	states.back().lineStyle = style;
 }
 
 void Graphics::setLineJoin(Graphics::LineJoin join)
 {
-	lineJoin = join;
+	states.back().lineJoin = join;
 }
 
 float Graphics::getLineWidth() const
 {
-	return lineWidth;
+	return states.back().lineWidth;
 }
 
 Graphics::LineStyle Graphics::getLineStyle() const
 {
-	return lineStyle;
+	return states.back().lineStyle;
 }
 
 Graphics::LineJoin Graphics::getLineJoin() const
 {
-	return lineJoin;
+	return states.back().lineJoin;
 }
 
 void Graphics::setPointSize(float size)
 {
-	glPointSize((GLfloat)size);
+	glPointSize(size);
+	states.back().pointSize = size;
 }
 
 float Graphics::getPointSize() const
 {
-	GLfloat size;
-	glGetFloatv(GL_POINT_SIZE, &size);
-	return (float)size;
+	return states.back().pointSize;
 }
 
 void Graphics::setWireframe(bool enable)
 {
-	wireframe = enable;
 	glPolygonMode(GL_FRONT_AND_BACK, enable ? GL_LINE : GL_FILL);
+	states.back().wireframe = enable;
 }
 
 bool Graphics::isWireframe() const
 {
-	return wireframe;
+	return states.back().wireframe;
 }
 
 void Graphics::print(const std::string &str, float x, float y , float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
-	if (currentFont != nullptr)
-		currentFont->print(str, x, y, 0.0, angle, sx, sy, ox, oy, kx, ky);
+	DisplayState &state = states.back();
+
+	if (state.font.get() != nullptr)
+		state.font->print(str, x, y, 0.0, angle, sx, sy, ox, oy, kx, ky);
 }
 
 void Graphics::printf(const std::string &str, float x, float y, float wrap, AlignMode align, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
-	if (currentFont == nullptr)
+	DisplayState &state = states.back();
+
+	if (state.font.get() == nullptr)
 		return;
 
 	if (wrap < 0.0f)
@@ -790,59 +877,49 @@ void Graphics::printf(const std::string &str, float x, float y, float wrap, Alig
 	// wrappedlines indicates which lines were automatically wrapped. It's
 	// guaranteed to have the same number of elements as lines_to_draw.
 	vector<bool> wrappedlines;
-	vector<string> lines_to_draw = currentFont->getWrap(str, wrap, 0, &wrappedlines);
-
-	glPushMatrix();
+	vector<string> lines_to_draw = state.font->getWrap(str, wrap, 0, &wrappedlines);
 
 	static Matrix t;
 	t.setTransformation(ceilf(x), ceilf(y), angle, sx, sy, ox, oy, kx, ky);
-	glMultMatrixf((const GLfloat *)t.getElements());
+
+	OpenGL::TempTransform transform(gl);
+	transform.get() *= t;
 
 	x = y = 0.0f;
 
-	try
-	{
-		// now for the actual printing
-		vector<string>::const_iterator line_iter, line_end = lines_to_draw.end();
-		float extra_spacing = 0.0f;
-		int num_spaces = 0;
-		int i = 0;
+	// now for the actual printing
+	vector<string>::const_iterator line_iter, line_end = lines_to_draw.end();
+	float extra_spacing = 0.0f;
+	int num_spaces = 0;
+	int i = 0;
 
-		for (line_iter = lines_to_draw.begin(); line_iter != line_end; ++line_iter)
+	for (line_iter = lines_to_draw.begin(); line_iter != line_end; ++line_iter)
+	{
+		float width = static_cast<float>(state.font->getWidth(*line_iter));
+		switch (align)
 		{
-			float width = static_cast<float>(currentFont->getWidth(*line_iter));
-			switch (align)
-			{
-			case ALIGN_RIGHT:
-				currentFont->print(*line_iter, ceilf(x + (wrap - width)), ceilf(y), 0.0f);
-				break;
-			case ALIGN_CENTER:
-				currentFont->print(*line_iter, ceilf(x + (wrap - width) / 2), ceilf(y), 0.0f);
-				break;
-			case ALIGN_JUSTIFY:
-				num_spaces = std::count(line_iter->begin(), line_iter->end(), ' ');
-				if (wrappedlines[i] && num_spaces >= 1)
-					extra_spacing = (wrap - width) / float(num_spaces);
-				else
-					extra_spacing = 0.0f;
-				currentFont->print(*line_iter, ceilf(x), ceilf(y), extra_spacing);
-				break;
-			case ALIGN_LEFT:
-			default:
-				currentFont->print(*line_iter, ceilf(x), ceilf(y), 0.0f);
-				break;
-			}
-			y += currentFont->getHeight() * currentFont->getLineHeight();
-			i++;
+		case ALIGN_RIGHT:
+			state.font->print(*line_iter, ceilf(x + (wrap - width)), ceilf(y), 0.0f);
+			break;
+		case ALIGN_CENTER:
+			state.font->print(*line_iter, ceilf(x + (wrap - width) / 2), ceilf(y), 0.0f);
+			break;
+		case ALIGN_JUSTIFY:
+			num_spaces = std::count(line_iter->begin(), line_iter->end(), ' ');
+			if (wrappedlines[i] && num_spaces >= 1)
+				extra_spacing = (wrap - width) / float(num_spaces);
+			else
+				extra_spacing = 0.0f;
+			state.font->print(*line_iter, ceilf(x), ceilf(y), extra_spacing);
+			break;
+		case ALIGN_LEFT:
+		default:
+			state.font->print(*line_iter, ceilf(x), ceilf(y), 0.0f);
+			break;
 		}
+		y += state.font->getHeight() * state.font->getLineHeight();
+		i++;
 	}
-	catch (love::Exception &)
-	{
-		glPopMatrix();
-		throw;
-	}
-
-	glPopMatrix();
 }
 
 /**
@@ -860,22 +937,24 @@ void Graphics::point(float x, float y)
 
 void Graphics::polyline(const float *coords, size_t count)
 {
-	if (lineJoin == LINE_JOIN_NONE)
+	DisplayState &state = states.back();
+
+	if (state.lineJoin == LINE_JOIN_NONE)
 	{
 			NoneJoinPolyline line;
-			line.render(coords, count, lineWidth * .5f, float(pixel_size_stack.back()), lineStyle == LINE_SMOOTH);
+			line.render(coords, count, state.lineWidth * .5f, float(pixel_size_stack.back()), state.lineStyle == LINE_SMOOTH);
 			line.draw();
 	}
-	else if (lineJoin == LINE_JOIN_BEVEL)
+	else if (state.lineJoin == LINE_JOIN_BEVEL)
 	{
 		BevelJoinPolyline line;
-		line.render(coords, count, lineWidth * .5f, float(pixel_size_stack.back()), lineStyle == LINE_SMOOTH);
+		line.render(coords, count, state.lineWidth * .5f, float(pixel_size_stack.back()), state.lineStyle == LINE_SMOOTH);
 		line.draw();
 	}
 	else // LINE_JOIN_MITER
 	{
 		MiterJoinPolyline line;
-		line.render(coords, count, lineWidth * .5f, float(pixel_size_stack.back()), lineStyle == LINE_SMOOTH);
+		line.render(coords, count, state.lineWidth * .5f, float(pixel_size_stack.back()), state.lineStyle == LINE_SMOOTH);
 		line.draw();
 	}
 }
@@ -982,9 +1061,8 @@ love::image::ImageData *Graphics::newScreenshot(love::image::Image *image, bool
 {
 	// Temporarily unbind the currently active canvas (glReadPixels reads the
 	// active framebuffer, not the main one.)
-	Canvas *curcanvas = Canvas::current;
-	if (curcanvas)
-		Canvas::bindDefaultCanvas();
+	std::vector<Canvas *> canvases = getCanvas();
+	setCanvas();
 
 	int w = getWidth();
 	int h = getHeight();
@@ -1005,10 +1083,7 @@ love::image::ImageData *Graphics::newScreenshot(love::image::Image *image, bool
 	{
 		delete[] pixels;
 		delete[] screenshot;
-
-		if (curcanvas)
-			curcanvas->startGrab(curcanvas->getAttachedCanvases());
-
+		setCanvas(canvases);
 		throw love::Exception("Out of memory.");
 	}
 
@@ -1040,16 +1115,12 @@ love::image::ImageData *Graphics::newScreenshot(love::image::Image *image, bool
 	catch (love::Exception &)
 	{
 		delete[] screenshot;
-
-		if (curcanvas)
-			curcanvas->startGrab(curcanvas->getAttachedCanvases());
-
+		setCanvas(canvases);
 		throw;
 	}
 
 	// Re-bind the active canvas, if necessary.
-	if (curcanvas)
-		curcanvas->startGrab(curcanvas->getAttachedCanvases());
+	setCanvas(canvases);
 
 	return img;
 }
@@ -1130,53 +1201,140 @@ bool Graphics::isSupported(Support feature) const
 	}
 }
 
-void Graphics::push()
+void Graphics::push(StackType type)
 {
-	if (userMatrices == matrixLimit)
-		throw Exception("Maximum stack depth reached. (More pushes than pops?)");
-	glPushMatrix();
-	++userMatrices;
+	if (stackTypes.size() == MAX_USER_STACK_DEPTH)
+		throw Exception("Maximum stack depth reached (more pushes than pops?)");
+
+	gl.pushTransform();
+
 	pixel_size_stack.push_back(pixel_size_stack.back());
+
+	if (type == STACK_ALL)
+		states.push_back(states.back());
+
+	stackTypes.push_back(type);
 }
 
 void Graphics::pop()
 {
-	if (userMatrices < 1)
-		throw Exception("Minimum stack depth reached. (More pops than pushes?)");
-	glPopMatrix();
-	--userMatrices;
+	if (stackTypes.size() < 1)
+		throw Exception("Minimum stack depth reached (more pops than pushes?)");
+
+	gl.popTransform();
 	pixel_size_stack.pop_back();
+
+	if (stackTypes.back() == STACK_ALL)
+	{
+		DisplayState &newstate = states[states.size() - 2];
+
+		// Hack: the Lua-facing love.graphics.print function will set the current
+		// font if needed, but only on its first call... we always want a font.
+		if (newstate.font.get() == nullptr)
+			newstate.font.set(states.back().font.get());
+
+		restoreStateChecked(newstate);
+
+		// The last two states in the stack should be equal now.
+		states.pop_back();
+	}
+
+	stackTypes.pop_back();
 }
 
 void Graphics::rotate(float r)
 {
-	glRotatef(LOVE_TODEG(r), 0, 0, 1);
+	gl.getTransform().rotate(r);
 }
 
 void Graphics::scale(float x, float y)
 {
-	glScalef(x, y, 1);
+	gl.getTransform().scale(x, y);
 	pixel_size_stack.back() *= 2. / (fabs(x) + fabs(y));
 }
 
 void Graphics::translate(float x, float y)
 {
-	glTranslatef(x, y, 0);
+	gl.getTransform().translate(x, y);
 }
 
 void Graphics::shear(float kx, float ky)
 {
-	Matrix t;
-	t.setShear(kx, ky);
-	glMultMatrixf((const GLfloat *)t.getElements());
+	gl.getTransform().setShear(kx, ky);
 }
 
 void Graphics::origin()
 {
-	glLoadIdentity();
+	gl.getTransform().setIdentity();
 	pixel_size_stack.back() = 1;
 }
 
+Graphics::DisplayState::DisplayState()
+	: color(255, 255, 255, 255)
+	, backgroundColor(0, 0, 0, 255)
+	, blendMode(BLEND_ALPHA)
+	, lineWidth(1.0f)
+	, lineStyle(LINE_SMOOTH)
+	, lineJoin(LINE_JOIN_MITER)
+	, pointSize(1.0f)
+	, scissor(false)
+	, scissorBox()
+	, font(nullptr)
+	, shader(nullptr)
+	, wireframe(false)
+{
+	// We should just directly initialize the array in the initializer list, but
+	// that feature of C++11 is broken in Visual Studio 2013...
+	colorMask[0] = colorMask[1] = colorMask[2] = colorMask[3] = true;
+}
+
+Graphics::DisplayState::DisplayState(const DisplayState &other)
+	: color(other.color)
+	, backgroundColor(other.backgroundColor)
+	, blendMode(other.blendMode)
+	, lineWidth(other.lineWidth)
+	, lineStyle(other.lineStyle)
+	, lineJoin(other.lineJoin)
+	, pointSize(other.pointSize)
+	, scissor(other.scissor)
+	, scissorBox(other.scissorBox)
+	, font(other.font)
+	, shader(other.shader)
+	, canvases(other.canvases)
+	, wireframe(other.wireframe)
+{
+	for (int i = 0; i < 4; i++)
+		colorMask[i] = other.colorMask[i];
+}
+
+Graphics::DisplayState::~DisplayState()
+{
+}
+
+Graphics::DisplayState &Graphics::DisplayState::operator = (const DisplayState &other)
+{
+	color = other.color;
+	backgroundColor = other.backgroundColor;
+	blendMode = other.blendMode;
+	lineWidth = other.lineWidth;
+	lineStyle = other.lineStyle;
+	lineJoin = other.lineJoin;
+	pointSize = other.pointSize;
+	scissor = other.scissor;
+	scissorBox = other.scissorBox;
+
+	font = other.font;
+	shader = other.shader;
+	canvases = other.canvases;
+
+	for (int i = 0; i < 4; i++)
+		colorMask[i] = other.colorMask[i];
+
+	wireframe = other.wireframe;
+
+	return *this;
+}
+
 } // opengl
 } // graphics
 } // love

+ 60 - 59
src/modules/graphics/opengl/Graphics.h

@@ -54,50 +54,6 @@ namespace graphics
 namespace opengl
 {
 
-// During display mode changing, certain
-// variables about the OpenGL context are
-// lost.
-struct DisplayState
-{
-	// Colors.
-	Color color;
-	Color backgroundColor;
-
-	// Blend mode.
-	Graphics::BlendMode blendMode;
-
-	// Line.
-	Graphics::LineStyle lineStyle;
-	Graphics::LineJoin lineJoin;
-
-	// Point.
-	float pointSize;
-
-	// Scissor.
-	bool scissor;
-	OpenGL::Viewport scissorBox;
-
-	// Color mask.
-	bool colorMask[4];
-
-	bool wireframe;
-
-	// Default values.
-	DisplayState()
-	{
-		color.set(255,255,255,255);
-		backgroundColor.set(0, 0, 0, 255);
-		blendMode = Graphics::BLEND_ALPHA;
-		lineStyle = Graphics::LINE_SMOOTH;
-		lineJoin  = Graphics::LINE_JOIN_MITER;
-		pointSize = 1.0f;
-		scissor = false;
-		colorMask[0] = colorMask[1] = colorMask[2] = colorMask[3] = true;
-		wireframe = false;
-	}
-
-};
-
 class Graphics : public love::graphics::Graphics
 {
 public:
@@ -108,10 +64,6 @@ public:
 	// Implements Module.
 	const char *getName() const;
 
-	DisplayState saveState();
-
-	void restoreState(const DisplayState &s);
-
 	virtual void setViewportSize(int width, int height);
 	virtual bool setMode(int width, int height, bool &sRGB);
 	virtual void unSetMode();
@@ -244,10 +196,22 @@ public:
 	 **/
 	Font *getFont() const;
 
+	void setShader(Shader *shader);
+	void setShader();
+
+	Shader *getShader() const;
+
+	void setCanvas(Canvas *canvas);
+	void setCanvas(const std::vector<Canvas *> &canvases);
+	void setCanvas(const std::vector<Object::StrongRef<Canvas>> &canvases);
+	void setCanvas();
+
+	std::vector<Canvas *> getCanvas() const;
+
 	/**
 	 * Sets the enabled color components when rendering.
 	 **/
-	void setColorMask(bool r, bool g, bool b, bool a);
+	void setColorMask(const bool mask[4]);
 
 	/**
 	 * Gets the current color mask.
@@ -447,8 +411,9 @@ public:
 	 **/
 	bool isSupported(Support feature) const;
 
-	void push();
+	void push(StackType type = STACK_TRANSFORM);
 	void pop();
+
 	void rotate(float r);
 	void scale(float x, float y = 1.0f);
 	void translate(float x, float y);
@@ -457,17 +422,50 @@ public:
 
 private:
 
-	Font *currentFont;
+	struct DisplayState
+	{
+		// Colors.
+		Color color;
+		Color backgroundColor;
+
+		// Blend mode.
+		BlendMode blendMode;
+
+		// Line.
+		float lineWidth;
+		LineStyle lineStyle;
+		LineJoin lineJoin;
+
+		// Point.
+		float pointSize;
+
+		// Scissor.
+		bool scissor;
+		OpenGL::Viewport scissorBox;
+
+		Object::StrongRef<Font> font;
+		Object::StrongRef<Shader> shader;
+
+		std::vector<Object::StrongRef<Canvas>> canvases;
+
+		// Color mask.
+		bool colorMask[4];
+
+		bool wireframe;
+
+		DisplayState();
+		DisplayState(const DisplayState &other);
+		~DisplayState();
+
+		DisplayState &operator = (const DisplayState &other);
+	};
+
+	void restoreState(const DisplayState &s);
+	void restoreStateChecked(const DisplayState &s);
+
 	love::window::Window *currentWindow;
 
 	std::vector<double> pixel_size_stack; // stores current size of a pixel (needed for line drawing)
-	LineStyle lineStyle;
-	LineJoin lineJoin;
-	float lineWidth;
-	GLint matrixLimit;
-	GLint userMatrices;
-	bool colorMask[4];
-	bool wireframe;
 
 	int width;
 	int height;
@@ -475,7 +473,10 @@ private:
 
 	bool activeStencil;
 
-	DisplayState savedState;
+	std::vector<DisplayState> states;
+	std::vector<StackType> stackTypes; // Keeps track of the pushed stack types.
+
+	static const size_t MAX_USER_STACK_DEPTH = 64;
 
 	bool displayedMinReqWarning;
 

+ 8 - 19
src/modules/graphics/opengl/Image.cpp

@@ -47,8 +47,6 @@ Image::Image(love::image::ImageData *data, const Flags &flags)
 {
 	width = data->getWidth();
 	height = data->getHeight();
-
-	data->retain();
 	preload();
 }
 
@@ -63,28 +61,22 @@ Image::Image(love::image::CompressedData *cdata, const Flags &flags)
 {
 	width = cdata->getWidth(0);
 	height = cdata->getHeight(0);
-
-	cdata->retain();
 	preload();
 }
 
 Image::~Image()
 {
-	if (data != nullptr)
-		data->release();
-	if (cdata != nullptr)
-		cdata->release();
 	unload();
 }
 
 love::image::ImageData *Image::getImageData() const
 {
-	return data;
+	return data.get();
 }
 
 love::image::CompressedData *Image::getCompressedData() const
 {
-	return cdata;
+	return cdata.get();
 }
 
 void Image::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
@@ -279,9 +271,9 @@ void Image::uploadTexture()
 {
 	bind();
 
-	if (isCompressed() && cdata)
+	if (isCompressed() && cdata.get())
 		uploadCompressedData();
-	else if (data)
+	else if (data.get())
 		uploadImageData();
 }
 
@@ -290,7 +282,7 @@ bool Image::loadVolatile()
 	if (flags.sRGB && !hasSRGBSupport())
 		throw love::Exception("sRGB images are not supported on this system.");
 
-	if (isCompressed() && cdata && !hasCompressedTextureSupport(cdata->getFormat()))
+	if (isCompressed() && cdata.get() && !hasCompressedTextureSupport(cdata->getFormat()))
 	{
 		const char *str;
 		if (image::CompressedData::getConstant(cdata->getFormat(), str))
@@ -397,11 +389,10 @@ void Image::uploadDefaultTexture()
 
 void Image::drawv(const Matrix &t, const Vertex *v)
 {
-	predraw();
+	OpenGL::TempTransform transform(gl);
+	transform.get() *= t;
 
-	glPushMatrix();
-
-	glMultMatrixf((const GLfloat *)t.getElements());
+	predraw();
 
 	glEnableClientState(GL_VERTEX_ARRAY);
 	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
@@ -415,8 +406,6 @@ void Image::drawv(const Matrix &t, const Vertex *v)
 	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 	glDisableClientState(GL_VERTEX_ARRAY);
 
-	glPopMatrix();
-
 	postdraw();
 }
 

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

@@ -157,11 +157,11 @@ private:
 
 	// The ImageData from which the texture is created. May be null if
 	// Compressed image data was used to create the texture.
-	love::image::ImageData *data;
+	Object::StrongRef<love::image::ImageData> data;
 
 	// Or the Compressed Image Data from which the texture is created. May be
 	// null if raw ImageData was used to create the texture.
-	love::image::CompressedData *cdata;
+	Object::StrongRef<love::image::CompressedData> cdata;
 
 	// OpenGL texture identifier.
 	GLuint texture;

+ 7 - 20
src/modules/graphics/opengl/Mesh.cpp

@@ -79,9 +79,6 @@ Mesh::Mesh(int vertexcount, Mesh::DrawMode mode)
 
 Mesh::~Mesh()
 {
-	if (texture)
-		texture->release();
-
 	delete vbo;
 	delete ibo;
 }
@@ -264,25 +261,17 @@ size_t Mesh::getVertexMapCount() const
 
 void Mesh::setTexture(Texture *tex)
 {
-	tex->retain();
-
-	if (texture)
-		texture->release();
-
-	texture = tex;
+	texture.set(tex);
 }
 
 void Mesh::setTexture()
 {
-	if (texture)
-		texture->release();
-
-	texture = nullptr;
+	texture.set(nullptr);
 }
 
 Texture *Mesh::getTexture() const
 {
-	return texture;
+	return texture.get();
 }
 
 void Mesh::setDrawMode(Mesh::DrawMode mode)
@@ -334,7 +323,7 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 	if (vertex_count == 0)
 		return;
 
-	if (texture)
+	if (texture.get())
 		texture->predraw();
 	else
 		gl.bindTexture(0);
@@ -342,8 +331,8 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 	Matrix m;
 	m.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
 
-	glPushMatrix();
-	glMultMatrixf(m.getElements());
+	OpenGL::TempTransform transform(gl);
+	transform.get() *= m;
 
 	VertexBuffer::Bind vbo_bind(*vbo);
 
@@ -412,9 +401,7 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 		gl.setColor(gl.getColor());
 	}
 
-	glPopMatrix();
-
-	if (texture)
+	if (texture.get())
 		texture->postdraw();
 }
 

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

@@ -178,7 +178,7 @@ private:
 	int range_min;
 	int range_max;
 
-	Texture *texture;
+	Object::StrongRef<Texture> texture;
 
 	// Whether the per-vertex colors are used when drawing.
 	bool colors_enabled;

+ 62 - 3
src/modules/graphics/opengl/OpenGL.cpp

@@ -28,6 +28,7 @@
 
 // C++
 #include <algorithm>
+#include <limits>
 
 // C
 #include <cstring>
@@ -47,6 +48,8 @@ OpenGL::OpenGL()
 	, vendor(VENDOR_UNKNOWN)
 	, state()
 {
+	matrices.transform.reserve(10);
+	matrices.projection.reserve(2);
 }
 
 void OpenGL::initContext()
@@ -56,6 +59,7 @@ void OpenGL::initContext()
 
 	initOpenGLFunctions();
 	initVendor();
+	initMatrices();
 
 	contextInitialized = true;
 }
@@ -117,6 +121,15 @@ void OpenGL::setupContext()
 	createDefaultTexture();
 
 	state.lastPseudoInstanceID = -1;
+
+	// Invalidate the cached matrices by setting some elements to NaN.
+	float nan = std::numeric_limits<float>::quiet_NaN();
+	state.lastProjectionMatrix.setTranslation(nan, nan);
+	state.lastTransformMatrix.setTranslation(nan, nan);
+
+	glMatrixMode(GL_MODELVIEW);
+
+	contextInitialized = true;
 }
 
 void OpenGL::deInitContext()
@@ -176,6 +189,15 @@ void OpenGL::initMaxValues()
 	maxRenderTargets = std::min(maxattachments, maxdrawbuffers);
 }
 
+void OpenGL::initMatrices()
+{
+	matrices.transform.clear();
+	matrices.projection.clear();
+
+	matrices.transform.push_back(Matrix());
+	matrices.projection.push_back(Matrix());
+}
+
 void OpenGL::createDefaultTexture()
 {
 	// Set the 'default' texture (id 0) as a repeating white pixel. Otherwise,
@@ -198,6 +220,21 @@ void OpenGL::createDefaultTexture()
 	bindTexture(curtexture);
 }
 
+void OpenGL::pushTransform()
+{
+	matrices.transform.push_back(matrices.transform.back());
+}
+
+void OpenGL::popTransform()
+{
+	matrices.transform.pop_back();
+}
+
+Matrix &OpenGL::getTransform()
+{
+	return matrices.transform.back();
+}
+
 void OpenGL::prepareDraw()
 {
 	Shader *shader = Shader::current;
@@ -218,15 +255,37 @@ void OpenGL::prepareDraw()
 		// 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)
+		for (auto &r : shader->getBoundRetainables())
 		{
 			// Even bigger hack! D:
-			Canvas *canvas = dynamic_cast<Canvas *>(it->second);
+			Canvas *canvas = dynamic_cast<Canvas *>(r.second);
 			if (canvas != nullptr)
 				canvas->resolveMSAA();
 		}
 	}
+
+	const float *curproj = matrices.projection.back().getElements();
+	const float *lastproj = state.lastProjectionMatrix.getElements();
+
+	// We only need to re-upload the projection matrix if it's changed.
+	if (memcmp(curproj, lastproj, sizeof(float) * 16) != 0)
+	{
+		glMatrixMode(GL_PROJECTION);
+		glLoadMatrixf(curproj);
+		glMatrixMode(GL_MODELVIEW);
+
+		state.lastProjectionMatrix = matrices.projection.back();
+	}
+
+	const float *curxform = matrices.transform.back().getElements();
+	const float *lastxform = state.lastTransformMatrix.getElements();
+
+	// Same with the transform matrix.
+	if (memcmp(curxform, lastxform, sizeof(float) * 16) != 0)
+	{
+		glLoadMatrixf(curxform);
+		state.lastTransformMatrix = matrices.transform.back();
+	}
 }
 
 void OpenGL::drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount)

+ 40 - 0
src/modules/graphics/opengl/OpenGL.h

@@ -26,9 +26,11 @@
 // LOVE
 #include "graphics/Color.h"
 #include "graphics/Texture.h"
+#include "common/Matrix.h"
 
 // C++
 #include <vector>
+#include <stack>
 
 // The last argument to AttribPointer takes a buffer offset casted to a pointer.
 #define BUFFER_OFFSET(i) ((char *) NULL + (i))
@@ -101,6 +103,36 @@ public:
 		GLenum func;
 	};
 
+	struct
+	{
+		std::vector<Matrix> transform;
+		std::vector<Matrix> projection;
+	} matrices;
+
+	class TempTransform
+	{
+	public:
+
+		TempTransform(OpenGL &gl)
+			: gl(gl)
+		{
+			gl.pushTransform();
+		}
+
+		~TempTransform()
+		{
+			gl.popTransform();
+		}
+
+		Matrix &get()
+		{
+			return gl.getTransform();
+		}
+
+	private:
+		OpenGL &gl;
+	};
+
 	OpenGL();
 
 	/**
@@ -121,6 +153,10 @@ public:
 	 **/
 	void deInitContext();
 
+	void pushTransform();
+	void popTransform();
+	Matrix &getTransform();
+
 	/**
 	 * Set up necessary state (LOVE-provided shader uniforms, etc.) for drawing.
 	 * This *MUST* be called directly before OpenGL drawing functions.
@@ -264,6 +300,7 @@ private:
 	void initVendor();
 	void initOpenGLFunctions();
 	void initMaxValues();
+	void initMatrices();
 	void createDefaultTexture();
 
 	bool contextInitialized;
@@ -296,6 +333,9 @@ private:
 		// The last ID value used for pseudo-instancing.
 		int lastPseudoInstanceID;
 
+		Matrix lastProjectionMatrix;
+		Matrix lastTransformMatrix;
+
 	} state;
 
 }; // OpenGL

+ 20 - 37
src/modules/graphics/opengl/ParticleSystem.cpp

@@ -102,7 +102,6 @@ ParticleSystem::ParticleSystem(Texture *texture, uint32 size)
 	sizes.push_back(1.0f);
 	colors.push_back(Colorf(1.0f, 1.0f, 1.0f, 1.0f));
 	setBufferSize(size);
-	texture->retain();
 }
 
 ParticleSystem::ParticleSystem(const ParticleSystem &p)
@@ -150,22 +149,10 @@ ParticleSystem::ParticleSystem(const ParticleSystem &p)
 	, relativeRotation(p.relativeRotation)
 {
 	setBufferSize(maxParticles);
-
-	if (texture != nullptr)
-		texture->retain();
-
-	for (Quad *quad : quads)
-		quad->retain();
 }
 
 ParticleSystem::~ParticleSystem()
 {
-	if (texture != nullptr)
-		texture->release();
-
-	for (Quad *quad : quads)
-		quad->release();
-
 	deleteBuffers();
 }
 
@@ -422,19 +409,14 @@ ParticleSystem::Particle *ParticleSystem::removeParticle(Particle *p)
 	return pNext;
 }
 
-void ParticleSystem::setTexture(Texture *texture)
+void ParticleSystem::setTexture(Texture *tex)
 {
-	Object::AutoRelease imagerelease(this->texture);
-
-	this->texture = texture;
-
-	if (texture)
-		texture->retain();
+	texture.set(tex);
 }
 
 Texture *ParticleSystem::getTexture() const
 {
-	return texture;
+	return texture.get();
 }
 
 void ParticleSystem::setInsertMode(InsertMode mode)
@@ -732,26 +714,29 @@ std::vector<Color> ParticleSystem::getColor() const
 
 void ParticleSystem::setQuads(const std::vector<Quad *> &newQuads)
 {
-	for (Quad *quad : newQuads)
-		quad->retain();
+	std::vector<StrongRef<Quad>> quadlist;
+	quadlist.reserve(newQuads.size());
 
-	for (Quad *quad : quads)
-		quad->release();
+	for (Quad *q : newQuads)
+		quadlist.push_back(q);
 
-	quads = newQuads;
+	quads = quadlist;
 }
 
 void ParticleSystem::setQuads()
 {
-	for (Quad *quad : quads)
-		quad->release();
-
 	quads.clear();
 }
 
-const std::vector<Quad *> &ParticleSystem::getQuads() const
+std::vector<Quad *> ParticleSystem::getQuads() const
 {
-	return quads;
+	std::vector<Quad *> quadlist;
+	quadlist.reserve(quads.size());
+
+	for (const Object::StrongRef<Quad> &q : quads)
+		quadlist.push_back(q.get());
+
+	return quadlist;
 }
 
 void ParticleSystem::setRelativeRotation(bool enable)
@@ -838,16 +823,16 @@ bool ParticleSystem::isFull() 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)
+	if (pCount == 0 || texture.get() == nullptr || pMem == nullptr || particleVerts == nullptr)
 		return;
 
 	Color curcolor = gl.getColor();
 
-	glPushMatrix();
-
 	static Matrix t;
 	t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
-	glMultMatrixf((const GLfloat *)t.getElements());
+
+	OpenGL::TempTransform transform(gl);
+	transform.get() *= t;
 
 	const Vertex *textureVerts = texture->getVertices();
 	Vertex *pVerts = particleVerts;
@@ -901,8 +886,6 @@ void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, flo
 
 	texture->postdraw();
 
-	glPopMatrix();
-
 	gl.setColor(curcolor);
 }
 

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

@@ -431,7 +431,7 @@ public:
 	/**
 	 * Gets the Quads used when drawing the particles.
 	 **/
-	const std::vector<Quad *> &getQuads() const;
+	std::vector<Quad *> getQuads() const;
 
 	/**
 	 * sets whether particle angles & rotations are relative to their velocities.
@@ -563,7 +563,7 @@ protected:
 	Vertex *particleVerts;
 
 	// The texture to be drawn.
-	Texture *texture;
+	Object::StrongRef<Texture> texture;
 
 	// Whether the particle emitter is active.
 	bool active;
@@ -640,7 +640,7 @@ protected:
 	std::vector<Colorf> colors;
 
 	// Quads.
-	std::vector<Quad *> quads;
+	std::vector<Object::StrongRef<Quad>> quads;
 
 	bool relativeRotation;
 

+ 12 - 19
src/modules/graphics/opengl/Shader.cpp

@@ -97,8 +97,8 @@ Shader::~Shader()
 	if (current == this)
 		detach();
 
-	for (auto it = boundRetainables.begin(); it != boundRetainables.end(); ++it)
-		it->second->release();
+	for (const auto &retainable : boundRetainables)
+		retainable.second->release();
 
 	boundRetainables.clear();
 
@@ -178,9 +178,8 @@ void Shader::createProgram(const std::vector<GLuint> &shaderids)
 	if (program == 0)
 		throw love::Exception("Cannot create shader program object.");
 
-	std::vector<GLuint>::const_iterator it;
-	for (it = shaderids.begin(); it != shaderids.end(); ++it)
-		glAttachShader(program, *it);
+	for (GLuint id : shaderids)
+		glAttachShader(program, id);
 
 	// Bind generic vertex attribute indices to names in the shader.
 	for (int i = 0; i < int(OpenGL::ATTRIB_MAX_ENUM); i++)
@@ -201,8 +200,8 @@ void Shader::createProgram(const std::vector<GLuint> &shaderids)
 	glLinkProgram(program);
 
 	// flag shaders for auto-deletion when the program object is deleted.
-	for (it = shaderids.begin(); it != shaderids.end(); ++it)
-		glDeleteShader(*it);
+	for (GLuint id : shaderids)
+		glDeleteShader(id);
 
 	GLint status;
 	glGetProgramiv(program, GL_LINK_STATUS, &status);
@@ -275,10 +274,9 @@ bool Shader::loadVolatile()
 
 	std::vector<GLuint> shaderids;
 
-	ShaderSources::const_iterator source;
-	for (source = shaderSources.begin(); source != shaderSources.end(); ++source)
+	for (const auto &source : shaderSources)
 	{
-		GLuint shaderid = compileCode(source->first, source->second);
+		GLuint shaderid = compileCode(source.first, source.second);
 		shaderids.push_back(shaderid);
 	}
 
@@ -364,11 +362,10 @@ std::string Shader::getWarnings() const
 	const char *typestr;
 
 	// Get the individual shader stage warnings
-	std::map<ShaderType, std::string>::const_iterator it;
-	for (it = shaderWarnings.begin(); it != shaderWarnings.end(); ++it)
+	for (const auto &warning : shaderWarnings)
 	{
-		if (typeNames.find(it->first, typestr))
-			warnings += std::string(typestr) + std::string(" shader:\n") + it->second;
+		if (typeNames.find(warning.first, typestr))
+			warnings += std::string(typestr) + std::string(" shader:\n") + warning.second;
 	}
 
 	warnings += getProgramWarnings();
@@ -380,13 +377,9 @@ void Shader::attach(bool temporary)
 {
 	if (current != this)
 	{
-		if (current != nullptr)
-			current->release();
-
 		glUseProgram(program);
 		current = this;
-
-		current->retain();
+		// retain/release happens in Graphics::setShader.
 	}
 
 	if (!temporary)

+ 5 - 15
src/modules/graphics/opengl/SpriteBatch.cpp

@@ -88,14 +88,10 @@ SpriteBatch::SpriteBatch(Texture *texture, int size, int usage)
 		delete element_buf;
 		throw love::Exception("Out of memory.");
 	}
-
-	texture->retain();
 }
 
 SpriteBatch::~SpriteBatch()
 {
-	texture->release();
-
 	delete color;
 	delete array_buf;
 	delete element_buf;
@@ -172,15 +168,12 @@ void SpriteBatch::flush()
 
 void SpriteBatch::setTexture(Texture *newtexture)
 {
-	Object::AutoRelease imagerelease(texture);
-
-	newtexture->retain();
-	texture = newtexture;
+	texture.set(newtexture);
 }
 
 Texture *SpriteBatch::getTexture()
 {
-	return texture;
+	return texture.get();
 }
 
 void SpriteBatch::setColor(const Color &color)
@@ -271,11 +264,10 @@ void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float
 		return;
 
 	static Matrix t;
-
-	glPushMatrix();
-
 	t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
-	glMultMatrixf((const GLfloat *)t.getElements());
+
+	OpenGL::TempTransform transform(gl);
+	transform.get() *= t;
 
 	texture->predraw();
 
@@ -314,8 +306,6 @@ void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float
 	}
 
 	texture->postdraw();
-
-	glPopMatrix();
 }
 
 void SpriteBatch::addv(const Vertex *v, int index)

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

@@ -126,7 +126,7 @@ private:
 	 */
 	void setColorv(Vertex *v, const Color &color);
 
-	Texture *texture;
+	Object::StrongRef<Texture> texture;
 
 	// Max number of sprites in the batch.
 	int size;

+ 18 - 9
src/modules/graphics/opengl/wrap_Canvas.cpp

@@ -19,6 +19,7 @@
  **/
 
 #include "wrap_Canvas.h"
+#include "Graphics.h"
 
 namespace love
 {
@@ -37,18 +38,26 @@ int w_Canvas_renderTo(lua_State *L)
 	Canvas *canvas = luax_checkcanvas(L, 1);
 	luaL_checktype(L, 2, LUA_TFUNCTION);
 
-	// Save the current Canvas so we can restore it when we're done.
-	Canvas *oldcanvas = Canvas::current;
+	Graphics *graphics = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 
-	luax_catchexcept(L, [&](){ canvas->startGrab(); });
+	if (graphics)
+	{
+		// Save the current Canvas so we can restore it when we're done.
+		std::vector<Canvas *> oldcanvases = graphics->getCanvas();
 
-	lua_settop(L, 2); // make sure the function is on top of the stack
-	lua_call(L, 0, 0);
+		for (Canvas *c : oldcanvases)
+			c->retain();
 
-	if (oldcanvas != nullptr)
-		oldcanvas->startGrab(oldcanvas->getAttachedCanvases());
-	else
-		Canvas::bindDefaultCanvas();
+		luax_catchexcept(L, [&](){ graphics->setCanvas(canvas); });
+
+		lua_settop(L, 2); // make sure the function is on top of the stack
+		lua_call(L, 0, 0);
+
+		graphics->setCanvas(oldcanvases);
+
+		for (Canvas *c : oldcanvases)
+			c->release();
+	}
 
 	return 0;
 }

+ 29 - 34
src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -651,8 +651,7 @@ int w_setColorMask(lua_State *L)
 			mask[i] = luax_toboolean(L, i + 1);
 	}
 
-	// r, g, b, a
-	instance()->setColorMask(mask[0], mask[1], mask[2], mask[3]);
+	instance()->setColorMask(mask);
 
 	return 0;
 }
@@ -865,43 +864,35 @@ int w_setCanvas(lua_State *L)
 	instance()->discardStencil();
 
 	// called with none -> reset to default buffer
-	if (lua_isnoneornil(L,1))
+	if (lua_isnoneornil(L, 1))
 	{
-		Canvas::bindDefaultCanvas();
+		instance()->setCanvas();
 		return 0;
 	}
 
 	bool is_table = lua_istable(L, 1);
-	std::vector<Canvas *> attachments;
-
-	Canvas *canvas = 0;
+	std::vector<Canvas *> canvases;
 
 	if (is_table)
 	{
-		// grab the first canvas in the array and attach the rest
-		lua_rawgeti(L, 1, 1);
-		canvas = luax_checkcanvas(L, -1);
-		lua_pop(L, 1);
-
-		for (size_t i = 2; i <= lua_objlen(L, 1); i++)
+		for (size_t i = 1; i <= lua_objlen(L, 1); i++)
 		{
 			lua_rawgeti(L, 1, i);
-			attachments.push_back(luax_checkcanvas(L, -1));
+			canvases.push_back(luax_checkcanvas(L, -1));
 			lua_pop(L, 1);
 		}
 	}
 	else
 	{
-		canvas = luax_checkcanvas(L, 1);
-		for (int i = 2; i <= lua_gettop(L); i++)
-			attachments.push_back(luax_checkcanvas(L, i));
+		for (int i = 1; i <= lua_gettop(L); i++)
+			canvases.push_back(luax_checkcanvas(L, i));
 	}
 
 	luax_catchexcept(L, [&]() {
-		if (attachments.size() > 0)
-			canvas->startGrab(attachments);
+		if (canvases.size() > 0)
+			instance()->setCanvas(canvases);
 		else
-			canvas->startGrab();
+			instance()->setCanvas();
 	});
 
 	return 0;
@@ -909,24 +900,23 @@ int w_setCanvas(lua_State *L)
 
 int w_getCanvas(lua_State *L)
 {
-	Canvas *canvas = Canvas::current;
-	int n = 1;
+	const std::vector<Canvas *> canvases = instance()->getCanvas();
+	int n = 0;
 
-	if (canvas)
+	if (!canvases.empty())
 	{
-		canvas->retain();
-		luax_pushtype(L, "Canvas", GRAPHICS_CANVAS_T, canvas);
-
-		const std::vector<Canvas *> &attachments = canvas->getAttachedCanvases();
-		for (size_t i = 0; i < attachments.size(); i++)
+		for (Canvas *c : canvases)
 		{
-			attachments[i]->retain();
-			luax_pushtype(L, "Canvas", GRAPHICS_CANVAS_T, attachments[i]);
+			c->retain();
+			luax_pushtype(L, "Canvas", GRAPHICS_CANVAS_T, c);
 			n++;
 		}
 	}
 	else
+	{
 		lua_pushnil(L);
+		n = 1;
+	}
 
 	return n;
 }
@@ -935,18 +925,18 @@ int w_setShader(lua_State *L)
 {
 	if (lua_isnoneornil(L,1))
 	{
-		Shader::detach();
+		instance()->setShader();
 		return 0;
 	}
 
 	Shader *shader = luax_checkshader(L, 1);
-	shader->attach();
+	instance()->setShader(shader);
 	return 0;
 }
 
 int w_getShader(lua_State *L)
 {
-	Shader *shader = Shader::current;
+	Shader *shader = instance()->getShader();
 	if (shader)
 	{
 		shader->retain();
@@ -1299,7 +1289,12 @@ int w_polygon(lua_State *L)
 
 int w_push(lua_State *L)
 {
-	luax_catchexcept(L, [&](){ instance()->push(); });
+	Graphics::StackType stype = Graphics::STACK_TRANSFORM;
+	const char *sname = lua_isnoneornil(L, 1) ? nullptr : luaL_checkstring(L, 1);
+	if (sname && !Graphics::getConstant(sname, stype))
+		return luaL_error(L, "Invalid graphics stack type: %s", sname);
+
+	luax_catchexcept(L, [&](){ instance()->push(stype); });
 	return 0;
 }
 

+ 7 - 13
src/modules/mouse/sdl/Mouse.cpp

@@ -77,11 +77,11 @@ Mouse::Mouse()
 
 Mouse::~Mouse()
 {
-	if (curCursor)
+	if (curCursor.get())
 		setCursor();
 
-	for (auto it = systemCursors.begin(); it != systemCursors.end(); ++it)
-		it->second->release();
+	for (auto &c : systemCursors)
+		c.second->release();
 }
 
 love::mouse::Cursor *Mouse::newCursor(love::image::ImageData *data, int hotx, int hoty)
@@ -91,7 +91,7 @@ love::mouse::Cursor *Mouse::newCursor(love::image::ImageData *data, int hotx, in
 
 love::mouse::Cursor *Mouse::getSystemCursor(Cursor::SystemCursor cursortype)
 {
-	Cursor *cursor = NULL;
+	Cursor *cursor = nullptr;
 	auto it = systemCursors.find(cursortype);
 
 	if (it != systemCursors.end())
@@ -107,25 +107,19 @@ love::mouse::Cursor *Mouse::getSystemCursor(Cursor::SystemCursor cursortype)
 
 void Mouse::setCursor(love::mouse::Cursor *cursor)
 {
-	Object::AutoRelease cursorrelease(curCursor);
-
-	curCursor = cursor;
-	curCursor->retain();
-
+	curCursor.set(cursor);
 	SDL_SetCursor((SDL_Cursor *) cursor->getHandle());
 }
 
 void Mouse::setCursor()
 {
-	Object::AutoRelease cursorrelease(curCursor);
-	curCursor = NULL;
-
+	curCursor.set(nullptr);
 	SDL_SetCursor(SDL_GetDefaultCursor());
 }
 
 love::mouse::Cursor *Mouse::getCursor() const
 {
-	return curCursor;
+	return curCursor.get();
 }
 
 int Mouse::getX() const

+ 1 - 1
src/modules/mouse/sdl/Mouse.h

@@ -67,7 +67,7 @@ public:
 
 private:
 
-	love::mouse::Cursor *curCursor;
+	Object::StrongRef<love::mouse::Cursor> curCursor;
 
 	std::map<Cursor::SystemCursor, Cursor *> systemCursors;
 

+ 2 - 5
src/modules/physics/box2d/Body.cpp

@@ -41,7 +41,6 @@ Body::Body(World *world, b2Vec2 p, Body::Type type)
 {
 	udata = new bodyudata();
 	udata->ref = nullptr;
-	world->retain();
 	b2BodyDef def;
 	def.position = Physics::scaleDown(p);
 	def.userData = (void *) udata;
@@ -57,8 +56,7 @@ Body::Body(b2Body *b)
 	, udata(nullptr)
 {
 	udata = (bodyudata *) b->GetUserData();
-	world = (World *)Memoizer::find(b->GetWorld());
-	world->retain();
+	world.set((World *) Memoizer::find(b->GetWorld()));
 	// Box2D body holds a reference to the love Body.
 	this->retain();
 	Memoizer::add(body, this);
@@ -69,7 +67,6 @@ Body::~Body()
 	if (udata != nullptr)
 		delete udata->ref;
 	delete udata;
-	world->release();
 }
 
 float Body::getX()
@@ -420,7 +417,7 @@ bool Body::isFixedRotation() const
 
 World *Body::getWorld() const
 {
-	return world;
+	return world.get();
 }
 
 int Body::getFixtureList(lua_State *L) const

+ 1 - 1
src/modules/physics/box2d/Body.h

@@ -436,7 +436,7 @@ private:
 	//
 	// This ensures that a World only can be destroyed
 	// once all bodies have been destroyed too.
-	World *world;
+	Object::StrongRef<World> world;
 
 	bodyudata *udata;
 

+ 2 - 2
src/modules/physics/box2d/Joint.cpp

@@ -40,7 +40,7 @@ namespace box2d
 {
 
 Joint::Joint(Body *body1)
-	: world(body1->world)
+	: world(body1->world.get())
 	, udata(nullptr)
 	, body1(body1)
 	, body2(nullptr)
@@ -50,7 +50,7 @@ Joint::Joint(Body *body1)
 }
 
 Joint::Joint(Body *body1, Body *body2)
-	: world(body1->world)
+	: world(body1->world.get())
 	, udata(nullptr)
 	, body1(body1)
 	, body2(body2)

+ 3 - 6
src/modules/physics/box2d/World.cpp

@@ -259,23 +259,20 @@ void World::update(float dt)
 	world->Step(dt, 8, 6);
 
 	// Destroy all objects marked during the time step.
-	for (auto i = destructBodies.begin(); i < destructBodies.end(); i++)
+	for (Body *b : destructBodies)
 	{
-		Body *b = *i;
 		if (b->body != 0) b->destroy();
 		// Release for reference in vector.
 		b->release();
 	}
-	for (auto i = destructFixtures.begin(); i < destructFixtures.end(); i++)
+	for (Fixture *f : destructFixtures)
 	{
-		Fixture *f = *i;
 		if (f->isValid()) f->destroy();
 		// Release for reference in vector.
 		f->release();
 	}
-	for (auto i = destructJoints.begin(); i < destructJoints.end(); i++)
+	for (Joint *j : destructJoints)
 	{
-		Joint *j = *i;
 		if (j->isValid()) j->destroyJoint();
 		// Release for reference in vector.
 		j->release();

+ 0 - 3
src/modules/sound/lullaby/Decoder.cpp

@@ -37,7 +37,6 @@ Decoder::Decoder(Data *data, const std::string &ext, int bufferSize)
 	, buffer(0)
 	, eof(false)
 {
-	data->retain();
 	buffer = new char[bufferSize];
 }
 
@@ -45,8 +44,6 @@ Decoder::~Decoder()
 {
 	if (buffer != 0)
 		delete [](char *) buffer;
-	if (data != 0)
-		data->release();
 }
 
 void *Decoder::getBuffer() const

+ 1 - 1
src/modules/sound/lullaby/Decoder.h

@@ -53,7 +53,7 @@ protected:
 
 	// The encoded data. This should be replaced with buffered file
 	// reads in the future.
-	Data *data;
+	Object::StrongRef<Data> data;
 
 	// File extension.
 	std::string ext;

+ 1 - 1
src/modules/sound/lullaby/FLACDecoder.cpp

@@ -69,7 +69,7 @@ bool FLACDecoder::accepts(const std::string &ext)
 
 love::sound::Decoder *FLACDecoder::clone()
 {
-	return new FLACDecoder(data, ext, bufferSize, sampleRate);
+	return new FLACDecoder(data.get(), ext, bufferSize, sampleRate);
 }
 
 int FLACDecoder::decode()

+ 1 - 1
src/modules/sound/lullaby/GmeDecoder.cpp

@@ -86,7 +86,7 @@ bool GmeDecoder::accepts(const std::string &ext)
 
 love::sound::Decoder *GmeDecoder::clone()
 {
-	return new GmeDecoder(data, ext, bufferSize);
+	return new GmeDecoder(data.get(), ext, bufferSize);
 }
 
 int GmeDecoder::decode()

+ 1 - 1
src/modules/sound/lullaby/ModPlugDecoder.cpp

@@ -98,7 +98,7 @@ bool ModPlugDecoder::accepts(const std::string &ext)
 
 love::sound::Decoder *ModPlugDecoder::clone()
 {
-	return new ModPlugDecoder(data, ext, bufferSize);
+	return new ModPlugDecoder(data.get(), ext, bufferSize);
 }
 
 int ModPlugDecoder::decode()

+ 1 - 1
src/modules/sound/lullaby/Mpg123Decoder.cpp

@@ -96,7 +96,7 @@ void Mpg123Decoder::quit()
 
 love::sound::Decoder *Mpg123Decoder::clone()
 {
-	return new Mpg123Decoder(data, ext, bufferSize);
+	return new Mpg123Decoder(data.get(), ext, bufferSize);
 }
 
 int Mpg123Decoder::decode()

+ 1 - 1
src/modules/sound/lullaby/VorbisDecoder.cpp

@@ -179,7 +179,7 @@ bool VorbisDecoder::accepts(const std::string &ext)
 
 love::sound::Decoder *VorbisDecoder::clone()
 {
-	return new VorbisDecoder(data, ext, bufferSize);
+	return new VorbisDecoder(data.get(), ext, bufferSize);
 }
 
 int VorbisDecoder::decode()

+ 1 - 1
src/modules/sound/lullaby/WaveDecoder.cpp

@@ -117,7 +117,7 @@ bool WaveDecoder::accepts(const std::string &ext)
 
 love::sound::Decoder *WaveDecoder::clone()
 {
-	return new WaveDecoder(data, ext, bufferSize);
+	return new WaveDecoder(data.get(), ext, bufferSize);
 }
 
 int WaveDecoder::decode()

+ 20 - 3
src/modules/system/System.cpp

@@ -28,6 +28,7 @@
 #include <spawn.h>
 //#include <stdlib.h>
 //#include <unistd.h>
+#include <signal.h>
 #include <sys/wait.h>
 #elif defined(LOVE_WINDOWS)
 #include "common/utf8.h"
@@ -41,6 +42,20 @@ namespace love
 namespace system
 {
 
+System::System()
+{
+#if defined(LOVE_LINUX)
+	// Enable automatic cleanup of zombie processes
+	struct sigaction act = {0};
+	sigemptyset(&act.sa_mask);
+	act.sa_handler = SIG_DFL;
+	act.sa_flags = SA_NOCLDWAIT;
+
+	// Requires linux 2.6 or higher, so anything remotely modern
+	sigaction(SIGCHLD, &act, nullptr);
+#endif
+}
+
 std::string System::getOS() const
 {
 #if defined(LOVE_MACOSX)
@@ -86,12 +101,14 @@ bool System::openURL(const std::string &url) const
 	if (posix_spawnp(&pid, "xdg-open", nullptr, nullptr, const_cast<char **>(argv), environ) != 0)
 		return false;
 
-	// Wait for xdg-open to complete (or fail.)
+	// Check if xdg-open already completed (or failed.)
 	int status = 0;
-	if (waitpid(pid, &status, 0) == pid)
+	if (waitpid(pid, &status, WNOHANG) > 0)
 		return (status == 0);
 	else
-		return false;
+		// We can't tell what actually happens without waiting for
+		// the process to finish, which could take forever (literally).
+		return true;
 
 #elif defined(LOVE_WINDOWS)
 

+ 1 - 0
src/modules/system/System.h

@@ -48,6 +48,7 @@ public:
 		POWER_MAX_ENUM
 	};
 
+	System();
 	virtual ~System() {}
 
 	// Implements Module.

+ 0 - 3
src/modules/thread/LuaThread.cpp

@@ -37,14 +37,11 @@ LuaThread::LuaThread(const std::string &name, love::Data *code)
 	, args(0)
 	, nargs(0)
 {
-	code->retain();
 	threadName = name;
 }
 
 LuaThread::~LuaThread()
 {
-	code->release();
-
 	// No args should still exist at this point,
 	// but you never know.
 	for (int i = 0; i < nargs; ++i)

+ 1 - 1
src/modules/thread/LuaThread.h

@@ -50,7 +50,7 @@ private:
 
 	void onError();
 
-	love::Data *code;
+	Object::StrongRef<love::Data> code;
 	std::string name;
 	std::string error;
 

+ 4 - 10
src/modules/window/sdl/Window.cpp

@@ -48,9 +48,6 @@ Window::Window()
 
 Window::~Window()
 {
-	if (curMode.icon)
-		curMode.icon->release();
-
 	if (window)
 		SDL_DestroyWindow(window);
 
@@ -185,8 +182,8 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 		}
 
 		// Make sure the window keeps any previously set icon.
-		if (window && curMode.icon)
-			setIcon(curMode.icon);
+		if (window && curMode.icon.get())
+			setIcon(curMode.icon.get());
 	}
 
 	if (!window)
@@ -553,10 +550,7 @@ bool Window::setIcon(love::image::ImageData *imgd)
 	if (!imgd)
 		return false;
 
-	imgd->retain();
-	if (curMode.icon)
-		curMode.icon->release();
-	curMode.icon = imgd;
+	curMode.icon.set(imgd);
 
 	if (!window)
 		return false;
@@ -597,7 +591,7 @@ bool Window::setIcon(love::image::ImageData *imgd)
 
 love::image::ImageData *Window::getIcon()
 {
-	return curMode.icon;
+	return curMode.icon.get();
 }
 
 void Window::minimize()

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

@@ -111,7 +111,7 @@ private:
 		int width;
 		int height;
 		WindowSettings settings;
-		love::image::ImageData *icon;
+		Object::StrongRef<love::image::ImageData> icon;
 
 	} curMode;