Browse Source

Move some Canvas code to Texture, devirtualize Texture::draw.

Alex Szpakowski 5 years ago
parent
commit
b46b3ed552

+ 14 - 0
src/common/pixelformat.cpp

@@ -137,6 +137,20 @@ bool isPixelFormatStencil(PixelFormat format)
 	return format == PIXELFORMAT_STENCIL8 || format == PIXELFORMAT_DEPTH24_UNORM_STENCIL8 || format == PIXELFORMAT_DEPTH32_FLOAT_STENCIL8;
 }
 
+PixelFormat getSRGBPixelFormat(PixelFormat format)
+{
+	if (format == PIXELFORMAT_RGBA8_UNORM)
+		return PIXELFORMAT_sRGBA8_UNORM;
+	return format;
+}
+
+PixelFormat getLinearPixelFormat(PixelFormat format)
+{
+	if (format == PIXELFORMAT_sRGBA8_UNORM)
+		return PIXELFORMAT_RGBA8_UNORM;
+	return format;
+}
+
 size_t getPixelFormatSize(PixelFormat format)
 {
 	switch (format)

+ 10 - 0
src/common/pixelformat.h

@@ -132,6 +132,16 @@ bool isPixelFormatDepth(PixelFormat format);
  **/
 bool isPixelFormatStencil(PixelFormat format);
 
+/**
+ * Gets the sRGB version of a linear pixel format, if applicable.
+ **/
+PixelFormat getSRGBPixelFormat(PixelFormat format);
+
+/**
+ * Gets the linear version of a sRGB pixel format, if applicable.
+ **/
+PixelFormat getLinearPixelFormat(PixelFormat format);
+
 /**
  * Gets the size in bytes of the specified pixel format.
  * NOTE: Currently returns 0 for compressed formats.

+ 6 - 25
src/modules/graphics/Canvas.cpp

@@ -27,13 +27,15 @@ namespace graphics
 {
 
 love::Type Canvas::type("Canvas", &Texture::type);
-int Canvas::canvasCount = 0;
 
 Canvas::Canvas(const Settings &settings)
 	: Texture(settings.type)
 {
 	this->settings = settings;
 
+	renderTarget = true;
+	sRGB = false;
+
 	width = settings.width;
 	height = settings.height;
 	pixelWidth = (int) ((width * settings.dpiScale) + 0.5);
@@ -69,7 +71,7 @@ Canvas::Canvas(const Settings &settings)
 	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 	const Graphics::Capabilities &caps = gfx->getCapabilities();
 
-	if (!gfx->isPixelFormatSupported(format, true, readable, false))
+	if (!gfx->isPixelFormatSupported(format, renderTarget, readable, sRGB))
 	{
 		const char *fstr = "rgba8";
 		const char *readablestr = "";
@@ -93,13 +95,10 @@ Canvas::Canvas(const Settings &settings)
 	}
 
 	validateDimensions(true);
-
-	canvasCount++;
 }
 
 Canvas::~Canvas()
 {
-	canvasCount--;
 }
 
 Canvas::MipmapMode Canvas::getMipmapMode() const
@@ -131,12 +130,10 @@ love::image::ImageData *Canvas::newImageData(love::image::Image *module, int sli
 	}
 
 	Graphics *gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
-	if (gfx != nullptr && gfx->isCanvasActive(this))
+	if (gfx != nullptr && gfx->isRenderTargetActive(this))
 		throw love::Exception("Canvas:newImageData cannot be called while that Canvas is currently active.");
 
-	PixelFormat dataformat = getPixelFormat();
-	if (dataformat == PIXELFORMAT_sRGBA8_UNORM)
-		dataformat = PIXELFORMAT_RGBA8_UNORM;
+	PixelFormat dataformat = getLinearPixelFormat(getPixelFormat());
 
 	if (!image::ImageData::validPixelFormat(dataformat))
 	{
@@ -148,22 +145,6 @@ love::image::ImageData *Canvas::newImageData(love::image::Image *module, int sli
 	return module->newImageData(r.w, r.h, dataformat);
 }
 
-void Canvas::draw(Graphics *gfx, Quad *q, const Matrix4 &t)
-{
-	if (gfx->isCanvasActive(this))
-		throw love::Exception("Cannot render a Canvas to itself!");
-
-	Texture::draw(gfx, q, t);
-}
-
-void Canvas::drawLayer(Graphics *gfx, int layer, Quad *quad, const Matrix4 &m)
-{
-	if (gfx->isCanvasActive(this, layer))
-		throw love::Exception("Cannot render a Canvas to itself!");
-
-	Texture::drawLayer(gfx, layer, quad, m);
-}
-
 bool Canvas::getConstant(const char *in, MipmapMode &out)
 {
 	return mipmapModes.find(in, out);

+ 0 - 6
src/modules/graphics/Canvas.h

@@ -81,16 +81,10 @@ public:
 	int getRequestedMSAA() const;
 
 	virtual love::image::ImageData *newImageData(love::image::Image *module, int slice, int mipmap, const Rect &rect);
-	virtual void generateMipmaps() = 0;
 
 	virtual int getMSAA() const = 0;
 	virtual ptrdiff_t getRenderTargetHandle() const = 0;
 
-	void draw(Graphics *gfx, Quad *q, const Matrix4 &t) override;
-	void drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &t) override;
-
-	static int canvasCount;
-
 	static bool getConstant(const char *in, MipmapMode &out);
 	static bool getConstant(MipmapMode in, const char *&out);
 	static std::vector<std::string> getConstants(MipmapMode);

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

@@ -761,33 +761,33 @@ bool Graphics::isCanvasActive() const
 	return !rts.colors.empty() || rts.depthStencil.canvas != nullptr;
 }
 
-bool Graphics::isCanvasActive(love::graphics::Canvas *canvas) const
+bool Graphics::isRenderTargetActive(Texture *texture) const
 {
 	const auto &rts = states.back().renderTargets;
 
 	for (const auto &rt : rts.colors)
 	{
-		if (rt.canvas.get() == canvas)
+		if (rt.canvas.get() == texture)
 			return true;
 	}
 
-	if (rts.depthStencil.canvas.get() == canvas)
+	if (rts.depthStencil.canvas.get() == texture)
 		return true;
 
 	return false;
 }
 
-bool Graphics::isCanvasActive(Canvas *canvas, int slice) const
+bool Graphics::isRenderTargetActive(Texture *texture, int slice) const
 {
 	const auto &rts = states.back().renderTargets;
 
 	for (const auto &rt : rts.colors)
 	{
-		if (rt.canvas.get() == canvas && rt.slice == slice)
+		if (rt.canvas.get() == texture && rt.slice == slice)
 			return true;
 	}
 
-	if (rts.depthStencil.canvas.get() == canvas && rts.depthStencil.slice == slice)
+	if (rts.depthStencil.canvas.get() == texture && rts.depthStencil.slice == slice)
 		return true;
 
 	return false;
@@ -1593,8 +1593,7 @@ Graphics::Stats Graphics::getStats() const
 
 	stats.canvasSwitches = canvasSwitchCount;
 	stats.drawCallsBatched = drawCallsBatched;
-	stats.canvases = Canvas::canvasCount;
-	stats.images = Image::imageCount;
+	stats.textures = Texture::textureCount;
 	stats.fonts = Font::fontCount;
 	stats.textureMemory = Texture::totalGraphicsMemory;
 	

+ 4 - 5
src/modules/graphics/Graphics.h

@@ -202,8 +202,7 @@ public:
 		int drawCallsBatched;
 		int canvasSwitches;
 		int shaderSwitches;
-		int canvases;
-		int images;
+		int textures;
 		int fonts;
 		int64 textureMemory;
 	};
@@ -551,8 +550,8 @@ public:
 
 	RenderTargets getCanvas() const;
 	bool isCanvasActive() const;
-	bool isCanvasActive(Canvas *canvas) const;
-	bool isCanvasActive(Canvas *canvas, int slice) const;
+	bool isRenderTargetActive(Texture *texture) const;
+	bool isRenderTargetActive(Texture *texture, int slice) const;
 
 	/**
 	 * Scissor defines a box such that everything outside that box is discarded
@@ -781,7 +780,7 @@ public:
 	/**
 	 * Converts PIXELFORMAT_NORMAL and PIXELFORMAT_HDR into a real format.
 	 **/
-	virtual PixelFormat getSizedFormat(PixelFormat format) const = 0;
+	virtual PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable, bool sRGB) const = 0;
 
 	/**
 	 * Gets whether the specified pixel format is supported.

+ 3 - 17
src/modules/graphics/Image.cpp

@@ -31,15 +31,14 @@ namespace graphics
 
 love::Type Image::type("Image", &Texture::type);
 
-int Image::imageCount = 0;
-
 Image::Image(const Slices &data, const Settings &settings, bool validatedata)
 	: Texture(data.getTextureType())
 	, settings(settings)
 	, mipmapsType(settings.mipmaps ? MIPMAPS_GENERATED : MIPMAPS_NONE)
-	, sRGB(isGammaCorrect() && !settings.linear)
 	, usingDefaultTexture(false)
 {
+	renderTarget = false;
+	sRGB = isGammaCorrect() && !settings.linear;
 	if (validatedata && data.validate() && data.getMipmapCount() > 1)
 		mipmapsType = MIPMAPS_DATA;
 }
@@ -72,13 +71,12 @@ Image::Image(const Slices &slices, const Settings &settings)
 
 Image::~Image()
 {
-	--imageCount;
 }
 
 void Image::init(PixelFormat fmt, int w, int h, const Settings &settings)
 {
 	Graphics *gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
-	if (gfx != nullptr && !gfx->isPixelFormatSupported(fmt, false, true, sRGB))
+	if (gfx != nullptr && !gfx->isPixelFormatSupported(fmt, renderTarget, readable, sRGB))
 	{
 		const char *str;
 		if (love::getConstant(fmt, str))
@@ -104,8 +102,6 @@ void Image::init(PixelFormat fmt, int w, int h, const Settings &settings)
 	mipmapCount = mipmapsType == MIPMAPS_NONE ? 1 : getTotalMipmapCount(w, h, depth);
 
 	initQuad();
-
-	++imageCount;
 }
 
 void Image::uploadImageData(love::image::ImageDataBase *d, int level, int slice, int x, int y)
@@ -172,16 +168,6 @@ void Image::replacePixels(const void *data, size_t size, int slice, int mipmap,
 		generateMipmaps();
 }
 
-bool Image::isCompressed() const
-{
-	return isPixelFormatCompressed(format);
-}
-
-bool Image::isFormatLinear() const
-{
-	return isGammaCorrect() && !sRGB && format != PIXELFORMAT_sRGBA8_UNORM;
-}
-
 Image::MipmapsType Image::getMipmapsType() const
 {
 	return mipmapsType;

+ 0 - 6
src/modules/graphics/Image.h

@@ -58,11 +58,8 @@ public:
 	void replacePixels(const void *data, size_t size, int slice, int mipmap, const Rect &rect, bool reloadmipmaps);
 
 	bool isFormatLinear() const;
-	bool isCompressed() const;
 	MipmapsType getMipmapsType() const;
 
-	static int imageCount;
-
 	static bool getConstant(const char *in, SettingType &out);
 	static bool getConstant(SettingType in, const char *&out);
 	static const char *getConstant(SettingType in);
@@ -76,13 +73,10 @@ protected:
 	void uploadImageData(love::image::ImageDataBase *d, int level, int slice, int x, int y);
 	virtual void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r, love::image::ImageDataBase *imgd = nullptr) = 0;
 
-	virtual void generateMipmaps() = 0;
-
 	// The settings used to initialize this Image.
 	Settings settings;
 
 	MipmapsType mipmapsType;
-	bool sRGB;
 
 	// True if the image wasn't able to be properly created and it had to fall
 	// back to a default texture.

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

@@ -156,12 +156,15 @@ std::vector<std::string> SamplerState::getConstants(WrapMode)
 }
 
 love::Type Texture::type("Texture", &Drawable::type);
+int Texture::textureCount = 0;
 int64 Texture::totalGraphicsMemory = 0;
 
 Texture::Texture(TextureType texType)
 	: texType(texType)
 	, format(PIXELFORMAT_UNKNOWN)
+	, renderTarget(false)
 	, readable(true)
+	, sRGB(false)
 	, width(0)
 	, height(0)
 	, depth(1)
@@ -175,10 +178,12 @@ Texture::Texture(TextureType texType)
 	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 	if (gfx != nullptr)
 		samplerState = gfx->getDefaultSamplerState();
+	++textureCount;
 }
 
 Texture::~Texture()
 {
+	--textureCount;
 	setGraphicsMemorySize(0);
 }
 
@@ -207,11 +212,26 @@ PixelFormat Texture::getPixelFormat() const
 	return format;
 }
 
+bool Texture::isRenderTarget() const
+{
+	return renderTarget;
+}
+
 bool Texture::isReadable() const
 {
 	return readable;
 }
 
+bool Texture::isCompressed() const
+{
+	return isPixelFormatCompressed(format);
+}
+
+bool Texture::isFormatLinear() const
+{
+	return isGammaCorrect() && !sRGB && format != PIXELFORMAT_sRGBA8_UNORM;
+}
+
 bool Texture::isValidSlice(int slice) const
 {
 	if (slice < 0)
@@ -241,6 +261,9 @@ void Texture::draw(Graphics *gfx, Quad *q, const Matrix4 &localTransform)
 	if (!readable)
 		throw love::Exception("Textures with non-readable formats cannot be drawn.");
 
+	if (renderTarget && gfx->isRenderTargetActive(this))
+		throw love::Exception("Cannot render a Texture to itself.");
+
 	if (texType == TEXTURE_2D_ARRAY)
 	{
 		drawLayer(gfx, q->getLayer(), q, localTransform);
@@ -291,6 +314,9 @@ void Texture::drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &m)
 	if (!readable)
 		throw love::Exception("Textures with non-readable formats cannot be drawn.");
 
+	if (renderTarget && gfx->isRenderTargetActive(this, layer))
+		throw love::Exception("Cannot render a Texture to itself.");
+
 	if (texType != TEXTURE_2D_ARRAY)
 		throw love::Exception("drawLayer can only be used with Array Textures!");
 

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

@@ -126,6 +126,7 @@ class Texture : public Drawable, public Resource
 public:
 
 	static love::Type type;
+	static int textureCount;
 
 	enum MipmapsType
 	{
@@ -175,16 +176,20 @@ public:
 	/**
 	 * Draws the texture using the specified transformation with a Quad applied.
 	 **/
-	virtual void draw(Graphics *gfx, Quad *quad, const Matrix4 &m);
+	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);
+	void drawLayer(Graphics *gfx, int layer, Quad *quad, const Matrix4 &m);
 
 	TextureType getTextureType() const;
 	PixelFormat getPixelFormat() const;
 
+	bool isRenderTarget() const;
 	bool isReadable() const;
 
+	bool isCompressed() const;
+	bool isFormatLinear() const;
+
 	bool isValidSlice(int slice) const;
 
 	int getWidth(int mip = 0) const;
@@ -201,6 +206,8 @@ public:
 	virtual void setSamplerState(const SamplerState &s);
 	const SamplerState &getSamplerState() const;
 
+	virtual void generateMipmaps() = 0;
+
 	Quad *getQuad() const;
 
 	static int getTotalMipmapCount(int w, int h);
@@ -220,8 +227,11 @@ protected:
 	TextureType texType;
 
 	PixelFormat format;
+	bool renderTarget;
 	bool readable;
 
+	bool sRGB;
+
 	int width;
 	int height;
 

+ 33 - 42
src/modules/graphics/opengl/Canvas.cpp

@@ -31,7 +31,7 @@ namespace graphics
 namespace opengl
 {
 
-static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat format, GLuint texture, int layers, int nb_mips)
+static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat format, GLuint texture, int layers)
 {
 	// get currently bound fbo to reset to it later
 	GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
@@ -60,42 +60,35 @@ static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat fo
 		// Make sure all faces and layers of the texture are initialized to
 		// transparent black. This is unfortunately probably pretty slow for
 		// 2D-array and 3D textures with a lot of layers...
-		for (int mip = nb_mips - 1; mip >= 0; mip--)
+		for (int layer = layers - 1; layer >= 0; layer--)
 		{
-			int nlayers = layers;
-			if (texType == TEXTURE_VOLUME)
-				nlayers = std::max(layers >> mip, 1);
-
-			for (int layer = nlayers - 1; layer >= 0; layer--)
+			for (int face = faces - 1; face >= 0; face--)
 			{
-				for (int face = faces - 1; face >= 0; face--)
+				for (GLenum attachment : fmt.framebufferAttachments)
+				{
+					if (attachment == GL_NONE)
+						continue;
+
+					gl.framebufferTexture(attachment, texType, texture, 0, layer, face);
+				}
+
+				if (isPixelFormatDepthStencil(format))
+				{
+					bool hadDepthWrites = gl.hasDepthWrites();
+					if (!hadDepthWrites) // glDepthMask also affects glClear.
+						gl.setDepthWrites(true);
+
+					gl.clearDepth(1.0);
+					glClearStencil(0);
+					glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+					if (!hadDepthWrites)
+						gl.setDepthWrites(hadDepthWrites);
+				}
+				else
 				{
-					for (GLenum attachment : fmt.framebufferAttachments)
-					{
-						if (attachment == GL_NONE)
-							continue;
-
-						gl.framebufferTexture(attachment, texType, texture, mip, layer, face);
-					}
-
-					if (isPixelFormatDepthStencil(format))
-					{
-						bool hadDepthWrites = gl.hasDepthWrites();
-						if (!hadDepthWrites) // glDepthMask also affects glClear.
-							gl.setDepthWrites(true);
-
-						gl.clearDepth(1.0);
-						glClearStencil(0);
-						glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
-
-						if (!hadDepthWrites)
-							gl.setDepthWrites(hadDepthWrites);
-					}
-					else
-					{
-						glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-						glClear(GL_COLOR_BUFFER_BIT);
-					}
+					glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+					glClear(GL_COLOR_BUFFER_BIT);
 				}
 			}
 		}
@@ -198,7 +191,7 @@ Canvas::Canvas(const Settings &settings)
 {
 	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 	if (gfx != nullptr)
-		format = gfx->getSizedFormat(format);
+		format = gfx->getSizedFormat(format, renderTarget, readable, sRGB);
 
 	initQuad();
 	loadVolatile();
@@ -259,8 +252,8 @@ bool Canvas::loadVolatile()
 			return false;
 		}
 
-		// Create a canvas-local FBO used for glReadPixels as well as MSAA blitting.
-		status = createFBO(fbo, texType, format, texture, texType == TEXTURE_VOLUME ? depth : layers, mipmapCount);
+		// Create a local FBO used for glReadPixels as well as MSAA blitting.
+		status = createFBO(fbo, texType, format, texture, texType == TEXTURE_VOLUME ? depth : layers);
 
 		if (status != GL_FRAMEBUFFER_COMPLETE)
 		{
@@ -287,6 +280,9 @@ bool Canvas::loadVolatile()
 
 	setGraphicsMemorySize(memsize);
 
+	if (getMipmapCount() > 1)
+		generateMipmaps();
+
 	return true;
 }
 
@@ -390,11 +386,6 @@ void Canvas::generateMipmaps()
 	glGenerateMipmap(gltextype);
 }
 
-bool Canvas::isMultiFormatMultiCanvasSupported()
-{
-	return gl.getMaxRenderTargets() > 1 && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object);
-}
-
 } // opengl
 } // graphics
 } // love

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

@@ -68,8 +68,6 @@ public:
 		return fbo;
 	}
 
-	static bool isMultiFormatMultiCanvasSupported();
-
 private:
 
 	GLuint fbo;

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

@@ -1362,7 +1362,7 @@ void Graphics::getAPIStats(int &shaderswitches) const
 
 void Graphics::initCapabilities()
 {
-	capabilities.features[FEATURE_MULTI_CANVAS_FORMATS] = Canvas::isMultiFormatMultiCanvasSupported();
+	capabilities.features[FEATURE_MULTI_CANVAS_FORMATS] = gl.isMultiFormatMRTSupported();
 	capabilities.features[FEATURE_CLAMP_ZERO] = gl.isClampZeroOneTextureWrapSupported();
 	capabilities.features[FEATURE_BLENDMINMAX] = GLAD_VERSION_1_4 || GLAD_ES_VERSION_3_0 || GLAD_EXT_blend_minmax;
 	capabilities.features[FEATURE_LIGHTEN] = capabilities.features[FEATURE_BLENDMINMAX];
@@ -1388,14 +1388,14 @@ void Graphics::initCapabilities()
 		capabilities.textureTypes[i] = gl.isTextureTypeSupported((TextureType) i);
 }
 
-PixelFormat Graphics::getSizedFormat(PixelFormat format) const
+PixelFormat Graphics::getSizedFormat(PixelFormat format, bool rendertarget, bool readable, bool sRGB) const
 {
 	switch (format)
 	{
 	case PIXELFORMAT_NORMAL:
 		if (isGammaCorrect())
 			return PIXELFORMAT_sRGBA8_UNORM;
-		else if (!OpenGL::isPixelFormatSupported(PIXELFORMAT_RGBA8_UNORM, true, true, false))
+		else if (!OpenGL::isPixelFormatSupported(PIXELFORMAT_RGBA8_UNORM, rendertarget, readable, sRGB))
 			// 32-bit render targets don't have guaranteed support on GLES2.
 			return PIXELFORMAT_RGBA4_UNORM;
 		else
@@ -1409,14 +1409,14 @@ PixelFormat Graphics::getSizedFormat(PixelFormat format) const
 
 bool Graphics::isPixelFormatSupported(PixelFormat format, bool rendertarget, bool readable, bool sRGB)
 {
-	format = getSizedFormat(format);
-
 	if (sRGB && format == PIXELFORMAT_RGBA8_UNORM)
 	{
 		format = PIXELFORMAT_sRGBA8_UNORM;
 		sRGB = false;
 	}
 
+	format = getSizedFormat(format, rendertarget, readable, sRGB);
+
 	OptionalBool &supported = supportedFormats[format][rendertarget ? 1 : 0][readable ? 1 : 0][sRGB ? 1 : 0];
 
 	if (supported.hasValue)

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

@@ -104,7 +104,7 @@ public:
 
 	void setWireframe(bool enable) override;
 
-	PixelFormat getSizedFormat(PixelFormat format) const override;
+	PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable, bool sRGB) const override;
 	bool isPixelFormatSupported(PixelFormat format, bool rendertarget, bool readable, bool sRGB = false) override;
 	Renderer getRenderer() const override;
 	RendererInfo getRendererInfo() const override;

+ 10 - 7
src/modules/graphics/opengl/Image.cpp

@@ -105,9 +105,6 @@ void Image::loadData()
 	if (!isCompressed())
 		gl.rawTexStorage(texType, mipcount, format, sRGB, pixelWidth, pixelHeight, texType == TEXTURE_VOLUME ? depth : layers);
 
-	if (mipmapsType == MIPMAPS_GENERATED)
-		mipcount = 1;
-
 	int w = pixelWidth;
 	int h = pixelHeight;
 	int d = depth;
@@ -123,17 +120,23 @@ void Image::loadData()
 			if (texType == TEXTURE_2D_ARRAY || texType == TEXTURE_VOLUME)
 			{
 				for (int slice = 0; slice < slices.getSliceCount(mip); slice++)
-					mipsize += slices.get(slice, mip)->getSize();
+				{
+					auto id = slices.get(slice, mip);
+					if (id != nullptr)
+						mipsize += id->getSize();
+				}
 			}
 
-			GLenum gltarget = OpenGL::getGLTextureType(texType);
-			glCompressedTexImage3D(gltarget, mip, fmt.internalformat, w, h, d, 0, mipsize, nullptr);
+			if (mipsize > 0)
+			{
+				GLenum gltarget = OpenGL::getGLTextureType(texType);
+				glCompressedTexImage3D(gltarget, mip, fmt.internalformat, w, h, d, 0, mipsize, nullptr);
+			}
 		}
 
 		for (int slice = 0; slice < slicecount; slice++)
 		{
 			love::image::ImageDataBase *id = slices.get(slice, mip);
-
 			if (id != nullptr)
 				uploadImageData(id, mip, slice, 0, 0);
 		}

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

@@ -50,11 +50,11 @@ public:
 	ptrdiff_t getHandle() const override;
 
 	void setSamplerState(const SamplerState &s) override;
+	void generateMipmaps() override;
 
 private:
 
 	void uploadByteData(PixelFormat pixelformat, const void *data, size_t size, int level, int slice, const Rect &r, love::image::ImageDataBase *imgd = nullptr) override;
-	void generateMipmaps() override;
 
 	void loadDefaultTexture();
 	void loadData();

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

@@ -1328,6 +1328,11 @@ bool OpenGL::isBaseVertexSupported() const
 	return baseVertexSupported;
 }
 
+bool OpenGL::isMultiFormatMRTSupported() const
+{
+	return getMaxRenderTargets() > 1 && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object);
+}
+
 int OpenGL::getMax2DTextureSize() const
 {
 	return std::max(max2DTextureSize, 1);

+ 1 - 0
src/modules/graphics/opengl/OpenGL.h

@@ -347,6 +347,7 @@ public:
 	bool isDepthCompareSampleSupported() const;
 	bool isSamplerLODBiasSupported() const;
 	bool isBaseVertexSupported() const;
+	bool isMultiFormatMRTSupported() const;
 
 	/**
 	 * Returns the maximum supported width or height of a texture.

+ 2 - 5
src/modules/graphics/wrap_Graphics.cpp

@@ -2394,11 +2394,8 @@ int w_getStats(lua_State *L)
 	lua_pushinteger(L, stats.shaderSwitches);
 	lua_setfield(L, -2, "shaderswitches");
 
-	lua_pushinteger(L, stats.canvases);
-	lua_setfield(L, -2, "canvases");
-
-	lua_pushinteger(L, stats.images);
-	lua_setfield(L, -2, "images");
+	lua_pushinteger(L, stats.textures);
+	lua_setfield(L, -2, "textures");
 
 	lua_pushinteger(L, stats.fonts);
 	lua_setfield(L, -2, "fonts");