Bläddra i källkod

Using latest love-android branch (9a508a54700d)

fysx 10 år sedan
förälder
incheckning
f3dd769e98
63 ändrade filer med 732 tillägg och 550 borttagningar
  1. 1 0
      .gitignore
  2. 14 26
      jni/love/src/common/Reference.cpp
  3. 7 26
      jni/love/src/common/Reference.h
  4. 1 1
      jni/love/src/common/Variant.cpp
  5. 47 10
      jni/love/src/common/runtime.cpp
  6. 18 1
      jni/love/src/common/runtime.h
  7. 2 12
      jni/love/src/libraries/glad/glad.cpp
  8. 27 27
      jni/love/src/love.cpp
  9. 1 3
      jni/love/src/modules/audio/openal/Audio.cpp
  10. 1 1
      jni/love/src/modules/audio/openal/Audio.h
  11. 0 6
      jni/love/src/modules/audio/openal/Pool.cpp
  12. 1 1
      jni/love/src/modules/audio/openal/Pool.h
  13. 0 6
      jni/love/src/modules/event/Event.cpp
  14. 1 2
      jni/love/src/modules/event/Event.h
  15. 11 13
      jni/love/src/modules/event/sdl/Event.cpp
  16. 49 35
      jni/love/src/modules/filesystem/physfs/Filesystem.cpp
  17. 2 2
      jni/love/src/modules/font/BMFontRasterizer.cpp
  18. 1 1
      jni/love/src/modules/font/BMFontRasterizer.h
  19. 57 20
      jni/love/src/modules/graphics/opengl/Graphics.cpp
  20. 3 3
      jni/love/src/modules/graphics/opengl/Graphics.h
  21. 134 66
      jni/love/src/modules/graphics/opengl/Image.cpp
  22. 12 9
      jni/love/src/modules/graphics/opengl/Image.h
  23. 16 1
      jni/love/src/modules/graphics/opengl/OpenGL.cpp
  24. 1 1
      jni/love/src/modules/graphics/opengl/wrap_Canvas.cpp
  25. 55 25
      jni/love/src/modules/graphics/opengl/wrap_Graphics.cpp
  26. 16 3
      jni/love/src/modules/graphics/opengl/wrap_Image.cpp
  27. 18 38
      jni/love/src/modules/graphics/opengl/wrap_SpriteBatch.cpp
  28. 2 2
      jni/love/src/modules/image/CompressedImageData.h
  29. 0 2
      jni/love/src/modules/image/ImageData.cpp
  30. 3 3
      jni/love/src/modules/image/ImageData.h
  31. 8 5
      jni/love/src/modules/image/magpie/ImageData.cpp
  32. 1 2
      jni/love/src/modules/image/magpie/ImageData.h
  33. 23 17
      jni/love/src/modules/image/wrap_ImageData.cpp
  34. 0 1
      jni/love/src/modules/keyboard/Keyboard.cpp
  35. 0 1
      jni/love/src/modules/keyboard/Keyboard.h
  36. 1 2
      jni/love/src/modules/keyboard/sdl/Keyboard.cpp
  37. 3 1
      jni/love/src/modules/love/love.cpp
  38. 5 5
      jni/love/src/modules/mouse/sdl/Mouse.cpp
  39. 1 9
      jni/love/src/modules/physics/box2d/Body.cpp
  40. 1 1
      jni/love/src/modules/physics/box2d/Body.h
  41. 1 9
      jni/love/src/modules/physics/box2d/Fixture.cpp
  42. 1 1
      jni/love/src/modules/physics/box2d/Fixture.h
  43. 1 9
      jni/love/src/modules/physics/box2d/Joint.cpp
  44. 1 1
      jni/love/src/modules/physics/box2d/Joint.h
  45. 43 32
      jni/love/src/modules/physics/box2d/World.cpp
  46. 17 6
      jni/love/src/modules/physics/box2d/World.h
  47. 2 0
      jni/love/src/modules/physics/box2d/wrap_World.cpp
  48. 5 1
      jni/love/src/modules/sound/lullaby/ModPlugDecoder.h
  49. 4 2
      jni/love/src/modules/sound/lullaby/VorbisDecoder.cpp
  50. 6 3
      jni/love/src/modules/system/System.cpp
  51. 0 1
      jni/love/src/modules/system/System.h
  52. 1 1
      jni/love/src/modules/system/wrap_System.cpp
  53. 3 3
      jni/love/src/modules/thread/LuaThread.cpp
  54. 15 0
      jni/love/src/modules/thread/threads.cpp
  55. 12 0
      jni/love/src/modules/thread/threads.h
  56. 0 4
      jni/love/src/modules/window/Window.cpp
  57. 3 8
      jni/love/src/modules/window/Window.h
  58. 46 64
      jni/love/src/modules/window/sdl/Window.cpp
  59. 4 4
      jni/love/src/modules/window/sdl/Window.h
  60. 19 8
      jni/love/src/modules/window/wrap_Window.cpp
  61. 2 1
      jni/love/src/modules/window/wrap_Window.h
  62. 1 1
      jni/love/src/scripts/boot.lua
  63. 1 1
      jni/love/src/scripts/boot.lua.h

+ 1 - 0
.gitignore

@@ -13,6 +13,7 @@ local.properties
 *.so
 *.so
 *.a
 *.a
 *.orig
 *.orig
+*.rej
 
 
 !jni/LuaJIT-2.0.1/android/armeabi/libluajit.a
 !jni/LuaJIT-2.0.1/android/armeabi/libluajit.a
 !jni/LuaJIT-2.0.1/android/armeabi-v7a/libluajit.a
 !jni/LuaJIT-2.0.1/android/armeabi-v7a/libluajit.a

+ 14 - 26
jni/love/src/common/Reference.cpp

@@ -26,13 +26,13 @@ namespace love
 const char REFERENCE_TABLE_NAME[] = "love-references";
 const char REFERENCE_TABLE_NAME[] = "love-references";
 
 
 Reference::Reference()
 Reference::Reference()
-	: L(nullptr)
+	: pinnedL(nullptr)
 	, idx(LUA_REFNIL)
 	, idx(LUA_REFNIL)
 {
 {
 }
 }
 
 
 Reference::Reference(lua_State *L)
 Reference::Reference(lua_State *L)
-	: L(L)
+	: pinnedL(nullptr)
 	, idx(LUA_REFNIL)
 	, idx(LUA_REFNIL)
 {
 {
 	ref(L);
 	ref(L);
@@ -46,7 +46,7 @@ Reference::~Reference()
 void Reference::ref(lua_State *L)
 void Reference::ref(lua_State *L)
 {
 {
 	unref(); // Just to be safe.
 	unref(); // Just to be safe.
-	this->L = L;
+	pinnedL = luax_getpinnedthread(L);
 	luax_insist(L, LUA_REGISTRYINDEX, REFERENCE_TABLE_NAME);
 	luax_insist(L, LUA_REGISTRYINDEX, REFERENCE_TABLE_NAME);
 	lua_insert(L, -2); // Move reference table behind value.
 	lua_insert(L, -2); // Move reference table behind value.
 	idx = luaL_ref(L, -2);
 	idx = luaL_ref(L, -2);
@@ -57,38 +57,26 @@ void Reference::unref()
 {
 {
 	if (idx != LUA_REFNIL)
 	if (idx != LUA_REFNIL)
 	{
 	{
-		luax_insist(L, LUA_REGISTRYINDEX, REFERENCE_TABLE_NAME);
-		luaL_unref(L, -1, idx);
-		lua_pop(L, 1);
+		// We use a pinned thread/coroutine for the Lua state because we know it
+		// hasn't been garbage collected and is valid, as long as the whole lua
+		// state is still open.
+		luax_insist(pinnedL, LUA_REGISTRYINDEX, REFERENCE_TABLE_NAME);
+		luaL_unref(pinnedL, -1, idx);
+		lua_pop(pinnedL, 1);
 		idx = LUA_REFNIL;
 		idx = LUA_REFNIL;
 	}
 	}
 }
 }
 
 
-void Reference::push(lua_State *newL)
+void Reference::push(lua_State *L)
 {
 {
 	if (idx != LUA_REFNIL)
 	if (idx != LUA_REFNIL)
 	{
 	{
-		luax_insist(newL, LUA_REGISTRYINDEX, REFERENCE_TABLE_NAME);
-		lua_rawgeti(newL, -1, idx);
-		lua_remove(newL, -2);
+		luax_insist(L, LUA_REGISTRYINDEX, REFERENCE_TABLE_NAME);
+		lua_rawgeti(L, -1, idx);
+		lua_remove(L, -2);
 	}
 	}
 	else
 	else
-		lua_pushnil(newL);
-}
-
-void Reference::push()
-{
-	push(L);
-}
-
-lua_State *Reference::getL() const
-{
-	return L;
-}
-
-void Reference::setL(lua_State *newL)
-{
-	L = newL;
+		lua_pushnil(L);
 }
 }
 
 
 } // love
 } // love

+ 7 - 26
jni/love/src/common/Reference.h

@@ -64,36 +64,17 @@ public:
 	void unref();
 	void unref();
 
 
 	/**
 	/**
-	 * Pushes the referred value onto the stack of a different coroutine
-	 * in the same main Lua state.
-	 * THIS SHOULD NOT BE USED FOR DIFFERENT LUA STATES (created with
-	 * luaL_newstate)! Only with different coroutines!
+	 * Pushes the referred value onto the stack of the specified Lua coroutine.
+	 * NOTE: The coroutine *must* belong to the same Lua state that was used for
+	 * Reference::ref.
 	 **/
 	 **/
-	void push(lua_State *newL);
-
-	/**
-	 * Pushes the referred value onto the stack.
-	 **/
-	void push();
-
-	/**
-	 * Gets the Lua state associated with this
-	 * reference.
-	 **/
-	lua_State *getL() const;
-
-	/**
-	 * Associates a new Lua state with this reference.
-	 * THIS IS DANGEROUS! It is only designed to be
-	 * used with different coroutines from the same
-	 * main Lua state!
-	 **/
-	void setL(lua_State *newL);
+	void push(lua_State *L);
 
 
 private:
 private:
 
 
-	// The Lua state in which the reference resides.
-	lua_State *L;
+	// A pinned coroutine (probably the main thread) belonging to the Lua state
+	// in which the reference resides.
+	lua_State *pinnedL;
 
 
 	// Index to the Lua reference.
 	// Index to the Lua reference.
 	int idx;
 	int idx;

+ 1 - 1
jni/love/src/common/Variant.cpp

@@ -31,7 +31,7 @@ static love::Type extractudatatype(lua_State *L, int idx)
 	Type t = INVALID_ID;
 	Type t = INVALID_ID;
 	if (!lua_isuserdata(L, idx))
 	if (!lua_isuserdata(L, idx))
 		return t;
 		return t;
-	if (luaL_getmetafield(L, idx, "__tostring") == 0)
+	if (luaL_getmetafield(L, idx, "type") == 0)
 		return t;
 		return t;
 	lua_pushvalue(L, idx);
 	lua_pushvalue(L, idx);
 	int result = lua_pcall(L, 1, 1, 0);
 	int result = lua_pcall(L, 1, 1, 0);

+ 47 - 10
jni/love/src/common/runtime.cpp

@@ -47,6 +47,14 @@ static int w__gc(lua_State *L)
 }
 }
 
 
 static int w__tostring(lua_State *L)
 static int w__tostring(lua_State *L)
+{
+	Proxy *p = (Proxy *) lua_touserdata(L, 1);
+	const char *typname = lua_tostring(L, lua_upvalueindex(1));
+	lua_pushfstring(L, "%s: %p", typname, p->object);
+	return 1;
+}
+
+static int w__type(lua_State *L)
 {
 {
 	lua_pushvalue(L, lua_upvalueindex(1));
 	lua_pushvalue(L, lua_upvalueindex(1));
 	return 1;
 	return 1;
@@ -83,7 +91,7 @@ Reference *luax_refif(lua_State *L, int type)
 
 
 void luax_printstack(lua_State *L)
 void luax_printstack(lua_State *L)
 {
 {
-	for (int i = 1; i<=lua_gettop(L); i++)
+	for (int i = 1; i <= lua_gettop(L); i++)
 		std::cout << i << " - " << luaL_typename(L, i) << std::endl;
 		std::cout << i << " - " << luaL_typename(L, i) << std::endl;
 }
 }
 
 
@@ -313,9 +321,9 @@ int luax_register_type(lua_State *L, love::Type type, const luaL_Reg *f, bool pu
 	lua_pushcclosure(L, w__tostring, 1);
 	lua_pushcclosure(L, w__tostring, 1);
 	lua_setfield(L, -2, "__tostring");
 	lua_setfield(L, -2, "__tostring");
 
 
-	// Add tostring to as type() as well.
+	// Add type
 	lua_pushstring(L, tname);
 	lua_pushstring(L, tname);
-	lua_pushcclosure(L, w__tostring, 1);
+	lua_pushcclosure(L, w__type, 1);
 	lua_setfield(L, -2, "type");
 	lua_setfield(L, -2, "type");
 
 
 	// Add typeOf
 	// Add typeOf
@@ -590,8 +598,6 @@ int luax_insistregistry(lua_State *L, Registry r)
 {
 {
 	switch (r)
 	switch (r)
 	{
 	{
-	case REGISTRY_GC:
-		return luax_insistlove(L, "_gc");
 	case REGISTRY_MODULES:
 	case REGISTRY_MODULES:
 		return luax_insistlove(L, "_modules");
 		return luax_insistlove(L, "_modules");
 	case REGISTRY_OBJECTS:
 	case REGISTRY_OBJECTS:
@@ -605,8 +611,6 @@ int luax_getregistry(lua_State *L, Registry r)
 {
 {
 	switch (r)
 	switch (r)
 	{
 	{
-	case REGISTRY_GC:
-		return luax_getlove(L, "_gc");
 	case REGISTRY_MODULES:
 	case REGISTRY_MODULES:
 		return luax_getlove(L, "_modules");
 		return luax_getlove(L, "_modules");
 	case REGISTRY_OBJECTS:
 	case REGISTRY_OBJECTS:
@@ -617,21 +621,54 @@ int luax_getregistry(lua_State *L, Registry r)
 	}
 	}
 }
 }
 
 
+static const char *MAIN_THREAD_KEY = "_love_mainthread";
+
+lua_State *luax_insistpinnedthread(lua_State *L)
+{
+	lua_getfield(L, LUA_REGISTRYINDEX, MAIN_THREAD_KEY);
+
+	if (lua_isnoneornil(L, -1))
+	{
+		lua_pop(L, 1);
+
+		// lua_pushthread returns 1 if it's actually the main thread, but we
+		// can't actually get the real main thread if lua_pushthread doesn't
+		// return it (in Lua 5.1 at least), so we ignore that for now...
+		// We do store a strong reference to the current thread/coroutine in
+		// the registry, however.
+		lua_pushthread(L);
+		lua_pushvalue(L, -1);
+		lua_setfield(L, LUA_REGISTRYINDEX, MAIN_THREAD_KEY);
+	}
+
+	lua_State *thread = lua_tothread(L, -1);
+	lua_pop(L, 1);
+	return thread;
+}
+
+lua_State *luax_getpinnedthread(lua_State *L)
+{
+	lua_getfield(L, LUA_REGISTRYINDEX, MAIN_THREAD_KEY);
+	lua_State *thread = lua_tothread(L, -1);
+	lua_pop(L, 1);
+	return thread;
+}
+
 extern "C" int luax_typerror(lua_State *L, int narg, const char *tname)
 extern "C" int luax_typerror(lua_State *L, int narg, const char *tname)
 {
 {
 	int argtype = lua_type(L, narg);
 	int argtype = lua_type(L, narg);
 	const char *argtname = 0;
 	const char *argtname = 0;
 
 
 	// We want to use the love type name for userdata, if possible.
 	// We want to use the love type name for userdata, if possible.
-	if (argtype == LUA_TUSERDATA && luaL_getmetafield(L, narg, "__tostring") != 0)
+	if (argtype == LUA_TUSERDATA && luaL_getmetafield(L, narg, "type") != 0)
 	{
 	{
 		lua_pushvalue(L, narg);
 		lua_pushvalue(L, narg);
 		if (lua_pcall(L, 1, 1, 0) == 0 && lua_type(L, -1) == LUA_TSTRING)
 		if (lua_pcall(L, 1, 1, 0) == 0 && lua_type(L, -1) == LUA_TSTRING)
 		{
 		{
 			argtname = lua_tostring(L, -1);
 			argtname = lua_tostring(L, -1);
 
 
-			// Non-love userdata might have a tostring metamethod which doesn't
-			// describe its type, so we only use __tostring for love types.
+			// Non-love userdata might have a type metamethod which doesn't
+			// describe its type properly, so we only use it for love types.
 			love::Type t;
 			love::Type t;
 			if (!love::getType(argtname, t))
 			if (!love::getType(argtname, t))
 				argtname = 0;
 				argtname = 0;

+ 18 - 1
jni/love/src/common/runtime.h

@@ -49,7 +49,6 @@ class Reference;
  **/
  **/
 enum Registry
 enum Registry
 {
 {
-	REGISTRY_GC,
 	REGISTRY_MODULES,
 	REGISTRY_MODULES,
 	REGISTRY_OBJECTS
 	REGISTRY_OBJECTS
 };
 };
@@ -390,6 +389,24 @@ int luax_insistregistry(lua_State *L, Registry r);
  **/
  **/
 int luax_getregistry(lua_State *L, Registry r);
 int luax_getregistry(lua_State *L, Registry r);
 
 
+/**
+ * Gets (and pins if needed) a "pinned" Lua thread (coroutine) in the specified
+ * Lua state. This will usually be the main Lua thread, unless the first call
+ * to this function for a specific Lua state is made from within a coroutine.
+ * NOTE: This does not push anything to the stack.
+ **/
+lua_State *luax_insistpinnedthread(lua_State *L);
+
+/**
+ * Gets a "pinned" Lua thread (coroutine) in the specified Lua state. This will
+ * usually be the main Lua thread. This can be used to access global variables
+ * in a specific Lua state without needing another alive lua_State value.
+ * PRECONDITION: luax_insistpinnedthread must have been called on a lua_State
+ * value corresponding to the Lua state which will be used with this function.
+ * NOTE: This does not push anything to the stack.
+ **/
+lua_State *luax_getpinnedthread(lua_State *L);
+
 extern "C" { // Also called from luasocket
 extern "C" { // Also called from luasocket
 	int luax_typerror(lua_State *L, int narg, const char *tname);
 	int luax_typerror(lua_State *L, int narg, const char *tname);
 }
 }

+ 2 - 12
jni/love/src/libraries/glad/glad.cpp

@@ -1,11 +1,9 @@
 #include <string.h>
 #include <string.h>
 #include "glad.hpp"
 #include "glad.hpp"
-#include <dlfcn.h>
-
-#define GLAD_USE_SDL
 
 
 namespace glad {
 namespace glad {
 
 
+
 #ifdef GLAD_USE_SDL
 #ifdef GLAD_USE_SDL
 #include <SDL.h>
 #include <SDL.h>
 #if !SDL_VERSION_ATLEAST(2,0,0)
 #if !SDL_VERSION_ATLEAST(2,0,0)
@@ -15,17 +13,9 @@ namespace glad {
 #include <assert.h>
 #include <assert.h>
 #endif
 #endif
 
 
-void* LoaderDlsymOrSDLGetProc (const char* name) {
-	void* proc = dlsym(RTLD_DEFAULT, name);
-	if (!proc) {
-		proc = SDL_GL_GetProcAddress (name);
-	}
-	return proc;
-}
-
 bool gladLoadGL(void) {
 bool gladLoadGL(void) {
 #ifdef GLAD_USE_SDL
 #ifdef GLAD_USE_SDL
-    return gladLoadGLLoader(LoaderDlsymOrSDLGetProc);
+    return gladLoadGLLoader(SDL_GL_GetProcAddress);
 #else
 #else
     // generic gladLoadGL is not implemented, use gladLoadGLLoader or define GLAD_USE_SDL
     // generic gladLoadGL is not implemented, use gladLoadGLLoader or define GLAD_USE_SDL
     assert(0);
     assert(0);

+ 27 - 27
jni/love/src/love.cpp

@@ -164,7 +164,9 @@ static int love_preload(lua_State *L, lua_CFunction f, const char *name)
 	return 0;
 	return 0;
 }
 }
 
 
-static int l_print_sdl_log (lua_State *L) {
+#ifdef LOVE_ANDROID
+static int l_print_sdl_log(lua_State *L)
+{
 	int nargs = lua_gettop(L);
 	int nargs = lua_gettop(L);
 
 
 	if (nargs == 0)
 	if (nargs == 0)
@@ -172,44 +174,42 @@ static int l_print_sdl_log (lua_State *L) {
 
 
 	std::string out_string = "";
 	std::string out_string = "";
 
 
-	for (int i = 1; i <= nargs; i++) {
-		int type = lua_type (L, i);
+	for (int i = 1; i <= nargs; i++)
+	{
+		int type = lua_type(L, i);
 		char pointer_buf[16];
 		char pointer_buf[16];
-		switch (type) {
-			case LUA_TNUMBER:
-			case LUA_TSTRING:
-				out_string += lua_tostring (L, i);
-				break;
-			case LUA_TNIL:
-				out_string += "nil";
-				break;
-			case LUA_TBOOLEAN:
-				out_string += lua_toboolean (L, i) ? "true" : "false";
-				break;
-			default:
-				out_string += lua_typename (L, lua_type(L, i));
-				sprintf (pointer_buf, ": 0x%x", (unsigned int) lua_topointer (L, i));
-				out_string += pointer_buf;
-				break;
+		switch (type)
+		{
+		case LUA_TNUMBER:
+		case LUA_TSTRING:
+			out_string += lua_tostring(L, i);
+			break;
+		case LUA_TNIL:
+			out_string += "nil";
+			break;
+		case LUA_TBOOLEAN:
+			out_string += lua_toboolean(L, i) ? "true" : "false";
+			break;
+		default:
+			out_string += lua_typename(L, lua_type(L, i));
+			sprintf(pointer_buf, ": 0x%lx", (size_t) lua_topointer(L, i));
+			out_string += pointer_buf;
+			break;
 		}
 		}
 
 
-		if (i != nargs - 1) {
+		if (i != nargs - 1)
 			out_string += "\t";
 			out_string += "\t";
-		}
 	}
 	}
 
 
-	SDL_Log ("[LOVE] %s", out_string.c_str());
+	SDL_Log("[LOVE] %s", out_string.c_str());
 	return 0;
 	return 0;
 }
 }
+#endif
 
 
 int main(int argc, char **argv)
 int main(int argc, char **argv)
 {
 {
 	int retval = 0;
 	int retval = 0;
 
 
-#ifdef LOVE_ANDROID
-	SDL_SetHint("LOVE_GRAPHICS_USE_OPENGLES", "1");
-#endif
-
 #ifdef LOVE_IOS
 #ifdef LOVE_IOS
 	int orig_argc = argc;
 	int orig_argc = argc;
 	char **orig_argv = argv;
 	char **orig_argv = argv;
@@ -262,7 +262,7 @@ int main(int argc, char **argv)
 	luaL_openlibs(L);
 	luaL_openlibs(L);
 
 
 #ifdef LOVE_ANDROID
 #ifdef LOVE_ANDROID
-	lua_register (L, "print", l_print_sdl_log);
+	lua_register(L, "print", l_print_sdl_log);
 #endif
 #endif
 
 
 	// Add love to package.preload for easy requiring.
 	// Add love to package.preload for easy requiring.

+ 1 - 3
jni/love/src/modules/audio/openal/Audio.cpp

@@ -36,13 +36,11 @@ Audio::PoolThread::PoolThread(Pool *pool)
 	: pool(pool)
 	: pool(pool)
 	, finish(false)
 	, finish(false)
 {
 {
-	mutex = thread::newMutex();
 	threadName = "AudioPool";
 	threadName = "AudioPool";
 }
 }
 
 
 Audio::PoolThread::~PoolThread()
 Audio::PoolThread::~PoolThread()
 {
 {
-	delete mutex;
 }
 }
 
 
 
 
@@ -193,7 +191,7 @@ void Audio::pause()
 {
 {
 	pool->pause();
 	pool->pause();
 #ifdef LOVE_ANDROID
 #ifdef LOVE_ANDROID
-	alcDevicePauseSOFT (device);
+	alcDevicePauseSOFT(device);
 #endif
 #endif
 }
 }
 
 

+ 1 - 1
jni/love/src/modules/audio/openal/Audio.h

@@ -127,7 +127,7 @@ private:
 		volatile bool finish;
 		volatile bool finish;
 
 
 		// finish lock
 		// finish lock
-		thread::Mutex *mutex;
+		love::thread::MutexRef mutex;
 
 
 	public:
 	public:
 		PoolThread(Pool *pool);
 		PoolThread(Pool *pool);

+ 0 - 6
jni/love/src/modules/audio/openal/Pool.cpp

@@ -32,7 +32,6 @@ namespace openal
 Pool::Pool()
 Pool::Pool()
 	: sources()
 	: sources()
 	, totalSources(0)
 	, totalSources(0)
-	, mutex(nullptr)
 {
 {
 	// Clear errors.
 	// Clear errors.
 	alGetError();
 	alGetError();
@@ -53,9 +52,6 @@ Pool::Pool()
 	if (totalSources < 4)
 	if (totalSources < 4)
 		throw love::Exception("Could not generate sources.");
 		throw love::Exception("Could not generate sources.");
 
 
-	// Create the mutex.
-	mutex = thread::newMutex();
-
 #ifdef AL_SOFT_direct_channels
 #ifdef AL_SOFT_direct_channels
 	ALboolean hasext = alIsExtensionPresent("AL_SOFT_direct_channels");
 	ALboolean hasext = alIsExtensionPresent("AL_SOFT_direct_channels");
 #endif
 #endif
@@ -79,8 +75,6 @@ Pool::~Pool()
 {
 {
 	stop();
 	stop();
 
 
-	delete mutex;
-
 	// Free all sources.
 	// Free all sources.
 	alDeleteSources(totalSources, sources);
 	alDeleteSources(totalSources, sources);
 }
 }

+ 1 - 1
jni/love/src/modules/audio/openal/Pool.h

@@ -124,7 +124,7 @@ private:
 
 
 	// Only one thread can access this object at the same time. This mutex will
 	// Only one thread can access this object at the same time. This mutex will
 	// make sure of that.
 	// make sure of that.
-	thread::Mutex *mutex;
+	love::thread::MutexRef mutex;
 
 
 }; // Pool
 }; // Pool
 
 

+ 0 - 6
jni/love/src/modules/event/Event.cpp

@@ -81,14 +81,8 @@ Message *Message::fromLua(lua_State *L, int n)
 	return new Message(name, vargs);
 	return new Message(name, vargs);
 }
 }
 
 
-Event::Event()
-{
-	mutex = thread::newMutex();
-}
-
 Event::~Event()
 Event::~Event()
 {
 {
-	delete mutex;
 }
 }
 
 
 void Event::push(Message *msg)
 void Event::push(Message *msg)

+ 1 - 2
jni/love/src/modules/event/Event.h

@@ -59,7 +59,6 @@ private:
 class Event : public Module
 class Event : public Module
 {
 {
 public:
 public:
-	Event();
 	virtual ~Event();
 	virtual ~Event();
 
 
 	// Implements Module.
 	// Implements Module.
@@ -73,7 +72,7 @@ public:
 	virtual Message *wait() = 0;
 	virtual Message *wait() = 0;
 
 
 protected:
 protected:
-	thread::Mutex *mutex;
+	love::thread::MutexRef mutex;
 	std::queue<Message *> queue;
 	std::queue<Message *> queue;
 
 
 }; // Event
 }; // Event

+ 11 - 13
jni/love/src/modules/event/sdl/Event.cpp

@@ -45,7 +45,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);
 }
 }
@@ -53,7 +53,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)
@@ -71,7 +71,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)
 	{
 	{
@@ -153,7 +153,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;
@@ -177,7 +176,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;
 		}
 		}
@@ -395,7 +394,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;
 
 
@@ -511,24 +510,24 @@ Message *Event::convertJoystickEvent(const SDL_Event &e) const
 			msg = new Message("joystickremoved", vargs);
 			msg = new Message("joystickremoved", vargs);
 		}
 		}
 		break;
 		break;
-	default:
-		break;
 #ifdef LOVE_ANDROID
 #ifdef LOVE_ANDROID
-		case SDL_WINDOWEVENT_MINIMIZED:
+	case SDL_WINDOWEVENT_MINIMIZED:
 		{
 		{
-			audio::Audio *audio = Module::getInstance<audio::Audio>(Module::M_AUDIO);
+			auto audio = Module::getInstance<audio::Audio>(Module::M_AUDIO);
 			if (audio)
 			if (audio)
 				audio->pause();
 				audio->pause();
 		}
 		}
 		break;
 		break;
-		case SDL_WINDOWEVENT_RESTORED:
+	case SDL_WINDOWEVENT_RESTORED:
 		{
 		{
-			audio::Audio *audio = Module::getInstance<audio::Audio>(Module::M_AUDIO);
+			auto audio = Module::getInstance<audio::Audio>(Module::M_AUDIO);
 			if (audio)
 			if (audio)
 				audio->resume();
 				audio->resume();
 		}
 		}
 		break;
 		break;
 #endif
 #endif
+	default:
+		break;
 	}
 	}
 
 
 	// We gave +1 refs to the StrongRef list, so we should release them.
 	// We gave +1 refs to the StrongRef list, so we should release them.
@@ -754,7 +753,6 @@ std::map<SDL_Keycode, love::keyboard::Keyboard::Key> Event::createKeyMap()
 	k[SDLK_EXECUTE] = Keyboard::KEY_EXECUTE;
 	k[SDLK_EXECUTE] = Keyboard::KEY_EXECUTE;
 	k[SDLK_HELP] = Keyboard::KEY_HELP;
 	k[SDLK_HELP] = Keyboard::KEY_HELP;
 	k[SDLK_MENU] = Keyboard::KEY_MENU;
 	k[SDLK_MENU] = Keyboard::KEY_MENU;
-	k[SDLK_AC_SEARCH] = Keyboard::KEY_SEARCH;
 	k[SDLK_SELECT] = Keyboard::KEY_SELECT;
 	k[SDLK_SELECT] = Keyboard::KEY_SELECT;
 	k[SDLK_STOP] = Keyboard::KEY_STOP;
 	k[SDLK_STOP] = Keyboard::KEY_STOP;
 	k[SDLK_AGAIN] = Keyboard::KEY_AGAIN;
 	k[SDLK_AGAIN] = Keyboard::KEY_AGAIN;

+ 49 - 35
jni/love/src/modules/filesystem/physfs/Filesystem.cpp

@@ -50,10 +50,10 @@
 #	include "common/iOS.h"
 #	include "common/iOS.h"
 #endif
 #endif
 
 
-#include "SDL.h"
 #include <string>
 #include <string>
 
 
 #ifdef LOVE_ANDROID
 #ifdef LOVE_ANDROID
+#include <SDL.h>
 #include "common/android.h"
 #include "common/android.h"
 #endif
 #endif
 
 
@@ -180,22 +180,22 @@ bool Filesystem::setIdentity(const char *ident, bool appendToPath)
 
 
 	save_path_full = std::string(SDL_AndroidGetInternalStoragePath()) + std::string("/save/") + save_identity;
 	save_path_full = std::string(SDL_AndroidGetInternalStoragePath()) + std::string("/save/") + save_identity;
 
 
-	if (love::android::directoryExists (save_path_full.c_str())) {
-		SDL_Log ("dir exists");
-	} else {
-		SDL_Log ("does not exist");
-	}
+	if (love::android::directoryExists(save_path_full.c_str()))
+		SDL_Log("dir exists");
+	else
+		SDL_Log("does not exist");
 
 
-	if (!love::android::directoryExists (save_path_full.c_str())) {
-		if (!love::android::mkdir (save_path_full.c_str())) {
-			SDL_Log ("Error: Could not create save directory %s!", save_path_full.c_str());
-		} else {
-			SDL_Log ("Save directory %s successfuly created!", save_path_full.c_str());
-		}
-	} else {
-		SDL_Log ("Save directory %s exists!", save_path_full.c_str());
+	if (!love::android::directoryExists(save_path_full.c_str()))
+	{
+		if (!love::android::mkdir(save_path_full.c_str()))
+			SDL_Log("Error: Could not create save directory %s!", save_path_full.c_str());
+		else
+			SDL_Log("Save directory %s successfuly created!", save_path_full.c_str());
 	}
 	}
+	else
+		SDL_Log("Save directory %s exists!", save_path_full.c_str());
 #endif
 #endif
+
 	// We now have something like:
 	// We now have something like:
 	// save_identity: game
 	// save_identity: game
 	// save_path_relative: ./LOVE/game
 	// save_path_relative: ./LOVE/game
@@ -235,52 +235,61 @@ bool Filesystem::setSource(const char *source)
 	std::string new_search_path = source;
 	std::string new_search_path = source;
 
 
 #ifdef LOVE_ANDROID
 #ifdef LOVE_ANDROID
-	if (!love::android::createStorageDirectories ()) {
-		SDL_Log ("Error creating storage directories!");
-	}
+	if (!love::android::createStorageDirectories ())
+		SDL_Log("Error creating storage directories!");
 
 
 	char* game_archive_ptr = NULL;
 	char* game_archive_ptr = NULL;
 	size_t game_archive_size = 0;
 	size_t game_archive_size = 0;
 	bool archive_loaded = false;
 	bool archive_loaded = false;
 
 
 	// try to load the game that was sent to LÖVE via a Intent
 	// try to load the game that was sent to LÖVE via a Intent
-	archive_loaded = love::android::loadGameArchiveToMemory (love::android::getSelectedGameFile(), &game_archive_ptr, &game_archive_size);
+	archive_loaded = love::android::loadGameArchiveToMemory(love::android::getSelectedGameFile(), &game_archive_ptr, &game_archive_size);
 
 
-	if (!archive_loaded) {
+	if (!archive_loaded)
+	{
 		// try to load the game in the assets/ folder
 		// try to load the game in the assets/ folder
-		archive_loaded = love::android::loadGameArchiveToMemory ("game.love", &game_archive_ptr, &game_archive_size);
+		archive_loaded = love::android::loadGameArchiveToMemory("game.love", &game_archive_ptr, &game_archive_size);
 	}
 	}
 
 
-	if (archive_loaded) {
-		if (PHYSFS_mountMemory (game_archive_ptr, game_archive_size, love::android::freeGameArchiveMemory, "archive.zip", "/", 0)) {
-			SDL_Log ("Mounting of in-memory game archive successful!");
-		} else {
-			SDL_Log ("Mounting of in-memory game archive failed!");
-			love::android::freeGameArchiveMemory (game_archive_ptr);
+	if (archive_loaded)
+	{
+		if (PHYSFS_mountMemory(game_archive_ptr, game_archive_size, love::android::freeGameArchiveMemory, "archive.zip", "/", 0))
+			SDL_Log("Mounting of in-memory game archive successful!");
+		else
+		{
+			SDL_Log("Mounting of in-memory game archive failed!");
+			love::android::freeGameArchiveMemory(game_archive_ptr);
 			return false;
 			return false;
 		}
 		}
-	} else {
+	}
+	else
+	{
 		// try to load the game in the directory that was sent to LÖVE via an
 		// try to load the game in the directory that was sent to LÖVE via an
 		// Intent ...
 		// Intent ...
 		std::string game_path = std::string(love::android::getSelectedGameFile());
 		std::string game_path = std::string(love::android::getSelectedGameFile());
 
 
-		if (game_path == "") {
+		if (game_path == "")
+		{
 			// ... or fall back to the game at /sdcard/lovegame
 			// ... or fall back to the game at /sdcard/lovegame
 			game_path = "/sdcard/lovegame/";
 			game_path = "/sdcard/lovegame/";
 		}
 		}
 
 
 		SDL_RWops *sdcard_main = SDL_RWFromFile(std::string(game_path + "main.lua").c_str(), "rb");
 		SDL_RWops *sdcard_main = SDL_RWFromFile(std::string(game_path + "main.lua").c_str(), "rb");
 
 
-		if (sdcard_main) {
-			SDL_Log ("using game from %s", game_path.c_str());
+		if (sdcard_main)
+		{
+			SDL_Log("using game from %s", game_path.c_str());
 			new_search_path = game_path;
 			new_search_path = game_path;
 			sdcard_main->close(sdcard_main);
 			sdcard_main->close(sdcard_main);
 
 
-			if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1)) {
-				SDL_Log ("mounting of %s failed", new_search_path.c_str());
+			if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1))
+			{
+				SDL_Log("mounting of %s failed", new_search_path.c_str());
 				return false;
 				return false;
 			}
 			}
-		} else {
+		}
+		else
+		{
 			// Neither assets/game.love or /sdcard/lovegame was mounted
 			// Neither assets/game.love or /sdcard/lovegame was mounted
 			// sucessfully, therefore simply fail.
 			// sucessfully, therefore simply fail.
 			return false;
 			return false;
@@ -288,8 +297,7 @@ bool Filesystem::setSource(const char *source)
 	}
 	}
 #else
 #else
 	// Add the directory.
 	// Add the directory.
-	if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1)) {
-		SDL_Log ("mounting of %s failed", new_search_path.c_str());
+	if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1))
 		return false;
 		return false;
 #endif
 #endif
 
 
@@ -501,7 +509,13 @@ const char *Filesystem::getWorkingDirectory()
 
 
 std::string Filesystem::getUserDirectory()
 std::string Filesystem::getUserDirectory()
 {
 {
+#ifdef LOVE_IOS
+	// PHYSFS_getUserDir doesn't give exactly the path we want on iOS.
+	static std::string userDir = normalize(love::ios::getHomeDirectory());
+#else
 	static std::string userDir = normalize(PHYSFS_getUserDir());
 	static std::string userDir = normalize(PHYSFS_getUserDir());
+#endif
+
 	return userDir;
 	return userDir;
 }
 }
 
 

+ 2 - 2
jni/love/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!");

+ 1 - 1
jni/love/src/modules/font/BMFontRasterizer.h

@@ -50,7 +50,7 @@ public:
 	GlyphData *getGlyphData(uint32 glyph) const override;
 	GlyphData *getGlyphData(uint32 glyph) const override;
 	int getGlyphCount() const override;
 	int getGlyphCount() const override;
 	bool hasGlyph(uint32 glyph) const override;
 	bool hasGlyph(uint32 glyph) const override;
-	float getKerning(uint32 leftglyph, uint32 rightglyph) const;
+	float getKerning(uint32 leftglyph, uint32 rightglyph) const override;
 
 
 	static bool accepts(love::filesystem::FileData *fontdef);
 	static bool accepts(love::filesystem::FileData *fontdef);
 
 

+ 57 - 20
jni/love/src/modules/graphics/opengl/Graphics.cpp

@@ -24,7 +24,6 @@
 #include "common/Vector.h"
 #include "common/Vector.h"
 
 
 #include "Graphics.h"
 #include "Graphics.h"
-#include "window/sdl/Window.h"
 #include "font/Font.h"
 #include "font/Font.h"
 #include "Polyline.h"
 #include "Polyline.h"
 
 
@@ -50,7 +49,8 @@ namespace opengl
 {
 {
 
 
 Graphics::Graphics()
 Graphics::Graphics()
-	: quadIndices(nullptr)
+	: currentWindow(Module::getInstance<love::window::Window>(Module::M_WINDOW))
+	, quadIndices(nullptr)
 	, width(0)
 	, width(0)
 	, height(0)
 	, height(0)
 	, created(false)
 	, created(false)
@@ -62,20 +62,21 @@ Graphics::Graphics()
 	states.reserve(10);
 	states.reserve(10);
 	states.push_back(DisplayState());
 	states.push_back(DisplayState());
 
 
-	currentWindow = love::window::sdl::Window::createSingleton();
-
-	int w, h;
-	love::window::WindowSettings wsettings;
+	if (currentWindow.get())
+	{
+		int w, h;
+		love::window::WindowSettings wsettings;
 
 
-	currentWindow->getWindow(w, h, wsettings);
+		currentWindow->getWindow(w, h, wsettings);
 
 
-	if (currentWindow->isCreated())
-		setMode(w, h, wsettings.sRGB);
+		if (currentWindow->isOpen())
+			setMode(w, h, wsettings.sRGB);
+	}
 }
 }
 
 
 Graphics::~Graphics()
 Graphics::~Graphics()
 {
 {
-	// We do this manually so the love objects get released before the window.
+	// We do this manually so the graphics objects are released before the window.
 	states.clear();
 	states.clear();
 	defaultFont.set(nullptr);
 	defaultFont.set(nullptr);
 
 
@@ -87,8 +88,6 @@ Graphics::~Graphics()
 
 
 	if (quadIndices)
 	if (quadIndices)
 		delete quadIndices;
 		delete quadIndices;
-
-	currentWindow->release();
 }
 }
 
 
 const char *Graphics::getName() const
 const char *Graphics::getName() const
@@ -195,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.");
 
 
@@ -238,6 +237,8 @@ void Graphics::setViewportSize(int width, int height)
 
 
 bool Graphics::setMode(int width, int height, bool &sRGB)
 bool Graphics::setMode(int width, int height, bool &sRGB)
 {
 {
+	currentWindow.set(Module::getInstance<love::window::Window>(Module::M_WINDOW));
+
 	this->width = width;
 	this->width = width;
 	this->height = height;
 	this->height = height;
 
 
@@ -355,7 +356,7 @@ bool Graphics::isActive() const
 {
 {
 	// The graphics module is only completely 'active' if there's a window, a
 	// The graphics module is only completely 'active' if there's a window, a
 	// context, and the active variable is set.
 	// context, and the active variable is set.
-	return active && isCreated() && currentWindow && currentWindow->isCreated();
+	return active && isCreated() && currentWindow.get() && currentWindow->isOpen();
 }
 }
 
 
 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*/)
@@ -494,11 +495,13 @@ void Graphics::discard(const std::vector<bool> &colorbuffers, bool stencil)
 	}
 	}
 	else
 	else
 	{
 	{
-		int activecanvascount = (int) states.back().canvases.size();
+		int rendertargetcount = 1;
+		if (Canvas::current)
+			rendertargetcount = (int) states.back().canvases.size();
 
 
 		for (int i = 0; i < (int) colorbuffers.size(); i++)
 		for (int i = 0; i < (int) colorbuffers.size(); i++)
 		{
 		{
-			if (colorbuffers[i] && i < activecanvascount)
+			if (colorbuffers[i] && i < rendertargetcount)
 				attachments.push_back(GL_COLOR_ATTACHMENT0 + i);
 				attachments.push_back(GL_COLOR_ATTACHMENT0 + i);
 		}
 		}
 
 
@@ -536,7 +539,8 @@ void Graphics::present()
 	glBindRenderbuffer(GL_RENDERBUFFER, info.info.uikit.colorbuffer);
 	glBindRenderbuffer(GL_RENDERBUFFER, info.info.uikit.colorbuffer);
 #endif
 #endif
 
 
-	currentWindow->swapBuffers();
+	if (currentWindow.get())
+		currentWindow->swapBuffers();
 
 
 	// Restore the currently active canvas, if there is one.
 	// Restore the currently active canvas, if there is one.
 	setCanvas(canvases);
 	setCanvas(canvases);
@@ -660,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);
 }
 }
@@ -945,7 +949,7 @@ void Graphics::setBlendMode(BlendMode mode, bool multiplyalpha)
 		func = GL_FUNC_REVERSE_SUBTRACT;
 		func = GL_FUNC_REVERSE_SUBTRACT;
 	case BLEND_ADD:
 	case BLEND_ADD:
 		srcRGB = GL_ONE;
 		srcRGB = GL_ONE;
-		srcA = GL_SRC_ALPHA; // FIXME: This isn't correct...
+		srcA = GL_ZERO;
 		dstRGB = dstA = GL_ONE;
 		dstRGB = dstA = GL_ONE;
 		break;
 		break;
 	case BLEND_SCREEN:
 	case BLEND_SCREEN:
@@ -1133,6 +1137,13 @@ void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h, floa
 		return;
 		return;
 	}
 	}
 
 
+	// Radius values that are more than half the rectangle's size aren't handled
+	// correctly (for now)...
+	if (w >= 0.02f)
+		rx = std::min(rx, w / 2.0f - 0.01f);
+	if (h >= 0.02f)
+		ry = std::min(ry, h / 2.0f - 0.01f);
+
 	points = std::max(points, 1);
 	points = std::max(points, 1);
 
 
 	const float half_pi = static_cast<float>(LOVE_M_PI / 2);
 	const float half_pi = static_cast<float>(LOVE_M_PI / 2);
@@ -1308,8 +1319,34 @@ love::image::ImageData *Graphics::newScreenshot(love::image::Image *image, bool
 		throw love::Exception("Out of memory.");
 		throw love::Exception("Out of memory.");
 	}
 	}
 
 
+#ifdef LOVE_IOS
+	SDL_SysWMinfo info = {};
+	SDL_VERSION(&info.version);
+	SDL_GetWindowWMInfo(SDL_GL_GetCurrentWindow(), &info);
+
+	if (info.info.uikit.resolveFramebuffer != 0)
+	{
+		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, info.info.uikit.resolveFramebuffer);
+
+		// We need to do an explicit MSAA resolve on iOS, because it uses GLES
+		// FBOs rather than a system framebuffer.
+		if (GLAD_ES_VERSION_3_0)
+			glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+		else if (GLAD_APPLE_framebuffer_multisample)
+			glResolveMultisampleFramebufferAPPLE();
+
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, info.info.uikit.resolveFramebuffer);
+	}
+#endif
+
 	glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
 	glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
 
 
+#ifdef LOVE_IOS
+	// Restore the previous binding for the main framebuffer.
+	if (info.info.uikit.resolveFramebuffer != 0)
+		glBindFramebuffer(GL_FRAMEBUFFER, gl.getDefaultFBO());
+#endif
+
 	if (!copyAlpha)
 	if (!copyAlpha)
 	{
 	{
 		// Replace alpha values with full opacity.
 		// Replace alpha values with full opacity.

+ 3 - 3
jni/love/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);
 
 
@@ -508,7 +508,7 @@ private:
 
 
 	void checkSetDefaultFont();
 	void checkSetDefaultFont();
 
 
-	love::window::Window *currentWindow;
+	StrongRef<love::window::Window> currentWindow;
 
 
 	StrongRef<Font> defaultFont;
 	StrongRef<Font> defaultFont;
 
 

+ 134 - 66
jni/love/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
 
 
 #ifdef LOVE_ANDROID
 #ifdef LOVE_ANDROID
@@ -50,18 +49,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();
@@ -69,27 +112,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 || compresseddata[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();
@@ -169,14 +214,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));
 	}
 	}
 }
 }
 
 
@@ -192,23 +248,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);
@@ -222,7 +284,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;
@@ -254,13 +317,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
@@ -291,17 +351,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);
 
 
@@ -333,31 +388,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;
 }
 }
 
 
@@ -398,14 +466,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
jni/love/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;

+ 16 - 1
jni/love/src/modules/graphics/opengl/OpenGL.cpp

@@ -40,6 +40,10 @@
 #include <SDL_syswm.h>
 #include <SDL_syswm.h>
 #endif
 #endif
 
 
+#ifdef LOVE_ANDROID
+#include <dlfcn.h>
+#endif
+
 namespace love
 namespace love
 {
 {
 namespace graphics
 namespace graphics
@@ -47,6 +51,17 @@ namespace graphics
 namespace opengl
 namespace opengl
 {
 {
 
 
+static void *LOVEGetProcAddress(const char *name)
+{
+#ifdef LOVE_ANDROID
+	void *proc = dlsym(RTLD_DEFAULT, name);
+	if (proc)
+		return proc;
+#endif
+
+	return SDL_GL_GetProcAddress(name);
+}
+
 OpenGL::OpenGL()
 OpenGL::OpenGL()
 	: stats()
 	: stats()
 	, contextInitialized(false)
 	, contextInitialized(false)
@@ -67,7 +82,7 @@ bool OpenGL::initContext()
 	if (contextInitialized)
 	if (contextInitialized)
 		return true;
 		return true;
 
 
-	if (!gladLoadGLLoader(SDL_GL_GetProcAddress))
+	if (!gladLoadGLLoader(LOVEGetProcAddress))
 		return false;
 		return false;
 
 
 	initOpenGLFunctions();
 	initOpenGLFunctions();

+ 1 - 1
jni/love/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 - 25
jni/love/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);
 	}
 	}
 
 
@@ -1557,11 +1592,6 @@ int w_rectangle(lua_State *L)
 	float rx = (float)luaL_optnumber(L, 6, 0.0);
 	float rx = (float)luaL_optnumber(L, 6, 0.0);
 	float ry = (float)luaL_optnumber(L, 7, rx);
 	float ry = (float)luaL_optnumber(L, 7, rx);
 
 
-	if (w > 0.0 && rx >= w / 2.0)
-		return luaL_error(L, "Invalid rectangle x-axis radius (must be less than half the width)");
-	if (h > 0.0 && ry >= h / 2.0)
-		return luaL_error(L, "Invalid rectangle y-axis radius (must be less than half the height)");
-
 	int points;
 	int points;
 	if (lua_isnoneornil(L, 8))
 	if (lua_isnoneornil(L, 8))
 		points = std::max(rx, ry) > 20.0 ? (int)(std::max(rx, ry) / 2) : 10;
 		points = std::max(rx, ry) > 20.0 ? (int)(std::max(rx, ry) / 2) : 10;

+ 16 - 3
jni/love/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)

+ 18 - 38
jni/love/src/modules/graphics/opengl/wrap_SpriteBatch.cpp

@@ -39,19 +39,17 @@ SpriteBatch *luax_checkspritebatch(lua_State *L, int idx)
 	return luax_checktype<SpriteBatch>(L, idx, GRAPHICS_SPRITE_BATCH_ID);
 	return luax_checktype<SpriteBatch>(L, idx, GRAPHICS_SPRITE_BATCH_ID);
 }
 }
 
 
-int w_SpriteBatch_add(lua_State *L)
+static inline int w_SpriteBatch_add_or_set(lua_State *L, SpriteBatch *t, int startidx, int index)
 {
 {
-	SpriteBatch *t = luax_checkspritebatch(L, 1);
 	Quad *quad = nullptr;
 	Quad *quad = nullptr;
-	int startidx = 2;
 
 
-	if (luax_istype(L, 2, GRAPHICS_QUAD_ID))
+	if (luax_istype(L, startidx, GRAPHICS_QUAD_ID))
 	{
 	{
-		quad = luax_totype<Quad>(L, 2, GRAPHICS_QUAD_ID);
-		startidx = 3;
+		quad = luax_totype<Quad>(L, startidx, GRAPHICS_QUAD_ID);
+		startidx++;
 	}
 	}
-	else if (lua_isnil(L, 2) && !lua_isnoneornil(L, 3))
-		return luax_typerror(L, 2, "Quad");
+	else if (lua_isnil(L, startidx) && !lua_isnoneornil(L, startidx + 1))
+		return luax_typerror(L, startidx, "Quad");
 
 
 	float x  = (float) luaL_optnumber(L, startidx + 0, 0.0);
 	float x  = (float) luaL_optnumber(L, startidx + 0, 0.0);
 	float y  = (float) luaL_optnumber(L, startidx + 1, 0.0);
 	float y  = (float) luaL_optnumber(L, startidx + 1, 0.0);
@@ -63,15 +61,23 @@ int w_SpriteBatch_add(lua_State *L)
 	float kx = (float) luaL_optnumber(L, startidx + 7, 0.0);
 	float kx = (float) luaL_optnumber(L, startidx + 7, 0.0);
 	float ky = (float) luaL_optnumber(L, startidx + 8, 0.0);
 	float ky = (float) luaL_optnumber(L, startidx + 8, 0.0);
 
 
-	int index = 0;
 	luax_catchexcept(L, [&]() {
 	luax_catchexcept(L, [&]() {
 		if (quad)
 		if (quad)
-			index = t->addq(quad, x, y, a, sx, sy, ox, oy, kx, ky);
+			index = t->addq(quad, x, y, a, sx, sy, ox, oy, kx, ky, index);
 		else
 		else
-			index = t->add(x, y, a, sx, sy, ox, oy, kx, ky);
+			index = t->add(x, y, a, sx, sy, ox, oy, kx, ky, index);
 	});
 	});
 
 
+	return index;
+}
+
+int w_SpriteBatch_add(lua_State *L)
+{
+	SpriteBatch *t = luax_checkspritebatch(L, 1);
+
+	int index = w_SpriteBatch_add_or_set(L, t, 2, -1);
 	lua_pushinteger(L, index + 1);
 	lua_pushinteger(L, index + 1);
+
 	return 1;
 	return 1;
 }
 }
 
 
@@ -80,33 +86,7 @@ int w_SpriteBatch_set(lua_State *L)
 	SpriteBatch *t = luax_checkspritebatch(L, 1);
 	SpriteBatch *t = luax_checkspritebatch(L, 1);
 	int index = (int) luaL_checknumber(L, 2) - 1;
 	int index = (int) luaL_checknumber(L, 2) - 1;
 
 
-	Quad *quad = nullptr;
-	int startidx = 3;
-
-	if (luax_istype(L, 3, GRAPHICS_QUAD_ID))
-	{
-		quad = luax_totype<Quad>(L, 3, GRAPHICS_QUAD_ID);
-		startidx = 4;
-	}
-	else if (lua_isnil(L, 3) && !lua_isnoneornil(L, 4))
-		return luax_typerror(L, 3, "Quad");
-
-	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);
-
-	luax_catchexcept(L, [&]() {
-		if (quad)
-			t->addq(quad, x, y, a, sx, sy, ox, oy, kx, ky, index);
-		else
-			t->add(x, y, a, sx, sy, ox, oy, kx, ky, index);
-	});
+	w_SpriteBatch_add_or_set(L, t, 3, index);
 
 
 	return 0;
 	return 0;
 }
 }

+ 2 - 2
jni/love/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.

+ 0 - 2
jni/love/src/modules/image/ImageData.cpp

@@ -30,12 +30,10 @@ namespace image
 ImageData::ImageData()
 ImageData::ImageData()
 	: data(nullptr)
 	: data(nullptr)
 {
 {
-	mutex = thread::newMutex();
 }
 }
 
 
 ImageData::~ImageData()
 ImageData::~ImageData()
 {
 {
-	delete mutex;
 }
 }
 
 
 size_t ImageData::getSize() const
 size_t ImageData::getSize() const

+ 3 - 3
jni/love/src/modules/image/ImageData.h

@@ -23,7 +23,7 @@
 
 
 // LOVE
 // LOVE
 #include "common/Data.h"
 #include "common/Data.h"
-#include "filesystem/File.h"
+#include "filesystem/FileData.h"
 #include "thread/threads.h"
 #include "thread/threads.h"
 
 
 using love::thread::Mutex;
 using love::thread::Mutex;
@@ -121,7 +121,7 @@ public:
 	 * @param f The file to save the encoded image data to.
 	 * @param f The file to save the encoded image data to.
 	 * @param format The format of the encoded data.
 	 * @param format The format of the encoded data.
 	 **/
 	 **/
-	virtual void encode(love::filesystem::File *f, EncodedFormat format) = 0;
+	virtual love::filesystem::FileData *encode(EncodedFormat format, const char *filename) = 0;
 
 
 	love::thread::Mutex *getMutex() const;
 	love::thread::Mutex *getMutex() const;
 
 
@@ -146,7 +146,7 @@ protected:
 	// We need to be thread-safe
 	// We need to be thread-safe
 	// so we lock when we're accessing our
 	// so we lock when we're accessing our
 	// data
 	// data
-	Mutex *mutex;
+	love::thread::MutexRef mutex;
 
 
 private:
 private:
 
 

+ 8 - 5
jni/love/src/modules/image/magpie/ImageData.cpp

@@ -144,7 +144,7 @@ void ImageData::decode(love::filesystem::FileData *data)
 	decodeHandler = decoder;
 	decodeHandler = decoder;
 }
 }
 
 
-void ImageData::encode(love::filesystem::File *f, ImageData::EncodedFormat format)
+love::filesystem::FileData *ImageData::encode(EncodedFormat format, const char *filename)
 {
 {
 	FormatHandler *encoder = nullptr;
 	FormatHandler *encoder = nullptr;
 	FormatHandler::EncodedImage encodedimage;
 	FormatHandler::EncodedImage encodedimage;
@@ -174,14 +174,14 @@ void ImageData::encode(love::filesystem::File *f, ImageData::EncodedFormat forma
 	{
 	{
 		const char *fname = "unknown";
 		const char *fname = "unknown";
 		getConstant(format, fname);
 		getConstant(format, fname);
-		throw love::Exception("no suitable image encoder for %s format.", fname);
+		throw love::Exception("No suitable image encoder for %s format.", fname);
 	}
 	}
 
 
+	love::filesystem::FileData *filedata = nullptr;
+
 	try
 	try
 	{
 	{
-		f->open(love::filesystem::File::MODE_WRITE);
-		f->write(encodedimage.data, encodedimage.size);
-		f->close();
+		filedata = new love::filesystem::FileData(encodedimage.size, filename);
 	}
 	}
 	catch (love::Exception &)
 	catch (love::Exception &)
 	{
 	{
@@ -189,7 +189,10 @@ void ImageData::encode(love::filesystem::File *f, ImageData::EncodedFormat forma
 		throw;
 		throw;
 	}
 	}
 
 
+	memcpy(filedata->getData(), encodedimage.data, encodedimage.size);
 	encoder->free(encodedimage.data);
 	encoder->free(encodedimage.data);
+
+	return filedata;
 }
 }
 
 
 } // magpie
 } // magpie

+ 1 - 2
jni/love/src/modules/image/magpie/ImageData.h

@@ -23,7 +23,6 @@
 
 
 // LOVE
 // LOVE
 #include "FormatHandler.h"
 #include "FormatHandler.h"
-#include "filesystem/File.h"
 #include "image/ImageData.h"
 #include "image/ImageData.h"
 
 
 // C++
 // C++
@@ -46,7 +45,7 @@ public:
 	virtual ~ImageData();
 	virtual ~ImageData();
 
 
 	// Implements image::ImageData.
 	// Implements image::ImageData.
-	virtual void encode(love::filesystem::File *f, ImageData::EncodedFormat format);
+	virtual love::filesystem::FileData *encode(EncodedFormat format, const char *filename);
 
 
 private:
 private:
 
 

+ 23 - 17
jni/love/src/modules/image/wrap_ImageData.cpp

@@ -217,31 +217,37 @@ int w_ImageData_paste(lua_State *L)
 
 
 int w_ImageData_encode(lua_State *L)
 int w_ImageData_encode(lua_State *L)
 {
 {
-	std::string ext;
-	const char *fmt;
-	ImageData::EncodedFormat format = ImageData::ENCODED_MAX_ENUM;
 	ImageData *t = luax_checkimagedata(L, 1);
 	ImageData *t = luax_checkimagedata(L, 1);
 
 
-	if (lua_isstring(L, 2))
-		luax_convobj(L, 2, "filesystem", "newFile");
-	love::filesystem::File *file = luax_checktype<love::filesystem::File>(L, 2, FILESYSTEM_FILE_ID);
+	ImageData::EncodedFormat format;
+	const char *fmt = luaL_checkstring(L, 2);
+	if (!ImageData::getConstant(fmt, format))
+		return luaL_error(L, "Invalid encoded image format '%s'.", fmt);
 
 
-	if (lua_isnoneornil(L, 3))
+	bool hasfilename = false;
+
+	std::string filename = "Image." + std::string(fmt);
+	if (!lua_isnoneornil(L, 3))
 	{
 	{
-		ext = file->getExtension();
-		fmt = ext.c_str();
-		if (!ImageData::getConstant(fmt, format))
-			return luaL_error(L, "Invalid image format '%s'.", fmt);
+		hasfilename = true;
+		filename = luax_checkstring(L, 3);
 	}
 	}
-	else
+
+	love::filesystem::FileData *filedata = nullptr;
+	luax_catchexcept(L, [&](){ filedata = t->encode(format, filename.c_str()); });
+
+	luax_pushtype(L, FILESYSTEM_FILE_DATA_ID, filedata);
+	filedata->release();
+
+	if (hasfilename)
 	{
 	{
-		fmt = luaL_checkstring(L, 3);
-		if (!ImageData::getConstant(fmt, format))
-			return luaL_error(L, "Invalid image format '%s'.", fmt);
+		luax_getfunction(L, "filesystem", "write");
+		lua_pushvalue(L, 3); // filename
+		lua_pushvalue(L, -3); // FileData
+		lua_call(L, 2, 0);
 	}
 	}
 
 
-	luax_catchexcept(L, [&](){ t->encode(file, format); });
-	return 0;
+	return 1;
 }
 }
 
 
 int w_ImageData__performAtomic(lua_State *L)
 int w_ImageData__performAtomic(lua_State *L)

+ 0 - 1
jni/love/src/modules/keyboard/Keyboard.cpp

@@ -188,7 +188,6 @@ StringMap<Keyboard::Key, Keyboard::KEY_MAX_ENUM>::Entry Keyboard::keyEntries[] =
 	{"execute", Keyboard::KEY_EXECUTE},
 	{"execute", Keyboard::KEY_EXECUTE},
 	{"help", Keyboard::KEY_HELP},
 	{"help", Keyboard::KEY_HELP},
 	{"menu", Keyboard::KEY_MENU},
 	{"menu", Keyboard::KEY_MENU},
-	{"search", Keyboard::KEY_SEARCH},
 	{"select", Keyboard::KEY_SELECT},
 	{"select", Keyboard::KEY_SELECT},
 	{"stop", Keyboard::KEY_STOP},
 	{"stop", Keyboard::KEY_STOP},
 	{"again", Keyboard::KEY_AGAIN},
 	{"again", Keyboard::KEY_AGAIN},

+ 0 - 1
jni/love/src/modules/keyboard/Keyboard.h

@@ -181,7 +181,6 @@ public:
 		KEY_EXECUTE,
 		KEY_EXECUTE,
 		KEY_HELP,
 		KEY_HELP,
 		KEY_MENU,
 		KEY_MENU,
-		KEY_SEARCH,
 		KEY_SELECT,
 		KEY_SELECT,
 		KEY_STOP,
 		KEY_STOP,
 		KEY_AGAIN,
 		KEY_AGAIN,

+ 1 - 2
jni/love/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);
@@ -299,7 +299,6 @@ const SDL_Keycode *Keyboard::createKeyMap()
 	k[Keyboard::KEY_EXECUTE] = SDLK_EXECUTE;
 	k[Keyboard::KEY_EXECUTE] = SDLK_EXECUTE;
 	k[Keyboard::KEY_HELP] = SDLK_HELP;
 	k[Keyboard::KEY_HELP] = SDLK_HELP;
 	k[Keyboard::KEY_MENU] = SDLK_MENU;
 	k[Keyboard::KEY_MENU] = SDLK_MENU;
-	k[Keyboard::KEY_SEARCH] = SDLK_AC_SEARCH;
 	k[Keyboard::KEY_SELECT] = SDLK_SELECT;
 	k[Keyboard::KEY_SELECT] = SDLK_SELECT;
 	k[Keyboard::KEY_STOP] = SDLK_STOP;
 	k[Keyboard::KEY_STOP] = SDLK_STOP;
 	k[Keyboard::KEY_AGAIN] = SDLK_AGAIN;
 	k[Keyboard::KEY_AGAIN] = SDLK_AGAIN;

+ 3 - 1
jni/love/src/modules/love/love.cpp

@@ -238,8 +238,10 @@ static int w_love_isVersionCompatible(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
-int luaopen_love(lua_State * L)
+int luaopen_love(lua_State *L)
 {
 {
+	love::luax_insistpinnedthread(L);
+
 	love::luax_insistglobal(L, "love");
 	love::luax_insistglobal(L, "love");
 
 
 	// Set version information.
 	// Set version information.

+ 5 - 5
jni/love/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

+ 1 - 9
jni/love/src/modules/physics/box2d/Body.cpp

@@ -535,15 +535,7 @@ int Body::setUserData(lua_State *L)
 		body->SetUserData((void *) udata);
 		body->SetUserData((void *) udata);
 	}
 	}
 
 
-	if (udata->ref != nullptr)
-	{
-		// We set the Reference's lua_State to this one before deleting it, so
-		// it unrefs using the current lua_State's stack. This is necessary
-		// if setUserData is called in a coroutine.
-		udata->ref->setL(L);
-		delete udata->ref;
-	}
-
+	delete udata->ref;
 	udata->ref = new Reference(L);
 	udata->ref = new Reference(L);
 
 
 	return 0;
 	return 0;

+ 1 - 1
jni/love/src/modules/physics/box2d/Body.h

@@ -48,7 +48,7 @@ class Fixture;
 struct bodyudata
 struct bodyudata
 {
 {
 	// Reference to arbitrary data.
 	// Reference to arbitrary data.
-	Reference *ref;
+	Reference *ref = nullptr;
 };
 };
 
 
 /**
 /**

+ 1 - 9
jni/love/src/modules/physics/box2d/Fixture.cpp

@@ -227,15 +227,7 @@ int Fixture::setUserData(lua_State *L)
 {
 {
 	love::luax_assert_argc(L, 1, 1);
 	love::luax_assert_argc(L, 1, 1);
 
 
-	if (data->ref != nullptr)
-	{
-		// We set the Reference's lua_State to this one before deleting it, so
-		// it unrefs using the current lua_State's stack. This is necessary
-		// if setUserData is called in a coroutine.
-		data->ref->setL(L);
-		delete data->ref;
-	}
-
+	delete data->ref;
 	data->ref = new Reference(L);
 	data->ref = new Reference(L);
 
 
 	return 0;
 	return 0;

+ 1 - 1
jni/love/src/modules/physics/box2d/Fixture.h

@@ -47,7 +47,7 @@ namespace box2d
 struct fixtureudata
 struct fixtureudata
 {
 {
 	// Reference to arbitrary data.
 	// Reference to arbitrary data.
-	Reference *ref;
+	Reference *ref = nullptr;
 };
 };
 
 
 /**
 /**

+ 1 - 9
jni/love/src/modules/physics/box2d/Joint.cpp

@@ -193,15 +193,7 @@ int Joint::setUserData(lua_State *L)
 {
 {
 	love::luax_assert_argc(L, 1, 1);
 	love::luax_assert_argc(L, 1, 1);
 
 
-	if (udata->ref != nullptr)
-	{
-		// We set the Reference's lua_State to this one before deleting it, so
-		// it unrefs using the current lua_State's stack. This is necessary
-		// if setUserData is called in a coroutine.
-		udata->ref->setL(L);
-		delete udata->ref;
-	}
-
+	delete udata->ref;
 	udata->ref = new Reference(L);
 	udata->ref = new Reference(L);
 
 
 	return 0;
 	return 0;

+ 1 - 1
jni/love/src/modules/physics/box2d/Joint.h

@@ -46,7 +46,7 @@ class World;
 struct jointudata
 struct jointudata
 {
 {
     // Reference to arbitrary data.
     // Reference to arbitrary data.
-    Reference *ref;
+    Reference *ref = nullptr;
 };
 };
 
 
 /**
 /**

+ 43 - 32
jni/love/src/modules/physics/box2d/World.cpp

@@ -36,6 +36,7 @@ namespace box2d
 
 
 World::ContactCallback::ContactCallback()
 World::ContactCallback::ContactCallback()
 	: ref(nullptr)
 	: ref(nullptr)
+	, L(nullptr)
 {
 {
 }
 }
 
 
@@ -48,10 +49,9 @@ World::ContactCallback::~ContactCallback()
 void World::ContactCallback::process(b2Contact *contact, const b2ContactImpulse *impulse)
 void World::ContactCallback::process(b2Contact *contact, const b2ContactImpulse *impulse)
 {
 {
 	// Process contacts.
 	// Process contacts.
-	if (ref != nullptr)
+	if (ref != nullptr && L != nullptr)
 	{
 	{
-		lua_State *L = ref->getL();
-		ref->push();
+		ref->push(L);
 
 
 		// Push first fixture.
 		// Push first fixture.
 		{
 		{
@@ -97,6 +97,7 @@ void World::ContactCallback::process(b2Contact *contact, const b2ContactImpulse
 
 
 World::ContactFilter::ContactFilter()
 World::ContactFilter::ContactFilter()
 	: ref(nullptr)
 	: ref(nullptr)
+	, L(nullptr)
 {
 {
 }
 }
 
 
@@ -124,10 +125,9 @@ bool World::ContactFilter::process(Fixture *a, Fixture *b)
 		(filterB[1] & filterA[0]) == 0)
 		(filterB[1] & filterA[0]) == 0)
 		return false; // A and B aren't set to collide
 		return false; // A and B aren't set to collide
 
 
-	if (ref != nullptr)
+	if (ref != nullptr && L != nullptr)
 	{
 	{
-		lua_State *L = ref->getL();
-		ref->push();
+		ref->push(L);
 		luax_pushtype(L, PHYSICS_FIXTURE_ID, a);
 		luax_pushtype(L, PHYSICS_FIXTURE_ID, a);
 		luax_pushtype(L, PHYSICS_FIXTURE_ID, b);
 		luax_pushtype(L, PHYSICS_FIXTURE_ID, b);
 		lua_call(L, 2, 1);
 		lua_call(L, 2, 1);
@@ -136,50 +136,51 @@ bool World::ContactFilter::process(Fixture *a, Fixture *b)
 	return true;
 	return true;
 }
 }
 
 
-World::QueryCallback::QueryCallback()
-	: ref(nullptr)
+World::QueryCallback::QueryCallback(lua_State *L, int idx)
+	: L(L)
+	, funcidx(idx)
 {
 {
+	luaL_checktype(L, funcidx, LUA_TFUNCTION);
 }
 }
 
 
 World::QueryCallback::~QueryCallback()
 World::QueryCallback::~QueryCallback()
 {
 {
-	if (ref != nullptr)
-		delete ref;
 }
 }
 
 
 bool World::QueryCallback::ReportFixture(b2Fixture *fixture)
 bool World::QueryCallback::ReportFixture(b2Fixture *fixture)
 {
 {
-	if (ref != nullptr)
+	if (L != nullptr)
 	{
 	{
-		lua_State *L = ref->getL();
-		ref->push();
+		lua_pushvalue(L, funcidx);
 		Fixture *f = (Fixture *)Memoizer::find(fixture);
 		Fixture *f = (Fixture *)Memoizer::find(fixture);
 		if (!f)
 		if (!f)
 			throw love::Exception("A fixture has escaped Memoizer!");
 			throw love::Exception("A fixture has escaped Memoizer!");
 		luax_pushtype(L, PHYSICS_FIXTURE_ID, f);
 		luax_pushtype(L, PHYSICS_FIXTURE_ID, f);
 		lua_call(L, 1, 1);
 		lua_call(L, 1, 1);
-		return luax_toboolean(L, -1);
+		bool cont = luax_toboolean(L, -1);
+		lua_pop(L, 1);
+		return cont;
 	}
 	}
+
 	return true;
 	return true;
 }
 }
 
 
-World::RayCastCallback::RayCastCallback()
-	: ref(nullptr)
+World::RayCastCallback::RayCastCallback(lua_State *L, int idx)
+	: L(L)
+	, funcidx(idx)
 {
 {
+	luaL_checktype(L, funcidx, LUA_TFUNCTION);
 }
 }
 
 
 World::RayCastCallback::~RayCastCallback()
 World::RayCastCallback::~RayCastCallback()
 {
 {
-	if (ref != nullptr)
-		delete ref;
 }
 }
 
 
 float32 World::RayCastCallback::ReportFixture(b2Fixture *fixture, const b2Vec2 &point, const b2Vec2 &normal, float32 fraction)
 float32 World::RayCastCallback::ReportFixture(b2Fixture *fixture, const b2Vec2 &point, const b2Vec2 &normal, float32 fraction)
 {
 {
-	if (ref != nullptr)
+	if (L != nullptr)
 	{
 	{
-		lua_State *L = ref->getL();
-		ref->push();
+		lua_pushvalue(L, funcidx);
 		Fixture *f = (Fixture *)Memoizer::find(fixture);
 		Fixture *f = (Fixture *)Memoizer::find(fixture);
 		if (!f)
 		if (!f)
 			throw love::Exception("A fixture has escaped Memoizer!");
 			throw love::Exception("A fixture has escaped Memoizer!");
@@ -193,8 +194,11 @@ float32 World::RayCastCallback::ReportFixture(b2Fixture *fixture, const b2Vec2 &
 		lua_call(L, 6, 1);
 		lua_call(L, 6, 1);
 		if (!lua_isnumber(L, -1))
 		if (!lua_isnumber(L, -1))
 			luaL_error(L, "Raycast callback didn't return a number!");
 			luaL_error(L, "Raycast callback didn't return a number!");
-		return (float32)lua_tonumber(L, -1);
+		float32 fraction = (float32) lua_tonumber(L, -1);
+		lua_pop(L, 1);
+		return fraction;
 	}
 	}
+
 	return 0;
 	return 0;
 }
 }
 
 
@@ -345,24 +349,28 @@ int World::setCallbacks(lua_State *L)
 	{
 	{
 		lua_pushvalue(L, 1);
 		lua_pushvalue(L, 1);
 		begin.ref = luax_refif(L, LUA_TFUNCTION);
 		begin.ref = luax_refif(L, LUA_TFUNCTION);
+		begin.L = L;
 	}
 	}
 
 
 	if (nargs >= 2)
 	if (nargs >= 2)
 	{
 	{
 		lua_pushvalue(L, 2);
 		lua_pushvalue(L, 2);
 		end.ref = luax_refif(L, LUA_TFUNCTION);
 		end.ref = luax_refif(L, LUA_TFUNCTION);
+		end.L = L;
 	}
 	}
 
 
 	if (nargs >= 3)
 	if (nargs >= 3)
 	{
 	{
 		lua_pushvalue(L, 3);
 		lua_pushvalue(L, 3);
 		presolve.ref = luax_refif(L, LUA_TFUNCTION);
 		presolve.ref = luax_refif(L, LUA_TFUNCTION);
+		presolve.L = L;
 	}
 	}
 
 
 	if (nargs >= 4)
 	if (nargs >= 4)
 	{
 	{
 		lua_pushvalue(L, 4);
 		lua_pushvalue(L, 4);
 		postsolve.ref = luax_refif(L, LUA_TFUNCTION);
 		postsolve.ref = luax_refif(L, LUA_TFUNCTION);
+		postsolve.L = L;
 	}
 	}
 
 
 	return 0;
 	return 0;
@@ -370,13 +378,18 @@ int World::setCallbacks(lua_State *L)
 
 
 int World::getCallbacks(lua_State *L)
 int World::getCallbacks(lua_State *L)
 {
 {
-	begin.ref ? begin.ref->push() : lua_pushnil(L);
-	end.ref ? end.ref->push() : lua_pushnil(L);
-	presolve.ref ? presolve.ref->push() : lua_pushnil(L);
-	postsolve.ref ? postsolve.ref->push() : lua_pushnil(L);
+	begin.ref ? begin.ref->push(L) : lua_pushnil(L);
+	end.ref ? end.ref->push(L) : lua_pushnil(L);
+	presolve.ref ? presolve.ref->push(L) : lua_pushnil(L);
+	postsolve.ref ? postsolve.ref->push(L) : lua_pushnil(L);
 	return 4;
 	return 4;
 }
 }
 
 
+void World::setCallbacksL(lua_State *L)
+{
+	begin.L = end.L = presolve.L = postsolve.L = filter.L = L;
+}
+
 int World::setContactFilter(lua_State *L)
 int World::setContactFilter(lua_State *L)
 {
 {
 	if (!lua_isnoneornil(L, 1))
 	if (!lua_isnoneornil(L, 1))
@@ -385,12 +398,13 @@ int World::setContactFilter(lua_State *L)
 	if (filter.ref)
 	if (filter.ref)
 		delete filter.ref;
 		delete filter.ref;
 	filter.ref = luax_refif(L, LUA_TFUNCTION);
 	filter.ref = luax_refif(L, LUA_TFUNCTION);
+	filter.L = L;
 	return 0;
 	return 0;
 }
 }
 
 
 int World::getContactFilter(lua_State *L)
 int World::getContactFilter(lua_State *L)
 {
 {
-	filter.ref ? filter.ref->push() : lua_pushnil(L);
+	filter.ref ? filter.ref->push(L) : lua_pushnil(L);
 	return 1;
 	return 1;
 }
 }
 
 
@@ -519,8 +533,7 @@ int World::queryBoundingBox(lua_State *L)
 	box.lowerBound = Physics::scaleDown(b2Vec2(lx, ly));
 	box.lowerBound = Physics::scaleDown(b2Vec2(lx, ly));
 	box.upperBound = Physics::scaleDown(b2Vec2(ux, uy));
 	box.upperBound = Physics::scaleDown(b2Vec2(ux, uy));
 	luaL_checktype(L, 5, LUA_TFUNCTION);
 	luaL_checktype(L, 5, LUA_TFUNCTION);
-	if (query.ref) delete query.ref;
-	query.ref = luax_refif(L, LUA_TFUNCTION);
+	QueryCallback query(L, 5);
 	world->QueryAABB(&query, box);
 	world->QueryAABB(&query, box);
 	return 0;
 	return 0;
 }
 }
@@ -534,9 +547,7 @@ int World::rayCast(lua_State *L)
 	b2Vec2 v1 = Physics::scaleDown(b2Vec2(x1, y1));
 	b2Vec2 v1 = Physics::scaleDown(b2Vec2(x1, y1));
 	b2Vec2 v2 = Physics::scaleDown(b2Vec2(x2, y2));
 	b2Vec2 v2 = Physics::scaleDown(b2Vec2(x2, y2));
 	luaL_checktype(L, 5, LUA_TFUNCTION);
 	luaL_checktype(L, 5, LUA_TFUNCTION);
-	if (raycast.ref)
-		delete raycast.ref;
-	raycast.ref = luax_refif(L, LUA_TFUNCTION);
+	RayCastCallback raycast(L, 5);
 	world->RayCast(&raycast, v1, v2);
 	world->RayCast(&raycast, v1, v2);
 	return 0;
 	return 0;
 }
 }

+ 17 - 6
jni/love/src/modules/physics/box2d/World.h

@@ -70,6 +70,7 @@ public:
 	{
 	{
 	public:
 	public:
 		Reference *ref;
 		Reference *ref;
+		lua_State *L;
 		ContactCallback();
 		ContactCallback();
 		~ContactCallback();
 		~ContactCallback();
 		void process(b2Contact *contact, const b2ContactImpulse *impulse = NULL);
 		void process(b2Contact *contact, const b2ContactImpulse *impulse = NULL);
@@ -79,6 +80,7 @@ public:
 	{
 	{
 	public:
 	public:
 		Reference *ref;
 		Reference *ref;
+		lua_State *L;
 		ContactFilter();
 		ContactFilter();
 		~ContactFilter();
 		~ContactFilter();
 		bool process(Fixture *a, Fixture *b);
 		bool process(Fixture *a, Fixture *b);
@@ -87,19 +89,23 @@ public:
 	class QueryCallback : public b2QueryCallback
 	class QueryCallback : public b2QueryCallback
 	{
 	{
 	public:
 	public:
-		Reference *ref;
-		QueryCallback();
+		QueryCallback(lua_State *L, int idx);
 		~QueryCallback();
 		~QueryCallback();
 		virtual bool ReportFixture(b2Fixture *fixture);
 		virtual bool ReportFixture(b2Fixture *fixture);
+	private:
+		lua_State *L;
+		int funcidx;
 	};
 	};
 
 
 	class RayCastCallback : public b2RayCastCallback
 	class RayCastCallback : public b2RayCastCallback
 	{
 	{
 	public:
 	public:
-		Reference *ref;
-		RayCastCallback();
+		RayCastCallback(lua_State *L, int idx);
 		~RayCastCallback();
 		~RayCastCallback();
 		virtual float32 ReportFixture(b2Fixture *fixture, const b2Vec2 &point, const b2Vec2 &normal, float32 fraction);
 		virtual float32 ReportFixture(b2Fixture *fixture, const b2Vec2 &point, const b2Vec2 &normal, float32 fraction);
+	private:
+		lua_State *L;
+		int funcidx;
 	};
 	};
 
 
 	/**
 	/**
@@ -157,6 +163,13 @@ public:
 	 **/
 	 **/
 	int getCallbacks(lua_State *L);
 	int getCallbacks(lua_State *L);
 
 
+	/**
+	 * Updates the Lua thread/coroutine used when callbacks are executed in
+	 * the update method. This should be called in the same Lua function which
+	 * calls update().
+	 **/
+	void setCallbacksL(lua_State *L);
+
 	/**
 	/**
 	 * Sets the ContactFilter callback.
 	 * Sets the ContactFilter callback.
 	 **/
 	 **/
@@ -281,8 +294,6 @@ private:
 	// Contact callbacks.
 	// Contact callbacks.
 	ContactCallback begin, end, presolve, postsolve;
 	ContactCallback begin, end, presolve, postsolve;
 	ContactFilter filter;
 	ContactFilter filter;
-	QueryCallback query;
-	RayCastCallback	raycast;
 };
 };
 
 
 } // box2d
 } // box2d

+ 2 - 0
jni/love/src/modules/physics/box2d/wrap_World.cpp

@@ -39,6 +39,8 @@ int w_World_update(lua_State *L)
 {
 {
 	World *t = luax_checkworld(L, 1);
 	World *t = luax_checkworld(L, 1);
 	float dt = (float)luaL_checknumber(L, 2);
 	float dt = (float)luaL_checknumber(L, 2);
+	// Make sure the world callbacks are using the calling Lua thread.
+	t->setCallbacksL(L);
 	luax_catchexcept(L, [&](){ t->update(dt); });
 	luax_catchexcept(L, [&](){ t->update(dt); });
 	return 0;
 	return 0;
 }
 }

+ 5 - 1
jni/love/src/modules/sound/lullaby/ModPlugDecoder.h

@@ -29,8 +29,12 @@
 #include "common/Data.h"
 #include "common/Data.h"
 #include "Decoder.h"
 #include "Decoder.h"
 
 
-// SDL_sound
+// libmodplug
+#ifdef LOVE_ANDROID
 #include <modplug.h>
 #include <modplug.h>
+#else
+#include <libmodplug/modplug.h>
+#endif
 
 
 namespace love
 namespace love
 {
 {

+ 4 - 2
jni/love/src/modules/sound/lullaby/VorbisDecoder.cpp

@@ -106,10 +106,12 @@ static int vorbisSeek(void *datasource	/* ptr to the data that the vorbis files
 		vorbisData->dataRead += (int)actualOffset;
 		vorbisData->dataRead += (int)actualOffset;
 		break;
 		break;
 	case SEEK_END: // Seek from the end of the file
 	case SEEK_END: // Seek from the end of the file
-		vorbisData->dataRead = vorbisData->dataSize+1;
+		if (offset < 0)
+			vorbisData->dataRead = vorbisData->dataSize + offset;
+		else
+			vorbisData->dataRead = vorbisData->dataSize;
 		break;
 		break;
 	default:
 	default:
-		throw love::Exception("Unknown seek command in vorbisSeek");
 		break;
 		break;
 	};
 	};
 
 

+ 6 - 3
jni/love/src/modules/system/System.cpp

@@ -104,7 +104,7 @@ bool System::openURL(const std::string &url) const
 
 
 #elif defined(LOVE_ANDROID)
 #elif defined(LOVE_ANDROID)
 
 
-	return love::android::openURL (url);	
+	return love::android::openURL(url);
 
 
 #elif defined(LOVE_LINUX)
 #elif defined(LOVE_LINUX)
 
 
@@ -142,9 +142,12 @@ bool System::openURL(const std::string &url) const
 #endif
 #endif
 }
 }
 
 
-void System::vibrate(double seconds) const {
+void System::vibrate(double seconds) const
+{
 #ifdef LOVE_ANDROID
 #ifdef LOVE_ANDROID
-	love::android::vibrate (seconds);	
+	love::android::vibrate(seconds);
+#else
+	LOVE_UNUSED(seconds);
 #endif
 #endif
 }
 }
 
 

+ 0 - 1
jni/love/src/modules/system/System.h

@@ -103,7 +103,6 @@ public:
 	 * Vibrates for the specified amount of seconds.
 	 * Vibrates for the specified amount of seconds.
 	 *
 	 *
 	 * @param number of seconds to vibrate.
 	 * @param number of seconds to vibrate.
-	 *
 	 */
 	 */
 	virtual void vibrate(double seconds) const;
 	virtual void vibrate(double seconds) const;
 
 

+ 1 - 1
jni/love/src/modules/system/wrap_System.cpp

@@ -88,7 +88,7 @@ int w_openURL(lua_State *L)
 
 
 int w_vibrate(lua_State *L)
 int w_vibrate(lua_State *L)
 {
 {
-	double seconds = static_cast<double>(luaL_checknumber(L, 1));
+	double seconds = luaL_checknumber(L, 1);
 	instance()->vibrate(seconds);
 	instance()->vibrate(seconds);
 	return 0;
 	return 0;
 }
 }

+ 3 - 3
jni/love/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();
 }
 }
 
 

+ 15 - 0
jni/love/src/modules/thread/threads.cpp

@@ -104,5 +104,20 @@ const char *Threadable::getThreadName() const
 	return threadName.empty() ? nullptr : threadName.c_str();
 	return threadName.empty() ? nullptr : threadName.c_str();
 }
 }
 
 
+MutexRef::MutexRef()
+	: mutex(newMutex())
+{
+}
+
+MutexRef::~MutexRef()
+{
+	delete mutex;
+}
+
+MutexRef::operator Mutex*() const
+{
+	return mutex;
+}
+
 } // thread
 } // thread
 } // love
 } // love

+ 12 - 0
jni/love/src/modules/thread/threads.h

@@ -96,6 +96,18 @@ protected:
 
 
 };
 };
 
 
+class MutexRef
+{
+public:
+	MutexRef();
+	~MutexRef();
+
+	operator Mutex*() const;
+
+private:
+	Mutex *mutex;
+};
+
 Mutex *newMutex();
 Mutex *newMutex();
 Conditional *newConditional();
 Conditional *newConditional();
 Thread *newThread(Threadable *t);
 Thread *newThread(Threadable *t);

+ 0 - 4
jni/love/src/modules/window/Window.cpp

@@ -26,12 +26,8 @@ namespace love
 namespace window
 namespace window
 {
 {
 
 
-Window *Window::singleton = nullptr;
-
 Window::~Window()
 Window::~Window()
 {
 {
-	if (singleton == this)
-		singleton = nullptr;
 }
 }
 
 
 void Window::swapBuffers()
 void Window::swapBuffers()

+ 3 - 8
jni/love/src/modules/window/Window.h

@@ -112,6 +112,8 @@ public:
 	virtual bool setWindow(int width = 800, int height = 600, WindowSettings *settings = nullptr) = 0;
 	virtual bool setWindow(int width = 800, int height = 600, WindowSettings *settings = nullptr) = 0;
 	virtual void getWindow(int &width, int &height, WindowSettings &settings) = 0;
 	virtual void getWindow(int &width, int &height, WindowSettings &settings) = 0;
 
 
+	virtual void close() = 0;
+
 	virtual bool setFullscreen(bool fullscreen, FullscreenType fstype) = 0;
 	virtual bool setFullscreen(bool fullscreen, FullscreenType fstype) = 0;
 	virtual bool setFullscreen(bool fullscreen) = 0;
 	virtual bool setFullscreen(bool fullscreen) = 0;
 
 
@@ -128,7 +130,7 @@ public:
 	virtual void setPosition(int x, int y, int displayindex) = 0;
 	virtual void setPosition(int x, int y, int displayindex) = 0;
 	virtual void getPosition(int &x, int &y, int &displayindex) = 0;
 	virtual void getPosition(int &x, int &y, int &displayindex) = 0;
 
 
-	virtual bool isCreated() const = 0;
+	virtual bool isOpen() const = 0;
 
 
 	virtual void setWindowTitle(const std::string &title) = 0;
 	virtual void setWindowTitle(const std::string &title) = 0;
 	virtual const std::string &getWindowTitle() const = 0;
 	virtual const std::string &getWindowTitle() const = 0;
@@ -173,9 +175,6 @@ public:
 
 
 	virtual void requestAttention(bool continuous) = 0;
 	virtual void requestAttention(bool continuous) = 0;
 
 
-	//virtual static Window *createSingleton() = 0;
-	// No virtual statics, of course, but you are supposed to implement this static.
-
 	static bool getConstant(const char *in, Setting &out);
 	static bool getConstant(const char *in, Setting &out);
 	static bool getConstant(Setting in, const char *&out);
 	static bool getConstant(Setting in, const char *&out);
 
 
@@ -185,10 +184,6 @@ public:
 	static bool getConstant(const char *in, MessageBoxType &out);
 	static bool getConstant(const char *in, MessageBoxType &out);
 	static bool getConstant(MessageBoxType in, const char *&out);
 	static bool getConstant(MessageBoxType in, const char *&out);
 
 
-protected:
-
-	static Window *singleton;
-
 private:
 private:
 
 
 	static StringMap<Setting, SETTING_MAX_ENUM>::Entry settingEntries[];
 	static StringMap<Setting, SETTING_MAX_ENUM>::Entry settingEntries[];

+ 46 - 64
jni/love/src/modules/window/sdl/Window.cpp

@@ -56,7 +56,7 @@ namespace sdl
 {
 {
 
 
 Window::Window()
 Window::Window()
-	: created(false)
+	: open(false)
 	, mouseGrabbed(false)
 	, mouseGrabbed(false)
 	, window(nullptr)
 	, window(nullptr)
 	, context(nullptr)
 	, context(nullptr)
@@ -69,17 +69,7 @@ Window::Window()
 
 
 Window::~Window()
 Window::~Window()
 {
 {
-	if (context)
-	{
-		graphics::Graphics *gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-		if (gfx != nullptr)
-			gfx->unSetMode();
-
-		SDL_GL_DeleteContext(context);
-	}
-
-	if (window)
-		SDL_DestroyWindow(window);
+	close();
 
 
 	SDL_QuitSubSystem(SDL_INIT_VIDEO);
 	SDL_QuitSubSystem(SDL_INIT_VIDEO);
 }
 }
@@ -104,6 +94,13 @@ void Window::setGLFramebufferAttributes(int msaa, bool sRGB)
 #if !defined(LOVE_LINUX)
 #if !defined(LOVE_LINUX)
 	SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, sRGB ? 1 : 0);
 	SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, sRGB ? 1 : 0);
 #endif
 #endif
+
+#if defined(LOVE_WINDOWS)
+	// Avoid the Microsoft OpenGL 1.1 software renderer on Windows. Apparently
+	// older Intel drivers like to use it as a fallback when requesting some
+	// unsupported framebuffer attribute values, rather than properly failing.
+	SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
+#endif
 }
 }
 
 
 void Window::setGLContextAttributes(const ContextAttribs &attribs)
 void Window::setGLContextAttributes(const ContextAttribs &attribs)
@@ -349,22 +346,12 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla
 			}
 			}
 		}
 		}
 
 
-		if (context)
-		{
-			SDL_GL_DeleteContext(context);
-			context = nullptr;
-		}
-
-		if (window)
-		{
-			SDL_DestroyWindow(window);
-			SDL_FlushEvent(SDL_WINDOWEVENT);
-			window = nullptr;
-		}
+		close();
 
 
 		return false;
 		return false;
 	}
 	}
 
 
+	open = true;
 	return true;
 	return true;
 }
 }
 
 
@@ -448,33 +435,11 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 			x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(f.display);
 			x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(f.display);
 	}
 	}
 
 
-	graphics::Graphics *gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-	if (gfx != nullptr)
-		gfx->unSetMode();
-
-	if (context)
-	{
-		SDL_GL_DeleteContext(context);
-		context = nullptr;
-	}
-
-	if (window)
-	{
-		SDL_DestroyWindow(window);
-		window = nullptr;
-
-		// The old window may have generated pending events which are no longer
-		// relevant. Destroy them all!
-		SDL_FlushEvent(SDL_WINDOWEVENT);
-	}
-
-	created = false;
+	close();
 
 
 	if (!createWindowAndContext(x, y, width, height, sdlflags, f.msaa, f.sRGB))
 	if (!createWindowAndContext(x, y, width, height, sdlflags, f.msaa, f.sRGB))
 		return false;
 		return false;
 
 
-	created = true;
-
 	// Make sure the window keeps any previously set icon.
 	// Make sure the window keeps any previously set icon.
 	setIcon(curMode.icon.get());
 	setIcon(curMode.icon.get());
 
 
@@ -493,6 +458,7 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 
 
 	updateSettings(f);
 	updateSettings(f);
 
 
+	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
 	if (gfx != nullptr)
 	if (gfx != nullptr)
 		gfx->setMode(curMode.pixelwidth, curMode.pixelheight, curMode.settings.sRGB);
 		gfx->setMode(curMode.pixelwidth, curMode.pixelheight, curMode.settings.sRGB);
 
 
@@ -513,7 +479,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);
 
 
@@ -601,6 +567,31 @@ void Window::getWindow(int &width, int &height, WindowSettings &settings)
 	settings = curMode.settings;
 	settings = curMode.settings;
 }
 }
 
 
+void Window::close()
+{
+	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
+	if (gfx != nullptr)
+		gfx->unSetMode();
+
+	if (context)
+	{
+		SDL_GL_DeleteContext(context);
+		context = nullptr;
+	}
+
+	if (window)
+	{
+		SDL_DestroyWindow(window);
+		window = nullptr;
+
+		// The old window may have generated pending events which are no longer
+		// relevant. Destroy them all!
+		SDL_FlushEvent(SDL_WINDOWEVENT);
+	}
+
+	open = false;
+}
+
 bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype)
 bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype)
 {
 {
 	if (!window)
 	if (!window)
@@ -639,7 +630,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);
 
 
@@ -739,21 +730,22 @@ void Window::getPosition(int &x, int &y, int &displayindex)
 
 
 	SDL_GetWindowPosition(window, &x, &y);
 	SDL_GetWindowPosition(window, &x, &y);
 
 
-	// SDL always reports 0, 0 for fullscreen windows.
-	if (!(SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN))
+	// In SDL <= 2.0.3, fullscreen windows are always reported as 0,0. In every
+	// other case we need to convert the position from global coordinates to the
+	// monitor's coordinate space.
+	if (x != 0 || y != 0)
 	{
 	{
 		SDL_Rect displaybounds = {};
 		SDL_Rect displaybounds = {};
 		SDL_GetDisplayBounds(displayindex, &displaybounds);
 		SDL_GetDisplayBounds(displayindex, &displaybounds);
 
 
-		// The position needs to be in the monitor's coordinate space.
 		x -= displaybounds.x;
 		x -= displaybounds.x;
 		y -= displaybounds.y;
 		y -= displaybounds.y;
 	}
 	}
 }
 }
 
 
-bool Window::isCreated() const
+bool Window::isOpen() const
 {
 {
-	return created;
+	return open;
 }
 }
 
 
 void Window::setWindowTitle(const std::string &title)
 void Window::setWindowTitle(const std::string &title)
@@ -1034,16 +1026,6 @@ void Window::requestAttention(bool continuous)
 	// TODO: Linux?
 	// TODO: Linux?
 }
 }
 
 
-love::window::Window *Window::createSingleton()
-{
-	if (!singleton)
-		singleton = new Window();
-	else
-		singleton->retain();
-
-	return singleton;
-}
-
 const char *Window::getName() const
 const char *Window::getName() const
 {
 {
 	return "love.window.sdl";
 	return "love.window.sdl";

+ 4 - 4
jni/love/src/modules/window/sdl/Window.h

@@ -44,6 +44,8 @@ public:
 	bool setWindow(int width = 800, int height = 600, WindowSettings *settings = nullptr);
 	bool setWindow(int width = 800, int height = 600, WindowSettings *settings = nullptr);
 	void getWindow(int &width, int &height, WindowSettings &settings);
 	void getWindow(int &width, int &height, WindowSettings &settings);
 
 
+	void close();
+
 	bool setFullscreen(bool fullscreen, FullscreenType fstype);
 	bool setFullscreen(bool fullscreen, FullscreenType fstype);
 	bool setFullscreen(bool fullscreen);
 	bool setFullscreen(bool fullscreen);
 
 
@@ -60,7 +62,7 @@ public:
 	void setPosition(int x, int y, int displayindex);
 	void setPosition(int x, int y, int displayindex);
 	void getPosition(int &x, int &y, int &displayindex);
 	void getPosition(int &x, int &y, int &displayindex);
 
 
-	bool isCreated() const;
+	bool isOpen() const;
 
 
 	void setWindowTitle(const std::string &title);
 	void setWindowTitle(const std::string &title);
 	const std::string &getWindowTitle() const;
 	const std::string &getWindowTitle() const;
@@ -102,8 +104,6 @@ public:
 
 
 	void requestAttention(bool continuous);
 	void requestAttention(bool continuous);
 
 
-	static love::window::Window *createSingleton();
-
 	const char *getName() const;
 	const char *getName() const;
 
 
 private:
 private:
@@ -139,7 +139,7 @@ private:
 
 
 	} curMode;
 	} curMode;
 
 
-	bool created;
+	bool open;
 
 
 	bool mouseGrabbed;
 	bool mouseGrabbed;
 
 

+ 19 - 8
jni/love/src/modules/window/wrap_Window.cpp

@@ -194,7 +194,7 @@ int w_getFullscreenModes(lua_State *L)
 {
 {
 	int displayindex = 0;
 	int displayindex = 0;
 	if (!lua_isnoneornil(L, 1))
 	if (!lua_isnoneornil(L, 1))
-		displayindex = (int) luaL_checknumber(L, 1);
+		displayindex = (int) luaL_checknumber(L, 1) - 1;
 	else
 	else
 	{
 	{
 		int x, y;
 		int x, y;
@@ -260,18 +260,24 @@ int w_getFullscreen(lua_State *L)
 	return 2;
 	return 2;
 }
 }
 
 
-int w_isCreated(lua_State *L)
+int w_isOpen(lua_State *L)
 {
 {
-	luax_pushboolean(L, instance()->isCreated());
+	luax_pushboolean(L, instance()->isOpen());
 	return 1;
 	return 1;
 }
 }
 
 
+int w_close(lua_State * /*L*/)
+{
+	instance()->close();
+	return 0;
+}
+
 int w_getDesktopDimensions(lua_State *L)
 int w_getDesktopDimensions(lua_State *L)
 {
 {
 	int width = 0, height = 0;
 	int width = 0, height = 0;
 	int displayindex = 0;
 	int displayindex = 0;
 	if (!lua_isnoneornil(L, 1))
 	if (!lua_isnoneornil(L, 1))
-		displayindex = (int) luaL_checknumber(L, 1);
+		displayindex = (int) luaL_checknumber(L, 1) - 1;
 	else
 	else
 	{
 	{
 		int x, y;
 		int x, y;
@@ -290,7 +296,7 @@ int w_setPosition(lua_State *L)
 
 
 	int displayindex = 0;
 	int displayindex = 0;
 	if (!lua_isnoneornil(L, 3))
 	if (!lua_isnoneornil(L, 3))
-		displayindex = (int) luaL_checknumber(L, 3);
+		displayindex = (int) luaL_checknumber(L, 3) - 1;
 	else
 	else
 	{
 	{
 		int x_unused, y_unused;
 		int x_unused, y_unused;
@@ -499,7 +505,9 @@ static const luaL_Reg functions[] =
 	{ "getFullscreenModes", w_getFullscreenModes },
 	{ "getFullscreenModes", w_getFullscreenModes },
 	{ "setFullscreen", w_setFullscreen },
 	{ "setFullscreen", w_setFullscreen },
 	{ "getFullscreen", w_getFullscreen },
 	{ "getFullscreen", w_getFullscreen },
-	{ "isCreated", w_isCreated },
+	{ "isOpen", w_isOpen },
+	{ "isCreated", w_isOpen }, // For compatibility with old error handlers...
+	{ "close", w_close },
 	{ "getDesktopDimensions", w_getDesktopDimensions },
 	{ "getDesktopDimensions", w_getDesktopDimensions },
 	{ "setPosition", w_setPosition },
 	{ "setPosition", w_setPosition },
 	{ "getPosition", w_getPosition },
 	{ "getPosition", w_getPosition },
@@ -522,8 +530,11 @@ static const luaL_Reg functions[] =
 
 
 extern "C" int luaopen_love_window(lua_State *L)
 extern "C" int luaopen_love_window(lua_State *L)
 {
 {
-	Window *instance = nullptr;
-	luax_catchexcept(L, [&](){ instance = sdl::Window::createSingleton(); });
+	Window *instance = instance();
+	if (instance == nullptr)
+		luax_catchexcept(L, [&](){ instance = new love::window::sdl::Window(); });
+	else
+		instance->retain();
 
 
 	WrappedModule w;
 	WrappedModule w;
 	w.module = instance;
 	w.module = instance;

+ 2 - 1
jni/love/src/modules/window/wrap_Window.h

@@ -36,7 +36,8 @@ int w_getMode(lua_State *L);
 int w_getFullscreenModes(lua_State *L);
 int w_getFullscreenModes(lua_State *L);
 int w_setFullscreen(lua_State *L);
 int w_setFullscreen(lua_State *L);
 int w_getFullscreen(lua_State *L);
 int w_getFullscreen(lua_State *L);
-int w_isCreated(lua_State *L);
+int w_isOpen(lua_State *L);
+int w_close(lua_State *L);
 int w_getDesktopDimensions(lua_State *L);
 int w_getDesktopDimensions(lua_State *L);
 int w_setPosition(lua_State *L);
 int w_setPosition(lua_State *L);
 int w_getPosition(lua_State *L);
 int w_getPosition(lua_State *L);

+ 1 - 1
jni/love/src/scripts/boot.lua

@@ -567,7 +567,7 @@ function love.errhand(msg)
 		return
 		return
 	end
 	end
 
 
-	if not love.graphics.isCreated() or not love.window.isCreated() then
+	if not love.graphics.isCreated() or not love.window.isOpen() then
 		local success, status = pcall(love.window.setMode, 800, 600)
 		local success, status = pcall(love.window.setMode, 800, 600)
 		if not success or not status then
 		if not success or not status then
 			return
 			return

+ 1 - 1
jni/love/src/scripts/boot.lua.h

@@ -1034,7 +1034,7 @@ const unsigned char boot_lua[] =
 	0x09, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 
 	0x09, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 
 	0x69, 0x63, 0x73, 0x2e, 0x69, 0x73, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x28, 0x29, 0x20, 0x6f, 0x72, 
 	0x69, 0x63, 0x73, 0x2e, 0x69, 0x73, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x28, 0x29, 0x20, 0x6f, 0x72, 
 	0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x69, 
 	0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x69, 
-	0x73, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x28, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
+	0x73, 0x4f, 0x70, 0x65, 0x6e, 0x28, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x2c, 0x20, 0x73, 
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x2c, 0x20, 0x73, 
 	0x74, 0x61, 0x74, 0x75, 0x73, 0x20, 0x3d, 0x20, 0x70, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x6c, 0x6f, 0x76, 0x65, 
 	0x74, 0x61, 0x74, 0x75, 0x73, 0x20, 0x3d, 0x20, 0x70, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x6c, 0x6f, 0x76, 0x65, 
 	0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x73, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x38, 
 	0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x73, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x38,