Browse Source

Implement automatic batching for points, lines, shapes, and images/quads.

The above are batched together into a single draw call when called without other drawing calls in between, as long as the following criteria are met:

- The texture is the same.
- The primitive type is the same (points cannot be batched with non-points).
- The love.graphics state is the same (aside from the current color - i.e. setColor - and the transform state).
- The active shader’s uniform values are the same.

You can examine the ‘drawcalls’ field of love.graphics.getStats() to help determine if your code is allowing for optimum batching.

--HG--
branch : minor
Alex Szpakowski 8 years ago
parent
commit
0d2e08baff
43 changed files with 1246 additions and 512 deletions
  1. 2 0
      CMakeLists.txt
  2. 19 3
      platform/xcode/liblove.xcodeproj/project.pbxproj
  3. 1 1
      src/common/Matrix.cpp
  4. 8 1
      src/common/Vector.h
  5. 0 7
      src/common/math.h
  6. 35 0
      src/modules/graphics/Color.h
  7. 3 1
      src/modules/graphics/Drawable.h
  8. 153 7
      src/modules/graphics/Graphics.cpp
  9. 88 0
      src/modules/graphics/Graphics.h
  10. 1 0
      src/modules/graphics/Quad.h
  11. 80 0
      src/modules/graphics/StreamBuffer.cpp
  12. 77 0
      src/modules/graphics/StreamBuffer.h
  13. 33 0
      src/modules/graphics/Texture.cpp
  14. 11 2
      src/modules/graphics/Texture.h
  15. 7 30
      src/modules/graphics/opengl/Canvas.cpp
  16. 2 6
      src/modules/graphics/opengl/Canvas.h
  17. 10 9
      src/modules/graphics/opengl/Font.cpp
  18. 3 15
      src/modules/graphics/opengl/GLBuffer.cpp
  19. 235 54
      src/modules/graphics/opengl/Graphics.cpp
  20. 16 14
      src/modules/graphics/opengl/Graphics.h
  21. 3 32
      src/modules/graphics/opengl/Image.cpp
  22. 1 13
      src/modules/graphics/opengl/Image.h
  23. 7 8
      src/modules/graphics/opengl/Mesh.cpp
  24. 1 1
      src/modules/graphics/opengl/Mesh.h
  25. 14 30
      src/modules/graphics/opengl/OpenGL.cpp
  26. 2 42
      src/modules/graphics/opengl/OpenGL.h
  27. 13 13
      src/modules/graphics/opengl/ParticleSystem.cpp
  28. 1 1
      src/modules/graphics/opengl/ParticleSystem.h
  29. 57 102
      src/modules/graphics/opengl/Polyline.cpp
  30. 26 15
      src/modules/graphics/opengl/Polyline.h
  31. 21 4
      src/modules/graphics/opengl/Shader.cpp
  32. 2 0
      src/modules/graphics/opengl/Shader.h
  33. 12 18
      src/modules/graphics/opengl/SpriteBatch.cpp
  34. 1 10
      src/modules/graphics/opengl/SpriteBatch.h
  35. 11 11
      src/modules/graphics/opengl/Text.cpp
  36. 1 1
      src/modules/graphics/opengl/Text.h
  37. 6 4
      src/modules/graphics/opengl/Video.cpp
  38. 1 1
      src/modules/graphics/opengl/Video.h
  39. 31 31
      src/modules/graphics/opengl/wrap_Graphics.cpp
  40. 134 0
      src/modules/graphics/vertex.cpp
  41. 105 0
      src/modules/graphics/vertex.h
  42. 3 6
      src/scripts/nogame.lua
  43. 9 19
      src/scripts/nogame.lua.h

+ 2 - 0
CMakeLists.txt

@@ -474,6 +474,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT
 	src/modules/graphics/ParticleSystem.h
 	src/modules/graphics/Quad.cpp
 	src/modules/graphics/Quad.h
+	src/modules/graphics/StreamBuffer.cpp
+	src/modules/graphics/StreamBuffer.h
 	src/modules/graphics/Texture.cpp
 	src/modules/graphics/Texture.h
 	src/modules/graphics/Volatile.cpp

+ 19 - 3
platform/xcode/liblove.xcodeproj/project.pbxproj

@@ -876,6 +876,10 @@
 		FA27B3C61B4985D8008A9DCE /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA27B3C31B4985D8008A9DCE /* Video.cpp */; };
 		FA27B3C71B4985D8008A9DCE /* Video.h in Headers */ = {isa = PBXBuildFile; fileRef = FA27B3C41B4985D8008A9DCE /* Video.h */; };
 		FA27B3C91B498623008A9DCE /* Theora.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA27B3C81B498623008A9DCE /* Theora.framework */; };
+		FA29C0051E12355B00268CD8 /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA29C0041E12355B00268CD8 /* StreamBuffer.cpp */; };
+		FA29C0061E12355B00268CD8 /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA29C0041E12355B00268CD8 /* StreamBuffer.cpp */; };
+		FA2AF6741DAD64970032B62C /* vertex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA2AF6731DAD64970032B62C /* vertex.cpp */; };
+		FA2AF6751DAD64970032B62C /* vertex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA2AF6731DAD64970032B62C /* vertex.cpp */; };
 		FA317EBA18F28B6D00B0BCD7 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FA317EB918F28B6D00B0BCD7 /* libz.dylib */; };
 		FA41A3C81C0A1F950084430C /* ASTCHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA41A3C61C0A1F950084430C /* ASTCHandler.cpp */; };
 		FA41A3C91C0A1F950084430C /* ASTCHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA41A3C61C0A1F950084430C /* ASTCHandler.cpp */; };
@@ -1311,13 +1315,13 @@
 		FA0B7B861A95902C000E1D17 /* wrap_Rasterizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Rasterizer.h; sourceTree = "<group>"; };
 		FA0B7B881A95902C000E1D17 /* Color.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Color.h; sourceTree = "<group>"; };
 		FA0B7B891A95902C000E1D17 /* Drawable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Drawable.h; sourceTree = "<group>"; };
-		FA0B7B8A1A95902C000E1D17 /* Graphics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Graphics.cpp; sourceTree = "<group>"; };
+		FA0B7B8A1A95902C000E1D17 /* Graphics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = Graphics.cpp; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.cpp; };
 		FA0B7B8B1A95902C000E1D17 /* Graphics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Graphics.h; sourceTree = "<group>"; };
 		FA0B7B8D1A95902C000E1D17 /* Canvas.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Canvas.cpp; sourceTree = "<group>"; };
 		FA0B7B8E1A95902C000E1D17 /* Canvas.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Canvas.h; sourceTree = "<group>"; };
 		FA0B7B8F1A95902C000E1D17 /* Font.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Font.cpp; sourceTree = "<group>"; };
 		FA0B7B901A95902C000E1D17 /* Font.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Font.h; sourceTree = "<group>"; };
-		FA0B7B911A95902C000E1D17 /* Graphics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Graphics.cpp; sourceTree = "<group>"; };
+		FA0B7B911A95902C000E1D17 /* Graphics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = Graphics.cpp; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.cpp; };
 		FA0B7B921A95902C000E1D17 /* Graphics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Graphics.h; sourceTree = "<group>"; };
 		FA0B7B931A95902C000E1D17 /* Image.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Image.cpp; sourceTree = "<group>"; };
 		FA0B7B941A95902C000E1D17 /* Image.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Image.h; sourceTree = "<group>"; };
@@ -1357,7 +1361,7 @@
 		FA0B7BB91A95902C000E1D17 /* wrap_Text.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Text.h; sourceTree = "<group>"; };
 		FA0B7BBC1A95902C000E1D17 /* Quad.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Quad.cpp; sourceTree = "<group>"; };
 		FA0B7BBD1A95902C000E1D17 /* Quad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Quad.h; sourceTree = "<group>"; };
-		FA0B7BBE1A95902C000E1D17 /* Texture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Texture.cpp; sourceTree = "<group>"; };
+		FA0B7BBE1A95902C000E1D17 /* Texture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = Texture.cpp; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.cpp; };
 		FA0B7BBF1A95902C000E1D17 /* Texture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Texture.h; sourceTree = "<group>"; };
 		FA0B7BC01A95902C000E1D17 /* Volatile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Volatile.cpp; sourceTree = "<group>"; };
 		FA0B7BC11A95902C000E1D17 /* Volatile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Volatile.h; sourceTree = "<group>"; };
@@ -1626,6 +1630,10 @@
 		FA27B3C81B498623008A9DCE /* Theora.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Theora.framework; path = /Library/Frameworks/Theora.framework; sourceTree = "<absolute>"; };
 		FA283EDC1B27CFAA00C70067 /* nogame.lua */ = {isa = PBXFileReference; lastKnownFileType = text; path = nogame.lua; sourceTree = "<group>"; };
 		FA283EDD1B27CFAA00C70067 /* nogame.lua.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = nogame.lua.h; sourceTree = "<group>"; };
+		FA29C0041E12355B00268CD8 /* StreamBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StreamBuffer.cpp; sourceTree = "<group>"; };
+		FA2AF6711DAC76FF0032B62C /* vertex.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = vertex.h; sourceTree = "<group>"; };
+		FA2AF6721DAD62710032B62C /* StreamBuffer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StreamBuffer.h; sourceTree = "<group>"; };
+		FA2AF6731DAD64970032B62C /* vertex.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = vertex.cpp; sourceTree = "<group>"; };
 		FA2E9BFE1C19E00C0004A1EE /* wrap_RandomGenerator.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = wrap_RandomGenerator.lua; sourceTree = "<group>"; };
 		FA317EB918F28B6D00B0BCD7 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
 		FA41A3C61C0A1F950084430C /* ASTCHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ASTCHandler.cpp; sourceTree = "<group>"; };
@@ -2431,8 +2439,12 @@
 				FAE272511C05A15B00A67640 /* ParticleSystem.h */,
 				FA0B7BBC1A95902C000E1D17 /* Quad.cpp */,
 				FA0B7BBD1A95902C000E1D17 /* Quad.h */,
+				FA29C0041E12355B00268CD8 /* StreamBuffer.cpp */,
+				FA2AF6721DAD62710032B62C /* StreamBuffer.h */,
 				FA0B7BBE1A95902C000E1D17 /* Texture.cpp */,
 				FA0B7BBF1A95902C000E1D17 /* Texture.h */,
+				FA2AF6731DAD64970032B62C /* vertex.cpp */,
+				FA2AF6711DAC76FF0032B62C /* vertex.h */,
 				FA0B7BC01A95902C000E1D17 /* Volatile.cpp */,
 				FA0B7BC11A95902C000E1D17 /* Volatile.h */,
 				FA620A2E1AA2F8DB005DB4C2 /* wrap_Quad.cpp */,
@@ -3675,6 +3687,7 @@
 				FA0B7DAC1A95902C000E1D17 /* STBHandler.cpp in Sources */,
 				FA0B79301A958E3B000E1D17 /* Module.cpp in Sources */,
 				FA0B7DDA1A95902C000E1D17 /* RandomGenerator.cpp in Sources */,
+				FA2AF6751DAD64970032B62C /* vertex.cpp in Sources */,
 				FA0B7D801A95902C000E1D17 /* Volatile.cpp in Sources */,
 				FA0B792D1A958E3B000E1D17 /* Memoizer.cpp in Sources */,
 				FA0B7EBC1A95902C000E1D17 /* LuaThread.cpp in Sources */,
@@ -3778,6 +3791,7 @@
 				FAB17BF61ABFC4B100F9BA27 /* lz4hc.c in Sources */,
 				FA0B7EA71A95902C000E1D17 /* wrap_Decoder.cpp in Sources */,
 				FA0B7E1C1A95902C000E1D17 /* MouseJoint.cpp in Sources */,
+				FA29C0061E12355B00268CD8 /* StreamBuffer.cpp in Sources */,
 				FA0B7CF51A95902C000E1D17 /* File.cpp in Sources */,
 				FA0B7E341A95902C000E1D17 /* WeldJoint.cpp in Sources */,
 				FA4F2C091DE936E200CA37D7 /* luasocket.c in Sources */,
@@ -3989,6 +4003,7 @@
 				FA0B7AB81A958EA3000E1D17 /* enet.cpp in Sources */,
 				FA0B7DD91A95902C000E1D17 /* RandomGenerator.cpp in Sources */,
 				FA0B7A9B1A958EA3000E1D17 /* b2MouseJoint.cpp in Sources */,
+				FA2AF6741DAD64970032B62C /* vertex.cpp in Sources */,
 				FA0B7D7F1A95902C000E1D17 /* Volatile.cpp in Sources */,
 				FA0B7A3B1A958EA3000E1D17 /* b2TimeOfImpact.cpp in Sources */,
 				217DFBED1D9F6D490055D849 /* luasocket.c in Sources */,
@@ -4092,6 +4107,7 @@
 				FA0B7D5B1A95902C000E1D17 /* wrap_Font.cpp in Sources */,
 				FA0B7D301A95902C000E1D17 /* Graphics.cpp in Sources */,
 				FA0B7E9D1A95902C000E1D17 /* WaveDecoder.cpp in Sources */,
+				FA29C0051E12355B00268CD8 /* StreamBuffer.cpp in Sources */,
 				FA0B7EB21A95902C000E1D17 /* System.cpp in Sources */,
 				FA0B7D1B1A95902C000E1D17 /* GlyphData.cpp in Sources */,
 				FA0B7AD11A958EA3000E1D17 /* protocol.c in Sources */,

+ 1 - 1
src/common/Matrix.cpp

@@ -97,7 +97,7 @@ Matrix4::~Matrix4()
 Matrix4 Matrix4::operator * (const Matrix4 &m) const
 {
 	Matrix4 t;
-	multiply(*this, m, t);
+	multiply(*this, m, t.e);
 	return t;
 }
 

+ 8 - 1
src/common/Vector.h

@@ -86,6 +86,8 @@ public:
 	 **/
 	Vector getNormal(float scale) const;
 
+	float cross(const Vector &v) const;
+
 	/**
 	 * Adds a Vector to this Vector.
 	 * @param v The Vector we want to add to this Vector.
@@ -200,6 +202,11 @@ inline Vector Vector::getNormal(float scale) const
 	return Vector(-y * scale, x * scale);
 }
 
+inline float Vector::cross(const Vector &v) const
+{
+	return x * v.getY() - y * v.getX();
+}
+
 inline float Vector::normalize(float length)
 {
 
@@ -283,7 +290,7 @@ inline float Vector::operator * (const Vector &v) const
 
 inline float Vector::operator ^ (const Vector &v) const
 {
-	return x * v.getY() - y * v.getX();
+	return cross(v);
 }
 
 inline bool Vector::operator == (const Vector &v) const

+ 0 - 7
src/common/math.h

@@ -72,13 +72,6 @@ struct Rect
 	}
 };
 
-struct Vertex
-{
-	float x, y;
-	float s, t;
-	unsigned char r, g, b, a;
-};
-
 inline int nextP2(int x)
 {
 	x += (x == 0);

+ 35 - 0
src/modules/graphics/Color.h

@@ -48,6 +48,7 @@ struct ColorT
 	bool operator!=(const ColorT<T> &other) const;
 
 	ColorT<T> operator+=(const ColorT<T> &other);
+	ColorT<T> operator*=(const ColorT<T> &other);
 	ColorT<T> operator*=(T s);
 	ColorT<T> operator/=(T s);
 };
@@ -74,6 +75,16 @@ ColorT<T> ColorT<T>::operator+=(const ColorT<T> &other)
 	return *this;
 }
 
+template <typename T>
+ColorT<T> ColorT<T>::operator*=(const ColorT<T> &other)
+{
+	r *= other.r;
+	g *= other.g;
+	b *= other.b;
+	a *= other.a;
+	return *this;
+}
+
 template <typename T>
 ColorT<T> ColorT<T>::operator*=(T s)
 {
@@ -101,6 +112,17 @@ ColorT<T> operator+(const ColorT<T> &a, const ColorT<T> &b)
 	return tmp += b;
 }
 
+template <typename T>
+ColorT<T> operator*(const ColorT<T> &a, const ColorT<T> &b)
+{
+	ColorT<T> res;
+	res.r = a.r * b.r;
+	res.g = a.g * b.g;
+	res.b = a.b * b.b;
+	res.a = a.a * b.a;
+	return res;
+}
+
 template <typename T>
 ColorT<T> operator*(const ColorT<T> &a, T s)
 {
@@ -118,6 +140,19 @@ ColorT<T> operator/(const ColorT<T> &a, T s)
 typedef ColorT<unsigned char> Color;
 typedef ColorT<float> Colorf;
 
+inline Color toColor(Colorf cf)
+{
+	return Color((unsigned char) (cf.r * 255.0f),
+	             (unsigned char) (cf.g * 255.0f),
+	             (unsigned char) (cf.b * 255.0f),
+	             (unsigned char) (cf.a * 255.0f));
+}
+
+inline Colorf toColorf(Color c)
+{
+	return Colorf(c.r / 255.0f, c.g / 255.0f, c.b / 255.0f, c.a / 255.0f);
+}
+
 } // graphics
 } // love
 

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

@@ -30,6 +30,8 @@ namespace love
 namespace graphics
 {
 
+class Graphics;
+
 /**
  * A Drawable is anything that can be drawn on screen with a
  * position, scale and orientation.
@@ -48,7 +50,7 @@ public:
 	/**
 	 * Draws the object with the specified transformation matrix.
 	 **/
-	virtual void draw(const Matrix4 &m) = 0;
+	virtual void draw(Graphics *gfx, const Matrix4 &m) = 0;
 };
 
 } // graphics

+ 153 - 7
src/modules/graphics/Graphics.cpp

@@ -60,8 +60,154 @@ void unGammaCorrectColor(Colorf &c)
 
 love::Type Graphics::type("graphics", &Module::type);
 
+Graphics::Graphics()
+	: streamBufferState()
+	, projectionMatrix()
+{
+	transformStack.reserve(16);
+	transformStack.push_back(Matrix4());
+}
+
 Graphics::~Graphics()
 {
+	delete streamBufferState.vb[0];
+	delete streamBufferState.vb[1];
+	delete streamBufferState.indexBuffer;
+}
+
+Graphics::StreamVertexData Graphics::requestStreamDraw(const StreamDrawRequest &req)
+{
+	using namespace vertex;
+
+	StreamBufferState &state = streamBufferState;
+
+	bool shouldflush = false;
+	bool shouldresize = false;
+
+	if (req.primitiveMode != state.primitiveMode
+		|| req.formats[0] != state.formats[0] || req.formats[1] != state.formats[1]
+		|| ((req.indexMode != TriangleIndexMode::NONE) != (state.indexCount > 0))
+		|| req.texture != state.texture)
+	{
+		shouldflush = true;
+	}
+
+	int totalvertices = state.vertexCount + req.vertexCount;
+
+	if (totalvertices > LOVE_UINT16_MAX && req.indexMode != TriangleIndexMode::NONE)
+		shouldflush = true;
+
+	int reqIndexCount = getIndexCount(req.indexMode, req.vertexCount);
+	size_t reqIndexSize = reqIndexCount * sizeof(uint16);
+
+	size_t newdatasizes[2] = {0, 0};
+	size_t buffersizes[3] = {0, 0, 0};
+
+	for (int i = 0; i < 2; i++)
+	{
+		if (req.formats[i] != CommonFormat::NONE)
+		{
+			size_t stride = getFormatStride(req.formats[i]);
+			size_t datasize = stride * totalvertices;
+
+			size_t cursize = state.vb[i]->getSize();
+
+			if (datasize > cursize)
+			{
+				shouldflush = true;
+
+				if (stride * req.vertexCount > cursize)
+				{
+					buffersizes[i] = std::max(datasize, cursize * 2);
+					shouldresize = true;
+				}
+			}
+
+			newdatasizes[i] = stride * req.vertexCount;
+		}
+	}
+
+	{
+		size_t datasize = (state.indexCount + reqIndexCount) * sizeof(uint16);
+		size_t cursize = state.indexBuffer->getSize();
+
+		if (datasize > cursize)
+		{
+			shouldflush = true;
+
+			if (reqIndexSize > cursize)
+			{
+				buffersizes[2] = std::max(datasize, cursize * 2);
+				shouldresize = true;
+			}
+		}
+	}
+
+	if (shouldflush)
+	{
+		flushStreamDraws();
+
+		state.primitiveMode = req.primitiveMode;
+		state.formats[0] = req.formats[0];
+		state.formats[1] = req.formats[1];
+		state.texture = req.texture;
+	}
+
+	if (shouldresize)
+	{
+		for (int i = 0; i < 2; i++)
+		{
+			if (state.vb[i]->getSize() < buffersizes[i])
+				state.vb[i]->setSize(buffersizes[i]);
+		}
+
+		if (state.indexBuffer->getSize() < buffersizes[2])
+			state.indexBuffer->setSize(buffersizes[2]);
+	}
+
+	if (req.indexMode != TriangleIndexMode::NONE)
+	{
+		uint16 *indices = (uint16 *) state.indexBuffer->getOffsetData();
+		fillIndices(req.indexMode, state.vertexCount, req.vertexCount, indices);
+		state.indexBuffer->incrementOffset(reqIndexSize);
+	}
+
+	StreamVertexData d;
+	d.stream[0] = state.vb[0]->getOffsetData();
+	d.stream[1] = state.vb[1]->getOffsetData();
+
+	state.vertexCount += req.vertexCount;
+	state.indexCount  += reqIndexCount;
+
+	state.vb[0]->incrementOffset(newdatasizes[0]);
+	state.vb[1]->incrementOffset(newdatasizes[1]);
+
+	return d;
+}
+
+const Matrix4 &Graphics::getTransform() const
+{
+	return transformStack.back();
+}
+
+const Matrix4 &Graphics::getProjection() const
+{
+	return projectionMatrix;
+}
+
+void Graphics::pushTransform()
+{
+	transformStack.push_back(transformStack.back());
+}
+
+void Graphics::pushIdentityTransform()
+{
+	transformStack.push_back(Matrix4());
+}
+
+void Graphics::popTransform()
+{
+	transformStack.pop_back();
 }
 
 bool Graphics::getConstant(const char *in, DrawMode &out)
@@ -232,12 +378,12 @@ StringMap<Graphics::LineJoin, Graphics::LINE_JOIN_MAX_ENUM> Graphics::lineJoins(
 
 StringMap<Graphics::StencilAction, Graphics::STENCIL_MAX_ENUM>::Entry Graphics::stencilActionEntries[] =
 {
-	{ "replace", STENCIL_REPLACE },
-	{ "increment", STENCIL_INCREMENT },
-	{ "decrement", STENCIL_DECREMENT },
+	{ "replace",       STENCIL_REPLACE        },
+	{ "increment",     STENCIL_INCREMENT      },
+	{ "decrement",     STENCIL_DECREMENT      },
 	{ "incrementwrap", STENCIL_INCREMENT_WRAP },
 	{ "decrementwrap", STENCIL_DECREMENT_WRAP },
-	{ "invert", STENCIL_INVERT },
+	{ "invert",        STENCIL_INVERT         },
 };
 
 StringMap<Graphics::StencilAction, Graphics::STENCIL_MAX_ENUM> Graphics::stencilActions(Graphics::stencilActionEntries, sizeof(Graphics::stencilActionEntries));
@@ -258,8 +404,8 @@ StringMap<Graphics::CompareMode, Graphics::COMPARE_MAX_ENUM> Graphics::compareMo
 StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM>::Entry Graphics::featureEntries[] =
 {
 	{ "multicanvasformats", FEATURE_MULTI_CANVAS_FORMATS },
-	{ "clampzero", FEATURE_CLAMP_ZERO },
-	{ "lighten", FEATURE_LIGHTEN },
+	{ "clampzero",          FEATURE_CLAMP_ZERO           },
+	{ "lighten",            FEATURE_LIGHTEN              },
 	{ "fullnpot", FEATURE_FULL_NPOT },
 	{ "pixelshaderhighp", FEATURE_PIXEL_SHADER_HIGHP },
 };
@@ -279,7 +425,7 @@ StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM> Graphics::systemLimit
 
 StringMap<Graphics::StackType, Graphics::STACK_MAX_ENUM>::Entry Graphics::stackTypeEntries[] =
 {
-	{ "all", STACK_ALL },
+	{ "all",       STACK_ALL       },
 	{ "transform", STACK_TRANSFORM },
 };
 

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

@@ -24,10 +24,14 @@
 // LOVE
 #include "common/Module.h"
 #include "common/StringMap.h"
+#include "StreamBuffer.h"
+#include "vertex.h"
 #include "Color.h"
+#include "Texture.h"
 
 // C++
 #include <string>
+#include <vector>
 
 namespace love
 {
@@ -222,6 +226,47 @@ public:
 		bool enabled;
 	};
 
+	struct StreamDrawRequest
+	{
+		vertex::PrimitiveMode primitiveMode = vertex::PrimitiveMode::TRIANGLES;
+		vertex::CommonFormat formats[2] = {vertex::CommonFormat::NONE, vertex::CommonFormat::NONE};
+		vertex::TriangleIndexMode indexMode = vertex::TriangleIndexMode::NONE;
+		int vertexCount = 0;
+		Texture *texture = nullptr;
+	};
+
+	struct StreamVertexData
+	{
+		void *stream[2];
+	};
+
+	class TempTransform
+	{
+	public:
+
+		TempTransform(Graphics *gfx)
+			: gfx(gfx)
+		{
+			gfx->pushTransform();
+		}
+
+		TempTransform(Graphics *gfx, const Matrix4 &t)
+			: gfx(gfx)
+		{
+			gfx->pushTransform();
+			gfx->transformStack.back() *= t;
+		}
+
+		~TempTransform()
+		{
+			gfx->popTransform();
+		}
+
+	private:
+		Graphics *gfx;
+	};
+
+	Graphics();
 	virtual ~Graphics();
 
 	// Implements Module.
@@ -261,6 +306,25 @@ public:
 
 	virtual bool isCanvasActive() const = 0;
 
+	virtual void flushStreamDraws() = 0;
+	StreamVertexData requestStreamDraw(const StreamDrawRequest &request);
+
+	virtual Colorf getColor() const = 0;
+
+	const Matrix4 &getTransform() const;
+	const Matrix4 &getProjection() const;
+
+	template <typename T>
+	T *getScratchBuffer(size_t count)
+	{
+		size_t bytes = sizeof(T) * count;
+
+		if (scratchBuffer.size() < bytes)
+			scratchBuffer.resize(bytes);
+
+		return (T *) scratchBuffer.data();
+	}
+
 	static bool getConstant(const char *in, DrawMode &out);
 	static bool getConstant(DrawMode in, const char *&out);
 
@@ -294,8 +358,32 @@ public:
 	static bool getConstant(const char *in, StackType &out);
 	static bool getConstant(StackType in, const char *&out);
 
+protected:
+
+	struct StreamBufferState
+	{
+		StreamBuffer *vb[2];
+		StreamBuffer *indexBuffer;
+		vertex::PrimitiveMode primitiveMode;
+		vertex::CommonFormat formats[2];
+		StrongRef<Texture> texture;
+		int vertexCount;
+		int indexCount;
+	};
+
+	void pushTransform();
+	void pushIdentityTransform();
+	void popTransform();
+
+	StreamBufferState streamBufferState;
+
+	std::vector<Matrix4> transformStack;
+	Matrix4 projectionMatrix;
+
 private:
 
+	std::vector<uint8> scratchBuffer;
+
 	static StringMap<DrawMode, DRAW_MAX_ENUM>::Entry drawModeEntries[];
 	static StringMap<DrawMode, DRAW_MAX_ENUM> drawModes;
 

+ 1 - 0
src/modules/graphics/Quad.h

@@ -24,6 +24,7 @@
 // LOVE
 #include "common/Object.h"
 #include "common/math.h"
+#include "vertex.h"
 
 namespace love
 {

+ 80 - 0
src/modules/graphics/StreamBuffer.cpp

@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2006-2016 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#include "StreamBuffer.h"
+#include "common/Exception.h"
+
+namespace love
+{
+namespace graphics
+{
+
+StreamBuffer::StreamBuffer(Mode mode, size_t size)
+	: data(nullptr)
+	, offset(0)
+	, totalSize(size)
+	, mode(mode)
+{
+	setSize(size);
+}
+
+StreamBuffer::~StreamBuffer()
+{
+		delete[] data;
+}
+
+void *StreamBuffer::getData() const
+{
+	return data;
+}
+
+void *StreamBuffer::getOffsetData() const
+{
+	return data + offset;
+}
+
+void StreamBuffer::incrementOffset(size_t amount)
+{
+	offset += amount;
+}
+
+void StreamBuffer::resetOffset()
+{
+	offset = 0;
+}
+
+void StreamBuffer::setSize(size_t size)
+{
+	delete[] data;
+
+	try
+	{
+		data = new uint8[size];
+	}
+	catch (std::exception &)
+	{
+		throw love::Exception("Out of memory.");
+	}
+
+	this->totalSize = size;
+}
+
+} // graphics
+} // love

+ 77 - 0
src/modules/graphics/StreamBuffer.h

@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2006-2016 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#pragma once
+
+// LOVE
+#include "common/int.h"
+
+// C
+#include <cstddef>
+
+namespace love
+{
+namespace graphics
+{
+
+// TODO: This class will need to be changed significantly in the future to
+// accomodate non-client-side vertex/index data.
+class StreamBuffer
+{
+public:
+
+	enum Mode
+	{
+		MODE_VERTEX,
+		MODE_INDEX,
+	};
+
+	StreamBuffer(Mode mode, size_t size);
+	~StreamBuffer();
+
+	void *getData() const;
+	void *getOffsetData() const;
+
+	void incrementOffset(size_t amount);
+	void resetOffset();
+
+	void setSize(size_t size);
+
+	size_t getSize() const
+	{
+		return totalSize;
+	}
+
+	Mode getMode() const
+	{
+		return mode;
+	}
+
+private:
+
+	uint8 *data;
+	size_t offset;
+	size_t totalSize;
+	Mode mode;
+
+}; // StreamBuffer
+
+} // graphics
+} // love

+ 33 - 0
src/modules/graphics/Texture.cpp

@@ -19,6 +19,7 @@
  **/
 
 #include "Texture.h"
+#include "Graphics.h"
 
 namespace love
 {
@@ -47,6 +48,38 @@ PixelFormat Texture::getPixelFormat() const
 	return format;
 }
 
+void Texture::draw(Graphics *gfx, const Matrix4 &m)
+{
+	drawv(gfx, m, vertices);
+}
+
+void Texture::drawq(Graphics *gfx, Quad *quad, const Matrix4 &m)
+{
+	drawv(gfx, m, quad->getVertices());
+}
+
+void Texture::drawv(Graphics *gfx, const Matrix4 &localTransform, const Vertex *v)
+{
+	Matrix4 t = gfx->getTransform() * localTransform;
+
+	Vertex verts[4] = {v[0], v[1], v[2], v[3]};
+	t.transform(verts, v, 4);
+
+	Color c = toColor(gfx->getColor());
+
+	for (int i = 0; i < 4; i++)
+		verts[i].color = c;
+
+	Graphics::StreamDrawRequest req;
+	req.formats[0] = vertex::CommonFormat::XYf_STf_RGBAub;
+	req.indexMode = vertex::TriangleIndexMode::QUADS;
+	req.vertexCount = 4;
+	req.texture = this;
+
+	Graphics::StreamVertexData data = gfx->requestStreamDraw(req);
+	memcpy(data.stream[0], verts, sizeof(Vertex) * 4);
+}
+
 int Texture::getWidth() const
 {
 	return width;

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

@@ -27,6 +27,10 @@
 #include "common/pixelformat.h"
 #include "Drawable.h"
 #include "Quad.h"
+#include "vertex.h"
+
+// C
+#include <stddef.h>
 
 namespace love
 {
@@ -77,10 +81,13 @@ public:
 	Texture();
 	virtual ~Texture();
 
+	// Drawable.
+	void draw(Graphics *gfx, const Matrix4 &m) override;
+
 	/**
 	 * Draws the texture using the specified transformation with a Quad applied.
 	 **/
-	virtual void drawq(Quad *quad, const Matrix4 &m) = 0;
+	void drawq(Graphics *gfx, Quad *quad, const Matrix4 &m);
 
 	PixelFormat getPixelFormat() const;
 
@@ -95,7 +102,7 @@ public:
 
 	virtual const Vertex *getVertices() const;
 
-	virtual const void *getHandle() const = 0;
+	virtual ptrdiff_t getHandle() const = 0;
 
 	// The default filter.
 	static void setDefaultFilter(const Filter &f);
@@ -111,6 +118,8 @@ public:
 
 protected:
 
+	virtual void drawv(Graphics *gfx, const Matrix4 &localTransform, const Vertex *v);
+
 	PixelFormat format;
 
 	int width;

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

@@ -250,37 +250,14 @@ void Canvas::unloadVolatile()
 	texture_memory = 0;
 }
 
-void Canvas::drawv(const Matrix4 &t, const Vertex *v)
+void Canvas::drawv(love::graphics::Graphics *gfx, const Matrix4 &localTransform, const Vertex *v)
 {
-	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
-	if (gfx != nullptr && gfx->isCanvasActive(this))
-		throw love::Exception("Cannot render a Canvas to itself!");
-
-	OpenGL::TempDebugGroup debuggroup("Canvas draw");
-
-	OpenGL::TempTransform transform(gl);
-	transform.get() *= t;
-
-	gl.bindTextureToUnit(texture, 0, false);
-
-	gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD);
-
-	gl.bindBuffer(BUFFER_VERTEX, 0);
-	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), &v[0].x);
-	glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), &v[0].s);
+	Graphics *glgfx = (Graphics *) gfx;
 
-	gl.prepareDraw();
-	gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
-}
-
-void Canvas::draw(const Matrix4 &m)
-{
-	drawv(m, vertices);
-}
+	if (glgfx != nullptr && glgfx->isCanvasActive(this))
+		throw love::Exception("Cannot render a Canvas to itself!");
 
-void Canvas::drawq(Quad *quad, const Matrix4 &m)
-{
-	drawv(m, quad->getVertices());
+	Texture::drawv(gfx, localTransform, v);
 }
 
 void Canvas::setFilter(const Texture::Filter &f)
@@ -322,9 +299,9 @@ bool Canvas::setWrap(const Texture::Wrap &w)
 	return success;
 }
 
-const void *Canvas::getHandle() const
+ptrdiff_t Canvas::getHandle() const
 {
-	return &texture;
+	return texture;
 }
 
 love::image::ImageData *Canvas::newImageData(love::image::Image *module, int x, int y, int w, int h)

+ 2 - 6
src/modules/graphics/opengl/Canvas.h

@@ -52,14 +52,10 @@ public:
 	bool loadVolatile() override;
 	void unloadVolatile() override;
 
-	// Implements Drawable.
-	void draw(const Matrix4 &m) override;
-
 	// Implements Texture.
-	void drawq(Quad *quad, const Matrix4 &m) override;
 	void setFilter(const Texture::Filter &f) override;
 	bool setWrap(const Texture::Wrap &w) override;
-	const void *getHandle() const override;
+	ptrdiff_t getHandle() const override;
 
 	love::image::ImageData *newImageData(love::image::Image *module, int x, int y, int w, int h);
 
@@ -97,7 +93,7 @@ public:
 
 private:
 
-	void drawv(const Matrix4 &t, const Vertex *v);
+	void drawv(Graphics *gfx, const Matrix4 &t, const Vertex *v) override;
 
 	GLuint fbo;
 

+ 10 - 9
src/modules/graphics/opengl/Font.cpp

@@ -431,7 +431,9 @@ std::vector<Font::DrawCommand> Font::generateVertices(const ColoredCodepoints &c
 
 	uint32 prevglyph = 0;
 
-	Color curcolor(255, 255, 255, 255);
+	Color initialcolor(255, 255, 255, 255);
+
+	Color curcolor = initialcolor;
 	int curcolori = -1;
 	int ncolors = (int) codepoints.colors.size();
 
@@ -441,11 +443,7 @@ std::vector<Font::DrawCommand> Font::generateVertices(const ColoredCodepoints &c
 
 		if (curcolori + 1 < ncolors && codepoints.colors[curcolori + 1].index == i)
 		{
-			const Colorf &c = codepoints.colors[++curcolori].color;
-			curcolor.r = (unsigned char) (c.r * 255.0f);
-			curcolor.g = (unsigned char) (c.g * 255.0f);
-			curcolor.b = (unsigned char) (c.b * 255.0f);
-			curcolor.a = (unsigned char) (c.a * 255.0f);
+			curcolor = toColor(codepoints.colors[++curcolori].color);
 		}
 
 		if (g == '\n')
@@ -478,7 +476,7 @@ std::vector<Font::DrawCommand> Font::generateVertices(const ColoredCodepoints &c
 			vertices.resize(vertstartsize);
 			prevglyph = 0;
 			curcolori = -1;
-			curcolor = Color(255, 255, 255, 255);
+			curcolor = initialcolor;
 			continue;
 		}
 
@@ -689,10 +687,13 @@ void Font::printv(const Matrix4 &t, const std::vector<DrawCommand> &drawcommands
 	if (vertices.empty() || drawcommands.empty())
 		return;
 
+	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
+
+	gfx->flushStreamDraws();
+
 	OpenGL::TempDebugGroup debuggroup("Font print");
 
-	OpenGL::TempTransform transform(gl);
-	transform.get() *= t;
+	Graphics::TempTransform transform(gfx, t);
 
 	gl.bindBuffer(BUFFER_VERTEX, 0);
 	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(GlyphVertex), &vertices[0].x);

+ 3 - 15
src/modules/graphics/opengl/GLBuffer.cpp

@@ -21,6 +21,7 @@
 #include "GLBuffer.h"
 
 #include "common/Exception.h"
+#include "graphics/vertex.h"
 
 #include <cstdlib>
 #include <cstring>
@@ -362,22 +363,9 @@ const void *QuadIndices::getIndices(size_t offset) const
 template <typename T>
 void QuadIndices::fill()
 {
-	T *inds = (T *) indices;
+	using namespace love::graphics::vertex;
 
-	// 0----2
-	// |  / |
-	// | /  |
-	// 1----3
-	for (size_t i = 0; i < maxSize; ++i)
-	{
-		inds[i*6+0] = T(i * 4 + 0);
-		inds[i*6+1] = T(i * 4 + 1);
-		inds[i*6+2] = T(i * 4 + 2);
-
-		inds[i*6+3] = T(i * 4 + 2);
-		inds[i*6+4] = T(i * 4 + 1);
-		inds[i*6+5] = T(i * 4 + 3);
-	}
+	fillIndices(TriangleIndexMode::QUADS, 0, maxSize * 4, (T *) indices);
 
 	indexBuffer->fill(0, indexBuffer->getSize(), indices);
 }

+ 235 - 54
src/modules/graphics/opengl/Graphics.cpp

@@ -232,7 +232,7 @@ void Graphics::setViewportSize(int width, int height)
 		gl.setViewport({0, 0, width, height}, false);
 
 		// Set up the projection matrix
-		gl.matrices.projection = Matrix4::ortho(0.0, (float) width, (float) height, 0.0);
+		projectionMatrix = Matrix4::ortho(0.0, (float) width, (float) height, 0.0);
 	}
 }
 
@@ -289,6 +289,15 @@ bool Graphics::setMode(int width, int height)
 
 	setDebug(enabledebug);
 
+	if (streamBufferState.vb[0] == nullptr)
+	{
+		// Initial sizes that should be good enough for most cases. It will
+		// resize to fit if needed, later.
+		streamBufferState.vb[0] = new StreamBuffer(StreamBuffer::MODE_VERTEX, 1024 * 1024 * 1);
+		streamBufferState.vb[1] = new StreamBuffer(StreamBuffer::MODE_VERTEX, 256  * 1024 * 1);
+		streamBufferState.indexBuffer = new StreamBuffer(StreamBuffer::MODE_INDEX, sizeof(uint16) * LOVE_UINT16_MAX);
+	}
+
 	// Reload all volatile objects.
 	if (!Volatile::loadAll())
 		::printf("Could not reload all volatile objects.\n");
@@ -339,6 +348,8 @@ void Graphics::unSetMode()
 	if (!isCreated())
 		return;
 
+	flushStreamDraws();
+
 	// Unload all volatile objects. These must be reloaded after the display
 	// mode change.
 	Volatile::unloadAll();
@@ -359,6 +370,8 @@ void Graphics::unSetMode()
 
 void Graphics::setActive(bool enable)
 {
+	flushStreamDraws();
+
 	// Make sure all pending OpenGL commands have fully executed before
 	// returning, when going from active to inactive. This is required on iOS.
 	if (isCreated() && this->active && !enable)
@@ -375,6 +388,113 @@ bool Graphics::isActive() const
 	return active && isCreated() && window != nullptr && window->isOpen();
 }
 
+void Graphics::flushStreamDraws()
+{
+	using namespace vertex;
+
+	const auto &sbstate = streamBufferState;
+
+	if (sbstate.vertexCount == 0 && sbstate.indexCount == 0)
+		return;
+
+	OpenGL::TempDebugGroup debuggroup("Stream vertices flush and draw");
+
+	uint32 attribs = 0;
+
+	for (int i = 0; i < 2; i++)
+	{
+		if (sbstate.formats[i] == CommonFormat::NONE)
+			continue;
+
+		StreamBuffer *buffer = sbstate.vb[i];
+
+		buffer->resetOffset();
+		ptrdiff_t offset = (ptrdiff_t) buffer->getData();
+		GLsizei stride = (GLsizei) getFormatStride(sbstate.formats[i]);
+
+		gl.bindBuffer(BUFFER_VERTEX, 0);
+
+		switch (sbstate.formats[i])
+		{
+		case CommonFormat::NONE:
+			break;
+		case CommonFormat::XYf:
+			attribs |= ATTRIBFLAG_POS;
+			glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(offset));
+			break;
+		case CommonFormat::RGBAub:
+			attribs |= ATTRIBFLAG_COLOR;
+			glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, BUFFER_OFFSET(offset));
+			break;
+		case CommonFormat::XYf_STf:
+			attribs |= ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD;
+			glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STf, x)));
+			glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STf, s)));
+			break;
+		case CommonFormat::XYf_STf_RGBAub:
+			attribs |= ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD | ATTRIBFLAG_COLOR;
+			glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STf_RGBAub, x)));
+			glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STf_RGBAub, s)));
+			glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STf_RGBAub, color.r)));
+			break;
+		case CommonFormat::XYf_STus_RGBAub:
+			attribs |= ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD | ATTRIBFLAG_COLOR;
+			glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STus_RGBAub, x)));
+			glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_UNSIGNED_SHORT, GL_TRUE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STus_RGBAub, s)));
+			glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STus_RGBAub, color.r)));
+			break;
+		}
+	}
+
+	if (attribs == 0)
+		return;
+
+	GLenum glmode = GL_ZERO;
+
+	switch (sbstate.primitiveMode)
+	{
+	case PrimitiveMode::TRIANGLES:
+		glmode = GL_TRIANGLES;
+		break;
+	case PrimitiveMode::POINTS:
+		glmode = GL_POINTS;
+		break;
+	}
+
+	if (attribs & ATTRIBFLAG_COLOR)
+		glVertexAttrib4f(ATTRIB_CONSTANTCOLOR, 1.0f, 1.0f, 1.0f, 1.0f);
+
+	pushIdentityTransform();
+
+	gl.prepareDraw();
+
+	gl.bindTextureToUnit(sbstate.texture, 0, false);
+
+	gl.useVertexAttribArrays(attribs);
+
+	if (sbstate.indexCount > 0)
+	{
+		sbstate.indexBuffer->resetOffset();
+		ptrdiff_t offset = (ptrdiff_t) sbstate.indexBuffer->getData();
+
+		gl.bindBuffer(BUFFER_INDEX, 0);
+		gl.drawElements(glmode, sbstate.indexCount, GL_UNSIGNED_SHORT, BUFFER_OFFSET(offset));
+	}
+	else
+		gl.drawArrays(glmode, 0, sbstate.vertexCount);
+
+	popTransform();
+
+	if (attribs & ATTRIB_CONSTANTCOLOR)
+	{
+		Colorf nc = states.back().gammaCorrectedColor;
+		glVertexAttrib4f(ATTRIB_CONSTANTCOLOR, nc.r, nc.g, nc.b, nc.a);
+	}
+
+	streamBufferState.vertexCount = 0;
+	streamBufferState.indexCount = 0;
+}
+
 static void APIENTRY debugCB(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei /*len*/, const GLchar *msg, const GLvoid* /*usr*/)
 {
 	// Human-readable strings for the debug info.
@@ -513,7 +633,7 @@ void Graphics::setCanvas(const std::vector<Canvas *> &canvases)
 	int h = firstcanvas->getHeight();
 
 	gl.setViewport({0, 0, w, h}, true);
-	gl.matrices.projection = Matrix4::ortho(0.0, (float) w, 0.0, (float) h);
+	projectionMatrix = Matrix4::ortho(0.0, (float) w, 0.0, (float) h);
 
 	// Make sure the correct sRGB setting is used when drawing to the canvases.
 	if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control)
@@ -564,7 +684,7 @@ void Graphics::setCanvas()
 
 	// The projection matrix is flipped compared to rendering to a canvas, due
 	// to OpenGL considering (0,0) bottom-left instead of top-left.
-	gl.matrices.projection = Matrix4::ortho(0.0, (float) width, (float) height, 0.0);
+	projectionMatrix = Matrix4::ortho(0.0, (float) width, (float) height, 0.0);
 
 	if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control)
 	{
@@ -590,6 +710,8 @@ std::vector<Canvas *> Graphics::getCanvas() const
 
 void Graphics::endPass()
 {
+	flushStreamDraws();
+
 	// Discard the stencil buffer.
 	discard({}, true);
 
@@ -617,6 +739,8 @@ void Graphics::endPass()
 
 void Graphics::clear(Colorf c)
 {
+	flushStreamDraws();
+
 	gammaCorrectColor(c);
 	glClearColor(c.r, c.g, c.b, c.a);
 	glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
@@ -646,6 +770,8 @@ void Graphics::clear(const std::vector<OptionalColorf> &colors)
 		return;
 	}
 
+	flushStreamDraws();
+
 	bool drawbuffersmodified = false;
 
 	for (int i = 0; i < ncolors; i++)
@@ -712,6 +838,7 @@ bool Graphics::isCanvasActive(Canvas *canvas) const
 
 void Graphics::discard(const std::vector<bool> &colorbuffers, bool depthstencil)
 {
+	flushStreamDraws();
 	discard(OpenGL::FRAMEBUFFER_ALL, colorbuffers, depthstencil);
 }
 
@@ -799,7 +926,7 @@ void Graphics::bindCachedFBO(const std::vector<Canvas *> &canvases)
 			}
 			else
 			{
-				GLuint tex = *(GLuint *) canvases[i]->getHandle();
+				GLuint tex = (GLuint) canvases[i]->getHandle();
 				glFramebufferTexture2D(GL_FRAMEBUFFER, drawbuffers[i], GL_TEXTURE_2D, tex, 0);
 			}
 		}
@@ -1047,6 +1174,8 @@ bool Graphics::isCreated() const
 
 void Graphics::setScissor(const Rect &rect)
 {
+	flushStreamDraws();
+
 	DisplayState &state = states.back();
 
 	glEnable(GL_SCISSOR_TEST);
@@ -1081,6 +1210,7 @@ void Graphics::intersectScissor(const Rect &rect)
 
 void Graphics::setScissor()
 {
+	flushStreamDraws();
 	states.back().scissor = false;
 	glDisable(GL_SCISSOR_TEST);
 }
@@ -1135,6 +1265,8 @@ void Graphics::stopDrawToStencilBuffer()
 	if (!writingToStencil)
 		return;
 
+	flushStreamDraws();
+
 	writingToStencil = false;
 
 	const DisplayState &state = states.back();
@@ -1324,11 +1456,17 @@ bool Graphics::isGammaCorrect() const
 
 void Graphics::setColor(Colorf c)
 {
+	c.r = std::min(std::max(c.r, 0.0f), 1.0f);
+	c.g = std::min(std::max(c.g, 0.0f), 1.0f);
+	c.b = std::min(std::max(c.b, 0.0f), 1.0f);
+	c.a = std::min(std::max(c.a, 0.0f), 1.0f);
+
 	Colorf nc = c;
 	gammaCorrectColor(nc);
 	glVertexAttrib4f(ATTRIB_CONSTANTCOLOR, nc.r, nc.g, nc.b, nc.a);
 
 	states.back().color = c;
+	states.back().gammaCorrectedColor = nc;
 }
 
 Colorf Graphics::getColor() const
@@ -1350,7 +1488,6 @@ void Graphics::setFont(Font *font)
 {
 	// We don't need to set a default font here if null is passed in, since we
 	// only care about the default font in getFont and print.
-
 	DisplayState &state = states.back();
 	state.font.set(font);
 }
@@ -1366,6 +1503,8 @@ void Graphics::setShader(Shader *shader)
 	if (shader == nullptr)
 		return setShader();
 
+	flushStreamDraws();
+
 	DisplayState &state = states.back();
 
 	shader->attach();
@@ -1375,6 +1514,8 @@ void Graphics::setShader(Shader *shader)
 
 void Graphics::setShader()
 {
+	flushStreamDraws();
+
 	DisplayState &state = states.back();
 
 	// This will activate the default shader.
@@ -1390,6 +1531,8 @@ Shader *Graphics::getShader() const
 
 void Graphics::setColorMask(ColorMask mask)
 {
+	flushStreamDraws();
+
 	glColorMask(mask.r, mask.g, mask.b, mask.a);
 	states.back().colorMask = mask;
 }
@@ -1401,6 +1544,8 @@ Graphics::ColorMask Graphics::getColorMask() const
 
 void Graphics::setBlendMode(BlendMode mode, BlendAlpha alphamode)
 {
+	flushStreamDraws();
+
 	GLenum func   = GL_FUNC_ADD;
 	GLenum srcRGB = GL_ONE;
 	GLenum srcA   = GL_ONE;
@@ -1538,6 +1683,9 @@ Graphics::LineJoin Graphics::getLineJoin() const
 
 void Graphics::setPointSize(float size)
 {
+	if (streamBufferState.primitiveMode == vertex::PrimitiveMode::POINTS)
+		flushStreamDraws();
+
 	gl.setPointSize(size);
 	states.back().pointSize = size;
 }
@@ -1553,6 +1701,8 @@ void Graphics::setWireframe(bool enable)
 	if (GLAD_ES_VERSION_2_0)
 		return;
 
+	flushStreamDraws();
+
 	glPolygonMode(GL_FRONT_AND_BACK, enable ? GL_LINE : GL_FILL);
 	states.back().wireframe = enable;
 }
@@ -1564,12 +1714,12 @@ bool Graphics::isWireframe() const
 
 void Graphics::draw(Drawable *drawable, const Matrix4 &m)
 {
-	drawable->draw(m);
+	drawable->draw(this, m);
 }
 
-void Graphics::drawq(Texture *texture, Quad *quad, const Matrix4 &m)
+void Graphics::draw(Texture *texture, Quad *quad, const Matrix4 &m)
 {
-	texture->drawq(quad, m);
+	texture->drawq(this, quad, m);
 }
 
 void Graphics::print(const std::vector<Font::ColoredString> &str, const Matrix4 &m)
@@ -1596,25 +1746,51 @@ void Graphics::printf(const std::vector<Font::ColoredString> &str, float wrap, F
  * Primitives
  **/
 
-void Graphics::points(const float *coords, const uint8 *colors, size_t numpoints)
+void Graphics::points(const float *coords, const Colorf *colors, size_t numpoints)
 {
-	OpenGL::TempDebugGroup debuggroup("Graphics points draw");
+	StreamDrawRequest req;
+	req.primitiveMode = vertex::PrimitiveMode::POINTS;
+	req.formats[0] = vertex::CommonFormat::XYf;
+	if (colors)
+		req.formats[1] = vertex::CommonFormat::RGBAub;
+	req.vertexCount = (int) numpoints;
 
-	gl.prepareDraw();
-	gl.bindTextureToUnit(gl.getDefaultTexture(), 0, false);
-	gl.bindBuffer(BUFFER_VERTEX, 0);
+	StreamVertexData data = requestStreamDraw(req);
+
+	const Matrix4 &t = getTransform();
+	t.transform((Vector *) data.stream[0], (const Vector *) coords, req.vertexCount);
 
-	uint32 attribflags = ATTRIBFLAG_POS;
-	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, 0, coords);
+	Color *colordata = (Color *) data.stream[1];
 
 	if (colors)
 	{
-		attribflags |= ATTRIBFLAG_COLOR;
-		glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, colors);
+		Colorf nc = getColor();
+		gammaCorrectColor(nc);
+
+		if (isGammaCorrect())
+		{
+			for (int i = 0; i < req.vertexCount; i++)
+			{
+				Colorf ci = colors[i];
+				gammaCorrectColor(ci);
+				ci *= nc;
+				unGammaCorrectColor(ci);
+				colordata[i] = toColor(ci);
+			}
+		}
+		else
+		{
+			for (int i = 0; i < req.vertexCount; i++)
+				colordata[i] = toColor(nc * colors[i]);
+		}
 	}
+	else
+	{
+		Color c = toColor(getColor());
 
-	gl.useVertexAttribArrays(attribflags);
-	gl.drawArrays(GL_POINTS, 0, (GLsizei) numpoints);
+		for (int i = 0; i < req.vertexCount; i++)
+			colordata[i] = c;
+	}
 }
 
 void Graphics::polyline(const float *coords, size_t count)
@@ -1626,19 +1802,19 @@ void Graphics::polyline(const float *coords, size_t count)
 	{
 		NoneJoinPolyline line;
 		line.render(coords, count, state.lineWidth * .5f, pixelsize, state.lineStyle == LINE_SMOOTH);
-		line.draw();
+		line.draw(this);
 	}
 	else if (state.lineJoin == LINE_JOIN_BEVEL)
 	{
 		BevelJoinPolyline line;
 		line.render(coords, count, state.lineWidth * .5f, pixelsize, state.lineStyle == LINE_SMOOTH);
-		line.draw();
+		line.draw(this);
 	}
 	else // LINE_JOIN_MITER
 	{
 		MiterJoinPolyline line;
 		line.render(coords, count, state.lineWidth * .5f, pixelsize, state.lineStyle == LINE_SMOOTH);
-		line.draw();
+		line.draw(this);
 	}
 }
 
@@ -1669,7 +1845,7 @@ void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h, floa
 	float angle_shift = half_pi / ((float) points + 1.0f);
 
 	int num_coords = (points + 2) * 8;
-	float *coords = new float[num_coords + 2];
+	float *coords = getScratchBuffer<float>(num_coords + 2);
 	float phi = .0f;
 
 	for (int i = 0; i <= points + 2; ++i, phi += angle_shift)
@@ -1706,8 +1882,6 @@ void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h, floa
 	coords[num_coords + 1] = coords[1];
 
 	polygon(mode, coords, num_coords + 2);
-
-	delete[] coords;
 }
 
 int Graphics::calculateEllipsePoints(float rx, float ry) const
@@ -1733,12 +1907,12 @@ void Graphics::circle(DrawMode mode, float x, float y, float radius)
 
 void Graphics::ellipse(DrawMode mode, float x, float y, float a, float b, int points)
 {
-	float two_pi = static_cast<float>(LOVE_M_PI * 2);
+	float two_pi = (float) (LOVE_M_PI * 2);
 	if (points <= 0) points = 1;
 	float angle_shift = (two_pi / points);
 	float phi = .0f;
 
-	float *coords = new float[2 * (points + 1)];
+	float *coords = getScratchBuffer<float>(2 * (points + 1));
 	for (int i = 0; i < points; ++i, phi += angle_shift)
 	{
 		coords[2*i+0] = x + a * cosf(phi);
@@ -1749,8 +1923,6 @@ void Graphics::ellipse(DrawMode mode, float x, float y, float a, float b, int po
 	coords[2*points+1] = coords[1];
 
 	polygon(mode, coords, (points + 1) * 2);
-
-	delete[] coords;
 }
 
 void Graphics::ellipse(DrawMode mode, float x, float y, float a, float b)
@@ -1804,7 +1976,7 @@ void Graphics::arc(DrawMode drawmode, ArcMode arcmode, float x, float y, float r
 	if (arcmode == ARC_PIE)
 	{
 		num_coords = (points + 3) * 2;
-		coords = new float[num_coords];
+		coords = getScratchBuffer<float>(num_coords);
 
 		coords[0] = coords[num_coords - 2] = x;
 		coords[1] = coords[num_coords - 1] = y;
@@ -1814,14 +1986,14 @@ void Graphics::arc(DrawMode drawmode, ArcMode arcmode, float x, float y, float r
 	else if (arcmode == ARC_OPEN)
 	{
 		num_coords = (points + 1) * 2;
-		coords = new float[num_coords];
+		coords = getScratchBuffer<float>(num_coords);
 
 		createPoints(coords);
 	}
 	else // ARC_CLOSED
 	{
 		num_coords = (points + 2) * 2;
-		coords = new float[num_coords];
+		coords = getScratchBuffer<float>(num_coords);
 
 		createPoints(coords);
 
@@ -1830,10 +2002,7 @@ void Graphics::arc(DrawMode drawmode, ArcMode arcmode, float x, float y, float r
 		coords[num_coords - 1] = coords[1];
 	}
 
-	// NOTE: We rely on polygon() using GL_TRIANGLE_FAN, when fill mode is used.
 	polygon(drawmode, coords, num_coords);
-
-	delete[] coords;
 }
 
 void Graphics::arc(DrawMode drawmode, ArcMode arcmode, float x, float y, float radius, float angle1, float angle2)
@@ -1861,14 +2030,21 @@ void Graphics::polygon(DrawMode mode, const float *coords, size_t count)
 	}
 	else
 	{
-		OpenGL::TempDebugGroup debuggroup("Filled polygon draw");
+		StreamDrawRequest req;
+		req.formats[0] = vertex::CommonFormat::XYf;
+		req.formats[1] = vertex::CommonFormat::RGBAub;
+		req.indexMode = vertex::TriangleIndexMode::FAN;
+		req.vertexCount = (int)count/2 - 1;
 
-		gl.prepareDraw();
-		gl.bindTextureToUnit(gl.getDefaultTexture(), 0, false);
-		gl.bindBuffer(BUFFER_VERTEX, 0);
-		gl.useVertexAttribArrays(ATTRIBFLAG_POS);
-		glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, 0, coords);
-		gl.drawArrays(GL_TRIANGLE_FAN, 0, (int)count/2-1); // opengl will close the polygon for us
+		StreamVertexData data = requestStreamDraw(req);
+
+		const Matrix4 &t = getTransform();
+		t.transform((Vector *) data.stream[0], (const Vector *) coords, req.vertexCount);
+
+		Color c = toColor(getColor());
+		Color *colordata = (Color *) data.stream[1];
+		for (int i = 0; i < req.vertexCount; i++)
+			colordata[i] = c;
 	}
 }
 
@@ -1904,9 +2080,14 @@ Graphics::RendererInfo Graphics::getRendererInfo() const
 
 Graphics::Stats Graphics::getStats() const
 {
+	int drawcalls = gl.stats.drawCalls;
+
+	if (streamBufferState.vertexCount > 0)
+		drawcalls++;
+
 	Stats stats;
 
-	stats.drawCalls = gl.stats.drawCalls;
+	stats.drawCalls = drawcalls;
 	stats.canvasSwitches = canvasSwitchCount;
 	stats.shaderSwitches = gl.stats.shaderSwitches;
 	stats.canvases = Canvas::canvasCount;
@@ -1960,7 +2141,7 @@ void Graphics::push(StackType type)
 	if (stackTypes.size() == MAX_USER_STACK_DEPTH)
 		throw Exception("Maximum stack depth reached (more pushes than pops?)");
 
-	gl.pushTransform();
+	pushTransform();
 
 	pixelScaleStack.push_back(pixelScaleStack.back());
 
@@ -1975,7 +2156,7 @@ void Graphics::pop()
 	if (stackTypes.size() < 1)
 		throw Exception("Minimum stack depth reached (more pops than pushes?)");
 
-	gl.popTransform();
+	popTransform();
 	pixelScaleStack.pop_back();
 
 	if (stackTypes.back() == STACK_ALL)
@@ -1993,34 +2174,34 @@ void Graphics::pop()
 
 void Graphics::rotate(float r)
 {
-	gl.getTransform().rotate(r);
+	transformStack.back().rotate(r);
 }
 
 void Graphics::scale(float x, float y)
 {
-	gl.getTransform().scale(x, y);
+	transformStack.back().scale(x, y);
 	pixelScaleStack.back() *= (fabs(x) + fabs(y)) / 2.0;
 }
 
 void Graphics::translate(float x, float y)
 {
-	gl.getTransform().translate(x, y);
+	transformStack.back().translate(x, y);
 }
 
 void Graphics::shear(float kx, float ky)
 {
-	gl.getTransform().shear(kx, ky);
+	transformStack.back().shear(kx, ky);
 }
 
 void Graphics::origin()
 {
-	gl.getTransform().setIdentity();
+	transformStack.back().setIdentity();
 	pixelScaleStack.back() = 1;
 }
 
 void Graphics::applyTransform(love::math::Transform *transform)
 {
-	Matrix4 &m = gl.getTransform();
+	Matrix4 &m = transformStack.back();
 	m *= transform->getMatrix();
 
 	float sx, sy;
@@ -2031,7 +2212,7 @@ void Graphics::applyTransform(love::math::Transform *transform)
 void Graphics::replaceTransform(love::math::Transform *transform)
 {
 	const Matrix4 &m = transform->getMatrix();
-	gl.getTransform() = m;
+	transformStack.back() = m;
 
 	float sx, sy;
 	m.getApproximateScale(sx, sy);
@@ -2041,7 +2222,7 @@ void Graphics::replaceTransform(love::math::Transform *transform)
 Vector Graphics::transformPoint(Vector point)
 {
 	Vector p;
-	gl.getTransform().transform(&p, &point, 1);
+	transformStack.back().transform(&p, &point, 1);
 	return p;
 }
 
@@ -2050,7 +2231,7 @@ Vector Graphics::inverseTransformPoint(Vector point)
 	Vector p;
 	// TODO: We should probably cache the inverse transform so we don't have to
 	// re-calculate it every time this is called.
-	gl.getTransform().inverse().transform(&p, &point, 1);
+	transformStack.back().inverse().transform(&p, &point, 1);
 	return p;
 }
 

+ 16 - 14
src/modules/graphics/opengl/Graphics.h

@@ -62,7 +62,7 @@ namespace graphics
 namespace opengl
 {
 
-class Graphics : public love::graphics::Graphics
+class Graphics final : public love::graphics::Graphics
 {
 public:
 
@@ -78,16 +78,16 @@ public:
 	virtual ~Graphics();
 
 	// Implements Module.
-	const char *getName() const;
+	const char *getName() const override;
 
-	virtual void setViewportSize(int width, int height);
-	virtual bool setMode(int width, int height);
-	virtual void unSetMode();
+	void setViewportSize(int width, int height) override;
+	bool setMode(int width, int height) override;
+	void unSetMode() override;
 
-	virtual void setActive(bool active);
-	virtual bool isActive() const;
+	void setActive(bool active) override;
+	bool isActive() const override;
 
-	void setDebug(bool enable);
+	void flushStreamDraws() override;
 
 	/**
 	 * Resets the current color, background color,
@@ -101,7 +101,7 @@ public:
 
 	void discard(const std::vector<bool> &colorbuffers, bool depthstencil);
 
-	virtual bool isCanvasActive() const;
+	bool isCanvasActive() const override;
 	bool isCanvasActive(Canvas *canvas) const;
 
 	/**
@@ -203,15 +203,14 @@ public:
 	bool isGammaCorrect() const;
 
 	/**
-	 * Sets the foreground color.
-	 * @param c The new foreground color.
+	 * Sets the current constant color.
 	 **/
 	void setColor(Colorf c);
 
 	/**
 	 * Gets current color.
 	 **/
-	Colorf getColor() const;
+	Colorf getColor() const override;
 
 	/**
 	 * Sets the background Color.
@@ -324,7 +323,7 @@ public:
 	bool isWireframe() const;
 
 	void draw(Drawable *drawable, const Matrix4 &m);
-	void drawq(Texture *texture, Quad *quad, const Matrix4 &m);
+	void draw(Texture *texture, Quad *quad, const Matrix4 &m);
 
 	/**
 	 * Draws text at the specified coordinates
@@ -341,7 +340,7 @@ public:
 	 * @param x Point along x-axis.
 	 * @param y Point along y-axis.
 	 **/
-	void points(const float *coords, const uint8 *colors, size_t numpoints);
+	void points(const float *coords, const Colorf *colors, size_t numpoints);
 
 	/**
 	 * Draws a series of lines connecting the given vertices.
@@ -462,6 +461,7 @@ private:
 	struct DisplayState
 	{
 		Colorf color = Colorf(1.0, 1.0, 1.0, 1.0);
+		Colorf gammaCorrectedColor = Colorf(1.0f, 1.0f, 1.0f, 1.0f);
 		Colorf backgroundColor = Colorf(0.0, 0.0, 0.0, 1.0);
 
 		BlendMode blendMode = BLEND_ALPHA;
@@ -512,6 +512,8 @@ private:
 	void discard(OpenGL::FramebufferTarget target, const std::vector<bool> &colorbuffers, bool depthstencil);
 	GLuint attachCachedStencilBuffer(int w, int h, int samples);
 
+	void setDebug(bool enable);
+
 	void checkSetDefaultFont();
 
 	int calculateEllipsePoints(float rx, float ry) const;

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

@@ -169,7 +169,7 @@ Image::~Image()
 void Image::preload()
 {
 	for (int i = 0; i < 4; i++)
-		vertices[i].r = vertices[i].g = vertices[i].b = vertices[i].a = 255;
+		vertices[i].color = Color(255, 255, 255, 255);
 
 	// Vertices are ordered for use with triangle strips:
 	// 0----2
@@ -448,38 +448,9 @@ bool Image::refresh(int xoffset, int yoffset, int w, int h)
 	return true;
 }
 
-void Image::drawv(const Matrix4 &t, const Vertex *v)
+ptrdiff_t Image::getHandle() const
 {
-	OpenGL::TempDebugGroup debuggroup("Image draw");
-
-	OpenGL::TempTransform transform(gl);
-	transform.get() *= t;
-
-	gl.bindTextureToUnit(texture, 0, false);
-
-	gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD);
-
-	gl.bindBuffer(BUFFER_VERTEX, 0);
-	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), &v[0].x);
-	glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), &v[0].s);
-
-	gl.prepareDraw();
-	gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
-}
-
-void Image::draw(const Matrix4 &m)
-{
-	drawv(m, vertices);
-}
-
-void Image::drawq(Quad *quad, const Matrix4 &m)
-{
-	drawv(m, quad->getVertices());
-}
-
-const void *Image::getHandle() const
-{
-	return &texture;
+	return texture;
 }
 
 const std::vector<StrongRef<love::image::ImageData>> &Image::getImageData() const

+ 1 - 13
src/modules/graphics/opengl/Image.h

@@ -90,17 +90,7 @@ public:
 	bool loadVolatile() override;
 	void unloadVolatile() override;
 
-	/**
-	 * @copydoc Drawable::draw()
-	 **/
-	void draw(const Matrix4 &m) override;
-
-	/**
-	 * @copydoc Texture::drawq()
-	 **/
-	void drawq(Quad *quad, const Matrix4 &m) override;
-
-	const void *getHandle() const override;
+	ptrdiff_t getHandle() const override;
 
 	const std::vector<StrongRef<love::image::ImageData>> &getImageData() const;
 	const std::vector<StrongRef<love::image::CompressedImageData>> &getCompressedData() const;
@@ -139,8 +129,6 @@ public:
 
 private:
 
-	void drawv(const Matrix4 &t, const Vertex *v);
-
 	void preload();
 
 	void generateMipmaps();

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

@@ -23,6 +23,7 @@
 #include "common/Matrix.h"
 #include "common/Exception.h"
 #include "Shader.h"
+#include "graphics/Graphics.h"
 
 // C++
 #include <algorithm>
@@ -44,7 +45,7 @@ static const char *getBuiltinAttribName(VertexAttribID attribid)
 
 static_assert(offsetof(Vertex, x) == sizeof(float) * 0, "Incorrect position offset in Vertex struct");
 static_assert(offsetof(Vertex, s) == sizeof(float) * 2, "Incorrect texture coordinate offset in Vertex struct");
-static_assert(offsetof(Vertex, r) == sizeof(float) * 4, "Incorrect color offset in Vertex struct");
+static_assert(offsetof(Vertex, color.r) == sizeof(float) * 4, "Incorrect color offset in Vertex struct");
 
 static std::vector<Mesh::AttribFormat> getDefaultVertexFormat()
 {
@@ -574,11 +575,13 @@ int Mesh::bindAttributeToShaderInput(int attributeindex, const std::string &inpu
 	return attriblocation;
 }
 
-void Mesh::draw(const Matrix4 &m)
+void Mesh::draw(Graphics *gfx, const Matrix4 &m)
 {
 	if (vertexCount <= 0)
 		return;
 
+	gfx->flushStreamDraws();
+
 	OpenGL::TempDebugGroup debuggroup("Mesh draw");
 
 	uint32 enabledattribs = 0;
@@ -601,13 +604,9 @@ void Mesh::draw(const Matrix4 &m)
 
 	gl.useVertexAttribArrays(enabledattribs);
 
-	if (texture.get())
-		gl.bindTextureToUnit(*(GLuint *) texture->getHandle(), 0, false);
-	else
-		gl.bindTextureToUnit(gl.getDefaultTexture(), 0, false);
+	gl.bindTextureToUnit(texture, 0, false);
 
-	OpenGL::TempTransform transform(gl);
-	transform.get() *= m;
+	Graphics::TempTransform transform(gfx, m);
 
 	gl.prepareDraw();
 

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

@@ -200,7 +200,7 @@ public:
 	int bindAttributeToShaderInput(int attributeindex, const std::string &inputname);
 
 	// Implements Drawable.
-	void draw(const Matrix4 &m) override;
+	void draw(Graphics *gfx, const Matrix4 &m) override;
 
 	static GLenum getGLBufferUsage(Usage usage);
 

+ 14 - 30
src/modules/graphics/opengl/OpenGL.cpp

@@ -25,6 +25,8 @@
 #include "Shader.h"
 #include "common/Exception.h"
 
+#include "graphics/Graphics.h"
+
 // C++
 #include <algorithm>
 #include <limits>
@@ -74,7 +76,6 @@ OpenGL::OpenGL()
 	, vendor(VENDOR_UNKNOWN)
 	, state()
 {
-	matrices.transform.reserve(10);
 }
 
 bool OpenGL::initContext()
@@ -87,7 +88,6 @@ bool OpenGL::initContext()
 
 	initOpenGLFunctions();
 	initVendor();
-	initMatrices();
 
 	bugs = {};
 
@@ -314,14 +314,6 @@ void OpenGL::initMaxValues()
 	maxPointSize = limits[1];
 }
 
-void OpenGL::initMatrices()
-{
-	matrices.transform.clear();
-
-	matrices.transform.push_back(Matrix4());
-	matrices.projection = Matrix4();
-}
-
 void OpenGL::createDefaultTexture()
 {
 	// Set the 'default' texture (id 0) as a repeating white pixel. Otherwise,
@@ -346,21 +338,6 @@ void OpenGL::createDefaultTexture()
 	bindTextureToUnit(curtexture, 0, false);
 }
 
-void OpenGL::pushTransform()
-{
-	matrices.transform.push_back(matrices.transform.back());
-}
-
-void OpenGL::popTransform()
-{
-	matrices.transform.pop_back();
-}
-
-Matrix4 &OpenGL::getTransform()
-{
-	return matrices.transform.back();
-}
-
 void OpenGL::prepareDraw()
 {
 	TempDebugGroup debuggroup("Prepare OpenGL draw");
@@ -373,8 +350,9 @@ void OpenGL::prepareDraw()
 	// because uniform uploads can be significantly slower than glLoadMatrix.
 	if (GLAD_VERSION_1_0)
 	{
-		const Matrix4 &curproj = matrices.projection;
-		const Matrix4 &curxform = matrices.transform.back();
+		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
+		const Matrix4 &curxform = gfx->getTransform();
+		const Matrix4 &curproj = gfx->getProjection();
 
 		const Matrix4 &lastproj = state.lastProjectionMatrix;
 		const Matrix4 &lastxform = state.lastTransformMatrix;
@@ -386,14 +364,14 @@ void OpenGL::prepareDraw()
 			glLoadMatrixf(curproj.getElements());
 			glMatrixMode(GL_MODELVIEW);
 
-			state.lastProjectionMatrix = matrices.projection;
+			state.lastProjectionMatrix = curproj;
 		}
 
 		// Same with the transform matrix.
 		if (memcmp(curxform.getElements(), lastxform.getElements(), sizeof(float) * 16) != 0)
 		{
 			glLoadMatrixf(curxform.getElements());
-			state.lastTransformMatrix = matrices.transform.back();
+			state.lastTransformMatrix = curxform;
 		}
 	}
 }
@@ -454,7 +432,7 @@ void OpenGL::useVertexAttribArrays(uint32 arraybits)
 	// than 32. Lets hope that doesn't change...
 	for (uint32 i = 0; i < 32; i++)
 	{
-		uint32 bit = 1 << i;
+		uint32 bit = 1u << i;
 
 		if (diff & bit)
 		{
@@ -637,6 +615,12 @@ void OpenGL::bindTextureToUnit(GLuint texture, int textureunit, bool restoreprev
 	}
 }
 
+void OpenGL::bindTextureToUnit(Texture *texture, int textureunit, bool restoreprev)
+{
+	GLuint handle = texture != nullptr ? (GLuint) texture->getHandle() : getDefaultTexture();
+	bindTextureToUnit(handle, textureunit, restoreprev);
+}
+
 void OpenGL::deleteTexture(GLuint texture)
 {
 	// glDeleteTextures binds texture 0 to all texture units the deleted texture

+ 2 - 42
src/modules/graphics/opengl/OpenGL.h

@@ -27,6 +27,7 @@
 #include "common/math.h"
 #include "graphics/Color.h"
 #include "graphics/Texture.h"
+#include "graphics/vertex.h"
 #include "common/Matrix.h"
 
 // GLAD
@@ -70,13 +71,6 @@ enum VertexAttribFlags
 	ATTRIBFLAG_CONSTANTCOLOR = 1 << ATTRIB_CONSTANTCOLOR
 };
 
-enum BufferType
-{
-	BUFFER_VERTEX = 0,
-	BUFFER_INDEX,
-	BUFFER_MAX_ENUM
-};
-
 /**
  * Thin layer between OpenGL and the rest of the program.
  * Internally shadows some OpenGL context state for improved efficiency and
@@ -119,36 +113,6 @@ public:
 		GLenum type = 0;
 	};
 
-	struct
-	{
-		std::vector<Matrix4> transform;
-		Matrix4 projection;
-	} matrices;
-
-	class TempTransform
-	{
-	public:
-
-		TempTransform(OpenGL &gl)
-			: gl(gl)
-		{
-			gl.pushTransform();
-		}
-
-		~TempTransform()
-		{
-			gl.popTransform();
-		}
-
-		Matrix4 &get()
-		{
-			return gl.getTransform();
-		}
-
-	private:
-		OpenGL &gl;
-	};
-
 	class TempDebugGroup
 	{
 	public:
@@ -242,10 +206,6 @@ public:
 	 **/
 	void deInitContext();
 
-	void pushTransform();
-	void popTransform();
-	Matrix4 &getTransform();
-
 	/**
 	 * Set up necessary state (LOVE-provided shader uniforms, etc.) for drawing.
 	 * This *MUST* be called directly before OpenGL drawing functions.
@@ -359,6 +319,7 @@ public:
 	 * @param restoreprev Restore previously bound texture unit when done.
 	 **/
 	void bindTextureToUnit(GLuint texture, int textureunit, bool restoreprev);
+	void bindTextureToUnit(Texture *texture, int textureunit, bool restoreprev);
 
 	/**
 	 * Helper for deleting an OpenGL texture.
@@ -440,7 +401,6 @@ private:
 	void initVendor();
 	void initOpenGLFunctions();
 	void initMaxValues();
-	void initMatrices();
 	void createDefaultTexture();
 
 	bool contextInitialized;

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

@@ -21,6 +21,7 @@
 //LOVE
 #include "common/config.h"
 #include "ParticleSystem.h"
+#include "graphics/Graphics.h"
 
 #include "OpenGL.h"
 
@@ -61,7 +62,7 @@ void ParticleSystem::createVertices(size_t numparticles)
 {
 	try
 	{
-		love::Vertex *pverts = new love::Vertex[numparticles * 4];
+		Vertex *pverts = new Vertex[numparticles * 4];
 		delete[] particleVerts;
 		particleVerts = pverts;
 	}
@@ -84,17 +85,18 @@ void ParticleSystem::setBufferSize(uint32 size)
 	createVertices(size);
 }
 
-void ParticleSystem::draw(const Matrix4 &m)
+void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m)
 {
 	uint32 pCount = getCount();
 
 	if (pCount == 0 || texture.get() == nullptr || pMem == nullptr || particleVerts == nullptr)
 		return;
 
+	gfx->flushStreamDraws();
+
 	OpenGL::TempDebugGroup debuggroup("ParticleSystem draw");
 
-	OpenGL::TempTransform transform(gl);
-	transform.get() *= m;
+	Graphics::TempTransform transform(gfx, m);
 
 	const Vertex *textureVerts = texture->getVertices();
 	Vertex *pVerts = particleVerts;
@@ -114,31 +116,29 @@ void ParticleSystem::draw(const Matrix4 &m)
 		t.setTransformation(p->position.x, p->position.y, p->angle, p->size, p->size, offset.x, offset.y, 0.0f, 0.0f);
 		t.transform(pVerts, textureVerts, 4);
 
+		// Particle colors are stored as floats (0-1) but vertex colors are
+		// unsigned bytes (0-255).
+		Color c = toColor(p->color);
+
 		// set the texture coordinate and color data for particle vertices
 		for (int v = 0; v < 4; v++)
 		{
 			pVerts[v].s = textureVerts[v].s;
 			pVerts[v].t = textureVerts[v].t;
-
-			// Particle colors are stored as floats (0-1) but vertex colors are
-			// unsigned bytes (0-255).
-			pVerts[v].r = (unsigned char) (p->color.r*255);
-			pVerts[v].g = (unsigned char) (p->color.g*255);
-			pVerts[v].b = (unsigned char) (p->color.b*255);
-			pVerts[v].a = (unsigned char) (p->color.a*255);
+			pVerts[v].color = c;
 		}
 
 		pVerts += 4;
 		p = p->next;
 	}
 
-	gl.bindTextureToUnit(*(GLuint *) texture->getHandle(), 0, false);
+	gl.bindTextureToUnit(texture, 0, false);
 	gl.prepareDraw();
 
 	gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD | ATTRIBFLAG_COLOR);
 
 	gl.bindBuffer(BUFFER_VERTEX, 0);
-	glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), &particleVerts[0].r);
+	glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), &particleVerts[0].color.r);
 	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), &particleVerts[0].x);
 	glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), &particleVerts[0].s);
 

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

@@ -43,7 +43,7 @@ public:
 
 	ParticleSystem *clone() override;
 	void setBufferSize(uint32 size) override;
-	void draw(const Matrix4 &m) override;
+	void draw(Graphics *gfx, const Matrix4 &m) override;
 
 private:
 

+ 57 - 102
src/modules/graphics/opengl/Polyline.cpp

@@ -20,9 +20,7 @@
 
 // LOVE
 #include "Polyline.h"
-
-// OpenGL
-#include "OpenGL.h"
+#include "graphics/Graphics.h"
 
 // C++
 #include <algorithm>
@@ -86,7 +84,7 @@ void Polyline::render(const float *coords, size_t count, size_t size_hint, float
 		// extra degenerate triangle in between the core line and the overdraw
 		// line in order to break up the strip into two. This will let us draw
 		// everything in one draw call.
-		if (draw_mode == GL_TRIANGLE_STRIP)
+		if (triangle_mode == vertex::TriangleIndexMode::STRIP)
 			extra_vertices = 2;
 	}
 
@@ -115,6 +113,12 @@ void NoneJoinPolyline::renderEdge(std::vector<Vector> &anchors, std::vector<Vect
                                 Vector &s, float &len_s, Vector &ns,
                                 const Vector &q, const Vector &r, float hw)
 {
+	//   ns1------ns2
+	//    |        |
+	//    q ------ r
+	//    |        |
+	// (-ns1)----(-ns2)
+
 	anchors.push_back(q);
 	anchors.push_back(q);
 	normals.push_back(ns);
@@ -126,8 +130,8 @@ void NoneJoinPolyline::renderEdge(std::vector<Vector> &anchors, std::vector<Vect
 
 	anchors.push_back(q);
 	anchors.push_back(q);
-	normals.push_back(-ns);
 	normals.push_back(ns);
+	normals.push_back(-ns);
 }
 
 
@@ -324,31 +328,36 @@ void NoneJoinPolyline::render_overdraw(const std::vector<Vector> &/*normals*/, f
 {
 	for (size_t i = 2; i + 3 < vertex_count; i += 4)
 	{
-		Vector s = vertices[i] - vertices[i+3];
-		Vector t = vertices[i] - vertices[i+1];
+		// v0-v2
+		// | / | <- main quad line
+		// v1-v3
+
+		Vector s = vertices[i+0] - vertices[i+2];
+		Vector t = vertices[i+0] - vertices[i+1];
 		s.normalize(pixel_size);
 		t.normalize(pixel_size);
 
 		const size_t k = 4 * (i - 2);
-		overdraw[k  ] = vertices[i];
-		overdraw[k+1] = vertices[i]   + s + t;
-		overdraw[k+2] = vertices[i+1] + s - t;
-		overdraw[k+3] = vertices[i+1];
+
+		overdraw[k+0] = vertices[i+0];
+		overdraw[k+1] = vertices[i+1];
+		overdraw[k+2] = vertices[i+0] + s + t;
+		overdraw[k+3] = vertices[i+1] + s - t;
 
 		overdraw[k+4] = vertices[i+1];
-		overdraw[k+5] = vertices[i+1] + s - t;
-		overdraw[k+6] = vertices[i+2] - s - t;
-		overdraw[k+7] = vertices[i+2];
-
-		overdraw[k+8]  = vertices[i+2];
-		overdraw[k+9]  = vertices[i+2] - s - t;
-		overdraw[k+10] = vertices[i+3] - s + t;
-		overdraw[k+11] = vertices[i+3];
-
-		overdraw[k+12] = vertices[i+3];
-		overdraw[k+13] = vertices[i+3] - s + t;
-		overdraw[k+14] = vertices[i]   + s + t;
-		overdraw[k+15] = vertices[i];
+		overdraw[k+5] = vertices[i+3];
+		overdraw[k+6] = vertices[i+1] + s - t;
+		overdraw[k+7] = vertices[i+3] - s - t;
+
+		overdraw[k+ 8] = vertices[i+3];
+		overdraw[k+ 9] = vertices[i+2];
+		overdraw[k+10] = vertices[i+3] - s - t;
+		overdraw[k+11] = vertices[i+2] - s + t;
+
+		overdraw[k+12] = vertices[i+2];
+		overdraw[k+13] = vertices[i+0];
+		overdraw[k+14] = vertices[i+2] - s + t;
+		overdraw[k+15] = vertices[i+0] + s + t;
 	}
 }
 
@@ -358,104 +367,50 @@ Polyline::~Polyline()
 		delete[] vertices;
 }
 
-void Polyline::draw()
+void Polyline::draw(love::graphics::Graphics *gfx)
 {
-	OpenGL::TempDebugGroup debuggroup("Line draw");
-
-	GLushort *indices = nullptr;
-	Color *colors = nullptr;
-
-	size_t total_vertex_count = vertex_count;
-	if (overdraw)
-		total_vertex_count = overdraw_vertex_start + overdraw_vertex_count;
-
-	// TODO: We should probably be using a reusable index buffer.
-	if (use_quad_indices)
-	{
-		size_t numindices = (total_vertex_count / 4) * 6;
-
-		try
-		{
-			indices = new GLushort[numindices];
-		}
-		catch (std::bad_alloc &)
-		{
-			throw love::Exception("Out of memory.");
-		}
-
-		// Fill the index array to make 2 triangles from each quad.
-		// NOTE: The triangle vertex ordering here is important!
-		for (size_t i = 0; i < numindices / 6; i++)
-		{
-			// First triangle.
-			indices[i * 6 + 0] = GLushort(i * 4 + 0);
-			indices[i * 6 + 1] = GLushort(i * 4 + 1);
-			indices[i * 6 + 2] = GLushort(i * 4 + 2);
-
-			// Second triangle.
-			indices[i * 6 + 3] = GLushort(i * 4 + 0);
-			indices[i * 6 + 4] = GLushort(i * 4 + 2);
-			indices[i * 6 + 5] = GLushort(i * 4 + 3);
-		}
-	}
-
-	gl.prepareDraw();
-
-	gl.bindTextureToUnit(gl.getDefaultTexture(), 0, false);
-	gl.bindBuffer(BUFFER_VERTEX, 0);
-
-	uint32 enabledattribs = ATTRIBFLAG_POS;
-
+	int total_vertex_count = (int) vertex_count;
 	if (overdraw)
-	{
-		// Prepare per-vertex colors. Set the core to white, and the overdraw
-		// line's colors to white on one side and transparent on the other.
-		colors = new Color[total_vertex_count];
-		memset(colors, 255, overdraw_vertex_start * sizeof(Color));
-		fill_color_array(colors + overdraw_vertex_start);
+		total_vertex_count = (int) (overdraw_vertex_start + overdraw_vertex_count);
 
-		glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, colors);
+	Graphics::StreamDrawRequest req;
+	req.formats[0] = vertex::CommonFormat::XYf;
+	req.formats[1] = vertex::CommonFormat::RGBAub;
+	req.indexMode = triangle_mode;
+	req.vertexCount = total_vertex_count;
 
-		enabledattribs |= ATTRIBFLAG_COLOR;
-	}
+	Graphics::StreamVertexData data = gfx->requestStreamDraw(req);
 
-	gl.useVertexAttribArrays(enabledattribs);
+	const Matrix4 &t = gfx->getTransform();
+	t.transform((Vector *) data.stream[0], vertices, total_vertex_count);
 
-	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, 0, vertices);
+	Color curcolor = toColor(gfx->getColor());
+	Color *colordata = (Color *) data.stream[1];
 
-	// Draw the core line and the overdraw in a single draw call. We can do this
-	// because the vertex array contains both the core line and the overdraw
-	// vertices.
-	if (use_quad_indices)
-	{
-		gl.bindBuffer(BUFFER_INDEX, 0);
-		gl.drawElements(draw_mode, (int) (total_vertex_count / 4) * 6, GL_UNSIGNED_SHORT, indices);
-	}
-	else
-		gl.drawArrays(draw_mode, 0, (int) total_vertex_count);
+	for (int i = 0; i < (int) vertex_count; i++)
+		colordata[i] = curcolor;
 
 	if (overdraw)
-		delete[] colors;
-
-	if (indices)
-		delete[] indices;
+		fill_color_array(curcolor, colordata + overdraw_vertex_start);
 }
 
-void Polyline::fill_color_array(Color *colors)
+void Polyline::fill_color_array(Color constant_color, Color *colors)
 {
 	for (size_t i = 0; i < overdraw_vertex_count; ++i)
 	{
-		// avoids branching. equiv to if (i%2 == 1) colors[i].a = 0;
-		colors[i] = {255, 255, 255, GLubyte(255 * ((i+1) % 2))};
+		Color c = constant_color;
+		c.a *= (i+1) % 2; // avoids branching. equiv to if (i%2 == 1) c.a = 0;
+		colors[i] = c;
 	}
 }
 
-void NoneJoinPolyline::fill_color_array(Color *colors)
+void NoneJoinPolyline::fill_color_array(Color constant_color, Color *colors)
 {
 	for (size_t i = 0; i < overdraw_vertex_count; ++i)
 	{
-		// if (i % 4 == 1 || i % 4 == 2) colors[i].a = 0
-		colors[i] = {255, 255, 255, GLubyte(255 * ((i+1) % 4 < 2))};
+		Color c = constant_color;
+		c.a *= (i & 3) < 2; // if (i % 4 == 2 || i % 4 == 3) c.a = 0
+		colors[i] = c;
 	}
 }
 

+ 26 - 15
src/modules/graphics/opengl/Polyline.h

@@ -24,9 +24,7 @@
 // LOVE
 #include "common/config.h"
 #include "common/Vector.h"
-
-// OpenGL
-#include "OpenGL.h"
+#include "graphics/vertex.h"
 
 // C++
 #include <vector>
@@ -36,6 +34,9 @@ namespace love
 {
 namespace graphics
 {
+
+class Graphics;
+
 namespace opengl
 {
 
@@ -46,15 +47,16 @@ namespace opengl
 class Polyline
 {
 public:
-	Polyline(GLenum mode = GL_TRIANGLE_STRIP, bool quadindices = false)
+
+	Polyline(vertex::TriangleIndexMode mode = vertex::TriangleIndexMode::STRIP)
 		: vertices(nullptr)
 		, overdraw(nullptr)
 		, vertex_count(0)
 		, overdraw_vertex_count(0)
-		, draw_mode(mode)
-		, use_quad_indices(quadindices)
+		, triangle_mode(mode)
 		, overdraw_vertex_start(0)
 	{}
+
 	virtual ~Polyline();
 
 	/**
@@ -69,12 +71,13 @@ public:
 
 	/** Draws the line on the screen
 	 */
-	void draw();
+	void draw(love::graphics::Graphics *gfx);
 
 protected:
+
 	virtual void calc_overdraw_vertex_count(bool is_looping);
 	virtual void render_overdraw(const std::vector<Vector> &normals, float pixel_size, bool is_looping);
-	virtual void fill_color_array(Color *colors);
+	virtual void fill_color_array(Color constant_color, Color *colors);
 
 	/** Calculate line boundary points.
 	 *
@@ -95,8 +98,7 @@ protected:
 	Vector *overdraw;
 	size_t vertex_count;
 	size_t overdraw_vertex_count;
-	GLenum draw_mode;
-	bool use_quad_indices;
+	vertex::TriangleIndexMode triangle_mode;
 	size_t overdraw_vertex_start;
 
 }; // Polyline
@@ -109,8 +111,9 @@ protected:
 class NoneJoinPolyline : public Polyline
 {
 public:
+
 	NoneJoinPolyline()
-		: Polyline(GL_TRIANGLES, true)
+		: Polyline(vertex::TriangleIndexMode::QUADS)
 	{}
 
 	void render(const float *vertices, size_t count, float halfwidth, float pixel_size, bool draw_overdraw)
@@ -131,13 +134,15 @@ public:
 	}
 
 protected:
+
 	virtual void calc_overdraw_vertex_count(bool is_looping);
 	virtual void render_overdraw(const std::vector<Vector> &normals, float pixel_size, bool is_looping);
-	virtual void fill_color_array(Color *colors);
+	virtual void fill_color_array(Color constant_color, Color *colors);
 	virtual void renderEdge(std::vector<Vector> &anchors, std::vector<Vector> &normals,
 	                        Vector &s, float &len_s, Vector &ns,
 	                        const Vector &q, const Vector &r, float hw);
-};
+
+}; // NoneJoinPolyline
 
 
 /**
@@ -147,16 +152,19 @@ protected:
 class MiterJoinPolyline : public Polyline
 {
 public:
+
 	void render(const float *vertices, size_t count, float halfwidth, float pixel_size, bool draw_overdraw)
 	{
 		Polyline::render(vertices, count, count, halfwidth, pixel_size, draw_overdraw);
 	}
 
 protected:
+
 	virtual void renderEdge(std::vector<Vector> &anchors, std::vector<Vector> &normals,
 	                        Vector &s, float &len_s, Vector &ns,
 	                        const Vector &q, const Vector &r, float hw);
-};
+
+}; // MiterJoinPolyline
 
 
 /**
@@ -166,16 +174,19 @@ protected:
 class BevelJoinPolyline : public Polyline
 {
 public:
+
 	void render(const float *vertices, size_t count, float halfwidth, float pixel_size, bool draw_overdraw)
 	{
 		Polyline::render(vertices, count, 2 * count - 4, halfwidth, pixel_size, draw_overdraw);
 	}
 
 protected:
+
 	virtual void renderEdge(std::vector<Vector> &anchors, std::vector<Vector> &normals,
 	                        Vector &s, float &len_s, Vector &ns,
 	                        const Vector &q, const Vector &r, float hw);
-};
+
+}; // BevelJoinPolyline
 
 } // opengl
 } // graphics

+ 21 - 4
src/modules/graphics/opengl/Shader.cpp

@@ -587,6 +587,9 @@ const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const
 
 void Shader::updateUniform(const UniformInfo *info, int count, bool internalUpdate)
 {
+	if (!internalUpdate)
+		flushStreamDraws();
+
 	TemporaryAttacher attacher(this, !internalUpdate);
 
 	int location = info->location;
@@ -679,6 +682,9 @@ void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count
 	if (info->baseType != UNIFORM_SAMPLER)
 		return;
 
+	if (!internalUpdate)
+		flushStreamDraws();
+
 	count = std::min(count, info->count);
 	bool updateuniform = false;
 
@@ -713,7 +719,7 @@ void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count
 
 		if (textures[i] != nullptr)
 		{
-			GLuint gltex = *(GLuint *) textures[i]->getHandle();
+			GLuint gltex = (GLuint) textures[i]->getHandle();
 
 			gl.bindTextureToUnit(gltex, texunit, false);
 
@@ -722,13 +728,23 @@ void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count
 		}
 		else
 		{
-			gl.bindTextureToUnit(0, texunit, false);
+			gl.bindTextureToUnit((GLuint) 0, texunit, false);
 			textureUnits[texunit].texture = 0;
 			textureUnits[texunit].active = false;
 		}
 	}
 }
 
+void Shader::flushStreamDraws() const
+{
+	if (current == this)
+	{
+		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
+		if (gfx != nullptr)
+			gfx->flushStreamDraws();
+	}
+}
+
 bool Shader::hasUniform(const std::string &name) const
 {
 	return uniforms.find(name) != uniforms.end();
@@ -871,9 +887,10 @@ void Shader::checkSetBuiltinUniforms()
 	{
 		checkSetPointSize(gl.getPointSize());
 
-		const Matrix4 &curxform = gl.matrices.transform.back();
-		const Matrix4 &curproj = gl.matrices.projection;
+		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
+		const Matrix4 &curproj = gfx->getProjection();
 
+		const Matrix4 &curxform = gfx->getTransform();
 		TemporaryAttacher attacher(this, true);
 
 		bool tpmatrixneedsupdate = false;

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

@@ -215,6 +215,8 @@ private:
 
 	int getFreeTextureUnits(int count);
 
+	void flushStreamDraws() const;
+
 	// Get any warnings or errors generated only by the shader program object.
 	std::string getProgramWarnings() const;
 

+ 12 - 18
src/modules/graphics/opengl/SpriteBatch.cpp

@@ -27,6 +27,7 @@
 // LOVE
 #include "GLBuffer.h"
 #include "graphics/Texture.h"
+#include "graphics/Graphics.h"
 
 // C++
 #include <algorithm>
@@ -240,21 +241,22 @@ bool SpriteBatch::getDrawRange(int &start, int &count) const
 	return true;
 }
 
-void SpriteBatch::draw(const Matrix4 &m)
+void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
 {
 	const size_t pos_offset   = offsetof(Vertex, x);
 	const size_t texel_offset = offsetof(Vertex, s);
-	const size_t color_offset = offsetof(Vertex, r);
+	const size_t color_offset = offsetof(Vertex, color.r);
 
 	if (next == 0)
 		return;
 
+	gfx->flushStreamDraws();
+
 	OpenGL::TempDebugGroup debuggroup("SpriteBatch draw");
 
-	OpenGL::TempTransform transform(gl);
-	transform.get() *= m;
+	Graphics::TempTransform transform(gfx, m);
 
-	gl.bindTextureToUnit(*(GLuint *) texture->getHandle(), 0, false);
+	gl.bindTextureToUnit(texture, 0, false);
 
 	uint32 enabledattribs = ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD;
 
@@ -315,8 +317,11 @@ void SpriteBatch::addv(const Vertex *v, const Matrix4 &m, int index)
 
 	m.transform(sprite, sprite, 4);
 
-	if (color)
-		setColorv(sprite, *color);
+	if (color != nullptr)
+	{
+		for (int i = 0; i < 4; i++)
+			sprite[i].color = *color;
+	}
 
 	// Always keep the VBO mapped when adding data for now (it'll be unmapped
 	// on draw.)
@@ -325,17 +330,6 @@ void SpriteBatch::addv(const Vertex *v, const Matrix4 &m, int index)
 	array_buf->fill(index * sprite_size, sprite_size, sprite);
 }
 
-void SpriteBatch::setColorv(Vertex *v, const Color &color)
-{
-	for (size_t i = 0; i < 4; ++i)
-	{
-		v[i].r = color.r;
-		v[i].g = color.g;
-		v[i].b = color.b;
-		v[i].a = color.a;
-	}
-}
-
 } // opengl
 } // graphics
 } // love

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

@@ -108,7 +108,7 @@ public:
 	bool getDrawRange(int &start, int &count) const;
 
 	// Implements Drawable.
-	void draw(const Matrix4 &m) override;
+	void draw(Graphics *gfx, const Matrix4 &m) override;
 
 private:
 
@@ -126,15 +126,6 @@ private:
 
 	void addv(const Vertex *v, const Matrix4 &m, int index);
 
-	/**
-	 * Set the color for vertices.
-	 *
-	 * @param v The vertices to set the color for. Must be an array of
-	 *          of size 4.
-	 * @param color The color to assign to each vertex.
-	 */
-	void setColorv(Vertex *v, const Color &color);
-
 	StrongRef<Texture> texture;
 
 	// Max number of sprites in the batch.

+ 11 - 11
src/modules/graphics/opengl/Text.cpp

@@ -20,6 +20,7 @@
 
 #include "Text.h"
 #include "common/Matrix.h"
+#include "graphics/Graphics.h"
 
 #include <algorithm>
 
@@ -207,11 +208,13 @@ void Text::clear()
 	vert_offset = 0;
 }
 
-void Text::draw(const Matrix4 &m)
+void Text::draw(Graphics *gfx, const Matrix4 &m)
 {
 	if (vbo == nullptr || draw_commands.empty())
 		return;
 
+	gfx->flushStreamDraws();
+
 	OpenGL::TempDebugGroup debuggroup("Text object draw");
 
 	// Re-generate the text if the Font's texture cache was invalidated.
@@ -223,18 +226,15 @@ void Text::draw(const Matrix4 &m)
 	const size_t color_offset = offsetof(Font::GlyphVertex, color.r);
 	const size_t stride = sizeof(Font::GlyphVertex);
 
-	OpenGL::TempTransform transform(gl);
-	transform.get() *= m;
+	Graphics::TempTransform transform(gfx, m);
 
-	{
-		vbo->bind();
-		vbo->unmap(); // Make sure all pending data is flushed to the GPU.
+	vbo->bind();
+	vbo->unmap(); // Make sure all pending data is flushed to the GPU.
 
-		// Font::drawVertices expects AttribPointer calls to be done already.
-		glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, vbo->getPointer(pos_offset));
-		glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_UNSIGNED_SHORT, GL_TRUE, stride, vbo->getPointer(tex_offset));
-		glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, vbo->getPointer(color_offset));
-	}
+	// Font::drawVertices expects AttribPointer calls to be done already.
+	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, vbo->getPointer(pos_offset));
+	glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_UNSIGNED_SHORT, GL_TRUE, stride, vbo->getPointer(tex_offset));
+	glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, vbo->getPointer(color_offset));
 
 	gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD | ATTRIBFLAG_COLOR);
 

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

@@ -52,7 +52,7 @@ public:
 	void clear();
 
 	// Implements Drawable.
-	void draw(const Matrix4 &m) override;
+	void draw(Graphics *gfx, const Matrix4 &m) override;
 
 	void setFont(Font *f);
 	Font *getFont() const;

+ 6 - 4
src/modules/graphics/opengl/Video.cpp

@@ -22,6 +22,7 @@
 
 // LOVE
 #include "Shader.h"
+#include "graphics/Graphics.h"
 
 namespace love
 {
@@ -41,7 +42,7 @@ Video::Video(love::video::VideoStream *stream)
 	stream->fillBackBuffer();
 
 	for (int i = 0; i < 4; i++)
-		vertices[i].r = vertices[i].g = vertices[i].b = vertices[i].a = 255;
+		vertices[i].color = Color(255, 255, 255, 255);
 
 	// Vertices are ordered for use with triangle strips:
 	// 0----2
@@ -116,10 +117,12 @@ love::video::VideoStream *Video::getStream()
 	return stream;
 }
 
-void Video::draw(const Matrix4 &m)
+void Video::draw(Graphics *gfx, const Matrix4 &m)
 {
 	update();
 
+	gfx->flushStreamDraws();
+
 	Shader *shader = Shader::current;
 	bool defaultShader = (shader == Shader::defaultShader);
 	if (defaultShader)
@@ -131,8 +134,7 @@ void Video::draw(const Matrix4 &m)
 
 	shader->setVideoTextures(textures[0], textures[1], textures[2]);
 
-	OpenGL::TempTransform transform(gl);
-	transform.get() *= m;
+	Graphics::TempTransform transform(gfx, m);
 
 	gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD);
 

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

@@ -50,7 +50,7 @@ public:
 	void unloadVolatile() override;
 
 	// Drawable
-	void draw(const Matrix4 &m) override;
+	void draw(Graphics *gfx, const Matrix4 &m) override;
 
 	love::video::VideoStream *getStream();
 

+ 31 - 31
src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -826,10 +826,10 @@ static Mesh *newStandardMesh(lua_State *L)
 			v.s = (float) luaL_optnumber(L, -6, 0.0);
 			v.t = (float) luaL_optnumber(L, -5, 0.0);
 
-			v.r = (unsigned char) (luaL_optnumber(L, -4, 1.0) * 255.0);
-			v.g = (unsigned char) (luaL_optnumber(L, -3, 1.0) * 255.0);
-			v.b = (unsigned char) (luaL_optnumber(L, -2, 1.0) * 255.0);
-			v.a = (unsigned char) (luaL_optnumber(L, -1, 1.0) * 255.0);
+			v.color.r = (unsigned char) (luaL_optnumber(L, -4, 1.0) * 255.0);
+			v.color.g = (unsigned char) (luaL_optnumber(L, -3, 1.0) * 255.0);
+			v.color.b = (unsigned char) (luaL_optnumber(L, -2, 1.0) * 255.0);
+			v.color.a = (unsigned char) (luaL_optnumber(L, -1, 1.0) * 255.0);
 
 			lua_pop(L, 9);
 			vertices.push_back(v);
@@ -1544,7 +1544,7 @@ int w_draw(lua_State *L)
 		math::Transform *tf = luax_totype<math::Transform>(L, startidx);
 		luax_catchexcept(L, [&]() {
 			if (texture && quad)
-				instance()->drawq(texture, quad, tf->getMatrix());
+				instance()->draw(texture, quad, tf->getMatrix());
 			else
 				instance()->draw(drawable, tf->getMatrix());
 		});
@@ -1565,7 +1565,7 @@ int w_draw(lua_State *L)
 
 		luax_catchexcept(L, [&]() {
 			if (texture && quad)
-				instance()->drawq(texture, quad, m);
+				instance()->draw(texture, quad, m);
 			else if (drawable)
 				instance()->draw(drawable, m);
 		});
@@ -1673,12 +1673,18 @@ int w_points(lua_State *L)
 		numpoints = args;
 
 	float *coords = nullptr;
-	uint8 *colors = nullptr;
-
-	coords = new float[numpoints * 2];
+	Colorf *colors = nullptr;
 
 	if (is_table_of_tables)
-		colors = new uint8[numpoints * 4];
+	{
+		size_t datasize = (sizeof(float) * 2 + sizeof(Colorf)) * numpoints;
+		uint8 *data = instance()->getScratchBuffer<uint8>(datasize);
+
+		coords = (float *) data;
+		colors = (Colorf *) (data + sizeof(float) * numpoints * 2);
+	}
+	else
+		coords = instance()->getScratchBuffer<float>(numpoints * 2);
 
 	if (is_table)
 	{
@@ -1694,10 +1700,10 @@ int w_points(lua_State *L)
 				coords[i * 2 + 0] = luax_tofloat(L, -6);
 				coords[i * 2 + 1] = luax_tofloat(L, -5);
 
-				colors[i * 4 + 0] = (uint8) (luaL_optnumber(L, -4, 1.0) * 255.0);
-				colors[i * 4 + 1] = (uint8) (luaL_optnumber(L, -3, 1.0) * 255.0);
-				colors[i * 4 + 2] = (uint8) (luaL_optnumber(L, -2, 1.0) * 255.0);
-				colors[i * 4 + 3] = (uint8) (luaL_optnumber(L, -1, 1.0) * 255.0);
+				colors[i].r = luaL_optnumber(L, -4, 1.0);
+				colors[i].g = luaL_optnumber(L, -3, 1.0);
+				colors[i].b = luaL_optnumber(L, -2, 1.0);
+				colors[i].a = luaL_optnumber(L, -1, 1.0);
 
 				lua_pop(L, 7);
 			}
@@ -1719,15 +1725,7 @@ int w_points(lua_State *L)
 			coords[i] = luax_tofloat(L, i + 1);
 	}
 
-	luax_catchexcept(L,
-		[&](){ instance()->points(coords, colors, numpoints); },
-		[&](bool) {
-			delete[] coords;
-			if (colors)
-				delete[] colors;
-		}
-	);
-
+	luax_catchexcept(L, [&](){ instance()->points(coords, colors, numpoints); });
 	return 0;
 }
 
@@ -1746,7 +1744,7 @@ int w_line(lua_State *L)
 	else if (args < 4)
 		return luaL_error(L, "Need at least two vertices to draw a line");
 
-	float *coords = new float[args];
+	float *coords = instance()->getScratchBuffer<float>(args);
 	if (is_table)
 	{
 		for (int i = 0; i < args; ++i)
@@ -1894,7 +1892,6 @@ int w_polygon(lua_State *L)
 		return luaL_error(L, "Invalid draw mode: %s", str);
 
 	bool is_table = false;
-	float *coords;
 	if (args == 1 && lua_istable(L, 2))
 	{
 		args = (int) luax_objlen(L, 2);
@@ -1907,7 +1904,7 @@ int w_polygon(lua_State *L)
 		return luaL_error(L, "Need at least three vertices to draw a polygon");
 
 	// fetch coords
-	coords = new float[args + 2];
+	float *coords = instance()->getScratchBuffer<float>(args + 2);
 	if (is_table)
 	{
 		for (int i = 0; i < args; ++i)
@@ -1927,11 +1924,13 @@ int w_polygon(lua_State *L)
 	coords[args]   = coords[0];
 	coords[args+1] = coords[1];
 
-	luax_catchexcept(L,
-		[&](){ instance()->polygon(mode, coords, args+2); },
-		[&](bool) { delete[] coords; }
-	);
+	luax_catchexcept(L, [&](){ instance()->polygon(mode, coords, args+2); });
+	return 0;
+}
 
+int w_flush(lua_State *)
+{
+	instance()->flushStreamDraws();
 	return 0;
 }
 
@@ -2123,9 +2122,10 @@ static const luaL_Reg functions[] =
 	{ "circle", w_circle },
 	{ "ellipse", w_ellipse },
 	{ "arc", w_arc },
-
 	{ "polygon", w_polygon },
 
+	{ "flush", w_flush },
+
 	{ "push", w_push },
 	{ "pop", w_pop },
 	{ "rotate", w_rotate },

+ 134 - 0
src/modules/graphics/vertex.cpp

@@ -0,0 +1,134 @@
+/**
+ * Copyright (c) 2006-2016 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#include "vertex.h"
+
+namespace love
+{
+namespace graphics
+{
+namespace vertex
+{
+
+static_assert(sizeof(Color) == 4, "sizeof(Color) incorrect!");
+static_assert(sizeof(Vertex) == sizeof(float)*2 + sizeof(float)*2 + sizeof(Color), "sizeof(Vertex) incorrect!");
+static_assert(sizeof(XYf_STf) == sizeof(float)*2 + sizeof(float)*2, "sizeof(XYf_STf) incorrect!");
+static_assert(sizeof(XYf_STf_RGBAub) == sizeof(float)*2 + sizeof(float)*2 + sizeof(Color), "sizeof(XYf_STf_RGBAub) incorrect!");
+static_assert(sizeof(XYf_STus_RGBAub) == sizeof(float)*2 + sizeof(uint16)*2 + sizeof(Color), "sizeof(XYf_STus_RGBAub) incorrect!");
+
+size_t getFormatStride(CommonFormat format)
+{
+	switch (format)
+	{
+	case CommonFormat::NONE:
+		return 0;
+	case CommonFormat::XYf:
+		return sizeof(float) * 2;
+	case CommonFormat::RGBAub:
+		return sizeof(uint8) * 4;
+	case CommonFormat::XYf_STf:
+		return sizeof(XYf_STf);
+	case CommonFormat::XYf_STf_RGBAub:
+		return sizeof(XYf_STf_RGBAub);
+	case CommonFormat::XYf_STus_RGBAub:
+		return sizeof(XYf_STus_RGBAub);
+	}
+}
+
+int getIndexCount(TriangleIndexMode mode, int vertexCount)
+{
+	switch (mode)
+	{
+	case TriangleIndexMode::NONE:
+		return 0;
+	case TriangleIndexMode::STRIP:
+	case TriangleIndexMode::FAN:
+		return 3 * (vertexCount - 2);
+	case TriangleIndexMode::QUADS:
+		return vertexCount * 6 / 4;
+	}
+}
+
+template <typename T>
+static void fillIndicesT(TriangleIndexMode mode, T vertexStart, T vertexCount, T *indices)
+{
+	switch (mode)
+	{
+	case TriangleIndexMode::NONE:
+		break;
+	case TriangleIndexMode::STRIP:
+		{
+			int i = 0;
+			for (T index = 0; index < vertexCount - 2; index++)
+			{
+				indices[i++] = vertexStart + index;
+				indices[i++] = vertexStart + index + 1 + (index & 1);
+				indices[i++] = vertexStart + index + 2 - (index & 1);
+			}
+		}
+		break;
+	case TriangleIndexMode::FAN:
+		{
+			int i = 0;
+			for (T index = 2; index < vertexCount; index++)
+			{
+				indices[i++] = vertexStart;
+				indices[i++] = vertexStart + index - 1;
+				indices[i++] = vertexStart + index;
+			}
+		}
+		break;
+	case TriangleIndexMode::QUADS:
+		{
+			// 0---2
+			// | / |
+			// 1---3
+			int count = vertexCount / 4;
+			for (int i = 0; i < count; i++)
+			{
+				int ii = i * 6;
+				T vi = T(vertexStart + i * 4);
+
+				indices[ii + 0] = vi + 0;
+				indices[ii + 1] = vi + 1;
+				indices[ii + 2] = vi + 2;
+
+				indices[ii + 3] = vi + 2;
+				indices[ii + 4] = vi + 1;
+				indices[ii + 5] = vi + 3;
+			}
+		}
+		break;
+	}
+}
+
+void fillIndices(TriangleIndexMode mode, uint16 vertexStart, uint16 vertexCount, uint16 *indices)
+{
+	fillIndicesT(mode, vertexStart, vertexCount, indices);
+}
+
+void fillIndices(TriangleIndexMode mode, uint32 vertexStart, uint32 vertexCount, uint32 *indices)
+{
+	fillIndicesT(mode, vertexStart, vertexCount, indices);
+}
+
+} // vertex
+} // graphics
+} // love

+ 105 - 0
src/modules/graphics/vertex.h

@@ -0,0 +1,105 @@
+/**
+ * Copyright (c) 2006-2016 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#pragma once
+
+// LOVE
+#include "common/int.h"
+#include "Color.h"
+
+// C
+#include <stddef.h>
+
+namespace love
+{
+namespace graphics
+{
+
+enum BufferType
+{
+	BUFFER_VERTEX = 0,
+	BUFFER_INDEX,
+	BUFFER_MAX_ENUM
+};
+
+struct Vertex
+{
+	float x, y;
+	float s, t;
+	Color color;
+};
+
+namespace vertex
+{
+
+enum class PrimitiveMode
+{
+	TRIANGLES,
+	POINTS,
+};
+
+enum class TriangleIndexMode
+{
+	NONE,
+	STRIP,
+	FAN,
+	QUADS,
+};
+
+enum class CommonFormat
+{
+	NONE,
+	XYf,
+	RGBAub,
+	XYf_STf,
+	XYf_STf_RGBAub,
+	XYf_STus_RGBAub,
+};
+
+struct XYf_STf
+{
+	float x, y;
+	float s, t;
+};
+
+struct XYf_STf_RGBAub
+{
+	float x, y;
+	float s, t;
+	Color color;
+};
+
+struct XYf_STus_RGBAub
+{
+	float x, y;
+	uint16 s, t;
+	Color color;
+};
+
+size_t getFormatStride(CommonFormat format);
+
+int getIndexCount(TriangleIndexMode mode, int vertexCount);
+
+void fillIndices(TriangleIndexMode mode, uint16 vertexStart, uint16 vertexCount, uint16 *indices);
+void fillIndices(TriangleIndexMode mode, uint32 vertexStart, uint32 vertexCount, uint32 *indices);
+
+} // vertex
+} // graphics
+} // love

+ 3 - 6
src/scripts/nogame.lua

@@ -738,7 +738,7 @@ function love.nogame()
 		local SIZE_Y = math.floor(wh / 32 + 2)
 		local SIZE = SIZE_X * SIZE_Y
 
-		self.batch = love.graphics.newSpriteBatch(mosaic_image, SIZE, "stream")
+		self.image = mosaic_image
 		self.pieces = {}
 		self.color_t = 1
 		self.generation = 1
@@ -915,8 +915,6 @@ function love.nogame()
 	end
 
 	function Mosaic:draw()
-		self.batch:clear()
-		love.graphics.setColor(1, 1, 1, 0.25)
 		for idx,piece in ipairs(self.pieces) do
 			local ct = 1 - self.color_t
 			local c0 = piece.color.prev
@@ -925,11 +923,10 @@ function love.nogame()
 			local g = easeOut(ct, c0[2], c1[2] - c0[2], 1)
 			local b = easeOut(ct, c0[3], c1[3] - c0[3], 1)
 
-			self.batch:setColor(r, g, b)
-			self.batch:add(piece.quad, piece.x, piece.y, piece.r, 1, 1, 16, 16)
+			love.graphics.setColor(r, g, b)
+			love.graphics.draw(self.image, piece.quad, piece.x, piece.y, piece.r, 1, 1, 16, 16)
 		end
 		love.graphics.setColor(1, 1, 1, 1)
-		love.graphics.draw(self.batch, 0, 0)
 	end
 
 	function love.load()

+ 9 - 19
src/scripts/nogame.lua.h

@@ -3148,11 +3148,8 @@ const unsigned char nogame_lua[] =
 	0x2b, 0x20, 0x32, 0x29, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x53, 0x49, 0x5a, 0x45, 0x20, 0x3d, 0x20, 0x53, 0x49, 0x5a, 
 	0x45, 0x5f, 0x58, 0x20, 0x2a, 0x20, 0x53, 0x49, 0x5a, 0x45, 0x5f, 0x59, 0x0a,
-	0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x62, 0x61, 0x74, 0x63, 0x68, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 
-	0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x53, 0x70, 0x72, 0x69, 
-	0x74, 0x65, 0x42, 0x61, 0x74, 0x63, 0x68, 0x28, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x61, 
-	0x67, 0x65, 0x2c, 0x20, 0x53, 0x49, 0x5a, 0x45, 0x2c, 0x20, 0x22, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x22, 
-	0x29, 0x0a,
+	0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x20, 0x3d, 0x20, 0x6d, 0x6f, 0x73, 
+	0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x0a,
 	0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x70, 0x69, 0x65, 0x63, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x7b, 0x7d, 0x0a,
 	0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x74, 0x20, 0x3d, 0x20, 0x31, 0x0a,
 	0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 
@@ -3527,11 +3524,6 @@ const unsigned char nogame_lua[] =
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x4d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x3a, 0x64, 
 	0x72, 0x61, 0x77, 0x28, 0x29, 0x0a,
-	0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x62, 0x61, 0x74, 0x63, 0x68, 0x3a, 0x63, 0x6c, 0x65, 0x61, 0x72, 
-	0x28, 0x29, 0x0a,
-	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 
-	0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x30, 0x2e, 
-	0x32, 0x35, 0x29, 0x0a,
 	0x09, 0x09, 0x66, 0x6f, 0x72, 0x20, 0x69, 0x64, 0x78, 0x2c, 0x70, 0x69, 0x65, 0x63, 0x65, 0x20, 0x69, 0x6e, 
 	0x20, 0x69, 0x70, 0x61, 0x69, 0x72, 0x73, 0x28, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x70, 0x69, 0x65, 0x63, 0x65, 
 	0x73, 0x29, 0x20, 0x64, 0x6f, 0x0a,
@@ -3550,18 +3542,16 @@ const unsigned char nogame_lua[] =
 	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x62, 0x20, 0x3d, 0x20, 0x65, 0x61, 0x73, 0x65, 0x4f, 
 	0x75, 0x74, 0x28, 0x63, 0x74, 0x2c, 0x20, 0x63, 0x30, 0x5b, 0x33, 0x5d, 0x2c, 0x20, 0x63, 0x31, 0x5b, 0x33, 
 	0x5d, 0x20, 0x2d, 0x20, 0x63, 0x30, 0x5b, 0x33, 0x5d, 0x2c, 0x20, 0x31, 0x29, 0x0a,
-	0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x62, 0x61, 0x74, 0x63, 0x68, 0x3a, 0x73, 0x65, 0x74, 0x43, 
-	0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x72, 0x2c, 0x20, 0x67, 0x2c, 0x20, 0x62, 0x29, 0x0a,
-	0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x62, 0x61, 0x74, 0x63, 0x68, 0x3a, 0x61, 0x64, 0x64, 0x28, 
-	0x70, 0x69, 0x65, 0x63, 0x65, 0x2e, 0x71, 0x75, 0x61, 0x64, 0x2c, 0x20, 0x70, 0x69, 0x65, 0x63, 0x65, 0x2e, 
-	0x78, 0x2c, 0x20, 0x70, 0x69, 0x65, 0x63, 0x65, 0x2e, 0x79, 0x2c, 0x20, 0x70, 0x69, 0x65, 0x63, 0x65, 0x2e, 
-	0x72, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x36, 0x2c, 0x20, 0x31, 0x36, 0x29, 0x0a,
+	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 
+	0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x72, 0x2c, 0x20, 0x67, 0x2c, 0x20, 0x62, 0x29, 0x0a,
+	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x64, 
+	0x72, 0x61, 0x77, 0x28, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x70, 0x69, 
+	0x65, 0x63, 0x65, 0x2e, 0x71, 0x75, 0x61, 0x64, 0x2c, 0x20, 0x70, 0x69, 0x65, 0x63, 0x65, 0x2e, 0x78, 0x2c, 
+	0x20, 0x70, 0x69, 0x65, 0x63, 0x65, 0x2e, 0x79, 0x2c, 0x20, 0x70, 0x69, 0x65, 0x63, 0x65, 0x2e, 0x72, 0x2c, 
+	0x20, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x36, 0x2c, 0x20, 0x31, 0x36, 0x29, 0x0a,
 	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 
 	0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x29, 0x0a,
-	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x64, 0x72, 
-	0x61, 0x77, 0x28, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x62, 0x61, 0x74, 0x63, 0x68, 0x2c, 0x20, 0x30, 0x2c, 0x20, 
-	0x30, 0x29, 0x0a,
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6c, 0x6f, 0x61, 
 	0x64, 0x28, 0x29, 0x0a,