Browse Source

Array textures can be easily drawn without a shader (resolves issue #1111).

Added love.graphics.drawLayer(texture, layerindex, …). ‘texture’ must be an array texture.
Added SpriteBatch:add/setLayer.

Added Quad:get/setLayer. This applies to array textures that are drawn without specifying an explicit layer index in the draw call.

Added love.graphics.newQuad variants which have layer arguments.

--HG--
branch : minor
Alex Szpakowski 8 years ago
parent
commit
66b299822a

+ 8 - 8
src/common/Matrix.h

@@ -192,8 +192,8 @@ public:
 	 * @param src The source vertices.
 	 * @param src The source vertices.
 	 * @param size The number of vertices.
 	 * @param size The number of vertices.
 	 **/
 	 **/
-	template <typename V>
-	void transform(V *dst, const V *src, int size) const;
+	template <typename Vdst, typename Vsrc>
+	void transform(Vdst *dst, const Vsrc *src, int size) const;
 
 
 	/**
 	/**
 	 * Computes and returns the inverse of the matrix.
 	 * Computes and returns the inverse of the matrix.
@@ -273,8 +273,8 @@ public:
 	/**
 	/**
 	 * Transforms an array of vertices by this matrix.
 	 * Transforms an array of vertices by this matrix.
 	 **/
 	 **/
-	template <typename V>
-	void transform(V *dst, const V *src, int size) const;
+	template <typename Vdst, typename Vsrc>
+	void transform(Vdst *dst, const Vsrc *src, int size) const;
 
 
 private:
 private:
 
 
@@ -296,8 +296,8 @@ private:
 // | e2 e6 e10 e14 |
 // | e2 e6 e10 e14 |
 // | e3 e7 e11 e15 |
 // | e3 e7 e11 e15 |
 
 
-template <typename V>
-void Matrix4::transform(V *dst, const V *src, int size) const
+template <typename Vdst, typename Vsrc>
+void Matrix4::transform(Vdst *dst, const Vsrc *src, int size) const
 {
 {
 	for (int i = 0; i < size; i++)
 	for (int i = 0; i < size; i++)
 	{
 	{
@@ -316,8 +316,8 @@ void Matrix4::transform(V *dst, const V *src, int size) const
 // | e0 e3 e6 |
 // | e0 e3 e6 |
 // | e1 e4 e7 |
 // | e1 e4 e7 |
 // | e2 e5 e8 |
 // | e2 e5 e8 |
-template <typename V>
-void Matrix3::transform(V *dst, const V *src, int size) const
+template <typename Vdst, typename Vsrc>
+void Matrix3::transform(Vdst *dst, const Vsrc *src, int size) const
 {
 {
 	for (int i = 0; i < size; i++)
 	for (int i = 0; i < size; i++)
 	{
 	{

+ 2 - 2
src/modules/graphics/Canvas.cpp

@@ -40,12 +40,12 @@ Canvas::~Canvas()
 	canvasCount--;
 	canvasCount--;
 }
 }
 
 
-void Canvas::drawv(love::graphics::Graphics *gfx, const love::Matrix4 &t, const Vertex *v)
+void Canvas::draw(Graphics *gfx, Quad *q, const Matrix4 &t)
 {
 {
 	if (gfx->isCanvasActive(this))
 	if (gfx->isCanvasActive(this))
 		throw love::Exception("Cannot render a Canvas to itself!");
 		throw love::Exception("Cannot render a Canvas to itself!");
 
 
-	Texture::drawv(gfx, t, v);
+	Texture::draw(gfx, q, t);
 }
 }
 
 
 } // graphics
 } // graphics

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

@@ -57,11 +57,9 @@ public:
 	virtual int getRequestedMSAA() const = 0;
 	virtual int getRequestedMSAA() const = 0;
 	virtual ptrdiff_t getMSAAHandle() const = 0;
 	virtual ptrdiff_t getMSAAHandle() const = 0;
 
 
-	static int canvasCount;
-
-private:
+	void draw(Graphics *gfx, Quad *q, const Matrix4 &t) override;
 
 
-	void drawv(Graphics *gfx, const Matrix4 &t, const Vertex *v) override;
+	static int canvasCount;
 	
 	
 }; // Canvas
 }; // Canvas
 
 

+ 34 - 28
src/modules/graphics/Graphics.cpp

@@ -99,8 +99,7 @@ bool isDebugEnabled()
 
 
 love::Type Graphics::type("graphics", &Module::type);
 love::Type Graphics::type("graphics", &Module::type);
 
 
-Shader::ShaderSource Graphics::defaultShaderCode[Shader::LANGUAGE_MAX_ENUM][2];
-Shader::ShaderSource Graphics::defaultVideoShaderCode[Shader::LANGUAGE_MAX_ENUM][2];
+Shader::ShaderSource Graphics::defaultShaderCode[Shader::STANDARD_MAX_ENUM][Shader::LANGUAGE_MAX_ENUM][2];
 
 
 Graphics::Graphics()
 Graphics::Graphics()
 	: width(0)
 	: width(0)
@@ -130,16 +129,13 @@ Graphics::~Graphics()
 
 
 	defaultFont.set(nullptr);
 	defaultFont.set(nullptr);
 
 
-	if (Shader::defaultShader)
+	for (int i = 0; i < Shader::STANDARD_MAX_ENUM; i++)
 	{
 	{
-		Shader::defaultShader->release();
-		Shader::defaultShader = nullptr;
-	}
-
-	if (Shader::defaultVideoShader)
-	{
-		Shader::defaultVideoShader->release();
-		Shader::defaultVideoShader = nullptr;
+		if (Shader::standardShaders[i])
+		{
+			Shader::standardShaders[i]->release();
+			Shader::standardShaders[i] = nullptr;
+		}
 	}
 	}
 
 
 	delete streamBufferState.vb[0];
 	delete streamBufferState.vb[0];
@@ -390,7 +386,7 @@ void Graphics::setShader()
 {
 {
 	flushStreamDraws();
 	flushStreamDraws();
 
 
-	Shader::attachDefault();
+	Shader::attachDefault(Shader::STANDARD_DEFAULT);
 
 
 	states.back().shader.set(nullptr);
 	states.back().shader.set(nullptr);
 }
 }
@@ -692,6 +688,31 @@ Graphics::StreamVertexData Graphics::requestStreamDraw(const StreamDrawRequest &
  * Drawing
  * Drawing
  **/
  **/
 
 
+void Graphics::draw(Drawable *drawable, const Matrix4 &m)
+{
+	drawable->draw(this, m);
+}
+
+void Graphics::draw(Texture *texture, Quad *quad, const Matrix4 &m)
+{
+	texture->draw(this, quad, m);
+}
+
+void Graphics::drawLayer(Texture *texture, int layer, const Matrix4 &m)
+{
+	texture->drawLayer(this, layer, m);
+}
+
+void Graphics::drawLayer(Texture *texture, int layer, Quad *quad, const Matrix4 &m)
+{
+	texture->drawLayer(this, layer, quad, m);
+}
+
+void Graphics::drawInstanced(Mesh *mesh, const Matrix4 &m, int instancecount)
+{
+	mesh->drawInstanced(this, m, instancecount);
+}
+
 void Graphics::print(const std::vector<Font::ColoredString> &str, const Matrix4 &m)
 void Graphics::print(const std::vector<Font::ColoredString> &str, const Matrix4 &m)
 {
 {
 	checkSetDefaultFont();
 	checkSetDefaultFont();
@@ -712,21 +733,6 @@ void Graphics::printf(const std::vector<Font::ColoredString> &str, float wrap, F
 		state.font->printf(this, str, wrap, align, m, state.color);
 		state.font->printf(this, str, wrap, align, m, state.color);
 }
 }
 
 
-void Graphics::draw(Drawable *drawable, const Matrix4 &m)
-{
-	drawable->draw(this, m);
-}
-
-void Graphics::draw(Texture *texture, Quad *quad, const Matrix4 &m)
-{
-	texture->drawq(this, quad, m);
-}
-
-void Graphics::drawInstanced(Mesh *mesh, const Matrix4 &m, int instancecount)
-{
-	mesh->drawInstanced(this, m, instancecount);
-}
-
 /**
 /**
  * Primitives (points, shapes, lines).
  * Primitives (points, shapes, lines).
  **/
  **/
@@ -1165,7 +1171,7 @@ Vector Graphics::inverseTransformPoint(Vector point)
 
 
 const Shader::ShaderSource &Graphics::getCurrentDefaultShaderCode() const
 const Shader::ShaderSource &Graphics::getCurrentDefaultShaderCode() const
 {
 {
-	return defaultShaderCode[getShaderLanguageTarget()][isGammaCorrect() ? 1 : 0];
+	return defaultShaderCode[Shader::STANDARD_DEFAULT][getShaderLanguageTarget()][isGammaCorrect() ? 1 : 0];
 }
 }
 
 
 /**
 /**

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

@@ -585,6 +585,8 @@ public:
 
 
 	void draw(Drawable *drawable, const Matrix4 &m);
 	void draw(Drawable *drawable, const Matrix4 &m);
 	void draw(Texture *texture, Quad *quad, const Matrix4 &m);
 	void draw(Texture *texture, Quad *quad, const Matrix4 &m);
+	void drawLayer(Texture *texture, int layer, const Matrix4 &m);
+	void drawLayer(Texture *texture, int layer, Quad *quad, const Matrix4 &m);
 	void drawInstanced(Mesh *mesh, const Matrix4 &m, int instancecount);
 	void drawInstanced(Mesh *mesh, const Matrix4 &m, int instancecount);
 
 
 	/**
 	/**
@@ -782,8 +784,7 @@ public:
 	static bool getConstant(StackType in, const char *&out);
 	static bool getConstant(StackType in, const char *&out);
 
 
 	// Default shader code (a shader is always required internally.)
 	// Default shader code (a shader is always required internally.)
-	static Shader::ShaderSource defaultShaderCode[Shader::LANGUAGE_MAX_ENUM][2];
-	static Shader::ShaderSource defaultVideoShaderCode[Shader::LANGUAGE_MAX_ENUM][2];
+	static Shader::ShaderSource defaultShaderCode[Shader::STANDARD_MAX_ENUM][Shader::LANGUAGE_MAX_ENUM][2];
 
 
 protected:
 protected:
 
 

+ 7 - 1
src/modules/graphics/ParticleSystem.cpp

@@ -97,6 +97,9 @@ ParticleSystem::ParticleSystem(Graphics *gfx, Texture *texture, uint32 size)
 	if (size == 0 || size > MAX_PARTICLES)
 	if (size == 0 || size > MAX_PARTICLES)
 		throw love::Exception("Invalid ParticleSystem size.");
 		throw love::Exception("Invalid ParticleSystem size.");
 
 
+	if (texture->getTextureType() != TEXTURE_2D)
+		throw love::Exception("Only 2D textures can be used with ParticleSystems.");
+
 	sizes.push_back(1.0f);
 	sizes.push_back(1.0f);
 	colors.push_back(Colorf(1.0f, 1.0f, 1.0f, 1.0f));
 	colors.push_back(Colorf(1.0f, 1.0f, 1.0f, 1.0f));
 
 
@@ -434,6 +437,9 @@ ParticleSystem::Particle *ParticleSystem::removeParticle(Particle *p)
 
 
 void ParticleSystem::setTexture(Texture *tex)
 void ParticleSystem::setTexture(Texture *tex)
 {
 {
+	if (texture->getTextureType() != TEXTURE_2D)
+		throw love::Exception("Only 2D textures can be used with ParticleSystems.");
+
 	texture.set(tex);
 	texture.set(tex);
 
 
 	if (defaultOffset)
 	if (defaultOffset)
@@ -958,7 +964,7 @@ bool ParticleSystem::prepareDraw(Graphics *gfx, const Matrix4 &m)
 
 
 	Graphics::TempTransform transform(gfx, m);
 	Graphics::TempTransform transform(gfx, m);
 
 
-	const Vertex *textureVerts = texture->getVertices();
+	const vertex::XYf_STf *textureVerts = texture->getQuad()->getVertices();
 	Vertex *pVerts = (Vertex *) buffer->map();
 	Vertex *pVerts = (Vertex *) buffer->map();
 	Particle *p = pHead;
 	Particle *p = pHead;
 
 

+ 13 - 3
src/modules/graphics/Quad.cpp

@@ -35,7 +35,7 @@ Quad::Quad(const Quad::Viewport &v, double sw, double sh)
 	: sw(sw)
 	: sw(sw)
 	, sh(sh)
 	, sh(sh)
 {
 {
-	memset(vertices, 255, sizeof(Vertex) * 4);
+	arrayLayer = 0;
 	refresh(v, sw, sh);
 	refresh(v, sw, sh);
 }
 }
 
 
@@ -45,7 +45,7 @@ Quad::~Quad()
 
 
 void Quad::refresh(const Quad::Viewport &v, double sw, double sh)
 void Quad::refresh(const Quad::Viewport &v, double sw, double sh)
 {
 {
-	viewport = v;
+	this->viewport = v;
 	this->sw = sw;
 	this->sw = sw;
 	this->sh = sh;
 	this->sh = sh;
 
 
@@ -92,10 +92,20 @@ double Quad::getTextureHeight() const
 	return sh;
 	return sh;
 }
 }
 
 
-const Vertex *Quad::getVertices() const
+const vertex::XYf_STf *Quad::getVertices() const
 {
 {
 	return vertices;
 	return vertices;
 }
 }
 
 
+void Quad::setLayer(int layer)
+{
+	arrayLayer = layer;
+}
+
+int Quad::getLayer() const
+{
+	return arrayLayer;
+}
+
 } // graphics
 } // graphics
 } // love
 } // love

+ 7 - 2
src/modules/graphics/Quad.h

@@ -53,11 +53,16 @@ public:
 	double getTextureWidth() const;
 	double getTextureWidth() const;
 	double getTextureHeight() const;
 	double getTextureHeight() const;
 
 
-	const Vertex *getVertices() const;
+	const vertex::XYf_STf *getVertices() const;
+
+	void setLayer(int layer);
+	int getLayer() const;
 
 
 private:
 private:
 
 
-	Vertex vertices[4];
+	vertex::XYf_STf vertices[4];
+
+	int arrayLayer;
 
 
 	Viewport viewport;
 	Viewport viewport;
 	double sw;
 	double sw;

+ 26 - 14
src/modules/graphics/Shader.cpp

@@ -132,9 +132,9 @@ namespace graphics
 {
 {
 
 
 love::Type Shader::type("Shader", &Object::type);
 love::Type Shader::type("Shader", &Object::type);
+
 Shader *Shader::current = nullptr;
 Shader *Shader::current = nullptr;
-Shader *Shader::defaultShader = nullptr;
-Shader *Shader::defaultVideoShader = nullptr;
+Shader *Shader::standardShaders[Shader::STANDARD_MAX_ENUM] = {nullptr};
 
 
 Shader::Shader(const ShaderSource &source)
 Shader::Shader(const ShaderSource &source)
 	: shaderSource(source)
 	: shaderSource(source)
@@ -151,27 +151,39 @@ Shader::Shader(const ShaderSource &source)
 
 
 Shader::~Shader()
 Shader::~Shader()
 {
 {
-	if (defaultShader == this)
-		defaultShader = nullptr;
-
-	if (defaultVideoShader == this)
-		defaultVideoShader = nullptr;
+	for (int i = 0; i < STANDARD_MAX_ENUM; i++)
+	{
+		if (this == standardShaders[i])
+			standardShaders[i] = nullptr;
+	}
 
 
 	if (current == this)
 	if (current == this)
-		attachDefault();
+		attachDefault(STANDARD_DEFAULT);
 }
 }
 
 
-void Shader::attachDefault()
+void Shader::attachDefault(StandardShader defaultType)
 {
 {
-	if (defaultShader)
-	{
-		if (current != defaultShader)
-			defaultShader->attach();
+	Shader *defaultshader = standardShaders[defaultType];
 
 
+	if (defaultshader == nullptr)
+	{
+		current = nullptr;
 		return;
 		return;
 	}
 	}
 
 
-	current = nullptr;
+	if (current != defaultshader)
+		defaultshader->attach();
+}
+
+bool Shader::isDefaultActive()
+{
+	for (int i = 0; i < STANDARD_MAX_ENUM; i++)
+	{
+		if (current == standardShaders[i])
+			return true;
+	}
+
+	return false;
 }
 }
 
 
 TextureType Shader::getMainTextureType() const
 TextureType Shader::getMainTextureType() const

+ 16 - 4
src/modules/graphics/Shader.h

@@ -95,6 +95,14 @@ public:
 		UNIFORM_MAX_ENUM
 		UNIFORM_MAX_ENUM
 	};
 	};
 
 
+	enum StandardShader
+	{
+		STANDARD_DEFAULT,
+		STANDARD_VIDEO,
+		STANDARD_ARRAY,
+		STANDARD_MAX_ENUM
+	};
+
 	struct ShaderSource
 	struct ShaderSource
 	{
 	{
 		std::string vertex;
 		std::string vertex;
@@ -137,8 +145,7 @@ public:
 	static Shader *current;
 	static Shader *current;
 
 
 	// Pointer to the default Shader.
 	// Pointer to the default Shader.
-	static Shader *defaultShader;
-	static Shader *defaultVideoShader;
+	static Shader *standardShaders[STANDARD_MAX_ENUM];
 
 
 	Shader(const ShaderSource &source);
 	Shader(const ShaderSource &source);
 	virtual ~Shader();
 	virtual ~Shader();
@@ -149,9 +156,14 @@ public:
 	virtual void attach() = 0;
 	virtual void attach() = 0;
 
 
 	/**
 	/**
-	 * Attach the default shader.
+	 * Attach a default shader.
+	 **/
+	static void attachDefault(StandardShader defaultType);
+
+	/**
+	 * Gets whether any of the default shaders are currently active.
 	 **/
 	 **/
-	static void attachDefault();
+	static bool isDefaultActive();
 
 
 	/**
 	/**
 	 * Returns any warnings this Shader may have generated.
 	 * Returns any warnings this Shader may have generated.

+ 80 - 36
src/modules/graphics/SpriteBatch.cpp

@@ -44,7 +44,8 @@ SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, vertex::Usag
 	: texture(texture)
 	: texture(texture)
 	, size(size)
 	, size(size)
 	, next(0)
 	, next(0)
-	, color(0)
+	, color(255, 255, 255, 255)
+	, color_active(false)
 	, array_buf(nullptr)
 	, array_buf(nullptr)
 	, quad_indices(gfx, size)
 	, quad_indices(gfx, size)
 	, range_start(-1)
 	, range_start(-1)
@@ -53,26 +54,59 @@ SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, vertex::Usag
 	if (size <= 0)
 	if (size <= 0)
 		throw love::Exception("Invalid SpriteBatch size.");
 		throw love::Exception("Invalid SpriteBatch size.");
 
 
-	size_t vertex_size = sizeof(Vertex) * 4 * size;
+	if (texture == nullptr)
+		throw love::Exception("A texture must be used when creating a SpriteBatch.");
 
 
+	if (texture->getTextureType() == TEXTURE_2D_ARRAY)
+		vertex_format = vertex::CommonFormat::XYf_STPf_RGBAub;
+	else
+		vertex_format = vertex::CommonFormat::XYf_STf_RGBAub;
+
+	format_stride = vertex::getFormatStride(vertex_format);
+
+	size_t vertex_size = format_stride * 4 * size;
 	array_buf = gfx->newBuffer(vertex_size, nullptr, BUFFER_VERTEX, usage, Buffer::MAP_EXPLICIT_RANGE_MODIFY);
 	array_buf = gfx->newBuffer(vertex_size, nullptr, BUFFER_VERTEX, usage, Buffer::MAP_EXPLICIT_RANGE_MODIFY);
 }
 }
 
 
 SpriteBatch::~SpriteBatch()
 SpriteBatch::~SpriteBatch()
 {
 {
-	delete color;
 	delete array_buf;
 	delete array_buf;
 }
 }
 
 
 int SpriteBatch::add(const Matrix4 &m, int index /*= -1*/)
 int SpriteBatch::add(const Matrix4 &m, int index /*= -1*/)
 {
 {
+	return add(texture->getQuad(), m, index);
+}
+
+int SpriteBatch::add(Quad *quad, const Matrix4 &m, int index /*= -1*/)
+{
+	using namespace vertex;
+
+	if (vertex_format == CommonFormat::XYf_STPf_RGBAub)
+		return addLayer(quad->getLayer(), quad, m, index);
+
 	if (index < -1 || index >= size)
 	if (index < -1 || index >= size)
 		throw love::Exception("Invalid sprite index: %d", index + 1);
 		throw love::Exception("Invalid sprite index: %d", index + 1);
 
 
 	if (index == -1 && next >= size)
 	if (index == -1 && next >= size)
 		setBufferSize(size * 2);
 		setBufferSize(size * 2);
 
 
-	addv(texture->getVertices(), m, (index == -1) ? next : index);
+	const XYf_STf *quadverts = quad->getVertices();
+
+	// Always keep the VBO mapped when adding data (it'll be unmapped on draw.)
+	size_t offset = (index == -1 ? next : index) * format_stride * 4;
+	auto verts = (XYf_STf_RGBAub *) ((uint8 *) array_buf->map() + offset);
+
+	m.transform(verts, quadverts, 4);
+
+	for (int i = 0; i < 4; i++)
+	{
+		verts[i].s = quadverts[i].s;
+		verts[i].t = quadverts[i].t;
+		verts[i].color = color;
+	}
+
+	array_buf->setMappedRangeModified(offset, format_stride * 4);
 
 
 	// Increment counter.
 	// Increment counter.
 	if (index == -1)
 	if (index == -1)
@@ -81,15 +115,44 @@ int SpriteBatch::add(const Matrix4 &m, int index /*= -1*/)
 	return index;
 	return index;
 }
 }
 
 
-int SpriteBatch::addq(Quad *quad, const Matrix4 &m, int index /*= -1*/)
+int SpriteBatch::addLayer(int layer, const Matrix4 &m, int index)
+{
+	return addLayer(layer, texture->getQuad(), m, index);
+}
+
+int SpriteBatch::addLayer(int layer, Quad *quad, const Matrix4 &m, int index)
 {
 {
+	using namespace vertex;
+
+	if (vertex_format != CommonFormat::XYf_STPf_RGBAub)
+		throw love::Exception("addLayer can only be called on a SpriteBatch that uses an Array Texture!");
+
 	if (index < -1 || index >= size)
 	if (index < -1 || index >= size)
 		throw love::Exception("Invalid sprite index: %d", index + 1);
 		throw love::Exception("Invalid sprite index: %d", index + 1);
 
 
+	if (layer < 0 || layer >= texture->getLayerCount())
+		throw love::Exception("Invalid layer: %d (Texture has %d layers)", layer + 1, texture->getLayerCount());
+
 	if (index == -1 && next >= size)
 	if (index == -1 && next >= size)
 		setBufferSize(size * 2);
 		setBufferSize(size * 2);
 
 
-	addv(quad->getVertices(), m, (index == -1) ? next : index);
+	const XYf_STf *quadverts = quad->getVertices();
+
+	// Always keep the VBO mapped when adding data (it'll be unmapped on draw.)
+	size_t offset = (index == -1 ? next : index) * format_stride * 4;
+	auto verts = (XYf_STPf_RGBAub *) ((uint8 *) array_buf->map() + offset);
+
+	m.transform(verts, quadverts, 4);
+
+	for (int i = 0; i < 4; i++)
+	{
+		verts[i].s = quadverts[i].s;
+		verts[i].t = quadverts[i].t;
+		verts[i].p = (float) layer;
+		verts[i].color = color;
+	}
+
+	array_buf->setMappedRangeModified(offset, format_stride * 4);
 
 
 	// Increment counter.
 	// Increment counter.
 	if (index == -1)
 	if (index == -1)
@@ -111,6 +174,9 @@ void SpriteBatch::flush()
 
 
 void SpriteBatch::setTexture(Texture *newtexture)
 void SpriteBatch::setTexture(Texture *newtexture)
 {
 {
+	if (texture->getTextureType() != newtexture->getTextureType())
+		throw love::Exception("Texture must have the same texture type as the SpriteBatch's previous texture.");
+
 	texture.set(newtexture);
 	texture.set(newtexture);
 }
 }
 
 
@@ -121,20 +187,19 @@ Texture *SpriteBatch::getTexture() const
 
 
 void SpriteBatch::setColor(const Color &color)
 void SpriteBatch::setColor(const Color &color)
 {
 {
-	if (!this->color)
-		this->color = new Color(color);
-	else
-		*(this->color) = color;
+	color_active = true;
+	this->color = color;
 }
 }
 
 
 void SpriteBatch::setColor()
 void SpriteBatch::setColor()
 {
 {
-	delete color;
-	color = nullptr;
+	color_active = false;
+	color = Color(255, 255, 255, 255);
 }
 }
 
 
-const Color *SpriteBatch::getColor() const
+const Color &SpriteBatch::getColor(bool &active) const
 {
 {
+	active = color_active;
 	return color;
 	return color;
 }
 }
 
 
@@ -151,7 +216,7 @@ void SpriteBatch::setBufferSize(int newsize)
 	if (newsize == size)
 	if (newsize == size)
 		return;
 		return;
 
 
-	size_t vertex_size = sizeof(Vertex) * 4 * newsize;
+	size_t vertex_size = format_stride * 4 * newsize;
 	love::graphics::Buffer *new_array_buf = nullptr;
 	love::graphics::Buffer *new_array_buf = nullptr;
 
 
 	int new_next = std::min(next, newsize);
 	int new_next = std::min(next, newsize);
@@ -162,7 +227,7 @@ void SpriteBatch::setBufferSize(int newsize)
 		new_array_buf = gfx->newBuffer(vertex_size, nullptr, array_buf->getType(), array_buf->getUsage(), array_buf->getMapFlags());
 		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.
 		// Copy as much of the old data into the new GLBuffer as can fit.
-		size_t copy_size = sizeof(Vertex) * 4 * new_next;
+		size_t copy_size = format_stride * 4 * new_next;
 		array_buf->copyTo(0, copy_size, new_array_buf, 0);
 		array_buf->copyTo(0, copy_size, new_array_buf, 0);
 
 
 		quad_indices = QuadIndices(gfx, newsize);
 		quad_indices = QuadIndices(gfx, newsize);
@@ -233,26 +298,5 @@ bool SpriteBatch::getDrawRange(int &start, int &count) const
 	return true;
 	return true;
 }
 }
 
 
-void SpriteBatch::addv(const Vertex *v, const Matrix4 &m, int index)
-{
-	// Needed for colors.
-	Vertex sprite[4] = {v[0], v[1], v[2], v[3]};
-	const size_t sprite_size = 4 * sizeof(Vertex); // bytecount
-	
-	m.transform(sprite, sprite, 4);
-	
-	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.)
-	array_buf->map();
-	
-	array_buf->fill(index * sprite_size, sprite_size, sprite);
-}
-
 } // graphics
 } // graphics
 } // love
 } // love

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

@@ -54,7 +54,10 @@ public:
 	virtual ~SpriteBatch();
 	virtual ~SpriteBatch();
 
 
 	int add(const Matrix4 &m, int index = -1);
 	int add(const Matrix4 &m, int index = -1);
-	int addq(Quad *quad, const Matrix4 &m, int index = -1);
+	int add(Quad *quad, const Matrix4 &m, int index = -1);
+	int addLayer(int layer, const Matrix4 &m, int index = -1);
+	int addLayer(int layer, Quad *quad, const Matrix4 &m, int index = -1);
+
 	void clear();
 	void clear();
 
 
 	void flush();
 	void flush();
@@ -78,10 +81,9 @@ public:
 	void setColor();
 	void setColor();
 
 
 	/**
 	/**
-	 * Get the current color for this SpriteBatch. Returns NULL if no color is
-	 * set.
+	 * Get the current color for this SpriteBatch.
 	 **/
 	 **/
-	const Color *getColor() const;
+	const Color &getColor(bool &active) const;
 
 
 	/**
 	/**
 	 * Get the number of sprites currently in this SpriteBatch.
 	 * Get the number of sprites currently in this SpriteBatch.
@@ -117,8 +119,6 @@ protected:
 	 **/
 	 **/
 	void setBufferSize(int newsize);
 	void setBufferSize(int newsize);
 
 
-	void addv(const Vertex *v, const Matrix4 &m, int index);
-
 	StrongRef<Texture> texture;
 	StrongRef<Texture> texture;
 
 
 	// Max number of sprites in the batch.
 	// Max number of sprites in the batch.
@@ -129,7 +129,11 @@ protected:
 
 
 	// Current color. This color, if present, will be applied to the next
 	// Current color. This color, if present, will be applied to the next
 	// added sprite.
 	// added sprite.
-	Color *color;
+	Color color;
+	bool color_active;
+
+	vertex::CommonFormat vertex_format;
+	size_t format_stride;
 	
 	
 	love::graphics::Buffer *array_buf;
 	love::graphics::Buffer *array_buf;
 	QuadIndices quad_indices;
 	QuadIndices quad_indices;

+ 64 - 43
src/modules/graphics/Texture.cpp

@@ -61,7 +61,6 @@ Texture::Texture(TextureType texType)
 	, filter(defaultFilter)
 	, filter(defaultFilter)
 	, wrap()
 	, wrap()
 	, mipmapSharpness(defaultMipmapSharpness)
 	, mipmapSharpness(defaultMipmapSharpness)
-	, vertices()
 {
 {
 }
 }
 
 
@@ -69,32 +68,10 @@ Texture::~Texture()
 {
 {
 }
 }
 
 
-void Texture::initVertices()
+void Texture::initQuad()
 {
 {
-	for (int i = 0; i < 4; i++)
-		vertices[i].color = Color(255, 255, 255, 255);
-
-	// Vertices are ordered for use with triangle strips:
-	// 0---2
-	// | / |
-	// 1---3
-	vertices[0].x = 0.0f;
-	vertices[0].y = 0.0f;
-	vertices[1].x = 0.0f;
-	vertices[1].y = (float) height;
-	vertices[2].x = (float) width;
-	vertices[2].y = 0.0f;
-	vertices[3].x = (float) width;
-	vertices[3].y = (float) height;
-
-	vertices[0].s = 0.0f;
-	vertices[0].t = 0.0f;
-	vertices[1].s = 0.0f;
-	vertices[1].t = 1.0f;
-	vertices[2].s = 1.0f;
-	vertices[2].t = 0.0f;
-	vertices[3].s = 1.0f;
-	vertices[3].t = 1.0f;
+	Quad::Viewport v = {0, 0, (double) width, (double) height};
+	quad.set(new Quad(v, width, height), Acquire::NORETAIN);
 }
 }
 
 
 TextureType Texture::getTextureType() const
 TextureType Texture::getTextureType() const
@@ -109,37 +86,81 @@ PixelFormat Texture::getPixelFormat() const
 
 
 void Texture::draw(Graphics *gfx, const Matrix4 &m)
 void Texture::draw(Graphics *gfx, const Matrix4 &m)
 {
 {
-	drawv(gfx, m, vertices);
+	draw(gfx, quad, m);
 }
 }
 
 
-void Texture::drawq(Graphics *gfx, Quad *quad, const Matrix4 &m)
+void Texture::draw(Graphics *gfx, Quad *q, const Matrix4 &localTransform)
 {
 {
-	drawv(gfx, m, quad->getVertices());
-}
+	using namespace vertex;
 
 
-void Texture::drawv(Graphics *gfx, const Matrix4 &localTransform, const Vertex *v)
-{
-	if (Shader::current)
-		Shader::current->checkMainTextureType(texType);
+	if (texType == TEXTURE_2D_ARRAY)
+	{
+		drawLayer(gfx, q->getLayer(), q, localTransform);
+		return;
+	}
 
 
-	Matrix4 t(gfx->getTransform(), localTransform);
+	Color c = toColor(gfx->getColor());
+
+	Graphics::StreamDrawRequest req;
+	req.formats[0] = CommonFormat::XYf_STf_RGBAub;
+	req.indexMode = TriangleIndexMode::QUADS;
+	req.vertexCount = 4;
+	req.texture = this;
 
 
-	Vertex verts[4] = {v[0], v[1], v[2], v[3]};
-	t.transform(verts, v, 4);
+	Graphics::StreamVertexData data = gfx->requestStreamDraw(req);
 
 
-	Color c = toColor(gfx->getColor());
+	XYf_STf_RGBAub *verts = (XYf_STf_RGBAub *) data.stream[0];
+	const XYf_STf *quadverts = q->getVertices();
+
+	Matrix4 t(gfx->getTransform(), localTransform);
+	t.transform(verts, quadverts, 4);
 
 
 	for (int i = 0; i < 4; i++)
 	for (int i = 0; i < 4; i++)
+	{
+		verts[i].s = quadverts[i].s;
+		verts[i].t = quadverts[i].t;
 		verts[i].color = c;
 		verts[i].color = c;
+	}
+}
+
+void Texture::drawLayer(Graphics *gfx, int layer, const Matrix4 &m)
+{
+	drawLayer(gfx, layer, quad, m);
+}
+
+void Texture::drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &m)
+{
+	using namespace vertex;
+
+	if (texType != TEXTURE_2D_ARRAY)
+		throw love::Exception("drawLayer can only be used with Array Textures!");
+
+	if (layer < 0 || layer >= layers)
+		throw love::Exception("Invalid layer: %d (Texture has %d layers)", layer + 1, layers);
+
+	Color c = toColor(gfx->getColor());
 
 
 	Graphics::StreamDrawRequest req;
 	Graphics::StreamDrawRequest req;
-	req.formats[0] = vertex::CommonFormat::XYf_STf_RGBAub;
-	req.indexMode = vertex::TriangleIndexMode::QUADS;
+	req.formats[0] = CommonFormat::XYf_STPf_RGBAub;
+	req.indexMode = TriangleIndexMode::QUADS;
 	req.vertexCount = 4;
 	req.vertexCount = 4;
 	req.texture = this;
 	req.texture = this;
 
 
 	Graphics::StreamVertexData data = gfx->requestStreamDraw(req);
 	Graphics::StreamVertexData data = gfx->requestStreamDraw(req);
-	memcpy(data.stream[0], verts, sizeof(Vertex) * 4);
+
+	XYf_STPf_RGBAub *verts = (XYf_STPf_RGBAub *) data.stream[0];
+	const XYf_STf *quadverts = q->getVertices();
+
+	Matrix4 t(gfx->getTransform(), m);
+	t.transform(verts, quadverts, 4);
+
+	for (int i = 0; i < 4; i++)
+	{
+		verts[i].s = quadverts[i].s;
+		verts[i].t = quadverts[i].t;
+		verts[i].p = (float) layer;
+		verts[i].color = c;
+	}
 }
 }
 
 
 int Texture::getWidth() const
 int Texture::getWidth() const
@@ -197,9 +218,9 @@ float Texture::getMipmapSharpness() const
 	return mipmapSharpness;
 	return mipmapSharpness;
 }
 }
 
 
-const Vertex *Texture::getVertices() const
+Quad *Texture::getQuad() const
 {
 {
-	return vertices;
+	return quad;
 }
 }
 
 
 bool Texture::validateFilter(const Filter &f, bool mipmapsAllowed)
 bool Texture::validateFilter(const Filter &f, bool mipmapsAllowed)

+ 8 - 6
src/modules/graphics/Texture.h

@@ -112,7 +112,10 @@ public:
 	/**
 	/**
 	 * Draws the texture using the specified transformation with a Quad applied.
 	 * Draws the texture using the specified transformation with a Quad applied.
 	 **/
 	 **/
-	void drawq(Graphics *gfx, Quad *quad, const Matrix4 &m);
+	virtual void draw(Graphics *gfx, Quad *quad, const Matrix4 &m);
+
+	void drawLayer(Graphics *gfx, int layer, const Matrix4 &m);
+	virtual void drawLayer(Graphics *gfx, int layer, Quad *quad, const Matrix4 &m);
 
 
 	TextureType getTextureType() const;
 	TextureType getTextureType() const;
 	PixelFormat getPixelFormat() const;
 	PixelFormat getPixelFormat() const;
@@ -138,7 +141,7 @@ public:
 	virtual bool setMipmapSharpness(float sharpness) = 0;
 	virtual bool setMipmapSharpness(float sharpness) = 0;
 	float getMipmapSharpness() const;
 	float getMipmapSharpness() const;
 
 
-	virtual const Vertex *getVertices() const;
+	Quad *getQuad() const;
 
 
 	virtual ptrdiff_t getHandle() const = 0;
 	virtual ptrdiff_t getHandle() const = 0;
 
 
@@ -158,8 +161,7 @@ public:
 
 
 protected:
 protected:
 
 
-	void initVertices();
-	virtual void drawv(Graphics *gfx, const Matrix4 &localTransform, const Vertex *v);
+	void initQuad();
 
 
 	TextureType texType;
 	TextureType texType;
 
 
@@ -167,6 +169,7 @@ protected:
 
 
 	int width;
 	int width;
 	int height;
 	int height;
+
 	int depth;
 	int depth;
 	int layers;
 	int layers;
 	int mipmapCount;
 	int mipmapCount;
@@ -174,13 +177,12 @@ protected:
 	int pixelWidth;
 	int pixelWidth;
 	int pixelHeight;
 	int pixelHeight;
 
 
-
 	Filter filter;
 	Filter filter;
 	Wrap wrap;
 	Wrap wrap;
 
 
 	float mipmapSharpness;
 	float mipmapSharpness;
 
 
-	Vertex vertices[4];
+	StrongRef<Quad> quad;
 
 
 private:
 private:
 
 

+ 10 - 8
src/modules/graphics/Video.cpp

@@ -109,16 +109,18 @@ void Video::draw(Graphics *gfx, const Matrix4 &m)
 
 
 	gfx->flushStreamDraws();
 	gfx->flushStreamDraws();
 
 
-	love::graphics::Shader *shader = Shader::current;
-	bool usingdefaultshader = (shader == Shader::defaultShader);
+	bool usingdefaultshader = Shader::isDefaultActive();
+	Shader *prevdefaultshader = nullptr;
+
+	// If we're using the default shader, substitute the video version.
 	if (usingdefaultshader)
 	if (usingdefaultshader)
 	{
 	{
-		// If we're using the default shader, substitute the video version.
-		Shader::defaultVideoShader->attach();
-		shader = Shader::defaultVideoShader;
+		prevdefaultshader = Shader::current;
+		Shader::standardShaders[Shader::STANDARD_VIDEO]->attach();
 	}
 	}
 
 
-	shader->setVideoTextures(images[0], images[1], images[2]);
+	if (Shader::current != nullptr)
+		Shader::current->setVideoTextures(images[0], images[1], images[2]);
 
 
 	Graphics::StreamDrawRequest req;
 	Graphics::StreamDrawRequest req;
 	req.formats[0] = vertex::CommonFormat::XYf_STf_RGBAub;
 	req.formats[0] = vertex::CommonFormat::XYf_STf_RGBAub;
@@ -142,8 +144,8 @@ void Video::draw(Graphics *gfx, const Matrix4 &m)
 
 
 	gfx->flushStreamDraws();
 	gfx->flushStreamDraws();
 
 
-	if (usingdefaultshader)
-		Shader::defaultShader->attach();
+	if (usingdefaultshader && prevdefaultshader != nullptr)
+		prevdefaultshader->attach();
 }
 }
 
 
 void Video::update()
 void Video::update()

+ 1 - 1
src/modules/graphics/opengl/Canvas.cpp

@@ -140,7 +140,7 @@ Canvas::Canvas(const Settings &settings)
 
 
 	this->format = getSizedFormat(settings.format);
 	this->format = getSizedFormat(settings.format);
 
 
-	initVertices();
+	initQuad();
 	loadVolatile();
 	loadVolatile();
 
 
 	if (status != GL_FRAMEBUFFER_COMPLETE)
 	if (status != GL_FRAMEBUFFER_COMPLETE)

+ 23 - 13
src/modules/graphics/opengl/Graphics.cpp

@@ -268,25 +268,19 @@ bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, b
 	restoreState(states.back());
 	restoreState(states.back());
 
 
 	int gammacorrect = isGammaCorrect() ? 1 : 0;
 	int gammacorrect = isGammaCorrect() ? 1 : 0;
+	Shader::Language target = getShaderLanguageTarget();
 
 
 	// We always need a default shader.
 	// We always need a default shader.
-	if (!Shader::defaultShader)
+	for (int i = 0; i < Shader::STANDARD_MAX_ENUM; i++)
 	{
 	{
-		Shader::Language target = getShaderLanguageTarget();
-		Shader::defaultShader = newShader(defaultShaderCode[target][gammacorrect]);
-	}
-
-	// and a default video shader.
-	if (!Shader::defaultVideoShader)
-	{
-		Shader::Language target = getShaderLanguageTarget();
-		Shader::defaultVideoShader = newShader(defaultVideoShaderCode[target][gammacorrect]);
+		if (!Shader::standardShaders[i])
+			Shader::standardShaders[i] = newShader(defaultShaderCode[i][target][gammacorrect]);
 	}
 	}
 
 
 	// A shader should always be active, but the default shader shouldn't be
 	// A shader should always be active, but the default shader shouldn't be
 	// returned by getShader(), so we don't do setShader(defaultShader).
 	// returned by getShader(), so we don't do setShader(defaultShader).
 	if (!Shader::current)
 	if (!Shader::current)
-		Shader::defaultShader->attach();
+		Shader::standardShaders[Shader::STANDARD_DEFAULT]->attach();
 
 
 	return true;
 	return true;
 }
 }
@@ -343,8 +337,21 @@ void Graphics::flushStreamDraws()
 	if (sbstate.vertexCount == 0 && sbstate.indexCount == 0)
 	if (sbstate.vertexCount == 0 && sbstate.indexCount == 0)
 		return;
 		return;
 
 
-	if (Shader::current && sbstate.texture.get())
-		Shader::current->checkMainTextureType(sbstate.texture->getTextureType());
+	love::graphics::Shader *prevdefaultshader = nullptr;
+
+	if (sbstate.texture.get())
+	{
+		TextureType textype = sbstate.texture->getTextureType();
+
+		if (textype == TEXTURE_2D_ARRAY && Shader::isDefaultActive())
+		{
+			prevdefaultshader = Shader::current;
+			Shader::standardShaders[Shader::STANDARD_ARRAY]->attach();
+		}
+
+		if (Shader::current)
+			Shader::current->checkMainTextureType(textype);
+	}
 
 
 	OpenGL::TempDebugGroup debuggroup("Stream vertices flush and draw");
 	OpenGL::TempDebugGroup debuggroup("Stream vertices flush and draw");
 
 
@@ -425,6 +432,9 @@ void Graphics::flushStreamDraws()
 
 
 	streamBufferState.vertexCount = 0;
 	streamBufferState.vertexCount = 0;
 	streamBufferState.indexCount = 0;
 	streamBufferState.indexCount = 0;
+
+	if (prevdefaultshader != nullptr)
+		prevdefaultshader->attach();
 }
 }
 
 
 static void APIENTRY debugCB(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei /*len*/, const GLchar *msg, const GLvoid* /*usr*/)
 static void APIENTRY debugCB(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei /*len*/, const GLchar *msg, const GLvoid* /*usr*/)

+ 1 - 1
src/modules/graphics/opengl/Image.cpp

@@ -92,8 +92,8 @@ void Image::init(PixelFormat fmt, int w, int h, const Settings &settings)
 	if (getMipmapCount() > 1)
 	if (getMipmapCount() > 1)
 		filter.mipmap = defaultMipmapFilter;
 		filter.mipmap = defaultMipmapFilter;
 
 
+	initQuad();
 	loadVolatile();
 	loadVolatile();
-	initVertices();
 }
 }
 
 
 void Image::generateMipmaps()
 void Image::generateMipmaps()

+ 9 - 0
src/modules/graphics/opengl/OpenGL.cpp

@@ -650,6 +650,10 @@ void OpenGL::setVertexPointers(vertex::CommonFormat format, size_t stride, size_
 		glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STf, x)));
 		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)));
 		glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STf, s)));
 		break;
 		break;
+	case CommonFormat::XYf_STPf:
+		glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STPf, x)));
+		glVertexAttribPointer(ATTRIB_TEXCOORD, 3, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STPf, s)));
+		break;
 	case CommonFormat::XYf_STf_RGBAub:
 	case CommonFormat::XYf_STf_RGBAub:
 		glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STf_RGBAub, x)));
 		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_TEXCOORD, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STf_RGBAub, s)));
@@ -660,6 +664,11 @@ void OpenGL::setVertexPointers(vertex::CommonFormat format, size_t stride, size_
 		glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_UNSIGNED_SHORT, GL_TRUE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STus_RGBAub, s)));
 		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)));
 		glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STus_RGBAub, color.r)));
 		break;
 		break;
+	case CommonFormat::XYf_STPf_RGBAub:
+		glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STPf_RGBAub, x)));
+		glVertexAttribPointer(ATTRIB_TEXCOORD, 3, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STPf_RGBAub, s)));
+		glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, BUFFER_OFFSET(offset + offsetof(XYf_STPf_RGBAub, color.r)));
+		break;
 	}
 	}
 }
 }
 
 

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

@@ -60,8 +60,21 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
 
 
 	gfx->flushStreamDraws();
 	gfx->flushStreamDraws();
 
 
-	if (Shader::current && texture.get())
-		Shader::current->checkMainTextureType(texture->getTextureType());
+	Shader *prevdefaultshader = nullptr;
+
+	if (texture.get())
+	{
+		TextureType textype = texture->getTextureType();
+
+		if (textype == TEXTURE_2D_ARRAY && Shader::isDefaultActive())
+		{
+			prevdefaultshader = Shader::current;
+			Shader::standardShaders[Shader::STANDARD_ARRAY]->attach();
+		}
+
+		if (Shader::current)
+			Shader::current->checkMainTextureType(texture->getTextureType());
+	}
 
 
 	OpenGL::TempDebugGroup debuggroup("SpriteBatch draw");
 	OpenGL::TempDebugGroup debuggroup("SpriteBatch draw");
 
 
@@ -72,13 +85,19 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
 	// Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.)
 	// Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.)
 	array_buf->unmap();
 	array_buf->unmap();
 
 
-	CommonFormat format = CommonFormat::XYf_STf_RGBAub;
-	if (color == nullptr)
-		format = CommonFormat::XYf_STf;
+	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);
 	uint32 enabledattribs = getFormatFlags(format);
 
 
-	gl.setVertexPointers(format, array_buf, getFormatStride(CommonFormat::XYf_STf_RGBAub), 0);
+	gl.setVertexPointers(format, array_buf, format_stride, 0);
 
 
 	for (const auto &it : attached_attributes)
 	for (const auto &it : attached_attributes)
 	{
 	{
@@ -116,6 +135,9 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
 
 
 		gl.drawElements(GL_TRIANGLES, (GLsizei) quad_indices.getIndexCount(count), gltype, indices);
 		gl.drawElements(GL_TRIANGLES, (GLsizei) quad_indices.getIndexCount(count), gltype, indices);
 	}
 	}
+
+	if (prevdefaultshader != nullptr)
+		prevdefaultshader->attach();
 }
 }
 
 
 } // opengl
 } // opengl

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

@@ -30,8 +30,10 @@ namespace vertex
 
 
 static_assert(sizeof(Color) == 4, "sizeof(Color) incorrect!");
 static_assert(sizeof(Color) == 4, "sizeof(Color) incorrect!");
 static_assert(sizeof(XYf_STf) == sizeof(float)*2 + sizeof(float)*2, "sizeof(XYf_STf) incorrect!");
 static_assert(sizeof(XYf_STf) == sizeof(float)*2 + sizeof(float)*2, "sizeof(XYf_STf) incorrect!");
+static_assert(sizeof(XYf_STPf) == sizeof(float)*2 + sizeof(float)*3, "sizeof(XYf_STPf) incorrect!");
 static_assert(sizeof(XYf_STf_RGBAub) == sizeof(float)*2 + sizeof(float)*2 + sizeof(Color), "sizeof(XYf_STf_RGBAub) 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!");
 static_assert(sizeof(XYf_STus_RGBAub) == sizeof(float)*2 + sizeof(uint16)*2 + sizeof(Color), "sizeof(XYf_STus_RGBAub) incorrect!");
+static_assert(sizeof(XYf_STPf_RGBAub) == sizeof(float)*2 + sizeof(float)*3 + sizeof(Color), "sizeof(XYf_STPf_RGBAub) incorrect!");
 
 
 size_t getFormatStride(CommonFormat format)
 size_t getFormatStride(CommonFormat format)
 {
 {
@@ -45,10 +47,14 @@ size_t getFormatStride(CommonFormat format)
 		return sizeof(uint8) * 4;
 		return sizeof(uint8) * 4;
 	case CommonFormat::XYf_STf:
 	case CommonFormat::XYf_STf:
 		return sizeof(XYf_STf);
 		return sizeof(XYf_STf);
+	case CommonFormat::XYf_STPf:
+		return sizeof(XYf_STPf);
 	case CommonFormat::XYf_STf_RGBAub:
 	case CommonFormat::XYf_STf_RGBAub:
 		return sizeof(XYf_STf_RGBAub);
 		return sizeof(XYf_STf_RGBAub);
 	case CommonFormat::XYf_STus_RGBAub:
 	case CommonFormat::XYf_STus_RGBAub:
 		return sizeof(XYf_STus_RGBAub);
 		return sizeof(XYf_STus_RGBAub);
+	case CommonFormat::XYf_STPf_RGBAub:
+		return sizeof(XYf_STPf_RGBAub);
 	}
 	}
 }
 }
 
 
@@ -63,9 +69,11 @@ uint32 getFormatFlags(CommonFormat format)
 	case CommonFormat::RGBAub:
 	case CommonFormat::RGBAub:
 		return ATTRIBFLAG_COLOR;
 		return ATTRIBFLAG_COLOR;
 	case CommonFormat::XYf_STf:
 	case CommonFormat::XYf_STf:
+	case CommonFormat::XYf_STPf:
 		return ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD;
 		return ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD;
 	case CommonFormat::XYf_STf_RGBAub:
 	case CommonFormat::XYf_STf_RGBAub:
 	case CommonFormat::XYf_STus_RGBAub:
 	case CommonFormat::XYf_STus_RGBAub:
+	case CommonFormat::XYf_STPf_RGBAub:
 		return ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD | ATTRIBFLAG_COLOR;
 		return ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD | ATTRIBFLAG_COLOR;
 
 
 	}
 	}

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

@@ -97,8 +97,10 @@ enum class CommonFormat
 	XYf,
 	XYf,
 	RGBAub,
 	RGBAub,
 	XYf_STf,
 	XYf_STf,
+	XYf_STPf,
 	XYf_STf_RGBAub,
 	XYf_STf_RGBAub,
 	XYf_STus_RGBAub,
 	XYf_STus_RGBAub,
+	XYf_STPf_RGBAub,
 };
 };
 
 
 struct XYf_STf
 struct XYf_STf
@@ -107,6 +109,12 @@ struct XYf_STf
 	float s, t;
 	float s, t;
 };
 };
 
 
+struct XYf_STPf
+{
+	float x, y;
+	float s, t, p;
+};
+
 struct XYf_STf_RGBAub
 struct XYf_STf_RGBAub
 {
 {
 	float x, y;
 	float x, y;
@@ -121,6 +129,13 @@ struct XYf_STus_RGBAub
 	Color  color;
 	Color  color;
 };
 };
 
 
+struct XYf_STPf_RGBAub
+{
+	float x, y;
+	float s, t, p;
+	Color color;
+};
+
 size_t getFormatStride(CommonFormat format);
 size_t getFormatStride(CommonFormat format);
 uint32 getFormatFlags(CommonFormat format);
 uint32 getFormatFlags(CommonFormat format);
 size_t getIndexDataSize(IndexDataType type);
 size_t getIndexDataSize(IndexDataType type);

+ 69 - 68
src/modules/graphics/wrap_Graphics.cpp

@@ -828,6 +828,7 @@ int w_newQuad(lua_State *L)
 
 
 	double sw = 0.0f;
 	double sw = 0.0f;
 	double sh = 0.0f;
 	double sh = 0.0f;
+	int layer = 0;
 
 
 	if (luax_istype(L, 5, Texture::type))
 	if (luax_istype(L, 5, Texture::type))
 	{
 	{
@@ -835,6 +836,19 @@ int w_newQuad(lua_State *L)
 		sw = texture->getWidth();
 		sw = texture->getWidth();
 		sh = texture->getHeight();
 		sh = texture->getHeight();
 	}
 	}
+	else if (luax_istype(L, 6, Texture::type))
+	{
+		layer = (int) luaL_checknumber(L, 5) - 1;
+		Texture *texture = luax_checktexture(L, 6);
+		sw = texture->getWidth();
+		sh = texture->getHeight();
+	}
+	else if (!lua_isnoneornil(L, 7))
+	{
+		layer = (int) luaL_checknumber(L, 5) - 1;
+		sw = luaL_checknumber(L, 6);
+		sh = luaL_checknumber(L, 7);
+	}
 	else
 	else
 	{
 	{
 		sw = luaL_checknumber(L, 5);
 		sw = luaL_checknumber(L, 5);
@@ -842,6 +856,8 @@ int w_newQuad(lua_State *L)
 	}
 	}
 
 
 	Quad *quad = instance()->newQuad(v, sw, sh);
 	Quad *quad = instance()->newQuad(v, sw, sh);
+	quad->setLayer(layer);
+
 	luax_pushtype(L, quad);
 	luax_pushtype(L, quad);
 	quad->release();
 	quad->release();
 	return 1;
 	return 1;
@@ -1761,19 +1777,25 @@ int w_setDefaultShaderCode(lua_State *L)
 			lua_getfield(L, -1, "vertex");
 			lua_getfield(L, -1, "vertex");
 			lua_getfield(L, -2, "pixel");
 			lua_getfield(L, -2, "pixel");
 			lua_getfield(L, -3, "videopixel");
 			lua_getfield(L, -3, "videopixel");
+			lua_getfield(L, -4, "arraypixel");
 
 
 			Shader::ShaderSource code;
 			Shader::ShaderSource code;
-			code.vertex = luax_checkstring(L, -3);
-			code.pixel = luax_checkstring(L, -2);
+			code.vertex = luax_checkstring(L, -4);
+			code.pixel = luax_checkstring(L, -3);
 
 
 			Shader::ShaderSource videocode;
 			Shader::ShaderSource videocode;
-			videocode.vertex = luax_checkstring(L, -3);
-			videocode.pixel = luax_checkstring(L, -1);
+			videocode.vertex = luax_checkstring(L, -4);
+			videocode.pixel = luax_checkstring(L, -2);
 
 
-			lua_pop(L, 4);
+			Shader::ShaderSource arraycode;
+			arraycode.vertex = luax_checkstring(L, -4);
+			arraycode.pixel = luax_checkstring(L, -1);
+
+			lua_pop(L, 5);
 
 
-			Graphics::defaultShaderCode[lang][i] = code;
-			Graphics::defaultVideoShaderCode[lang][i] = videocode;
+			Graphics::defaultShaderCode[Shader::STANDARD_DEFAULT][lang][i] = code;
+			Graphics::defaultShaderCode[Shader::STANDARD_VIDEO][lang][i] = videocode;
+			Graphics::defaultShaderCode[Shader::STANDARD_ARRAY][lang][i] = arraycode;
 		}
 		}
 	}
 	}
 
 
@@ -1927,37 +1949,48 @@ int w_draw(lua_State *L)
 		startidx = 2;
 		startidx = 2;
 	}
 	}
 
 
-	if (luax_istype(L, startidx, math::Transform::type))
+	luax_checkstandardtransform(L, startidx, [&](const Matrix4 &m)
 	{
 	{
-		math::Transform *tf = luax_totype<math::Transform>(L, startidx);
-		luax_catchexcept(L, [&]() {
+		luax_catchexcept(L, [&]()
+		{
 			if (texture && quad)
 			if (texture && quad)
-				instance()->draw(texture, quad, tf->getMatrix());
+				instance()->draw(texture, quad, m);
 			else
 			else
-				instance()->draw(drawable, tf->getMatrix());
+				instance()->draw(drawable, m);
 		});
 		});
+	});
+
+	return 0;
+}
+
+int w_drawLayer(lua_State *L)
+{
+	Texture *texture = luax_checktexture(L, 1);
+	Quad *quad = nullptr;
+	int layer = (int) luaL_checknumber(L, 2) - 1;
+	int startidx = 3;
+
+	if (luax_istype(L, startidx, Quad::type))
+	{
+		texture = luax_checktexture(L, 1);
+		quad = luax_totype<Quad>(L, startidx);
+		startidx++;
 	}
 	}
-	else
+	else if (lua_isnil(L, startidx) && !lua_isnoneornil(L, startidx + 1))
 	{
 	{
-		float x  = (float) luaL_optnumber(L, startidx + 0, 0.0);
-		float y  = (float) luaL_optnumber(L, startidx + 1, 0.0);
-		float a  = (float) luaL_optnumber(L, startidx + 2, 0.0);
-		float sx = (float) luaL_optnumber(L, startidx + 3, 1.0);
-		float sy = (float) luaL_optnumber(L, startidx + 4, sx);
-		float ox = (float) luaL_optnumber(L, startidx + 5, 0.0);
-		float oy = (float) luaL_optnumber(L, startidx + 6, 0.0);
-		float kx = (float) luaL_optnumber(L, startidx + 7, 0.0);
-		float ky = (float) luaL_optnumber(L, startidx + 8, 0.0);
-
-		Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
+		return luax_typerror(L, startidx, "Quad");
+	}
 
 
-		luax_catchexcept(L, [&]() {
-			if (texture && quad)
-				instance()->draw(texture, quad, m);
-			else if (drawable)
-				instance()->draw(drawable, m);
+	luax_checkstandardtransform(L, startidx, [&](const Matrix4 &m)
+	{
+		luax_catchexcept(L, [&]()
+		{
+			if (quad)
+				instance()->drawLayer(texture, layer, quad, m);
+			else
+				instance()->drawLayer(texture, layer, m);
 		});
 		});
-	}
+	});
 
 
 	return 0;
 	return 0;
 }
 }
@@ -1967,27 +2000,10 @@ int w_drawInstanced(lua_State *L)
 	Mesh *t = luax_checkmesh(L, 1);
 	Mesh *t = luax_checkmesh(L, 1);
 	int instancecount = (int) luaL_checkinteger(L, 2);
 	int instancecount = (int) luaL_checkinteger(L, 2);
 
 
-	if (luax_istype(L, 3, math::Transform::type))
-	{
-		math::Transform *tf = luax_totype<math::Transform>(L, 3);
-		luax_catchexcept(L, [&]() { instance()->drawInstanced(t, tf->getMatrix(), instancecount); });
-	}
-	else
+	luax_checkstandardtransform(L, 3, [&](const Matrix4 &m)
 	{
 	{
-		float x  = (float) luaL_optnumber(L, 3,  0.0);
-		float y  = (float) luaL_optnumber(L, 4,  0.0);
-		float a  = (float) luaL_optnumber(L, 5,  0.0);
-		float sx = (float) luaL_optnumber(L, 6,  1.0);
-		float sy = (float) luaL_optnumber(L, 7,  sx);
-		float ox = (float) luaL_optnumber(L, 8,  0.0);
-		float oy = (float) luaL_optnumber(L, 9,  0.0);
-		float kx = (float) luaL_optnumber(L, 10, 0.0);
-		float ky = (float) luaL_optnumber(L, 11, 0.0);
-
-		Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
-
 		luax_catchexcept(L, [&]() { instance()->drawInstanced(t, m, instancecount); });
 		luax_catchexcept(L, [&]() { instance()->drawInstanced(t, m, instancecount); });
-	}
+	});
 
 
 	return 0;
 	return 0;
 }
 }
@@ -1997,27 +2013,11 @@ int w_print(lua_State *L)
 	std::vector<Font::ColoredString> str;
 	std::vector<Font::ColoredString> str;
 	luax_checkcoloredstring(L, 1, str);
 	luax_checkcoloredstring(L, 1, str);
 
 
-	if (luax_istype(L, 2, math::Transform::type))
+	luax_checkstandardtransform(L, 2, [&](const Matrix4 &m)
 	{
 	{
-		math::Transform *tf = luax_totype<math::Transform>(L, 2);
-		luax_catchexcept(L, [&](){ instance()->print(str, tf->getMatrix()); });
-	}
-	else
-	{
-		float x = (float)luaL_optnumber(L, 2, 0.0);
-		float y = (float)luaL_optnumber(L, 3, 0.0);
-		float angle = (float)luaL_optnumber(L, 4, 0.0f);
-		float sx = (float)luaL_optnumber(L, 5, 1.0f);
-		float sy = (float)luaL_optnumber(L, 6, sx);
-		float ox = (float)luaL_optnumber(L, 7, 0.0f);
-		float oy = (float)luaL_optnumber(L, 8, 0.0f);
-		float kx = (float)luaL_optnumber(L, 9, 0.0f);
-		float ky = (float)luaL_optnumber(L, 10, 0.0f);
-
-		Matrix4 m(x, y, angle, sx, sy, ox, oy, kx, ky);
-
 		luax_catchexcept(L, [&](){ instance()->print(str, m); });
 		luax_catchexcept(L, [&](){ instance()->print(str, m); });
-	}
+	});
+
 	return 0;
 	return 0;
 }
 }
 
 
@@ -2519,6 +2519,7 @@ static const luaL_Reg functions[] =
 	{ "captureScreenshot", w_captureScreenshot },
 	{ "captureScreenshot", w_captureScreenshot },
 
 
 	{ "draw", w_draw },
 	{ "draw", w_draw },
+	{ "drawLayer", w_drawLayer },
 	{ "drawInstanced", w_drawInstanced },
 	{ "drawInstanced", w_drawInstanced },
 
 
 	{ "print", w_print },
 	{ "print", w_print },

+ 6 - 0
src/modules/graphics/wrap_Graphics.lua

@@ -412,6 +412,11 @@ vec4 effect(vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord) {
 	videopixel = [[
 	videopixel = [[
 vec4 effect(vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord) {
 vec4 effect(vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord) {
 	return VideoTexel(texcoord) * vcolor;
 	return VideoTexel(texcoord) * vcolor;
+}]],
+	arraypixel = [[
+uniform ArrayImage MainTex;
+void effect() {
+	love_PixelColor = Texel(MainTex, VaryingTexCoord.xyz) * VaryingColor;
 }]],
 }]],
 }
 }
 
 
@@ -432,6 +437,7 @@ for lang, info in pairs(langs) do
 			vertex = createShaderStageCode("VERTEX", defaultcode.vertex, info.target, info.gles, false, gammacorrect),
 			vertex = createShaderStageCode("VERTEX", defaultcode.vertex, info.target, info.gles, false, gammacorrect),
 			pixel = createShaderStageCode("PIXEL", defaultcode.pixel, info.target, info.gles, false, gammacorrect, false),
 			pixel = createShaderStageCode("PIXEL", defaultcode.pixel, info.target, info.gles, false, gammacorrect, false),
 			videopixel = createShaderStageCode("PIXEL", defaultcode.videopixel, info.target, info.gles, false, gammacorrect, false),
 			videopixel = createShaderStageCode("PIXEL", defaultcode.videopixel, info.target, info.gles, false, gammacorrect, false),
+			arraypixel = createShaderStageCode("PIXEL", defaultcode.arraypixel, info.target, info.gles, false, gammacorrect, true),
 		}
 		}
 	end
 	end
 end
 end

+ 17 - 0
src/modules/graphics/wrap_Quad.cpp

@@ -74,11 +74,28 @@ int w_Quad_getTextureDimensions(lua_State *L)
 	return 2;
 	return 2;
 }
 }
 
 
+int w_Quad_setLayer(lua_State *L)
+{
+	Quad *quad = luax_checkquad(L, 1);
+	int layer = (int) luaL_checknumber(L, 2) - 1;
+	quad->setLayer(layer);
+	return 0;
+}
+
+int w_Quad_getLayer(lua_State *L)
+{
+	Quad *quad = luax_checkquad(L, 1);
+	lua_pushnumber(L, quad->getLayer() + 1);
+	return 1;
+}
+
 static const luaL_Reg w_Quad_functions[] =
 static const luaL_Reg w_Quad_functions[] =
 {
 {
 	{ "setViewport", w_Quad_setViewport },
 	{ "setViewport", w_Quad_setViewport },
 	{ "getViewport", w_Quad_getViewport },
 	{ "getViewport", w_Quad_getViewport },
 	{ "getTextureDimensions", w_Quad_getTextureDimensions },
 	{ "getTextureDimensions", w_Quad_getTextureDimensions },
+	{ "setLayer", w_Quad_setLayer },
+	{ "getLayer", w_Quad_getLayer },
 	{ 0, 0 }
 	{ 0, 0 }
 };
 };
 
 

+ 58 - 29
src/modules/graphics/wrap_SpriteBatch.cpp

@@ -23,7 +23,6 @@
 #include "Image.h"
 #include "Image.h"
 #include "Canvas.h"
 #include "Canvas.h"
 #include "wrap_Texture.h"
 #include "wrap_Texture.h"
-#include "math/wrap_Transform.h"
 
 
 namespace love
 namespace love
 {
 {
@@ -47,37 +46,44 @@ static inline int w_SpriteBatch_add_or_set(lua_State *L, SpriteBatch *t, int sta
 	else if (lua_isnil(L, startidx) && !lua_isnoneornil(L, startidx + 1))
 	else if (lua_isnil(L, startidx) && !lua_isnoneornil(L, startidx + 1))
 		return luax_typerror(L, startidx, "Quad");
 		return luax_typerror(L, startidx, "Quad");
 
 
-	if (luax_istype(L, startidx, math::Transform::type))
+	luax_checkstandardtransform(L, startidx, [&](const Matrix4 &m)
 	{
 	{
-		math::Transform *tf = luax_totype<math::Transform>(L, startidx);
-		luax_catchexcept(L, [&]() {
+		luax_catchexcept(L, [&]()
+		{
 			if (quad)
 			if (quad)
-				index = t->addq(quad, tf->getMatrix(), index);
+				index = t->add(quad, m, index);
 			else
 			else
-				index = t->add(tf->getMatrix(), index);
+				index = t->add(m, index);
 		});
 		});
+	});
+
+	return index;
+}
+
+static int w_SpriteBatch_addLayer_or_setLayer(lua_State *L, SpriteBatch *t, int startidx, int index)
+{
+	Quad *quad = nullptr;
+	int layer = (int) luaL_checknumber(L, startidx) - 1;
+	startidx++;
+
+	if (luax_istype(L, startidx, Quad::type))
+	{
+		quad = luax_totype<Quad>(L, startidx);
+		startidx++;
 	}
 	}
-	else
+	else if (lua_isnil(L, startidx) && !lua_isnoneornil(L, startidx + 1))
+		return luax_typerror(L, startidx, "Quad");
+
+	luax_checkstandardtransform(L, startidx, [&](const Matrix4 &m)
 	{
 	{
-		float x  = (float) luaL_optnumber(L, startidx + 0, 0.0);
-		float y  = (float) luaL_optnumber(L, startidx + 1, 0.0);
-		float a  = (float) luaL_optnumber(L, startidx + 2, 0.0);
-		float sx = (float) luaL_optnumber(L, startidx + 3, 1.0);
-		float sy = (float) luaL_optnumber(L, startidx + 4, sx);
-		float ox = (float) luaL_optnumber(L, startidx + 5, 0.0);
-		float oy = (float) luaL_optnumber(L, startidx + 6, 0.0);
-		float kx = (float) luaL_optnumber(L, startidx + 7, 0.0);
-		float ky = (float) luaL_optnumber(L, startidx + 8, 0.0);
-
-		Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
-
-		luax_catchexcept(L, [&]() {
+		luax_catchexcept(L, [&]()
+		{
 			if (quad)
 			if (quad)
-				index = t->addq(quad, m, index);
+				index = t->addLayer(layer, quad, m, index);
 			else
 			else
-				index = t->add(m, index);
+				index = t->addLayer(layer, m, index);
 		});
 		});
-	}
+	});
 
 
 	return index;
 	return index;
 }
 }
@@ -102,6 +108,26 @@ int w_SpriteBatch_set(lua_State *L)
 	return 0;
 	return 0;
 }
 }
 
 
+int w_SpriteBatch_addLayer(lua_State *L)
+{
+	SpriteBatch *t = luax_checkspritebatch(L, 1);
+
+	int index = w_SpriteBatch_addLayer_or_setLayer(L, t, 2, -1);
+	lua_pushinteger(L, index + 1);
+
+	return 1;
+}
+
+int w_SpriteBatch_setLayer(lua_State *L)
+{
+	SpriteBatch *t = luax_checkspritebatch(L, 1);
+	int index = (int) luaL_checknumber(L, 2) - 1;
+
+	w_SpriteBatch_addLayer_or_setLayer(L, t, 3, index);
+	
+	return 0;
+}
+
 int w_SpriteBatch_clear(lua_State *L)
 int w_SpriteBatch_clear(lua_State *L)
 {
 {
 	SpriteBatch *t = luax_checkspritebatch(L, 1);
 	SpriteBatch *t = luax_checkspritebatch(L, 1);
@@ -178,16 +204,17 @@ int w_SpriteBatch_setColor(lua_State *L)
 int w_SpriteBatch_getColor(lua_State *L)
 int w_SpriteBatch_getColor(lua_State *L)
 {
 {
 	SpriteBatch *t = luax_checkspritebatch(L, 1);
 	SpriteBatch *t = luax_checkspritebatch(L, 1);
-	const Color *color = t->getColor();
+	bool active = false;
+	const Color &color = t->getColor(active);
 
 
 	// getColor returns null if no color is set.
 	// getColor returns null if no color is set.
-	if (!color)
+	if (!active)
 		return 0;
 		return 0;
 
 
-	lua_pushnumber(L, (lua_Number) color->r / 255.0);
-	lua_pushnumber(L, (lua_Number) color->g / 255.0);
-	lua_pushnumber(L, (lua_Number) color->b / 255.0);
-	lua_pushnumber(L, (lua_Number) color->a / 255.0);
+	lua_pushnumber(L, (lua_Number) color.r / 255.0);
+	lua_pushnumber(L, (lua_Number) color.g / 255.0);
+	lua_pushnumber(L, (lua_Number) color.b / 255.0);
+	lua_pushnumber(L, (lua_Number) color.a / 255.0);
 
 
 	return 4;
 	return 4;
 }
 }
@@ -250,6 +277,8 @@ static const luaL_Reg w_SpriteBatch_functions[] =
 {
 {
 	{ "add", w_SpriteBatch_add },
 	{ "add", w_SpriteBatch_add },
 	{ "set", w_SpriteBatch_set },
 	{ "set", w_SpriteBatch_set },
+	{ "addLayer", w_SpriteBatch_addLayer },
+	{ "setLayer", w_SpriteBatch_setLayer },
 	{ "clear", w_SpriteBatch_clear },
 	{ "clear", w_SpriteBatch_clear },
 	{ "flush", w_SpriteBatch_flush },
 	{ "flush", w_SpriteBatch_flush },
 	{ "setTexture", w_SpriteBatch_setTexture },
 	{ "setTexture", w_SpriteBatch_setTexture },

+ 24 - 0
src/modules/graphics/wrap_SpriteBatch.h

@@ -22,12 +22,36 @@
 
 
 #include "common/runtime.h"
 #include "common/runtime.h"
 #include "SpriteBatch.h"
 #include "SpriteBatch.h"
+#include "math/wrap_Transform.h"
 
 
 namespace love
 namespace love
 {
 {
 namespace graphics
 namespace graphics
 {
 {
 
 
+template <typename T>
+static void luax_checkstandardtransform(lua_State *L, int idx, const T &func)
+{
+	if (luax_istype(L, idx, math::Transform::type))
+	{
+		math::Transform *tf = luax_totype<math::Transform>(L, idx);
+		func(tf->getMatrix());
+	}
+	else
+	{
+		float x  = (float) luaL_optnumber(L, idx + 0, 0.0);
+		float y  = (float) luaL_optnumber(L, idx + 1, 0.0);
+		float a  = (float) luaL_optnumber(L, idx + 2, 0.0);
+		float sx = (float) luaL_optnumber(L, idx + 3, 1.0);
+		float sy = (float) luaL_optnumber(L, idx + 4, sx);
+		float ox = (float) luaL_optnumber(L, idx + 5, 0.0);
+		float oy = (float) luaL_optnumber(L, idx + 6, 0.0);
+		float kx = (float) luaL_optnumber(L, idx + 7, 0.0);
+		float ky = (float) luaL_optnumber(L, idx + 8, 0.0);
+		func(Matrix4(x, y, a, sx, sy, ox, oy, kx, ky));
+	}
+}
+
 SpriteBatch *luax_checkspritebatch(lua_State *L, int idx);
 SpriteBatch *luax_checkspritebatch(lua_State *L, int idx);
 extern "C" int luaopen_spritebatch(lua_State *L);
 extern "C" int luaopen_spritebatch(lua_State *L);