Browse Source

love.graphics: move more platform-independent code out of the opengl backend

--HG--
branch : minor
Alex Szpakowski 8 years ago
parent
commit
864147c38c

+ 1 - 1
src/modules/graphics/Buffer.cpp

@@ -151,7 +151,7 @@ IndexDataType QuadIndices::getType(size_t s) const
 	return vertex::getIndexDataTypeFromMax(getIndexCount(s));
 }
 
-size_t QuadIndices::getElementSize()
+size_t QuadIndices::getElementSize() const
 {
 	return elementSize;
 }

+ 1 - 1
src/modules/graphics/Buffer.h

@@ -238,7 +238,7 @@ public:
 	 * Can be used with getPointer to calculate an offset into the array based
 	 * on a number of elements.
 	 **/
-	size_t getElementSize();
+	size_t getElementSize() const;
 
 	/**
 	 * Returns the pointer to the Buffer.

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

@@ -430,6 +430,128 @@ void Graphics::setCanvas(const RenderTargetsStrongRef &rts)
 	return setCanvas(targets);
 }
 
+void Graphics::setCanvas(const RenderTargets &rts)
+{
+	DisplayState &state = states.back();
+	int ncanvases = (int) rts.colors.size();
+
+	if (ncanvases == 0 && rts.depthStencil.canvas == nullptr)
+		return setCanvas();
+	else if (ncanvases == 0)
+		throw love::Exception("At least one color render target is required when using a custom depth/stencil buffer.");
+
+	const auto &prevRTs = state.renderTargets;
+
+	if (ncanvases == (int) prevRTs.colors.size())
+	{
+		bool modified = false;
+
+		for (int i = 0; i < ncanvases; i++)
+		{
+			if (rts.colors[i].canvas != prevRTs.colors[i].canvas.get()
+				|| rts.colors[i].slice != prevRTs.colors[i].slice
+				|| rts.colors[i].mipmap != prevRTs.colors[i].mipmap)
+			{
+				modified = true;
+				break;
+			}
+		}
+
+		if (!modified && (rts.depthStencil.canvas != prevRTs.depthStencil.canvas
+						  || rts.depthStencil.slice != prevRTs.depthStencil.slice
+						  || rts.depthStencil.mipmap != prevRTs.depthStencil.mipmap))
+		{
+			modified = true;
+		}
+
+		if (rts.temporaryRTFlags != prevRTs.temporaryRTFlags)
+			modified = true;
+
+		if (!modified)
+			return;
+	}
+
+	if (ncanvases > capabilities.limits[LIMIT_MULTI_CANVAS])
+		throw love::Exception("This system can't simultaneously render to %d canvases.", ncanvases);
+
+	love::graphics::Canvas *firstcanvas = rts.colors[0].canvas;
+
+	bool multiformatsupported = capabilities.features[FEATURE_MULTI_CANVAS_FORMATS];
+	PixelFormat firstformat = firstcanvas->getPixelFormat();
+
+	if (isPixelFormatDepthStencil(firstformat))
+		throw love::Exception("Depth/stencil format Canvases must be used with the 'depthstencil' field of the table passed into setCanvas.");
+
+	if (rts.colors[0].mipmap < 0 || rts.colors[0].mipmap >= firstcanvas->getMipmapCount())
+		throw love::Exception("Invalid mipmap level %d.", rts.colors[0].mipmap + 1);
+
+	bool hasSRGBcanvas = firstformat == PIXELFORMAT_sRGBA8;
+	int pixelw = firstcanvas->getPixelWidth(rts.colors[0].mipmap);
+	int pixelh = firstcanvas->getPixelHeight(rts.colors[0].mipmap);
+
+	for (int i = 1; i < ncanvases; i++)
+	{
+		love::graphics::Canvas *c = rts.colors[i].canvas;
+		PixelFormat format = c->getPixelFormat();
+		int mip = rts.colors[i].mipmap;
+
+		if (mip < 0 || mip >= c->getMipmapCount())
+			throw love::Exception("Invalid mipmap level %d.", mip + 1);
+
+		if (c->getPixelWidth(mip) != pixelw || c->getPixelHeight(mip) != pixelh)
+			throw love::Exception("All canvases must have the same pixel dimensions.");
+
+		if (!multiformatsupported && format != firstformat)
+			throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats.");
+
+		if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA())
+			throw love::Exception("All Canvases must have the same MSAA value.");
+
+		if (isPixelFormatDepthStencil(format))
+			throw love::Exception("Depth/stencil format Canvases must be used with the 'depthstencil' field of the table passed into setCanvas.");
+
+		if (format == PIXELFORMAT_sRGBA8)
+			hasSRGBcanvas = true;
+	}
+
+	if (rts.depthStencil.canvas != nullptr)
+	{
+		love::graphics::Canvas *c = rts.depthStencil.canvas;
+		int mip = rts.depthStencil.mipmap;
+
+		if (!isPixelFormatDepthStencil(c->getPixelFormat()))
+			throw love::Exception("Only depth/stencil format Canvases can be used with the 'depthstencil' field of the table passed into setCanvas.");
+
+		if (c->getPixelWidth(mip) != pixelw || c->getPixelHeight(mip) != pixelh)
+			throw love::Exception("All canvases must have the same pixel dimensions.");
+
+		if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA())
+			throw love::Exception("All Canvases must have the same MSAA value.");
+
+		if (mip < 0 || mip >= c->getMipmapCount())
+			throw love::Exception("Invalid mipmap level %d.", mip + 1);
+	}
+
+	int w = firstcanvas->getWidth(rts.colors[0].mipmap);
+	int h = firstcanvas->getHeight(rts.colors[0].mipmap);
+
+	flushStreamDraws();
+	setCanvasInternal(rts, w, h, pixelw, pixelh, hasSRGBcanvas);
+
+	RenderTargetsStrongRef refs;
+	refs.colors.reserve(rts.colors.size());
+
+	for (auto c : rts.colors)
+		refs.colors.emplace_back(c.canvas, c.slice);
+
+	refs.depthStencil = RenderTargetStrongRef(rts.depthStencil.canvas, rts.depthStencil.slice);
+	refs.temporaryRTFlags = rts.temporaryRTFlags;
+
+	std::swap(state.renderTargets, refs);
+
+	canvasSwitchCount++;
+}
+
 Graphics::RenderTargets Graphics::getCanvas() const
 {
 	const auto &curRTs = states.back().renderTargets;

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

@@ -478,7 +478,7 @@ public:
 	Shader *getShader() const;
 
 	void setCanvas(RenderTarget rt, uint32 temporaryRTFlags);
-	virtual void setCanvas(const RenderTargets &rts) = 0;
+	void setCanvas(const RenderTargets &rts);
 	void setCanvas(const RenderTargetsStrongRef &rts);
 	virtual void setCanvas() = 0;
 
@@ -861,6 +861,8 @@ protected:
 
 	virtual StreamBuffer *newStreamBuffer(BufferType type, size_t size) = 0;
 
+	virtual void setCanvasInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBcanvas) = 0;
+
 	virtual void initCapabilities() = 0;
 	virtual void getAPIStats(int &drawcalls, int &shaderswitches) const = 0;
 

+ 78 - 1
src/modules/graphics/Mesh.cpp

@@ -564,11 +564,88 @@ bool Mesh::getDrawRange(int &start, int &count) const
 	return true;
 }
 
-void Mesh::draw(love::graphics::Graphics *gfx, const love::Matrix4 &m)
+void Mesh::draw(Graphics *gfx, const love::Matrix4 &m)
 {
 	drawInstanced(gfx, m, 1);
 }
 
+void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount)
+{
+	if (vertexCount <= 0 || instancecount <= 0)
+		return;
+
+	if (instancecount > 1 && !gfx->getCapabilities().features[Graphics::FEATURE_INSTANCING])
+		throw love::Exception("Instancing is not supported on this system.");
+
+	gfx->flushStreamDraws();
+
+	if (Shader::isDefaultActive())
+		Shader::attachDefault(Shader::STANDARD_DEFAULT);
+
+	if (Shader::current && texture.get())
+		Shader::current->checkMainTexture(texture);
+
+	uint32 enabledattribs = 0;
+	uint32 instancedattribs = 0;
+
+	for (const auto &attrib : attachedAttributes)
+	{
+		if (!attrib.second.enabled)
+			continue;
+
+		love::graphics::Mesh *mesh = attrib.second.mesh;
+		int location = mesh->bindAttributeToShaderInput(attrib.second.index, attrib.first);
+
+		if (location >= 0)
+		{
+			uint32 bit = 1u << (uint32) location;
+
+			enabledattribs |= bit;
+
+			if (attrib.second.step == STEP_PER_INSTANCE)
+				instancedattribs |= bit;
+		}
+	}
+
+	// Not supported on all platforms or GL versions, I believe.
+	if (!(enabledattribs & ATTRIBFLAG_POS))
+		throw love::Exception("Mesh must have an enabled VertexPosition attribute to be drawn.");
+
+	bool useindexbuffer = useIndexBuffer && ibo != nullptr && elementCount > 0;
+
+	int start = 0;
+	int count = 0;
+
+	if (useindexbuffer)
+	{
+		// Make sure the index buffer isn't mapped (sends data to GPU if needed.)
+		ibo->unmap();
+
+		start = std::min(std::max(0, rangeStart), (int) elementCount - 1);
+
+		count = (int) elementCount;
+		if (rangeCount > 0)
+			count = std::min(count, rangeCount);
+
+		count = std::min(count, (int) elementCount - start);
+	}
+	else
+	{
+		start = std::min(std::max(0, rangeStart), (int) vertexCount - 1);
+
+		count = (int) vertexCount;
+		if (rangeCount > 0)
+			count = std::min(count, rangeCount);
+
+		count = std::min(count, (int) vertexCount - start);
+	}
+
+	Graphics::TempTransform transform(gfx, m);
+
+	if (count > 0)
+		drawInternal(start, count, instancecount, useindexbuffer, enabledattribs, instancedattribs);
+}
+
 size_t Mesh::getAttribFormatSize(const AttribFormat &format)
 {
 	switch (format.type)

+ 4 - 2
src/modules/graphics/Mesh.h

@@ -195,11 +195,11 @@ public:
 
 	virtual int bindAttributeToShaderInput(int attributeindex, const std::string &inputname) = 0;
 
-	virtual void drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount) = 0;
-
 	// Implements Drawable.
 	void draw(Graphics *gfx, const Matrix4 &m) override;
 
+	void drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount);
+
 	static bool getConstant(const char *in, DrawMode &out);
 	static bool getConstant(DrawMode in, const char *&out);
 
@@ -223,6 +223,8 @@ protected:
 	void calculateAttributeSizes();
 	size_t getAttributeOffset(size_t attribindex) const;
 
+	virtual void drawInternal(int start, int count, int instancecount, bool useindexbuffer, uint32 attribflags, uint32 instancedattribflags) const = 0;
+
 	static size_t getAttribFormatSize(const AttribFormat &format);
 	static std::vector<AttribFormat> getDefaultVertexFormat();
 

+ 10 - 3
src/modules/graphics/ParticleSystem.cpp

@@ -1048,15 +1048,21 @@ void ParticleSystem::update(float dt)
 	prevPosition = position;
 }
 
-bool ParticleSystem::prepareDraw(Graphics *gfx)
+void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m)
 {
 	uint32 pCount = getCount();
 
 	if (pCount == 0 || texture.get() == nullptr || pMem == nullptr || buffer == nullptr)
-		return false;
+		return;
 
 	gfx->flushStreamDraws();
 
+	if (Shader::isDefaultActive())
+		Shader::attachDefault(Shader::STANDARD_DEFAULT);
+
+	if (Shader::current && texture.get())
+		Shader::current->checkMainTexture(texture);
+
 	const Vector2 *positions = texture->getQuad()->getVertexPositions();
 	const Vector2 *texcoords = texture->getQuad()->getVertexTexCoords();
 
@@ -1098,7 +1104,8 @@ bool ParticleSystem::prepareDraw(Graphics *gfx)
 
 	buffer->unmap();
 
-	return true;
+	Graphics::TempTransform transform(gfx, m);
+	drawInternal();
 }
 
 bool ParticleSystem::getConstant(const char *in, AreaSpreadDistribution &out)

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

@@ -569,6 +569,9 @@ public:
 	 **/
 	void update(float dt);
 
+	// Implements Drawable.
+	void draw(Graphics *gfx, const Matrix4 &m) override;
+
 	static bool getConstant(const char *in, AreaSpreadDistribution &out);
 	static bool getConstant(AreaSpreadDistribution in, const char *&out);
 
@@ -612,7 +615,7 @@ protected:
 		int quadIndex;
 	};
 
-	bool prepareDraw(Graphics *gfx);
+	virtual void drawInternal() const = 0;
 
 	// Pointer to the beginning of the allocated memory.
 	Particle *pMem;

+ 62 - 8
src/modules/graphics/SpriteBatch.cpp

@@ -62,9 +62,9 @@ SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, vertex::Usag
 	else
 		vertex_format = vertex::CommonFormat::XYf_STf_RGBAub;
 
-	format_stride = vertex::getFormatStride(vertex_format);
+	vertex_stride = vertex::getFormatStride(vertex_format);
 
-	size_t vertex_size = format_stride * 4 * size;
+	size_t vertex_size = vertex_stride * 4 * size;
 	array_buf = gfx->newBuffer(vertex_size, nullptr, BUFFER_VERTEX, usage, Buffer::MAP_EXPLICIT_RANGE_MODIFY);
 }
 
@@ -95,7 +95,7 @@ int SpriteBatch::add(Quad *quad, const Matrix4 &m, int index /*= -1*/)
 	const Vector2 *quadtexcoords = quad->getVertexTexCoords();
 
 	// Always keep the VBO mapped when adding data (it'll be unmapped on draw.)
-	size_t offset = (index == -1 ? next : index) * format_stride * 4;
+	size_t offset = (index == -1 ? next : index) * vertex_stride * 4;
 	auto verts = (XYf_STf_RGBAub *) ((uint8 *) array_buf->map() + offset);
 
 	m.transformXY(verts, quadpositions, 4);
@@ -107,7 +107,7 @@ int SpriteBatch::add(Quad *quad, const Matrix4 &m, int index /*= -1*/)
 		verts[i].color = color;
 	}
 
-	array_buf->setMappedRangeModified(offset, format_stride * 4);
+	array_buf->setMappedRangeModified(offset, vertex_stride * 4);
 
 	// Increment counter.
 	if (index == -1)
@@ -141,7 +141,7 @@ int SpriteBatch::addLayer(int layer, Quad *quad, const Matrix4 &m, int index)
 	const Vector2 *quadtexcoords = quad->getVertexTexCoords();
 
 	// Always keep the VBO mapped when adding data (it'll be unmapped on draw.)
-	size_t offset = (index == -1 ? next : index) * format_stride * 4;
+	size_t offset = (index == -1 ? next : index) * vertex_stride * 4;
 	auto verts = (XYf_STPf_RGBAub *) ((uint8 *) array_buf->map() + offset);
 
 	m.transformXY(verts, quadpositions, 4);
@@ -154,7 +154,7 @@ int SpriteBatch::addLayer(int layer, Quad *quad, const Matrix4 &m, int index)
 		verts[i].color = color;
 	}
 
-	array_buf->setMappedRangeModified(offset, format_stride * 4);
+	array_buf->setMappedRangeModified(offset, vertex_stride * 4);
 
 	// Increment counter.
 	if (index == -1)
@@ -225,7 +225,7 @@ void SpriteBatch::setBufferSize(int newsize)
 	if (newsize == size)
 		return;
 
-	size_t vertex_size = format_stride * 4 * newsize;
+	size_t vertex_size = vertex_stride * 4 * newsize;
 	love::graphics::Buffer *new_array_buf = nullptr;
 
 	int new_next = std::min(next, newsize);
@@ -236,7 +236,7 @@ void SpriteBatch::setBufferSize(int newsize)
 		new_array_buf = gfx->newBuffer(vertex_size, nullptr, array_buf->getType(), array_buf->getUsage(), array_buf->getMapFlags());
 
 		// Copy as much of the old data into the new GLBuffer as can fit.
-		size_t copy_size = format_stride * 4 * new_next;
+		size_t copy_size = vertex_stride * 4 * new_next;
 		array_buf->copyTo(0, copy_size, new_array_buf, 0);
 
 		quad_indices = QuadIndices(gfx, newsize);
@@ -307,5 +307,59 @@ bool SpriteBatch::getDrawRange(int &start, int &count) const
 	return true;
 }
 
+void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
+{
+	using namespace vertex;
+
+	if (next == 0)
+		return;
+
+	gfx->flushStreamDraws();
+
+	if (texture.get())
+	{
+		if (Shader::isDefaultActive())
+		{
+			Shader::StandardShader defaultshader = Shader::STANDARD_DEFAULT;
+			if (texture->getTextureType() == TEXTURE_2D_ARRAY)
+				defaultshader = Shader::STANDARD_ARRAY;
+
+			Shader::attachDefault(defaultshader);
+		}
+
+		if (Shader::current)
+			Shader::current->checkMainTexture(texture);
+	}
+
+	// Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.)
+	array_buf->unmap();
+
+	CommonFormat format = vertex_format;
+
+	if (!color_active)
+	{
+		if (format == CommonFormat::XYf_STPf_RGBAub)
+			format = CommonFormat::XYf_STPf;
+		else
+			format = CommonFormat::XYf_STf;
+	}
+
+	int start = std::min(std::max(0, range_start), next - 1);
+
+	int count = next;
+	if (range_count > 0)
+		count = std::min(count, range_count);
+
+	count = std::min(count, next - start);
+
+	size_t indexbytestart = quad_indices.getIndexCount(start) * quad_indices.getElementSize();
+	size_t indexcount = quad_indices.getIndexCount(count);
+
+	Graphics::TempTransform transform(gfx, m);
+
+	if (count > 0)
+		drawInternal(format, indexbytestart, indexcount);
+}
+
 } // graphics
 } // love

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

@@ -32,6 +32,7 @@
 #include "Drawable.h"
 #include "Color.h"
 #include "Mesh.h"
+#include "vertex.h"
 
 namespace love
 {
@@ -105,6 +106,9 @@ public:
 	void setDrawRange();
 	bool getDrawRange(int &start, int &count) const;
 
+	// Implements Drawable.
+	void draw(Graphics *gfx, const Matrix4 &m) override;
+
 protected:
 
 	struct AttachedAttribute
@@ -119,6 +123,8 @@ protected:
 	 **/
 	void setBufferSize(int newsize);
 
+	virtual void drawInternal(vertex::CommonFormat format, size_t indexbytestart, size_t indexcount) = 0;
+
 	StrongRef<Texture> texture;
 
 	// Max number of sprites in the batch.
@@ -133,7 +139,7 @@ protected:
 	bool color_active;
 
 	vertex::CommonFormat vertex_format;
-	size_t format_stride;
+	size_t vertex_stride;
 	
 	love::graphics::Buffer *array_buf;
 	QuadIndices quad_indices;

+ 31 - 0
src/modules/graphics/Text.cpp

@@ -235,5 +235,36 @@ int Text::getHeight(int index) const
 	return text_data[index].text_info.height;
 }
 
+void Text::draw(Graphics *gfx, const Matrix4 &m)
+{
+	if (vbo == nullptr || draw_commands.empty())
+		return;
+
+	gfx->flushStreamDraws();
+
+	if (Shader::isDefaultActive())
+		Shader::attachDefault(Shader::STANDARD_DEFAULT);
+
+	if (Shader::current)
+		Shader::current->checkMainTextureType(TEXTURE_2D, false);
+
+	// Re-generate the text if the Font's texture cache was invalidated.
+	if (font->getTextureCacheID() != texture_cache_id)
+		regenerateVertices();
+
+	int totalverts = 0;
+	for (const Font::DrawCommand &cmd : draw_commands)
+		totalverts = std::max(cmd.startvertex + cmd.vertexcount, totalverts);
+
+	if ((size_t) totalverts / 4 > quadIndices.getSize())
+		quadIndices = QuadIndices(gfx, (size_t) totalverts / 4);
+
+	vbo->unmap(); // Make sure all pending data is flushed to the GPU.
+
+	Graphics::TempTransform transform(gfx, m);
+
+	drawInternal(draw_commands);
+}
+
 } // graphics
 } // love

+ 5 - 0
src/modules/graphics/Text.h

@@ -63,6 +63,9 @@ public:
 	 **/
 	int getHeight(int index = 0) const;
 
+	// Implements Drawable.
+	void draw(love::graphics::Graphics *gfx, const Matrix4 &m) override;
+
 protected:
 
 	struct TextData
@@ -80,6 +83,8 @@ protected:
 	void regenerateVertices();
 	void addTextData(const TextData &s);
 
+	virtual void drawInternal(const std::vector<Font::DrawCommand> &commands) const = 0;
+
 	StrongRef<Font> font;
 	Buffer *vbo;
 	QuadIndices quadIndices;

+ 5 - 118
src/modules/graphics/opengl/Graphics.cpp

@@ -492,107 +492,9 @@ void Graphics::setDebug(bool enable)
 	::printf("OpenGL debug output enabled (LOVE_GRAPHICS_DEBUG=1)\n");
 }
 
-void Graphics::setCanvas(const RenderTargets &rts)
+void Graphics::setCanvasInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBcanvas)
 {
-	DisplayState &state = states.back();
-	int ncanvases = (int) rts.colors.size();
-
-	if (ncanvases == 0 && rts.depthStencil.canvas == nullptr)
-		return setCanvas();
-	else if (ncanvases == 0)
-		throw love::Exception("At least one color render target is required when using a custom depth/stencil buffer.");
-
-	const auto &prevRTs = state.renderTargets;
-
-	if (ncanvases == (int) prevRTs.colors.size())
-	{
-		bool modified = false;
-
-		for (int i = 0; i < ncanvases; i++)
-		{
-			if (rts.colors[i].canvas != prevRTs.colors[i].canvas.get()
-				|| rts.colors[i].slice != prevRTs.colors[i].slice
-				|| rts.colors[i].mipmap != prevRTs.colors[i].mipmap)
-			{
-				modified = true;
-				break;
-			}
-		}
-
-		if (!modified && (rts.depthStencil.canvas != prevRTs.depthStencil.canvas
-			|| rts.depthStencil.slice != prevRTs.depthStencil.slice
-			|| rts.depthStencil.mipmap != prevRTs.depthStencil.mipmap))
-		{
-			modified = true;
-		}
-
-		if (rts.temporaryRTFlags != prevRTs.temporaryRTFlags)
-			modified = true;
-
-		if (!modified)
-			return;
-	}
-
-	if (ncanvases > gl.getMaxRenderTargets())
-		throw love::Exception("This system can't simultaneously render to %d canvases.", ncanvases);
-
-	love::graphics::Canvas *firstcanvas = rts.colors[0].canvas;
-
-	bool multiformatsupported = Canvas::isMultiFormatMultiCanvasSupported();
-	PixelFormat firstformat = firstcanvas->getPixelFormat();
-
-	if (isPixelFormatDepthStencil(firstformat))
-		throw love::Exception("Depth/stencil format Canvases must be used with the 'depthstencil' field of the table passed into setCanvas.");
-
-	if (rts.colors[0].mipmap < 0 || rts.colors[0].mipmap >= firstcanvas->getMipmapCount())
-		throw love::Exception("Invalid mipmap level %d.", rts.colors[0].mipmap + 1);
-
-	bool hasSRGBcanvas = firstformat == PIXELFORMAT_sRGBA8;
-	int pixelwidth = firstcanvas->getPixelWidth(rts.colors[0].mipmap);
-	int pixelheight = firstcanvas->getPixelHeight(rts.colors[0].mipmap);
-
-	for (int i = 1; i < ncanvases; i++)
-	{
-		love::graphics::Canvas *c = rts.colors[i].canvas;
-		PixelFormat format = c->getPixelFormat();
-		int mip = rts.colors[i].mipmap;
-
-		if (mip < 0 || mip >= c->getMipmapCount())
-			throw love::Exception("Invalid mipmap level %d.", mip + 1);
-
-		if (c->getPixelWidth(mip) != pixelwidth || c->getPixelHeight(mip) != pixelheight)
-			throw love::Exception("All canvases must have the same pixel dimensions.");
-
-		if (!multiformatsupported && format != firstformat)
-			throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats.");
-
-		if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA())
-			throw love::Exception("All Canvases must have the same MSAA value.");
-
-		if (isPixelFormatDepthStencil(format))
-			throw love::Exception("Depth/stencil format Canvases must be used with the 'depthstencil' field of the table passed into setCanvas.");
-
-		if (format == PIXELFORMAT_sRGBA8)
-			hasSRGBcanvas = true;
-	}
-
-	if (rts.depthStencil.canvas != nullptr)
-	{
-		love::graphics::Canvas *c = rts.depthStencil.canvas;
-		int mip = rts.depthStencil.mipmap;
-
-		if (!isPixelFormatDepthStencil(c->getPixelFormat()))
-			throw love::Exception("Only depth/stencil format Canvases can be used with the 'depthstencil' field of the table passed into setCanvas.");
-
-		if (c->getPixelWidth(mip) != pixelwidth || c->getPixelHeight(mip) != pixelheight)
-			throw love::Exception("All canvases must have the same pixel dimensions.");
-
-		if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA())
-			throw love::Exception("All Canvases must have the same MSAA value.");
-
-		if (mip < 0 || mip >= c->getMipmapCount())
-			throw love::Exception("Invalid mipmap level %d.", mip + 1);
-	}
+	const DisplayState &state = states.back();
 
 	OpenGL::TempDebugGroup debuggroup("setCanvas(...)");
 
@@ -600,15 +502,13 @@ void Graphics::setCanvas(const RenderTargets &rts)
 
 	bindCachedFBO(rts);
 
-	gl.setViewport({0, 0, pixelwidth, pixelheight});
+	gl.setViewport({0, 0, pixelw, pixelh});
 
 	// Re-apply the scissor if it was active, since the rectangle passed to
 	// glScissor is affected by the viewport dimensions.
 	if (state.scissor)
 		setScissor(state.scissorRect);
 
-	int w = firstcanvas->getWidth(rts.colors[0].mipmap);
-	int h = firstcanvas->getHeight(rts.colors[0].mipmap);
 	projectionMatrix = Matrix4::ortho(0.0, (float) w, 0.0, (float) h);
 
 	// Make sure the correct sRGB setting is used when drawing to the canvases.
@@ -619,19 +519,6 @@ void Graphics::setCanvas(const RenderTargets &rts)
 		else if (!hasSRGBcanvas && gl.hasFramebufferSRGB())
 			gl.setFramebufferSRGB(false);
 	}
-
-	RenderTargetsStrongRef refs;
-	refs.colors.reserve(rts.colors.size());
-
-	for (auto c : rts.colors)
-		refs.colors.emplace_back(c.canvas, c.slice);
-
-	refs.depthStencil = RenderTargetStrongRef(rts.depthStencil.canvas, rts.depthStencil.slice);
-	refs.temporaryRTFlags = rts.temporaryRTFlags;
-
-	std::swap(state.renderTargets, refs);
-
-	canvasSwitchCount++;
 }
 
 void Graphics::setCanvas()
@@ -643,6 +530,7 @@ void Graphics::setCanvas()
 
 	OpenGL::TempDebugGroup debuggroup("setCanvas()");
 
+	flushStreamDraws();
 	endPass();
 
 	state.renderTargets = RenderTargetsStrongRef();
@@ -673,8 +561,6 @@ void Graphics::setCanvas()
 
 void Graphics::endPass()
 {
-	flushStreamDraws();
-
 	auto &rts = states.back().renderTargets;
 	love::graphics::Canvas *depthstencil = rts.depthStencil.canvas.get();
 
@@ -1013,6 +899,7 @@ void Graphics::present(void *screenshotCallbackData)
 	if (isCanvasActive())
 		throw love::Exception("present cannot be called while a Canvas is active.");
 
+	flushStreamDraws();
 	endPass();
 
 	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO());

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

@@ -97,7 +97,6 @@ public:
 
 	void setColor(Colorf c) override;
 
-	void setCanvas(const RenderTargets &rts) override;
 	void setCanvas() override;
 
 	void setScissor(const Rect &rect) override;
@@ -128,6 +127,7 @@ public:
 private:
 
 	love::graphics::StreamBuffer *newStreamBuffer(BufferType type, size_t size) override;
+	void setCanvasInternal(const RenderTargets &rts, int w, int h, int pixelw, int pixelh, bool hasSRGBcanvas) override;
 	void initCapabilities() override;
 	void getAPIStats(int &drawcalls, int &shaderswitches) const override;
 

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

@@ -91,94 +91,28 @@ int Mesh::bindAttributeToShaderInput(int attributeindex, const std::string &inpu
 	return attriblocation;
 }
 
-void Mesh::drawInstanced(love::graphics::Graphics *gfx, const love::Matrix4 &m, int instancecount)
+void Mesh::drawInternal(int start, int count, int instancecount, bool useindexbuffer, uint32 attribflags, uint32 instancedattribflags) const
 {
-	if (vertexCount <= 0 || instancecount <= 0)
-		return;
-
-	if (instancecount > 1 && !gfx->getCapabilities().features[Graphics::FEATURE_INSTANCING])
-		throw love::Exception("Instancing is not supported on this system.");
-
-	gfx->flushStreamDraws();
-
-	if (Shader::isDefaultActive())
-		Shader::attachDefault(Shader::STANDARD_DEFAULT);
-
-	if (Shader::current && texture.get())
-		Shader::current->checkMainTexture(texture);
-
 	OpenGL::TempDebugGroup debuggroup("Mesh draw");
 
-	uint32 enabledattribs = 0;
-	uint32 instancedattribs = 0;
-
-	for (const auto &attrib : attachedAttributes)
-	{
-		if (!attrib.second.enabled)
-			continue;
-
-		love::graphics::Mesh *mesh = attrib.second.mesh;
-		int location = mesh->bindAttributeToShaderInput(attrib.second.index, attrib.first);
-
-		if (location >= 0)
-		{
-			uint32 bit = 1u << (uint32) location;
-
-			enabledattribs |= bit;
-
-			if (attrib.second.step == STEP_PER_INSTANCE)
-				instancedattribs |= bit;
-		}
-	}
-
-	// Not supported on all platforms or GL versions, I believe.
-	if (!(enabledattribs & ATTRIBFLAG_POS))
-		throw love::Exception("Mesh must have an enabled VertexPosition attribute to be drawn.");
-
-	gl.useVertexAttribArrays(enabledattribs, instancedattribs);
-
+	gl.useVertexAttribArrays(attribflags, instancedattribflags);
 	gl.bindTextureToUnit(texture, 0, false);
-
-	Graphics::TempTransform transform(gfx, m);
-
 	gl.prepareDraw();
 
-	if (useIndexBuffer && ibo && elementCount > 0)
-	{
-		// Use the custom vertex map (index buffer) to draw the vertices.
-		gl.bindBuffer(BUFFER_INDEX, (GLuint) ibo->getHandle());
-
-		// Make sure the index buffer isn't mapped (sends data to GPU if needed.)
-		ibo->unmap();
-
-		int start = std::min(std::max(0, rangeStart), (int) elementCount - 1);
-
-		int count = (int) elementCount;
-		if (rangeCount > 0)
-			count = std::min(count, rangeCount);
-
-		count = std::min(count, (int) elementCount - start);
+	GLenum gldrawmode = getGLDrawMode(drawMode);
 
+	if (useindexbuffer)
+	{
 		size_t elementsize = vertex::getIndexDataSize(elementDataType);
 		const void *indices = BUFFER_OFFSET(start * elementsize);
 		GLenum type = OpenGL::getGLIndexDataType(elementDataType);
 
-		if (count > 0)
-			gl.drawElements(getGLDrawMode(drawMode), count, type, indices, instancecount);
+		gl.bindBuffer(BUFFER_INDEX, (GLuint) ibo->getHandle());
+		gl.drawElements(gldrawmode, count, type, indices, instancecount);
 	}
 	else
 	{
-		int start = std::min(std::max(0, rangeStart), (int) vertexCount - 1);
-
-		int count = (int) vertexCount;
-		if (rangeCount > 0)
-			count = std::min(count, rangeCount);
-
-		count = std::min(count, (int) vertexCount - start);
-
-		// Normal non-indexed drawing (no custom vertex map.)
-		if (count > 0)
-			gl.drawArrays(getGLDrawMode(drawMode), start, count, instancecount);
+		gl.drawArrays(gldrawmode, start, count, instancecount);
 	}
 }
 

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

@@ -44,7 +44,10 @@ public:
 	virtual ~Mesh();
 
 	int bindAttributeToShaderInput(int attributeindex, const std::string &inputname) override;
-	void drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount) override;
+
+protected:
+
+	void drawInternal(int start, int count, int instancecount, bool useindexbuffer, uint32 attribflags, uint32 instancedattribflags) const override;
 
 private:
 

+ 1 - 12
src/modules/graphics/opengl/ParticleSystem.cpp

@@ -50,21 +50,10 @@ ParticleSystem *ParticleSystem::clone()
 	return new ParticleSystem(*this);
 }
 
-void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m)
+void ParticleSystem::drawInternal() const
 {
 	using namespace vertex;
 
-	if (!prepareDraw(gfx))
-		return;
-
-	Graphics::TempTransform transform(gfx, m);
-
-	if (Shader::isDefaultActive())
-		Shader::attachDefault(Shader::STANDARD_DEFAULT);
-
-	if (Shader::current && texture.get())
-		Shader::current->checkMainTexture(texture);
-
 	OpenGL::TempDebugGroup debuggroup("ParticleSystem draw");
 
 	gl.bindTextureToUnit(texture, 0, false);

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

@@ -44,7 +44,10 @@ public:
 	virtual ~ParticleSystem();
 
 	ParticleSystem *clone() override;
-	void draw(Graphics *gfx, const Matrix4 &m) override;
+
+private:
+
+	void drawInternal() const override;
 
 }; // ParticleSystem
 

+ 9 - 56
src/modules/graphics/opengl/SpriteBatch.cpp

@@ -51,52 +51,15 @@ SpriteBatch::~SpriteBatch()
 {
 }
 
-void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
+void SpriteBatch::drawInternal(vertex::CommonFormat format, size_t indexbytestart, size_t indexcount)
 {
-	using namespace vertex;
-
-	if (next == 0)
-		return;
-
-	gfx->flushStreamDraws();
-
-	if (texture.get())
-	{
-		if (Shader::isDefaultActive())
-		{
-			Shader::StandardShader defaultshader = Shader::STANDARD_DEFAULT;
-			if (texture->getTextureType() == TEXTURE_2D_ARRAY)
-				defaultshader = Shader::STANDARD_ARRAY;
-
-			Shader::attachDefault(defaultshader);
-		}
-
-		if (Shader::current)
-			Shader::current->checkMainTexture(texture);
-	}
-
 	OpenGL::TempDebugGroup debuggroup("SpriteBatch draw");
 
-	Graphics::TempTransform transform(gfx, m);
-
-	gl.bindTextureToUnit(texture, 0, false);
-
-	// Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.)
-	array_buf->unmap();
-
-	CommonFormat format = vertex_format;
-
-	if (!color_active)
-	{
-		if (format == CommonFormat::XYf_STPf_RGBAub)
-			format = CommonFormat::XYf_STPf;
-		else
-			format = CommonFormat::XYf_STf;
-	}
-
 	uint32 enabledattribs = getFormatFlags(format);
 
-	gl.setVertexPointers(format, array_buf, format_stride, 0);
+	// We want attached attributes to override local attributes, so we should
+	// call this before binding attached attributes.
+	gl.setVertexPointers(format, array_buf, vertex_stride, 0);
 
 	for (const auto &it : attached_attributes)
 	{
@@ -114,26 +77,16 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
 	}
 
 	gl.useVertexAttribArrays(enabledattribs);
+	gl.bindTextureToUnit(texture, 0, false);
 
 	gl.prepareDraw();
 
-	int start = std::min(std::max(0, range_start), next - 1);
+	gl.bindBuffer(BUFFER_INDEX, (GLuint) quad_indices.getBuffer()->getHandle());
 
-	int count = next;
-	if (range_count > 0)
-		count = std::min(count, range_count);
+	const void *indices = BUFFER_OFFSET(indexbytestart);
+	GLenum gltype = OpenGL::getGLIndexDataType(quad_indices.getType());
 
-	count = std::min(count, next - start);
-
-	if (count > 0)
-	{
-		gl.bindBuffer(BUFFER_INDEX, (GLuint) quad_indices.getBuffer()->getHandle());
-
-		const void *indices = BUFFER_OFFSET(start * quad_indices.getElementSize());
-		GLenum gltype = OpenGL::getGLIndexDataType(quad_indices.getType());
-
-		gl.drawElements(GL_TRIANGLES, (GLsizei) quad_indices.getIndexCount(count), gltype, indices);
-	}
+	gl.drawElements(GL_TRIANGLES, (GLsizei) indexcount, gltype, indices);
 }
 
 } // opengl

+ 3 - 2
src/modules/graphics/opengl/SpriteBatch.h

@@ -37,8 +37,9 @@ public:
 	SpriteBatch(Graphics *gfx, Texture *texture, int size, vertex::Usage usage);
 	virtual ~SpriteBatch();
 
-	// Implements Drawable.
-	void draw(Graphics *gfx, const Matrix4 &m) override;
+protected:
+
+	void drawInternal(vertex::CommonFormat format, size_t indexbytestart, size_t indexcount) override;
 
 }; // SpriteBatch
 

+ 2 - 30
src/modules/graphics/opengl/Text.cpp

@@ -40,36 +40,10 @@ Text::~Text()
 {
 }
 
-void Text::draw(Graphics *gfx, const Matrix4 &m)
+void Text::drawInternal(const std::vector<Font::DrawCommand> &commands) const
 {
-	if (vbo == nullptr || draw_commands.empty())
-		return;
-
-	gfx->flushStreamDraws();
-
-	if (Shader::isDefaultActive())
-		Shader::attachDefault(Shader::STANDARD_DEFAULT);
-
-	if (Shader::current)
-		Shader::current->checkMainTextureType(TEXTURE_2D, false);
-
 	OpenGL::TempDebugGroup debuggroup("Text object draw");
 
-	// Re-generate the text if the Font's texture cache was invalidated.
-	if (font->getTextureCacheID() != texture_cache_id)
-		regenerateVertices();
-
-	int totalverts = 0;
-	for (const Font::DrawCommand &cmd : draw_commands)
-		totalverts = std::max(cmd.startvertex + cmd.vertexcount, totalverts);
-
-	if ((size_t) totalverts / 4 > quadIndices.getSize())
-		quadIndices = QuadIndices(gfx, (size_t) totalverts / 4);
-
-	vbo->unmap(); // Make sure all pending data is flushed to the GPU.
-
-	Graphics::TempTransform transform(gfx, m);
-
 	gl.prepareDraw();
 
 	gl.setVertexPointers(Font::vertexFormat, vbo, 0);
@@ -82,14 +56,12 @@ void Text::draw(Graphics *gfx, const Matrix4 &m)
 
 	// We need a separate draw call for every section of the text which uses a
 	// different texture than the previous section.
-	for (const Font::DrawCommand &cmd : draw_commands)
+	for (const Font::DrawCommand &cmd : commands)
 	{
 		GLsizei count = (cmd.vertexcount / 4) * 6;
 		size_t offset = (cmd.startvertex / 4) * 6 * elemsize;
 
-		// TODO: Use glDrawElementsBaseVertex when supported?
 		gl.bindTextureToUnit(cmd.texture, 0, false);
-
 		gl.drawElements(GL_TRIANGLES, count, gltype, BUFFER_OFFSET(offset));
 	}
 }

+ 3 - 2
src/modules/graphics/opengl/Text.h

@@ -38,8 +38,9 @@ public:
 	Text(love::graphics::Graphics *gfx, love::graphics::Font *font, const std::vector<Font::ColoredString> &text = {});
 	virtual ~Text();
 
-	// Implements Drawable.
-	void draw(love::graphics::Graphics *gfx, const Matrix4 &m) override;
+protected:
+
+	void drawInternal(const std::vector<Font::DrawCommand> &commands) const override;
 
 }; // Text