Commits

Alex Szpakowski  committed 8031875

Added custom modelview and projection matrix stacks to replace the deprecated OpenGL matrix functions; added wrappers for s/getting the Viewport and for s/getting the current blend state.

  • Participants
  • Parent commits 8639ff2
  • Branches GLES2-compatibility

Comments (0)

Files changed (12)

File src/common/Matrix.cpp

 
 void Matrix::translate(float x, float y)
 {
-	Matrix t;
+	static Matrix t;
 	t.setTranslation(x, y);
 	this->operator *=(t);
 }
 
 void Matrix::rotate(float rad)
 {
-	Matrix t;
+	static Matrix t;
 	t.setRotation(rad);
 	this->operator *=(t);
 }
 
 void Matrix::scale(float sx, float sy)
 {
-	Matrix t;
+	static Matrix t;
 	t.setScale(sx, sy);
 	this->operator *=(t);
 }
 
 void Matrix::shear(float kx, float ky)
 {
-	Matrix t;
+	static Matrix t;
 	t.setShear(kx,ky);
 	this->operator *=(t);
 }
 	}
 }
 
+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

File src/common/Matrix.h

 	 **/
 	void transform(vertex *dst, const vertex *src, int size) const;
 
+	static Matrix ortho(float left, float right, float bottom, float top);
+
 private:
 
 	/**

File src/libraries/glew/glew.h

 #define __GL2PLATFORM_H_
 #define __khrplatform_h_
 #define __KHRPLATFORM_H_
+
 #define GLEW_NO_GLU
+#define GLEW_STATIC
 
 
 #if defined(GLEW_USE_LIB_ES11) || defined(GLEW_USE_LIB_ES20)

File src/modules/graphics/opengl/Canvas.cpp

 
 		// generate texture save target
 		GLint internalFormat;
-		GLenum format;
+		GLenum type;
 		switch (texture_type)
 		{
 			case Canvas::TYPE_HDR:
 				internalFormat = GL_RGBA16F;
-				format = GL_FLOAT;
+				type = GL_FLOAT;
 				break;
 			case Canvas::TYPE_NORMAL:
 			default:
 				internalFormat = GL_RGBA;
-				format = GL_UNSIGNED_BYTE;
+				type = GL_UNSIGNED_BYTE;
 		}
 
 		glGenTextures(1, &img);
 		ctx->setTextureFilter(Image::getDefaultFilter());
 
 		glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height,
-			0, GL_RGBA, format, NULL);
+			0, GL_RGBA, type, NULL);
 		ctx->bindTexture(0);
 		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
 			GL_TEXTURE_2D, img, 0);
 
 		// generate texture save target
 		GLint internalFormat;
-		GLenum format;
+		GLenum type;
 		switch (texture_type)
 		{
 			case Canvas::TYPE_HDR:
 				internalFormat = GL_RGBA16F;
-				format = GL_FLOAT;
+				type = GL_FLOAT;
 				break;
 			case Canvas::TYPE_NORMAL:
 			default:
 				internalFormat = GL_RGBA;
-				format = GL_UNSIGNED_BYTE;
+				type = GL_UNSIGNED_BYTE;
 		}
 
 		glGenTextures(1, &img);
 		ctx->setTextureFilter(Image::getDefaultFilter());
 
 		glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height,
-			0, GL_RGBA, format, NULL);
+			0, GL_RGBA, type, NULL);
 		ctx->bindTexture(0);
 		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
 			GL_TEXTURE_2D, img, 0);
 
 		// generate texture save target
 		GLint internalFormat;
-		GLenum format;
+		GLenum type;
 		switch (texture_type)
 		{
 			case Canvas::TYPE_HDR:
 				internalFormat = GL_RGBA16F;
-				format = GL_FLOAT;
+				type = GL_FLOAT;
 				break;
 			case Canvas::TYPE_NORMAL:
 			default:
 				internalFormat = GL_RGBA;
-				format = GL_UNSIGNED_BYTE;
+				type = GL_UNSIGNED_BYTE;
 		}
 
 		glGenTextures(1, &img);
 		ctx->setTextureFilter(Image::getDefaultFilter());
 
 		glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height,
-			0, GL_RGBA, format, NULL);
+			0, GL_RGBA, type, NULL);
 		ctx->bindTexture(0);
 		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
 			GL_TEXTURE_2D, img, 0);
 	if (current == this)
 		return;
 
+	Context *ctx = getContext();
+
 	// cleanup after previous fbo
 	if (current != NULL)
 		current->stopGrab();
 
+	// Save the previous framebuffer's viewport so we can restore it later
+	savedViewport = ctx->getViewport();
+
 	// bind buffer and clear screen
-	glPushAttrib(GL_VIEWPORT_BIT | GL_TRANSFORM_BIT);
 	strategy->bindFBO(fbo);
-	glViewport(0, 0, width, height);
+	ctx->setViewport(0, 0, width, height);
 
-	// Reset the projection matrix
-	glMatrixMode(GL_PROJECTION);
-	glPushMatrix();
-	glLoadIdentity();
-
-	// Set up orthographic view (no depth)
-	glOrtho(0.0, width, height, 0.0, -1.0, 1.0);
-
-	// Switch back to modelview matrix
-	glMatrixMode(GL_MODELVIEW);
+	// Set the orthographic projection matrix to this canvas' dimensions
+	Matrix ortho = Matrix::ortho(0.0f, width, height, 0.0f);
+	ctx->projectionStack.push_back(ortho);
 
 	// indicate we are using this fbo
 	current = this;
 	if (current != this)
 		return;
 
+	Context *ctx = getContext();
+
 	// bind default
 	strategy->bindFBO(0);
-	glMatrixMode(GL_PROJECTION);
-	glPopMatrix();
-	glPopAttrib();
+
+	// Restore previous orthographic projection matrix
+	if (ctx->projectionStack.size() > 1)
+		ctx->projectionStack.pop_back();
+
+	// Restore the previous framebuffer's viewport
+	ctx->setViewport(savedViewport);
+	
 	current = NULL;
 }
 
 
 void Canvas::getPixel(unsigned char* pixel_rgba, int x, int y)
 {
-	strategy->bindFBO( fbo );
+	strategy->bindFBO(fbo);
 
 	glReadPixels(x, height - y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel_rgba);
 
 
 void Canvas::drawv(const Matrix &t, const vertex *v) const
 {
-	glPushMatrix();
+	Context *ctx = getContext();
 
-	glMultMatrixf((const GLfloat *)t.getElements());
-
-	Context *ctx = getContext();
+	ctx->modelViewStack.push_back(ctx->modelViewStack.back());
+	ctx->modelViewStack.back() *= t;
 
 	ctx->bindTexture(img);
 
 	ctx->setupRender();
 	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
 
-	glPopMatrix();
+	ctx->modelViewStack.pop_back();
 }
 
 bool Canvas::getConstant(const char *in, Canvas::TextureType &out)

File src/modules/graphics/opengl/Canvas.h

 
 	GLenum status;
 
+	// Viewport used by the previously bound canvas.
+	Context::Viewport savedViewport;
+
 	struct
 	{
 		Image::Filter filter;

File src/modules/graphics/opengl/Context.cpp

 #include "common/config.h"
 #include "common/Exception.h"
 
+#include "Context.h"
 #include "Shader.h"
-#include "Context.h"
 
 #include <algorithm>
 
 
 Context::~Context()
 {
-	deInitState();
+	if (current == this)
+		current = NULL;
 }
 
 void Context::initExtensions()
 {
 	state.enabledCapabilities.clear();
 
+	// ES2 doesn't have these, and they shouldn't ever be disabled anyway
+	if (!GLEW_ES_VERSION_2_0)
+	{
+		setCapability(GL_TEXTURE_2D, true);
+		setCapability(GL_MULTISAMPLE, true);
+	}
+
 	// getCapability starts tracking the state if the capability isn't in the map yet.
+	getCapability(GL_POINT_SMOOTH);
+	
 	getCapability(GL_BLEND);
-	getCapability(GL_TEXTURE_2D);
 	getCapability(GL_SCISSOR_TEST);
 	getCapability(GL_STENCIL_TEST);
 	getCapability(GL_SCISSOR_TEST);
-	getCapability(GL_POINT_SMOOTH);
-	getCapability(GL_MULTISAMPLE);
 }
 
 void Context::initTextureState()
 
 void Context::initState()
 {
+	// Set up matrix stacks with a sane amount of preallocated space
+	modelViewStack.clear();
+	modelViewStack.reserve(64);
+	modelViewStack.push_back(Matrix());
+
+	projectionStack.clear();
+	projectionStack.reserve(16);
+	projectionStack.push_back(Matrix());
+
 	initCapabilityState();
 	initTextureState();
 
 	state.enabledVertexAttribArrays.clear();
 	useVertexAttribArrays(ATTRIB_NONE);
 
+	// get blend state
+	glGetIntegerv(GLEW_ES_VERSION_2_0 ? GL_BLEND_SRC_RGB : GL_BLEND_SRC, (GLint *) &state.blend.source);
+	glGetIntegerv(GLEW_ES_VERSION_2_0 ? GL_BLEND_DST_RGB : GL_BLEND_DST, (GLint *) &state.blend.destination);
+
+	if (GLEW_ES_VERSION_2_0 || GLEW_VERSION_1_4 || GLEW_ARB_imaging || (GLEW_EXT_blend_minmax && GLEW_EXT_blend_subtract))
+		glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint *) &state.blend.function);
+	else
+		state.blend.function = GL_FUNC_ADD;
+	
+
 	// get the current color
 	GLfloat color[4];
 	glGetFloatv(GL_CURRENT_COLOR, color);
 	state.maxPointSize = pointsizerange[1];
 }
 
-void Context::deInitState()
-{
-	// ?
-}
-
 void Context::createDefaultTexture()
 {
 	// Set the 'default' texture (id 0) as a repeating white pixel.
 		// modelview/projection/normal matrices, point size, etc.
 	}
 
-	// TODO: otherwise, load current matrices into OpenGL with glLoadMatrix
+	// Send modelview and projection matrices to OpenGL for use when rendering.
+	// TODO: only send matrices if they change.
+
+	const Matrix &projectionMatrix = projectionStack.back();
+	const Matrix &modelViewMatrix = modelViewStack.back();
+
+	glMatrixMode(GL_PROJECTION);
+	glLoadMatrixf(projectionMatrix.getElements());
+
+	glMatrixMode(GL_MODELVIEW);
+	glLoadMatrixf(modelViewMatrix.getElements());
 }
 
 void Context::setCapability(GLenum capability, bool enable)
 	return isenabled;
 }
 
+void Context::setViewport(const Context::Viewport &v)
+{
+	glViewport(v.x, v.y, v.width, v.height);
+	state.viewport = v;
+}
+
+void Context::setViewport(GLint x, GLint y, GLsizei width, GLsizei height)
+{
+	setViewport(Viewport(x, y, width, height));
+}
+
+const Context::Viewport &Context::getViewport() const
+{
+	return state.viewport;
+}
+
+void Context::setBlendState(const Context::BlendState &s)
+{
+	if (GLEW_ES_VERSION_2_0 || GLEW_VERSION_1_4 || GLEW_ARB_imaging)
+		glBlendEquation(s.function);
+	else if (GLEW_EXT_blend_minmax && GLEW_EXT_blend_subtract)
+		glBlendEquationEXT(s.function);
+	else if (s.function != GL_FUNC_ADD)
+	{
+		if (s.function == GL_FUNC_REVERSE_SUBTRACT)
+			throw love::Exception("This graphics card does not support the subtract blend mode!");
+		else
+			throw love::Exception("This graphics card does not support this blend mode!");
+	}
+
+	glBlendFunc(s.source, s.destination);
+
+	state.blend = s;
+}
+
+void Context::setBlendState(GLenum function, GLenum source, GLenum destination)
+{
+	BlendState b = {function, source, destination};
+	setBlendState(b);
+}
+
+const Context::BlendState &Context::getBlendState() const
+{
+	return state.blend;
+}
+
 bool Context::isGenericVertexAttrib(unsigned int attrib) const
 {
 	return GLEW_ES_VERSION_2_0 || attrib > ATTRIB_COLOR;

File src/modules/graphics/opengl/Context.h

 #define LOVE_GRAPHICS_OPENGL_CONTEXT_H
 
 #include "libraries/glew/glew.h"
+#include "common/Matrix.h"
+
 #include "graphics/Color.h"
 #include "graphics/Image.h"
 
+
 #include <vector>
 #include <map>
 
 
 public:
 
-	// Standard vertex attributes
+	// Standard vertex attributes.
 	enum VertexAttribType
 	{
 		ATTRIB_NONE = 0x100000,
 		ATTRIB_COLOR = 0x04,
 	};
 
+	// Structure for viewport state.
+	struct Viewport
+	{
+		GLint x, y;
+		GLsizei width, height;
+
+		Viewport() : x(0), y(0), width(0), height(0) { };
+		
+		Viewport(GLint x, GLint y, GLsizei width, GLsizei height)
+		: x(x), y(y), width(width), height(height) { };
+	};
+
+	// Structure for blend mode state.
+	struct BlendState
+	{
+		GLenum function;
+		GLenum source;
+		GLenum destination;
+	};
+
+	// Matrix stacks used when rendering.
+	std::vector<Matrix> modelViewStack;
+	std::vector<Matrix> projectionStack;
+
 	Context();
 	~Context();
 
 	void initState();
 
-	void deInitState();
-
 	/**
 	 * Sets state required for rendering.
-	 * Call this method before every draw call!
+	 * Call this method directly before every draw call!
 	 **/
 	void setupRender();
 
 	 **/
 	bool getCapability(GLenum capability);
 
+	/**
+	 * Sets the window viewport for the currently bound framebuffer.
+	 **/
+	void setViewport(const Viewport &v);
+	void setViewport(GLint x, GLint y, GLsizei width, GLsizei height);
+
+	/**
+	 * Returns the last window viewport set with setViewport.
+	 **/
+	const Viewport &getViewport() const;
+
+	/**
+	 * Returns true if the specified vertex attribute doesn't go through the
+	 * fixed-function pipeline.
+	 *
+	 * @param attrib See VertexAttribType.
+	 **/
 	bool isGenericVertexAttrib(unsigned int attrib) const;
 
+	/**
+	 * Returns the OpenGL representation of the specified vertex attribute.
+	 * Will be the attribute index for generic vertex attributes, or a
+	 * GL_*_ARRAY for fixed-function ones.
+	 *
+	 * @param attrib See VertexAttribType.
+	 **/
 	GLint getVertexAttribID(unsigned int attrib) const;
 
 	/**
 	void vertexAttribPointer(VertexAttribType attrib, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer) const;
 
 	/**
-	 * Sets the current constant color (glColor4ub).
+	 * Sets the current constant color (replaces glColor4ub).
 	 **/
 	void setColor(const Color &color);
 
 	float getMaxPointSize() const;
 
 	/**
+	 * Sets the current blend mode.
+	 **/
+	void setBlendState(const BlendState &s);
+	void setBlendState(GLenum function, GLenum source, GLenum destination);
+
+	/**
+	 * Returns the currently set blend mode state.
+	 **/
+	const BlendState &getBlendState() const;
+
+	/**
 	 * Sets the active texture unit.
 	 *
 	 * @param textureunit Index in the range of [0, maxtextureunits-1]
 	void initTextureState();
 	void createDefaultTexture();
 
-	// Currently tracked OpenGL state.
+	// Tracked OpenGL state.
 	struct
 	{
 		// Tracks bound texture for each unit.
 		std::map<unsigned int, GLenum> vertexAttribMap;
 		std::map<unsigned int, bool> enabledVertexAttribArrays;
 
-		// tracks glEnable/glDisable.
+		// Tracks glEnable/glDisable.
 		std::map<GLenum, bool> enabledCapabilities;
 
+		// Current color.
 		Color color;
+
 		float pointSize;
 		float maxPointSize;
 
+		// The last viewport set with setViewport.
+		Viewport viewport;
+
+		BlendState blend;
+
 	} state;
 
 	static bool extensionsInitialized;
  **/
 Context *getContext();
 
+/**
+ * Resets the context state by destroying the current context and creating a new
+ * one. Invalidates old context pointers!
+ **/
 Context *resetContext();
 
 } // opengl

File src/modules/graphics/opengl/Font.cpp

 
 	int quadindex = 0;
 
-	glPushMatrix();
+	ctx->modelViewStack.push_back(ctx->modelViewStack.back());
 
-	Matrix t;
+	static Matrix t;
 	t.setTransformation(ceil(x), ceil(y), angle, sx, sy, ox, oy, kx, ky);
-	glMultMatrixf((const GLfloat *)t.getElements());
+	ctx->modelViewStack.back() *= t;
 
 	try
 	{
 	}
 	catch (love::Exception &)
 	{
-		glPopMatrix();
+		ctx->modelViewStack.pop_back();
 		throw;
 	}
 	catch (utf8::exception &e)
 	{
-		glPopMatrix();
+		ctx->modelViewStack.pop_back();
 		throw love::Exception("%s", e.what());
 	}
 	
 			catch (love::Exception &)
 			{
 				delete newelementbuffer;
-				glPopMatrix();
+				ctx->modelViewStack.pop_back();
 				throw;
 			}
 			if (newelementbuffer)
 		ctx->vertexAttribPointer(Context::ATTRIB_VERTEX, 2, GL_FLOAT, sizeof(vertex), (GLvoid *)&glyphquads[0].vertices[0].x);
 		ctx->vertexAttribPointer(Context::ATTRIB_TEXCOORD, 2, GL_FLOAT, sizeof(vertex), (GLvoid *)&glyphquads[0].vertices[0].s);
 
+		ctx->setupRender();
+
 		// we need to draw a new vertex array for every section of the string that uses a different texture than the previous section
 		std::vector<GlyphArrayDrawInfo>::const_iterator it;
 		for (it = glyphinfolist.begin(); it != glyphinfolist.end(); ++it)
 		{
 			ctx->bindTexture(it->texture);
 
+			size_t numelements = elementBuffer->getIndexCount(it->numQuads);
 			size_t elementoffset = elementBuffer->getIndexCount(it->startQuad) * elementBuffer->getElementSize();
 
-			ctx->setupRender();
-			glDrawElements(GL_TRIANGLES, elementBuffer->getIndexCount(it->numQuads), elementBuffer->getType(), elementBuffer->getPointer(elementoffset));
+			glDrawElements(GL_TRIANGLES, numelements, elementBuffer->getType(), elementBuffer->getPointer(elementoffset));
 		}
 	}
 
-	glPopMatrix();
+	ctx->modelViewStack.pop_back();
 }
 
 int Font::getWidth(const std::string &str)

File src/modules/graphics/opengl/Graphics.cpp

 	, lineStyle(LINE_SMOOTH)
 	, lineWidth(1)
 	, matrixLimit(0)
-	, userMatrices(0)
 {
 	currentWindow = love::window::sdl::Window::getSingleton();
 }
 	ctx->setCapability(GL_BLEND, true);
 
 	// "Normal" blending
-	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+	ctx->setBlendState(GL_FUNC_ADD, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
 	// Enable line/point smoothing.
 	setLineStyle(LINE_SMOOTH);
 	ctx->setActiveTextureUnit(0);
 
 	// Set the viewport to top-left corner
-	glViewport(0, 0, width, height);
+	ctx->setViewport(0, 0, width, height);
 
-	// 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);
+	// Set the projection matrix to an orthographic view with no depth
+	ctx->projectionStack.push_back(Matrix());
+	ctx->projectionStack.back() *= Matrix::ortho(0.0f, width, height, 0.0f);
 
 	// Reset modelview matrix
-	glMatrixMode(GL_MODELVIEW);
-	glLoadIdentity();
+	ctx->modelViewStack.back() = Matrix();
 
 	// Set pixel row alignment
 	glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
 	// Restore the display state.
 	restoreState(tempState);
 
-	// Get the maximum number of matrices
-	// subtract a few to give the engine some room.
-	glGetIntegerv(GL_MAX_MODELVIEW_STACK_DEPTH, &matrixLimit);
-	matrixLimit -= 5;
+	// Arbitrary matrix limit. Any larger is pretty pointless!
+	matrixLimit = 60;
 
 	return success;
 }
 void Graphics::clear()
 {
 	glClear(GL_COLOR_BUFFER_BIT);
-	glLoadIdentity();
+	getContext()->modelViewStack.back() = Matrix();
 }
 
 void Graphics::present()
 
 void Graphics::setBlendMode(Graphics::BlendMode mode)
 {
-	if (GLEW_ES_VERSION_2_0 || GLEW_VERSION_1_4 || GLEW_ARB_imaging)
+	Context::BlendState s;
+	
+	s.function = (mode == BLEND_SUBTRACTIVE) ? GL_FUNC_REVERSE_SUBTRACT : GL_FUNC_ADD;
+
+	switch (mode)
 	{
-		if (mode == BLEND_SUBTRACTIVE)
-			glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
-		else
-			glBlendEquation(GL_FUNC_ADD);
-	}
-	else if (GLEW_EXT_blend_minmax && GLEW_EXT_blend_subtract)
-	{
-		if (mode == BLEND_SUBTRACTIVE)
-			glBlendEquationEXT(GL_FUNC_REVERSE_SUBTRACT_EXT);
-		else
-			glBlendEquationEXT(GL_FUNC_ADD_EXT);
-	}
-	else
-	{
-		if (mode == BLEND_SUBTRACTIVE)
-			throw Exception("This graphics card does not support the subtract blend mode!");
-		// GL_FUNC_ADD is the default even without access to glBlendEquation, so that'll still work.
+	case BLEND_ALPHA:
+		s.source = GL_SRC_ALPHA;
+		s.destination = GL_ONE_MINUS_SRC_ALPHA;
+		break;
+	case BLEND_MULTIPLICATIVE:
+		s.source = GL_DST_COLOR;
+		s.destination = GL_ZERO;
+		break;
+	case BLEND_PREMULTIPLIED:
+		s.source = GL_ONE;
+		s.destination = GL_ONE_MINUS_SRC_ALPHA;
+		break;
+	case BLEND_ADDITIVE:
+	case BLEND_SUBTRACTIVE:
+		s.source = GL_SRC_ALPHA;
+		s.destination = GL_ONE;
+		break;
+	case BLEND_NONE:
+		s.source = GL_ONE;
+		s.destination = GL_ZERO;
+		break;
+	default:
+		return;
 	}
 
-	if (mode == BLEND_ALPHA)
-		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-	else if (mode == BLEND_MULTIPLICATIVE)
-		glBlendFunc(GL_DST_COLOR, GL_ZERO);
-	else if (mode == BLEND_PREMULTIPLIED)
-		glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
-	else if (mode == BLEND_NONE)
-		glBlendFunc(GL_ONE, GL_ZERO);
-	else // mode == BLEND_ADDITIVE || mode == BLEND_SUBTRACTIVE
-		glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+	getContext()->setBlendState(s);
 }
 
 void Graphics::setColorMode(Graphics::ColorMode mode)
 
 Graphics::BlendMode Graphics::getBlendMode()
 {
-	GLint dst, src, equation;
-	glGetIntegerv(GL_BLEND_DST, &dst);
-	glGetIntegerv(GL_BLEND_SRC, &src);
-	glGetIntegerv(GL_BLEND_EQUATION, &equation);
+	Context::BlendState s = getContext()->getBlendState();
 
-	if (equation == GL_FUNC_REVERSE_SUBTRACT)  // && src == GL_SRC_ALPHA && dst == GL_ONE
+	if (s.function == GL_FUNC_REVERSE_SUBTRACT)  // && src == GL_SRC_ALPHA && dst == GL_ONE
 		return BLEND_SUBTRACTIVE;
-	else if (src == GL_SRC_ALPHA && dst == GL_ONE)  // && equation == GL_FUNC_ADD
+	else if (s.source == GL_SRC_ALPHA && s.destination == GL_ONE)  // && equation == GL_FUNC_ADD
 		return BLEND_ADDITIVE;
-	else if (src == GL_SRC_ALPHA && dst == GL_ONE_MINUS_SRC_ALPHA)  // && equation == GL_FUNC_ADD
+	else if (s.source == GL_SRC_ALPHA && s.destination == GL_ONE_MINUS_SRC_ALPHA)  // && equation == GL_FUNC_ADD
 		return BLEND_ALPHA;
-	else if (src == GL_DST_COLOR && dst == GL_ZERO)  // && equation == GL_FUNC_ADD
+	else if (s.source == GL_DST_COLOR && s.destination == GL_ZERO)  // && equation == GL_FUNC_ADD
 		return BLEND_MULTIPLICATIVE;
-	else if (src == GL_ONE && dst == GL_ONE_MINUS_SRC_ALPHA)  // && equation == GL_FUNC_ADD
+	else if (s.source == GL_ONE && s.destination == GL_ONE_MINUS_SRC_ALPHA)  // && equation == GL_FUNC_ADD
 		return BLEND_PREMULTIPLIED;
-	else if (src == GL_ONE && dst == GL_ZERO)
+	else if (s.source == GL_ONE && s.destination == GL_ZERO)
 		return BLEND_NONE;
 
 	return BLEND_MAX_ENUM; // Should never be reached.
 
 void Graphics::push()
 {
-	if (userMatrices == matrixLimit)
+	Context *ctx = getContext();
+
+	if (ctx->modelViewStack.size() == matrixLimit)
 		throw Exception("Maximum stack depth reached. (More pushes than pops?)");
-	glPushMatrix();
-	++userMatrices;
+
+	ctx->modelViewStack.push_back(ctx->modelViewStack.back());
 }
 
 void Graphics::pop()
 {
-	if (userMatrices < 1)
+	Context *ctx = getContext();
+
+	if (ctx->modelViewStack.size() <= 1)
 		throw Exception("Minimum stack depth reached. (More pops than pushes?)");
-	glPopMatrix();
-	--userMatrices;
+
+	ctx->modelViewStack.pop_back();
 }
 
 void Graphics::rotate(float r)
 {
-	glRotatef(LOVE_TODEG(r), 0, 0, 1);
+	getContext()->modelViewStack.back().rotate(r);
 }
 
 void Graphics::scale(float x, float y)
 {
-	glScalef(x, y, 1);
+	getContext()->modelViewStack.back().scale(x, y);
 }
 
 void Graphics::translate(float x, float y)
 {
-	glTranslatef(x, y, 0);
+	getContext()->modelViewStack.back().translate(x, y);
 }
 
 void Graphics::shear(float kx, float ky)
 {
-	Matrix t;
-	t.setShear(kx, ky);
-	glMultMatrixf((const GLfloat *)t.getElements());
+	getContext()->modelViewStack.back().shear(kx, ky);
 }
 
 bool Graphics::hasFocus()

File src/modules/graphics/opengl/Graphics.h

 
 	LineStyle lineStyle;
 	float lineWidth;
-	GLint matrixLimit;
-	GLint userMatrices;
+	size_t matrixLimit;
 
 	int getRenderHeight();
 }; // Graphics

File src/modules/graphics/opengl/Image.cpp

 
 	Context *ctx = getContext();
 
-	glPushMatrix();
-
-	glMultMatrixf((const GLfloat *)t.getElements());
+	ctx->modelViewStack.push_back(ctx->modelViewStack.back());
+	ctx->modelViewStack.back() *= t;
 
 	ctx->useVertexAttribArrays(Context::ATTRIB_VERTEX | Context::ATTRIB_TEXCOORD);
 	
 	ctx->setupRender();
 	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
 
-	glPopMatrix();
+	ctx->modelViewStack.pop_back();
 }
 
 bool Image::hasNpot()

File src/modules/graphics/opengl/SpriteBatch.cpp

 	const int vertex_offset = sizeof(unsigned char) * 4;
 	const int texel_offset = sizeof(unsigned char) * 4 + sizeof(float) * 2;
 
+	Context *ctx = getContext();
+
 	static Matrix t;
+	t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
 
-	glPushMatrix();
-
-	t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
-	glMultMatrixf((const GLfloat *)t.getElements());
+	ctx->modelViewStack.push_back(ctx->modelViewStack.back());
+	ctx->modelViewStack.back() *= t;
 
 	image->bind();
 
 	VertexBuffer::Bind array_bind(*array_buf);
 	VertexBuffer::Bind element_bind(*element_buf->getVertexBuffer());
 
-	Context *ctx = getContext();
-
 	ctx->useVertexAttribArrays(Context::ATTRIB_VERTEX | Context::ATTRIB_TEXCOORD);
 
 	ctx->vertexAttribPointer(Context::ATTRIB_VERTEX, 2, GL_FLOAT, sizeof(vertex), array_buf->getPointer(vertex_offset));
 	if (color)
 		ctx->setColor(ctx->getColor());
 
-	glPopMatrix();
+	ctx->modelViewStack.pop_back();
 }
 
 void SpriteBatch::addv(const vertex *v, int index)