Browse Source

Added the ability to use custom mipmaps in Images, via love.graphics.newImage(filename, {mipmap1, mipmap2, ...}). Resolves issue #1064.

All mipmap levels must be present if custom mipmaps are used (and their sizes must be half the size of the previous mipmap level, rounded down.)

Image:getData now returns all custom mipmaps in an Image.
Alex Szpakowski 10 years ago
parent
commit
66942d5f31

+ 5 - 6
src/modules/event/sdl/Event.cpp

@@ -44,7 +44,7 @@ namespace sdl
 // we want them in pixel coordinates (may be different with high-DPI enabled.)
 // we want them in pixel coordinates (may be different with high-DPI enabled.)
 static void windowToPixelCoords(double *x, double *y)
 static void windowToPixelCoords(double *x, double *y)
 {
 {
-	window::Window *window = Module::getInstance<window::Window>(Module::M_WINDOW);
+	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
 	if (window)
 	if (window)
 		window->windowToPixelCoords(x, y);
 		window->windowToPixelCoords(x, y);
 }
 }
@@ -52,7 +52,7 @@ static void windowToPixelCoords(double *x, double *y)
 #ifndef LOVE_MACOSX
 #ifndef LOVE_MACOSX
 static void normalizedToPixelCoords(double *x, double *y)
 static void normalizedToPixelCoords(double *x, double *y)
 {
 {
-	window::Window *window = Module::getInstance<window::Window>(Module::M_WINDOW);
+	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
 	int w = 1, h = 1;
 	int w = 1, h = 1;
 
 
 	if (window)
 	if (window)
@@ -70,7 +70,7 @@ static void normalizedToPixelCoords(double *x, double *y)
 // handling inside the function which triggered them on some backends.
 // handling inside the function which triggered them on some backends.
 static int SDLCALL watchAppEvents(void * /*udata*/, SDL_Event *event)
 static int SDLCALL watchAppEvents(void * /*udata*/, SDL_Event *event)
 {
 {
-	graphics::Graphics *gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
+	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
 
 
 	switch (event->type)
 	switch (event->type)
 	{
 	{
@@ -152,7 +152,6 @@ Message *Event::convert(const SDL_Event &e) const
 	std::vector<StrongRef<Variant>> vargs;
 	std::vector<StrongRef<Variant>> vargs;
 	vargs.reserve(4);
 	vargs.reserve(4);
 
 
-	love::keyboard::Keyboard *kb = nullptr;
 	love::filesystem::Filesystem *filesystem = nullptr;
 	love::filesystem::Filesystem *filesystem = nullptr;
 
 
 	love::keyboard::Keyboard::Key key = love::keyboard::Keyboard::KEY_UNKNOWN;
 	love::keyboard::Keyboard::Key key = love::keyboard::Keyboard::KEY_UNKNOWN;
@@ -176,7 +175,7 @@ Message *Event::convert(const SDL_Event &e) const
 	case SDL_KEYDOWN:
 	case SDL_KEYDOWN:
 		if (e.key.repeat)
 		if (e.key.repeat)
 		{
 		{
-			kb = Module::getInstance<love::keyboard::Keyboard>(Module::M_KEYBOARD);
+			auto kb = Module::getInstance<love::keyboard::Keyboard>(Module::M_KEYBOARD);
 			if (kb && !kb->hasKeyRepeat())
 			if (kb && !kb->hasKeyRepeat())
 				break;
 				break;
 		}
 		}
@@ -394,7 +393,7 @@ Message *Event::convert(const SDL_Event &e) const
 
 
 Message *Event::convertJoystickEvent(const SDL_Event &e) const
 Message *Event::convertJoystickEvent(const SDL_Event &e) const
 {
 {
-	joystick::JoystickModule *joymodule = Module::getInstance<joystick::JoystickModule>(Module::M_JOYSTICK);
+	auto joymodule = Module::getInstance<joystick::JoystickModule>(Module::M_JOYSTICK);
 	if (!joymodule)
 	if (!joymodule)
 		return nullptr;
 		return nullptr;
 
 

+ 2 - 2
src/modules/font/BMFontRasterizer.cpp

@@ -190,8 +190,8 @@ void BMFontRasterizer::parseConfig(const std::string &configtext)
 			{
 			{
 				using namespace love::filesystem;
 				using namespace love::filesystem;
 
 
-				Filesystem *filesystem = Module::getInstance<Filesystem>(Module::M_FILESYSTEM);
-				image::Image *imagemodule = Module::getInstance<image::Image>(Module::M_IMAGE);
+				auto filesystem  = Module::getInstance<Filesystem>(Module::M_FILESYSTEM);
+				auto imagemodule = Module::getInstance<image::Image>(Module::M_IMAGE);
 
 
 				if (!filesystem)
 				if (!filesystem)
 					throw love::Exception("Filesystem module not loaded!");
 					throw love::Exception("Filesystem module not loaded!");

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

@@ -194,7 +194,7 @@ void Graphics::checkSetDefaultFont()
 	// Create a new default font if we don't have one yet.
 	// Create a new default font if we don't have one yet.
 	if (!defaultFont.get())
 	if (!defaultFont.get())
 	{
 	{
-		font::Font *fontmodule = Module::getInstance<font::Font>(M_FONT);
+		auto fontmodule = Module::getInstance<font::Font>(M_FONT);
 		if (!fontmodule)
 		if (!fontmodule)
 			throw love::Exception("Font module has not been loaded.");
 			throw love::Exception("Font module has not been loaded.");
 
 
@@ -664,12 +664,12 @@ void Graphics::clearStencil()
 	glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 	glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 }
 }
 
 
-Image *Graphics::newImage(love::image::ImageData *data, const Image::Flags &flags)
+Image *Graphics::newImage(const std::vector<love::image::ImageData *> &data, const Image::Flags &flags)
 {
 {
 	return new Image(data, flags);
 	return new Image(data, flags);
 }
 }
 
 
-Image *Graphics::newImage(love::image::CompressedImageData *cdata, const Image::Flags &flags)
+Image *Graphics::newImage(const std::vector<love::image::CompressedImageData *> &cdata, const Image::Flags &flags)
 {
 {
 	return new Image(cdata, flags);
 	return new Image(cdata, flags);
 }
 }

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

@@ -157,8 +157,8 @@ public:
 	/**
 	/**
 	 * Creates an Image object with padding and/or optimization.
 	 * Creates an Image object with padding and/or optimization.
 	 **/
 	 **/
-	Image *newImage(love::image::ImageData *data, const Image::Flags &flags);
-	Image *newImage(love::image::CompressedImageData *cdata, const Image::Flags &flags);
+	Image *newImage(const std::vector<love::image::ImageData *> &data, const Image::Flags &flags);
+	Image *newImage(const std::vector<love::image::CompressedImageData *> &cdata, const Image::Flags &flags);
 
 
 	Quad *newQuad(Quad::Viewport v, float sw, float sh);
 	Quad *newQuad(Quad::Viewport v, float sw, float sh);
 
 

+ 134 - 66
src/modules/graphics/opengl/Image.cpp

@@ -23,7 +23,6 @@
 #include "common/int.h"
 #include "common/int.h"
 
 
 // STD
 // STD
-#include <cstring> // For memcpy
 #include <algorithm> // for min/max
 #include <algorithm> // for min/max
 
 
 namespace love
 namespace love
@@ -40,18 +39,62 @@ float Image::maxMipmapSharpness = 0.0f;
 Texture::FilterMode Image::defaultMipmapFilter = Texture::FILTER_LINEAR;
 Texture::FilterMode Image::defaultMipmapFilter = Texture::FILTER_LINEAR;
 float Image::defaultMipmapSharpness = 0.0f;
 float Image::defaultMipmapSharpness = 0.0f;
 
 
-Image::Image(love::image::ImageData *data, const Flags &flags)
-	: data(data)
-	, cdata(nullptr)
-	, texture(0)
+static int getMipmapCount(int basewidth, int baseheight)
+{
+	return (int) log2(std::max(basewidth, baseheight)) + 1;
+}
+
+template <typename T>
+static bool verifyMipmapLevels(const std::vector<T> &miplevels)
+{
+	int numlevels = (int) miplevels.size();
+
+	if (numlevels == 1)
+		return false;
+
+	int width  = miplevels[0]->getWidth();
+	int height = miplevels[0]->getHeight();
+
+	int expectedlevels = getMipmapCount(width, height);
+
+	// All mip levels must be present when not using auto-generated mipmaps.
+	if (numlevels != expectedlevels)
+		throw love::Exception("Image does not have all required mipmap levels (expected %d, got %d)", expectedlevels, numlevels);
+
+	// Verify the size of each mip level.
+	for (int i = 1; i < numlevels; i++)
+	{
+		width  = std::max(width / 2, 1);
+		height = std::max(height / 2, 1);
+
+		if (miplevels[i]->getWidth() != width)
+			throw love::Exception("Width of image mipmap level %d is incorrect (expected %d, got %d)", i+1, width, miplevels[i]->getWidth());
+		if (miplevels[i]->getHeight() != height)
+			throw love::Exception("Height of image mipmap level %d is incorrect (expected %d, got %d)", i+1, height, miplevels[i]->getHeight());
+	}
+
+	return true;
+}
+
+Image::Image(const std::vector<love::image::ImageData *> &imagedata, const Flags &flags)
+	: texture(0)
 	, mipmapSharpness(defaultMipmapSharpness)
 	, mipmapSharpness(defaultMipmapSharpness)
 	, compressed(false)
 	, compressed(false)
 	, flags(flags)
 	, flags(flags)
 	, usingDefaultTexture(false)
 	, usingDefaultTexture(false)
 	, textureMemorySize(0)
 	, textureMemorySize(0)
 {
 {
-	width = data->getWidth();
-	height = data->getHeight();
+	if (imagedata.empty())
+		throw love::Exception("");
+
+	width = imagedata[0]->getWidth();
+	height = imagedata[0]->getHeight();
+
+	if (verifyMipmapLevels(imagedata))
+		this->flags.mipmaps = true;
+
+	for (const auto &id : imagedata)
+		data.push_back(id);
 
 
 	preload();
 	preload();
 	loadVolatile();
 	loadVolatile();
@@ -59,27 +102,29 @@ Image::Image(love::image::ImageData *data, const Flags &flags)
 	++imageCount;
 	++imageCount;
 }
 }
 
 
-Image::Image(love::image::CompressedImageData *cdata, const Flags &flags)
-	: data(nullptr)
-	, cdata(cdata)
-	, texture(0)
+Image::Image(const std::vector<love::image::CompressedImageData *> &compresseddata, const Flags &flags)
+	: texture(0)
 	, mipmapSharpness(defaultMipmapSharpness)
 	, mipmapSharpness(defaultMipmapSharpness)
 	, compressed(true)
 	, compressed(true)
 	, flags(flags)
 	, flags(flags)
 	, usingDefaultTexture(false)
 	, usingDefaultTexture(false)
 	, textureMemorySize(0)
 	, textureMemorySize(0)
 {
 {
-	this->flags.sRGB = (flags.sRGB || cdata->isSRGB());
+	this->flags.sRGB = (flags.sRGB || cdata[0]->isSRGB());
 
 
-	width = cdata->getWidth(0);
-	height = cdata->getHeight(0);
+	width = compresseddata[0]->getWidth(0);
+	height = compresseddata[0]->getHeight(0);
 
 
-	if (flags.mipmaps)
+	if (verifyMipmapLevels(compresseddata))
+		this->flags.mipmaps = true;
+	else if (flags.mipmaps && getMipmapCount(width, height) != compresseddata[0]->getMipmapCount())
+		throw love::Exception("Image cannot have mipmaps: compressed image data does not have all required mipmap levels.");
+
+	for (const auto &cd : compresseddata)
 	{
 	{
-		// The mipmap texture data comes from the CompressedImageData in this case,
-		// so we should make sure it has all necessary mipmap levels.
-		if (cdata->getMipmapCount() < (int) log2(std::max(width, height)) + 1)
-			throw love::Exception("Image cannot have mipmaps: compressed image data does not have all required mipmap levels.");
+		cdata.push_back(cd);
+		if (cd->getFormat() != cdata[0]->getFormat())
+			throw love::Exception("All image mipmap levels must have the same format.");
 	}
 	}
 
 
 	preload();
 	preload();
@@ -159,14 +204,25 @@ void Image::loadDefaultTexture()
 
 
 void Image::loadFromCompressedData()
 void Image::loadFromCompressedData()
 {
 {
-	GLenum iformat = getCompressedFormat(cdata->getFormat());
-	int count = flags.mipmaps ? cdata->getMipmapCount() : 1;
+	GLenum iformat = getCompressedFormat(cdata[0]->getFormat());
+
+	int count = 1;
+
+	if (flags.mipmaps && cdata.size() > 1)
+		count = (int) cdata.size();
+	else if (flags.mipmaps)
+		count = cdata[0]->getMipmapCount();
 
 
 	for (int i = 0; i < count; i++)
 	for (int i = 0; i < count; i++)
 	{
 	{
-		glCompressedTexImage2D(GL_TEXTURE_2D, i, iformat,
-		                       cdata->getWidth(i), cdata->getHeight(i), 0,
-		                       (GLsizei) cdata->getSize(i), cdata->getData(i));
+		// Compressed image mipmaps can come from separate CompressedImageData
+		// objects, or all from a single object.
+		auto cd = cdata.size() > 1 ? cdata[i].get() : cdata[0].get();
+		int datamip = cdata.size() > 1 ? 0 : i;
+
+		glCompressedTexImage2D(GL_TEXTURE_2D, i, iformat, cd->getWidth(datamip),
+		                       cd->getHeight(datamip), 0,
+		                       (GLsizei) cd->getSize(datamip), cd->getData(datamip));
 	}
 	}
 }
 }
 
 
@@ -182,23 +238,29 @@ void Image::loadFromImageData()
 		iformat = format;
 		iformat = format;
 	}
 	}
 
 
+	int mipcount = flags.mipmaps ? (int) data.size() : 1;
+
+	for (int i = 0; i < mipcount; i++)
 	{
 	{
-		love::thread::Lock lock(data->getMutex());
-		glTexImage2D(GL_TEXTURE_2D, 0, iformat, width, height, 0, format,
-		             GL_UNSIGNED_BYTE, data->getData());
+		love::image::ImageData *id = data[i].get();
+		love::thread::Lock lock(id->getMutex());
+
+		glTexImage2D(GL_TEXTURE_2D, i, iformat, id->getWidth(), id->getHeight(),
+		             0, format, GL_UNSIGNED_BYTE, id->getData());
 	}
 	}
 
 
-	generateMipmaps();
+	if (data.size() <= 1)
+		generateMipmaps();
 }
 }
 
 
 bool Image::loadVolatile()
 bool Image::loadVolatile()
 {
 {
 	OpenGL::TempDebugGroup debuggroup("Image load");
 	OpenGL::TempDebugGroup debuggroup("Image load");
 
 
-	if (isCompressed() && !hasCompressedTextureSupport(cdata->getFormat(), flags.sRGB))
+	if (isCompressed() && !hasCompressedTextureSupport(cdata[0]->getFormat(), flags.sRGB))
 	{
 	{
 		const char *str;
 		const char *str;
-		if (image::CompressedImageData::getConstant(cdata->getFormat(), str))
+		if (image::CompressedImageData::getConstant(cdata[0]->getFormat(), str))
 		{
 		{
 			throw love::Exception("Cannot create image: "
 			throw love::Exception("Cannot create image: "
 			                      "%s%s compressed images are not supported on this system.", flags.sRGB ? "sRGB " : "", str);
 			                      "%s%s compressed images are not supported on this system.", flags.sRGB ? "sRGB " : "", str);
@@ -212,7 +274,8 @@ bool Image::loadVolatile()
 			throw love::Exception("sRGB images are not supported on this system.");
 			throw love::Exception("sRGB images are not supported on this system.");
 
 
 		// GL_EXT_sRGB doesn't support glGenerateMipmap for sRGB textures.
 		// GL_EXT_sRGB doesn't support glGenerateMipmap for sRGB textures.
-		if (flags.sRGB && (GLAD_ES_VERSION_2_0 && GLAD_EXT_sRGB && !GLAD_ES_VERSION_3_0))
+		if (flags.sRGB && (GLAD_ES_VERSION_2_0 && GLAD_EXT_sRGB && !GLAD_ES_VERSION_3_0)
+			&& data.size() <= 1)
 		{
 		{
 			flags.mipmaps = false;
 			flags.mipmaps = false;
 			filter.mipmap = FILTER_NONE;
 			filter.mipmap = FILTER_NONE;
@@ -244,13 +307,10 @@ bool Image::loadVolatile()
 		return true;
 		return true;
 	}
 	}
 
 
-	if ((isCompressed() || !flags.mipmaps) && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_1_0))
-	{
-		int count = (flags.mipmaps && isCompressed()) ? cdata->getMipmapCount() : 1;
-		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, count - 1);
-	}
+	if (!flags.mipmaps && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_1_0))
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
 
 
-	if (flags.mipmaps && !isCompressed() &&
+	if (flags.mipmaps && !isCompressed() && data.size() <= 1 &&
 		!(GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object))
 		!(GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object))
 	{
 	{
 		// Auto-generate mipmaps every time the texture is modified, if
 		// Auto-generate mipmaps every time the texture is modified, if
@@ -281,17 +341,12 @@ bool Image::loadVolatile()
 	size_t prevmemsize = textureMemorySize;
 	size_t prevmemsize = textureMemorySize;
 
 
 	if (isCompressed())
 	if (isCompressed())
-	{
-		textureMemorySize = 0;
-		for (int i = 0; i < (flags.mipmaps ? cdata->getMipmapCount() : 1); i++)
-			textureMemorySize += cdata->getSize(i);
-	}
+		textureMemorySize = cdata[0]->getSize();
 	else
 	else
-	{
-		textureMemorySize = width * height * 4;
-		if (flags.mipmaps)
-			textureMemorySize *= 1.333;
-	}
+		textureMemorySize = data[0]->getSize();
+
+	if (flags.mipmaps)
+		textureMemorySize *= 1.33334;
 
 
 	gl.updateTextureMemorySize(prevmemsize, textureMemorySize);
 	gl.updateTextureMemorySize(prevmemsize, textureMemorySize);
 
 
@@ -323,31 +378,44 @@ bool Image::refresh(int xoffset, int yoffset, int w, int h)
 		throw love::Exception("Invalid rectangle dimensions.");
 		throw love::Exception("Invalid rectangle dimensions.");
 	}
 	}
 
 
+	OpenGL::TempDebugGroup debuggroup("Image refresh");
+
 	gl.bindTexture(texture);
 	gl.bindTexture(texture);
 
 
 	if (isCompressed())
 	if (isCompressed())
-		loadFromCompressedData();
-	else
 	{
 	{
-		const image::pixel *pdata = (const image::pixel *) data->getData();
-		pdata += yoffset * data->getWidth() + xoffset;
+		loadFromCompressedData();
+		return true;
+	}
 
 
-		GLenum format = GL_RGBA;
+	GLenum format = GL_RGBA;
 
 
-		// In ES2, the format parameter of TexSubImage must match the internal
-		// format of the texture.
-		if (flags.sRGB && (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0))
-			format = GL_SRGB_ALPHA;
+	// In ES2, the format parameter of TexSubImage must match the internal
+	// format of the texture.
+	if (flags.sRGB && (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0))
+		format = GL_SRGB_ALPHA;
 
 
-		{
-			thread::Lock lock(data->getMutex());
-			glTexSubImage2D(GL_TEXTURE_2D, 0, xoffset, yoffset, w, h, format,
-			                GL_UNSIGNED_BYTE, pdata);
-		}
+	int mipcount = flags.mipmaps ? (int) data.size() : 1;
 
 
-		generateMipmaps();
+	// Reupload the sub-rectangle of each mip level (if we have custom mipmaps.)
+	for (int i = 0; i < mipcount; i++)
+	{
+		const image::pixel *pdata = (const image::pixel *) data[i]->getData();
+		pdata += yoffset * data[i]->getWidth() + xoffset;
+
+		thread::Lock lock(data[i]->getMutex());
+		glTexSubImage2D(GL_TEXTURE_2D, i, xoffset, yoffset, w, h, format,
+						GL_UNSIGNED_BYTE, pdata);
+
+		xoffset /= 2;
+		yoffset /= 2;
+		w = std::max(w / 2, 1);
+		h = std::max(h / 2, 1);
 	}
 	}
 
 
+	if (data.size() <= 1)
+		generateMipmaps();
+
 	return true;
 	return true;
 }
 }
 
 
@@ -388,14 +456,14 @@ const void *Image::getHandle() const
 	return &texture;
 	return &texture;
 }
 }
 
 
-love::image::ImageData *Image::getImageData() const
+const std::vector<StrongRef<love::image::ImageData>> &Image::getImageData() const
 {
 {
-	return data.get();
+	return data;
 }
 }
 
 
-love::image::CompressedImageData *Image::getCompressedData() const
+const std::vector<StrongRef<love::image::CompressedImageData>> &Image::getCompressedData() const
 {
 {
-	return cdata.get();
+	return cdata;
 }
 }
 
 
 void Image::setFilter(const Texture::Filter &f)
 void Image::setFilter(const Texture::Filter &f)

+ 12 - 9
src/modules/graphics/opengl/Image.h

@@ -69,16 +69,18 @@ public:
 	 * Creates a new Image. Not that anything is ready to use
 	 * Creates a new Image. Not that anything is ready to use
 	 * before load is called.
 	 * before load is called.
 	 *
 	 *
-	 * @param data The data from which to load the image.
+	 * @param data The data from which to load the image. Each element in the
+	 * array is a mipmap level. If more than the base level is present, all
+	 * mip levels must be present.
 	 **/
 	 **/
-	Image(love::image::ImageData *data, const Flags &flags);
+	Image(const std::vector<love::image::ImageData *> &data, const Flags &flags);
 
 
 	/**
 	/**
 	 * Creates a new Image with compressed image data.
 	 * Creates a new Image with compressed image data.
 	 *
 	 *
 	 * @param cdata The compressed data from which to load the image.
 	 * @param cdata The compressed data from which to load the image.
 	 **/
 	 **/
-	Image(love::image::CompressedImageData *cdata, const Flags &flags);
+	Image(const std::vector<love::image::CompressedImageData *> &cdata, const Flags &flags);
 
 
 	virtual ~Image();
 	virtual ~Image();
 
 
@@ -98,8 +100,8 @@ public:
 
 
 	virtual const void *getHandle() const;
 	virtual const void *getHandle() const;
 
 
-	love::image::ImageData *getImageData() const;
-	love::image::CompressedImageData *getCompressedData() const;
+	const std::vector<StrongRef<love::image::ImageData>> &getImageData() const;
+	const std::vector<StrongRef<love::image::CompressedImageData>> &getCompressedData() const;
 
 
 	virtual void setFilter(const Texture::Filter &f);
 	virtual void setFilter(const Texture::Filter &f);
 	virtual bool setWrap(const Texture::Wrap &w);
 	virtual bool setWrap(const Texture::Wrap &w);
@@ -147,13 +149,14 @@ private:
 
 
 	GLenum getCompressedFormat(image::CompressedImageData::Format cformat) const;
 	GLenum getCompressedFormat(image::CompressedImageData::Format cformat) const;
 
 
-	// The ImageData from which the texture is created. May be null if
+	// The ImageData from which the texture is created. May be empty if
 	// Compressed image data was used to create the texture.
 	// Compressed image data was used to create the texture.
-	StrongRef<love::image::ImageData> data;
+	// Each element in the array is a mipmap level.
+	std::vector<StrongRef<love::image::ImageData>> data;
 
 
 	// Or the Compressed Image Data from which the texture is created. May be
 	// Or the Compressed Image Data from which the texture is created. May be
-	// null if raw ImageData was used to create the texture.
-	StrongRef<love::image::CompressedImageData> cdata;
+	// empty if raw ImageData was used to create the texture.
+	std::vector<StrongRef<love::image::CompressedImageData>> cdata;
 
 
 	// OpenGL texture identifier.
 	// OpenGL texture identifier.
 	GLuint texture;
 	GLuint texture;

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

@@ -38,7 +38,7 @@ int w_Canvas_renderTo(lua_State *L)
 	Canvas *canvas = luax_checkcanvas(L, 1);
 	Canvas *canvas = luax_checkcanvas(L, 1);
 	luaL_checktype(L, 2, LUA_TFUNCTION);
 	luaL_checktype(L, 2, LUA_TFUNCTION);
 
 
-	Graphics *graphics = Module::getInstance<Graphics>(Module::M_GRAPHICS);
+	auto graphics = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 
 
 	if (graphics)
 	if (graphics)
 	{
 	{

+ 55 - 20
src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -25,6 +25,7 @@
 #include "image/Image.h"
 #include "image/Image.h"
 #include "font/Rasterizer.h"
 #include "font/Rasterizer.h"
 #include "filesystem/wrap_Filesystem.h"
 #include "filesystem/wrap_Filesystem.h"
+#include "image/wrap_Image.h"
 
 
 #include <cassert>
 #include <cassert>
 #include <cstring>
 #include <cstring>
@@ -236,8 +237,8 @@ static const char *imageFlagName(Image::FlagType flagtype)
 
 
 int w_newImage(lua_State *L)
 int w_newImage(lua_State *L)
 {
 {
-	love::image::ImageData *data = nullptr;
-	love::image::CompressedImageData *cdata = nullptr;
+	std::vector<love::image::ImageData *> data;
+	std::vector<love::image::CompressedImageData *> cdata;
 
 
 	Image::Flags flags;
 	Image::Flags flags;
 	if (!lua_isnoneornil(L, 2))
 	if (!lua_isnoneornil(L, 2))
@@ -252,23 +253,23 @@ int w_newImage(lua_State *L)
 	// Convert to ImageData / CompressedImageData, if necessary.
 	// Convert to ImageData / CompressedImageData, if necessary.
 	if (lua_isstring(L, 1) || luax_istype(L, 1, FILESYSTEM_FILE_ID) || luax_istype(L, 1, FILESYSTEM_FILE_DATA_ID))
 	if (lua_isstring(L, 1) || luax_istype(L, 1, FILESYSTEM_FILE_ID) || luax_istype(L, 1, FILESYSTEM_FILE_DATA_ID))
 	{
 	{
-		love::image::Image *image = Module::getInstance<love::image::Image>(Module::M_IMAGE);
-		if (image == nullptr)
+		auto imagemodule = Module::getInstance<love::image::Image>(Module::M_IMAGE);
+		if (imagemodule == nullptr)
 			return luaL_error(L, "Cannot load images without the love.image module.");
 			return luaL_error(L, "Cannot load images without the love.image module.");
 
 
 		love::filesystem::FileData *fdata = love::filesystem::luax_getfiledata(L, 1);
 		love::filesystem::FileData *fdata = love::filesystem::luax_getfiledata(L, 1);
 
 
-		if (image->isCompressed(fdata))
+		if (imagemodule->isCompressed(fdata))
 		{
 		{
 			luax_catchexcept(L,
 			luax_catchexcept(L,
-				[&]() { cdata = image->newCompressedData(fdata); },
+				[&]() { cdata.push_back(imagemodule->newCompressedData(fdata)); },
 				[&](bool) { fdata->release(); }
 				[&](bool) { fdata->release(); }
 			);
 			);
 		}
 		}
 		else
 		else
 		{
 		{
 			luax_catchexcept(L,
 			luax_catchexcept(L,
-				[&]() { data = image->newImageData(fdata); },
+				[&]() { data.push_back(imagemodule->newImageData(fdata)); },
 				[&](bool) { fdata->release(); }
 				[&](bool) { fdata->release(); }
 			);
 			);
 		}
 		}
@@ -277,27 +278,61 @@ int w_newImage(lua_State *L)
 		releasedata = true;
 		releasedata = true;
 	}
 	}
 	else if (luax_istype(L, 1, IMAGE_COMPRESSED_IMAGE_DATA_ID))
 	else if (luax_istype(L, 1, IMAGE_COMPRESSED_IMAGE_DATA_ID))
-		cdata = luax_checktype<love::image::CompressedImageData>(L, 1, IMAGE_COMPRESSED_IMAGE_DATA_ID);
+		cdata.push_back(love::image::luax_checkcompressedimagedata(L, 1));
 	else
 	else
-		data = luax_checktype<love::image::ImageData>(L, 1, IMAGE_IMAGE_DATA_ID);
+		data.push_back(love::image::luax_checkimagedata(L, 1));
 
 
-	if (!data && !cdata)
-		return luaL_error(L, "Error creating image (could not load data.)");
+	if (lua_istable(L, 2))
+	{
+		lua_getfield(L, 2, imageFlagName(Image::FLAG_TYPE_MIPMAPS));
+
+		// Add all manually specified mipmap images to the array of imagedata.
+		// i.e. flags = {mipmaps = {mip1, mip2, ...}}.
+		if (lua_istable(L, -1))
+		{
+			for (size_t i = 1; i <= luax_objlen(L, -1); i++)
+			{
+				lua_rawgeti(L, -1, i);
+
+				if (!data.empty())
+				{
+					if (!luax_istype(L, -1, IMAGE_IMAGE_DATA_ID))
+						luax_convobj(L, -1, "image", "newImageData");
+
+					data.push_back(love::image::luax_checkimagedata(L, -1));
+				}
+				else if (!cdata.empty())
+				{
+					if (!luax_istype(L, -1, IMAGE_COMPRESSED_IMAGE_DATA_ID))
+						luax_convobj(L, -1, "image", "newCompressedData");
+
+					cdata.push_back(love::image::luax_checkcompressedimagedata(L, -1));
+				}
+
+				lua_pop(L, 1);
+			}
+		}
+
+		lua_pop(L, 1);
+	}
 
 
 	// Create the image.
 	// Create the image.
 	Image *image = nullptr;
 	Image *image = nullptr;
 	luax_catchexcept(L,
 	luax_catchexcept(L,
 		[&]() {
 		[&]() {
-			if (cdata)
+			if (!cdata.empty())
 				image = instance()->newImage(cdata, flags);
 				image = instance()->newImage(cdata, flags);
-			else if (data)
+			else if (!data.empty())
 				image = instance()->newImage(data, flags);
 				image = instance()->newImage(data, flags);
 		},
 		},
 		[&](bool) {
 		[&](bool) {
-			if (releasedata && data)
-				data->release();
-			else if (releasedata && cdata)
-				cdata->release();
+			if (releasedata)
+			{
+				for (auto d : data)
+					d->release();
+				for (auto d : cdata)
+					d->release();
+			}
 		}
 		}
 	);
 	);
 
 
@@ -363,10 +398,10 @@ int w_newImageFont(lua_State *L)
 	{
 	{
 		Image *i = luax_checktype<Image>(L, 1, GRAPHICS_IMAGE_ID);
 		Image *i = luax_checktype<Image>(L, 1, GRAPHICS_IMAGE_ID);
 		filter = i->getFilter();
 		filter = i->getFilter();
-		love::image::ImageData *id = i->getImageData();
-		if (!id)
+		const auto &idlevels = i->getImageData();
+		if (idlevels.empty())
 			return luaL_argerror(L, 1, "Image must not be compressed.");
 			return luaL_argerror(L, 1, "Image must not be compressed.");
-		luax_pushtype(L, IMAGE_IMAGE_DATA_ID, id);
+		luax_pushtype(L, IMAGE_IMAGE_DATA_ID, idlevels[0].get());
 		lua_replace(L, 1);
 		lua_replace(L, 1);
 	}
 	}
 
 

+ 16 - 3
src/modules/graphics/opengl/wrap_Image.cpp

@@ -92,13 +92,26 @@ int w_Image_refresh(lua_State *L)
 int w_Image_getData(lua_State *L)
 int w_Image_getData(lua_State *L)
 {
 {
 	Image *i = luax_checkimage(L, 1);
 	Image *i = luax_checkimage(L, 1);
+	int n = 0;
 
 
 	if (i->isCompressed())
 	if (i->isCompressed())
-		luax_pushtype(L, IMAGE_COMPRESSED_IMAGE_DATA_ID, i->getCompressedData());
+	{
+		for (const auto &cdata : i->getCompressedData())
+		{
+			luax_pushtype(L, IMAGE_COMPRESSED_IMAGE_DATA_ID, cdata.get());
+			n++;
+		}
+	}
 	else
 	else
-		luax_pushtype(L, IMAGE_IMAGE_DATA_ID, i->getImageData());
+	{
+		for (const auto &data : i->getImageData())
+		{
+			luax_pushtype(L, IMAGE_IMAGE_DATA_ID, data.get());
+			n++;
+		}
+	}
 
 
-	return 1;
+	return n;
 }
 }
 
 
 static const char *imageFlagName(Image::FlagType flagtype)
 static const char *imageFlagName(Image::FlagType flagtype)

+ 2 - 2
src/modules/image/CompressedImageData.h

@@ -107,12 +107,12 @@ public:
 	/**
 	/**
 	 * Gets the width of a sub-image at the specified mipmap level.
 	 * Gets the width of a sub-image at the specified mipmap level.
 	 **/
 	 **/
-	int getWidth(int miplevel) const;
+	int getWidth(int miplevel = 0) const;
 
 
 	/**
 	/**
 	 * Gets the height of a sub-image at the specified mipmap level.
 	 * Gets the height of a sub-image at the specified mipmap level.
 	 **/
 	 **/
-	int getHeight(int miplevel) const;
+	int getHeight(int miplevel = 0) const;
 
 
 	/**
 	/**
 	 * Gets the format of the compressed data.
 	 * Gets the format of the compressed data.

+ 1 - 1
src/modules/keyboard/sdl/Keyboard.cpp

@@ -122,7 +122,7 @@ void Keyboard::setTextInput(bool enable, double x, double y, double w, double h)
 {
 {
 	// SDL_SetTextInputRect expects coordinates in window-space but setTextInput
 	// SDL_SetTextInputRect expects coordinates in window-space but setTextInput
 	// takes pixels, so we should convert.
 	// takes pixels, so we should convert.
-	window::Window *window = Module::getInstance<window::Window>(M_WINDOW);
+	auto window = Module::getInstance<window::Window>(M_WINDOW);
 	if (window)
 	if (window)
 	{
 	{
 		window->pixelToWindowCoords(&x, &y);
 		window->pixelToWindowCoords(&x, &y);

+ 5 - 5
src/modules/mouse/sdl/Mouse.cpp

@@ -36,7 +36,7 @@ namespace sdl
 // we want them in pixel coordinates (may be different with high-DPI enabled.)
 // we want them in pixel coordinates (may be different with high-DPI enabled.)
 static void windowToPixelCoords(double *x, double *y)
 static void windowToPixelCoords(double *x, double *y)
 {
 {
-	window::Window *window = Module::getInstance<window::Window>(Module::M_WINDOW);
+	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
 	if (window)
 	if (window)
 		window->windowToPixelCoords(x, y);
 		window->windowToPixelCoords(x, y);
 }
 }
@@ -44,7 +44,7 @@ static void windowToPixelCoords(double *x, double *y)
 // And vice versa for setting mouse coordinates.
 // And vice versa for setting mouse coordinates.
 static void pixelToWindowCoords(double *x, double *y)
 static void pixelToWindowCoords(double *x, double *y)
 {
 {
-	window::Window *window = Module::getInstance<window::Window>(Module::M_WINDOW);
+	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
 	if (window)
 	if (window)
 		window->pixelToWindowCoords(x, y);
 		window->pixelToWindowCoords(x, y);
 }
 }
@@ -146,7 +146,7 @@ void Mouse::getPosition(double &x, double &y) const
 
 
 void Mouse::setPosition(double x, double y)
 void Mouse::setPosition(double x, double y)
 {
 {
-	window::Window *window = Module::getInstance<window::Window>(Module::M_WINDOW);
+	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
 
 
 	SDL_Window *handle = nullptr;
 	SDL_Window *handle = nullptr;
 	if (window)
 	if (window)
@@ -211,14 +211,14 @@ bool Mouse::isVisible() const
 
 
 void Mouse::setGrabbed(bool grab)
 void Mouse::setGrabbed(bool grab)
 {
 {
-	window::Window *window = Module::getInstance<window::Window>(Module::M_WINDOW);
+	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
 	if (window)
 	if (window)
 		window->setMouseGrab(grab);
 		window->setMouseGrab(grab);
 }
 }
 
 
 bool Mouse::isGrabbed() const
 bool Mouse::isGrabbed() const
 {
 {
-	window::Window *window = Module::getInstance<window::Window>(Module::M_WINDOW);
+	auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
 	if (window)
 	if (window)
 		return window->isMouseGrabbed();
 		return window->isMouseGrabbed();
 	else
 	else

+ 3 - 3
src/modules/thread/LuaThread.cpp

@@ -118,8 +118,8 @@ void LuaThread::onError()
 	if (error.empty())
 	if (error.empty())
 		return;
 		return;
 
 
-	event::Event *event = Module::getInstance<event::Event>(Module::M_EVENT);
-	if (!event)
+	auto eventmodule = Module::getInstance<event::Event>(Module::M_EVENT);
+	if (!eventmodule)
 		return;
 		return;
 
 
 	Proxy p;
 	Proxy p;
@@ -136,7 +136,7 @@ void LuaThread::onError()
 	for (const StrongRef<Variant> &v : vargs)
 	for (const StrongRef<Variant> &v : vargs)
 		v->release();
 		v->release();
 
 
-	event->push(msg);
+	eventmodule->push(msg);
 	msg->release();
 	msg->release();
 }
 }
 
 

+ 2 - 2
src/modules/window/sdl/Window.cpp

@@ -466,7 +466,7 @@ bool Window::onSizeChanged(int width, int height)
 
 
 	SDL_GL_GetDrawableSize(window, &curMode.pixelwidth, &curMode.pixelheight);
 	SDL_GL_GetDrawableSize(window, &curMode.pixelwidth, &curMode.pixelheight);
 
 
-	graphics::Graphics *gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
+	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
 	if (gfx != nullptr)
 	if (gfx != nullptr)
 		gfx->setViewportSize(curMode.pixelwidth, curMode.pixelheight);
 		gfx->setViewportSize(curMode.pixelwidth, curMode.pixelheight);
 
 
@@ -609,7 +609,7 @@ bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype)
 		updateSettings(newsettings);
 		updateSettings(newsettings);
 
 
 		// Update the viewport size now instead of waiting for event polling.
 		// Update the viewport size now instead of waiting for event polling.
-		graphics::Graphics *gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
+		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
 		if (gfx != nullptr)
 		if (gfx != nullptr)
 			gfx->setViewportSize(curMode.pixelwidth, curMode.pixelheight);
 			gfx->setViewportSize(curMode.pixelwidth, curMode.pixelheight);