Răsfoiți Sursa

Updated LÖVE source to changeset da332813bc05

Martin Felis 10 ani în urmă
părinte
comite
08d51fbf28
38 a modificat fișierele cu 836 adăugiri și 448 ștergeri
  1. 5 1
      jni/love/src/common/runtime.cpp
  2. 17 17
      jni/love/src/modules/event/wrap_Event.cpp
  3. 12 6
      jni/love/src/modules/filesystem/Filesystem.h
  4. 8 0
      jni/love/src/modules/filesystem/physfs/File.cpp
  5. 7 0
      jni/love/src/modules/filesystem/physfs/File.h
  6. 46 7
      jni/love/src/modules/filesystem/physfs/Filesystem.cpp
  7. 2 1
      jni/love/src/modules/filesystem/physfs/Filesystem.h
  8. 16 8
      jni/love/src/modules/filesystem/wrap_Filesystem.cpp
  9. 67 4
      jni/love/src/modules/graphics/Graphics.cpp
  10. 48 0
      jni/love/src/modules/graphics/Graphics.h
  11. 1 1
      jni/love/src/modules/graphics/opengl/Canvas.cpp
  12. 16 4
      jni/love/src/modules/graphics/opengl/Font.cpp
  13. 1 1
      jni/love/src/modules/graphics/opengl/Font.h
  14. 28 10
      jni/love/src/modules/graphics/opengl/GLBuffer.cpp
  15. 17 0
      jni/love/src/modules/graphics/opengl/GLBuffer.h
  16. 109 39
      jni/love/src/modules/graphics/opengl/Graphics.cpp
  17. 10 8
      jni/love/src/modules/graphics/opengl/Graphics.h
  18. 8 5
      jni/love/src/modules/graphics/opengl/ParticleSystem.cpp
  19. 1 1
      jni/love/src/modules/graphics/opengl/Text.cpp
  20. 21 21
      jni/love/src/modules/graphics/opengl/Video.cpp
  21. 56 19
      jni/love/src/modules/graphics/opengl/wrap_Graphics.cpp
  22. 1 1
      jni/love/src/modules/graphics/opengl/wrap_Graphics.lua
  23. 18 4
      jni/love/src/modules/graphics/opengl/wrap_Mesh.cpp
  24. 1 0
      jni/love/src/modules/graphics/opengl/wrap_Mesh.h
  25. 1 12
      jni/love/src/modules/graphics/opengl/wrap_Video.lua
  26. 2 49
      jni/love/src/modules/math/MathModule.h
  27. 7 44
      jni/love/src/modules/math/wrap_Math.cpp
  28. 33 6
      jni/love/src/modules/math/wrap_Math.lua
  29. 46 30
      jni/love/src/modules/math/wrap_RandomGenerator.cpp
  30. 0 1
      jni/love/src/modules/math/wrap_RandomGenerator.h
  31. 80 0
      jni/love/src/modules/math/wrap_RandomGenerator.lua
  32. 1 1
      jni/love/src/modules/sound/lullaby/ModPlugDecoder.h
  33. 113 122
      jni/love/src/modules/window/sdl/Window.cpp
  34. 6 10
      jni/love/src/modules/window/sdl/Window.h
  35. 4 4
      jni/love/src/scripts/boot.lua
  36. 11 9
      jni/love/src/scripts/boot.lua.h
  37. 7 1
      jni/love/src/scripts/nogame.lua
  38. 9 1
      jni/love/src/scripts/nogame.lua.h

+ 5 - 1
jni/love/src/common/runtime.cpp

@@ -465,7 +465,11 @@ bool luax_istype(lua_State *L, int idx, love::Type type)
 		return false;
 
 	Proxy *p = (Proxy *) lua_touserdata(L, idx);
-	return typeFlags[p->type][type];
+
+	if (p->type > INVALID_ID && p->type < TYPE_MAX_ENUM)
+		return typeFlags[p->type][type];
+	else
+		return false;
 }
 
 int luax_getfunction(lua_State *L, const char *mod, const char *fn)

+ 17 - 17
jni/love/src/modules/event/wrap_Event.cpp

@@ -32,11 +32,11 @@ namespace event
 
 #define instance() (Module::getInstance<Event>(Module::M_EVENT))
 
-static int poll_i(lua_State *L)
+static int w_poll_i(lua_State *L)
 {
-	Message *m;
+	Message *m = nullptr;
 
-	while (instance()->poll(m))
+	if (instance()->poll(m))
 	{
 		int args = m->toLua(L);
 		m->release();
@@ -47,23 +47,22 @@ static int poll_i(lua_State *L)
 	return 0;
 }
 
-int w_pump(lua_State *)
+int w_poll(lua_State *L)
 {
-	instance()->pump();
-	return 0;
+	lua_pushcclosure(L, &w_poll_i, 0);
+	return 1;
 }
 
-int w_poll(lua_State *L)
+int w_pump(lua_State *)
 {
-	lua_pushcclosure(L, &poll_i, 0);
-	return 1;
+	instance()->pump();
+	return 0;
 }
 
 int w_wait(lua_State *L)
 {
-	Message *m;
-
-	if ((m = instance()->wait()))
+	Message *m = instance()->wait();
+	if (m)
 	{
 		int args = m->toLua(L);
 		m->release();
@@ -75,12 +74,11 @@ int w_wait(lua_State *L)
 
 int w_push(lua_State *L)
 {
-	Message *m;
+	Message *m = Message::fromLua(L, 1);
 
-	bool success = (m = Message::fromLua(L, 1)) != NULL;
-	luax_pushboolean(L, success);
+	luax_pushboolean(L, m != nullptr);
 
-	if (!success)
+	if (m == nullptr)
 		return 1;
 
 	instance()->push(m);
@@ -143,7 +141,9 @@ extern "C" int luaopen_love_event(lua_State *L)
 	w.functions = functions;
 	w.types = nullptr;
 
-	return luax_register_module(L, w);
+	int ret = luax_register_module(L, w);
+
+	return ret;
 }
 
 } // event

+ 12 - 6
jni/love/src/modules/filesystem/Filesystem.h

@@ -156,6 +156,12 @@ public:
 	 **/
 	virtual std::string getRealDirectory(const char *filename) const = 0;
 
+	/**
+	 * Checks if a path exists.
+	 * @param path The path to check.
+	 **/
+	virtual bool exists(const char *path) const = 0;
+
 	/**
 	 * Checks if a path is a directory.
 	 * @param dir The directory name to check.
@@ -168,6 +174,12 @@ public:
 	 **/
 	virtual bool isFile(const char *file) const = 0;
 
+	/**
+	 * Gets whether a filepath is actually a symlink.
+	 * Always returns false if symlinks are not enabled.
+	 **/
+	virtual bool isSymlink(const char *filename) const = 0;
+
 	/**
 	 * Creates a directory. Write dir must be set.
 	 * @param dir The directory to create.
@@ -232,12 +244,6 @@ public:
 	 **/
 	virtual bool areSymlinksEnabled() const = 0;
 
-	/**
-	 * Gets whether a filepath is actually a symlink.
-	 * Always returns false if symlinks are not enabled.
-	 **/
-	virtual bool isSymlink(const char *filename) const = 0;
-
 	// Require path accessors
 	// Not const because it's R/W
 	virtual std::vector<std::string> &getRequirePath() = 0;

+ 8 - 0
jni/love/src/modules/filesystem/physfs/File.cpp

@@ -154,7 +154,11 @@ int64 File::read(void *dst, int64 size)
 	if (size < 0)
 		throw love::Exception("Invalid read size.");
 
+#ifdef LOVE_USE_PHYSFS_2_1
+	int64 read = PHYSFS_readBytes(file, dst, (PHYSFS_uint64) size);
+#else
 	int64 read = (int64)PHYSFS_read(file, dst, 1, (PHYSFS_uint32) size);
+#endif
 
 	return read;
 }
@@ -171,7 +175,11 @@ bool File::write(const void *data, int64 size)
 		throw love::Exception("Invalid write size.");
 
 	// Try to write.
+#ifdef LOVE_USE_PHYSFS_2_1
+	int64 written = PHYSFS_writeBytes(file, data, (PHYSFS_uint64) size);
+#else
 	int64 written = (int64) PHYSFS_write(file, data, 1, (PHYSFS_uint32) size);
+#endif
 
 	// Check that correct amount of data was written.
 	if (written != size)

+ 7 - 0
jni/love/src/modules/filesystem/physfs/File.h

@@ -22,6 +22,7 @@
 #define LOVE_FILESYSTEM_PHYSFS_FILE_H
 
 // LOVE
+#include "common/config.h"
 #include "filesystem/File.h"
 
 // PhysFS
@@ -34,6 +35,12 @@
 // STD
 #include <string>
 
+// These platforms always use PhysFS 2.1.
+#if (defined(LOVE_IOS) || defined(LOVE_ANDROID)) \
+&& (PHYSFS_VER_MAJOR == 2 && PHYSFS_VER_MINOR >= 1)
+#define LOVE_USE_PHYSFS_2_1
+#endif
+
 namespace love
 {
 namespace filesystem

+ 46 - 7
jni/love/src/modules/filesystem/physfs/Filesystem.cpp

@@ -188,7 +188,13 @@ bool Filesystem::setIdentity(const char *ident, bool appendToPath)
 	// We don't want old read-only save paths to accumulate when we set a new
 	// identity.
 	if (!old_save_path.empty())
+	{
+#ifdef LOVE_USE_PHYSFS_2_1
+		PHYSFS_unmount(old_save_path.c_str());
+#else
 		PHYSFS_removeFromSearchPath(old_save_path.c_str());
+#endif
+	}
 
 	// Try to add the save directory to the search path.
 	// (No error on fail, it means that the path doesn't exist).
@@ -433,7 +439,11 @@ bool Filesystem::unmount(const char *archive)
 	if (!mountPoint)
 		return false;
 
+#ifdef LOVE_USE_PHYSFS_2_1
+	return PHYSFS_unmount(realPath.c_str()) != 0;
+#else
 	return PHYSFS_removeFromSearchPath(realPath.c_str()) != 0;
+#endif
 }
 
 love::filesystem::File *Filesystem::newFile(const char *filename) const
@@ -569,14 +579,40 @@ std::string Filesystem::getRealDirectory(const char *filename) const
 	return std::string(dir);
 }
 
+bool Filesystem::exists(const char *path) const
+{
+	return PHYSFS_exists(path) != 0;
+}
+
 bool Filesystem::isDirectory(const char *dir) const
 {
+#ifdef LOVE_USE_PHYSFS_2_1
+	PHYSFS_Stat stat = {};
+	if (PHYSFS_stat(dir, &stat))
+		return stat.filetype == PHYSFS_FILETYPE_DIRECTORY;
+	else
+		return false;
+#else
 	return PHYSFS_isDirectory(dir) != 0;
+#endif
 }
 
 bool Filesystem::isFile(const char *file) const
 {
-	return PHYSFS_exists(file) && !PHYSFS_isDirectory(file);
+	return PHYSFS_exists(file) && !isDirectory(file);
+}
+
+bool Filesystem::isSymlink(const char *filename) const
+{
+#ifdef LOVE_USE_PHYSFS_2_1
+	PHYSFS_Stat stat = {};
+	if (PHYSFS_stat(filename, &stat))
+		return stat.filetype == PHYSFS_FILETYPE_SYMLINK;
+	else
+		return false;
+#else
+	return PHYSFS_isSymbolicLink(filename) != 0;
+#endif
 }
 
 bool Filesystem::createDirectory(const char *dir)
@@ -643,7 +679,15 @@ void Filesystem::getDirectoryItems(const char *dir, std::vector<std::string> &it
 
 int64 Filesystem::getLastModified(const char *filename) const
 {
-	PHYSFS_sint64 time = PHYSFS_getLastModTime(filename);
+	PHYSFS_sint64 time = -1;
+
+#ifdef LOVE_USE_PHYSFS_2_1
+	PHYSFS_Stat stat = {};
+	if (PHYSFS_stat(filename, &stat))
+		time = stat.modtime;
+#else
+	time = PHYSFS_getLastModTime(filename);
+#endif
 
 	if (time == -1)
 		throw love::Exception("Could not determine file modification date.");
@@ -679,11 +723,6 @@ bool Filesystem::areSymlinksEnabled() const
 	return PHYSFS_symbolicLinksPermitted() != 0;
 }
 
-bool Filesystem::isSymlink(const char *filename) const
-{
-	return PHYSFS_isSymbolicLink(filename) != 0;
-}
-
 std::vector<std::string> &Filesystem::getRequirePath()
 {
 	return requirePath;

+ 2 - 1
jni/love/src/modules/filesystem/physfs/Filesystem.h

@@ -75,8 +75,10 @@ public:
 
 	std::string getRealDirectory(const char *filename) const;
 
+	bool exists(const char *path) const;
 	bool isDirectory(const char *dir) const;
 	bool isFile(const char *file) const;
+	bool isSymlink(const char *filename) const;
 
 	bool createDirectory(const char *dir);
 
@@ -93,7 +95,6 @@ public:
 
 	void setSymlinksEnabled(bool enable);
 	bool areSymlinksEnabled() const;
-	bool isSymlink(const char *filename) const;
 
 	std::vector<std::string> &getRequirePath();
 

+ 16 - 8
jni/love/src/modules/filesystem/wrap_Filesystem.cpp

@@ -316,6 +316,13 @@ int w_getExecutablePath(lua_State *L)
 	return 1;
 }
 
+int w_exists(lua_State *L)
+{
+	const char *arg = luaL_checkstring(L, 1);
+	luax_pushboolean(L, instance()->exists(arg));
+	return 1;
+}
+
 int w_isDirectory(lua_State *L)
 {
 	const char *arg = luaL_checkstring(L, 1);
@@ -330,6 +337,13 @@ int w_isFile(lua_State *L)
 	return 1;
 }
 
+int w_isSymlink(lua_State *L)
+{
+	const char *filename = luaL_checkstring(L, 1);
+	luax_pushboolean(L, instance()->isSymlink(filename));
+	return 1;
+}
+
 int w_createDirectory(lua_State *L)
 {
 	const char *arg = luaL_checkstring(L, 1);
@@ -551,13 +565,6 @@ int w_areSymlinksEnabled(lua_State *L)
 	return 1;
 }
 
-int w_isSymlink(lua_State *L)
-{
-	const char *filename = luaL_checkstring(L, 1);
-	luax_pushboolean(L, instance()->isSymlink(filename));
-	return 1;
-}
-
 int w_getRequirePath(lua_State *L)
 {
 	std::stringstream path;
@@ -715,8 +722,10 @@ static const luaL_Reg functions[] =
 	{ "getSourceBaseDirectory", w_getSourceBaseDirectory },
 	{ "getRealDirectory", w_getRealDirectory },
 	{ "getExecutablePath", w_getExecutablePath },
+	{ "exists", w_exists },
 	{ "isDirectory", w_isDirectory },
 	{ "isFile", w_isFile },
+	{ "isSymlink", w_isSymlink },
 	{ "createDirectory", w_createDirectory },
 	{ "remove", w_remove },
 	{ "read", w_read },
@@ -729,7 +738,6 @@ static const luaL_Reg functions[] =
 	{ "getSize", w_getSize },
 	{ "setSymlinksEnabled", w_setSymlinksEnabled },
 	{ "areSymlinksEnabled", w_areSymlinksEnabled },
-	{ "isSymlink", w_isSymlink },
 	{ "newFileData", w_newFileData },
 	{ "getRequirePath", w_getRequirePath },
 	{ "setRequirePath", w_setRequirePath },

+ 67 - 4
jni/love/src/modules/graphics/Graphics.cpp

@@ -82,6 +82,16 @@ bool Graphics::getConstant(BlendMode in, const char *&out)
 	return blendModes.find(in, out);
 }
 
+bool Graphics::getConstant(const char *in, BlendAlpha &out)
+{
+	return blendAlphaModes.find(in, out);
+}
+
+bool Graphics::getConstant(BlendAlpha in, const char *&out)
+{
+	return blendAlphaModes.find(in, out);
+}
+
 bool Graphics::getConstant(const char *in, LineStyle &out)
 {
 	return lineStyles.find(in, out);
@@ -102,6 +112,26 @@ bool Graphics::getConstant(LineJoin in, const char *&out)
 	return lineJoins.find(in, out);
 }
 
+bool Graphics::getConstant(const char *in, StencilAction &out)
+{
+	return stencilActions.find(in, out);
+}
+
+bool Graphics::getConstant(StencilAction in, const char *&out)
+{
+	return stencilActions.find(in, out);
+}
+
+bool Graphics::getConstant(const char *in, CompareMode &out)
+{
+	return compareModes.find(in, out);
+}
+
+bool Graphics::getConstant(CompareMode in, const char *&out)
+{
+	return compareModes.find(in, out);
+}
+
 bool Graphics::getConstant(const char *in, Support &out)
 {
 	return support.find(in, out);
@@ -152,16 +182,24 @@ StringMap<Graphics::DrawMode, Graphics::DRAW_MAX_ENUM> Graphics::drawModes(Graph
 
 StringMap<Graphics::BlendMode, Graphics::BLEND_MAX_ENUM>::Entry Graphics::blendModeEntries[] =
 {
-	{ "alpha", BLEND_ALPHA },
-	{ "add", BLEND_ADD },
+	{ "alpha",    BLEND_ALPHA    },
+	{ "add",      BLEND_ADD      },
 	{ "subtract", BLEND_SUBTRACT },
 	{ "multiply", BLEND_MULTIPLY },
-	{ "screen", BLEND_SCREEN },
-	{ "replace", BLEND_REPLACE },
+	{ "screen",   BLEND_SCREEN   },
+	{ "replace",  BLEND_REPLACE  },
 };
 
 StringMap<Graphics::BlendMode, Graphics::BLEND_MAX_ENUM> Graphics::blendModes(Graphics::blendModeEntries, sizeof(Graphics::blendModeEntries));
 
+StringMap<Graphics::BlendAlpha, Graphics::BLENDALPHA_MAX_ENUM>::Entry Graphics::blendAlphaEntries[] =
+{
+	{ "alphamultiply", BLENDALPHA_MULTIPLY      },
+	{ "premultiplied", BLENDALPHA_PREMULTIPLIED },
+};
+
+StringMap<Graphics::BlendAlpha, Graphics::BLENDALPHA_MAX_ENUM> Graphics::blendAlphaModes(Graphics::blendAlphaEntries, sizeof(Graphics::blendAlphaEntries));
+
 StringMap<Graphics::LineStyle, Graphics::LINE_MAX_ENUM>::Entry Graphics::lineStyleEntries[] =
 {
 	{ "smooth", LINE_SMOOTH },
@@ -179,6 +217,31 @@ StringMap<Graphics::LineJoin, Graphics::LINE_JOIN_MAX_ENUM>::Entry Graphics::lin
 
 StringMap<Graphics::LineJoin, Graphics::LINE_JOIN_MAX_ENUM> Graphics::lineJoins(Graphics::lineJoinEntries, sizeof(Graphics::lineJoinEntries));
 
+StringMap<Graphics::StencilAction, Graphics::STENCIL_MAX_ENUM>::Entry Graphics::stencilActionEntries[] =
+{
+	{ "replace", STENCIL_REPLACE },
+	{ "increment", STENCIL_INCREMENT },
+	{ "decrement", STENCIL_DECREMENT },
+	{ "incrementwrap", STENCIL_INCREMENT_WRAP },
+	{ "decrementwrap", STENCIL_DECREMENT_WRAP },
+	{ "invert", STENCIL_INVERT },
+};
+
+StringMap<Graphics::StencilAction, Graphics::STENCIL_MAX_ENUM> Graphics::stencilActions(Graphics::stencilActionEntries, sizeof(Graphics::stencilActionEntries));
+
+StringMap<Graphics::CompareMode, Graphics::COMPARE_MAX_ENUM>::Entry Graphics::compareModeEntries[] =
+{
+	{ "less",     COMPARE_LESS     },
+	{ "lequal",   COMPARE_LEQUAL   },
+	{ "equal",    COMPARE_EQUAL    },
+	{ "gequal",   COMPARE_GEQUAL   },
+	{ "greater",  COMPARE_GREATER  },
+	{ "notequal", COMPARE_NOTEQUAL },
+	{ "always",   COMPARE_ALWAYS   },
+};
+
+StringMap<Graphics::CompareMode, Graphics::COMPARE_MAX_ENUM> Graphics::compareModes(Graphics::compareModeEntries, sizeof(Graphics::compareModeEntries));
+
 StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM>::Entry Graphics::supportEntries[] =
 {
 	{ "multicanvasformats", SUPPORT_MULTI_CANVAS_FORMATS },

+ 48 - 0
jni/love/src/modules/graphics/Graphics.h

@@ -81,6 +81,13 @@ public:
 		BLEND_MAX_ENUM
 	};
 
+	enum BlendAlpha
+	{
+		BLENDALPHA_MULTIPLY,
+		BLENDALPHA_PREMULTIPLIED,
+		BLENDALPHA_MAX_ENUM
+	};
+
 	enum LineStyle
 	{
 		LINE_ROUGH,
@@ -96,6 +103,29 @@ public:
 		LINE_JOIN_MAX_ENUM
 	};
 
+	enum StencilAction
+	{
+		STENCIL_REPLACE,
+		STENCIL_INCREMENT,
+		STENCIL_DECREMENT,
+		STENCIL_INCREMENT_WRAP,
+		STENCIL_DECREMENT_WRAP,
+		STENCIL_INVERT,
+		STENCIL_MAX_ENUM
+	};
+
+	enum CompareMode
+	{
+		COMPARE_LESS,
+		COMPARE_LEQUAL,
+		COMPARE_EQUAL,
+		COMPARE_GEQUAL,
+		COMPARE_GREATER,
+		COMPARE_NOTEQUAL,
+		COMPARE_ALWAYS,
+		COMPARE_MAX_ENUM
+	};
+
 	enum Support
 	{
 		SUPPORT_MULTI_CANVAS_FORMATS,
@@ -232,12 +262,21 @@ public:
 	static bool getConstant(const char *in, BlendMode &out);
 	static bool getConstant(BlendMode in, const char *&out);
 
+	static bool getConstant(const char *in, BlendAlpha &out);
+	static bool getConstant(BlendAlpha in, const char *&out);
+
 	static bool getConstant(const char *in, LineStyle &out);
 	static bool getConstant(LineStyle in, const char *&out);
 
 	static bool getConstant(const char *in, LineJoin &out);
 	static bool getConstant(LineJoin in, const char *&out);
 
+	static bool getConstant(const char *in, StencilAction &out);
+	static bool getConstant(StencilAction in, const char *&out);
+
+	static bool getConstant(const char *in, CompareMode &out);
+	static bool getConstant(CompareMode in, const char *&out);
+
 	static bool getConstant(const char *in, Support &out);
 	static bool getConstant(Support in, const char *&out);
 
@@ -258,12 +297,21 @@ private:
 	static StringMap<BlendMode, BLEND_MAX_ENUM>::Entry blendModeEntries[];
 	static StringMap<BlendMode, BLEND_MAX_ENUM> blendModes;
 
+	static StringMap<BlendAlpha, BLENDALPHA_MAX_ENUM>::Entry blendAlphaEntries[];
+	static StringMap<BlendAlpha, BLENDALPHA_MAX_ENUM> blendAlphaModes;
+
 	static StringMap<LineStyle, LINE_MAX_ENUM>::Entry lineStyleEntries[];
 	static StringMap<LineStyle, LINE_MAX_ENUM> lineStyles;
 
 	static StringMap<LineJoin, LINE_JOIN_MAX_ENUM>::Entry lineJoinEntries[];
 	static StringMap<LineJoin, LINE_JOIN_MAX_ENUM> lineJoins;
 
+	static StringMap<StencilAction, STENCIL_MAX_ENUM>::Entry stencilActionEntries[];
+	static StringMap<StencilAction, STENCIL_MAX_ENUM> stencilActions;
+
+	static StringMap<CompareMode, COMPARE_MAX_ENUM>::Entry compareModeEntries[];
+	static StringMap<CompareMode, COMPARE_MAX_ENUM> compareModes;
+
 	static StringMap<Support, SUPPORT_MAX_ENUM>::Entry supportEntries[];
 	static StringMap<Support, SUPPORT_MAX_ENUM> support;
 

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

@@ -558,7 +558,7 @@ bool Canvas::checkCreateStencil()
 	if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object
 		|| GLAD_EXT_packed_depth_stencil || GLAD_OES_packed_depth_stencil)
 	{
-		format = GL_DEPTH_STENCIL;
+		format = GL_DEPTH24_STENCIL8;
 		attachment = GL_DEPTH_STENCIL_ATTACHMENT;
 	}
 

+ 16 - 4
jni/love/src/modules/graphics/opengl/Font.cpp

@@ -617,7 +617,7 @@ std::vector<Font::DrawCommand> Font::generateVerticesFormatted(const ColoredCode
 	return drawcommands;
 }
 
-void Font::drawVertices(const std::vector<DrawCommand> &drawcommands)
+void Font::drawVertices(const std::vector<DrawCommand> &drawcommands, bool bufferedvertices)
 {
 	// Vertex attribute pointers need to be set before calling this function.
 	// This assumes that the attribute pointers are constant for all vertices.
@@ -634,7 +634,12 @@ void Font::drawVertices(const std::vector<DrawCommand> &drawcommands)
 	const GLenum gltype = quadIndices.getType();
 	const size_t elemsize = quadIndices.getElementSize();
 
-	GLBuffer::Bind bind(*quadIndices.getBuffer());
+	// We only get indices from the index buffer if we're also using vertex
+	// buffers, because at least one graphics driver (the one for Kepler nvidia
+	// GPUs in OS X 10.11) fails to render geometry if an index buffer is used
+	// with client-side vertex arrays.
+	if (bufferedvertices)
+		quadIndices.getBuffer()->bind();
 
 	// We need a separate draw call for every section of the text which uses a
 	// different texture than the previous section.
@@ -645,8 +650,15 @@ void Font::drawVertices(const std::vector<DrawCommand> &drawcommands)
 
 		// TODO: Use glDrawElementsBaseVertex when supported?
 		gl.bindTexture(cmd.texture);
-		gl.drawElements(GL_TRIANGLES, count, gltype, quadIndices.getPointer(offset));
+
+		if (bufferedvertices)
+			gl.drawElements(GL_TRIANGLES, count, gltype, quadIndices.getPointer(offset));
+		else
+			gl.drawElements(GL_TRIANGLES, count, gltype, quadIndices.getIndices(offset));
 	}
+
+	if (bufferedvertices)
+		quadIndices.getBuffer()->unbind();
 }
 
 void Font::printv(const Matrix4 &t, const std::vector<DrawCommand> &drawcommands, const std::vector<GlyphVertex> &vertices)
@@ -665,7 +677,7 @@ void Font::printv(const Matrix4 &t, const std::vector<DrawCommand> &drawcommands
 
 	gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD | ATTRIBFLAG_COLOR);
 
-	drawVertices(drawcommands);
+	drawVertices(drawcommands, false);
 }
 
 void Font::print(const std::vector<ColoredString> &text, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)

+ 1 - 1
jni/love/src/modules/graphics/opengl/Font.h

@@ -109,7 +109,7 @@ public:
 
 	std::vector<DrawCommand> generateVerticesFormatted(const ColoredCodepoints &text, float wrap, AlignMode align, std::vector<GlyphVertex> &vertices, TextInfo *info = nullptr);
 
-	void drawVertices(const std::vector<DrawCommand> &drawcommands);
+	void drawVertices(const std::vector<DrawCommand> &drawcommands, bool bufferedvertices);
 
 	static void getCodepointsFromString(const std::string &str, Codepoints &codepoints);
 	static void getCodepointsFromString(const std::vector<ColoredString> &strs, ColoredCodepoints &codepoints);

+ 28 - 10
jni/love/src/modules/graphics/opengl/GLBuffer.cpp

@@ -244,7 +244,9 @@ void GLBuffer::unload()
 size_t QuadIndices::maxSize = 0;
 size_t QuadIndices::elementSize = 0;
 size_t QuadIndices::objectCount = 0;
+
 GLBuffer *QuadIndices::indexBuffer = nullptr;
+char *QuadIndices::indices = nullptr;
 
 QuadIndices::QuadIndices(size_t size)
 	: size(size)
@@ -259,6 +261,7 @@ QuadIndices::QuadIndices(size_t size)
 	if (indexBuffer == nullptr || size > maxSize)
 	{
 		GLBuffer *newbuffer = nullptr;
+		char *newindices = nullptr;
 
 		// Depending on the size, a switch to int and more memory is needed.
 		GLenum targettype = getType(size);
@@ -271,9 +274,12 @@ QuadIndices::QuadIndices(size_t size)
 		try
 		{
 			newbuffer = new GLBuffer(buffersize, nullptr, GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW);
+			newindices = new char[buffersize];
 		}
 		catch (std::bad_alloc &)
 		{
+			delete[] newbuffer;
+			delete[] newindices;
 			throw love::Exception("Out of memory.");
 		}
 
@@ -281,6 +287,10 @@ QuadIndices::QuadIndices(size_t size)
 		// The old GLBuffer can now be deleted.
 		delete indexBuffer;
 		indexBuffer = newbuffer;
+
+		delete[] indices;
+		indices = newindices;
+
 		maxSize = size;
 		elementSize = elemsize;
 
@@ -319,6 +329,9 @@ QuadIndices::~QuadIndices()
 	{
 		delete indexBuffer;
 		indexBuffer = nullptr;
+
+		delete[] indices;
+		indices = nullptr;
 	}
 }
 
@@ -355,13 +368,15 @@ const void *QuadIndices::getPointer(size_t offset) const
 	return indexBuffer->getPointer(offset);
 }
 
+const void *QuadIndices::getIndices(size_t offset) const
+{
+	return indices + offset;
+}
+
 template <typename T>
 void QuadIndices::fill()
 {
-	GLBuffer::Bind bind(*indexBuffer);
-	GLBuffer::Mapper mapper(*indexBuffer);
-
-	T *indices = (T *) mapper.get();
+	T *inds = (T *) indices;
 
 	// 0----2
 	// |  / |
@@ -369,14 +384,17 @@ void QuadIndices::fill()
 	// 1----3
 	for (size_t i = 0; i < maxSize; ++i)
 	{
-		indices[i*6+0] = T(i * 4 + 0);
-		indices[i*6+1] = T(i * 4 + 1);
-		indices[i*6+2] = T(i * 4 + 2);
+		inds[i*6+0] = T(i * 4 + 0);
+		inds[i*6+1] = T(i * 4 + 1);
+		inds[i*6+2] = T(i * 4 + 2);
 
-		indices[i*6+3] = T(i * 4 + 2);
-		indices[i*6+4] = T(i * 4 + 1);
-		indices[i*6+5] = T(i * 4 + 3);
+		inds[i*6+3] = T(i * 4 + 2);
+		inds[i*6+4] = T(i * 4 + 1);
+		inds[i*6+5] = T(i * 4 + 3);
 	}
+
+	GLBuffer::Bind bind(*indexBuffer);
+	indexBuffer->fill(0, indexBuffer->getSize(), indices);
 }
 
 } // opengl

+ 17 - 0
jni/love/src/modules/graphics/opengl/GLBuffer.h

@@ -384,6 +384,20 @@ public:
 	 */
 	const void *getPointer(size_t offset) const;
 
+	/**
+	 * Returns a direct pointer to the index data.
+	 *
+	 * At least one graphics driver (the one for Kepler nvidia GPUs in OS X)
+	 * fails to render geometry if the vertex data was a direct CPU pointer but
+	 * the index data came from an Index Buffer.
+	 * So the direct pointer to the index buffer should be used instead of the
+	 * index buffer when rendering using client-side vertex arrays.
+	 *
+	 * @param offset An offset in bytes into the index data.
+	 * @return A direct pointer to the index data at the specified offset.
+	 **/
+	const void *getIndices(size_t offset) const;
+
 private:
 
 	/**
@@ -406,6 +420,9 @@ private:
 
 	// The GLBuffer for the element array. Can be null.
 	static GLBuffer *indexBuffer;
+
+	// The array of indices that will also be stored in the index buffer.
+	static char *indices;
 };
 
 } // opengl

+ 109 - 39
jni/love/src/modules/graphics/opengl/Graphics.cpp

@@ -106,7 +106,7 @@ void Graphics::restoreState(const DisplayState &s)
 	setColor(s.color);
 	setBackgroundColor(s.backgroundColor);
 
-	setBlendMode(s.blendMode, s.blendMultiplyAlpha);
+	setBlendMode(s.blendMode, s.blendAlphaMode);
 
 	setLineWidth(s.lineWidth);
 	setLineStyle(s.lineStyle);
@@ -119,7 +119,7 @@ void Graphics::restoreState(const DisplayState &s)
 	else
 		setScissor();
 
-	setStencilTest(s.stencilTest, s.stencilInvert);
+	setStencilTest(s.stencilCompare, s.stencilTestValue);
 
 	setFont(s.font.get());
 	setShader(s.shader.get());
@@ -141,8 +141,8 @@ void Graphics::restoreStateChecked(const DisplayState &s)
 
 	setBackgroundColor(s.backgroundColor);
 
-	if (s.blendMode != cur.blendMode || s.blendMultiplyAlpha != cur.blendMultiplyAlpha)
-		setBlendMode(s.blendMode, s.blendMultiplyAlpha);
+	if (s.blendMode != cur.blendMode || s.blendAlphaMode != cur.blendAlphaMode)
+		setBlendMode(s.blendMode, s.blendAlphaMode);
 
 	// These are just simple assignments.
 	setLineWidth(s.lineWidth);
@@ -160,8 +160,8 @@ void Graphics::restoreStateChecked(const DisplayState &s)
 			setScissor();
 	}
 
-	if (s.stencilTest != cur.stencilTest || s.stencilInvert != cur.stencilInvert)
-		setStencilTest(s.stencilTest, s.stencilInvert);
+	if (s.stencilCompare != cur.stencilCompare || s.stencilTestValue != cur.stencilTestValue)
+		setStencilTest(s.stencilCompare, s.stencilTestValue);
 
 	setFont(s.font.get());
 	setShader(s.shader.get());
@@ -433,7 +433,7 @@ void Graphics::setDebug(bool enable)
 void Graphics::reset()
 {
 	DisplayState s;
-	drawToStencilBuffer(false);
+	stopDrawToStencilBuffer();
 	restoreState(s);
 	origin();
 }
@@ -641,24 +641,9 @@ bool Graphics::getScissor(int &x, int &y, int &width, int &height) const
 	return state.scissor;
 }
 
-void Graphics::drawToStencilBuffer(bool enable)
+void Graphics::drawToStencilBuffer(StencilAction action, int value)
 {
-	if (writingToStencil == enable)
-		return;
-
-	writingToStencil = enable;
-
-	if (!enable)
-	{
-		const DisplayState &state = states.back();
-
-		// Revert the color write mask.
-		setColorMask(state.colorMask);
-
-		// Use the user-set stencil test state when writes are disabled.
-		setStencilTest(state.stencilTest, state.stencilInvert);
-		return;
-	}
+	writingToStencil = true;
 
 	// Make sure the active canvas has a stencil buffer.
 	if (Canvas::current)
@@ -667,23 +652,63 @@ void Graphics::drawToStencilBuffer(bool enable)
 	// Disable color writes but don't save the state for it.
 	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
 
+	GLenum glaction = GL_REPLACE;
+
+	switch (action)
+	{
+	case STENCIL_REPLACE:
+	default:
+		glaction = GL_REPLACE;
+		break;
+	case STENCIL_INCREMENT:
+		glaction = GL_INCR;
+		break;
+	case STENCIL_DECREMENT:
+		glaction = GL_DECR;
+		break;
+	case STENCIL_INCREMENT_WRAP:
+		glaction = GL_INCR_WRAP;
+		break;
+	case STENCIL_DECREMENT_WRAP:
+		glaction = GL_DECR_WRAP;
+		break;
+	case STENCIL_INVERT:
+		glaction = GL_INVERT;
+		break;
+	}
+
 	// The stencil test must be enabled in order to write to the stencil buffer.
 	glEnable(GL_STENCIL_TEST);
+	glStencilFunc(GL_ALWAYS, value, 0xFFFFFFFF);
+	glStencilOp(GL_KEEP, GL_KEEP, glaction);
+}
 
-	glStencilFunc(GL_ALWAYS, 1, 1);
-	glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
+void Graphics::stopDrawToStencilBuffer()
+{
+	if (!writingToStencil)
+		return;
+
+	writingToStencil = false;
+
+	const DisplayState &state = states.back();
+
+	// Revert the color write mask.
+	setColorMask(state.colorMask);
+
+	// Use the user-set stencil test state when writes are disabled.
+	setStencilTest(state.stencilCompare, state.stencilTestValue);
 }
 
-void Graphics::setStencilTest(bool enable, bool invert)
+void Graphics::setStencilTest(CompareMode compare, int value)
 {
 	DisplayState &state = states.back();
-	state.stencilTest = enable;
-	state.stencilInvert = invert;
+	state.stencilCompare = compare;
+	state.stencilTestValue = value;
 
 	if (writingToStencil)
 		return;
 
-	if (!enable)
+	if (compare == COMPARE_ALWAYS)
 	{
 		glDisable(GL_STENCIL_TEST);
 		return;
@@ -693,16 +718,61 @@ void Graphics::setStencilTest(bool enable, bool invert)
 	if (Canvas::current)
 		Canvas::current->checkCreateStencil();
 
+	GLenum glcompare = GL_EQUAL;
+
+	/**
+	 * Q: Why are some of the compare modes inverted (e.g. COMPARE_LESS becomes
+	 * GL_GREATER)?
+	 *
+	 * A: OpenGL / GPUs do the comparison in the opposite way that makes sense
+	 * for this API. For example, if the compare function is GL_GREATER then the
+	 * stencil test will pass if the reference value is greater than the value
+	 * in the stencil buffer. With our API it's more intuitive to assume that
+	 * setStencilTest(COMPARE_GREATER, 4) will make it pass if the stencil
+	 * buffer has a value greater than 4.
+	 **/
+
+	switch (compare)
+	{
+	case COMPARE_LESS:
+		glcompare = GL_GREATER;
+		break;
+	case COMPARE_LEQUAL:
+		glcompare = GL_GEQUAL;
+		break;
+	case COMPARE_EQUAL:
+	default:
+		glcompare = GL_EQUAL;
+		break;
+	case COMPARE_GEQUAL:
+		glcompare = GL_LEQUAL;
+		break;
+	case COMPARE_GREATER:
+		glcompare = GL_LESS;
+		break;
+	case COMPARE_NOTEQUAL:
+		glcompare = GL_NOTEQUAL;
+		break;
+	case COMPARE_ALWAYS:
+		glcompare = GL_ALWAYS;
+		break;
+	}
+
 	glEnable(GL_STENCIL_TEST);
-	glStencilFunc(GL_EQUAL, invert ? 0 : 1, 1);
+	glStencilFunc(glcompare, value, 0xFFFFFFFF);
 	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
 }
 
-void Graphics::getStencilTest(bool &enable, bool &invert)
+void Graphics::setStencilTest()
+{
+	setStencilTest(COMPARE_ALWAYS, 0);
+}
+
+void Graphics::getStencilTest(CompareMode &compare, int &value)
 {
 	const DisplayState &state = states.back();
-	enable = state.stencilTest;
-	invert = state.stencilInvert;
+	compare = state.stencilCompare;
+	value = state.stencilTestValue;
 }
 
 void Graphics::clearStencil()
@@ -987,7 +1057,7 @@ Graphics::ColorMask Graphics::getColorMask() const
 	return states.back().colorMask;
 }
 
-void Graphics::setBlendMode(BlendMode mode, bool multiplyalpha)
+void Graphics::setBlendMode(BlendMode mode, BlendAlpha alphamode)
 {
 	GLenum func   = GL_FUNC_ADD;
 	GLenum srcRGB = GL_ONE;
@@ -1024,19 +1094,19 @@ void Graphics::setBlendMode(BlendMode mode, bool multiplyalpha)
 	}
 
 	// We can only do alpha-multiplication when srcRGB would have been unmodified.
-	if (srcRGB == GL_ONE && multiplyalpha)
+	if (srcRGB == GL_ONE && alphamode == BLENDALPHA_MULTIPLY)
 		srcRGB = GL_SRC_ALPHA;
 
 	glBlendEquation(func);
 	glBlendFuncSeparate(srcRGB, dstRGB, srcA, dstA);
 
 	states.back().blendMode = mode;
-	states.back().blendMultiplyAlpha = multiplyalpha;
+	states.back().blendAlphaMode = alphamode;
 }
 
-Graphics::BlendMode Graphics::getBlendMode(bool &multiplyalpha) const
+Graphics::BlendMode Graphics::getBlendMode(BlendAlpha &alphamode) const
 {
-	multiplyalpha = states.back().blendMultiplyAlpha;
+	alphamode = states.back().blendAlphaMode;
 	return states.back().blendMode;
 }
 

+ 10 - 8
jni/love/src/modules/graphics/opengl/Graphics.h

@@ -146,13 +146,15 @@ public:
 	 * Enables or disables drawing to the stencil buffer. When enabled, the
 	 * color buffer is disabled.
 	 **/
-	void drawToStencilBuffer(bool enable);
+	void drawToStencilBuffer(StencilAction action, int value);
+	void stopDrawToStencilBuffer();
 
 	/**
 	 * Sets whether stencil testing is enabled.
 	 **/
-	void setStencilTest(bool enable, bool invert);
-	void getStencilTest(bool &enable, bool &invert);
+	void setStencilTest(CompareMode compare, int value);
+	void setStencilTest();
+	void getStencilTest(CompareMode &compare, int &value);
 
 	/**
 	 * Clear the stencil buffer in the active Canvas(es.)
@@ -241,12 +243,12 @@ public:
 	/**
 	 * Sets the current blend mode.
 	 **/
-	void setBlendMode(BlendMode mode, bool multiplyalpha);
+	void setBlendMode(BlendMode mode, BlendAlpha alphamode);
 
 	/**
 	 * Gets the current blend mode.
 	 **/
-	BlendMode getBlendMode(bool &multiplyalpha) const;
+	BlendMode getBlendMode(BlendAlpha &alphamode) const;
 
 	/**
 	 * Sets the default filter for images, canvases, and fonts.
@@ -476,7 +478,7 @@ private:
 		Colorf backgroundColor = Colorf(0.0, 0.0, 0.0, 255.0);
 
 		BlendMode blendMode = BLEND_ALPHA;
-		bool blendMultiplyAlpha = true;
+		BlendAlpha blendAlphaMode = BLENDALPHA_MULTIPLY;
 
 		float lineWidth = 1.0f;
 		LineStyle lineStyle = LINE_SMOOTH;
@@ -488,8 +490,8 @@ private:
 		ScissorRect scissorRect = ScissorRect();
 
 		// Stencil.
-		bool stencilTest = false;
-		bool stencilInvert = false;
+		CompareMode stencilCompare = COMPARE_ALWAYS;
+		int stencilTestValue = 0;
 
 		StrongRef<Font> font;
 		StrongRef<Shader> shader;

+ 8 - 5
jni/love/src/modules/graphics/opengl/ParticleSystem.cpp

@@ -141,11 +141,14 @@ void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, flo
 	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), &particleVerts[0].x);
 	glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), &particleVerts[0].s);
 
-	{
-		GLsizei count = (GLsizei) quadIndices.getIndexCount(pCount);
-		GLBuffer::Bind ibo_bind(*quadIndices.getBuffer());
-		gl.drawElements(GL_TRIANGLES, count, quadIndices.getType(), quadIndices.getPointer(0));
-	}
+	GLsizei count = (GLsizei) quadIndices.getIndexCount(pCount);
+	GLenum gltype = quadIndices.getType();
+
+	// We use a client-side index array instead of an Index Buffers, because
+	// at least one graphics driver (the one for Kepler nvidia GPUs in OS X
+	// 10.11) fails to render geometry if an index buffer is used with
+	// client-side vertex arrays.
+	gl.drawElements(GL_TRIANGLES, count, gltype, quadIndices.getIndices(0));
 }
 
 } // opengl

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

@@ -245,7 +245,7 @@ void Text::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 
 	gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD | ATTRIBFLAG_COLOR);
 
-	font->drawVertices(draw_commands);
+	font->drawVertices(draw_commands, true);
 }
 
 void Text::setFont(Font *f)

+ 21 - 21
jni/love/src/modules/graphics/opengl/Video.cpp

@@ -79,23 +79,23 @@ bool Video::loadVolatile()
 	// Create the textures using the initial frame data.
 	auto frame = (const love::video::VideoStream::Frame*) stream->getFrontBuffer();
 
-	gl.bindTexture(textures[0]);
-	gl.setTextureFilter(filter);
+	int widths[3]  = {frame->yw, frame->cw, frame->cw};
+	int heights[3] = {frame->yh, frame->ch, frame->ch};
 
-	glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, frame->yw, frame->yh,
-	             0, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->yplane);
+	const unsigned char *data[3] = {frame->yplane, frame->cbplane, frame->crplane};
 
-	gl.bindTexture(textures[1]);
-	gl.setTextureFilter(filter);
+	Texture::Wrap wrap; // Clamp wrap mode.
 
-	glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, frame->cw, frame->ch,
-	             0, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->cbplane);
+	for (int i = 0; i < 3; i++)
+	{
+		gl.bindTexture(textures[i]);
 
-	gl.bindTexture(textures[2]);
-	gl.setTextureFilter(filter);
+		gl.setTextureFilter(filter);
+		gl.setTextureWrap(wrap);
 
-	glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, frame->cw, frame->ch,
-	             0, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->crplane);
+		glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, widths[i], heights[i], 0,
+		             GL_LUMINANCE, GL_UNSIGNED_BYTE, data[i]);
+	}
 
 	return true;
 }
@@ -154,17 +154,17 @@ void Video::update()
 	{
 		auto frame = (const love::video::VideoStream::Frame*) stream->getFrontBuffer();
 
-		gl.bindTexture(textures[0]);
-		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frame->yw, frame->yh,
-		                GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->yplane);
+		int widths[3]  = {frame->yw, frame->cw, frame->cw};
+		int heights[3] = {frame->yh, frame->ch, frame->ch};
 
-		gl.bindTexture(textures[1]);
-		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frame->cw, frame->ch,
-		                GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->cbplane);
+		const unsigned char *data[3] = {frame->yplane, frame->cbplane, frame->crplane};
 
-		gl.bindTexture(textures[2]);
-		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frame->cw, frame->ch,
-		                GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->crplane);
+		for (int i = 0; i < 3; i++)
+		{
+			gl.bindTexture(textures[i]);
+			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, widths[i], heights[i],
+			                GL_LUMINANCE, GL_UNSIGNED_BYTE, data[i]);
+		}
 	}
 }
 

+ 56 - 19
jni/love/src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -217,35 +217,63 @@ int w_stencil(lua_State *L)
 {
 	luaL_checktype(L, 1, LUA_TFUNCTION);
 
-	// Second argument: whether to keep the contents of the stencil buffer.
-	if (lua_toboolean(L, 2) == 0)
+	Graphics::StencilAction action = Graphics::STENCIL_REPLACE;
+
+	if (!lua_isnoneornil(L, 2))
+	{
+		const char *actionstr = luaL_checkstring(L, 2);
+		if (!Graphics::getConstant(actionstr, action))
+			return luaL_error(L, "Invalid stencil draw action: %s", actionstr);
+	}
+
+	int stencilvalue = (int) luaL_optnumber(L, 3, 1);
+
+	// Fourth argument: whether to keep the contents of the stencil buffer.
+	if (lua_toboolean(L, 4) == 0)
 		instance()->clearStencil();
 
-	instance()->drawToStencilBuffer(true);
+	instance()->drawToStencilBuffer(action, stencilvalue);
 
 	// Call stencilfunc()
 	lua_pushvalue(L, 1);
 	lua_call(L, 0, 0);
 
-	instance()->drawToStencilBuffer(false);
-
+	instance()->stopDrawToStencilBuffer();
 	return 0;
 }
 
 int w_setStencilTest(lua_State *L)
 {
-	bool enable = luax_toboolean(L, 1);
-	bool invert = luax_toboolean(L, 2);
-	instance()->setStencilTest(enable, invert);
+	// COMPARE_ALWAYS effectively disables stencil testing.
+	Graphics::CompareMode compare = Graphics::COMPARE_ALWAYS;
+	int comparevalue = 0;
+
+	if (!lua_isnoneornil(L, 1))
+	{
+		const char *comparestr = luaL_checkstring(L, 1);
+		if (!Graphics::getConstant(comparestr, compare))
+			return luaL_error(L, "Invalid compare mode: %s", comparestr);
+
+		comparevalue = (int) luaL_checknumber(L, 2);
+	}
+
+	instance()->setStencilTest(compare, comparevalue);
 	return 0;
 }
 
 int w_getStencilTest(lua_State *L)
 {
-	bool enabled, inverted;
-	instance()->getStencilTest(enabled, inverted);
-	luax_pushboolean(L, enabled);
-	luax_pushboolean(L, inverted);
+	Graphics::CompareMode compare = Graphics::COMPARE_ALWAYS;
+	int comparevalue = 1;
+
+	instance()->getStencilTest(compare, comparevalue);
+
+	const char *comparestr;
+	if (!Graphics::getConstant(compare, comparestr))
+		return luaL_error(L, "Unknown compare mode.");
+
+	lua_pushstring(L, comparestr);
+	lua_pushnumber(L, comparevalue);
 	return 2;
 }
 
@@ -1014,25 +1042,34 @@ int w_setBlendMode(lua_State *L)
 	if (!Graphics::getConstant(str, mode))
 		return luaL_error(L, "Invalid blend mode: %s", str);
 
-	bool multiplyalpha = luax_optboolean(L, 2, true);
+	Graphics::BlendAlpha alphamode = Graphics::BLENDALPHA_MULTIPLY;
+	if (!lua_isnoneornil(L, 2))
+	{
+		const char *alphastr = luaL_checkstring(L, 2);
+		if (!Graphics::getConstant(alphastr, alphamode))
+			return luaL_error(L, "Invalid blend alpha mode: %s", alphastr);
+	}
 
-	luax_catchexcept(L, [&](){ instance()->setBlendMode(mode, multiplyalpha); });
+	luax_catchexcept(L, [&](){ instance()->setBlendMode(mode, alphamode); });
 	return 0;
 }
 
 int w_getBlendMode(lua_State *L)
 {
 	const char *str;
-	Graphics::BlendMode mode;
-	bool multiplyalpha = false;
+	const char *alphastr;
 
-	luax_catchexcept(L, [&](){ mode = instance()->getBlendMode(multiplyalpha); });
+	Graphics::BlendAlpha alphamode;
+	Graphics::BlendMode mode = instance()->getBlendMode(alphamode);
 
 	if (!Graphics::getConstant(mode, str))
 		return luaL_error(L, "Unknown blend mode");
 
+	if (!Graphics::getConstant(alphamode, alphastr))
+		return luaL_error(L, "Unknown blend alpha mode");
+
 	lua_pushstring(L, str);
-	lua_pushboolean(L, multiplyalpha);
+	lua_pushstring(L, alphastr);
 	return 2;
 }
 
@@ -1201,7 +1238,7 @@ int w_newScreenshot(lua_State *L)
 int w_setCanvas(lua_State *L)
 {
 	// Disable stencil writes.
-	instance()->drawToStencilBuffer(false);
+	instance()->stopDrawToStencilBuffer();
 
 	// called with none -> reset to default buffer
 	if (lua_isnoneornil(L, 1))

+ 1 - 1
jni/love/src/modules/graphics/opengl/wrap_Graphics.lua

@@ -366,7 +366,7 @@ function love.graphics.newVideo(file, loadaudio)
 	elseif loadaudio == true then
 		error("Video had no audio track", 2)
 	else
-		video:getStream():setSync(love.video.newRemote())
+		video:getStream():setSync()
 	end
 
 	return video

+ 18 - 4
jni/love/src/modules/graphics/opengl/wrap_Mesh.cpp

@@ -26,6 +26,7 @@
 
 // C++
 #include <typeinfo>
+#include <algorithm>
 
 namespace love
 {
@@ -108,12 +109,28 @@ const char *luax_readAttributeData(lua_State *L, Mesh::DataType type, int compon
 int w_Mesh_setVertices(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
-	luaL_checktype(L, 2, LUA_TTABLE);
 	size_t vertoffset = (size_t) luaL_optnumber(L, 3, 1) - 1;
 
 	if (vertoffset >= t->getVertexCount())
 		return luaL_error(L, "Invalid vertex start index (must be between 1 and %d)", (int) t->getVertexCount());
 
+	size_t stride = t->getVertexStride();
+	size_t byteoffset = vertoffset * stride;
+
+	if (luax_istype(L, 2, DATA_ID))
+	{
+		Data *d = luax_checktype<Data>(L, 2, DATA_ID);
+
+		size_t datasize = std::min(d->getSize(), (t->getVertexCount() - vertoffset) * stride);
+		char *bytedata = (char *) t->mapVertexData() + byteoffset;
+
+		memcpy(bytedata, d->getData(), datasize);
+
+		t->unmapVertexData(byteoffset, datasize);
+		return 0;
+	}
+
+	luaL_checktype(L, 2, LUA_TTABLE);
 	size_t nvertices = luax_objlen(L, 2);
 
 	if (vertoffset + nvertices > t->getVertexCount())
@@ -125,9 +142,6 @@ int w_Mesh_setVertices(lua_State *L)
 	for (const Mesh::AttribFormat &format : vertexformat)
 		ncomponents += format.components;
 
-	size_t stride = t->getVertexStride();
-	size_t byteoffset = vertoffset * stride;
-
 	char *data = (char *) t->mapVertexData() + byteoffset;
 
 	for (size_t i = 0; i < nvertices; i++)

+ 1 - 0
jni/love/src/modules/graphics/opengl/wrap_Mesh.h

@@ -22,6 +22,7 @@
 #define LOVE_GRAPHICS_OPENGL_WRAP_MESH_H
 
 // LOVE
+#include "common/config.h"
 #include "common/runtime.h"
 #include "Mesh.h"
 

+ 1 - 12
jni/love/src/modules/graphics/opengl/wrap_Video.lua

@@ -25,20 +25,9 @@ misrepresented as being the original software.
 local Video_mt = ...
 local Video = Video_mt.__index
 
-function Video:loadRemote()
-	local stream = self:getStream()
-	local remote = love.video.newRemote()
-	stream:setSync(remote)
-	return remote
-end
-
 function Video:setSource(source)
 	self:_setSource(source)
-	if source then
-		self:getStream():setSync(source)
-	else
-		self:getStream():setSync(love.video.newRemote())
-	end
+	self:getStream():setSync(source)
 end
 
 function Video:play()

+ 2 - 49
jni/love/src/modules/math/MathModule.h

@@ -55,56 +55,9 @@ public:
 
 	virtual ~Math();
 
-	/**
-	 * @copydoc RandomGenerator::random()
-	 **/
-	inline double random()
-	{
-		return rng.random();
-	}
-
-	/**
-	 * @copydoc RandomGenerator::random(double)
-	 **/
-	inline double random(double max)
-	{
-		return rng.random(max);
-	}
-
-	/**
-	 * @copydoc RandomGenerator::random(double,double)
-	 **/
-	inline double random(double min, double max)
-	{
-		return rng.random(min, max);
-	}
-
-	/**
-	 * @copydoc RandomGenerator::randomNormal()
-	 **/
-	inline double randomNormal(double stddev)
-	{
-		return rng.randomNormal(stddev);
-	}
-
-	inline void setRandomSeed(RandomGenerator::Seed seed)
-	{
-		rng.setSeed(seed);
-	}
-
-	inline RandomGenerator::Seed getRandomSeed() const
-	{
-		return rng.getSeed();
-	}
-
-	inline void setRandomState(const std::string &statestr)
-	{
-		rng.setState(statestr);
-	}
-
-	inline std::string getRandomState() const
+	RandomGenerator *getRandomGenerator()
 	{
-		return rng.getState();
+		return &rng;
 	}
 
 	/**

+ 7 - 44
jni/love/src/modules/math/wrap_Math.cpp

@@ -39,44 +39,10 @@ namespace love
 namespace math
 {
 
-int w_random(lua_State *L)
+int w__getRandomGenerator(lua_State *L)
 {
-	return luax_getrandom(L, 1, Math::instance.random());
-}
-
-int w_randomNormal(lua_State *L)
-{
-	double stddev = luaL_optnumber(L, 1, 1.0);
-	double mean = luaL_optnumber(L, 2, 0.0);
-	double r = Math::instance.randomNormal(stddev);
-
-	lua_pushnumber(L, r + mean);
-	return 1;
-}
-
-int w_setRandomSeed(lua_State *L)
-{
-	luax_catchexcept(L, [&](){ Math::instance.setRandomSeed(luax_checkrandomseed(L, 1)); });
-	return 0;
-}
-
-int w_getRandomSeed(lua_State *L)
-{
-	RandomGenerator::Seed s = Math::instance.getRandomSeed();
-	lua_pushnumber(L, (lua_Number) s.b32.low);
-	lua_pushnumber(L, (lua_Number) s.b32.high);
-	return 2;
-}
-
-int w_setRandomState(lua_State *L)
-{
-	luax_catchexcept(L, [&](){ Math::instance.setRandomState(luax_checkstring(L, 1)); });
-	return 0;
-}
-
-int w_getRandomState(lua_State *L)
-{
-	luax_pushstring(L, Math::instance.getRandomState());
+	RandomGenerator *t = Math::instance.getRandomGenerator();
+	luax_pushtype(L, MATH_RANDOM_GENERATOR_ID, t);
 	return 1;
 }
 
@@ -463,12 +429,9 @@ static FFI_Math ffifuncs =
 // List of functions to wrap.
 static const luaL_Reg functions[] =
 {
-	{ "random", w_random },
-	{ "randomNormal", w_randomNormal },
-	{ "setRandomSeed", w_setRandomSeed },
-	{ "getRandomSeed", w_getRandomSeed },
-	{ "setRandomState", w_setRandomState },
-	{ "getRandomState", w_getRandomState },
+	// love.math.random etc. are defined in wrap_Math.lua.
+
+	{ "_getRandomGenerator", w__getRandomGenerator },
 	{ "newRandomGenerator", w_newRandomGenerator },
 	{ "newBezierCurve", w_newBezierCurve },
 	{ "triangulate", w_triangulate },
@@ -502,7 +465,7 @@ extern "C" int luaopen_love_math(lua_State *L)
 
 	int n = luax_register_module(L, w);
 
-	// Execute wrap_Event.lua, sending the math table and ffifuncs pointer as args.
+	// Execute wrap_Math.lua, sending the math table and ffifuncs pointer as args.
 	luaL_loadbuffer(L, math_lua, sizeof(math_lua), "wrap_Math.lua");
 	lua_pushvalue(L, -2);
 	lua_pushlightuserdata(L, &ffifuncs);

+ 33 - 6
jni/love/src/modules/math/wrap_Math.lua

@@ -22,7 +22,36 @@ misrepresented as being the original software.
 3. This notice may not be removed or altered from any source distribution.
 --]]
 
-local math, ffifuncspointer = ...
+local love_math, ffifuncspointer = ...
+
+local type, tonumber, error = type, tonumber, error
+local floor = math.floor
+
+local rng = love_math._getRandomGenerator()
+
+function love_math.random(l, u)
+	return rng:random(l, u)
+end
+
+function love_math.randomNormal(stddev, mean)
+	return rng:randomNormal(stddev, mean)
+end
+
+function love_math.setRandomSeed(low, high)
+	return rng:setSeed(low, high)
+end
+
+function love_math.getRandomSeed()
+	return rng:getSeed()
+end
+
+function love_math.setRandomState(state)
+	return rng:setState(state)
+end
+
+function love_math.getRandomState()
+	return rng:getState()
+end
 
 if type(jit) ~= "table" or not jit.status() then
 	-- LuaJIT's FFI is *much* slower than LOVE's regular methods when the JIT
@@ -33,8 +62,6 @@ end
 local status, ffi = pcall(require, "ffi")
 if not status then return end
 
-local type, tonumber = type, tonumber
-
 -- Matches the struct declaration in wrap_Math.cpp.
 pcall(ffi.cdef, [[
 typedef struct FFI_Math
@@ -54,7 +81,7 @@ local ffifuncs = ffi.cast("FFI_Math *", ffifuncspointer)
 
 -- Overwrite some regular love.math functions with FFI implementations.
 
-function math.noise(x, y, z, w)
+function love_math.noise(x, y, z, w)
 	if w ~= nil then
 		return tonumber(ffifuncs.noise4(x, y, z, w))
 	elseif z ~= nil then
@@ -73,7 +100,7 @@ local function gammaToLinear(c)
 	return c
 end
 
-function math.gammaToLinear(r, g, b, a)
+function love_math.gammaToLinear(r, g, b, a)
 	if type(r) == "table" then
 		local t = r
 		return gammaToLinear(t[1]), gammaToLinear(t[2]), gammaToLinear(t[3]), t[4]
@@ -88,7 +115,7 @@ local function linearToGamma(c)
 	return c
 end
 
-function math.linearToGamma(r, g, b, a)
+function love_math.linearToGamma(r, g, b, a)
 	if type(r) == "table" then
 		local t = r
 		return linearToGamma(t[1]), linearToGamma(t[2]), linearToGamma(t[3]), t[4]

+ 46 - 30
jni/love/src/modules/math/wrap_RandomGenerator.cpp

@@ -23,6 +23,11 @@
 #include <cmath>
 #include <algorithm>
 
+// Put the Lua code directly into a raw string literal.
+static const char randomgenerator_lua[] =
+#include "wrap_RandomGenerator.lua"
+;
+
 namespace love
 {
 namespace math
@@ -56,41 +61,16 @@ RandomGenerator::Seed luax_checkrandomseed(lua_State *L, int idx)
 	return s;
 }
 
-int luax_getrandom(lua_State *L, int startidx, double r)
-{
-	int l, u;
-	// from lua 5.1.4 source code: lmathlib.c:185 ff.
-	switch (lua_gettop(L) - (startidx - 1))
-	{
-	case 0:
-		lua_pushnumber(L, r);
-		break;
-	case 1:
-		u = (int) luaL_checknumber(L, startidx);
-		luaL_argcheck(L, 1 <= u, startidx, "interval is empty");
-		lua_pushnumber(L, floor(r * u) + 1);
-		break;
-	case 2:
-		l = (int) luaL_checknumber(L, startidx);
-		u = (int) luaL_checknumber(L, startidx + 1);
-		luaL_argcheck(L, l <= u, startidx + 1, "interval is empty");
-		lua_pushnumber(L, floor(r * (u - l + 1)) + l);
-		break;
-	default:
-		return luaL_error(L, "wrong number of arguments");
-	}
-	return 1;
-}
-
 RandomGenerator *luax_checkrandomgenerator(lua_State *L, int idx)
 {
 	return luax_checktype<RandomGenerator>(L, idx, MATH_RANDOM_GENERATOR_ID);
 }
 
-int w_RandomGenerator_random(lua_State *L)
+int w_RandomGenerator__random(lua_State *L)
 {
 	RandomGenerator *rng = luax_checkrandomgenerator(L, 1);
-	return luax_getrandom(L, 2, rng->random());
+	lua_pushnumber(L, rng->random());
+	return 1;
 }
 
 int w_RandomGenerator_randomNormal(lua_State *L)
@@ -135,9 +115,28 @@ int w_RandomGenerator_getState(lua_State *L)
 	return 1;
 }
 
+// C functions in a struct, necessary for the FFI versions of RandomGenerator functions.
+struct FFI_RandomGenerator
+{
+	double (*random)(Proxy *p);
+};
+
+static FFI_RandomGenerator ffifuncs =
+{
+	[](Proxy *p) -> double // random()
+	{
+		// FIXME: We need better type-checking...
+		if (!typeFlags[p->type][MATH_RANDOM_GENERATOR_ID])
+			return 0.0;
+
+		RandomGenerator *rng = (RandomGenerator *) p->object;
+		return rng->random();
+	}
+};
+
 static const luaL_Reg w_RandomGenerator_functions[] =
 {
-	{ "random", w_RandomGenerator_random },
+	{ "_random", w_RandomGenerator__random }, // random() is defined in wrap_RandomGenerator.lua.
 	{ "randomNormal", w_RandomGenerator_randomNormal },
 	{ "setSeed", w_RandomGenerator_setSeed },
 	{ "getSeed", w_RandomGenerator_getSeed },
@@ -148,7 +147,24 @@ static const luaL_Reg w_RandomGenerator_functions[] =
 
 extern "C" int luaopen_randomgenerator(lua_State *L)
 {
-	return luax_register_type(L, MATH_RANDOM_GENERATOR_ID, "RandomGenerator", w_RandomGenerator_functions, nullptr);
+	int n = luax_register_type(L, MATH_RANDOM_GENERATOR_ID, "RandomGenerator", w_RandomGenerator_functions, nullptr);
+
+	luax_gettypemetatable(L, MATH_RANDOM_GENERATOR_ID);
+
+	// Load and execute wrap_RandomGenerator.lua, sending the metatable and the
+	// ffi functions struct pointer as arguments.
+	if (lua_istable(L, -1))
+	{
+		luaL_loadbuffer(L, randomgenerator_lua, sizeof(randomgenerator_lua), "wrap_RandomGenerator.lua");
+		lua_pushvalue(L, -2);
+		lua_pushlightuserdata(L, &ffifuncs);
+		lua_call(L, 2, 0);
+	}
+
+	// Pop the metatable.
+	lua_pop(L, 1);
+
+	return n;
 }
 
 } // math

+ 0 - 1
jni/love/src/modules/math/wrap_RandomGenerator.h

@@ -33,7 +33,6 @@ namespace math
 
 // Helper functions.
 RandomGenerator::Seed luax_checkrandomseed(lua_State *L, int idx);
-int luax_getrandom(lua_State *L, int startidx, double r);
 
 RandomGenerator *luax_checkrandomgenerator(lua_State *L, int idx);
 extern "C" int luaopen_randomgenerator(lua_State *L);

+ 80 - 0
jni/love/src/modules/math/wrap_RandomGenerator.lua

@@ -0,0 +1,80 @@
+R"luastring"--(
+-- DO NOT REMOVE THE ABOVE LINE. It is used to load this file as a C++ string.
+-- There is a matching delimiter at the bottom of the file.
+
+--[[
+Copyright (c) 2006-2015 LOVE Development Team
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+claim that you wrote the original software. If you use this software
+in a product, an acknowledgment in the product documentation would be
+appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+--]]
+
+local RandomGenerator_mt, ffifuncspointer = ...
+local RandomGenerator = RandomGenerator_mt.__index
+
+local type, tonumber, error = type, tonumber, error
+local floor = math.floor
+
+local _random = RandomGenerator._random
+
+local function getrandom(r, l, u)
+	if u ~= nil then
+		if type(r) ~= "number" then error("bad argument #1 to 'random' (number expected)", 2) end
+		if type(l) ~= "number" then error("bad argument #2 to 'random' (number expected)", 2) end
+		return floor(r * (u - l + 1)) + l
+	elseif l ~= nil then
+		if type(l) ~= "number" then error("bad argument #1 to 'random' (number expected)", 2) end
+		return floor(r * l) + 1
+	else
+		return r
+	end
+end
+
+function RandomGenerator:random(l, u)
+	local r = _random(self)
+	return getrandom(r, l, u)
+end
+
+if type(jit) ~= "table" or not jit.status() then
+	-- LuaJIT's FFI is *much* slower than LOVE's regular methods when the JIT
+	-- compiler is disabled.
+	return
+end
+
+local status, ffi = pcall(require, "ffi")
+if not status then return end
+
+pcall(ffi.cdef, [[
+typedef struct Proxy Proxy;
+
+typedef struct FFI_RandomGenerator
+{
+	double (*random)(Proxy *p);
+} FFI_RandomGenerator;
+]])
+
+local ffifuncs = ffi.cast("FFI_RandomGenerator *", ffifuncspointer)
+
+
+-- Overwrite some regular love.math functions with FFI implementations.
+
+function RandomGenerator:random(l, u)
+	local r = tonumber(ffifuncs.random(self))
+	return getrandom(r, l, u)
+end
+
+-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string.
+--)luastring"--"

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

@@ -30,7 +30,7 @@
 #include "Decoder.h"
 
 // libmodplug
-#ifdef LOVE_ANDROID
+#if defined(LOVE_ANDROID) || defined(LOVE_IOS)
 #include <modplug.h>
 #else
 #include <libmodplug/modplug.h>

+ 113 - 122
jni/love/src/modules/window/sdl/Window.cpp

@@ -90,7 +90,7 @@ void Window::setGLFramebufferAttributes(int msaa, bool sRGB)
 	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
 	SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
 	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
-	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1);
+	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
 	SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 0);
 
 	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, (msaa > 0) ? 1 : 0);
@@ -122,9 +122,9 @@ void Window::setGLContextAttributes(const ContextAttribs &attribs)
 	int contextflags = 0;
 
 	if (attribs.gles)
-		profilemask |= SDL_GL_CONTEXT_PROFILE_ES;
+		profilemask = SDL_GL_CONTEXT_PROFILE_ES;
 	else if (attribs.debug)
-		profilemask |= SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
+		profilemask = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
 
 	if (attribs.debug)
 		contextflags |= SDL_GL_CONTEXT_DEBUG_FLAG;
@@ -237,21 +237,24 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla
 	if (preferGLES)
 		std::rotate(attribslist.begin(), attribslist.begin() + 1, attribslist.end());
 
-	if (context)
-	{
-		SDL_GL_DeleteContext(context);
-		context = nullptr;
-	}
-
 	std::string windowerror;
+	std::string contexterror;
 	std::string glversion;
 
-	// Try each context profile in order.
-	for (ContextAttribs attribs : attribslist)
+	// Unfortunately some OpenGL context settings are part of the internal
+	// window state in the Windows and Linux SDL backends, so we have to
+	// recreate the window when we want to change those settings...
+	// Also, apparently some Intel drivers on Windows give back a Microsoft
+	// OpenGL 1.1 software renderer context when high MSAA values are requested!
+
+	const auto create = [&](ContextAttribs attribs) -> bool
 	{
-		// Unfortunately some OpenGL context settings are part of the internal
-		// window state in the Windows and Linux SDL backends, so we have to
-		// recreate the window when we want to change those settings...
+		if (context)
+		{
+			SDL_GL_DeleteContext(context);
+			context = nullptr;
+		}
+
 		if (window)
 		{
 			SDL_DestroyWindow(window);
@@ -259,20 +262,55 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla
 			window = nullptr;
 		}
 
+		window = SDL_CreateWindow(title.c_str(), x, y, w, h, windowflags);
+
+		if (!window)
+		{
+			windowerror = std::string(SDL_GetError());
+			return false;
+		}
+
+		context = SDL_GL_CreateContext(window);
+
+		if (!context)
+			contexterror = std::string(SDL_GetError());
+
+		// Make sure the context's version is at least what we requested.
+		if (context && !checkGLVersion(attribs, glversion))
+		{
+			SDL_GL_DeleteContext(context);
+			context = nullptr;
+		}
+
+		if (!context)
+		{
+			SDL_DestroyWindow(window);
+			window = nullptr;
+			return false;
+		}
+
+		return true;
+	};
+
+	// Try each context profile in order.
+	for (ContextAttribs attribs : attribslist)
+	{
 		int curMSAA  = msaa;
 		bool curSRGB = love::graphics::isGammaCorrect();
 
 		setGLFramebufferAttributes(curMSAA, curSRGB);
 		setGLContextAttributes(attribs);
 
-		window = SDL_CreateWindow(title.c_str(), x, y, w, h, windowflags);
+		windowerror.clear();
+		contexterror.clear();
+
+		create(attribs);
 
 		if (!window && curMSAA > 0)
 		{
 			// The MSAA setting could have caused the failure.
 			setGLFramebufferAttributes(0, curSRGB);
-			window = SDL_CreateWindow(title.c_str(), x, y, w, h, windowflags);
-			if (window)
+			if (create(attribs))
 				curMSAA = 0;
 		}
 
@@ -280,8 +318,7 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla
 		{
 			// same with sRGB.
 			setGLFramebufferAttributes(curMSAA, false);
-			window = SDL_CreateWindow(title.c_str(), x, y, w, h, windowflags);
-			if (window)
+			if (create(attribs))
 				curSRGB = false;
 		}
 
@@ -289,60 +326,14 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla
 		{
 			// Or both!
 			setGLFramebufferAttributes(0, false);
-			window = SDL_CreateWindow(title.c_str(), x, y, w, h, windowflags);
-			if (window)
+			if (create(attribs))
 			{
 				curMSAA = 0;
 				curSRGB = false;
 			}
 		}
 
-		// Immediately try the next context profile if window creation failed.
-		if (!window)
-		{
-			windowerror = std::string(SDL_GetError());
-			continue;
-		}
-
-		windowerror.clear();
-
-		context = SDL_GL_CreateContext(window);
-
-		if (!context && curMSAA > 0)
-		{
-			// MSAA and sRGB settings can also cause CreateContext to fail, on
-			// certain SDL backends.
-			setGLFramebufferAttributes(0, curSRGB);
-			context = SDL_GL_CreateContext(window);
-		}
-
-		if (!context && curSRGB)
-		{
-			setGLFramebufferAttributes(curMSAA, false);
-			context = SDL_GL_CreateContext(window);
-		}
-
-		if (!context && curMSAA > 0 && curSRGB)
-		{
-			setGLFramebufferAttributes(0, false);
-			context = SDL_GL_CreateContext(window);
-		}
-
-		if (!context && attribs.debug)
-		{
-			attribs.debug = false;
-			setGLContextAttributes(attribs);
-			context = SDL_GL_CreateContext(window);
-		}
-
-		// Make sure the context's version is at least what we requested.
-		if (context && !checkGLVersion(attribs, glversion))
-		{
-			SDL_GL_DeleteContext(context);
-			context = nullptr;
-		}
-
-		if (context)
+		if (window && context)
 		{
 			love::graphics::setGammaCorrect(curSRGB);
 			break;
@@ -370,6 +361,8 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla
 			std::string title = "Unable to initialize OpenGL";
 			std::string message = "This program requires a graphics card and video drivers which support OpenGL 2.1 or OpenGL ES 2.";
 
+			if (!contexterror.empty())
+				message += "\n\nOpenGL context creation error: " + contexterror;
 			if (!glversion.empty())
 				message += " \n\nDetected OpenGL version: " + glversion;
 
@@ -478,7 +471,7 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 		return false;
 
 	// Make sure the window keeps any previously set icon.
-	setIcon(curMode.icon.get());
+	setIcon(icon.get());
 
 	// Make sure the mouse keeps its previous grab setting.
 	setMouseGrab(mouseGrabbed);
@@ -497,7 +490,7 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 
 	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
 	if (gfx != nullptr)
-		gfx->setMode(curMode.pixelwidth, curMode.pixelheight);
+		gfx->setMode(pixelWidth, pixelHeight);
 
 #ifdef LOVE_ANDROID
 		love::android::setImmersive(f.fullscreen);
@@ -511,14 +504,14 @@ bool Window::onSizeChanged(int width, int height)
 	if (!window)
 		return false;
 
-	curMode.width = width;
-	curMode.height = height;
+	windowWidth = width;
+	windowHeight = height;
 
-	SDL_GL_GetDrawableSize(window, &curMode.pixelwidth, &curMode.pixelheight);
+	SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight);
 
 	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
 	if (gfx != nullptr)
-		gfx->setViewportSize(curMode.pixelwidth, curMode.pixelheight);
+		gfx->setViewportSize(pixelWidth, pixelHeight);
 
 	return true;
 }
@@ -528,49 +521,43 @@ void Window::updateSettings(const WindowSettings &newsettings)
 	Uint32 wflags = SDL_GetWindowFlags(window);
 
 	// Set the new display mode as the current display mode.
-	SDL_GetWindowSize(window, &curMode.width, &curMode.height);
-	SDL_GL_GetDrawableSize(window, &curMode.pixelwidth, &curMode.pixelheight);
+	SDL_GetWindowSize(window, &windowWidth, &windowHeight);
+	SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight);
 
 	if ((wflags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP)
 	{
-		curMode.settings.fullscreen = true;
-		curMode.settings.fstype = FULLSCREEN_DESKTOP;
+		settings.fullscreen = true;
+		settings.fstype = FULLSCREEN_DESKTOP;
 	}
 	else if ((wflags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN)
 	{
-		curMode.settings.fullscreen = true;
-		curMode.settings.fstype = FULLSCREEN_EXCLUSIVE;
+		settings.fullscreen = true;
+		settings.fstype = FULLSCREEN_EXCLUSIVE;
 	}
 	else
 	{
-		curMode.settings.fullscreen = false;
-		curMode.settings.fstype = newsettings.fstype;
+		settings.fullscreen = false;
+		settings.fstype = newsettings.fstype;
 	}
 
 #ifdef LOVE_ANDROID
-	curMode.settings.fullscreen = love::android::getImmersive();
+	settings.fullscreen = love::android::getImmersive();
 #endif
 
-	// The min width/height is set to 0 internally in SDL when in fullscreen.
-	if (curMode.settings.fullscreen)
-	{
-		curMode.settings.minwidth = newsettings.minwidth;
-		curMode.settings.minheight = newsettings.minheight;
-	}
-	else
-		SDL_GetWindowMinimumSize(window, &curMode.settings.minwidth, &curMode.settings.minheight);
+	// SDL_GetWindowMinimumSize gives back 0,0 sometimes...
+	settings.minwidth = newsettings.minwidth;
+	settings.minheight = newsettings.minheight;
 
-	curMode.settings.resizable = (wflags & SDL_WINDOW_RESIZABLE) != 0;
-	curMode.settings.borderless = (wflags & SDL_WINDOW_BORDERLESS) != 0;
-	curMode.settings.centered = newsettings.centered;
+	settings.resizable = (wflags & SDL_WINDOW_RESIZABLE) != 0;
+	settings.borderless = (wflags & SDL_WINDOW_BORDERLESS) != 0;
+	settings.centered = newsettings.centered;
 
-	getPosition(curMode.settings.x, curMode.settings.y, curMode.settings.display);
+	getPosition(settings.x, settings.y, settings.display);
 
-	curMode.settings.highdpi = (wflags & SDL_WINDOW_ALLOW_HIGHDPI) != 0;
+	settings.highdpi = (wflags & SDL_WINDOW_ALLOW_HIGHDPI) != 0;
 
-	// Only minimize on focus loss if the window is in exclusive-fullscreen
-	// mode.
-	if (curMode.settings.fullscreen && curMode.settings.fstype == FULLSCREEN_EXCLUSIVE)
+	// Only minimize on focus loss if the window is in exclusive-fullscreen mode
+	if (settings.fullscreen && settings.fstype == FULLSCREEN_EXCLUSIVE)
 		SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "1");
 	else
 		SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
@@ -581,25 +568,25 @@ void Window::updateSettings(const WindowSettings &newsettings)
 	SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &buffers);
 	SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &samples);
 
-	curMode.settings.msaa = (buffers > 0 ? samples : 0);
-	curMode.settings.vsync = SDL_GL_GetSwapInterval() != 0;
+	settings.msaa = (buffers > 0 ? samples : 0);
+	settings.vsync = SDL_GL_GetSwapInterval() != 0;
 
 	SDL_DisplayMode dmode = {};
-	SDL_GetCurrentDisplayMode(curMode.settings.display, &dmode);
+	SDL_GetCurrentDisplayMode(settings.display, &dmode);
 
 	// May be 0 if the refresh rate can't be determined.
-	curMode.settings.refreshrate = (double) dmode.refresh_rate;
+	settings.refreshrate = (double) dmode.refresh_rate;
 }
 
-void Window::getWindow(int &width, int &height, WindowSettings &settings)
+void Window::getWindow(int &width, int &height, WindowSettings &newsettings)
 {
 	// The window might have been modified (moved, resized, etc.) by the user.
 	if (window)
-		updateSettings(curMode.settings);
+		updateSettings(settings);
 
-	width = curMode.width;
-	height = curMode.height;
-	settings = curMode.settings;
+	width = windowWidth;
+	height = windowHeight;
+	newsettings = settings;
 }
 
 void Window::close()
@@ -632,7 +619,7 @@ bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype)
 	if (!window)
 		return false;
 
-	WindowSettings newsettings = curMode.settings;
+	WindowSettings newsettings = settings;
 	newsettings.fullscreen = fullscreen;
 	newsettings.fstype = fstype;
 
@@ -647,8 +634,8 @@ bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype)
 			sdlflags = SDL_WINDOW_FULLSCREEN;
 
 			SDL_DisplayMode mode = {};
-			mode.w = curMode.width;
-			mode.h = curMode.height;
+			mode.w = windowWidth;
+			mode.h = windowHeight;
 
 			SDL_GetClosestDisplayMode(SDL_GetWindowDisplayIndex(window), &mode, &mode);
 			SDL_SetWindowDisplayMode(window, &mode);
@@ -664,10 +651,14 @@ bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype)
 		SDL_GL_MakeCurrent(window, context);
 		updateSettings(newsettings);
 
+		// Apparently this gets un-set when we exit fullscreen (at least in OS X).
+		if (!fullscreen)
+			SDL_SetWindowMinimumSize(window, settings.minwidth, settings.minheight);
+
 		// Update the viewport size now instead of waiting for event polling.
 		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
 		if (gfx != nullptr)
-			gfx->setViewportSize(curMode.pixelwidth, curMode.pixelheight);
+			gfx->setViewportSize(pixelWidth, pixelHeight);
 
 		return true;
 	}
@@ -677,7 +668,7 @@ bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype)
 
 bool Window::setFullscreen(bool fullscreen)
 {
-	return setFullscreen(fullscreen, curMode.settings.fstype);
+	return setFullscreen(fullscreen, settings.fstype);
 }
 
 int Window::getDisplayCount() const
@@ -749,7 +740,7 @@ void Window::setPosition(int x, int y, int displayindex)
 
 	SDL_SetWindowPosition(window, x, y);
 
-	curMode.settings.useposition = true;
+	settings.useposition = true;
 }
 
 void Window::getPosition(int &x, int &y, int &displayindex)
@@ -801,7 +792,7 @@ bool Window::setIcon(love::image::ImageData *imgd)
 	if (!imgd)
 		return false;
 
-	curMode.icon.set(imgd);
+	icon.set(imgd);
 
 	if (!window)
 		return false;
@@ -842,7 +833,7 @@ bool Window::setIcon(love::image::ImageData *imgd)
 
 love::image::ImageData *Window::getIcon()
 {
-	return curMode.icon.get();
+	return icon.get();
 }
 
 void Window::setDisplaySleepEnabled(bool enable)
@@ -917,24 +908,24 @@ bool Window::isMouseGrabbed() const
 
 void Window::getPixelDimensions(int &w, int &h) const
 {
-	w = curMode.pixelwidth;
-	h = curMode.pixelheight;
+	w = pixelWidth;
+	h = pixelHeight;
 }
 
 void Window::windowToPixelCoords(double *x, double *y) const
 {
 	if (x != nullptr)
-		*x = (*x) * ((double) curMode.pixelwidth / (double) curMode.width);
+		*x = (*x) * ((double) pixelWidth / (double) windowWidth);
 	if (y != nullptr)
-		*y = (*y) * ((double) curMode.pixelheight / (double) curMode.height);
+		*y = (*y) * ((double) pixelHeight / (double) windowHeight);
 }
 
 void Window::pixelToWindowCoords(double *x, double *y) const
 {
 	if (x != nullptr)
-		*x = (*x) * ((double) curMode.width / (double) curMode.pixelwidth);
+		*x = (*x) * ((double) windowWidth / (double) pixelWidth);
 	if (y != nullptr)
-		*y = (*y) * ((double) curMode.height / (double) curMode.pixelheight);
+		*y = (*y) * ((double) windowHeight / (double) pixelHeight);
 }
 
 double Window::getPixelScale() const
@@ -942,7 +933,7 @@ double Window::getPixelScale() const
 #ifdef LOVE_ANDROID
 	return love::android::getScreenScale();
 #else
-	return (double) curMode.pixelheight / (double) curMode.height;
+	return (double) pixelHeight / (double) windowHeight;
 #endif
 }
 

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

@@ -131,16 +131,12 @@ private:
 
 	std::string title;
 
-	struct _currentMode
-	{
-		int width  = 800;
-		int height = 600;
-		int pixelwidth = 800;
-		int pixelheight = 600;
-		WindowSettings settings;
-		StrongRef<love::image::ImageData> icon;
-
-	} curMode;
+	int windowWidth  = 800;
+	int windowHeight = 600;
+	int pixelWidth   = 800;
+	int pixelHeight  = 600;
+	WindowSettings settings;
+	StrongRef<love::image::ImageData> icon;
 
 	bool open;
 

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

@@ -386,13 +386,13 @@ function love.init()
 	end
 
 	-- If config file exists, load it and allow it to update config table.
-	if not love.conf and love.filesystem and love.filesystem.isFile("conf.lua") then
-		require("conf")
+	local confok, conferr
+	if (not love.conf) and love.filesystem and love.filesystem.isFile("conf.lua") then
+		confok, conferr = pcall(require, "conf")
 	end
 
 	-- Yes, conf.lua might not exist, but there are other ways of making
 	-- love.conf appear, so we should check for it anyway.
-	local confok, conferr
 	if love.conf then
 		confok, conferr = pcall(love.conf, c)
 		-- If love.conf errors, we'll trigger the error after loading modules so
@@ -635,7 +635,7 @@ function love.errhand(msg)
 				return
 			elseif e == "keypressed" and a == "escape" then
 				return
-			elseif e == "touchreleased" then
+			elseif e == "touchpressed" then
 				local name = love.window.getTitle()
 				if #name == 0 or name == "Untitled" then name = "Game" end
 				local buttons = {"OK", "Cancel"}

+ 11 - 9
jni/love/src/scripts/boot.lua.h

@@ -708,12 +708,16 @@ const unsigned char boot_lua[] =
 	0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x2c, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x69, 0x74, 0x20, 0x61, 
 	0x6e, 0x64, 0x20, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x20, 0x69, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x75, 0x70, 0x64, 
 	0x61, 0x74, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x20, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x0a,
-	0x09, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x20, 
-	0x61, 0x6e, 0x64, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 
-	0x6d, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 
-	0x74, 0x65, 0x6d, 0x2e, 0x69, 0x73, 0x46, 0x69, 0x6c, 0x65, 0x28, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x2e, 0x6c, 
-	0x75, 0x61, 0x22, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
-	0x09, 0x09, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x28, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x22, 0x29, 0x0a,
+	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x6f, 0x6b, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 
+	0x66, 0x65, 0x72, 0x72, 0x0a,
+	0x09, 0x69, 0x66, 0x20, 0x28, 0x6e, 0x6f, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 
+	0x29, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 
+	0x74, 0x65, 0x6d, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 
+	0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x69, 0x73, 0x46, 0x69, 0x6c, 0x65, 0x28, 0x22, 0x63, 0x6f, 0x6e, 0x66, 
+	0x2e, 0x6c, 0x75, 0x61, 0x22, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
+	0x09, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x6f, 0x6b, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x65, 0x72, 0x72, 0x20, 
+	0x3d, 0x20, 0x70, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x2c, 0x20, 0x22, 
+	0x63, 0x6f, 0x6e, 0x66, 0x22, 0x29, 0x0a,
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x2d, 0x2d, 0x20, 0x59, 0x65, 0x73, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x2e, 0x6c, 0x75, 0x61, 0x20, 
 	0x6d, 0x69, 0x67, 0x68, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x2c, 0x20, 0x62, 
@@ -723,8 +727,6 @@ const unsigned char boot_lua[] =
 	0x61, 0x72, 0x2c, 0x20, 0x73, 0x6f, 0x20, 0x77, 0x65, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x63, 
 	0x68, 0x65, 0x63, 0x6b, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x69, 0x74, 0x20, 0x61, 0x6e, 0x79, 0x77, 0x61, 0x79, 
 	0x2e, 0x0a,
-	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x6f, 0x6b, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 
-	0x66, 0x65, 0x72, 0x72, 0x0a,
 	0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
 	0x09, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x6f, 0x6b, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x65, 0x72, 0x72, 0x20, 
 	0x3d, 0x20, 0x70, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x2c, 
@@ -1154,7 +1156,7 @@ const unsigned char boot_lua[] =
 	0x20, 0x22, 0x65, 0x73, 0x63, 0x61, 0x70, 0x65, 0x22, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
 	0x09, 0x09, 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x0a,
 	0x09, 0x09, 0x09, 0x65, 0x6c, 0x73, 0x65, 0x69, 0x66, 0x20, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x22, 0x74, 0x6f, 
-	0x75, 0x63, 0x68, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x64, 0x22, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
+	0x75, 0x63, 0x68, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x22, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
 	0x09, 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x3d, 0x20, 0x6c, 
 	0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x67, 0x65, 0x74, 0x54, 0x69, 0x74, 0x6c, 
 	0x65, 0x28, 0x29, 0x0a,

+ 7 - 1
jni/love/src/scripts/nogame.lua

@@ -887,7 +887,7 @@ function love.nogame()
 	function Mosaic:addGeneration()
 		self.generation = self.generation + 1
 		if self.generation % 5 == 0 then
-			if love.math.random(0, 100) < 30 then
+			if love.math.random(0, 100) < 60 then
 				self.generator = self.generators[love.math.random(2, #self.generators)]
 			else
 				self.generator = self.generators[1]
@@ -975,6 +975,12 @@ function love.nogame()
 		g_entities.toast:center()
 	end
 
+	function love.keypressed(key)
+		if key == "escape" then
+			love.event.quit()
+		end
+	end
+
 	function love.keyreleased(key)
 		if key == "f" then
 			local is_fs = love.window.getFullscreen()

+ 9 - 1
jni/love/src/scripts/nogame.lua.h

@@ -3471,7 +3471,7 @@ const unsigned char nogame_lua[] =
 	0x09, 0x09, 0x69, 0x66, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 
 	0x6f, 0x6e, 0x20, 0x25, 0x20, 0x35, 0x20, 0x3d, 0x3d, 0x20, 0x30, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
 	0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x72, 0x61, 
-	0x6e, 0x64, 0x6f, 0x6d, 0x28, 0x30, 0x2c, 0x20, 0x31, 0x30, 0x30, 0x29, 0x20, 0x3c, 0x20, 0x33, 0x30, 0x20, 
+	0x6e, 0x64, 0x6f, 0x6d, 0x28, 0x30, 0x2c, 0x20, 0x31, 0x30, 0x30, 0x29, 0x20, 0x3c, 0x20, 0x36, 0x30, 0x20, 
 	0x74, 0x68, 0x65, 0x6e, 0x0a,
 	0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 
 	0x20, 0x3d, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x73, 
@@ -3649,6 +3649,14 @@ const unsigned char nogame_lua[] =
 	0x3a, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x28, 0x29, 0x0a,
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6b, 0x65, 0x79, 
+	0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x28, 0x6b, 0x65, 0x79, 0x29, 0x0a,
+	0x09, 0x09, 0x69, 0x66, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x3d, 0x3d, 0x20, 0x22, 0x65, 0x73, 0x63, 0x61, 0x70, 
+	0x65, 0x22, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
+	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x71, 0x75, 0x69, 0x74, 
+	0x28, 0x29, 0x0a,
+	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
+	0x09, 0x65, 0x6e, 0x64, 0x0a,
+	0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6b, 0x65, 0x79, 
 	0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x64, 0x28, 0x6b, 0x65, 0x79, 0x29, 0x0a,
 	0x09, 0x09, 0x69, 0x66, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x3d, 0x3d, 0x20, 0x22, 0x66, 0x22, 0x20, 0x74, 0x68, 
 	0x65, 0x6e, 0x0a,