Browse Source

Added stack type enums to love.graphics.push (resolves issue #906.) Current enums are "transform" and "all". "transform" is the default (for compatibility.) When love.graphics.push("all") is used, love.graphics.pop() will restore all love.graphics module state to what it was when push was called.

Updated the graphics code to use a custom matrix stack rather than OpenGL1's APIs.
Alex Szpakowski 11 years ago
parent
commit
afc505e183

+ 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
 } // love

+ 6 - 0
src/common/Matrix.h

@@ -150,6 +150,12 @@ public:
 	 **/
 	 **/
 	void transform(Vertex *dst, const Vertex *src, int size) const;
 	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:
 private:
 
 
 	/**
 	/**

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

@@ -109,6 +109,16 @@ bool Graphics::getConstant(SystemLimit in, const char *&out)
 	return systemLimits.find(in, 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[] =
 StringMap<Graphics::DrawMode, Graphics::DRAW_MAX_ENUM>::Entry Graphics::drawModeEntries[] =
 {
 {
 	{ "line", Graphics::DRAW_LINE },
 	{ "line", Graphics::DRAW_LINE },
@@ -192,5 +202,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::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
 } // graphics
 } // love
 } // love

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

@@ -112,6 +112,13 @@ public:
 		LIMIT_MAX_ENUM
 		LIMIT_MAX_ENUM
 	};
 	};
 
 
+	enum StackType
+	{
+		STACK_ALL,
+		STACK_TRANSFORM,
+		STACK_MAX_ENUM
+	};
+
 	struct RendererInfo
 	struct RendererInfo
 	{
 	{
 		std::string name;
 		std::string name;
@@ -167,6 +174,9 @@ public:
 	static bool getConstant(const char *in, SystemLimit &out);
 	static bool getConstant(const char *in, SystemLimit &out);
 	static bool getConstant(SystemLimit in, const char *&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:
 private:
 
 
 	static StringMap<DrawMode, DRAW_MAX_ENUM>::Entry drawModeEntries[];
 	static StringMap<DrawMode, DRAW_MAX_ENUM>::Entry drawModeEntries[];
@@ -193,6 +203,9 @@ private:
 	static StringMap<SystemLimit, LIMIT_MAX_ENUM>::Entry systemLimitEntries[];
 	static StringMap<SystemLimit, LIMIT_MAX_ENUM>::Entry systemLimitEntries[];
 	static StringMap<SystemLimit, LIMIT_MAX_ENUM> systemLimits;
 	static StringMap<SystemLimit, LIMIT_MAX_ENUM> systemLimits;
 
 
+	static StringMap<StackType, STACK_MAX_ENUM>::Entry stackTypeEntries[];
+	static StringMap<StackType, STACK_MAX_ENUM> stackTypes;
+
 }; // Graphics
 }; // Graphics
 
 
 } // graphics
 } // graphics

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

@@ -594,16 +594,13 @@ void Canvas::unloadVolatile()
 	fbo = depth_stencil = texture = 0;
 	fbo = depth_stencil = texture = 0;
 	resolve_fbo = msaa_buffer = 0;
 	resolve_fbo = msaa_buffer = 0;
 
 
-	for (size_t i = 0; i < attachedCanvases.size(); i++)
-		attachedCanvases[i]->release();
-
 	attachedCanvases.clear();
 	attachedCanvases.clear();
 }
 }
 
 
 void Canvas::drawv(const Matrix &t, const Vertex *v)
 void Canvas::drawv(const Matrix &t, const Vertex *v)
 {
 {
-	glPushMatrix();
-	glMultMatrixf((const GLfloat *)t.getElements());
+	OpenGL::TempTransform transform(gl);
+	transform.get() *= t;
 
 
 	predraw();
 	predraw();
 
 
@@ -620,8 +617,6 @@ void Canvas::drawv(const Matrix &t, const Vertex *v)
 	glDisableClientState(GL_VERTEX_ARRAY);
 	glDisableClientState(GL_VERTEX_ARRAY);
 
 
 	postdraw();
 	postdraw();
-	
-	glPopMatrix();
 }
 }
 
 
 void Canvas::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 void Canvas::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
@@ -691,16 +686,8 @@ void Canvas::setupGrab()
 	strategy->bindFBO(fbo);
 	strategy->bindFBO(fbo);
 	gl.setViewport(OpenGL::Viewport(0, 0, width, height));
 	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.
 	// Make sure the correct sRGB setting is used when drawing to the canvas.
 	if (format == FORMAT_SRGB)
 	if (format == FORMAT_SRGB)
@@ -754,11 +741,8 @@ void Canvas::startGrab(const std::vector<Canvas *> &canvases)
 	// Attach the canvas textures to the active FBO and set up MRTs.
 	// Attach the canvas textures to the active FBO and set up MRTs.
 	strategy->setAttachments(canvases);
 	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;
 	attachedCanvases = canvases;
 }
 }
@@ -773,10 +757,6 @@ void Canvas::startGrab()
 	// make sure the FBO is only using a single canvas
 	// make sure the FBO is only using a single canvas
 	strategy->setAttachments();
 	strategy->setAttachments();
 
 
-	// release any previously attached canvases
-	for (size_t i = 0; i < attachedCanvases.size(); i++)
-		attachedCanvases[i]->release();
-
 	attachedCanvases.clear();
 	attachedCanvases.clear();
 }
 }
 
 
@@ -786,9 +766,7 @@ void Canvas::stopGrab(bool switchingToOtherCanvas)
 	if (current != this)
 	if (current != this)
 		return;
 		return;
 
 
-	glMatrixMode(GL_PROJECTION);
-	glPopMatrix();
-	glMatrixMode(GL_MODELVIEW);
+	gl.matrices.projection.pop_back();
 
 
 	if (switchingToOtherCanvas)
 	if (switchingToOtherCanvas)
 	{
 	{
@@ -1123,12 +1101,6 @@ bool Canvas::isFormatSupported(Canvas::Format format)
 	return supported;
 	return supported;
 }
 }
 
 
-void Canvas::bindDefaultCanvas()
-{
-	if (current != nullptr)
-		current->stopGrab();
-}
-
 bool Canvas::getConstant(const char *in, Format &out)
 bool Canvas::getConstant(const char *in, Format &out)
 {
 {
 	return formats.find(in, 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 bool isFormatSupported(Format format);
 
 
 	static Canvas *current;
 	static Canvas *current;
-	static void bindDefaultCanvas();
 
 
 	// The viewport dimensions of the system (default) framebuffer.
 	// The viewport dimensions of the system (default) framebuffer.
 	static OpenGL::Viewport systemViewport;
 	static OpenGL::Viewport systemViewport;

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

@@ -368,11 +368,11 @@ void Font::print(const std::string &text, float x, float y, float extra_spacing,
 	// second (using the struct's < operator).
 	// second (using the struct's < operator).
 	std::sort(glyphinfolist.begin(), glyphinfolist.end());
 	std::sort(glyphinfolist.begin(), glyphinfolist.end());
 
 
-	glPushMatrix();
-
 	Matrix t;
 	Matrix t;
 	t.setTransformation(ceilf(x), ceilf(y), angle, sx, sy, ox, oy, kx, ky);
 	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_VERTEX_ARRAY);
 	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
 	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
@@ -393,8 +393,6 @@ void Font::print(const std::string &text, float x, float y, float extra_spacing,
 
 
 	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 	glDisableClientState(GL_VERTEX_ARRAY);
 	glDisableClientState(GL_VERTEX_ARRAY);
-
-	glPopMatrix();
 }
 }
 
 
 int Font::getWidth(const std::string &str)
 int Font::getWidth(const std::string &str)

+ 436 - 232
src/modules/graphics/opengl/Graphics.cpp

@@ -44,19 +44,14 @@ namespace opengl
 {
 {
 
 
 Graphics::Graphics()
 Graphics::Graphics()
-	: currentFont(0)
-	, lineStyle(LINE_SMOOTH)
-	, lineJoin(LINE_JOIN_MITER)
-	, lineWidth(1)
-	, matrixLimit(0)
-	, userMatrices(0)
-	, colorMask()
-	, width(0)
+	: width(0)
 	, height(0)
 	, height(0)
 	, created(false)
 	, created(false)
 	, activeStencil(false)
 	, activeStencil(false)
-	, savedState()
 {
 {
+	states.reserve(10);
+	states.push_back(DisplayState());
+
 	currentWindow = love::window::sdl::Window::createSingleton();
 	currentWindow = love::window::sdl::Window::createSingleton();
 
 
 	int w, h;
 	int w, h;
@@ -70,8 +65,8 @@ Graphics::Graphics()
 
 
 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();
 	currentWindow->release();
 }
 }
@@ -81,53 +76,90 @@ const char *Graphics::getName() const
 	return "love.graphics.opengl";
 	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 point style
-	s.pointStyle = (glIsEnabled(GL_POINT_SMOOTH) == GL_TRUE) ? Graphics::POINT_SMOOTH : Graphics::POINT_ROUGH;
-	//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];
-
-	s.wireframe = isWireframe();
-
-	return s;
-}
-
 void Graphics::restoreState(const DisplayState &s)
 void Graphics::restoreState(const DisplayState &s)
 {
 {
 	setColor(s.color);
 	setColor(s.color);
 	setBackgroundColor(s.backgroundColor);
 	setBackgroundColor(s.backgroundColor);
+
 	setBlendMode(s.blendMode);
 	setBlendMode(s.blendMode);
-	setLineWidth(lineWidth);
+
+	setLineWidth(s.lineWidth);
 	setLineStyle(s.lineStyle);
 	setLineStyle(s.lineStyle);
 	setLineJoin(s.lineJoin);
 	setLineJoin(s.lineJoin);
+
 	setPointSize(s.pointSize);
 	setPointSize(s.pointSize);
 	setPointStyle(s.pointStyle);
 	setPointStyle(s.pointStyle);
+
 	if (s.scissor)
 	if (s.scissor)
 		setScissor(s.scissorBox.x, s.scissorBox.y, s.scissorBox.w, s.scissorBox.h);
 		setScissor(s.scissorBox.x, s.scissorBox.y, s.scissorBox.w, s.scissorBox.h);
 	else
 	else
 		setScissor();
 		setScissor();
-	setColorMask(s.colorMask[0], s.colorMask[1], s.colorMask[2], s.colorMask[3]);
+
+	setFont(s.font);
+	setShader(s.shader);
+	setCanvas(s.canvases);
+
+	setColorMask(s.colorMask);
 	setWireframe(s.wireframe);
 	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.pointStyle != cur.pointStyle)
+		setPointStyle(s.pointStyle);
+
+	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);
+	setShader(s.shader);
+
+	for (size_t i = 0; i < s.canvases.size() && i < cur.canvases.size(); i++)
+	{
+		if (s.canvases[i] != cur.canvases[i])
+		{
+			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)
 void Graphics::setViewportSize(int width, int height)
 {
 {
 	this->width = width;
 	this->width = width;
@@ -138,8 +170,8 @@ void Graphics::setViewportSize(int width, int height)
 
 
 	// We want to affect the main screen, not any Canvas that's currently active
 	// We want to affect the main screen, not any Canvas that's currently active
 	// (not that any *should* be active when this is called.)
 	// (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.
 	// Set the viewport to top-left corner.
 	gl.setViewport(OpenGL::Viewport(0, 0, width, height));
 	gl.setViewport(OpenGL::Viewport(0, 0, width, height));
@@ -148,18 +180,11 @@ void Graphics::setViewportSize(int width, int height)
 	// made aware of the new system viewport size.
 	// made aware of the new system viewport size.
 	Canvas::systemViewport = gl.getViewport();
 	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.
 	// Restore the previously active Canvas.
-	if (c != nullptr)
-		c->startGrab(c->getAttachedCanvases());
+	setCanvas(canvases);
 }
 }
 
 
 bool Graphics::setMode(int width, int height, bool &sRGB)
 bool Graphics::setMode(int width, int height, bool &sRGB)
@@ -182,7 +207,8 @@ bool Graphics::setMode(int width, int height, bool &sRGB)
 	glEnable(GL_BLEND);
 	glEnable(GL_BLEND);
 
 
 	// Enable all color component writes.
 	// Enable all color component writes.
-	setColorMask(true, true, true, true);
+	bool colormask[] = {true, true, true, true};
+	setColorMask(colormask);
 
 
 	// Enable line/point smoothing.
 	// Enable line/point smoothing.
 	setLineStyle(LINE_SMOOTH);
 	setLineStyle(LINE_SMOOTH);
@@ -197,28 +223,9 @@ bool Graphics::setMode(int width, int height, bool &sRGB)
 	glEnable(GL_TEXTURE_2D);
 	glEnable(GL_TEXTURE_2D);
 	gl.setTextureUnit(0);
 	gl.setTextureUnit(0);
 
 
-	// Reset modelview matrix
-	glMatrixMode(GL_MODELVIEW);
-	glLoadIdentity();
-
 	// Set pixel row alignment
 	// Set pixel row alignment
 	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
 	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.
 	// Set whether drawing converts input from linear -> sRGB colorspace.
 	if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_sRGB || GLEE_EXT_framebuffer_sRGB)
 	if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_sRGB || GLEE_EXT_framebuffer_sRGB)
 	{
 	{
@@ -244,6 +251,17 @@ bool Graphics::setMode(int width, int height, bool &sRGB)
 
 
 	setDebug(enabledebug);
 	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;
 	return true;
 }
 }
 
 
@@ -252,9 +270,6 @@ void Graphics::unSetMode()
 	if (!isCreated())
 	if (!isCreated())
 		return;
 		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
 	// Unload all volatile objects. These must be reloaded after the display
 	// mode change.
 	// mode change.
 	Volatile::unloadAll();
 	Volatile::unloadAll();
@@ -323,8 +338,6 @@ void Graphics::reset()
 {
 {
 	DisplayState s;
 	DisplayState s;
 	discardStencil();
 	discardStencil();
-	Canvas::bindDefaultCanvas();
-	Shader::detach();
 	origin();
 	origin();
 	restoreState(s);
 	restoreState(s);
 }
 }
@@ -356,13 +369,18 @@ bool Graphics::isCreated() const
 
 
 void Graphics::setScissor(int x, int y, int width, int height)
 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);
 	glEnable(GL_SCISSOR_TEST);
 	// OpenGL's reversed y-coordinate is compensated for in OpenGL::setScissor.
 	// 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()
 void Graphics::setScissor()
 {
 {
+	states.back().scissor = false;
 	glDisable(GL_SCISSOR_TEST);
 	glDisable(GL_SCISSOR_TEST);
 }
 }
 
 
@@ -375,7 +393,7 @@ bool Graphics::getScissor(int &x, int &y, int &width, int &height) const
 	width = scissor.w;
 	width = scissor.w;
 	height = scissor.h;
 	height = scissor.h;
 
 
-	return glIsEnabled(GL_SCISSOR_TEST) == GL_TRUE;
+	return states.back().scissor;
 }
 }
 
 
 void Graphics::defineStencil()
 void Graphics::defineStencil()
@@ -399,7 +417,7 @@ void Graphics::useStencil(bool invert)
 {
 {
 	glStencilFunc(GL_EQUAL, (GLint)(!invert), 1); // invert ? 0 : 1
 	glStencilFunc(GL_EQUAL, (GLint)(!invert), 1); // invert ? 0 : 1
 	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
 	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
-	setColorMask(colorMask[0], colorMask[1], colorMask[2], colorMask[3]);
+	setColorMask(states.back().colorMask);
 }
 }
 
 
 void Graphics::discardStencil()
 void Graphics::discardStencil()
@@ -407,7 +425,7 @@ void Graphics::discardStencil()
 	if (!activeStencil)
 	if (!activeStencil)
 		return;
 		return;
 
 
-	setColorMask(colorMask[0], colorMask[1], colorMask[2], colorMask[3]);
+	setColorMask(states.back().colorMask);
 	glDisable(GL_STENCIL_TEST);
 	glDisable(GL_STENCIL_TEST);
 	activeStencil = false;
 	activeStencil = false;
 }
 }
@@ -566,65 +584,160 @@ Mesh *Graphics::newMesh(int vertexcount, Mesh::DrawMode mode)
 void Graphics::setColor(const Color &c)
 void Graphics::setColor(const Color &c)
 {
 {
 	gl.setColor(c);
 	gl.setColor(c);
+	states.back().color = c;
 }
 }
 
 
 Color Graphics::getColor() const
 Color Graphics::getColor() const
 {
 {
-	return gl.getColor();
+	return states.back().color;
 }
 }
 
 
 void Graphics::setBackgroundColor(const Color &c)
 void Graphics::setBackgroundColor(const Color &c)
 {
 {
 	gl.setClearColor(c);
 	gl.setClearColor(c);
+	states.back().backgroundColor = c;
 }
 }
 
 
 Color Graphics::getBackgroundColor() const
 Color Graphics::getBackgroundColor() const
 {
 {
-	return gl.getClearColor();
+	return states.back().backgroundColor;
 }
 }
 
 
 void Graphics::setFont(Font *font)
 void Graphics::setFont(Font *font)
 {
 {
-	Object::AutoRelease fontrelease(currentFont);
+	DisplayState &state = states.back();
+
+	if (font != nullptr)
+		font->retain();
 
 
-	currentFont = font;
+	if (state.font != nullptr)
+		state.font->release();
 
 
-	if (font != 0)
-		currentFont->retain();
+	state.font = font;
 }
 }
 
 
 Font *Graphics::getFont() const
 Font *Graphics::getFont() const
 {
 {
-	return currentFont;
+	return states.back().font;
+}
+
+void Graphics::setShader(Shader *shader)
+{
+	if (shader == nullptr)
+		return setShader();
+
+	DisplayState &state = states.back();
+
+	shader->attach();
+
+	if (shader)
+		shader->retain();
+
+	if (state.shader)
+		state.shader->release();
+
+	state.shader = shader;
+}
+
+void Graphics::setShader()
+{
+	DisplayState &state = states.back();
+
+	Shader::detach();
+
+	if (state.shader)
+		state.shader->release();
+
+	state.shader = nullptr;
+}
+
+Shader *Graphics::getShader() const
+{
+	return states.back().shader;
+}
+
+void Graphics::setCanvas(Canvas *canvas)
+{
+	if (canvas == nullptr)
+		return setCanvas();
+
+	DisplayState &state = states.back();
+
+	canvas->startGrab();
+
+	canvas->retain();
+
+	for (Canvas *c : state.canvases)
+		c->release();
+
+	state.canvases.clear();
+	state.canvases.push_back(canvas);
+}
+
+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);
+
+	for (Canvas *c : canvases)
+		c->retain();
+
+	for (Canvas *c : state.canvases)
+		c->release();
+
+	state.canvases = canvases;
+}
+
+void Graphics::setCanvas()
+{
+	DisplayState &state = states.back();
+
+	if (Canvas::current != nullptr)
+		Canvas::current->stopGrab();
+
+	for (Canvas *c : state.canvases)
+		c->release();
+
+	state.canvases.clear();
+}
+
+std::vector<Canvas *> Graphics::getCanvas() const
+{
+	return states.back().canvases;
 }
 }
 
 
-void Graphics::setColorMask(bool r, bool g, bool b, bool a)
+void Graphics::setColorMask(const bool mask[4])
 {
 {
-	colorMask[0] = r;
-	colorMask[1] = g;
-	colorMask[2] = b;
-	colorMask[3] = a;
+	for (int i = 0; i < 4; i++)
+		states.back().colorMask[i] = mask[i];
 
 
-	glColorMask((GLboolean) r, (GLboolean) g, (GLboolean) b, (GLboolean) a);
+	glColorMask(mask[0], mask[1], mask[2], mask[3]);
 }
 }
 
 
 const bool *Graphics::getColorMask() const
 const bool *Graphics::getColorMask() const
 {
 {
-	return colorMask;
+	return states.back().colorMask;
 }
 }
 
 
 void Graphics::setBlendMode(Graphics::BlendMode mode)
 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)
 	switch (mode)
 	{
 	{
 	case BLEND_ALPHA:
 	case BLEND_ALPHA:
 		if (GLEE_VERSION_1_4 || GLEE_EXT_blend_func_separate)
 		if (GLEE_VERSION_1_4 || GLEE_EXT_blend_func_separate)
 		{
 		{
-			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;
 		}
 		}
 		else
 		else
 		{
 		{
@@ -632,65 +745,42 @@ void Graphics::setBlendMode(Graphics::BlendMode mode)
 			// This will most likely only be used for the Microsoft software renderer and
 			// This will most likely only be used for the Microsoft software renderer and
 			// since it's still stuck with OpenGL 1.1, the only expected difference is a
 			// since it's still stuck with OpenGL 1.1, the only expected difference is a
 			// different alpha value when reading back the default framebuffer (newScreenshot).
 			// different alpha value when reading back the default framebuffer (newScreenshot).
-			state.srcRGB = state.srcA = GL_SRC_ALPHA;
-			state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_ALPHA;
+			blend.srcRGB = blend.srcA = GL_SRC_ALPHA;
+			blend.dstRGB = blend.dstA = GL_ONE_MINUS_SRC_ALPHA;
 		}
 		}
 		break;
 		break;
 	case BLEND_MULTIPLICATIVE:
 	case BLEND_MULTIPLICATIVE:
-		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;
 		break;
 	case BLEND_PREMULTIPLIED:
 	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;
 		break;
 	case BLEND_SUBTRACTIVE:
 	case BLEND_SUBTRACTIVE:
-		state.func = GL_FUNC_REVERSE_SUBTRACT;
+		blend.func = GL_FUNC_REVERSE_SUBTRACT;
 	case BLEND_ADDITIVE:
 	case BLEND_ADDITIVE:
-		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;
 		break;
 	case BLEND_SCREEN:
 	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;
 		break;
 	case BLEND_REPLACE:
 	case BLEND_REPLACE:
 	default:
 	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;
 		break;
 	}
 	}
 
 
-	gl.setBlendState(state);
+	gl.setBlendState(blend);
+	states.back().blendMode = mode;
 }
 }
 
 
 Graphics::BlendMode Graphics::getBlendMode() const
 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_SUBTRACTIVE;
-	// 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_ADDITIVE;
-		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_MULTIPLICATIVE;
-		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)
 void Graphics::setDefaultFilter(const Texture::Filter &f)
@@ -717,37 +807,38 @@ void Graphics::getDefaultMipmapFilter(Texture::FilterMode *filter, float *sharpn
 
 
 void Graphics::setLineWidth(float width)
 void Graphics::setLineWidth(float width)
 {
 {
-	lineWidth = width;
+	states.back().lineWidth = width;
 }
 }
 
 
 void Graphics::setLineStyle(Graphics::LineStyle style)
 void Graphics::setLineStyle(Graphics::LineStyle style)
 {
 {
-	lineStyle = style;
+	states.back().lineStyle = style;
 }
 }
 
 
 void Graphics::setLineJoin(Graphics::LineJoin join)
 void Graphics::setLineJoin(Graphics::LineJoin join)
 {
 {
-	lineJoin = join;
+	states.back().lineJoin = join;
 }
 }
 
 
 float Graphics::getLineWidth() const
 float Graphics::getLineWidth() const
 {
 {
-	return lineWidth;
+	return states.back().lineWidth;
 }
 }
 
 
 Graphics::LineStyle Graphics::getLineStyle() const
 Graphics::LineStyle Graphics::getLineStyle() const
 {
 {
-	return lineStyle;
+	return states.back().lineStyle;
 }
 }
 
 
 Graphics::LineJoin Graphics::getLineJoin() const
 Graphics::LineJoin Graphics::getLineJoin() const
 {
 {
-	return lineJoin;
+	return states.back().lineJoin;
 }
 }
 
 
 void Graphics::setPointSize(float size)
 void Graphics::setPointSize(float size)
 {
 {
-	glPointSize((GLfloat)size);
+	glPointSize(size);
+	states.back().pointSize = size;
 }
 }
 
 
 void Graphics::setPointStyle(Graphics::PointStyle style)
 void Graphics::setPointStyle(Graphics::PointStyle style)
@@ -756,43 +847,44 @@ void Graphics::setPointStyle(Graphics::PointStyle style)
 		glEnable(GL_POINT_SMOOTH);
 		glEnable(GL_POINT_SMOOTH);
 	else // love::POINT_ROUGH
 	else // love::POINT_ROUGH
 		glDisable(GL_POINT_SMOOTH);
 		glDisable(GL_POINT_SMOOTH);
+
+	states.back().pointStyle = style;
 }
 }
 
 
 float Graphics::getPointSize() const
 float Graphics::getPointSize() const
 {
 {
-	GLfloat size;
-	glGetFloatv(GL_POINT_SIZE, &size);
-	return (float)size;
+	return states.back().pointSize;
 }
 }
 
 
 Graphics::PointStyle Graphics::getPointStyle() const
 Graphics::PointStyle Graphics::getPointStyle() const
 {
 {
-	if (glIsEnabled(GL_POINT_SMOOTH) == GL_TRUE)
-		return POINT_SMOOTH;
-	else
-		return POINT_ROUGH;
+	return states.back().pointStyle;
 }
 }
 
 
 void Graphics::setWireframe(bool enable)
 void Graphics::setWireframe(bool enable)
 {
 {
-	wireframe = enable;
 	glPolygonMode(GL_FRONT_AND_BACK, enable ? GL_LINE : GL_FILL);
 	glPolygonMode(GL_FRONT_AND_BACK, enable ? GL_LINE : GL_FILL);
+	states.back().wireframe = enable;
 }
 }
 
 
 bool Graphics::isWireframe() const
 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)
 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 != 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)
 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 == nullptr)
 		return;
 		return;
 
 
 	if (wrap < 0.0f)
 	if (wrap < 0.0f)
@@ -804,59 +896,49 @@ void Graphics::printf(const std::string &str, float x, float y, float wrap, Alig
 	// wrappedlines indicates which lines were automatically wrapped. It's
 	// wrappedlines indicates which lines were automatically wrapped. It's
 	// guaranteed to have the same number of elements as lines_to_draw.
 	// guaranteed to have the same number of elements as lines_to_draw.
 	vector<bool> wrappedlines;
 	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;
 	static Matrix t;
 	t.setTransformation(ceilf(x), ceilf(y), angle, sx, sy, ox, oy, kx, ky);
 	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;
 	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();
 }
 }
 
 
 /**
 /**
@@ -874,22 +956,24 @@ void Graphics::point(float x, float y)
 
 
 void Graphics::polyline(const float *coords, size_t count)
 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;
 			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();
 			line.draw();
 	}
 	}
-	else if (lineJoin == LINE_JOIN_BEVEL)
+	else if (state.lineJoin == LINE_JOIN_BEVEL)
 	{
 	{
 		BevelJoinPolyline line;
 		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();
 		line.draw();
 	}
 	}
 	else // LINE_JOIN_MITER
 	else // LINE_JOIN_MITER
 	{
 	{
 		MiterJoinPolyline line;
 		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();
 		line.draw();
 	}
 	}
 }
 }
@@ -996,9 +1080,8 @@ love::image::ImageData *Graphics::newScreenshot(love::image::Image *image, bool
 {
 {
 	// Temporarily unbind the currently active canvas (glReadPixels reads the
 	// Temporarily unbind the currently active canvas (glReadPixels reads the
 	// active framebuffer, not the main one.)
 	// active framebuffer, not the main one.)
-	Canvas *curcanvas = Canvas::current;
-	if (curcanvas)
-		Canvas::bindDefaultCanvas();
+	std::vector<Canvas *> canvases = getCanvas();
+	setCanvas();
 
 
 	int w = getWidth();
 	int w = getWidth();
 	int h = getHeight();
 	int h = getHeight();
@@ -1019,8 +1102,7 @@ love::image::ImageData *Graphics::newScreenshot(love::image::Image *image, bool
 	{
 	{
 		delete[] pixels;
 		delete[] pixels;
 		delete[] screenshot;
 		delete[] screenshot;
-		if (curcanvas)
-			curcanvas->startGrab(curcanvas->getAttachedCanvases());
+		setCanvas(canvases);
 		throw love::Exception("Out of memory.");
 		throw love::Exception("Out of memory.");
 	}
 	}
 
 
@@ -1052,14 +1134,12 @@ love::image::ImageData *Graphics::newScreenshot(love::image::Image *image, bool
 	catch (love::Exception &)
 	catch (love::Exception &)
 	{
 	{
 		delete[] screenshot;
 		delete[] screenshot;
-		if (curcanvas)
-			curcanvas->startGrab(curcanvas->getAttachedCanvases());
+		setCanvas(canvases);
 		throw;
 		throw;
 	}
 	}
 
 
 	// Re-bind the active canvas, if necessary.
 	// Re-bind the active canvas, if necessary.
-	if (curcanvas)
-		curcanvas->startGrab(curcanvas->getAttachedCanvases());
+	setCanvas(canvases);
 
 
 	return img;
 	return img;
 }
 }
@@ -1157,53 +1237,177 @@ 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());
 	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()
 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();
 	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 == nullptr)
+		{
+			newstate.font = states.back().font;
+			if (newstate.font != nullptr)
+				newstate.font->retain();
+		}
+
+		restoreStateChecked(newstate);
+
+		// The last two states in the stack should be equal now.
+		states.pop_back();
+	}
+
+	stackTypes.pop_back();
 }
 }
 
 
 void Graphics::rotate(float r)
 void Graphics::rotate(float r)
 {
 {
-	glRotatef(LOVE_TODEG(r), 0, 0, 1);
+	gl.getTransform().rotate(r);
 }
 }
 
 
 void Graphics::scale(float x, float y)
 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));
 	pixel_size_stack.back() *= 2. / (fabs(x) + fabs(y));
 }
 }
 
 
 void Graphics::translate(float x, float y)
 void Graphics::translate(float x, float y)
 {
 {
-	glTranslatef(x, y, 0);
+	gl.getTransform().translate(x, y);
 }
 }
 
 
 void Graphics::shear(float kx, float ky)
 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()
 void Graphics::origin()
 {
 {
-	glLoadIdentity();
+	gl.getTransform().setIdentity();
 	pixel_size_stack.back() = 1;
 	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)
+	, pointStyle(POINT_SMOOTH)
+	, scissor(false)
+	, scissorBox()
+	, font(nullptr)
+	, shader(nullptr)
+	, colorMask{true, true, true, true}
+	, wireframe(false)
+{
+}
+
+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)
+	, pointStyle(other.pointStyle)
+	, 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];
+
+	if (font)
+		font->retain();
+
+	if (shader)
+		shader->retain();
+
+	for (Canvas *c : canvases)
+		c->retain();
+}
+
+Graphics::DisplayState::~DisplayState()
+{
+	for (Canvas *c : canvases)
+		c->release();
+
+	if (shader)
+		shader->release();
+
+	if (font)
+		font->release();
+}
+
+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;
+	pointStyle = other.pointStyle;
+	scissor = other.scissor;
+	scissorBox = other.scissorBox;
+
+	Object::AutoRelease fontrelease(font);
+
+	font = other.font;
+	if (font)
+		font->retain();
+
+	Object::AutoRelease shaderrelease(shader);
+
+	shader = other.shader;
+	if (shader)
+		shader->retain();
+
+	for (Canvas *c : other.canvases)
+		c->retain();
+	for (Canvas *c : canvases)
+		c->release();
+
+	canvases = other.canvases;
+
+	for (int i = 0; i < 4; i++)
+		colorMask[i] = other.colorMask[i];
+
+	wireframe = other.wireframe;
+
+	return *this;
+}
+
 } // opengl
 } // opengl
 } // graphics
 } // graphics
 } // love
 } // love

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

@@ -57,48 +57,7 @@ namespace opengl
 // During display mode changing, certain
 // During display mode changing, certain
 // variables about the OpenGL context are
 // variables about the OpenGL context are
 // lost.
 // lost.
-struct DisplayState
-{
-	// Colors.
-	Color color;
-	Color backgroundColor;
-
-	// Blend mode.
-	Graphics::BlendMode blendMode;
-
-	// Line.
-	Graphics::LineStyle lineStyle;
-	Graphics::LineJoin lineJoin;
-
-	// Point.
-	float pointSize;
-	Graphics::PointStyle pointStyle;
 
 
-	// 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;
-		pointStyle = Graphics::POINT_SMOOTH;
-		scissor = false;
-		colorMask[0] = colorMask[1] = colorMask[2] = colorMask[3] = true;
-		wireframe = false;
-	}
-
-};
 
 
 class Graphics : public love::graphics::Graphics
 class Graphics : public love::graphics::Graphics
 {
 {
@@ -110,10 +69,6 @@ public:
 	// Implements Module.
 	// Implements Module.
 	const char *getName() const;
 	const char *getName() const;
 
 
-	DisplayState saveState();
-
-	void restoreState(const DisplayState &s);
-
 	virtual void setViewportSize(int width, int height);
 	virtual void setViewportSize(int width, int height);
 	virtual bool setMode(int width, int height, bool &sRGB);
 	virtual bool setMode(int width, int height, bool &sRGB);
 	virtual void unSetMode();
 	virtual void unSetMode();
@@ -246,10 +201,21 @@ public:
 	 **/
 	 **/
 	Font *getFont() const;
 	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();
+
+	std::vector<Canvas *> getCanvas() const;
+
 	/**
 	/**
 	 * Sets the enabled color components when rendering.
 	 * 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.
 	 * Gets the current color mask.
@@ -460,8 +426,9 @@ public:
 	 **/
 	 **/
 	bool isSupported(Support feature) const;
 	bool isSupported(Support feature) const;
 
 
-	void push();
+	void push(StackType type = STACK_TRANSFORM);
 	void pop();
 	void pop();
+
 	void rotate(float r);
 	void rotate(float r);
 	void scale(float x, float y = 1.0f);
 	void scale(float x, float y = 1.0f);
 	void translate(float x, float y);
 	void translate(float x, float y);
@@ -470,17 +437,50 @@ public:
 
 
 private:
 private:
 
 
-	Font *currentFont;
+	struct DisplayState
+	{
+		// Colors.
+		Color color;
+		Color backgroundColor;
+
+		// Blend mode.
+		BlendMode blendMode;
+
+		// Line.
+		float lineWidth;
+		LineStyle lineStyle;
+		LineJoin lineJoin;
+
+		// Point.
+		float pointSize;
+		PointStyle pointStyle;
+
+		// Scissor.
+		bool scissor;
+		OpenGL::Viewport scissorBox;
+
+		Font *font;
+		Shader *shader;
+		std::vector<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;
 	love::window::Window *currentWindow;
 
 
 	std::vector<double> pixel_size_stack; // stores current size of a pixel (needed for line drawing)
 	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 width;
 	int height;
 	int height;
@@ -488,7 +488,10 @@ private:
 
 
 	bool activeStencil;
 	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;
 
 
 }; // Graphics
 }; // Graphics
 
 

+ 3 - 6
src/modules/graphics/opengl/Image.cpp

@@ -526,11 +526,10 @@ void Image::uploadDefaultTexture()
 
 
 void Image::drawv(const Matrix &t, const Vertex *v)
 void Image::drawv(const Matrix &t, const Vertex *v)
 {
 {
-	predraw();
-
-	glPushMatrix();
+	OpenGL::TempTransform transform(gl);
+	transform.get() *= t;
 
 
-	glMultMatrixf((const GLfloat *)t.getElements());
+	predraw();
 
 
 	glEnableClientState(GL_VERTEX_ARRAY);
 	glEnableClientState(GL_VERTEX_ARRAY);
 	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
 	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
@@ -544,8 +543,6 @@ void Image::drawv(const Matrix &t, const Vertex *v)
 	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 	glDisableClientState(GL_VERTEX_ARRAY);
 	glDisableClientState(GL_VERTEX_ARRAY);
 
 
-	glPopMatrix();
-
 	postdraw();
 	postdraw();
 }
 }
 
 

+ 2 - 4
src/modules/graphics/opengl/Mesh.cpp

@@ -342,8 +342,8 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 	Matrix m;
 	Matrix m;
 	m.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
 	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);
 	VertexBuffer::Bind vbo_bind(*vbo);
 
 
@@ -412,8 +412,6 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 		gl.setColor(gl.getColor());
 		gl.setColor(gl.getColor());
 	}
 	}
 
 
-	glPopMatrix();
-
 	if (texture)
 	if (texture)
 		texture->postdraw();
 		texture->postdraw();
 }
 }

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

@@ -28,6 +28,7 @@
 
 
 // C++
 // C++
 #include <algorithm>
 #include <algorithm>
+#include <limits>
 
 
 // C
 // C
 #include <cstring>
 #include <cstring>
@@ -47,6 +48,8 @@ OpenGL::OpenGL()
 	, vendor(VENDOR_UNKNOWN)
 	, vendor(VENDOR_UNKNOWN)
 	, state()
 	, state()
 {
 {
+	matrices.transform.reserve(10);
+	matrices.projection.reserve(2);
 }
 }
 
 
 void OpenGL::initContext()
 void OpenGL::initContext()
@@ -56,6 +59,7 @@ void OpenGL::initContext()
 
 
 	initOpenGLFunctions();
 	initOpenGLFunctions();
 	initVendor();
 	initVendor();
+	initMatrices();
 
 
 	// Store the current color so we don't have to get it through GL later.
 	// Store the current color so we don't have to get it through GL later.
 	GLfloat glcolor[4];
 	GLfloat glcolor[4];
@@ -119,6 +123,13 @@ void OpenGL::initContext()
 
 
 	state.lastPseudoInstanceID = -1;
 	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;
 	contextInitialized = true;
 }
 }
 
 
@@ -209,6 +220,15 @@ void OpenGL::initMaxValues()
 		maxRenderTargets = 0;
 		maxRenderTargets = 0;
 }
 }
 
 
+void OpenGL::initMatrices()
+{
+	matrices.transform.clear();
+	matrices.projection.clear();
+
+	matrices.transform.push_back(Matrix());
+	matrices.projection.push_back(Matrix());
+}
+
 void OpenGL::createDefaultTexture()
 void OpenGL::createDefaultTexture()
 {
 {
 	// Set the 'default' texture (id 0) as a repeating white pixel. Otherwise,
 	// Set the 'default' texture (id 0) as a repeating white pixel. Otherwise,
@@ -231,6 +251,21 @@ void OpenGL::createDefaultTexture()
 	bindTexture(curtexture);
 	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()
 void OpenGL::prepareDraw()
 {
 {
 	Shader *shader = Shader::current;
 	Shader *shader = Shader::current;
@@ -251,15 +286,37 @@ void OpenGL::prepareDraw()
 		// We need to make sure antialiased Canvases are properly resolved
 		// We need to make sure antialiased Canvases are properly resolved
 		// before sampling from their textures in a shader.
 		// before sampling from their textures in a shader.
 		// This is kind of a big hack. :(
 		// 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:
 			// Even bigger hack! D:
-			Canvas *canvas = dynamic_cast<Canvas *>(it->second);
+			Canvas *canvas = dynamic_cast<Canvas *>(r.second);
 			if (canvas != nullptr)
 			if (canvas != nullptr)
 				canvas->resolveMSAA();
 				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)
 void OpenGL::drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount)

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

@@ -26,9 +26,11 @@
 // LOVE
 // LOVE
 #include "graphics/Color.h"
 #include "graphics/Color.h"
 #include "graphics/Texture.h"
 #include "graphics/Texture.h"
+#include "common/Matrix.h"
 
 
 // C++
 // C++
 #include <vector>
 #include <vector>
+#include <stack>
 
 
 // The last argument to AttribPointer takes a buffer offset casted to a pointer.
 // The last argument to AttribPointer takes a buffer offset casted to a pointer.
 #define BUFFER_OFFSET(i) ((char *) NULL + (i))
 #define BUFFER_OFFSET(i) ((char *) NULL + (i))
@@ -101,6 +103,36 @@ public:
 		GLenum func;
 		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();
 	OpenGL();
 
 
 	/**
 	/**
@@ -116,6 +148,10 @@ public:
 	 **/
 	 **/
 	void deInitContext();
 	void deInitContext();
 
 
+	void pushTransform();
+	void popTransform();
+	Matrix &getTransform();
+
 	/**
 	/**
 	 * Set up necessary state (LOVE-provided shader uniforms, etc.) for drawing.
 	 * Set up necessary state (LOVE-provided shader uniforms, etc.) for drawing.
 	 * This *MUST* be called directly before OpenGL drawing functions.
 	 * This *MUST* be called directly before OpenGL drawing functions.
@@ -258,6 +294,7 @@ private:
 	void initVendor();
 	void initVendor();
 	void initOpenGLFunctions();
 	void initOpenGLFunctions();
 	void initMaxValues();
 	void initMaxValues();
+	void initMatrices();
 	void createDefaultTexture();
 	void createDefaultTexture();
 
 
 	bool contextInitialized;
 	bool contextInitialized;
@@ -290,6 +327,9 @@ private:
 		// The last ID value used for pseudo-instancing.
 		// The last ID value used for pseudo-instancing.
 		int lastPseudoInstanceID;
 		int lastPseudoInstanceID;
 
 
+		Matrix lastProjectionMatrix;
+		Matrix lastTransformMatrix;
+
 	} state;
 	} state;
 
 
 }; // OpenGL
 }; // OpenGL

+ 3 - 5
src/modules/graphics/opengl/ParticleSystem.cpp

@@ -843,11 +843,11 @@ void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, flo
 
 
 	Color curcolor = gl.getColor();
 	Color curcolor = gl.getColor();
 
 
-	glPushMatrix();
-
 	static Matrix t;
 	static Matrix t;
 	t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
 	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();
 	const Vertex *textureVerts = texture->getVertices();
 	Vertex *pVerts = particleVerts;
 	Vertex *pVerts = particleVerts;
@@ -901,8 +901,6 @@ void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, flo
 
 
 	texture->postdraw();
 	texture->postdraw();
 
 
-	glPopMatrix();
-
 	gl.setColor(curcolor);
 	gl.setColor(curcolor);
 }
 }
 
 

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

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

+ 3 - 6
src/modules/graphics/opengl/SpriteBatch.cpp

@@ -271,11 +271,10 @@ void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float
 		return;
 		return;
 
 
 	static Matrix t;
 	static Matrix t;
-
-	glPushMatrix();
-
 	t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
 	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();
 	texture->predraw();
 
 
@@ -314,8 +313,6 @@ void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float
 	}
 	}
 
 
 	texture->postdraw();
 	texture->postdraw();
-
-	glPopMatrix();
 }
 }
 
 
 void SpriteBatch::addv(const Vertex *v, int index)
 void SpriteBatch::addv(const Vertex *v, int index)

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

@@ -19,6 +19,7 @@
  **/
  **/
 
 
 #include "wrap_Canvas.h"
 #include "wrap_Canvas.h"
+#include "Graphics.h"
 
 
 namespace love
 namespace love
 {
 {
@@ -37,18 +38,26 @@ int w_Canvas_renderTo(lua_State *L)
 	Canvas *canvas = luax_checkcanvas(L, 1);
 	Canvas *canvas = luax_checkcanvas(L, 1);
 	luaL_checktype(L, 2, LUA_TFUNCTION);
 	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;
 	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);
 			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;
 	return 0;
 }
 }
@@ -893,43 +892,35 @@ int w_setCanvas(lua_State *L)
 	instance()->discardStencil();
 	instance()->discardStencil();
 
 
 	// called with none -> reset to default buffer
 	// called with none -> reset to default buffer
-	if (lua_isnoneornil(L,1))
+	if (lua_isnoneornil(L, 1))
 	{
 	{
-		Canvas::bindDefaultCanvas();
+		instance()->setCanvas();
 		return 0;
 		return 0;
 	}
 	}
 
 
 	bool is_table = lua_istable(L, 1);
 	bool is_table = lua_istable(L, 1);
-	std::vector<Canvas *> attachments;
-
-	Canvas *canvas = 0;
+	std::vector<Canvas *> canvases;
 
 
 	if (is_table)
 	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);
 			lua_rawgeti(L, 1, i);
-			attachments.push_back(luax_checkcanvas(L, -1));
+			canvases.push_back(luax_checkcanvas(L, -1));
 			lua_pop(L, 1);
 			lua_pop(L, 1);
 		}
 		}
 	}
 	}
 	else
 	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, [&]() {
 	luax_catchexcept(L, [&]() {
-		if (attachments.size() > 0)
-			canvas->startGrab(attachments);
+		if (canvases.size() > 0)
+			instance()->setCanvas(canvases);
 		else
 		else
-			canvas->startGrab();
+			instance()->setCanvas();
 	});
 	});
 
 
 	return 0;
 	return 0;
@@ -937,24 +928,23 @@ int w_setCanvas(lua_State *L)
 
 
 int w_getCanvas(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++;
 			n++;
 		}
 		}
 	}
 	}
 	else
 	else
+	{
 		lua_pushnil(L);
 		lua_pushnil(L);
+		n = 1;
+	}
 
 
 	return n;
 	return n;
 }
 }
@@ -963,18 +953,18 @@ int w_setShader(lua_State *L)
 {
 {
 	if (lua_isnoneornil(L,1))
 	if (lua_isnoneornil(L,1))
 	{
 	{
-		Shader::detach();
+		instance()->setShader();
 		return 0;
 		return 0;
 	}
 	}
 
 
 	Shader *shader = luax_checkshader(L, 1);
 	Shader *shader = luax_checkshader(L, 1);
-	shader->attach();
+	instance()->setShader(shader);
 	return 0;
 	return 0;
 }
 }
 
 
 int w_getShader(lua_State *L)
 int w_getShader(lua_State *L)
 {
 {
-	Shader *shader = Shader::current;
+	Shader *shader = instance()->getShader();
 	if (shader)
 	if (shader)
 	{
 	{
 		shader->retain();
 		shader->retain();
@@ -1323,7 +1313,12 @@ int w_polygon(lua_State *L)
 
 
 int w_push(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;
 	return 0;
 }
 }