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

+ 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:
 
 	/**

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

@@ -109,6 +109,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 },
@@ -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::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

@@ -112,6 +112,13 @@ public:
 		LIMIT_MAX_ENUM
 	};
 
+	enum StackType
+	{
+		STACK_ALL,
+		STACK_TRANSFORM,
+		STACK_MAX_ENUM
+	};
+
 	struct RendererInfo
 	{
 		std::string name;
@@ -167,6 +174,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[];
@@ -193,6 +203,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

@@ -594,16 +594,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();
 
@@ -620,8 +617,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)
@@ -691,16 +686,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)
@@ -754,11 +741,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;
 }
@@ -773,10 +757,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();
 }
 
@@ -786,9 +766,7 @@ void Canvas::stopGrab(bool switchingToOtherCanvas)
 	if (current != this)
 		return;
 
-	glMatrixMode(GL_PROJECTION);
-	glPopMatrix();
-	glMatrixMode(GL_MODELVIEW);
+	gl.matrices.projection.pop_back();
 
 	if (switchingToOtherCanvas)
 	{
@@ -1123,12 +1101,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 - 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).
 	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 +393,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)

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

@@ -44,19 +44,14 @@ 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()
 {
+	states.reserve(10);
+	states.push_back(DisplayState());
+
 	currentWindow = love::window::sdl::Window::createSingleton();
 
 	int w, h;
@@ -70,8 +65,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();
 }
@@ -81,53 +76,90 @@ 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 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)
 {
 	setColor(s.color);
 	setBackgroundColor(s.backgroundColor);
+
 	setBlendMode(s.blendMode);
-	setLineWidth(lineWidth);
+
+	setLineWidth(s.lineWidth);
 	setLineStyle(s.lineStyle);
 	setLineJoin(s.lineJoin);
+
 	setPointSize(s.pointSize);
 	setPointStyle(s.pointStyle);
+
 	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);
+	setShader(s.shader);
+	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.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)
 {
 	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
 	// (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));
@@ -148,18 +180,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)
@@ -182,7 +207,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);
 
 	// Enable line/point smoothing.
 	setLineStyle(LINE_SMOOTH);
@@ -197,28 +223,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)
 	{
@@ -244,6 +251,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;
 }
 
@@ -252,9 +270,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();
@@ -323,8 +338,6 @@ void Graphics::reset()
 {
 	DisplayState s;
 	discardStencil();
-	Canvas::bindDefaultCanvas();
-	Shader::detach();
 	origin();
 	restoreState(s);
 }
@@ -356,13 +369,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);
 }
 
@@ -375,7 +393,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()
@@ -399,7 +417,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()
@@ -407,7 +425,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;
 }
@@ -566,65 +584,160 @@ 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();
+
+	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
 {
-	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
 {
-	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:
 		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
 		{
@@ -632,65 +745,42 @@ void Graphics::setBlendMode(Graphics::BlendMode mode)
 			// 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
 			// 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;
 	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;
 	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_SUBTRACTIVE:
-		state.func = GL_FUNC_REVERSE_SUBTRACT;
+		blend.func = GL_FUNC_REVERSE_SUBTRACT;
 	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;
 	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_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)
@@ -717,37 +807,38 @@ 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;
 }
 
 void Graphics::setPointStyle(Graphics::PointStyle style)
@@ -756,43 +847,44 @@ void Graphics::setPointStyle(Graphics::PointStyle style)
 		glEnable(GL_POINT_SMOOTH);
 	else // love::POINT_ROUGH
 		glDisable(GL_POINT_SMOOTH);
+
+	states.back().pointStyle = style;
 }
 
 float Graphics::getPointSize() const
 {
-	GLfloat size;
-	glGetFloatv(GL_POINT_SIZE, &size);
-	return (float)size;
+	return states.back().pointSize;
 }
 
 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)
 {
-	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 != 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 == nullptr)
 		return;
 
 	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
 	// 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();
 }
 
 /**
@@ -874,22 +956,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();
 	}
 }
@@ -996,9 +1080,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();
@@ -1019,8 +1102,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.");
 	}
 
@@ -1052,14 +1134,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;
 }
@@ -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());
+
+	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 == 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)
 {
-	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)
+	, 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
 } // graphics
 } // love

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

@@ -57,48 +57,7 @@ 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;
-	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
 {
@@ -110,10 +69,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();
@@ -246,10 +201,21 @@ 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();
+
+	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.
@@ -460,8 +426,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);
@@ -470,17 +437,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;
+		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;
 
 	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;
@@ -488,7 +488,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;
 
 }; // 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)
 {
-	predraw();
-
-	glPushMatrix();
+	OpenGL::TempTransform transform(gl);
+	transform.get() *= t;
 
-	glMultMatrixf((const GLfloat *)t.getElements());
+	predraw();
 
 	glEnableClientState(GL_VERTEX_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_VERTEX_ARRAY);
 
-	glPopMatrix();
-
 	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;
 	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,8 +412,6 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 		gl.setColor(gl.getColor());
 	}
 
-	glPopMatrix();
-
 	if (texture)
 		texture->postdraw();
 }

+ 60 - 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();
 
 	// Store the current color so we don't have to get it through GL later.
 	GLfloat glcolor[4];
@@ -119,6 +123,13 @@ void OpenGL::initContext()
 
 	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;
 }
 
@@ -209,6 +220,15 @@ void OpenGL::initMaxValues()
 		maxRenderTargets = 0;
 }
 
+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,
@@ -231,6 +251,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;
@@ -251,15 +286,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();
 
 	/**
@@ -116,6 +148,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.
@@ -258,6 +294,7 @@ private:
 	void initVendor();
 	void initOpenGLFunctions();
 	void initMaxValues();
+	void initMatrices();
 	void createDefaultTexture();
 
 	bool contextInitialized;
@@ -290,6 +327,9 @@ private:
 		// The last ID value used for pseudo-instancing.
 		int lastPseudoInstanceID;
 
+		Matrix lastProjectionMatrix;
+		Matrix lastTransformMatrix;
+
 	} state;
 
 }; // 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();
 
-	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 +901,6 @@ void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, flo
 
 	texture->postdraw();
 
-	glPopMatrix();
-
 	gl.setColor(curcolor);
 }
 

+ 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)

+ 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;
 
 	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 +313,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)

+ 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;
 }
@@ -893,43 +892,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;
@@ -937,24 +928,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;
 }
@@ -963,18 +953,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();
@@ -1323,7 +1313,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;
 }