瀏覽代碼

Updated LÖVE source to revision cff5bc8604bd

Martin Felis 10 年之前
父節點
當前提交
66685a2ade
共有 100 個文件被更改,包括 3728 次插入2867 次删除
  1. 1 0
      jni/love/src/common/Module.h
  2. 10 0
      jni/love/src/common/Object.h
  3. 64 0
      jni/love/src/common/Stream.h
  4. 3 5
      jni/love/src/common/Variant.cpp
  5. 1 1
      jni/love/src/common/Variant.h
  6. 2 0
      jni/love/src/common/config.h
  7. 23 15
      jni/love/src/common/runtime.cpp
  8. 12 7
      jni/love/src/common/runtime.h
  9. 15 91
      jni/love/src/common/types.cpp
  10. 8 2
      jni/love/src/common/types.h
  11. 1 1
      jni/love/src/common/version.h
  12. 1 1
      jni/love/src/common/wrap_Data.cpp
  13. 1 3
      jni/love/src/common/wrap_Data.h
  14. 6 3
      jni/love/src/libraries/Box2D/Collision/Shapes/b2ChainShape.cpp
  15. 3 0
      jni/love/src/libraries/Box2D/Collision/Shapes/b2ChainShape.h
  16. 9 1
      jni/love/src/libraries/Box2D/Collision/Shapes/b2PolygonShape.cpp
  17. 3 3
      jni/love/src/libraries/Box2D/Common/b2Draw.h
  18. 0 1
      jni/love/src/libraries/Box2D/Common/b2Math.h
  19. 1 1
      jni/love/src/libraries/Box2D/Common/b2Settings.cpp
  20. 23 18
      jni/love/src/libraries/Box2D/Dynamics/Contacts/b2ContactSolver.cpp
  21. 3 0
      jni/love/src/libraries/Box2D/Dynamics/Joints/b2PrismaticJoint.cpp
  22. 17 1
      jni/love/src/libraries/Box2D/Dynamics/Joints/b2WeldJoint.cpp
  23. 2 3
      jni/love/src/libraries/Box2D/Dynamics/Joints/b2WheelJoint.h
  24. 19 19
      jni/love/src/libraries/Box2D/Dynamics/b2World.cpp
  25. 1 1
      jni/love/src/libraries/Box2D/Dynamics/b2World.h
  26. 169 99
      jni/love/src/libraries/stb/stb_image.h
  27. 9 4
      jni/love/src/love.cpp
  28. 2 1
      jni/love/src/modules/audio/Source.h
  29. 5 0
      jni/love/src/modules/audio/null/Source.cpp
  30. 1 0
      jni/love/src/modules/audio/null/Source.h
  31. 6 0
      jni/love/src/modules/audio/openal/Pool.cpp
  32. 1 0
      jni/love/src/modules/audio/openal/Pool.h
  33. 53 8
      jni/love/src/modules/audio/openal/Source.cpp
  34. 9 0
      jni/love/src/modules/audio/openal/Source.h
  35. 0 23
      jni/love/src/modules/audio/wrap_Audio.h
  36. 16 2
      jni/love/src/modules/audio/wrap_Source.cpp
  37. 0 35
      jni/love/src/modules/audio/wrap_Source.h
  38. 0 6
      jni/love/src/modules/event/sdl/Event.cpp
  39. 1 1
      jni/love/src/modules/event/wrap_Event.cpp
  40. 0 7
      jni/love/src/modules/event/wrap_Event.h
  41. 14 7
      jni/love/src/modules/filesystem/physfs/Filesystem.cpp
  42. 1 23
      jni/love/src/modules/filesystem/wrap_DroppedFile.cpp
  43. 2 2
      jni/love/src/modules/filesystem/wrap_File.cpp
  44. 2 16
      jni/love/src/modules/filesystem/wrap_File.h
  45. 1 6
      jni/love/src/modules/filesystem/wrap_FileData.cpp
  46. 0 2
      jni/love/src/modules/filesystem/wrap_FileData.h
  47. 14 5
      jni/love/src/modules/filesystem/wrap_Filesystem.cpp
  48. 2 37
      jni/love/src/modules/filesystem/wrap_Filesystem.h
  49. 4 4
      jni/love/src/modules/font/freetype/TrueTypeRasterizer.cpp
  50. 2 7
      jni/love/src/modules/font/wrap_GlyphData.cpp
  51. 0 9
      jni/love/src/modules/font/wrap_GlyphData.h
  52. 2 2
      jni/love/src/modules/font/wrap_Rasterizer.cpp
  53. 0 8
      jni/love/src/modules/font/wrap_Rasterizer.h
  54. 15 0
      jni/love/src/modules/graphics/Color.h
  55. 1 1
      jni/love/src/modules/graphics/Graphics.cpp
  56. 12 1
      jni/love/src/modules/graphics/Graphics.h
  57. 986 0
      jni/love/src/modules/graphics/ParticleSystem.cpp
  58. 672 0
      jni/love/src/modules/graphics/ParticleSystem.h
  59. 14 14
      jni/love/src/modules/graphics/Quad.cpp
  60. 6 6
      jni/love/src/modules/graphics/Quad.h
  61. 7 6
      jni/love/src/modules/graphics/Texture.cpp
  62. 1 0
      jni/love/src/modules/graphics/Texture.h
  63. 9 10
      jni/love/src/modules/graphics/opengl/Canvas.cpp
  64. 7 8
      jni/love/src/modules/graphics/opengl/Canvas.h
  65. 337 156
      jni/love/src/modules/graphics/opengl/Font.cpp
  66. 34 19
      jni/love/src/modules/graphics/opengl/Font.h
  67. 4 3
      jni/love/src/modules/graphics/opengl/GLBuffer.cpp
  68. 62 38
      jni/love/src/modules/graphics/opengl/Graphics.cpp
  69. 12 5
      jni/love/src/modules/graphics/opengl/Graphics.h
  70. 117 106
      jni/love/src/modules/graphics/opengl/Image.cpp
  71. 70 52
      jni/love/src/modules/graphics/opengl/Mesh.cpp
  72. 8 3
      jni/love/src/modules/graphics/opengl/Mesh.h
  73. 55 16
      jni/love/src/modules/graphics/opengl/OpenGL.cpp
  74. 8 2
      jni/love/src/modules/graphics/opengl/OpenGL.h
  75. 18 931
      jni/love/src/modules/graphics/opengl/ParticleSystem.cpp
  76. 7 631
      jni/love/src/modules/graphics/opengl/ParticleSystem.h
  77. 60 0
      jni/love/src/modules/graphics/opengl/Shader.cpp
  78. 8 0
      jni/love/src/modules/graphics/opengl/Shader.h
  79. 56 28
      jni/love/src/modules/graphics/opengl/SpriteBatch.cpp
  80. 17 0
      jni/love/src/modules/graphics/opengl/SpriteBatch.h
  81. 47 34
      jni/love/src/modules/graphics/opengl/Text.cpp
  82. 9 9
      jni/love/src/modules/graphics/opengl/Text.h
  83. 212 0
      jni/love/src/modules/graphics/opengl/Video.cpp
  84. 79 0
      jni/love/src/modules/graphics/opengl/Video.h
  85. 2 11
      jni/love/src/modules/graphics/opengl/wrap_Canvas.cpp
  86. 0 4
      jni/love/src/modules/graphics/opengl/wrap_Canvas.h
  87. 2 2
      jni/love/src/modules/graphics/opengl/wrap_Font.cpp
  88. 0 12
      jni/love/src/modules/graphics/opengl/wrap_Font.h
  89. 72 15
      jni/love/src/modules/graphics/opengl/wrap_Graphics.cpp
  90. 1 79
      jni/love/src/modules/graphics/opengl/wrap_Graphics.h
  91. 50 3
      jni/love/src/modules/graphics/opengl/wrap_Graphics.lua
  92. 2 11
      jni/love/src/modules/graphics/opengl/wrap_Image.cpp
  93. 0 6
      jni/love/src/modules/graphics/opengl/wrap_Image.h
  94. 29 7
      jni/love/src/modules/graphics/opengl/wrap_Mesh.cpp
  95. 0 19
      jni/love/src/modules/graphics/opengl/wrap_Mesh.h
  96. 2 2
      jni/love/src/modules/graphics/opengl/wrap_ParticleSystem.cpp
  97. 0 60
      jni/love/src/modules/graphics/opengl/wrap_ParticleSystem.h
  98. 60 56
      jni/love/src/modules/graphics/opengl/wrap_Shader.cpp
  99. 0 8
      jni/love/src/modules/graphics/opengl/wrap_Shader.h
  100. 13 2
      jni/love/src/modules/graphics/opengl/wrap_SpriteBatch.cpp

+ 1 - 0
jni/love/src/common/Module.h

@@ -53,6 +53,7 @@ public:
 		M_TIMER,
 		M_TIMER,
 		M_TOUCH,
 		M_TOUCH,
 		M_WINDOW,
 		M_WINDOW,
+		M_VIDEO,
 		M_MAX_ENUM
 		M_MAX_ENUM
 	};
 	};
 
 

+ 10 - 0
jni/love/src/common/Object.h

@@ -117,6 +117,16 @@ public:
 		return object;
 		return object;
 	}
 	}
 
 
+	operator bool() const
+	{
+		return object != nullptr;
+	}
+
+	operator T*() const
+	{
+		return object;
+	}
+
 	void set(T *obj)
 	void set(T *obj)
 	{
 	{
 		if (obj) obj->retain();
 		if (obj) obj->retain();

+ 64 - 0
jni/love/src/common/Stream.h

@@ -0,0 +1,64 @@
+/**
+ * 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.
+ **/
+
+#ifndef LOVE_STREAM_H
+#define LOVE_STREAM_H
+
+// LOVE
+#include <stddef.h>
+#include "Object.h"
+
+namespace love
+{
+
+class Stream : public Object
+{
+public:
+	virtual ~Stream() {}
+
+	// getData and getSize are assumed to talk about
+	// the buffer
+
+	/**
+	 * A callback, gets called when some Stream consumer exhausts the data
+	 **/
+	virtual void fillBackBuffer() {}
+
+	/**
+	 * Get the front buffer, Streams are supposed to be (at least) double-buffered
+	 **/
+	virtual const void *getFrontBuffer() const = 0;
+
+	/**
+	 * Get the size of any (and in particular the front) buffer
+	 **/
+	virtual size_t getSize() const = 0;
+
+	/**
+	 * Swap buffers. Returns true if there is new data in the front buffer,
+     * false otherwise.
+	 * NOTE: If there is no back buffer ready, this call must be ignored
+	 **/
+	virtual bool swapBuffers() = 0;
+}; // Stream
+
+} // love
+
+#endif // LOVE_STREAM_H

+ 3 - 5
jni/love/src/common/Variant.cpp

@@ -24,8 +24,6 @@
 namespace love
 namespace love
 {
 {
 
 
-extern StringMap<Type, TYPE_MAX_ENUM> types;
-
 static love::Type extractudatatype(lua_State *L, int idx)
 static love::Type extractudatatype(lua_State *L, int idx)
 {
 {
 	Type t = INVALID_ID;
 	Type t = INVALID_ID;
@@ -36,7 +34,7 @@ static love::Type extractudatatype(lua_State *L, int idx)
 	lua_pushvalue(L, idx);
 	lua_pushvalue(L, idx);
 	int result = lua_pcall(L, 1, 1, 0);
 	int result = lua_pcall(L, 1, 1, 0);
 	if (result == 0)
 	if (result == 0)
-		types.find(lua_tostring(L, -1), t);
+		getTypeName(lua_tostring(L, -1), t);
 	if (result == 0 || result == LUA_ERRRUN)
 	if (result == 0 || result == LUA_ERRRUN)
 		lua_pop(L, 1);
 		lua_pop(L, 1);
 	return t;
 	return t;
@@ -165,7 +163,7 @@ Variant *Variant::fromLua(lua_State *L, int n, bool allowTables)
 		if (allowTables)
 		if (allowTables)
 		{
 		{
 			bool success = true;
 			bool success = true;
-			std::vector<std::pair<Variant*, Variant*> > *table = new std::vector<std::pair<Variant*, Variant*> >();
+			std::vector<std::pair<Variant*, Variant*>> *table = new std::vector<std::pair<Variant*, Variant*>>();
 			lua_pushnil(L);
 			lua_pushnil(L);
 			while (lua_next(L, n))
 			while (lua_next(L, n))
 			{
 			{
@@ -230,7 +228,7 @@ void Variant::toLua(lua_State *L)
 		// I can do (at the moment).
 		// I can do (at the moment).
 		break;
 		break;
 	case TABLE:
 	case TABLE:
-		lua_newtable(L);
+		lua_createtable(L, 0, (int) data.table->size());
 		for (size_t i = 0; i < data.table->size(); ++i)
 		for (size_t i = 0; i < data.table->size(); ++i)
 		{
 		{
 			std::pair<Variant*, Variant*> &kv = data.table->at(i);
 			std::pair<Variant*, Variant*> &kv = data.table->at(i);

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

@@ -71,7 +71,7 @@ public:
 			size_t len;
 			size_t len;
 		} string;
 		} string;
 		void *userdata;
 		void *userdata;
-		std::vector<std::pair<Variant*, Variant*> > *table;
+		std::vector<std::pair<Variant*, Variant*>> *table;
 	} data;
 	} data;
 
 
 private:
 private:

+ 2 - 0
jni/love/src/common/config.h

@@ -141,6 +141,8 @@
 #	define LOVE_ENABLE_TOUCH
 #	define LOVE_ENABLE_TOUCH
 #	define LOVE_ENABLE_TOUCH_SDL
 #	define LOVE_ENABLE_TOUCH_SDL
 #	define LOVE_ENABLE_UTF8
 #	define LOVE_ENABLE_UTF8
+#	define LOVE_ENABLE_VIDEO
+#	define LOVE_ENABLE_VIDEO_THEORA
 #	define LOVE_ENABLE_WINDOW
 #	define LOVE_ENABLE_WINDOW
 #	define LOVE_ENABLE_WINDOW_SDL
 #	define LOVE_ENABLE_WINDOW_SDL
 #	define LOVE_ENABLE_WUFF
 #	define LOVE_ENABLE_WUFF

+ 23 - 15
jni/love/src/common/runtime.cpp

@@ -216,6 +216,8 @@ int luax_require(lua_State *L, const char *name)
 
 
 int luax_register_module(lua_State *L, const WrappedModule &m)
 int luax_register_module(lua_State *L, const WrappedModule &m)
 {
 {
+	love::addTypeName(m.type, m.name);
+
 	// Put a reference to the C++ module in Lua.
 	// Put a reference to the C++ module in Lua.
 	luax_insistregistry(L, REGISTRY_MODULES);
 	luax_insistregistry(L, REGISTRY_MODULES);
 
 
@@ -270,12 +272,9 @@ int luax_preload(lua_State *L, lua_CFunction f, const char *name)
 	return 0;
 	return 0;
 }
 }
 
 
-int luax_register_type(lua_State *L, love::Type type, const luaL_Reg *f, bool pushmetatable)
+int luax_register_type(lua_State *L, love::Type type, const char *name, ...)
 {
 {
-	// Verify that this type name has a matching Type ID and type name mapping.
-	const char *tname = "Invalid";
-	if (!love::getType(type, tname))
-		printf("Missing type name entry for type ID %d\n", type);
+	love::addTypeName(type, name);
 
 
 	// Get the place for storing and re-using instantiated love types.
 	// Get the place for storing and re-using instantiated love types.
 	luax_getregistry(L, REGISTRY_OBJECTS);
 	luax_getregistry(L, REGISTRY_OBJECTS);
@@ -302,7 +301,7 @@ int luax_register_type(lua_State *L, love::Type type, const luaL_Reg *f, bool pu
 	else
 	else
 		lua_pop(L, 1);
 		lua_pop(L, 1);
 
 
-	luaL_newmetatable(L, tname);
+	luaL_newmetatable(L, name);
 
 
 	// m.__index = m
 	// m.__index = m
 	lua_pushvalue(L, -1);
 	lua_pushvalue(L, -1);
@@ -317,12 +316,12 @@ int luax_register_type(lua_State *L, love::Type type, const luaL_Reg *f, bool pu
 	lua_setfield(L, -2, "__eq");
 	lua_setfield(L, -2, "__eq");
 
 
 	// Add tostring function.
 	// Add tostring function.
-	lua_pushstring(L, tname);
+	lua_pushstring(L, name);
 	lua_pushcclosure(L, w__tostring, 1);
 	lua_pushcclosure(L, w__tostring, 1);
 	lua_setfield(L, -2, "__tostring");
 	lua_setfield(L, -2, "__tostring");
 
 
 	// Add type
 	// Add type
-	lua_pushstring(L, tname);
+	lua_pushstring(L, name);
 	lua_pushcclosure(L, w__type, 1);
 	lua_pushcclosure(L, w__type, 1);
 	lua_setfield(L, -2, "type");
 	lua_setfield(L, -2, "type");
 
 
@@ -330,16 +329,25 @@ int luax_register_type(lua_State *L, love::Type type, const luaL_Reg *f, bool pu
 	lua_pushcfunction(L, w__typeOf);
 	lua_pushcfunction(L, w__typeOf);
 	lua_setfield(L, -2, "typeOf");
 	lua_setfield(L, -2, "typeOf");
 
 
-	if (f != nullptr)
+	va_list fs;
+	va_start(fs, name);
+	for (const luaL_Reg *f = va_arg(fs, const luaL_Reg *); f; f = va_arg(fs, const luaL_Reg *))
 		luax_setfuncs(L, f);
 		luax_setfuncs(L, f);
-
-	if (pushmetatable)
-		return 1; // leave the metatable on the stack.
+	va_end(fs);
 
 
 	lua_pop(L, 1); // Pops metatable.
 	lua_pop(L, 1); // Pops metatable.
 	return 0;
 	return 0;
 }
 }
 
 
+void luax_gettypemetatable(lua_State *L, love::Type type)
+{
+	const char *name = nullptr;
+	if (getTypeName(type, name))
+		lua_getfield(L, LUA_REGISTRYINDEX, name);
+	else
+		lua_pushnil(L);
+}
+
 int luax_table_insert(lua_State *L, int tindex, int vindex, int pos)
 int luax_table_insert(lua_State *L, int tindex, int vindex, int pos)
 {
 {
 	if (tindex < 0)
 	if (tindex < 0)
@@ -403,7 +411,7 @@ void luax_rawnewtype(lua_State *L, love::Type type, love::Object *object)
 	u->type = type;
 	u->type = type;
 
 
 	const char *name = "Invalid";
 	const char *name = "Invalid";
-	getType(type, name);
+	getTypeName(type, name);
 
 
 	luaL_newmetatable(L, name);
 	luaL_newmetatable(L, name);
 	lua_setmetatable(L, -2);
 	lua_setmetatable(L, -2);
@@ -670,7 +678,7 @@ extern "C" int luax_typerror(lua_State *L, int narg, const char *tname)
 			// Non-love userdata might have a type metamethod which doesn't
 			// Non-love userdata might have a type metamethod which doesn't
 			// describe its type properly, so we only use it for love types.
 			// 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::getTypeName(argtname, t))
 				argtname = 0;
 				argtname = 0;
 		}
 		}
 	}
 	}
@@ -707,7 +715,7 @@ void luax_register(lua_State *L, const char *name, const luaL_Reg *l)
 Type luax_type(lua_State *L, int idx)
 Type luax_type(lua_State *L, int idx)
 {
 {
 	Type t = INVALID_ID;
 	Type t = INVALID_ID;
-	getType(luaL_checkstring(L, idx), t);
+	getTypeName(luaL_checkstring(L, idx), t);
 	return t;
 	return t;
 }
 }
 
 

+ 12 - 7
jni/love/src/common/runtime.h

@@ -247,10 +247,15 @@ int luax_preload(lua_State *L, lua_CFunction f, const char *name);
 /**
 /**
  * Register a new type.
  * Register a new type.
  * @param type The type.
  * @param type The type.
- * @param f The list of member functions for the type.
- * @param pushmetatable Whether to push the type's metatable to the stack.
+ * @param name The type's human-readable name
+ * @param ... The list of lists of member functions for the type. (of type luaL_Reg*)
  **/
  **/
-int luax_register_type(lua_State *L, love::Type type, const luaL_Reg *f = nullptr, bool pushmetatable = false);
+int luax_register_type(lua_State *L, love::Type type, const char *name, ...);
+
+/**
+ * Pushes the metatable of the specified type onto the stack.
+**/
+void luax_gettypemetatable(lua_State *L, love::Type type);
 
 
 /**
 /**
  * Do a table.insert from C
  * Do a table.insert from C
@@ -434,7 +439,7 @@ T *luax_checktype(lua_State *L, int idx, love::Type type)
 	if (lua_type(L, idx) != LUA_TUSERDATA)
 	if (lua_type(L, idx) != LUA_TUSERDATA)
 	{
 	{
 		const char *name = "Invalid";
 		const char *name = "Invalid";
-		getType(type, name);
+		getTypeName(type, name);
 		luax_typerror(L, idx, name);
 		luax_typerror(L, idx, name);
 	}
 	}
 
 
@@ -443,7 +448,7 @@ T *luax_checktype(lua_State *L, int idx, love::Type type)
 	if (!typeFlags[u->type][type])
 	if (!typeFlags[u->type][type])
 	{
 	{
 		const char *name = "Invalid";
 		const char *name = "Invalid";
-		getType(type, name);
+		getTypeName(type, name);
 		luax_typerror(L, idx, name);
 		luax_typerror(L, idx, name);
 	}
 	}
 
 
@@ -454,7 +459,7 @@ template <typename T>
 T *luax_getmodule(lua_State *L, love::Type type)
 T *luax_getmodule(lua_State *L, love::Type type)
 {
 {
 	const char *name = "Invalid";
 	const char *name = "Invalid";
-	getType(type, name);
+	getTypeName(type, name);
 
 
 	luax_insistregistry(L, REGISTRY_MODULES);
 	luax_insistregistry(L, REGISTRY_MODULES);
 	lua_getfield(L, -1, name);
 	lua_getfield(L, -1, name);
@@ -476,7 +481,7 @@ template <typename T>
 T *luax_optmodule(lua_State *L, love::Type type)
 T *luax_optmodule(lua_State *L, love::Type type)
 {
 {
 	const char *name = "Invalid";
 	const char *name = "Invalid";
-	getType(type, name);
+	getTypeName(type, name);
 
 
 	luax_insistregistry(L, REGISTRY_MODULES);
 	luax_insistregistry(L, REGISTRY_MODULES);
 	lua_getfield(L, -1, name);
 	lua_getfield(L, -1, name);

+ 15 - 91
jni/love/src/common/types.cpp

@@ -34,6 +34,7 @@ static const TypeBits *createTypeFlags()
 	b[OBJECT_ID] = one << OBJECT_ID;
 	b[OBJECT_ID] = one << OBJECT_ID;
 	b[DATA_ID] = (one << DATA_ID) | b[OBJECT_ID];
 	b[DATA_ID] = (one << DATA_ID) | b[OBJECT_ID];
 	b[MODULE_ID] = (one << MODULE_ID) | b[OBJECT_ID];
 	b[MODULE_ID] = (one << MODULE_ID) | b[OBJECT_ID];
+	b[STREAM_ID] = (one << STREAM_ID) | b[OBJECT_ID];
 
 
 	// Filesystem.
 	// Filesystem.
 	b[FILESYSTEM_FILE_ID] = (one << FILESYSTEM_FILE_ID) | b[OBJECT_ID];
 	b[FILESYSTEM_FILE_ID] = (one << FILESYSTEM_FILE_ID) | b[OBJECT_ID];
@@ -55,6 +56,7 @@ static const TypeBits *createTypeFlags()
 	b[GRAPHICS_SHADER_ID] = (one << GRAPHICS_SHADER_ID) | b[OBJECT_ID];
 	b[GRAPHICS_SHADER_ID] = (one << GRAPHICS_SHADER_ID) | b[OBJECT_ID];
 	b[GRAPHICS_MESH_ID] = (one << GRAPHICS_MESH_ID) | b[GRAPHICS_DRAWABLE_ID];
 	b[GRAPHICS_MESH_ID] = (one << GRAPHICS_MESH_ID) | b[GRAPHICS_DRAWABLE_ID];
 	b[GRAPHICS_TEXT_ID] = (one << GRAPHICS_TEXT_ID) | b[GRAPHICS_DRAWABLE_ID];
 	b[GRAPHICS_TEXT_ID] = (one << GRAPHICS_TEXT_ID) | b[GRAPHICS_DRAWABLE_ID];
+	b[GRAPHICS_VIDEO_ID] = (one << GRAPHICS_VIDEO_ID) | b[GRAPHICS_DRAWABLE_ID];
 
 
 	// Image.
 	// Image.
 	b[IMAGE_IMAGE_DATA_ID] = (one << IMAGE_IMAGE_DATA_ID) | b[DATA_ID];
 	b[IMAGE_IMAGE_DATA_ID] = (one << IMAGE_IMAGE_DATA_ID) | b[DATA_ID];
@@ -105,6 +107,9 @@ static const TypeBits *createTypeFlags()
 	b[THREAD_THREAD_ID] = (one << THREAD_THREAD_ID) | b[OBJECT_ID];
 	b[THREAD_THREAD_ID] = (one << THREAD_THREAD_ID) | b[OBJECT_ID];
 	b[THREAD_CHANNEL_ID] = (one << THREAD_CHANNEL_ID) | b[OBJECT_ID];
 	b[THREAD_CHANNEL_ID] = (one << THREAD_CHANNEL_ID) | b[OBJECT_ID];
 
 
+	// Video
+	b[VIDEO_VIDEO_STREAM_ID] = (one << VIDEO_VIDEO_STREAM_ID) | b[STREAM_ID];
+
 	// Modules.
 	// Modules.
 	b[MODULE_FILESYSTEM_ID] = (one << MODULE_FILESYSTEM_ID) | b[MODULE_ID];
 	b[MODULE_FILESYSTEM_ID] = (one << MODULE_FILESYSTEM_ID) | b[MODULE_ID];
 	b[MODULE_GRAPHICS_ID] = (one << MODULE_GRAPHICS_ID) | b[MODULE_ID];
 	b[MODULE_GRAPHICS_ID] = (one << MODULE_GRAPHICS_ID) | b[MODULE_ID];
@@ -116,102 +121,21 @@ static const TypeBits *createTypeFlags()
 
 
 const TypeBits *typeFlags = createTypeFlags();
 const TypeBits *typeFlags = createTypeFlags();
 
 
-StringMap<Type, TYPE_MAX_ENUM>::Entry typeEntries[] =
+static StringMap<Type, TYPE_MAX_ENUM> types(nullptr, 0);
+
+void addTypeName(Type type, const char *name)
 {
 {
-	{"Invalid", INVALID_ID},
-
-	{"Object", OBJECT_ID},
-	{"Data", DATA_ID},
-	{"Module", MODULE_ID},
-
-	// Filesystem
-	{"File", FILESYSTEM_FILE_ID},
-	{"DroppedFile", FILESYSTEM_DROPPED_FILE_ID},
-	{"FileData", FILESYSTEM_FILE_DATA_ID},
-
-	// Font
-	{"GlyphData", FONT_GLYPH_DATA_ID},
-	{"Rasterizer", FONT_RASTERIZER_ID},
-
-	// Graphics
-	{"Drawable", GRAPHICS_DRAWABLE_ID},
-	{"Texture", GRAPHICS_TEXTURE_ID},
-	{"Image", GRAPHICS_IMAGE_ID},
-	{"Quad", GRAPHICS_QUAD_ID},
-	{"Font", GRAPHICS_FONT_ID},
-	{"ParticleSystem", GRAPHICS_PARTICLE_SYSTEM_ID},
-	{"SpriteBatch", GRAPHICS_SPRITE_BATCH_ID},
-	{"Canvas", GRAPHICS_CANVAS_ID},
-	{"Shader", GRAPHICS_SHADER_ID},
-	{"Mesh", GRAPHICS_MESH_ID},
-	{"Text", GRAPHICS_TEXT_ID},
-
-	// Image
-	{"ImageData", IMAGE_IMAGE_DATA_ID},
-	{"CompressedImageData", IMAGE_COMPRESSED_IMAGE_DATA_ID},
-
-	// Joystick
-	{"Joystick", JOYSTICK_JOYSTICK_ID},
-
-	// Math
-	{"RandomGenerator", MATH_RANDOM_GENERATOR_ID},
-	{"BezierCurve", MATH_BEZIER_CURVE_ID},
-	{"CompressedData", MATH_COMPRESSED_DATA_ID},
-
-	// Audio
-	{"Source", AUDIO_SOURCE_ID},
-
-	// Sound
-	{"SoundData", SOUND_SOUND_DATA_ID},
-	{"Decoder", SOUND_DECODER_ID},
-
-	// Mouse
-	{"Cursor", MOUSE_CURSOR_ID},
-
-	// Physics
-	{"World", PHYSICS_WORLD_ID},
-	{"Contact", PHYSICS_CONTACT_ID},
-	{"Body", PHYSICS_BODY_ID},
-	{"Fixture", PHYSICS_FIXTURE_ID},
-	{"Shape", PHYSICS_SHAPE_ID},
-	{"CircleShape", PHYSICS_CIRCLE_SHAPE_ID},
-	{"PolygonShape", PHYSICS_POLYGON_SHAPE_ID},
-	{"EdgeShape", PHYSICS_EDGE_SHAPE_ID},
-	{"ChainShape", PHYSICS_CHAIN_SHAPE_ID},
-	{"Joint", PHYSICS_JOINT_ID},
-	{"MouseJoint", PHYSICS_MOUSE_JOINT_ID},
-	{"DistanceJoint", PHYSICS_DISTANCE_JOINT_ID},
-	{"PrismaticJoint", PHYSICS_PRISMATIC_JOINT_ID},
-	{"RevoluteJoint", PHYSICS_REVOLUTE_JOINT_ID},
-	{"PulleyJoint", PHYSICS_PULLEY_JOINT_ID},
-	{"GearJoint", PHYSICS_GEAR_JOINT_ID},
-	{"FrictionJoint", PHYSICS_FRICTION_JOINT_ID},
-	{"WeldJoint", PHYSICS_WELD_JOINT_ID},
-	{"RopeJoint", PHYSICS_ROPE_JOINT_ID},
-	{"WheelJoint", PHYSICS_WHEEL_JOINT_ID},
-	{"MotorJoint", PHYSICS_MOTOR_JOINT_ID},
-
-	// Thread
-	{"Thread", THREAD_THREAD_ID},
-	{"Channel", THREAD_CHANNEL_ID},
-
-	// The modules themselves. Only add abstracted modules here.
-	{"filesystem", MODULE_FILESYSTEM_ID},
-	{"graphics", MODULE_GRAPHICS_ID},
-	{"image", MODULE_IMAGE_ID},
-	{"sound", MODULE_SOUND_ID},
-};
-
-StringMap<Type, TYPE_MAX_ENUM> types(typeEntries, sizeof(typeEntries));
-
-static_assert((sizeof(typeEntries) / sizeof(typeEntries[0])) == TYPE_MAX_ENUM, "Type name array size doesn't match the total number of type IDs!");
-
-bool getType(const char *in, love::Type &out)
+	const char *n;
+	if (!types.find(type, n))
+		types.add(name, type);
+}
+
+bool getTypeName(const char *in, love::Type &out)
 {
 {
 	return types.find(in, out);
 	return types.find(in, out);
 }
 }
 
 
-bool getType(love::Type in, const char *&out)
+bool getTypeName(love::Type in, const char *&out)
 {
 {
 	return types.find(in, out);
 	return types.find(in, out);
 }
 }

+ 8 - 2
jni/love/src/common/types.h

@@ -34,6 +34,7 @@ enum Type
 	OBJECT_ID,
 	OBJECT_ID,
 	DATA_ID,
 	DATA_ID,
 	MODULE_ID,
 	MODULE_ID,
+	STREAM_ID,
 
 
 	// Filesystem.
 	// Filesystem.
 	FILESYSTEM_FILE_ID,
 	FILESYSTEM_FILE_ID,
@@ -56,6 +57,7 @@ enum Type
 	GRAPHICS_SHADER_ID,
 	GRAPHICS_SHADER_ID,
 	GRAPHICS_MESH_ID,
 	GRAPHICS_MESH_ID,
 	GRAPHICS_TEXT_ID,
 	GRAPHICS_TEXT_ID,
+	GRAPHICS_VIDEO_ID,
 
 
 	// Image
 	// Image
 	IMAGE_IMAGE_DATA_ID,
 	IMAGE_IMAGE_DATA_ID,
@@ -106,6 +108,9 @@ enum Type
 	THREAD_THREAD_ID,
 	THREAD_THREAD_ID,
 	THREAD_CHANNEL_ID,
 	THREAD_CHANNEL_ID,
 
 
+	// Video
+	VIDEO_VIDEO_STREAM_ID,
+
 	// The modules themselves. Only add abstracted modules here.
 	// The modules themselves. Only add abstracted modules here.
 	MODULE_FILESYSTEM_ID,
 	MODULE_FILESYSTEM_ID,
 	MODULE_GRAPHICS_ID,
 	MODULE_GRAPHICS_ID,
@@ -123,8 +128,9 @@ typedef std::bitset<TYPE_MAX_ENUM> TypeBits;
  **/
  **/
 extern const TypeBits *typeFlags;
 extern const TypeBits *typeFlags;
 
 
-bool getType(const char *in, Type &out);
-bool getType(Type in, const char *&out);
+void addTypeName(Type type, const char *name);
+bool getTypeName(const char *in, Type &out);
+bool getTypeName(Type in, const char *&out);
 
 
 } // love
 } // love
 
 

+ 1 - 1
jni/love/src/common/version.h

@@ -31,7 +31,7 @@ static const int VERSION_MINOR = 10;
 static const int VERSION_REV = 0;
 static const int VERSION_REV = 0;
 static const char *VERSION = LOVE_VERSION_STRING;
 static const char *VERSION = LOVE_VERSION_STRING;
 static const char *VERSION_COMPATIBILITY[] =  { VERSION, 0 };
 static const char *VERSION_COMPATIBILITY[] =  { VERSION, 0 };
-static const char *VERSION_CODENAME = "";
+static const char *VERSION_CODENAME = "Super Toast";
 
 
 } // love
 } // love
 
 

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

@@ -59,7 +59,7 @@ const luaL_Reg w_Data_functions[] =
 
 
 int w_Data_open(lua_State *L)
 int w_Data_open(lua_State *L)
 {
 {
-	luax_register_type(L, DATA_ID, w_Data_functions);
+	luax_register_type(L, DATA_ID, "Data", w_Data_functions, nullptr);
 	return 0;
 	return 0;
 }
 }
 
 

+ 1 - 3
jni/love/src/common/wrap_Data.h

@@ -29,10 +29,8 @@ namespace love
 {
 {
 
 
 Data *luax_checkdata(lua_State *L, int idx);
 Data *luax_checkdata(lua_State *L, int idx);
-int w_Data_getString(lua_State *L);
-int w_Data_getPointer(lua_State *L);
-int w_Data_getSize(lua_State *L);
 int w_Data_open(lua_State *L);
 int w_Data_open(lua_State *L);
+extern const luaL_Reg w_Data_functions[];
 
 
 } // love
 } // love
 
 

+ 6 - 3
jni/love/src/libraries/Box2D/Collision/Shapes/b2ChainShape.cpp

@@ -22,6 +22,11 @@
 #include <string.h>
 #include <string.h>
 
 
 b2ChainShape::~b2ChainShape()
 b2ChainShape::~b2ChainShape()
+{
+	Clear();
+}
+
+void b2ChainShape::Clear()
 {
 {
 	b2Free(m_vertices);
 	b2Free(m_vertices);
 	m_vertices = NULL;
 	m_vertices = NULL;
@@ -56,10 +61,8 @@ void b2ChainShape::CreateChain(const b2Vec2* vertices, int32 count)
 	b2Assert(count >= 2);
 	b2Assert(count >= 2);
 	for (int32 i = 1; i < count; ++i)
 	for (int32 i = 1; i < count; ++i)
 	{
 	{
-		b2Vec2 v1 = vertices[i-1];
-		b2Vec2 v2 = vertices[i];
 		// If the code crashes here, it means your vertices are too close together.
 		// If the code crashes here, it means your vertices are too close together.
-		b2Assert(b2DistanceSquared(v1, v2) > b2_linearSlop * b2_linearSlop);
+		b2Assert(b2DistanceSquared(vertices[i-1], vertices[i]) > b2_linearSlop * b2_linearSlop);
 	}
 	}
 
 
 	m_count = count;
 	m_count = count;

+ 3 - 0
jni/love/src/libraries/Box2D/Collision/Shapes/b2ChainShape.h

@@ -37,6 +37,9 @@ public:
 	/// The destructor frees the vertices using b2Free.
 	/// The destructor frees the vertices using b2Free.
 	~b2ChainShape();
 	~b2ChainShape();
 
 
+	/// Clear all data.
+	void Clear();
+
 	/// Create a loop. This automatically adjusts connectivity.
 	/// Create a loop. This automatically adjusts connectivity.
 	/// @param vertices an array of vertices, these are copied
 	/// @param vertices an array of vertices, these are copied
 	/// @param count the vertex count
 	/// @param count the vertex count

+ 9 - 1
jni/love/src/libraries/Box2D/Collision/Shapes/b2PolygonShape.cpp

@@ -138,7 +138,7 @@ void b2PolygonShape::Set(const b2Vec2* vertices, int32 count)
 		bool unique = true;
 		bool unique = true;
 		for (int32 j = 0; j < tempCount; ++j)
 		for (int32 j = 0; j < tempCount; ++j)
 		{
 		{
-			if (b2DistanceSquared(v, ps[j]) < 0.5f * b2_linearSlop)
+			if (b2DistanceSquared(v, ps[j]) < ((0.5f * b2_linearSlop) * (0.5f * b2_linearSlop)))
 			{
 			{
 				unique = false;
 				unique = false;
 				break;
 				break;
@@ -217,6 +217,14 @@ void b2PolygonShape::Set(const b2Vec2* vertices, int32 count)
 		}
 		}
 	}
 	}
 	
 	
+	if (m < 3)
+	{
+		// Polygon is degenerate.
+		b2Assert(false);
+		SetAsBox(1.0f, 1.0f);
+		return;
+	}
+
 	m_count = m;
 	m_count = m;
 
 
 	// Copy vertices.
 	// Copy vertices.

+ 3 - 3
jni/love/src/libraries/Box2D/Common/b2Draw.h

@@ -25,9 +25,9 @@
 struct b2Color
 struct b2Color
 {
 {
 	b2Color() {}
 	b2Color() {}
-	b2Color(float32 r, float32 g, float32 b) : r(r), g(g), b(b) {}
-	void Set(float32 ri, float32 gi, float32 bi) { r = ri; g = gi; b = bi; }
-	float32 r, g, b;
+	b2Color(float32 r, float32 g, float32 b, float32 a = 1.0f) : r(r), g(g), b(b), a(a) {}
+	void Set(float32 ri, float32 gi, float32 bi, float32 ai = 1.0f) { r = ri; g = gi; b = bi; a = ai; }
+	float32 r, g, b, a;
 };
 };
 
 
 /// Implement and register this class with a b2World to provide debug drawing of physics
 /// Implement and register this class with a b2World to provide debug drawing of physics

+ 0 - 1
jni/love/src/libraries/Box2D/Common/b2Math.h

@@ -21,7 +21,6 @@
 
 
 #include <Box2D/Common/b2Settings.h>
 #include <Box2D/Common/b2Settings.h>
 #include <math.h>
 #include <math.h>
-#include <float.h>
 
 
 /// This function is used to ensure that a floating point number is not a NaN or infinity.
 /// This function is used to ensure that a floating point number is not a NaN or infinity.
 inline bool b2IsValid(float32 x)
 inline bool b2IsValid(float32 x)

+ 1 - 1
jni/love/src/libraries/Box2D/Common/b2Settings.cpp

@@ -23,7 +23,7 @@
 
 
 #include "common/Exception.h"
 #include "common/Exception.h"
 
 
-b2Version b2_version = {2, 3, 0};
+b2Version b2_version = {2, 3, 2};
 
 
 // Memory allocators. Modify these to use your own allocator.
 // Memory allocators. Modify these to use your own allocator.
 void* b2Alloc(int32 size)
 void* b2Alloc(int32 size)

+ 23 - 18
jni/love/src/libraries/Box2D/Dynamics/Contacts/b2ContactSolver.cpp

@@ -26,6 +26,8 @@
 
 
 #define B2_DEBUG_SOLVER 0
 #define B2_DEBUG_SOLVER 0
 
 
+bool g_blockSolve = true;
+
 struct b2ContactPositionConstraint
 struct b2ContactPositionConstraint
 {
 {
 	b2Vec2 localPoints[b2_maxManifoldPoints];
 	b2Vec2 localPoints[b2_maxManifoldPoints];
@@ -213,7 +215,7 @@ void b2ContactSolver::InitializeVelocityConstraints()
 		}
 		}
 
 
 		// If we have two points, then prepare the block solver.
 		// If we have two points, then prepare the block solver.
-		if (vc->pointCount == 2)
+		if (vc->pointCount == 2 && g_blockSolve)
 		{
 		{
 			b2VelocityConstraintPoint* vcp1 = vc->points + 0;
 			b2VelocityConstraintPoint* vcp1 = vc->points + 0;
 			b2VelocityConstraintPoint* vcp2 = vc->points + 1;
 			b2VelocityConstraintPoint* vcp2 = vc->points + 1;
@@ -341,29 +343,32 @@ void b2ContactSolver::SolveVelocityConstraints()
 		}
 		}
 
 
 		// Solve normal constraints
 		// Solve normal constraints
-		if (vc->pointCount == 1)
+		if (pointCount == 1 || g_blockSolve == false)
 		{
 		{
-			b2VelocityConstraintPoint* vcp = vc->points + 0;
+			for (int32 i = 0; i < pointCount; ++i)
+			{
+				b2VelocityConstraintPoint* vcp = vc->points + i;
 
 
-			// Relative velocity at contact
-			b2Vec2 dv = vB + b2Cross(wB, vcp->rB) - vA - b2Cross(wA, vcp->rA);
+				// Relative velocity at contact
+				b2Vec2 dv = vB + b2Cross(wB, vcp->rB) - vA - b2Cross(wA, vcp->rA);
 
 
-			// Compute normal impulse
-			float32 vn = b2Dot(dv, normal);
-			float32 lambda = -vcp->normalMass * (vn - vcp->velocityBias);
+				// Compute normal impulse
+				float32 vn = b2Dot(dv, normal);
+				float32 lambda = -vcp->normalMass * (vn - vcp->velocityBias);
 
 
-			// b2Clamp the accumulated impulse
-			float32 newImpulse = b2Max(vcp->normalImpulse + lambda, 0.0f);
-			lambda = newImpulse - vcp->normalImpulse;
-			vcp->normalImpulse = newImpulse;
+				// b2Clamp the accumulated impulse
+				float32 newImpulse = b2Max(vcp->normalImpulse + lambda, 0.0f);
+				lambda = newImpulse - vcp->normalImpulse;
+				vcp->normalImpulse = newImpulse;
 
 
-			// Apply contact impulse
-			b2Vec2 P = lambda * normal;
-			vA -= mA * P;
-			wA -= iA * b2Cross(vcp->rA, P);
+				// Apply contact impulse
+				b2Vec2 P = lambda * normal;
+				vA -= mA * P;
+				wA -= iA * b2Cross(vcp->rA, P);
 
 
-			vB += mB * P;
-			wB += iB * b2Cross(vcp->rB, P);
+				vB += mB * P;
+				wB += iB * b2Cross(vcp->rB, P);
+			}
 		}
 		}
 		else
 		else
 		{
 		{

+ 3 - 0
jni/love/src/libraries/Box2D/Dynamics/Joints/b2PrismaticJoint.cpp

@@ -174,6 +174,9 @@ void b2PrismaticJoint::InitVelocityConstraints(const b2SolverData& data)
 		m_s1 = b2Cross(d + rA, m_perp);
 		m_s1 = b2Cross(d + rA, m_perp);
 		m_s2 = b2Cross(rB, m_perp);
 		m_s2 = b2Cross(rB, m_perp);
 
 
+        float32 s1test;
+        s1test = b2Cross(rA, m_perp);
+
 		float32 k11 = mA + mB + iA * m_s1 * m_s1 + iB * m_s2 * m_s2;
 		float32 k11 = mA + mB + iA * m_s1 * m_s1 + iB * m_s2 * m_s2;
 		float32 k12 = iA * m_s1 + iB * m_s2;
 		float32 k12 = iA * m_s1 + iB * m_s2;
 		float32 k13 = iA * m_s1 * m_a1 + iB * m_s2 * m_a2;
 		float32 k13 = iA * m_s1 * m_a1 + iB * m_s2 * m_a2;

+ 17 - 1
jni/love/src/libraries/Box2D/Dynamics/Joints/b2WeldJoint.cpp

@@ -129,6 +129,12 @@ void b2WeldJoint::InitVelocityConstraints(const b2SolverData& data)
 		invM += m_gamma;
 		invM += m_gamma;
 		m_mass.ez.z = invM != 0.0f ? 1.0f / invM : 0.0f;
 		m_mass.ez.z = invM != 0.0f ? 1.0f / invM : 0.0f;
 	}
 	}
+	else if (K.ez.z == 0.0f)
+	{
+		K.GetInverse22(&m_mass);
+		m_gamma = 0.0f;
+		m_bias = 0.0f;
+	}
 	else
 	else
 	{
 	{
 		K.GetSymInverse33(&m_mass);
 		K.GetSymInverse33(&m_mass);
@@ -271,7 +277,17 @@ bool b2WeldJoint::SolvePositionConstraints(const b2SolverData& data)
 
 
 		b2Vec3 C(C1.x, C1.y, C2);
 		b2Vec3 C(C1.x, C1.y, C2);
 	
 	
-		b2Vec3 impulse = -K.Solve33(C);
+		b2Vec3 impulse;
+		if (K.ez.z > 0.0f)
+		{
+			impulse = -K.Solve33(C);
+		}
+		else
+		{
+			b2Vec2 impulse2 = -K.Solve22(C1);
+			impulse.Set(impulse2.x, impulse2.y, 0.0f);
+		}
+
 		b2Vec2 P(impulse.x, impulse.y);
 		b2Vec2 P(impulse.x, impulse.y);
 
 
 		cA -= mA * P;
 		cA -= mA * P;

+ 2 - 3
jni/love/src/libraries/Box2D/Dynamics/Joints/b2WheelJoint.h

@@ -72,9 +72,8 @@ struct b2WheelJointDef : public b2JointDef
 };
 };
 
 
 /// A wheel joint. This joint provides two degrees of freedom: translation
 /// A wheel joint. This joint provides two degrees of freedom: translation
-/// along an axis fixed in bodyA and rotation in the plane. You can use a
-/// joint limit to restrict the range of motion and a joint motor to drive
-/// the rotation or to model rotational friction.
+/// along an axis fixed in bodyA and rotation in the plane. In other words, it is a point to
+/// line constraint with a rotational motor and a linear spring/damper.
 /// This joint is designed for vehicle suspensions.
 /// This joint is designed for vehicle suspensions.
 class b2WheelJoint : public b2Joint
 class b2WheelJoint : public b2Joint
 {
 {

+ 19 - 19
jni/love/src/libraries/Box2D/Dynamics/b2World.cpp

@@ -37,7 +37,7 @@
 b2World::b2World(const b2Vec2& gravity)
 b2World::b2World(const b2Vec2& gravity)
 {
 {
 	m_destructionListener = NULL;
 	m_destructionListener = NULL;
-	m_debugDraw = NULL;
+	g_debugDraw = NULL;
 
 
 	m_bodyList = NULL;
 	m_bodyList = NULL;
 	m_jointList = NULL;
 	m_jointList = NULL;
@@ -101,7 +101,7 @@ void b2World::SetContactListener(b2ContactListener* listener)
 
 
 void b2World::SetDebugDraw(b2Draw* debugDraw)
 void b2World::SetDebugDraw(b2Draw* debugDraw)
 {
 {
-	m_debugDraw = debugDraw;
+	g_debugDraw = debugDraw;
 }
 }
 
 
 b2Body* b2World::CreateBody(const b2BodyDef* def)
 b2Body* b2World::CreateBody(const b2BodyDef* def)
@@ -1040,7 +1040,7 @@ void b2World::DrawShape(b2Fixture* fixture, const b2Transform& xf, const b2Color
 			float32 radius = circle->m_radius;
 			float32 radius = circle->m_radius;
 			b2Vec2 axis = b2Mul(xf.q, b2Vec2(1.0f, 0.0f));
 			b2Vec2 axis = b2Mul(xf.q, b2Vec2(1.0f, 0.0f));
 
 
-			m_debugDraw->DrawSolidCircle(center, radius, axis, color);
+			g_debugDraw->DrawSolidCircle(center, radius, axis, color);
 		}
 		}
 		break;
 		break;
 
 
@@ -1049,7 +1049,7 @@ void b2World::DrawShape(b2Fixture* fixture, const b2Transform& xf, const b2Color
 			b2EdgeShape* edge = (b2EdgeShape*)fixture->GetShape();
 			b2EdgeShape* edge = (b2EdgeShape*)fixture->GetShape();
 			b2Vec2 v1 = b2Mul(xf, edge->m_vertex1);
 			b2Vec2 v1 = b2Mul(xf, edge->m_vertex1);
 			b2Vec2 v2 = b2Mul(xf, edge->m_vertex2);
 			b2Vec2 v2 = b2Mul(xf, edge->m_vertex2);
-			m_debugDraw->DrawSegment(v1, v2, color);
+			g_debugDraw->DrawSegment(v1, v2, color);
 		}
 		}
 		break;
 		break;
 
 
@@ -1063,8 +1063,8 @@ void b2World::DrawShape(b2Fixture* fixture, const b2Transform& xf, const b2Color
 			for (int32 i = 1; i < count; ++i)
 			for (int32 i = 1; i < count; ++i)
 			{
 			{
 				b2Vec2 v2 = b2Mul(xf, vertices[i]);
 				b2Vec2 v2 = b2Mul(xf, vertices[i]);
-				m_debugDraw->DrawSegment(v1, v2, color);
-				m_debugDraw->DrawCircle(v1, 0.05f, color);
+				g_debugDraw->DrawSegment(v1, v2, color);
+				g_debugDraw->DrawCircle(v1, 0.05f, color);
 				v1 = v2;
 				v1 = v2;
 			}
 			}
 		}
 		}
@@ -1082,7 +1082,7 @@ void b2World::DrawShape(b2Fixture* fixture, const b2Transform& xf, const b2Color
 				vertices[i] = b2Mul(xf, poly->m_vertices[i]);
 				vertices[i] = b2Mul(xf, poly->m_vertices[i]);
 			}
 			}
 
 
-			m_debugDraw->DrawSolidPolygon(vertices, vertexCount, color);
+			g_debugDraw->DrawSolidPolygon(vertices, vertexCount, color);
 		}
 		}
 		break;
 		break;
             
             
@@ -1107,7 +1107,7 @@ void b2World::DrawJoint(b2Joint* joint)
 	switch (joint->GetType())
 	switch (joint->GetType())
 	{
 	{
 	case e_distanceJoint:
 	case e_distanceJoint:
-		m_debugDraw->DrawSegment(p1, p2, color);
+		g_debugDraw->DrawSegment(p1, p2, color);
 		break;
 		break;
 
 
 	case e_pulleyJoint:
 	case e_pulleyJoint:
@@ -1115,9 +1115,9 @@ void b2World::DrawJoint(b2Joint* joint)
 			b2PulleyJoint* pulley = (b2PulleyJoint*)joint;
 			b2PulleyJoint* pulley = (b2PulleyJoint*)joint;
 			b2Vec2 s1 = pulley->GetGroundAnchorA();
 			b2Vec2 s1 = pulley->GetGroundAnchorA();
 			b2Vec2 s2 = pulley->GetGroundAnchorB();
 			b2Vec2 s2 = pulley->GetGroundAnchorB();
-			m_debugDraw->DrawSegment(s1, p1, color);
-			m_debugDraw->DrawSegment(s2, p2, color);
-			m_debugDraw->DrawSegment(s1, s2, color);
+			g_debugDraw->DrawSegment(s1, p1, color);
+			g_debugDraw->DrawSegment(s2, p2, color);
+			g_debugDraw->DrawSegment(s1, s2, color);
 		}
 		}
 		break;
 		break;
 
 
@@ -1126,20 +1126,20 @@ void b2World::DrawJoint(b2Joint* joint)
 		break;
 		break;
 
 
 	default:
 	default:
-		m_debugDraw->DrawSegment(x1, p1, color);
-		m_debugDraw->DrawSegment(p1, p2, color);
-		m_debugDraw->DrawSegment(x2, p2, color);
+		g_debugDraw->DrawSegment(x1, p1, color);
+		g_debugDraw->DrawSegment(p1, p2, color);
+		g_debugDraw->DrawSegment(x2, p2, color);
 	}
 	}
 }
 }
 
 
 void b2World::DrawDebugData()
 void b2World::DrawDebugData()
 {
 {
-	if (m_debugDraw == NULL)
+	if (g_debugDraw == NULL)
 	{
 	{
 		return;
 		return;
 	}
 	}
 
 
-	uint32 flags = m_debugDraw->GetFlags();
+	uint32 flags = g_debugDraw->GetFlags();
 
 
 	if (flags & b2Draw::e_shapeBit)
 	if (flags & b2Draw::e_shapeBit)
 	{
 	{
@@ -1191,7 +1191,7 @@ void b2World::DrawDebugData()
 			//b2Vec2 cA = fixtureA->GetAABB().GetCenter();
 			//b2Vec2 cA = fixtureA->GetAABB().GetCenter();
 			//b2Vec2 cB = fixtureB->GetAABB().GetCenter();
 			//b2Vec2 cB = fixtureB->GetAABB().GetCenter();
 
 
-			//m_debugDraw->DrawSegment(cA, cB, color);
+			//g_debugDraw->DrawSegment(cA, cB, color);
 		}
 		}
 	}
 	}
 
 
@@ -1219,7 +1219,7 @@ void b2World::DrawDebugData()
 					vs[2].Set(aabb.upperBound.x, aabb.upperBound.y);
 					vs[2].Set(aabb.upperBound.x, aabb.upperBound.y);
 					vs[3].Set(aabb.lowerBound.x, aabb.upperBound.y);
 					vs[3].Set(aabb.lowerBound.x, aabb.upperBound.y);
 
 
-					m_debugDraw->DrawPolygon(vs, 4, color);
+					g_debugDraw->DrawPolygon(vs, 4, color);
 				}
 				}
 			}
 			}
 		}
 		}
@@ -1231,7 +1231,7 @@ void b2World::DrawDebugData()
 		{
 		{
 			b2Transform xf = b->GetTransform();
 			b2Transform xf = b->GetTransform();
 			xf.p = b->GetWorldCenter();
 			xf.p = b->GetWorldCenter();
-			m_debugDraw->DrawTransform(xf);
+			g_debugDraw->DrawTransform(xf);
 		}
 		}
 	}
 	}
 }
 }

+ 1 - 1
jni/love/src/libraries/Box2D/Dynamics/b2World.h

@@ -247,7 +247,7 @@ private:
 	bool m_allowSleep;
 	bool m_allowSleep;
 
 
 	b2DestructionListener* m_destructionListener;
 	b2DestructionListener* m_destructionListener;
-	b2Draw* m_debugDraw;
+	b2Draw* g_debugDraw;
 
 
 	// This is used to compute the time step ratio to
 	// This is used to compute the time step ratio to
 	// support a variable time step.
 	// support a variable time step.

+ 169 - 99
jni/love/src/libraries/stb/stb_image.h

@@ -1,4 +1,4 @@
-/* stb_image - v2.05 - public domain image loader - http://nothings.org/stb_image.h
+/* stb_image - v2.07 - public domain image loader - http://nothings.org/stb_image.h
                                      no warranty implied; use at your own risk
                                      no warranty implied; use at your own risk
 
 
    Do this:
    Do this:
@@ -25,13 +25,16 @@
 
 
       TGA (not sure what subset, if a subset)
       TGA (not sure what subset, if a subset)
       BMP non-1bpp, non-RLE
       BMP non-1bpp, non-RLE
-      PSD (composited view only, no extra channels)
+      PSD (composited view only, no extra channels, 8/16 bit-per-channel)
 
 
       GIF (*comp always reports as 4-channel)
       GIF (*comp always reports as 4-channel)
       HDR (radiance rgbE format)
       HDR (radiance rgbE format)
       PIC (Softimage PIC)
       PIC (Softimage PIC)
       PNM (PPM and PGM binary only)
       PNM (PPM and PGM binary only)
 
 
+      Animated GIF still needs a proper API, but here's one way to do it:
+          http://gist.github.com/urraka/685d9a6340b26b830d49
+
       - decode from memory or through FILE (define STBI_NO_STDIO to remove code)
       - decode from memory or through FILE (define STBI_NO_STDIO to remove code)
       - decode from arbitrary I/O callbacks
       - decode from arbitrary I/O callbacks
       - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON)
       - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON)
@@ -143,6 +146,10 @@
 
 
 
 
    Latest revision history:
    Latest revision history:
+      2.07  (2015-09-13) partial animated GIF support
+                         limited 16-bit PSD support
+                         minor bugs, code cleanup, and compiler warnings
+      2.06  (2015-04-19) fix bug where PSD returns wrong '*comp' value
       2.05  (2015-04-19) fix bug in progressive JPEG handling, fix warning
       2.05  (2015-04-19) fix bug in progressive JPEG handling, fix warning
       2.04  (2015-04-15) try to re-enable SIMD on MinGW 64-bit
       2.04  (2015-04-15) try to re-enable SIMD on MinGW 64-bit
       2.03  (2015-04-12) additional corruption checking
       2.03  (2015-04-12) additional corruption checking
@@ -175,38 +182,41 @@
     Tom Seddon (pic)                             the Horde3D community
     Tom Seddon (pic)                             the Horde3D community
     Thatcher Ulrich (psd)                        Janez Zemva
     Thatcher Ulrich (psd)                        Janez Zemva
     Ken Miller (pgm, ppm)                        Jonathan Blow
     Ken Miller (pgm, ppm)                        Jonathan Blow
-                                                 Laurent Gomila
+    urraka@github (animated gif)                 Laurent Gomila
                                                  Aruelien Pocheville
                                                  Aruelien Pocheville
- Extensions, features                            Ryamond Barbiero
-    Jetro Lauha (stbi_info)                      David Woo
-    Martin "SpartanJ" Golini (stbi_info)         Martin Golini
-    James "moose2000" Brown (iPhone PNG)         Roy Eltham
-    Ben "Disch" Wenger (io callbacks)            Luke Graham
-    Omar Cornut (1/2/4-bit PNG)                  Thomas Ruf
-    Nicolas Guillemot (vertical flip)            John Bartholomew
-                                                 Ken Hamada
- Optimizations & bugfixes                        Cort Stratton
-    Fabian "ryg" Giesen                          Blazej Dariusz Roszkowski
-    Arseny Kapoulkine                            Thibault Reuille
+                                                 Ryamond Barbiero
+                                                 David Woo
+ Extensions, features                            Martin Golini
+    Jetro Lauha (stbi_info)                      Roy Eltham
+    Martin "SpartanJ" Golini (stbi_info)         Luke Graham
+    James "moose2000" Brown (iPhone PNG)         Thomas Ruf
+    Ben "Disch" Wenger (io callbacks)            John Bartholomew
+    Omar Cornut (1/2/4-bit PNG)                  Ken Hamada
+    Nicolas Guillemot (vertical flip)            Cort Stratton
+    Richard Mitton (16-bit PSD)                  Blazej Dariusz Roszkowski
+                                                 Thibault Reuille
                                                  Paul Du Bois
                                                  Paul Du Bois
                                                  Guillaume George
                                                  Guillaume George
-  If your name should be here but                Jerry Jansson
-  isn't, let Sean know.                          Hayaki Saito
+                                                 Jerry Jansson
+                                                 Hayaki Saito
                                                  Johan Duparc
                                                  Johan Duparc
                                                  Ronny Chevalier
                                                  Ronny Chevalier
-                                                 Michal Cichon
-                                                 Tero Hanninen
-                                                 Sergio Gonzalez
+ Optimizations & bugfixes                        Michal Cichon
+    Fabian "ryg" Giesen                          Tero Hanninen
+    Arseny Kapoulkine                            Sergio Gonzalez
                                                  Cass Everitt
                                                  Cass Everitt
                                                  Engin Manap
                                                  Engin Manap
-                                                 Martins Mozeiko
-                                                 Joseph Thomson
+  If your name should be here but                Martins Mozeiko
+  isn't, let Sean know.                          Joseph Thomson
                                                  Phil Jordan
                                                  Phil Jordan
+                                                 Nathan Reed
+                                                 Michaelangel007@github
+
+LICENSE
 
 
-License:
-   This software is in the public domain. Where that dedication is not
-   recognized, you are granted a perpetual, irrevocable license to copy
-   and modify this file however you want.
+This software is in the public domain. Where that dedication is not
+recognized, you are granted a perpetual, irrevocable license to copy,
+distribute, and modify this file as you see fit.
 
 
 */
 */
 
 
@@ -748,7 +758,7 @@ typedef struct
    stbi_uc buffer_start[128];
    stbi_uc buffer_start[128];
 
 
    stbi_uc *img_buffer, *img_buffer_end;
    stbi_uc *img_buffer, *img_buffer_end;
-   stbi_uc *img_buffer_original;
+   stbi_uc *img_buffer_original, *img_buffer_original_end;
 } stbi__context;
 } stbi__context;
 
 
 
 
@@ -760,7 +770,7 @@ static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len)
    s->io.read = NULL;
    s->io.read = NULL;
    s->read_from_callbacks = 0;
    s->read_from_callbacks = 0;
    s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer;
    s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer;
-   s->img_buffer_end = (stbi_uc *) buffer+len;
+   s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len;
 }
 }
 
 
 // initialize a callback-based context
 // initialize a callback-based context
@@ -772,6 +782,7 @@ static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *
    s->read_from_callbacks = 1;
    s->read_from_callbacks = 1;
    s->img_buffer_original = s->buffer_start;
    s->img_buffer_original = s->buffer_start;
    stbi__refill_buffer(s);
    stbi__refill_buffer(s);
+   s->img_buffer_original_end = s->img_buffer_end;
 }
 }
 
 
 #ifndef STBI_NO_STDIO
 #ifndef STBI_NO_STDIO
@@ -813,6 +824,7 @@ static void stbi__rewind(stbi__context *s)
    // but we just rewind to the beginning of the initial buffer, because
    // but we just rewind to the beginning of the initial buffer, because
    // we only use it after doing 'test', which only ever looks at at most 92 bytes
    // we only use it after doing 'test', which only ever looks at at most 92 bytes
    s->img_buffer = s->img_buffer_original;
    s->img_buffer = s->img_buffer_original;
+   s->img_buffer_end = s->img_buffer_original_end;
 }
 }
 
 
 #ifndef STBI_NO_JPEG
 #ifndef STBI_NO_JPEG
@@ -900,8 +912,8 @@ static void *stbi__malloc(size_t size)
    #define stbi__err(x,y)  stbi__err(x)
    #define stbi__err(x,y)  stbi__err(x)
 #endif
 #endif
 
 
-#define stbi__errpf(x,y)   ((float *) (stbi__err(x,y)?NULL:NULL))
-#define stbi__errpuc(x,y)  ((unsigned char *) (stbi__err(x,y)?NULL:NULL))
+#define stbi__errpf(x,y)   ((float *)(size_t) (stbi__err(x,y)?NULL:NULL))
+#define stbi__errpuc(x,y)  ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL))
 
 
 STBIDEF void stbi_image_free(void *retval_from_stbi_load)
 STBIDEF void stbi_image_free(void *retval_from_stbi_load)
 {
 {
@@ -988,6 +1000,7 @@ static unsigned char *stbi__load_flip(stbi__context *s, int *x, int *y, int *com
    return result;
    return result;
 }
 }
 
 
+#ifndef STBI_NO_HDR
 static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp)
 static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp)
 {
 {
    if (stbi__vertically_flip_on_load && result != NULL) {
    if (stbi__vertically_flip_on_load && result != NULL) {
@@ -1008,7 +1021,7 @@ static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, in
       }
       }
    }
    }
 }
 }
-
+#endif
 
 
 #ifndef STBI_NO_STDIO
 #ifndef STBI_NO_STDIO
 
 
@@ -1152,6 +1165,7 @@ STBIDEF int      stbi_is_hdr_from_file(FILE *f)
    stbi__start_file(&s,f);
    stbi__start_file(&s,f);
    return stbi__hdr_test(&s);
    return stbi__hdr_test(&s);
    #else
    #else
+   STBI_NOTUSED(f);
    return 0;
    return 0;
    #endif
    #endif
 }
 }
@@ -1164,6 +1178,8 @@ STBIDEF int      stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void
    stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
    stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
    return stbi__hdr_test(&s);
    return stbi__hdr_test(&s);
    #else
    #else
+   STBI_NOTUSED(clbk);
+   STBI_NOTUSED(user);
    return 0;
    return 0;
    #endif
    #endif
 }
 }
@@ -1278,23 +1294,29 @@ static int stbi__get16be(stbi__context *s)
    return (z << 8) + stbi__get8(s);
    return (z << 8) + stbi__get8(s);
 }
 }
 
 
+#if !defined(STBI_NO_PNG) || !defined(STBI_NO_PSD)
 static stbi__uint32 stbi__get32be(stbi__context *s)
 static stbi__uint32 stbi__get32be(stbi__context *s)
 {
 {
    stbi__uint32 z = stbi__get16be(s);
    stbi__uint32 z = stbi__get16be(s);
    return (z << 16) + stbi__get16be(s);
    return (z << 16) + stbi__get16be(s);
 }
 }
+#endif
 
 
+#if !defined(STBI_NO_BMP) || !defined(STBI_NO_TGA) || !defined(STBI_NO_GIF)
 static int stbi__get16le(stbi__context *s)
 static int stbi__get16le(stbi__context *s)
 {
 {
    int z = stbi__get8(s);
    int z = stbi__get8(s);
    return z + (stbi__get8(s) << 8);
    return z + (stbi__get8(s) << 8);
 }
 }
+#endif
 
 
+#ifndef STBI_NO_BMP
 static stbi__uint32 stbi__get32le(stbi__context *s)
 static stbi__uint32 stbi__get32le(stbi__context *s)
 {
 {
    stbi__uint32 z = stbi__get16le(s);
    stbi__uint32 z = stbi__get16le(s);
    return z + (stbi__get16le(s) << 16);
    return z + (stbi__get16le(s) << 16);
 }
 }
+#endif
 
 
 #define STBI__BYTECAST(x)  ((stbi_uc) ((x) & 255))  // truncate int to byte without warnings
 #define STBI__BYTECAST(x)  ((stbi_uc) ((x) & 255))  // truncate int to byte without warnings
 
 
@@ -2738,7 +2760,7 @@ static int stbi__process_frame_header(stbi__jpeg *z, int scan)
       if (z->img_comp[i].raw_data == NULL) {
       if (z->img_comp[i].raw_data == NULL) {
          for(--i; i >= 0; --i) {
          for(--i; i >= 0; --i) {
             STBI_FREE(z->img_comp[i].raw_data);
             STBI_FREE(z->img_comp[i].raw_data);
-            z->img_comp[i].data = NULL;
+            z->img_comp[i].raw_data = NULL;
          }
          }
          return stbi__err("outofmem", "Out of memory");
          return stbi__err("outofmem", "Out of memory");
       }
       }
@@ -3500,10 +3522,10 @@ static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num)
          z->size [c] = (stbi_uc     ) s;
          z->size [c] = (stbi_uc     ) s;
          z->value[c] = (stbi__uint16) i;
          z->value[c] = (stbi__uint16) i;
          if (s <= STBI__ZFAST_BITS) {
          if (s <= STBI__ZFAST_BITS) {
-            int k = stbi__bit_reverse(next_code[s],s);
-            while (k < (1 << STBI__ZFAST_BITS)) {
-               z->fast[k] = fastv;
-               k += (1 << s);
+            int j = stbi__bit_reverse(next_code[s],s);
+            while (j < (1 << STBI__ZFAST_BITS)) {
+               z->fast[j] = fastv;
+               j += (1 << s);
             }
             }
          }
          }
          ++next_code[s];
          ++next_code[s];
@@ -3542,7 +3564,7 @@ static void stbi__fill_bits(stbi__zbuf *z)
 {
 {
    do {
    do {
       STBI_ASSERT(z->code_buffer < (1U << z->num_bits));
       STBI_ASSERT(z->code_buffer < (1U << z->num_bits));
-      z->code_buffer |= stbi__zget8(z) << z->num_bits;
+      z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits;
       z->num_bits += 8;
       z->num_bits += 8;
    } while (z->num_bits <= 24);
    } while (z->num_bits <= 24);
 }
 }
@@ -4108,21 +4130,21 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r
             if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01);
             if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01);
          }
          }
          if (img_n != out_n) {
          if (img_n != out_n) {
+            int q;
             // insert alpha = 255
             // insert alpha = 255
-            stbi_uc *cur = a->out + stride*j;
-            int i;
+            cur = a->out + stride*j;
             if (img_n == 1) {
             if (img_n == 1) {
-               for (i=x-1; i >= 0; --i) {
-                  cur[i*2+1] = 255;
-                  cur[i*2+0] = cur[i];
+               for (q=x-1; q >= 0; --q) {
+                  cur[q*2+1] = 255;
+                  cur[q*2+0] = cur[q];
                }
                }
             } else {
             } else {
                STBI_ASSERT(img_n == 3);
                STBI_ASSERT(img_n == 3);
-               for (i=x-1; i >= 0; --i) {
-                  cur[i*4+3] = 255;
-                  cur[i*4+2] = cur[i*3+2];
-                  cur[i*4+1] = cur[i*3+1];
-                  cur[i*4+0] = cur[i*3+0];
+               for (q=x-1; q >= 0; --q) {
+                  cur[q*4+3] = 255;
+                  cur[q*4+2] = cur[q*3+2];
+                  cur[q*4+1] = cur[q*3+1];
+                  cur[q*4+0] = cur[q*3+0];
                }
                }
             }
             }
          }
          }
@@ -4577,7 +4599,7 @@ static int stbi__shiftsigned(int v, int shift, int bits)
 static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
 static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
 {
 {
    stbi_uc *out;
    stbi_uc *out;
-   unsigned int mr=0,mg=0,mb=0,ma=0, fake_a=0;
+   unsigned int mr=0,mg=0,mb=0,ma=0, all_a=255;
    stbi_uc pal[256][4];
    stbi_uc pal[256][4];
    int psize=0,i,j,compress=0,width;
    int psize=0,i,j,compress=0,width;
    int bpp, flip_vertically, pad, target, offset, hsz;
    int bpp, flip_vertically, pad, target, offset, hsz;
@@ -4626,8 +4648,7 @@ static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int
                   mg = 0xffu <<  8;
                   mg = 0xffu <<  8;
                   mb = 0xffu <<  0;
                   mb = 0xffu <<  0;
                   ma = 0xffu << 24;
                   ma = 0xffu << 24;
-                  fake_a = 1; // @TODO: check for cases like alpha value is all 0 and switch it to 255
-                  STBI_NOTUSED(fake_a);
+                  all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0
                } else {
                } else {
                   mr = 31u << 10;
                   mr = 31u << 10;
                   mg = 31u <<  5;
                   mg = 31u <<  5;
@@ -4738,6 +4759,7 @@ static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int
                out[z+0] = stbi__get8(s);
                out[z+0] = stbi__get8(s);
                z += 3;
                z += 3;
                a = (easy == 2 ? stbi__get8(s) : 255);
                a = (easy == 2 ? stbi__get8(s) : 255);
+               all_a |= a;
                if (target == 4) out[z++] = a;
                if (target == 4) out[z++] = a;
             }
             }
          } else {
          } else {
@@ -4748,12 +4770,19 @@ static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int
                out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount));
                out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount));
                out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount));
                out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount));
                a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255);
                a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255);
+               all_a |= a;
                if (target == 4) out[z++] = STBI__BYTECAST(a);
                if (target == 4) out[z++] = STBI__BYTECAST(a);
             }
             }
          }
          }
          stbi__skip(s, pad);
          stbi__skip(s, pad);
       }
       }
    }
    }
+   
+   // if alpha channel is all 0s, replace with all 255s
+   if (target == 4 && all_a == 0)
+      for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4)
+         out[i] = 255;
+
    if (flip_vertically) {
    if (flip_vertically) {
       stbi_uc t;
       stbi_uc t;
       for (j=0; j < (int) s->img_y>>1; ++j) {
       for (j=0; j < (int) s->img_y>>1; ++j) {
@@ -4907,8 +4936,8 @@ static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int
 
 
    if ( !tga_indexed && !tga_is_RLE) {
    if ( !tga_indexed && !tga_is_RLE) {
       for (i=0; i < tga_height; ++i) {
       for (i=0; i < tga_height; ++i) {
-         int y = tga_inverted ? tga_height -i - 1 : i;
-         stbi_uc *tga_row = tga_data + y*tga_width*tga_comp;
+         int row = tga_inverted ? tga_height -i - 1 : i;
+         stbi_uc *tga_row = tga_data + row*tga_width*tga_comp;
          stbi__getn(s, tga_row, tga_width * tga_comp);
          stbi__getn(s, tga_row, tga_width * tga_comp);
       }
       }
    } else  {
    } else  {
@@ -5053,6 +5082,7 @@ static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int
    int   pixelCount;
    int   pixelCount;
    int channelCount, compression;
    int channelCount, compression;
    int channel, i, count, len;
    int channel, i, count, len;
+   int bitdepth;
    int w,h;
    int w,h;
    stbi_uc *out;
    stbi_uc *out;
 
 
@@ -5077,8 +5107,9 @@ static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int
    w = stbi__get32be(s);
    w = stbi__get32be(s);
 
 
    // Make sure the depth is 8 bits.
    // Make sure the depth is 8 bits.
-   if (stbi__get16be(s) != 8)
-      return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 bit");
+   bitdepth = stbi__get16be(s);
+   if (bitdepth != 8 && bitdepth != 16)
+      return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit");
 
 
    // Make sure the color mode is RGB.
    // Make sure the color mode is RGB.
    // Valid options are:
    // Valid options are:
@@ -5139,7 +5170,8 @@ static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int
          p = out+channel;
          p = out+channel;
          if (channel >= channelCount) {
          if (channel >= channelCount) {
             // Fill this channel with default data.
             // Fill this channel with default data.
-            for (i = 0; i < pixelCount; i++) *p = (channel == 3 ? 255 : 0), p += 4;
+            for (i = 0; i < pixelCount; i++, p += 4)
+               *p = (channel == 3 ? 255 : 0);
          } else {
          } else {
             // Read the RLE data.
             // Read the RLE data.
             count = 0;
             count = 0;
@@ -5185,11 +5217,17 @@ static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int
          p = out + channel;
          p = out + channel;
          if (channel > channelCount) {
          if (channel > channelCount) {
             // Fill this channel with default data.
             // Fill this channel with default data.
-            for (i = 0; i < pixelCount; i++) *p = channel == 3 ? 255 : 0, p += 4;
+            for (i = 0; i < pixelCount; i++, p += 4)
+               *p = channel == 3 ? 255 : 0;
          } else {
          } else {
             // Read the data.
             // Read the data.
-            for (i = 0; i < pixelCount; i++)
-               *p = stbi__get8(s), p += 4;
+            if (bitdepth == 16) {
+               for (i = 0; i < pixelCount; i++, p += 4)
+                  *p = stbi__get16be(s) >> 8;
+            } else {
+               for (i = 0; i < pixelCount; i++, p += 4)
+                  *p = stbi__get8(s);
+            }
          }
          }
       }
       }
    }
    }
@@ -5199,7 +5237,7 @@ static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int
       if (out == NULL) return out; // stbi__convert_format frees input on failure
       if (out == NULL) return out; // stbi__convert_format frees input on failure
    }
    }
 
 
-   if (comp) *comp = channelCount;
+   if (comp) *comp = 4;
    *y = h;
    *y = h;
    *x = w;
    *x = w;
 
 
@@ -5347,7 +5385,6 @@ static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *c
 
 
                   if (count >= 128) { // Repeated
                   if (count >= 128) { // Repeated
                      stbi_uc value[4];
                      stbi_uc value[4];
-                     int i;
 
 
                      if (count==128)
                      if (count==128)
                         count = stbi__get16be(s);
                         count = stbi__get16be(s);
@@ -5435,8 +5472,8 @@ typedef struct
 typedef struct
 typedef struct
 {
 {
    int w,h;
    int w,h;
-   stbi_uc *out;                 // output buffer (always 4 components)
-   int flags, bgindex, ratio, transparent, eflags;
+   stbi_uc *out, *old_out;             // output buffer (always 4 components)
+   int flags, bgindex, ratio, transparent, eflags, delay;
    stbi_uc  pal[256][4];
    stbi_uc  pal[256][4];
    stbi_uc lpal[256][4];
    stbi_uc lpal[256][4];
    stbi__gif_lzw codes[4096];
    stbi__gif_lzw codes[4096];
@@ -5554,7 +5591,7 @@ static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code)
 static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g)
 static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g)
 {
 {
    stbi_uc lzw_cs;
    stbi_uc lzw_cs;
-   stbi__int32 len, code;
+   stbi__int32 len, init_code;
    stbi__uint32 first;
    stbi__uint32 first;
    stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear;
    stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear;
    stbi__gif_lzw *p;
    stbi__gif_lzw *p;
@@ -5567,10 +5604,10 @@ static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g)
    codemask = (1 << codesize) - 1;
    codemask = (1 << codesize) - 1;
    bits = 0;
    bits = 0;
    valid_bits = 0;
    valid_bits = 0;
-   for (code = 0; code < clear; code++) {
-      g->codes[code].prefix = -1;
-      g->codes[code].first = (stbi_uc) code;
-      g->codes[code].suffix = (stbi_uc) code;
+   for (init_code = 0; init_code < clear; init_code++) {
+      g->codes[init_code].prefix = -1;
+      g->codes[init_code].first = (stbi_uc) init_code;
+      g->codes[init_code].suffix = (stbi_uc) init_code;
    }
    }
 
 
    // support no starting clear code
    // support no starting clear code
@@ -5631,17 +5668,18 @@ static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g)
    }
    }
 }
 }
 
 
-static void stbi__fill_gif_background(stbi__gif *g)
+static void stbi__fill_gif_background(stbi__gif *g, int x0, int y0, int x1, int y1)
 {
 {
-   int i;
+   int x, y;
    stbi_uc *c = g->pal[g->bgindex];
    stbi_uc *c = g->pal[g->bgindex];
-   // @OPTIMIZE: write a dword at a time
-   for (i = 0; i < g->w * g->h * 4; i += 4) {
-      stbi_uc *p  = &g->out[i];
-      p[0] = c[2];
-      p[1] = c[1];
-      p[2] = c[0];
-      p[3] = c[3];
+   for (y = y0; y < y1; y += 4 * g->w) {
+      for (x = x0; x < x1; x += 4) {
+         stbi_uc *p  = &g->out[y + x];
+         p[0] = c[2];
+         p[1] = c[1];
+         p[2] = c[0];
+         p[3] = 0;
+      }
    }
    }
 }
 }
 
 
@@ -5649,27 +5687,40 @@ static void stbi__fill_gif_background(stbi__gif *g)
 static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp)
 static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp)
 {
 {
    int i;
    int i;
-   stbi_uc *old_out = 0;
+   stbi_uc *prev_out = 0;
 
 
-   if (g->out == 0) {
-      if (!stbi__gif_header(s, g, comp,0))     return 0; // stbi__g_failure_reason set by stbi__gif_header
-      g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h);
-      if (g->out == 0)                      return stbi__errpuc("outofmem", "Out of memory");
-      stbi__fill_gif_background(g);
-   } else {
-      // animated-gif-only path
-      if (((g->eflags & 0x1C) >> 2) == 3) {
-         old_out = g->out;
-         g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h);
-         if (g->out == 0)                   return stbi__errpuc("outofmem", "Out of memory");
-         memcpy(g->out, old_out, g->w*g->h*4);
-      }
+   if (g->out == 0 && !stbi__gif_header(s, g, comp,0))
+      return 0; // stbi__g_failure_reason set by stbi__gif_header
+
+   prev_out = g->out;
+   g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h);
+   if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory");
+
+   switch ((g->eflags & 0x1C) >> 2) {
+      case 0: // unspecified (also always used on 1st frame)
+         stbi__fill_gif_background(g, 0, 0, 4 * g->w, 4 * g->w * g->h);
+         break;
+      case 1: // do not dispose
+         if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h);
+         g->old_out = prev_out;
+         break;
+      case 2: // dispose to background
+         if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h);
+         stbi__fill_gif_background(g, g->start_x, g->start_y, g->max_x, g->max_y);
+         break;
+      case 3: // dispose to previous
+         if (g->old_out) {
+            for (i = g->start_y; i < g->max_y; i += 4 * g->w)
+               memcpy(&g->out[i + g->start_x], &g->old_out[i + g->start_x], g->max_x - g->start_x);
+         }
+         break;
    }
    }
 
 
    for (;;) {
    for (;;) {
       switch (stbi__get8(s)) {
       switch (stbi__get8(s)) {
          case 0x2C: /* Image Descriptor */
          case 0x2C: /* Image Descriptor */
          {
          {
+            int prev_trans = -1;
             stbi__int32 x, y, w, h;
             stbi__int32 x, y, w, h;
             stbi_uc *o;
             stbi_uc *o;
 
 
@@ -5702,10 +5753,10 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i
                stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1);
                stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1);
                g->color_table = (stbi_uc *) g->lpal;
                g->color_table = (stbi_uc *) g->lpal;
             } else if (g->flags & 0x80) {
             } else if (g->flags & 0x80) {
-               for (i=0; i < 256; ++i)  // @OPTIMIZE: stbi__jpeg_reset only the previous transparent
-                  g->pal[i][3] = 255;
-               if (g->transparent >= 0 && (g->eflags & 0x01))
+               if (g->transparent >= 0 && (g->eflags & 0x01)) {
+                  prev_trans = g->pal[g->transparent][3];
                   g->pal[g->transparent][3] = 0;
                   g->pal[g->transparent][3] = 0;
+               }
                g->color_table = (stbi_uc *) g->pal;
                g->color_table = (stbi_uc *) g->pal;
             } else
             } else
                return stbi__errpuc("missing color table", "Corrupt GIF");
                return stbi__errpuc("missing color table", "Corrupt GIF");
@@ -5713,8 +5764,9 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i
             o = stbi__process_gif_raster(s, g);
             o = stbi__process_gif_raster(s, g);
             if (o == NULL) return NULL;
             if (o == NULL) return NULL;
 
 
-            if (req_comp && req_comp != 4)
-               o = stbi__convert_format(o, 4, req_comp, g->w, g->h);
+            if (prev_trans != -1)
+               g->pal[g->transparent][3] = prev_trans;
+
             return o;
             return o;
          }
          }
 
 
@@ -5725,7 +5777,7 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i
                len = stbi__get8(s);
                len = stbi__get8(s);
                if (len == 4) {
                if (len == 4) {
                   g->eflags = stbi__get8(s);
                   g->eflags = stbi__get8(s);
-                  stbi__get16le(s); // delay
+                  g->delay = stbi__get16le(s);
                   g->transparent = stbi__get8(s);
                   g->transparent = stbi__get8(s);
                } else {
                } else {
                   stbi__skip(s, len);
                   stbi__skip(s, len);
@@ -5757,7 +5809,11 @@ static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int
    if (u) {
    if (u) {
       *x = g.w;
       *x = g.w;
       *y = g.h;
       *y = g.h;
+      if (req_comp && req_comp != 4)
+         u = stbi__convert_format(u, 4, req_comp, g.w, g.h);
    }
    }
+   else if (g.out)
+      STBI_FREE(g.out);
 
 
    return u;
    return u;
 }
 }
@@ -6059,14 +6115,22 @@ static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp)
    int act_comp=0,num_packets=0,chained;
    int act_comp=0,num_packets=0,chained;
    stbi__pic_packet packets[10];
    stbi__pic_packet packets[10];
 
 
-   stbi__skip(s, 92);
+   if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) {
+      stbi__rewind(s);
+      return 0;
+   }
+
+   stbi__skip(s, 88);
 
 
    *x = stbi__get16be(s);
    *x = stbi__get16be(s);
    *y = stbi__get16be(s);
    *y = stbi__get16be(s);
-   if (stbi__at_eof(s))  return 0;
+   if (stbi__at_eof(s)) {
+      stbi__rewind( s);
+      return 0;
+   }
    if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) {
    if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) {
-       stbi__rewind( s );
-       return 0;
+      stbi__rewind( s );
+      return 0;
    }
    }
 
 
    stbi__skip(s, 8);
    stbi__skip(s, 8);
@@ -6292,6 +6356,12 @@ STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int
 
 
 /*
 /*
    revision history:
    revision history:
+      2.07  (2015-09-13) fix compiler warnings
+                         partial animated GIF support
+                         limited 16-bit PSD support
+                         #ifdef unused functions
+                         bug with < 92 byte PIC,PNM,HDR,TGA
+      2.06  (2015-04-19) fix bug where PSD returns wrong '*comp' value
       2.05  (2015-04-19) fix bug in progressive JPEG handling, fix warning
       2.05  (2015-04-19) fix bug in progressive JPEG handling, fix warning
       2.04  (2015-04-15) try to re-enable SIMD on MinGW 64-bit
       2.04  (2015-04-15) try to re-enable SIMD on MinGW 64-bit
       2.03  (2015-04-12) extra corruption checking (mmozeiko)
       2.03  (2015-04-12) extra corruption checking (mmozeiko)

+ 9 - 4
jni/love/src/love.cpp

@@ -37,6 +37,7 @@ extern "C" {
 
 
 #ifdef LOVE_MACOSX
 #ifdef LOVE_MACOSX
 #include "common/macosx.h"
 #include "common/macosx.h"
+#include <unistd.h>
 #endif // LOVE_MACOSX
 #endif // LOVE_MACOSX
 
 
 #ifdef LOVE_IOS
 #ifdef LOVE_IOS
@@ -110,12 +111,16 @@ static void get_app_arguments(int argc, char **argv, int &new_argc, char **&new_
 	}
 	}
 
 
 #ifdef LOVE_MACOSX
 #ifdef LOVE_MACOSX
-	// Check for a drop file string.
-	std::string dropfilestr = love::macosx::checkDropEvents();
+	// Check for a drop file string, if the app wasn't launched in a terminal.
+	// Checking for the terminal is a pretty big hack, but works around an issue
+	// where OS X will switch Spaces if the terminal launching love is in its
+	// own full-screen Space.
+	std::string dropfilestr;
+	if (!isatty(STDIN_FILENO))
+		dropfilestr = love::macosx::checkDropEvents();
+
 	if (!dropfilestr.empty())
 	if (!dropfilestr.empty())
-	{
 		temp_argv.insert(temp_argv.begin() + 1, dropfilestr);
 		temp_argv.insert(temp_argv.begin() + 1, dropfilestr);
-	}
 	else
 	else
 #endif
 #endif
 	{
 	{

+ 2 - 1
jni/love/src/modules/audio/Source.h

@@ -39,7 +39,7 @@ public:
 		TYPE_STATIC,
 		TYPE_STATIC,
 		TYPE_STREAM,
 		TYPE_STREAM,
 		TYPE_MAX_ENUM
 		TYPE_MAX_ENUM
-	}; // Type
+	};
 
 
 	enum Unit
 	enum Unit
 	{
 	{
@@ -71,6 +71,7 @@ public:
 
 
 	virtual void seek(float offset, Unit unit) = 0;
 	virtual void seek(float offset, Unit unit) = 0;
 	virtual float tell(Unit unit) = 0;
 	virtual float tell(Unit unit) = 0;
+	virtual double getDuration(Unit unit) = 0;
 
 
 	// all float * v must be of size 3
 	// all float * v must be of size 3
 	virtual void setPosition(float *v) = 0;
 	virtual void setPosition(float *v) = 0;

+ 5 - 0
jni/love/src/modules/audio/null/Source.cpp

@@ -112,6 +112,11 @@ float Source::tell(Source::Unit)
 	return 0.0f;
 	return 0.0f;
 }
 }
 
 
+double Source::getDuration(Unit)
+{
+	return -1.0f;
+}
+
 void Source::setPosition(float *)
 void Source::setPosition(float *)
 {
 {
 }
 }

+ 1 - 0
jni/love/src/modules/audio/null/Source.h

@@ -54,6 +54,7 @@ public:
 	virtual float getVolume() const;
 	virtual float getVolume() const;
 	virtual void seek(float offset, Unit unit);
 	virtual void seek(float offset, Unit unit);
 	virtual float tell(Unit unit);
 	virtual float tell(Unit unit);
+	virtual double getDuration(Unit unit);
 	virtual void setPosition(float *v);
 	virtual void setPosition(float *v);
 	virtual void getPosition(float *v) const;
 	virtual void getPosition(float *v) const;
 	virtual void setVelocity(float *v);
 	virtual void setVelocity(float *v);

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

@@ -263,6 +263,12 @@ float Pool::tell(Source *source, void *unit)
 	return source->tellAtomic(unit);
 	return source->tellAtomic(unit);
 }
 }
 
 
+double Pool::getDuration(Source *source, void *unit)
+{
+	thread::Lock lock(mutex);
+	return source->getDurationAtomic(unit);
+}
+
 ALuint Pool::findi(const Source *source) const
 ALuint Pool::findi(const Source *source) const
 {
 {
 	std::map<Source *, ALuint>::const_iterator i = playing.find((Source *)source);
 	std::map<Source *, ALuint>::const_iterator i = playing.find((Source *)source);

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

@@ -93,6 +93,7 @@ public:
 	void softRewind(Source *source);
 	void softRewind(Source *source);
 	void seek(Source *source, float offset, void *unit);
 	void seek(Source *source, float offset, void *unit);
 	float tell(Source *source, void *unit);
 	float tell(Source *source, void *unit);
+	double getDuration(Source *source, void *unit);
 
 
 private:
 private:
 
 

+ 53 - 8
jni/love/src/modules/audio/openal/Source.cpp

@@ -65,6 +65,7 @@ Ensure the Source is not multi-channel before calling this function.")
 };
 };
 
 
 StaticDataBuffer::StaticDataBuffer(ALenum format, const ALvoid *data, ALsizei size, ALsizei freq)
 StaticDataBuffer::StaticDataBuffer(ALenum format, const ALvoid *data, ALsizei size, ALsizei freq)
+	: size(size)
 {
 {
 	alGenBuffers(1, &buffer);
 	alGenBuffers(1, &buffer);
 	alBufferData(buffer, format, data, size, freq);
 	alBufferData(buffer, format, data, size, freq);
@@ -95,6 +96,7 @@ Source::Source(Pool *pool, love::sound::SoundData *soundData)
 	, offsetSeconds(0)
 	, offsetSeconds(0)
 	, sampleRate(soundData->getSampleRate())
 	, sampleRate(soundData->getSampleRate())
 	, channels(soundData->getChannels())
 	, channels(soundData->getChannels())
+	, bitDepth(soundData->getBitDepth())
 	, decoder(nullptr)
 	, decoder(nullptr)
 	, toLoop(0)
 	, toLoop(0)
 {
 {
@@ -134,6 +136,7 @@ Source::Source(Pool *pool, love::sound::Decoder *decoder)
 	, offsetSeconds(0)
 	, offsetSeconds(0)
 	, sampleRate(decoder->getSampleRate())
 	, sampleRate(decoder->getSampleRate())
 	, channels(decoder->getChannels())
 	, channels(decoder->getChannels())
+	, bitDepth(decoder->getBitDepth())
 	, decoder(decoder)
 	, decoder(decoder)
 	, toLoop(0)
 	, toLoop(0)
 {
 {
@@ -169,6 +172,7 @@ Source::Source(const Source &s)
 	, offsetSeconds(0)
 	, offsetSeconds(0)
 	, sampleRate(s.sampleRate)
 	, sampleRate(s.sampleRate)
 	, channels(s.channels)
 	, channels(s.channels)
+	, bitDepth(s.bitDepth)
 	, decoder(nullptr)
 	, decoder(nullptr)
 	, toLoop(0)
 	, toLoop(0)
 {
 {
@@ -308,8 +312,11 @@ bool Source::update()
 			offsetSamples += (curOffsetSamples - newOffsetSamples);
 			offsetSamples += (curOffsetSamples - newOffsetSamples);
 			offsetSeconds += (curOffsetSecs - newOffsetSecs);
 			offsetSeconds += (curOffsetSecs - newOffsetSecs);
 
 
-			streamAtomic(buffer, decoder.get());
-			alSourceQueueBuffers(source, 1, &buffer);
+			// FIXME: We should put freed buffers into a list that we later
+			// consume here, so we can keep track of all free buffers even if we
+			// tried to stream data to one but the decoder didn't have data for it.
+			if (streamAtomic(buffer, decoder.get()) > 0)
+				alSourceQueueBuffers(source, 1, &buffer);
 		}
 		}
 
 
 		return true;
 		return true;
@@ -438,6 +445,36 @@ float Source::tell(Source::Unit unit)
 	return pool->tell(this, &unit);
 	return pool->tell(this, &unit);
 }
 }
 
 
+double Source::getDurationAtomic(void *vunit)
+{
+	Unit unit = *(Unit *) vunit;
+
+	if (type == TYPE_STREAM)
+	{
+		double seconds = decoder->getDuration();
+
+		if (unit == UNIT_SECONDS)
+			return seconds;
+		else
+			return seconds * decoder->getSampleRate();
+	}
+	else
+	{
+		ALsizei size = staticBuffer->getSize();
+		ALsizei samples = (size / channels) / (bitDepth / 8);
+
+		if (unit == UNIT_SAMPLES)
+			return (double) samples;
+		else
+			return (double) samples / (double) sampleRate;
+	}
+}
+
+double Source::getDuration(Unit unit)
+{
+	return pool->getDuration(this, &unit);
+}
+
 void Source::setPosition(float *v)
 void Source::setPosition(float *v)
 {
 {
 	if (channels > 1)
 	if (channels > 1)
@@ -575,8 +612,11 @@ bool Source::playAtomic()
 
 
 		for (unsigned int i = 0; i < MAX_BUFFERS; i++)
 		for (unsigned int i = 0; i < MAX_BUFFERS; i++)
 		{
 		{
-			streamAtomic(streamBuffers[i], decoder.get());
+			if (streamAtomic(streamBuffers[i], decoder.get()) == 0)
+				break;
+
 			++usedBuffers;
 			++usedBuffers;
+
 			if (decoder->isFinished())
 			if (decoder->isFinished())
 				break;
 				break;
 		}
 		}
@@ -736,13 +776,18 @@ ALenum Source::getFormat(int channels, int bitDepth) const
 int Source::streamAtomic(ALuint buffer, love::sound::Decoder *d)
 int Source::streamAtomic(ALuint buffer, love::sound::Decoder *d)
 {
 {
 	// Get more sound data.
 	// Get more sound data.
-	int decoded = d->decode();
-	decoded = decoded >= 0 ? decoded : 0;
+	int decoded = std::max(d->decode(), 0);
 
 
-	int fmt = getFormat(d->getChannels(), d->getBitDepth());
+	// OpenAL implementations are allowed to ignore 0-size alBufferData calls.
+	if (decoded > 0)
+	{
+		int fmt = getFormat(d->getChannels(), d->getBitDepth());
 
 
-	if (fmt != 0)
-		alBufferData(buffer, fmt, d->getBuffer(), decoded, d->getSampleRate());
+		if (fmt != 0)
+			alBufferData(buffer, fmt, d->getBuffer(), decoded, d->getSampleRate());
+		else
+			decoded = 0;
+	}
 
 
 	if (decoder->isFinished() && isLooping())
 	if (decoder->isFinished() && isLooping())
 	{
 	{

+ 9 - 0
jni/love/src/modules/audio/openal/Source.h

@@ -65,9 +65,15 @@ public:
 		return buffer;
 		return buffer;
 	}
 	}
 
 
+	inline ALsizei getSize() const
+	{
+		return size;
+	}
+
 private:
 private:
 
 
 	ALuint buffer;
 	ALuint buffer;
+	ALsizei size;
 
 
 }; // StaticDataBuffer
 }; // StaticDataBuffer
 
 
@@ -98,6 +104,8 @@ public:
 	virtual void seek(float offset, Unit unit);
 	virtual void seek(float offset, Unit unit);
 	virtual float tellAtomic(void *unit) const;
 	virtual float tellAtomic(void *unit) const;
 	virtual float tell(Unit unit);
 	virtual float tell(Unit unit);
+	virtual double getDurationAtomic(void *unit);
+	virtual double getDuration(Unit unit);
 	virtual void setPosition(float *v);
 	virtual void setPosition(float *v);
 	virtual void getPosition(float *v) const;
 	virtual void getPosition(float *v) const;
 	virtual void setVelocity(float *v);
 	virtual void setVelocity(float *v);
@@ -180,6 +188,7 @@ private:
 
 
 	int sampleRate;
 	int sampleRate;
 	int channels;
 	int channels;
+	int bitDepth;
 
 
 	StrongRef<love::sound::Decoder> decoder;
 	StrongRef<love::sound::Decoder> decoder;
 
 

+ 0 - 23
jni/love/src/modules/audio/wrap_Audio.h

@@ -32,29 +32,6 @@ namespace love
 namespace audio
 namespace audio
 {
 {
 
 
-int w_getSourceCount(lua_State *L);
-int w_newSource(lua_State *L);
-int w_play(lua_State *L);
-int w_stop(lua_State *L);
-int w_pause(lua_State *L);
-int w_resume(lua_State *L);
-int w_rewind(lua_State *L);
-int w_setVolume(lua_State *L);
-int w_getVolume(lua_State *L);
-int w_setPosition(lua_State *L);
-int w_getPosition(lua_State *L);
-int w_setOrientation(lua_State *L);
-int w_getOrientation(lua_State *L);
-int w_setVelocity(lua_State *L);
-int w_getVelocity(lua_State *L);
-int w_setDopplerScale(lua_State *L);
-int w_getDopplerScale(lua_State *L);
-int w_record(lua_State *L);
-int w_getRecordedData(lua_State *L);
-int w_stopRecording(lua_State *L);
-int w_canRecord(lua_State *L);
-int w_setDistanceModel(lua_State *L);
-int w_getDistanceModel(lua_State *L);
 extern "C" LOVE_EXPORT int luaopen_love_audio(lua_State *L);
 extern "C" LOVE_EXPORT int luaopen_love_audio(lua_State *L);
 
 
 } // audio
 } // audio

+ 16 - 2
jni/love/src/modules/audio/wrap_Source.cpp

@@ -140,6 +140,19 @@ int w_Source_tell(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
+int w_Source_getDuration(lua_State *L)
+{
+	Source *t = luax_checksource(L, 1);
+
+	Source::Unit u = Source::UNIT_SECONDS;
+	const char *unit = lua_isnoneornil(L, 2) ? 0 : lua_tostring(L, 2);
+	if (unit && !t->getConstant(unit, u))
+		return luaL_error(L, "Invalid Source time unit: %s", unit);
+
+	lua_pushnumber(L, t->getDuration(u));
+	return 1;
+}
+
 int w_Source_setPosition(lua_State *L)
 int w_Source_setPosition(lua_State *L)
 {
 {
 	Source *t = luax_checksource(L, 1);
 	Source *t = luax_checksource(L, 1);
@@ -357,7 +370,7 @@ int w_Source_getType(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
-static const luaL_Reg functions[] =
+static const luaL_Reg w_Source_functions[] =
 {
 {
 	{ "clone", w_Source_clone },
 	{ "clone", w_Source_clone },
 
 
@@ -373,6 +386,7 @@ static const luaL_Reg functions[] =
 	{ "getVolume", w_Source_getVolume },
 	{ "getVolume", w_Source_getVolume },
 	{ "seek", w_Source_seek },
 	{ "seek", w_Source_seek },
 	{ "tell", w_Source_tell },
 	{ "tell", w_Source_tell },
+	{ "getDuration", w_Source_getDuration },
 	{ "setPosition", w_Source_setPosition },
 	{ "setPosition", w_Source_setPosition },
 	{ "getPosition", w_Source_getPosition },
 	{ "getPosition", w_Source_getPosition },
 	{ "setVelocity", w_Source_setVelocity },
 	{ "setVelocity", w_Source_setVelocity },
@@ -406,7 +420,7 @@ static const luaL_Reg functions[] =
 
 
 extern "C" int luaopen_source(lua_State *L)
 extern "C" int luaopen_source(lua_State *L)
 {
 {
-	return luax_register_type(L, AUDIO_SOURCE_ID, functions);
+	return luax_register_type(L, AUDIO_SOURCE_ID, "Source", w_Source_functions, nullptr);
 }
 }
 
 
 } // audio
 } // audio

+ 0 - 35
jni/love/src/modules/audio/wrap_Source.h

@@ -30,41 +30,6 @@ namespace audio
 {
 {
 
 
 Source *luax_checksource(lua_State *L, int idx);
 Source *luax_checksource(lua_State *L, int idx);
-int w_Source_clone(lua_State *L);
-int w_Source_play(lua_State *L);
-int w_Source_stop(lua_State *L);
-int w_Source_pause(lua_State *L);
-int w_Source_resume(lua_State *L);
-int w_Source_rewind(lua_State *L);
-int w_Source_setPitch(lua_State *L);
-int w_Source_getPitch(lua_State *L);
-int w_Source_setVolume(lua_State *L);
-int w_Source_getVolume(lua_State *L);
-int w_Source_seek(lua_State *L);
-int w_Source_tell(lua_State *L);
-int w_Source_setPosition(lua_State *L);
-int w_Source_getPosition(lua_State *L);
-int w_Source_setVelocity(lua_State *L);
-int w_Source_getVelocity(lua_State *L);
-int w_Source_setDirection(lua_State *L);
-int w_Source_getDirection(lua_State *L);
-int w_Source_setCone(lua_State *L);
-int w_Source_getCone(lua_State *L);
-int w_Source_setRelative(lua_State *L);
-int w_Source_isRelative(lua_State *L);
-int w_Source_setLooping(lua_State *L);
-int w_Source_isLooping(lua_State *L);
-int w_Source_isStopped(lua_State *L);
-int w_Source_isPaused(lua_State *L);
-int w_Source_isPlaying(lua_State *L);
-int w_Source_setVolumeLimits(lua_State *L);
-int w_Source_getVolumeLimits(lua_State *L);
-int w_Source_setAttenuationDistances(lua_State *L);
-int w_Source_getAttenuationDistances(lua_State *L);
-int w_Source_setRolloff(lua_State *L);
-int w_Source_getRolloff(lua_State *L);
-int w_Source_getChannels(lua_State *L);
-int w_Source_getType(lua_State *L);
 extern "C" int luaopen_source(lua_State *L);
 extern "C" int luaopen_source(lua_State *L);
 
 
 } // audio
 } // audio

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

@@ -556,12 +556,6 @@ Message *Event::convertWindowEvent(const SDL_Event &e) const
 	{
 	{
 	case SDL_WINDOWEVENT_FOCUS_GAINED:
 	case SDL_WINDOWEVENT_FOCUS_GAINED:
 	case SDL_WINDOWEVENT_FOCUS_LOST:
 	case SDL_WINDOWEVENT_FOCUS_LOST:
-		// Users won't expect the screensaver to activate if a game is in
-		// focus. Also, joystick input may not delay the screensaver timer.
-		if (e.window.event == SDL_WINDOWEVENT_FOCUS_GAINED)
-			SDL_DisableScreenSaver();
-		else
-			SDL_EnableScreenSaver();
 		vargs.push_back(new Variant(e.window.event == SDL_WINDOWEVENT_FOCUS_GAINED));
 		vargs.push_back(new Variant(e.window.event == SDL_WINDOWEVENT_FOCUS_GAINED));
 		msg = new Message("focus", vargs);
 		msg = new Message("focus", vargs);
 		break;
 		break;

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

@@ -141,7 +141,7 @@ extern "C" int luaopen_love_event(lua_State *L)
 	w.name = "event";
 	w.name = "event";
 	w.type = MODULE_ID;
 	w.type = MODULE_ID;
 	w.functions = functions;
 	w.functions = functions;
-	w.types = 0;
+	w.types = nullptr;
 
 
 	return luax_register_module(L, w);
 	return luax_register_module(L, w);
 }
 }

+ 0 - 7
jni/love/src/modules/event/wrap_Event.h

@@ -30,13 +30,6 @@ namespace love
 namespace event
 namespace event
 {
 {
 
 
-int w_pump(lua_State *L);
-int w_poll(lua_State *L);
-int w_wait(lua_State *L);
-int w_push(lua_State *L);
-int w_clear(lua_State *L);
-int w_quit(lua_State *L);
-
 extern "C" LOVE_EXPORT int luaopen_love_event(lua_State *L);
 extern "C" LOVE_EXPORT int luaopen_love_event(lua_State *L);
 
 
 } // event
 } // event

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

@@ -125,12 +125,8 @@ void Filesystem::init(const char *arg0)
 	if (!PHYSFS_init(arg0))
 	if (!PHYSFS_init(arg0))
 		throw love::Exception("%s", PHYSFS_getLastError());
 		throw love::Exception("%s", PHYSFS_getLastError());
 
 
-	PHYSFS_Version version = {};
-	PHYSFS_getLinkedVersion(&version);
-
-	// FIXME: This is a workaround for a bug in PHYSFS_enumerateFiles in 2.1-alpha.
-	if (version.major == 2 && version.minor == 1)
-		PHYSFS_permitSymbolicLinks(1);
+	// Enable symlinks by default. Also fixes an issue in PhysFS 2.1-alpha.
+	setSymlinksEnabled(true);
 }
 }
 
 
 void Filesystem::setFused(bool fused)
 void Filesystem::setFused(bool fused)
@@ -223,7 +219,7 @@ 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 ())
+	if (!love::android::createStorageDirectories())
 		SDL_Log("Error creating storage directories!");
 		SDL_Log("Error creating storage directories!");
 
 
 	char* game_archive_ptr = NULL;
 	char* game_archive_ptr = NULL;
@@ -664,6 +660,17 @@ int64 Filesystem::getSize(const char *filename) const
 
 
 void Filesystem::setSymlinksEnabled(bool enable)
 void Filesystem::setSymlinksEnabled(bool enable)
 {
 {
+	if (!enable)
+	{
+		PHYSFS_Version version = {};
+		PHYSFS_getLinkedVersion(&version);
+
+		// FIXME: This is a workaround for a bug in PHYSFS_enumerateFiles in
+		// PhysFS 2.1-alpha.
+		if (version.major == 2 && version.minor == 1)
+			return;
+	}
+
 	PHYSFS_permitSymbolicLinks(enable ? 1 : 0);
 	PHYSFS_permitSymbolicLinks(enable ? 1 : 0);
 }
 }
 
 

+ 1 - 23
jni/love/src/modules/filesystem/wrap_DroppedFile.cpp

@@ -31,31 +31,9 @@ DroppedFile *luax_checkdroppedfile(lua_State *L, int idx)
 	return luax_checktype<DroppedFile>(L, idx, FILESYSTEM_DROPPED_FILE_ID);
 	return luax_checktype<DroppedFile>(L, idx, FILESYSTEM_DROPPED_FILE_ID);
 }
 }
 
 
-static const luaL_Reg functions[] =
-{
-	// Inherits from File.
-	{ "getSize", w_File_getSize },
-	{ "open", w_File_open },
-	{ "close", w_File_close },
-	{ "isOpen", w_File_isOpen },
-	{ "read", w_File_read },
-	{ "write", w_File_write },
-	{ "flush", w_File_flush },
-	{ "isEOF", w_File_isEOF },
-	{ "tell", w_File_tell },
-	{ "seek", w_File_seek },
-	{ "lines", w_File_lines },
-	{ "setBuffer", w_File_setBuffer },
-	{ "getBuffer", w_File_getBuffer },
-	{ "getMode", w_File_getMode },
-	{ "getFilename", w_File_getFilename },
-	{ "getExtension", w_File_getExtension },
-	{ 0, 0 }
-};
-
 extern "C" int luaopen_droppedfile(lua_State *L)
 extern "C" int luaopen_droppedfile(lua_State *L)
 {
 {
-	return luax_register_type(L, FILESYSTEM_DROPPED_FILE_ID, functions);
+	return luax_register_type(L, FILESYSTEM_DROPPED_FILE_ID, "DroppedFile", w_File_functions, nullptr);
 }
 }
 
 
 } // filesystem
 } // filesystem

+ 2 - 2
jni/love/src/modules/filesystem/wrap_File.cpp

@@ -413,7 +413,7 @@ int w_File_getExtension(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
-static const luaL_Reg functions[] =
+const luaL_Reg w_File_functions[] =
 {
 {
 	{ "getSize", w_File_getSize },
 	{ "getSize", w_File_getSize },
 	{ "open", w_File_open },
 	{ "open", w_File_open },
@@ -436,7 +436,7 @@ static const luaL_Reg functions[] =
 
 
 extern "C" int luaopen_file(lua_State *L)
 extern "C" int luaopen_file(lua_State *L)
 {
 {
-	return luax_register_type(L, FILESYSTEM_FILE_ID, functions);
+	return luax_register_type(L, FILESYSTEM_FILE_ID, "File", w_File_functions, nullptr);
 }
 }
 
 
 } // filesystem
 } // filesystem

+ 2 - 16
jni/love/src/modules/filesystem/wrap_File.h

@@ -34,25 +34,11 @@ namespace filesystem
 int luax_ioError(lua_State *L, const char *fmt, ...);
 int luax_ioError(lua_State *L, const char *fmt, ...);
 
 
 File *luax_checkfile(lua_State *L, int idx);
 File *luax_checkfile(lua_State *L, int idx);
-int w_File_getSize(lua_State *L);
-int w_File_open(lua_State *L);
-int w_File_close(lua_State *L);
-int w_File_isOpen(lua_State *L);
-int w_File_read(lua_State *L);
-int w_File_write(lua_State *L);
-int w_File_flush(lua_State *L);
-int w_File_isEOF(lua_State *L);
-int w_File_tell(lua_State *L);
-int w_File_seek(lua_State *L);
 int w_File_lines_i(lua_State *L);
 int w_File_lines_i(lua_State *L);
-int w_File_lines(lua_State *L);
-int w_File_setBuffer(lua_State *L);
-int w_File_getBuffer(lua_State *L);
-int w_File_getMode(lua_State *L);
-int w_File_getFilename(lua_State *L);
-int w_File_getExtension(lua_State *L);
 extern "C" int luaopen_file(lua_State *L);
 extern "C" int luaopen_file(lua_State *L);
 
 
+extern const luaL_Reg w_File_functions[];
+
 } // filesystem
 } // filesystem
 } // love
 } // love
 
 

+ 1 - 6
jni/love/src/modules/filesystem/wrap_FileData.cpp

@@ -48,11 +48,6 @@ int w_FileData_getExtension(lua_State *L)
 
 
 static const luaL_Reg w_FileData_functions[] =
 static const luaL_Reg w_FileData_functions[] =
 {
 {
-	// Data
-	{ "getString", w_Data_getString },
-	{ "getPointer", w_Data_getPointer },
-	{ "getSize", w_Data_getSize },
-
 	{ "getFilename", w_FileData_getFilename },
 	{ "getFilename", w_FileData_getFilename },
 	{ "getExtension", w_FileData_getExtension },
 	{ "getExtension", w_FileData_getExtension },
 
 
@@ -61,7 +56,7 @@ static const luaL_Reg w_FileData_functions[] =
 
 
 extern "C" int luaopen_filedata(lua_State *L)
 extern "C" int luaopen_filedata(lua_State *L)
 {
 {
-	return luax_register_type(L, FILESYSTEM_FILE_DATA_ID, w_FileData_functions);
+	return luax_register_type(L, FILESYSTEM_FILE_DATA_ID, "FileData", w_Data_functions, w_FileData_functions, nullptr);
 }
 }
 
 
 } // filesystem
 } // filesystem

+ 0 - 2
jni/love/src/modules/filesystem/wrap_FileData.h

@@ -31,8 +31,6 @@ namespace filesystem
 {
 {
 
 
 FileData *luax_checkfiledata(lua_State *L, int idx);
 FileData *luax_checkfiledata(lua_State *L, int idx);
-int w_FileData_getFilename(lua_State *L);
-int w_FileData_getExtension(lua_State *L);
 extern "C" int luaopen_filedata(lua_State *L);
 extern "C" int luaopen_filedata(lua_State *L);
 
 
 } // filesystem
 } // filesystem

+ 14 - 5
jni/love/src/modules/filesystem/wrap_Filesystem.cpp

@@ -155,19 +155,28 @@ int w_newFile(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
-FileData *luax_getfiledata(lua_State *L, int idx)
+File *luax_getfile(lua_State *L, int idx)
 {
 {
-	FileData *data = nullptr;
 	File *file = nullptr;
 	File *file = nullptr;
-
 	if (lua_isstring(L, idx))
 	if (lua_isstring(L, idx))
 	{
 	{
 		const char *filename = luaL_checkstring(L, idx);
 		const char *filename = luaL_checkstring(L, idx);
 		file = instance()->newFile(filename);
 		file = instance()->newFile(filename);
 	}
 	}
-	else if (luax_istype(L, idx, FILESYSTEM_FILE_ID))
-	{
+	else
 		file = luax_checkfile(L, idx);
 		file = luax_checkfile(L, idx);
+
+	return file;
+}
+
+FileData *luax_getfiledata(lua_State *L, int idx)
+{
+	FileData *data = nullptr;
+	File *file = nullptr;
+
+	if (lua_isstring(L, idx) || luax_istype(L, idx, FILESYSTEM_FILE_ID))
+	{
+		file = luax_getfile(L, idx);
 		file->retain();
 		file->retain();
 	}
 	}
 	else if (luax_istype(L, idx, FILESYSTEM_FILE_DATA_ID))
 	else if (luax_istype(L, idx, FILESYSTEM_FILE_DATA_ID))

+ 2 - 37
jni/love/src/modules/filesystem/wrap_Filesystem.h

@@ -23,6 +23,7 @@
 
 
 // LOVE
 // LOVE
 #include "common/runtime.h"
 #include "common/runtime.h"
+#include "File.h"
 #include "FileData.h"
 #include "FileData.h"
 
 
 namespace love
 namespace love
@@ -38,45 +39,9 @@ namespace filesystem
  * May trigger a Lua error.
  * May trigger a Lua error.
  **/
  **/
 FileData *luax_getfiledata(lua_State *L, int idx);
 FileData *luax_getfiledata(lua_State *L, int idx);
+File *luax_getfile(lua_State *L, int idx);
 
 
 bool hack_setupWriteDirectory();
 bool hack_setupWriteDirectory();
-int w_init(lua_State *L);
-int w_setFused(lua_State *L);
-int w_isFused(lua_State *L);
-int w_setIdentity(lua_State *L);
-int w_getIdentity(lua_State *L);
-int w_setSource(lua_State *L);
-int w_getSource(lua_State *L);
-int w_mount(lua_State *L);
-int w_unmount(lua_State *L);
-int w_newFile(lua_State *L);
-int w_newFileData(lua_State *L);
-int w_getWorkingDirectory(lua_State *L);
-int w_getUserDirectory(lua_State *L);
-int w_getAppdataDirectory(lua_State *L);
-int w_getSaveDirectory(lua_State *L);
-int w_getSourceBaseDirectory(lua_State *L);
-int w_getRealDirectory(lua_State *L);
-int w_getExecutablePath(lua_State *L);
-int w_isDirectory(lua_State *L);
-int w_isFile(lua_State *L);
-int w_createDirectory(lua_State *L);
-int w_remove(lua_State *L);
-int w_open(lua_State *L);
-int w_close(lua_State *L);
-int w_read(lua_State *L);
-int w_write(lua_State *L);
-int w_append(lua_State *L);
-int w_getDirectoryItems(lua_State *L);
-int w_lines(lua_State *L);
-int w_load(lua_State *L);
-int w_getLastModified(lua_State *L);
-int w_getSize(lua_State *L);
-int w_setSymlinksEnabled(lua_State *L);
-int w_areSymlinksEnabled(lua_State *L);
-int w_isSymlink(lua_State *L);
-int w_getRequirePath(lua_State *L);
-int w_setRequirePath(lua_State *L);
 int loader(lua_State *L);
 int loader(lua_State *L);
 int extloader(lua_State *L);
 int extloader(lua_State *L);
 extern "C" LOVE_EXPORT int luaopen_love_filesystem(lua_State *L);
 extern "C" LOVE_EXPORT int luaopen_love_filesystem(lua_State *L);

+ 4 - 4
jni/love/src/modules/font/freetype/TrueTypeRasterizer.cpp

@@ -116,9 +116,9 @@ GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const
 	// We treat the luminance of the FreeType bitmap as alpha in the GlyphData.
 	// We treat the luminance of the FreeType bitmap as alpha in the GlyphData.
 	if (bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
 	if (bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
 	{
 	{
-		for (int y = 0; y < bitmap.rows; y++)
+		for (int y = 0; y < (int) bitmap.rows; y++)
 		{
 		{
-			for (int x = 0; x < bitmap.width; x++)
+			for (int x = 0; x < (int) bitmap.width; x++)
 			{
 			{
 				// Extract the 1-bit value and convert it to uint8.
 				// Extract the 1-bit value and convert it to uint8.
 				uint8 v = ((pixels[x / 8]) & (1 << (7 - (x % 8)))) ? 255 : 0;
 				uint8 v = ((pixels[x / 8]) & (1 << (7 - (x % 8)))) ? 255 : 0;
@@ -131,9 +131,9 @@ GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const
 	}
 	}
 	else if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY)
 	else if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY)
 	{
 	{
-		for (int y = 0; y < bitmap.rows; y++)
+		for (int y = 0; y < (int) bitmap.rows; y++)
 		{
 		{
-			for (int x = 0; x < bitmap.width; x++)
+			for (int x = 0; x < (int) bitmap.width; x++)
 			{
 			{
 				dest[2 * (y * bitmap.width + x) + 0] = 255;
 				dest[2 * (y * bitmap.width + x) + 0] = 255;
 				dest[2 * (y * bitmap.width + x) + 1] = pixels[x];
 				dest[2 * (y * bitmap.width + x) + 1] = pixels[x];

+ 2 - 7
jni/love/src/modules/font/wrap_GlyphData.cpp

@@ -115,13 +115,8 @@ int w_GlyphData_getFormat(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
-static const luaL_Reg functions[] =
+const luaL_Reg w_GlyphData_functions[] =
 {
 {
-	// Data
-	{ "getString", w_Data_getString },
-	{ "getPointer", w_Data_getPointer },
-	{ "getSize", w_Data_getSize },
-
 	{ "getWidth", w_GlyphData_getWidth },
 	{ "getWidth", w_GlyphData_getWidth },
 	{ "getHeight", w_GlyphData_getHeight },
 	{ "getHeight", w_GlyphData_getHeight },
 	{ "getDimensions", w_GlyphData_getDimensions },
 	{ "getDimensions", w_GlyphData_getDimensions },
@@ -136,7 +131,7 @@ static const luaL_Reg functions[] =
 
 
 extern "C" int luaopen_glyphdata(lua_State *L)
 extern "C" int luaopen_glyphdata(lua_State *L)
 {
 {
-	return luax_register_type(L, FONT_GLYPH_DATA_ID, functions);
+	return luax_register_type(L, FONT_GLYPH_DATA_ID, "GlyphData", w_Data_functions, w_GlyphData_functions, nullptr);
 }
 }
 
 
 } // font
 } // font

+ 0 - 9
jni/love/src/modules/font/wrap_GlyphData.h

@@ -33,15 +33,6 @@ namespace font
 {
 {
 
 
 GlyphData *luax_checkglyphdata(lua_State *L, int idx);
 GlyphData *luax_checkglyphdata(lua_State *L, int idx);
-int w_GlyphData_getWidth(lua_State *L);
-int w_GlyphData_getHeight(lua_State *L);
-int w_GlyphData_getDimensions(lua_State *L);
-int w_GlyphData_getGlyph(lua_State *L);
-int w_GlyphData_getGlyphString(lua_State *L);
-int w_GlyphData_getAdvance(lua_State *L);
-int w_GlyphData_getBearing(lua_State *L);
-int w_GlyphData_getBoundingBox(lua_State *L);
-int w_GlyphData_getFormat(lua_State *L);
 extern "C" int luaopen_glyphdata(lua_State *L);
 extern "C" int luaopen_glyphdata(lua_State *L);
 
 
 } // font
 } // font

+ 2 - 2
jni/love/src/modules/font/wrap_Rasterizer.cpp

@@ -124,7 +124,7 @@ int w_Rasterizer_hasGlyphs(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
-static const luaL_Reg functions[] =
+const luaL_Reg w_Rasterizer_functions[] =
 {
 {
 	{ "getHeight", w_Rasterizer_getHeight },
 	{ "getHeight", w_Rasterizer_getHeight },
 	{ "getAdvance", w_Rasterizer_getAdvance },
 	{ "getAdvance", w_Rasterizer_getAdvance },
@@ -139,7 +139,7 @@ static const luaL_Reg functions[] =
 
 
 extern "C" int luaopen_rasterizer(lua_State *L)
 extern "C" int luaopen_rasterizer(lua_State *L)
 {
 {
-	return luax_register_type(L, FONT_RASTERIZER_ID, functions);
+	return luax_register_type(L, FONT_RASTERIZER_ID, "Rasterizer", w_Rasterizer_functions, nullptr);
 }
 }
 
 
 } // font
 } // font

+ 0 - 8
jni/love/src/modules/font/wrap_Rasterizer.h

@@ -31,14 +31,6 @@ namespace font
 {
 {
 
 
 Rasterizer *luax_checkrasterizer(lua_State *L, int idx);
 Rasterizer *luax_checkrasterizer(lua_State *L, int idx);
-int w_Rasterizer_getHeight(lua_State *L);
-int w_Rasterizer_getAdvance(lua_State *L);
-int w_Rasterizer_getAscent(lua_State *L);
-int w_Rasterizer_getDescent(lua_State *L);
-int w_Rasterizer_getLineHeight(lua_State *L);
-int w_Rasterizer_getGlyphData(lua_State *L);
-int w_Rasterizer_getGlyphCount(lua_State *L);
-int w_Rasterizer_hasGlyphs(lua_State *L);
 extern "C" int luaopen_rasterizer(lua_State *L);
 extern "C" int luaopen_rasterizer(lua_State *L);
 
 
 } // font
 } // font

+ 15 - 0
jni/love/src/modules/graphics/Color.h

@@ -44,11 +44,26 @@ struct ColorT
 		a = a_;
 		a = a_;
 	}
 	}
 
 
+	bool operator==(const ColorT<T> &other) const;
+	bool operator!=(const ColorT<T> &other) const;
+
 	ColorT<T> operator+=(const ColorT<T> &other);
 	ColorT<T> operator+=(const ColorT<T> &other);
 	ColorT<T> operator*=(T s);
 	ColorT<T> operator*=(T s);
 	ColorT<T> operator/=(T s);
 	ColorT<T> operator/=(T s);
 };
 };
 
 
+template <typename T>
+bool ColorT<T>::operator==(const ColorT<T> &other) const
+{
+	return r == other.r && g == other.g && b == other.b && a == other.a;
+}
+
+template <typename T>
+bool ColorT<T>::operator!=(const ColorT<T> &other) const
+{
+	return !(operator==(other));
+}
+
 template <typename T>
 template <typename T>
 ColorT<T> ColorT<T>::operator+=(const ColorT<T> &other)
 ColorT<T> ColorT<T>::operator+=(const ColorT<T> &other)
 {
 {

+ 1 - 1
jni/love/src/modules/graphics/Graphics.cpp

@@ -181,8 +181,8 @@ StringMap<Graphics::LineJoin, Graphics::LINE_JOIN_MAX_ENUM> Graphics::lineJoins(
 
 
 StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM>::Entry Graphics::supportEntries[] =
 StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM>::Entry Graphics::supportEntries[] =
 {
 {
-	{ "multicanvas", SUPPORT_MULTI_CANVAS },
 	{ "multicanvasformats", SUPPORT_MULTI_CANVAS_FORMATS },
 	{ "multicanvasformats", SUPPORT_MULTI_CANVAS_FORMATS },
+	{ "clampzero", SUPPORT_CLAMP_ZERO },
 };
 };
 
 
 StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM> Graphics::support(Graphics::supportEntries, sizeof(Graphics::supportEntries));
 StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM> Graphics::support(Graphics::supportEntries, sizeof(Graphics::supportEntries));

+ 12 - 1
jni/love/src/modules/graphics/Graphics.h

@@ -98,8 +98,8 @@ public:
 
 
 	enum Support
 	enum Support
 	{
 	{
-		SUPPORT_MULTI_CANVAS,
 		SUPPORT_MULTI_CANVAS_FORMATS,
 		SUPPORT_MULTI_CANVAS_FORMATS,
+		SUPPORT_CLAMP_ZERO,
 		SUPPORT_MAX_ENUM
 		SUPPORT_MAX_ENUM
 	};
 	};
 
 
@@ -178,6 +178,17 @@ public:
 		}
 		}
 	};
 	};
 
 
+	struct ScissorRect
+	{
+		int x, y;
+		int w, h;
+
+		bool operator == (const ScissorRect &rhs) const
+		{
+			return x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h;
+		}
+	};
+
 	virtual ~Graphics();
 	virtual ~Graphics();
 
 
 	// Implements Module.
 	// Implements Module.

+ 986 - 0
jni/love/src/modules/graphics/ParticleSystem.cpp

@@ -0,0 +1,986 @@
+/**
+ * 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.
+ **/
+
+//LOVE
+#include "common/config.h"
+#include "ParticleSystem.h"
+
+#include "common/math.h"
+#include "modules/math/RandomGenerator.h"
+
+// STD
+#include <algorithm>
+#include <cmath>
+#include <cstdlib>
+
+namespace love
+{
+namespace graphics
+{
+
+namespace
+{
+
+love::math::RandomGenerator rng;
+
+float calculate_variation(float inner, float outer, float var)
+{
+	float low = inner - (outer/2.0f)*var;
+	float high = inner + (outer/2.0f)*var;
+	float r = (float) rng.random();
+	return low*(1-r)+high*r;
+}
+
+} // anonymous namespace
+
+ParticleSystem::ParticleSystem(Texture *texture, uint32 size)
+	: pMem(nullptr)
+	, pFree(nullptr)
+	, pHead(nullptr)
+	, pTail(nullptr)
+	, texture(texture)
+	, active(true)
+	, insertMode(INSERT_MODE_TOP)
+	, maxParticles(0)
+	, activeParticles(0)
+	, emissionRate(0)
+	, emitCounter(0)
+	, areaSpreadDistribution(DISTRIBUTION_NONE)
+	, lifetime(-1)
+	, life(0)
+	, particleLifeMin(0)
+	, particleLifeMax(0)
+	, direction(0)
+	, spread(0)
+	, speedMin(0)
+	, speedMax(0)
+	, linearAccelerationMin(0, 0)
+	, linearAccelerationMax(0, 0)
+	, radialAccelerationMin(0)
+	, radialAccelerationMax(0)
+	, tangentialAccelerationMin(0)
+	, tangentialAccelerationMax(0)
+	, linearDampingMin(0.0f)
+	, linearDampingMax(0.0f)
+	, sizeVariation(0)
+	, rotationMin(0)
+	, rotationMax(0)
+	, spinStart(0)
+	, spinEnd(0)
+	, spinVariation(0)
+	, offset(float(texture->getWidth())*0.5f, float(texture->getHeight())*0.5f)
+	, defaultOffset(true)
+	, relativeRotation(false)
+{
+	if (size == 0 || size > MAX_PARTICLES)
+		throw love::Exception("Invalid ParticleSystem size.");
+
+	sizes.push_back(1.0f);
+	colors.push_back(Colorf(1.0f, 1.0f, 1.0f, 1.0f));
+	setBufferSize(size);
+}
+
+ParticleSystem::ParticleSystem(const ParticleSystem &p)
+	: pMem(nullptr)
+	, pFree(nullptr)
+	, pHead(nullptr)
+	, pTail(nullptr)
+	, texture(p.texture)
+	, active(p.active)
+	, insertMode(p.insertMode)
+	, maxParticles(p.maxParticles)
+	, activeParticles(0)
+	, emissionRate(p.emissionRate)
+	, emitCounter(0.0f)
+	, position(p.position)
+	, prevPosition(p.prevPosition)
+	, areaSpreadDistribution(p.areaSpreadDistribution)
+	, areaSpread(p.areaSpread)
+	, lifetime(p.lifetime)
+	, life(p.lifetime) // Initialize with the maximum life time.
+	, particleLifeMin(p.particleLifeMin)
+	, particleLifeMax(p.particleLifeMax)
+	, direction(p.direction)
+	, spread(p.spread)
+	, speedMin(p.speedMin)
+	, speedMax(p.speedMax)
+	, linearAccelerationMin(p.linearAccelerationMin)
+	, linearAccelerationMax(p.linearAccelerationMax)
+	, radialAccelerationMin(p.radialAccelerationMin)
+	, radialAccelerationMax(p.radialAccelerationMax)
+	, tangentialAccelerationMin(p.tangentialAccelerationMin)
+	, tangentialAccelerationMax(p.tangentialAccelerationMax)
+	, linearDampingMin(p.linearDampingMin)
+	, linearDampingMax(p.linearDampingMax)
+	, sizes(p.sizes)
+	, sizeVariation(p.sizeVariation)
+	, rotationMin(p.rotationMin)
+	, rotationMax(p.rotationMax)
+	, spinStart(p.spinStart)
+	, spinEnd(p.spinEnd)
+	, spinVariation(p.spinVariation)
+	, offset(p.offset)
+	, defaultOffset(p.defaultOffset)
+	, colors(p.colors)
+	, quads(p.quads)
+	, relativeRotation(p.relativeRotation)
+{
+	setBufferSize(maxParticles);
+}
+
+ParticleSystem::~ParticleSystem()
+{
+	deleteBuffers();
+}
+
+void ParticleSystem::resetOffset()
+{
+	if (quads.empty())
+		offset = love::Vector(float(texture->getWidth())*0.5f, float(texture->getHeight())*0.5f);
+	else
+	{
+		Quad::Viewport v = quads[0]->getViewport();
+		offset = love::Vector(v.x*0.5f, v.y*0.5f);
+	}
+}
+
+void ParticleSystem::createBuffers(size_t size)
+{
+	try
+	{
+		pFree = pMem = new Particle[size];
+		maxParticles = (uint32) size;
+	}
+	catch (std::bad_alloc &)
+	{
+		deleteBuffers();
+		throw love::Exception("Out of memory");
+	}
+}
+
+void ParticleSystem::deleteBuffers()
+{
+	// Clean up for great gracefulness!
+	delete[] pMem;
+
+	pMem = nullptr;
+	maxParticles = 0;
+	activeParticles = 0;
+}
+
+void ParticleSystem::setBufferSize(uint32 size)
+{
+	if (size == 0 || size > MAX_PARTICLES)
+		throw love::Exception("Invalid buffer size");
+	deleteBuffers();
+	createBuffers(size);
+	reset();
+}
+
+uint32 ParticleSystem::getBufferSize() const
+{
+	return maxParticles;
+}
+
+void ParticleSystem::addParticle(float t)
+{
+	if (isFull())
+		return;
+
+	// Gets a free particle and updates the allocation pointer.
+	Particle *p = pFree++;
+	initParticle(p, t);
+
+	switch (insertMode)
+	{
+	default:
+	case INSERT_MODE_TOP:
+		insertTop(p);
+		break;
+	case INSERT_MODE_BOTTOM:
+		insertBottom(p);
+		break;
+	case INSERT_MODE_RANDOM:
+		insertRandom(p);
+		break;
+	}
+
+	activeParticles++;
+}
+
+void ParticleSystem::initParticle(Particle *p, float t)
+{
+	float min,max;
+
+	// Linearly interpolate between the previous and current emitter position.
+	love::Vector pos = prevPosition + (position - prevPosition) * t;
+
+	min = particleLifeMin;
+	max = particleLifeMax;
+	if (min == max)
+		p->life = min;
+	else
+		p->life = (float) rng.random(min, max);
+	p->lifetime = p->life;
+
+	p->position = pos;
+
+	switch (areaSpreadDistribution)
+	{
+	case DISTRIBUTION_UNIFORM:
+		p->position.x += (float) rng.random(-areaSpread.getX(), areaSpread.getX());
+		p->position.y += (float) rng.random(-areaSpread.getY(), areaSpread.getY());
+		break;
+	case DISTRIBUTION_NORMAL:
+		p->position.x += (float) rng.randomNormal(areaSpread.getX());
+		p->position.y += (float) rng.randomNormal(areaSpread.getY());
+		break;
+	case DISTRIBUTION_NONE:
+	default:
+		break;
+	}
+
+	p->origin = pos;
+
+	min = speedMin;
+	max = speedMax;
+	float speed = (float) rng.random(min, max);
+
+	min = direction - spread/2.0f;
+	max = direction + spread/2.0f;
+	float dir = (float) rng.random(min, max);
+
+	p->velocity = love::Vector(cosf(dir), sinf(dir)) * speed;
+
+	p->linearAcceleration.x = (float) rng.random(linearAccelerationMin.x, linearAccelerationMax.x);
+	p->linearAcceleration.y = (float) rng.random(linearAccelerationMin.y, linearAccelerationMax.y);
+
+	min = radialAccelerationMin;
+	max = radialAccelerationMax;
+	p->radialAcceleration = (float) rng.random(min, max);
+
+	min = tangentialAccelerationMin;
+	max = tangentialAccelerationMax;
+	p->tangentialAcceleration = (float) rng.random(min, max);
+
+	min = linearDampingMin;
+	max = linearDampingMax;
+	p->linearDamping = (float) rng.random(min, max);
+
+	p->sizeOffset       = (float) rng.random(sizeVariation); // time offset for size change
+	p->sizeIntervalSize = (1.0f - (float) rng.random(sizeVariation)) - p->sizeOffset;
+	p->size = sizes[(size_t)(p->sizeOffset - .5f) * (sizes.size() - 1)];
+
+	min = rotationMin;
+	max = rotationMax;
+	p->spinStart = calculate_variation(spinStart, spinEnd, spinVariation);
+	p->spinEnd = calculate_variation(spinEnd, spinStart, spinVariation);
+	p->rotation = (float) rng.random(min, max);
+
+	p->angle = p->rotation;
+	if (relativeRotation)
+		p->angle += atan2f(p->velocity.y, p->velocity.x);
+
+	p->color = colors[0];
+
+	p->quadIndex = 0;
+}
+
+void ParticleSystem::insertTop(Particle *p)
+{
+	if (pHead == nullptr)
+	{
+		pHead = p;
+		p->prev = nullptr;
+	}
+	else
+	{
+		pTail->next = p;
+		p->prev = pTail;
+	}
+	p->next = nullptr;
+	pTail = p;
+}
+
+void ParticleSystem::insertBottom(Particle *p)
+{
+	if (pTail == nullptr)
+	{
+		pTail = p;
+		p->next = nullptr;
+	}
+	else
+	{
+		pHead->prev = p;
+		p->next = pHead;
+	}
+	p->prev = nullptr;
+	pHead = p;
+}
+
+void ParticleSystem::insertRandom(Particle *p)
+{
+	// Nonuniform, but 64-bit is so large nobody will notice. Hopefully.
+	uint64 pos = rng.rand() % ((int64) activeParticles + 1);
+
+	// Special case where the particle gets inserted before the head.
+	if (pos == activeParticles)
+	{
+		Particle *pA = pHead;
+		if (pA)
+			pA->prev = p;
+		p->prev = nullptr;
+		p->next = pA;
+		pHead = p;
+		return;
+	}
+
+	// Inserts the particle after the randomly selected particle.
+	Particle *pA = pMem + pos;
+	Particle *pB = pA->next;
+	pA->next = p;
+	if (pB)
+		pB->prev = p;
+	else
+		pTail = p;
+	p->prev = pA;
+	p->next = pB;
+}
+
+ParticleSystem::Particle *ParticleSystem::removeParticle(Particle *p)
+{
+	// The linked list is updated in this function and old pointers may be
+	// invalidated. The returned pointer will inform the caller of the new
+	// pointer to the next particle.
+	Particle *pNext = nullptr;
+
+	// Removes the particle from the linked list.
+	if (p->prev)
+		p->prev->next = p->next;
+	else
+		pHead = p->next;
+
+	if (p->next)
+	{
+		p->next->prev = p->prev;
+		pNext = p->next;
+	}
+	else
+		pTail = p->prev;
+
+	// The (in memory) last particle can now be moved into the free slot.
+	// It will skip the moving if it happens to be the removed particle.
+	pFree--;
+	if (p != pFree)
+	{
+		*p = *pFree;
+		if (pNext == pFree)
+			pNext = p;
+
+		if (p->prev)
+			p->prev->next = p;
+		else
+			pHead = p;
+
+		if (p->next)
+			p->next->prev = p;
+		else
+			pTail = p;
+	}
+
+	activeParticles--;
+	return pNext;
+}
+
+void ParticleSystem::setTexture(Texture *tex)
+{
+	texture.set(tex);
+
+	if (defaultOffset)
+		resetOffset();
+}
+
+Texture *ParticleSystem::getTexture() const
+{
+	return texture.get();
+}
+
+void ParticleSystem::setInsertMode(InsertMode mode)
+{
+	insertMode = mode;
+}
+
+ParticleSystem::InsertMode ParticleSystem::getInsertMode() const
+{
+	return insertMode;
+}
+
+void ParticleSystem::setEmissionRate(float rate)
+{
+	if (rate < 0.0f)
+		throw love::Exception("Invalid emission rate");
+	emissionRate = rate;
+}
+
+float ParticleSystem::getEmissionRate() const
+{
+	return emissionRate;
+}
+
+void ParticleSystem::setEmitterLifetime(float life)
+{
+	this->life = lifetime = life;
+}
+
+float ParticleSystem::getEmitterLifetime() const
+{
+	return lifetime;
+}
+
+void ParticleSystem::setParticleLifetime(float min, float max)
+{
+	particleLifeMin = min;
+	if (max == 0)
+		particleLifeMax = min;
+	else
+		particleLifeMax = max;
+}
+
+void ParticleSystem::getParticleLifetime(float &min, float &max) const
+{
+	min = particleLifeMin;
+	max = particleLifeMax;
+}
+
+void ParticleSystem::setPosition(float x, float y)
+{
+	position = love::Vector(x, y);
+	prevPosition = position;
+}
+
+const love::Vector &ParticleSystem::getPosition() const
+{
+	return position;
+}
+
+void ParticleSystem::moveTo(float x, float y)
+{
+	position = love::Vector(x, y);
+}
+
+void ParticleSystem::setAreaSpread(AreaSpreadDistribution distribution, float x, float y)
+{
+	areaSpread = love::Vector(x, y);
+	areaSpreadDistribution = distribution;
+}
+
+ParticleSystem::AreaSpreadDistribution ParticleSystem::getAreaSpreadDistribution() const
+{
+	return areaSpreadDistribution;
+}
+
+const love::Vector &ParticleSystem::getAreaSpreadParameters() const
+{
+	return areaSpread;
+}
+
+void ParticleSystem::setDirection(float direction)
+{
+	this->direction = direction;
+}
+
+float ParticleSystem::getDirection() const
+{
+	return direction;
+}
+
+void ParticleSystem::setSpread(float spread)
+{
+	this->spread = spread;
+}
+
+float ParticleSystem::getSpread() const
+{
+	return spread;
+}
+
+void ParticleSystem::setSpeed(float speed)
+{
+	speedMin = speedMax = speed;
+}
+
+void ParticleSystem::setSpeed(float min, float max)
+{
+	speedMin = min;
+	speedMax = max;
+}
+
+void ParticleSystem::getSpeed(float &min, float &max) const
+{
+	min = speedMin;
+	max = speedMax;
+}
+
+void ParticleSystem::setLinearAcceleration(float x, float y)
+{
+	linearAccelerationMin.x = linearAccelerationMax.x = x;
+	linearAccelerationMin.y = linearAccelerationMax.y = y;
+}
+
+void ParticleSystem::setLinearAcceleration(float xmin, float ymin, float xmax, float ymax)
+{
+	linearAccelerationMin = love::Vector(xmin, ymin);
+	linearAccelerationMax = love::Vector(xmax, ymax);
+}
+
+void ParticleSystem::getLinearAcceleration(love::Vector &min, love::Vector &max) const
+{
+	min = linearAccelerationMin;
+	max = linearAccelerationMax;
+}
+
+void ParticleSystem::setRadialAcceleration(float acceleration)
+{
+	radialAccelerationMin = radialAccelerationMax = acceleration;
+}
+
+void ParticleSystem::setRadialAcceleration(float min, float max)
+{
+	radialAccelerationMin = min;
+	radialAccelerationMax = max;
+}
+
+void ParticleSystem::getRadialAcceleration(float &min, float &max) const
+{
+	min = radialAccelerationMin;
+	max = radialAccelerationMax;
+}
+
+void ParticleSystem::setTangentialAcceleration(float acceleration)
+{
+	tangentialAccelerationMin = tangentialAccelerationMax = acceleration;
+}
+
+void ParticleSystem::setTangentialAcceleration(float min, float max)
+{
+	tangentialAccelerationMin = min;
+	tangentialAccelerationMax = max;
+}
+
+void ParticleSystem::getTangentialAcceleration(float &min, float &max) const
+{
+	min = tangentialAccelerationMin;
+	max = tangentialAccelerationMax;
+}
+
+void ParticleSystem::setLinearDamping(float min, float max)
+{
+	linearDampingMin = min;
+	linearDampingMax = max;
+}
+
+void ParticleSystem::getLinearDamping(float &min, float &max) const
+{
+	min = linearDampingMin;
+	max = linearDampingMax;
+}
+
+void ParticleSystem::setSize(float size)
+{
+	sizes.resize(1);
+	sizes[0] = size;
+}
+
+void ParticleSystem::setSizes(const std::vector<float> &newSizes)
+{
+	sizes = newSizes;
+}
+
+const std::vector<float> &ParticleSystem::getSizes() const
+{
+	return sizes;
+}
+
+void ParticleSystem::setSizeVariation(float variation)
+{
+	sizeVariation = variation;
+}
+
+float ParticleSystem::getSizeVariation() const
+{
+	return sizeVariation;
+}
+
+void ParticleSystem::setRotation(float rotation)
+{
+	rotationMin = rotationMax = rotation;
+}
+
+void ParticleSystem::setRotation(float min, float max)
+{
+	rotationMin = min;
+	rotationMax = max;
+}
+
+void ParticleSystem::getRotation(float &min, float &max) const
+{
+	min = rotationMin;
+	max = rotationMax;
+}
+
+void ParticleSystem::setSpin(float spin)
+{
+	spinStart = spin;
+	spinEnd = spin;
+}
+
+void ParticleSystem::setSpin(float start, float end)
+{
+	spinStart = start;
+	spinEnd = end;
+}
+
+void ParticleSystem::getSpin(float &start, float &end) const
+{
+	start = spinStart;
+	end = spinEnd;
+}
+
+void ParticleSystem::setSpinVariation(float variation)
+{
+	spinVariation = variation;
+}
+
+float ParticleSystem::getSpinVariation() const
+{
+	return spinVariation;
+}
+
+void ParticleSystem::setOffset(float x, float y)
+{
+	offset = love::Vector(x, y);
+	defaultOffset = false;
+}
+
+love::Vector ParticleSystem::getOffset() const
+{
+	return offset;
+}
+
+void ParticleSystem::setColor(const std::vector<Colorf> &newColors)
+{
+	colors = newColors;
+
+	for (Colorf &c : colors)
+	{
+		// We want to store the colors as [0, 1], rather than [0, 255].
+		c.r /= 255.0f;
+		c.g /= 255.0f;
+		c.b /= 255.0f;
+		c.a /= 255.0f;
+	}
+}
+
+std::vector<Colorf> ParticleSystem::getColor() const
+{
+	// The particle system stores colors in the range of [0, 1]...
+	std::vector<Colorf> ncolors(colors);
+
+	for (Colorf &c : ncolors)
+	{
+		c.r *= 255.0f;
+		c.g *= 255.0f;
+		c.b *= 255.0f;
+		c.a *= 255.0f;
+	}
+
+	return ncolors;
+}
+
+void ParticleSystem::setQuads(const std::vector<Quad *> &newQuads)
+{
+	std::vector<StrongRef<Quad>> quadlist;
+	quadlist.reserve(newQuads.size());
+
+	for (Quad *q : newQuads)
+		quadlist.push_back(q);
+
+	quads = quadlist;
+
+	if (defaultOffset)
+		resetOffset();
+}
+
+void ParticleSystem::setQuads()
+{
+	quads.clear();
+}
+
+std::vector<Quad *> ParticleSystem::getQuads() const
+{
+	std::vector<Quad *> quadlist;
+	quadlist.reserve(quads.size());
+
+	for (const StrongRef<Quad> &q : quads)
+		quadlist.push_back(q.get());
+
+	return quadlist;
+}
+
+void ParticleSystem::setRelativeRotation(bool enable)
+{
+	relativeRotation = enable;
+}
+
+bool ParticleSystem::hasRelativeRotation() const
+{
+	return relativeRotation;
+}
+
+uint32 ParticleSystem::getCount() const
+{
+	return activeParticles;
+}
+
+void ParticleSystem::start()
+{
+	active = true;
+}
+
+void ParticleSystem::stop()
+{
+	active = false;
+	life = lifetime;
+	emitCounter = 0;
+}
+
+void ParticleSystem::pause()
+{
+	active = false;
+}
+
+void ParticleSystem::reset()
+{
+	if (pMem == nullptr)
+		return;
+
+	pFree = pMem;
+	pHead = nullptr;
+	pTail = nullptr;
+	activeParticles = 0;
+	life = lifetime;
+	emitCounter = 0;
+}
+
+void ParticleSystem::emit(uint32 num)
+{
+	if (!active)
+		return;
+
+	num = std::min(num, maxParticles - activeParticles);
+
+	while (num--)
+		addParticle(1.0f);
+}
+
+bool ParticleSystem::isActive() const
+{
+	return active;
+}
+
+bool ParticleSystem::isPaused() const
+{
+	return !active && life < lifetime;
+}
+
+bool ParticleSystem::isStopped() const
+{
+	return !active && life >= lifetime;
+}
+
+bool ParticleSystem::isEmpty() const
+{
+	return activeParticles == 0;
+}
+
+bool ParticleSystem::isFull() const
+{
+	return activeParticles == maxParticles;
+}
+
+void ParticleSystem::update(float dt)
+{
+	if (pMem == nullptr || dt == 0.0f)
+		return;
+
+	// Traverse all particles and update.
+	Particle *p = pHead;
+
+	while (p)
+	{
+		// Decrease lifespan.
+		p->life -= dt;
+
+		if (p->life <= 0)
+			p = removeParticle(p);
+		else
+		{
+			// Temp variables.
+			love::Vector radial, tangential;
+			love::Vector ppos = p->position;
+
+			// Get vector from particle center to particle.
+			radial = ppos - p->origin;
+			radial.normalize();
+			tangential = radial;
+
+			// Resize radial acceleration.
+			radial *= p->radialAcceleration;
+
+			// Calculate tangential acceleration.
+			{
+				float a = tangential.getX();
+				tangential.setX(-tangential.getY());
+				tangential.setY(a);
+			}
+
+			// Resize tangential.
+			tangential *= p->tangentialAcceleration;
+
+			// Update velocity.
+			p->velocity += (radial + tangential + p->linearAcceleration) * dt;
+
+			// Apply damping.
+			p->velocity *= 1.0f / (1.0f + p->linearDamping * dt);
+
+			// Modify position.
+			ppos += p->velocity * dt;
+
+			p->position = ppos;
+
+			const float t = 1.0f - p->life / p->lifetime;
+
+			// Rotate.
+			p->rotation += (p->spinStart * (1.0f - t) + p->spinEnd * t) * dt;
+
+			p->angle = p->rotation;
+
+			if (relativeRotation)
+				p->angle += atan2f(p->velocity.y, p->velocity.x);
+
+			// Change size according to given intervals:
+			// i = 0       1       2      3          n-1
+			//     |-------|-------|------|--- ... ---|
+			// t = 0    1/(n-1)        3/(n-1)        1
+			//
+			// `s' is the interpolation variable scaled to the current
+			// interval width, e.g. if n = 5 and t = 0.3, then the current
+			// indices are 1,2 and s = 0.3 - 0.25 = 0.05
+			float s = p->sizeOffset + t * p->sizeIntervalSize; // size variation
+			s *= (float)(sizes.size() - 1); // 0 <= s < sizes.size()
+			size_t i = (size_t)s;
+			size_t k = (i == sizes.size() - 1) ? i : i + 1; // boundary check (prevents failing on t = 1.0f)
+			s -= (float)i; // transpose s to be in interval [0:1]: i <= s < i + 1 ~> 0 <= s < 1
+			p->size = sizes[i] * (1.0f - s) + sizes[k] * s;
+
+			// Update color according to given intervals (as above)
+			s = t * (float)(colors.size() - 1);
+			i = (size_t)s;
+			k = (i == colors.size() - 1) ? i : i + 1;
+			s -= (float)i;                            // 0 <= s <= 1
+			p->color = colors[i] * (1.0f - s) + colors[k] * s;
+
+			// Update the quad index.
+			k = quads.size();
+			if (k > 0)
+			{
+				s = t * (float) k; // [0:numquads-1] (clamped below)
+				i = (s > 0.0f) ? (size_t) s : 0;
+				p->quadIndex = (int) ((i < k) ? i : k - 1);
+			}
+
+			// Next particle.
+			p = p->next;
+		}
+	}
+
+	// Make some more particles.
+	if (active)
+	{
+		float rate = 1.0f / emissionRate; // the amount of time between each particle emit
+		emitCounter += dt;
+		float total = emitCounter - rate;
+		while (emitCounter > rate)
+		{
+			addParticle(1.0f - (emitCounter - rate) / total);
+			emitCounter -= rate;
+		}
+		/*int particles = (int)(emissionRate * dt);
+		 for (int i = 0; i != particles; i++)
+		 add();*/
+
+		life -= dt;
+		if (lifetime != -1 && life < 0)
+			stop();
+	}
+
+	prevPosition = position;
+}
+
+bool ParticleSystem::getConstant(const char *in, AreaSpreadDistribution &out)
+{
+	return distributions.find(in, out);
+}
+
+bool ParticleSystem::getConstant(AreaSpreadDistribution in, const char *&out)
+{
+	return distributions.find(in, out);
+}
+
+bool ParticleSystem::getConstant(const char *in, InsertMode &out)
+{
+	return insertModes.find(in, out);
+}
+
+bool ParticleSystem::getConstant(InsertMode in, const char *&out)
+{
+	return insertModes.find(in, out);
+}
+
+StringMap<ParticleSystem::AreaSpreadDistribution, ParticleSystem::DISTRIBUTION_MAX_ENUM>::Entry ParticleSystem::distributionsEntries[] =
+{
+	{ "none",    DISTRIBUTION_NONE },
+	{ "uniform", DISTRIBUTION_UNIFORM },
+	{ "normal",  DISTRIBUTION_NORMAL },
+};
+
+StringMap<ParticleSystem::AreaSpreadDistribution, ParticleSystem::DISTRIBUTION_MAX_ENUM> ParticleSystem::distributions(ParticleSystem::distributionsEntries, sizeof(ParticleSystem::distributionsEntries));
+
+StringMap<ParticleSystem::InsertMode, ParticleSystem::INSERT_MODE_MAX_ENUM>::Entry ParticleSystem::insertModesEntries[] =
+{
+	{ "top",    INSERT_MODE_TOP },
+	{ "bottom", INSERT_MODE_BOTTOM },
+	{ "random", INSERT_MODE_RANDOM },
+};
+
+StringMap<ParticleSystem::InsertMode, ParticleSystem::INSERT_MODE_MAX_ENUM> ParticleSystem::insertModes(ParticleSystem::insertModesEntries, sizeof(ParticleSystem::insertModesEntries));
+
+} // graphics
+} // love

+ 672 - 0
jni/love/src/modules/graphics/ParticleSystem.h

@@ -0,0 +1,672 @@
+/**
+ * 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.
+ **/
+
+#ifndef LOVE_GRAPHICS_PARTICLE_SYSTEM_H
+#define LOVE_GRAPHICS_PARTICLE_SYSTEM_H
+
+// LOVE
+#include "common/int.h"
+#include "common/math.h"
+#include "common/Vector.h"
+#include "Drawable.h"
+#include "Color.h"
+#include "Quad.h"
+#include "Texture.h"
+
+// STL
+#include <vector>
+
+namespace love
+{
+namespace graphics
+{
+
+/**
+ * A class for creating, moving and drawing particles.
+ * A big thanks to bobthebloke.org
+ **/
+class ParticleSystem : public Drawable
+{
+public:
+	/**
+	 * Type of distribution new particles are drawn from: None, uniform, normal.
+	 */
+	enum AreaSpreadDistribution
+	{
+		DISTRIBUTION_NONE,
+		DISTRIBUTION_UNIFORM,
+		DISTRIBUTION_NORMAL,
+		DISTRIBUTION_MAX_ENUM
+	};
+
+	/**
+	 * Insertion modes of new particles in the list: top, bottom, random.
+	 */
+	enum InsertMode
+	{
+		INSERT_MODE_TOP,
+		INSERT_MODE_BOTTOM,
+		INSERT_MODE_RANDOM,
+		INSERT_MODE_MAX_ENUM
+	};
+
+	/**
+	 * Maximum numbers of particles in a ParticleSystem.
+	 * This limit comes from the fact that a quad requires four vertices and the
+	 * OpenGL API where GLsizei is a signed int.
+	 **/
+	static const uint32 MAX_PARTICLES = LOVE_INT32_MAX / 4;
+
+	/**
+	 * Creates a particle system with the specified buffer size and texture.
+	 **/
+	ParticleSystem(Texture *texture, uint32 buffer);
+	ParticleSystem(const ParticleSystem &p);
+
+	/**
+	 * Deletes any allocated memory.
+	 **/
+	virtual ~ParticleSystem();
+
+	/**
+	 * Creates an identical copy of this ParticleSystem. The clone does not
+	 * duplicate any existing particles from this ParticleSystem, just the
+	 * settable parameters.
+	 **/
+	virtual ParticleSystem *clone() = 0;
+
+	/**
+	 * Sets the texture used in the particle system.
+	 * @param texture The new texture.
+	 **/
+	void setTexture(Texture *texture);
+
+	/**
+	 * Returns the texture used when drawing the particle system.
+	 **/
+	Texture *getTexture() const;
+
+	/**
+	 * Clears the current buffer and allocates the appropriate amount of space for the buffer.
+	 * @param size The new buffer size.
+	 **/
+	virtual void setBufferSize(uint32 size);
+
+	/**
+	 * Returns the total amount of particles this ParticleSystem can have active
+	 * at any given point in time.
+	 **/
+	uint32 getBufferSize() const;
+
+	/**
+	 * Sets the insert mode for new particles.
+	 * @param mode The new insert mode.
+	 */
+	void setInsertMode(InsertMode mode);
+
+	/**
+	 * Returns the current insert mode.
+	 */
+	InsertMode getInsertMode() const;
+
+	/**
+	 * Sets the emission rate.
+	 * @param rate The amount of particles per second.
+	 **/
+	void setEmissionRate(float rate);
+
+	/**
+	 * Returns the number of particles created per second.
+	 **/
+	float getEmissionRate() const;
+
+	/**
+	 * Sets the lifetime of the particle emitter (-1 means eternal)
+	 * @param life The lifetime (in seconds).
+	 **/
+	void setEmitterLifetime(float life);
+
+	/**
+	 * Returns the lifetime of the particle emitter.
+	 **/
+	float getEmitterLifetime() const;
+
+	/**
+	 * Sets the life range of the particles.
+	 * @param min The minimum life.
+	 * @param max The maximum life (if 0, then becomes the same as minimum life).
+	 **/
+	void setParticleLifetime(float min, float max = 0);
+
+	/**
+	 * Gets the lifetime of a particle.
+	 * @param[out] min The minimum life.
+	 * @param[out] max The maximum life.
+	 **/
+	void getParticleLifetime(float &min, float &max) const;
+
+	/**
+	 * Sets the position of the center of the emitter.
+	 * Used to move the emitter without changing the position of already existing particles.
+	 * @param x The x-coordinate.
+	 * @param y The y-coordinate.
+	 **/
+	void setPosition(float x, float y);
+
+	/**
+	 * Returns the position of the emitter.
+	 **/
+	const love::Vector &getPosition() const;
+
+	/**
+	 * Moves the position of the center of the emitter.
+	 * When update is called, newly spawned particles will appear in a line
+	 * between the old emitter position and where the emitter was moved to,
+	 * resulting in a smoother-feeling particle system if moveTo is called
+	 * repeatedly.
+	 **/
+	void moveTo(float x, float y);
+
+	/**
+	 * Sets the emission area spread parameters and distribution type. The interpretation of
+	 * the parameters depends on the distribution type:
+	 *
+	 * * None:    Parameters are ignored. No area spread.
+	 * * Uniform: Parameters denote maximal (symmetric) displacement from emitter position.
+	 * * Normal:  Parameters denote the standard deviation in x and y direction. x and y are assumed to be uncorrelated.
+	 * @param x First parameter. Interpretation depends on distribution type.
+	 * @param y Second parameter. Interpretation depends on distribution type.
+	 * @param distribution Distribution type
+	 **/
+	void setAreaSpread(AreaSpreadDistribution distribution, float x, float y);
+
+	/**
+	 * Returns area spread distribution type.
+	 **/
+	AreaSpreadDistribution getAreaSpreadDistribution() const;
+
+	/**
+	 * Returns area spread parameters.
+	 **/
+	const love::Vector &getAreaSpreadParameters() const;
+
+	/**
+	 * Sets the direction of the particle emitter.
+	 * @param direction The direction (in degrees).
+	 **/
+	void setDirection(float direction);
+
+	/**
+	 * Returns the direction of the particle emitter (in radians).
+	 **/
+	float getDirection() const;
+
+	/**
+	 * Sets the spread of the particle emitter.
+	 * @param spread The spread (in radians).
+	 **/
+	void setSpread(float spread);
+
+	/**
+	 * Returns the directional spread of the emitter (in radians).
+	 **/
+	float getSpread() const;
+
+	/**
+	 * Sets the speed of the particles.
+	 * @param speed The speed.
+	 **/
+	void setSpeed(float speed);
+
+	/**
+	 * Sets the speed of the particles.
+	 * @param min The minimum speed.
+	 * @param max The maximum speed.
+	 **/
+	void setSpeed(float min, float max);
+
+	/**
+	 * Gets the speed of the particles.
+	 * @param[out] min The minimum speed.
+	 * @param[out] max The maximum speed.
+	 **/
+	void getSpeed(float &min, float &max) const;
+
+	/**
+	 * Sets the linear acceleration (the acceleration along the x and y axes).
+	 * @param x The acceleration along the x-axis.
+	 * @param y The acceleration along the y-axis.
+	 **/
+	void setLinearAcceleration(float x, float y);
+
+	/**
+	 * Sets the linear acceleration (the acceleration along the x and y axes).
+	 * @param xmin The minimum amount of acceleration along the x-axis.
+	 * @param ymin The minimum amount of acceleration along the y-axis.
+	 * @param xmax The maximum amount of acceleration along the x-axis.
+	 * @param ymax The maximum amount of acceleration along the y-axis.
+	 **/
+	void setLinearAcceleration(float xmin, float ymin, float xmax, float ymax);
+
+	/**
+	 * Gets the linear acceleration of the particles.
+	 * @param[out] min The minimum acceleration.
+	 * @param[out] max The maximum acceleration.
+	 **/
+	void getLinearAcceleration(love::Vector &min, love::Vector &max) const;
+
+	/**
+	 * Sets the radial acceleration (the acceleration towards the particle emitter).
+	 * @param acceleration The amount of acceleration.
+	 **/
+	void setRadialAcceleration(float acceleration);
+
+	/**
+	 * Sets the radial acceleration (the acceleration towards the particle emitter).
+	 * @param min The minimum acceleration.
+	 * @param max The maximum acceleration.
+	 **/
+	void setRadialAcceleration(float min, float max);
+
+	/**
+	 * Gets the radial acceleration.
+	 * @param[out] min The minimum amount of radial acceleration.
+	 * @param[out] max The maximum amount of radial acceleration.
+	 **/
+	void getRadialAcceleration(float &min, float &max) const;
+
+	/**
+	 * Sets the tangential acceleration (the acceleration perpendicular to the particle's direction).
+	 * @param acceleration The amount of acceleration.
+	 **/
+	void setTangentialAcceleration(float acceleration);
+
+	/**
+	 * Sets the tangential acceleration (the acceleration perpendicular to the particle's direction).
+	 * @param min The minimum acceleration.
+	 * @param max The maximum acceleration.
+	 **/
+	void setTangentialAcceleration(float min, float max);
+
+	/**
+	 * Gets the tangential acceleration.
+	 * @param[out] min The minimum tangential acceleration.
+	 * @param[out] max The maximum tangential acceleration.
+	 **/
+	void getTangentialAcceleration(float &min, float &max) const;
+
+	/**
+	 * Sets the amount of linear damping. Damping reduces the velocity of
+	 * particles over time. A value of 0 corresponds to no damping.
+	 **/
+	void setLinearDamping(float min, float max);
+
+	/**
+	 * Gets the current amount of linear damping.
+	 **/
+	void getLinearDamping(float &min, float &max) const;
+
+	/**
+	 * Sets the size of the sprite (1.0 being the default size).
+	 * @param size The size of the sprite.
+	 **/
+	void setSize(float size);
+
+	/**
+	 * Sets the sizes of the sprite upon creation and upon death (1.0 being the default size).
+	 * @param newSizes Array of sizes
+	 **/
+	void setSizes(const std::vector<float> &newSizes);
+
+	/**
+	 * Returns the sizes of the particle sprites.
+	 **/
+	const std::vector<float> &getSizes() const;
+
+	/**
+	 * Sets the amount of variation to the sprite's beginning size (0 being no variation and 1.0 a random size between start and end).
+	 * @param variation The amount of variation.
+	 **/
+	void setSizeVariation(float variation);
+
+	/**
+	 * Returns the amount of initial size variation between particles.
+	 **/
+	float getSizeVariation() const;
+
+	/**
+	 * Sets the amount of rotation a sprite starts out with.
+	 * @param rotation The amount of rotation.
+	 **/
+	void setRotation(float rotation);
+
+	/**
+	 * Sets the amount of rotation a sprite starts out with (a random value between min and max).
+	 * @param min The minimum amount of rotation.
+	 * @param max The maximum amount of rotation.
+	 **/
+	void setRotation(float min, float max);
+
+	/**
+	 * Gets the initial amount of rotation of a particle, in radians.
+	 * @param[out] min The minimum initial rotation.
+	 * @param[out] max The maximum initial rotation.
+	 **/
+	void getRotation(float &min, float &max) const;
+
+	/**
+	 * Sets the spin of the sprite.
+	 * @param spin The spin of the sprite (in degrees).
+	 **/
+	void setSpin(float spin);
+
+	/**
+	 * Sets the spin of the sprite upon particle creation and death.
+	 * @param start The spin of the sprite upon creation (in radians / second).
+	 * @param end The spin of the sprite upon death (in radians / second).
+	 **/
+	void setSpin(float start, float end);
+
+	/**
+	 * Gets the amount of spin of a particle during its lifetime.
+	 * @param[out] start The initial spin, in radians / s.
+	 * @param[out] end The final spin, in radians / s.
+	 **/
+	void getSpin(float &start, float &end) const;
+
+	/**
+	 * Sets the variation of the start spin (0 being no variation and 1 being a random spin between start and end).
+	 * @param variation The variation.
+	 **/
+	void setSpinVariation(float variation);
+
+	/**
+	 * Returns the amount of variation of the start spin of a particle.
+	 **/
+	float getSpinVariation() const;
+
+	/**
+	 * Sets the particles' offsets for rotation.
+	 * @param x The x offset.
+	 * @param y The y offset.
+	 **/
+	void setOffset(float x, float y);
+
+	/**
+	 * Returns of the particle offset.
+	 **/
+	love::Vector getOffset() const;
+
+	/**
+	 * Sets the color of the particles.
+	 * @param newColors Array of colors
+	 **/
+	void setColor(const std::vector<Colorf> &newColors);
+
+	/**
+	 * Returns the color of the particles.
+	 **/
+	std::vector<Colorf> getColor() const;
+
+	/**
+	 * Sets a list of Quads to use for particles over their lifetime.
+	 **/
+	void setQuads(const std::vector<Quad *> &newQuads);
+	void setQuads();
+
+	/**
+	 * Gets the Quads used when drawing the particles.
+	 **/
+	std::vector<Quad *> getQuads() const;
+
+	/**
+	 * sets whether particle angles & rotations are relative to their velocities.
+	 **/
+	void setRelativeRotation(bool enable);
+	bool hasRelativeRotation() const;
+
+	/**
+	 * Returns the amount of particles that are currently active in the system.
+	 **/
+	uint32 getCount() const;
+
+	/**
+	 * Starts/resumes the particle emitter.
+	 **/
+	void start();
+
+	/**
+	 * Stops the particle emitter and resets.
+	 **/
+	void stop();
+
+	/**
+	 * Pauses the particle emitter.
+	 **/
+	void pause();
+
+	/**
+	 * Resets the particle emitter.
+	 **/
+	void reset();
+
+	/**
+	 * Instantly emits a number of particles.
+	 * @param num The number of particles to emit.
+	 **/
+	void emit(uint32 num);
+
+	/**
+	 * Returns whether the particle emitter is active.
+	 **/
+	bool isActive() const;
+
+	/**
+	 * Returns whether the particle emitter is paused.
+	 **/
+	bool isPaused() const;
+
+	bool isStopped() const;
+
+	/**
+	 * Returns whether the particle system is empty of particles or not.
+	 **/
+	bool isEmpty() const;
+
+	/**
+	 * Returns whether the amount of particles has reached the buffer limit or not.
+	 **/
+	bool isFull() const;
+
+	/**
+	 * Updates the particle system.
+	 * @param dt Time since last update.
+	 **/
+	void update(float dt);
+
+	static bool getConstant(const char *in, AreaSpreadDistribution &out);
+	static bool getConstant(AreaSpreadDistribution in, const char *&out);
+
+	static bool getConstant(const char *in, InsertMode &out);
+	static bool getConstant(InsertMode in, const char *&out);
+
+protected:
+
+	// Represents a single particle.
+	struct Particle
+	{
+		Particle *prev;
+		Particle *next;
+
+		float lifetime;
+		float life;
+
+		love::Vector position;
+
+		// Particles gravitate towards this point.
+		love::Vector origin;
+
+		love::Vector velocity;
+		love::Vector linearAcceleration;
+		float radialAcceleration;
+		float tangentialAcceleration;
+
+		float linearDamping;
+
+		float size;
+		float sizeOffset;
+		float sizeIntervalSize;
+
+		float rotation; // Amount of rotation applied to the final angle.
+		float angle;
+		float spinStart;
+		float spinEnd;
+
+		Colorf color;
+
+		int quadIndex;
+	};
+
+	// Pointer to the beginning of the allocated memory.
+	Particle *pMem;
+
+	// Pointer to a free particle.
+	Particle *pFree;
+
+	// Pointer to the start of the linked list.
+	Particle *pHead;
+
+	// Pointer to the end of the linked list.
+	Particle *pTail;
+
+	// The texture to be drawn.
+	StrongRef<Texture> texture;
+
+	// Whether the particle emitter is active.
+	bool active;
+
+	// Insert mode of new particles.
+	InsertMode insertMode;
+
+	// The maximum number of particles.
+	uint32 maxParticles;
+
+	// The number of active particles.
+	uint32 activeParticles;
+
+	// The emission rate (particles/sec).
+	float emissionRate;
+
+	// Used to determine when a particle should be emitted.
+	float emitCounter;
+
+	// The relative position of the particle emitter.
+	love::Vector position;
+	love::Vector prevPosition;
+
+	// Emission area spread.
+	AreaSpreadDistribution areaSpreadDistribution;
+	love::Vector areaSpread;
+
+	// The lifetime of the particle emitter (-1 means infinite) and the life it has left.
+	float lifetime;
+	float life;
+
+	// The particle life.
+	float particleLifeMin;
+	float particleLifeMax;
+
+	// The direction (and spread) the particles will be emitted in. Measured in radians.
+	float direction;
+	float spread;
+
+	// The speed.
+	float speedMin;
+	float speedMax;
+
+	// Acceleration along the x and y axes.
+	love::Vector linearAccelerationMin;
+	love::Vector linearAccelerationMax;
+
+	// Acceleration towards the emitter's center
+	float radialAccelerationMin;
+	float radialAccelerationMax;
+
+	// Acceleration perpendicular to the particle's direction.
+	float tangentialAccelerationMin;
+	float tangentialAccelerationMax;
+
+	float linearDampingMin;
+	float linearDampingMax;
+
+	// Size.
+	std::vector<float> sizes;
+	float sizeVariation;
+
+	// Rotation
+	float rotationMin;
+	float rotationMax;
+
+	// Spin.
+	float spinStart;
+	float spinEnd;
+	float spinVariation;
+
+	// Offsets
+	love::Vector offset;
+
+	// Is the ParticleSystem using a default offset?
+	bool defaultOffset;
+
+	// Color.
+	std::vector<Colorf> colors;
+
+	// Quads.
+	std::vector<StrongRef<Quad>> quads;
+
+	bool relativeRotation;
+
+private:
+
+	void resetOffset();
+
+	void createBuffers(size_t size);
+	void deleteBuffers();
+
+	void addParticle(float t);
+	Particle *removeParticle(Particle *p);
+
+	// Called by addParticle.
+	void initParticle(Particle *p, float t);
+	void insertTop(Particle *p);
+	void insertBottom(Particle *p);
+	void insertRandom(Particle *p);
+
+	static StringMap<AreaSpreadDistribution, DISTRIBUTION_MAX_ENUM>::Entry distributionsEntries[];
+	static StringMap<AreaSpreadDistribution, DISTRIBUTION_MAX_ENUM> distributions;
+
+	static StringMap<InsertMode, INSERT_MODE_MAX_ENUM>::Entry insertModesEntries[];
+	static StringMap<InsertMode, INSERT_MODE_MAX_ENUM> insertModes;
+};
+
+} // graphics
+} // love
+
+#endif // LOVE_GRAPHICS_PARTICLE_SYSTEM_H

+ 14 - 14
jni/love/src/modules/graphics/Quad.cpp

@@ -29,7 +29,7 @@ namespace love
 namespace graphics
 namespace graphics
 {
 {
 
 
-Quad::Quad(const Quad::Viewport &v, float sw, float sh)
+Quad::Quad(const Quad::Viewport &v, double sw, double sh)
 	: sw(sw)
 	: sw(sw)
 	, sh(sh)
 	, sh(sh)
 {
 {
@@ -41,7 +41,7 @@ Quad::~Quad()
 {
 {
 }
 }
 
 
-void Quad::refresh(const Quad::Viewport &v, float sw, float sh)
+void Quad::refresh(const Quad::Viewport &v, double sw, double sh)
 {
 {
 	viewport = v;
 	viewport = v;
 
 
@@ -53,20 +53,20 @@ void Quad::refresh(const Quad::Viewport &v, float sw, float sh)
 	vertices[0].x = 0.0f;
 	vertices[0].x = 0.0f;
 	vertices[0].y = 0.0f;
 	vertices[0].y = 0.0f;
 	vertices[1].x = 0.0f;
 	vertices[1].x = 0.0f;
-	vertices[1].y = v.h;
-	vertices[2].x = v.w;
+	vertices[1].y = (float) v.h;
+	vertices[2].x = (float) v.w;
 	vertices[2].y = 0.0f;
 	vertices[2].y = 0.0f;
-	vertices[3].x = v.w;
-	vertices[3].y = v.h;
+	vertices[3].x = (float) v.w;
+	vertices[3].y = (float) v.h;
 
 
-	vertices[0].s = v.x/sw;
-	vertices[0].t = v.y/sh;
-	vertices[1].s = v.x/sw;
-	vertices[1].t = (v.y+v.h)/sh;
-	vertices[2].s = (v.x+v.w)/sw;
-	vertices[2].t = v.y/sh;
-	vertices[3].s = (v.x+v.w)/sw;
-	vertices[3].t = (v.y+v.h)/sh;
+	vertices[0].s = (float) (v.x/sw);
+	vertices[0].t = (float) (v.y/sh);
+	vertices[1].s = (float) (v.x/sw);
+	vertices[1].t = (float) ((v.y+v.h)/sh);
+	vertices[2].s = (float) ((v.x+v.w)/sw);
+	vertices[2].t = (float) (v.y/sh);
+	vertices[3].s = (float) ((v.x+v.w)/sw);
+	vertices[3].t = (float) ((v.y+v.h)/sh);
 }
 }
 
 
 void Quad::setViewport(const Quad::Viewport &v)
 void Quad::setViewport(const Quad::Viewport &v)

+ 6 - 6
jni/love/src/modules/graphics/Quad.h

@@ -36,14 +36,14 @@ public:
 
 
 	struct Viewport
 	struct Viewport
 	{
 	{
-		float x, y;
-		float w, h;
+		double x, y;
+		double w, h;
 	};
 	};
 
 
-	Quad(const Viewport &v, float sw, float sh);
+	Quad(const Viewport &v, double sw, double sh);
 	virtual ~Quad();
 	virtual ~Quad();
 
 
-	void refresh(const Viewport &v, float sw, float sh);
+	void refresh(const Viewport &v, double sw, double sh);
 	void setViewport(const Viewport &v);
 	void setViewport(const Viewport &v);
 	Viewport getViewport() const;
 	Viewport getViewport() const;
 
 
@@ -54,8 +54,8 @@ private:
 	Vertex vertices[4];
 	Vertex vertices[4];
 
 
 	Viewport viewport;
 	Viewport viewport;
-	float sw;
-	float sh;
+	double sw;
+	double sh;
 
 
 }; // Quad
 }; // Quad
 
 

+ 7 - 6
jni/love/src/modules/graphics/Texture.cpp

@@ -114,18 +114,19 @@ bool Texture::getConstant(WrapMode in, const char  *&out)
 
 
 StringMap<Texture::FilterMode, Texture::FILTER_MAX_ENUM>::Entry Texture::filterModeEntries[] =
 StringMap<Texture::FilterMode, Texture::FILTER_MAX_ENUM>::Entry Texture::filterModeEntries[] =
 {
 {
-	{ "linear", Texture::FILTER_LINEAR },
-	{ "nearest", Texture::FILTER_NEAREST },
-	{ "none", Texture::FILTER_NONE },
+	{ "linear", FILTER_LINEAR },
+	{ "nearest", FILTER_NEAREST },
+	{ "none", FILTER_NONE },
 };
 };
 
 
 StringMap<Texture::FilterMode, Texture::FILTER_MAX_ENUM> Texture::filterModes(Texture::filterModeEntries, sizeof(Texture::filterModeEntries));
 StringMap<Texture::FilterMode, Texture::FILTER_MAX_ENUM> Texture::filterModes(Texture::filterModeEntries, sizeof(Texture::filterModeEntries));
 
 
 StringMap<Texture::WrapMode, Texture::WRAP_MAX_ENUM>::Entry Texture::wrapModeEntries[] =
 StringMap<Texture::WrapMode, Texture::WRAP_MAX_ENUM>::Entry Texture::wrapModeEntries[] =
 {
 {
-	{ "clamp", Texture::WRAP_CLAMP },
-	{ "repeat", Texture::WRAP_REPEAT },
-	{ "mirroredrepeat", Texture::WRAP_MIRRORED_REPEAT },
+	{ "clamp", WRAP_CLAMP },
+	{ "clampzero", WRAP_CLAMP_ZERO },
+	{ "repeat", WRAP_REPEAT },
+	{ "mirroredrepeat", WRAP_MIRRORED_REPEAT },
 };
 };
 
 
 StringMap<Texture::WrapMode, Texture::WRAP_MAX_ENUM> Texture::wrapModes(Texture::wrapModeEntries, sizeof(Texture::wrapModeEntries));
 StringMap<Texture::WrapMode, Texture::WRAP_MAX_ENUM> Texture::wrapModes(Texture::wrapModeEntries, sizeof(Texture::wrapModeEntries));

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

@@ -43,6 +43,7 @@ public:
 	enum WrapMode
 	enum WrapMode
 	{
 	{
 		WRAP_CLAMP,
 		WRAP_CLAMP,
+		WRAP_CLAMP_ZERO,
 		WRAP_REPEAT,
 		WRAP_REPEAT,
 		WRAP_MIRRORED_REPEAT,
 		WRAP_MIRRORED_REPEAT,
 		WRAP_MAX_ENUM
 		WRAP_MAX_ENUM

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

@@ -361,6 +361,14 @@ bool Canvas::setWrap(const Texture::Wrap &w)
 		wrap.s = wrap.t = WRAP_CLAMP;
 		wrap.s = wrap.t = WRAP_CLAMP;
 	}
 	}
 
 
+	if (!gl.isClampZeroTextureWrapSupported())
+	{
+		if (wrap.s == WRAP_CLAMP_ZERO)
+			wrap.s = WRAP_CLAMP;
+		if (wrap.t == WRAP_CLAMP_ZERO)
+			wrap.t = WRAP_CLAMP;
+	}
+
 	gl.bindTexture(texture);
 	gl.bindTexture(texture);
 	gl.setTextureWrap(wrap);
 	gl.setTextureWrap(wrap);
 
 
@@ -407,9 +415,6 @@ void Canvas::startGrab(const std::vector<Canvas *> &canvases)
 
 
 	if (canvases.size() > 0)
 	if (canvases.size() > 0)
 	{
 	{
-		if (!isMultiCanvasSupported())
-			throw love::Exception("Multi-canvas rendering is not supported on this system.");
-
 		if ((int) canvases.size() + 1 > gl.getMaxRenderTargets())
 		if ((int) canvases.size() + 1 > gl.getMaxRenderTargets())
 			throw love::Exception("This system can't simultaneously render to %d canvases.", canvases.size()+1);
 			throw love::Exception("This system can't simultaneously render to %d canvases.", canvases.size()+1);
 
 
@@ -804,15 +809,9 @@ bool Canvas::isSupported()
 	return GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object || GLAD_EXT_framebuffer_object;
 	return GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object || GLAD_EXT_framebuffer_object;
 }
 }
 
 
-bool Canvas::isMultiCanvasSupported()
-{
-	// system must support at least 4 simultaneous active canvases.
-	return gl.getMaxRenderTargets() >= 4;
-}
-
 bool Canvas::isMultiFormatMultiCanvasSupported()
 bool Canvas::isMultiFormatMultiCanvasSupported()
 {
 {
-	return isMultiCanvasSupported() && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object);
+	return gl.getMaxRenderTargets() > 1 && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object);
 }
 }
 
 
 bool Canvas::supportedFormats[] = {false};
 bool Canvas::supportedFormats[] = {false};

+ 7 - 8
jni/love/src/modules/graphics/opengl/Canvas.h

@@ -55,13 +55,13 @@ public:
 		FORMAT_RG8,      // Two-channel (red and green) with 8 bits per channel.
 		FORMAT_RG8,      // Two-channel (red and green) with 8 bits per channel.
 		FORMAT_RGBA8,    // RGBA with 8 bits per channel.
 		FORMAT_RGBA8,    // RGBA with 8 bits per channel.
 		FORMAT_RGB10A2,  // RGB with 10 bits each, and A with 2 bits.
 		FORMAT_RGB10A2,  // RGB with 10 bits each, and A with 2 bits.
-		FORMAT_RG11B10F, // Floating point [0, +inf]. RG with 11 FP bits each, and B with 10 FP bits.
-		FORMAT_R16F,     // Floating point [-inf, +inf]. R with 16 FP bits.
-		FORMAT_RG16F,    // Floating point [-inf, +inf]. RG with 16 FP bits per channel.
-		FORMAT_RGBA16F,  // Floating point [-inf, +inf]. RGBA with 16 FP bits per channel.
-		FORMAT_R32F,     // Floating point [-inf, +inf]. R with 32 FP bits.
-		FORMAT_RG32F,    // Floating point [-inf, +inf]. RG with 32 FP bits per channel.
-		FORMAT_RGBA32F,  // Floating point [-inf, +inf]. RGBA with 32 FP bits per channel.
+		FORMAT_RG11B10F, // Floating point [0, +65024]. RG with 11 FP bits each, and B with 10 FP bits.
+		FORMAT_R16F,     // Floating point [-65504, +65504]. R with 16 FP bits.
+		FORMAT_RG16F,    // Floating point [-65504, +65504]. RG with 16 FP bits per channel.
+		FORMAT_RGBA16F,  // Floating point [-65504, +65504]. RGBA with 16 FP bits per channel.
+		FORMAT_R32F,     // Floating point [-65504, +65504]. R with 32 FP bits.
+		FORMAT_RG32F,    // Floating point [-65504, +65504]. RG with 32 FP bits per channel.
+		FORMAT_RGBA32F,  // Floating point [-65504, +65504]. RGBA with 32 FP bits per channel.
 		FORMAT_SRGB,     // sRGB with 8 bits per channel, plus 8 bit linear A.
 		FORMAT_SRGB,     // sRGB with 8 bits per channel, plus 8 bit linear A.
 		FORMAT_MAX_ENUM
 		FORMAT_MAX_ENUM
 	};
 	};
@@ -118,7 +118,6 @@ public:
 	}
 	}
 
 
 	static bool isSupported();
 	static bool isSupported();
-	static bool isMultiCanvasSupported();
 	static bool isMultiFormatMultiCanvasSupported();
 	static bool isMultiFormatMultiCanvasSupported();
 	static bool isFormatSupported(Format format);
 	static bool isFormatSupported(Format format);
 
 

+ 337 - 156
jni/love/src/modules/graphics/opengl/Font.cpp

@@ -38,6 +38,11 @@ namespace graphics
 namespace opengl
 namespace opengl
 {
 {
 
 
+static inline uint16 normToUint16(double n)
+{
+	return (uint16) (n * LOVE_UINT16_MAX);
+}
+
 int Font::fontCount = 0;
 int Font::fontCount = 0;
 
 
 Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter)
 Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter)
@@ -272,18 +277,20 @@ const Font::Glyph &Font::addGlyph(uint32 glyph)
 
 
 		g.texture = t;
 		g.texture = t;
 
 
-		float tX     = (float) textureX,     tY      = (float) textureY;
-		float tWidth = (float) textureWidth, tHeight = (float) textureHeight;
+		double tX     = (double) textureX,     tY      = (double) textureY;
+		double tWidth = (double) textureWidth, tHeight = (double) textureHeight;
+
+		Color c(255, 255, 255, 255);
 
 
 		// 0----2
 		// 0----2
 		// |  / |
 		// |  / |
 		// | /  |
 		// | /  |
 		// 1----3
 		// 1----3
 		const GlyphVertex verts[4] = {
 		const GlyphVertex verts[4] = {
-			{    0.0f,     0.0f,     tX/tWidth,     tY/tHeight},
-			{    0.0f, float(h),     tX/tWidth, (tY+h)/tHeight},
-			{float(w),     0.0f, (tX+w)/tWidth,     tY/tHeight},
-			{float(w), float(h), (tX+w)/tWidth, (tY+h)/tHeight}
+			{float(0), float(0), normToUint16((tX+0)/tWidth), normToUint16((tY+0)/tHeight), c},
+			{float(0), float(h), normToUint16((tX+0)/tWidth), normToUint16((tY+h)/tHeight), c},
+			{float(w), float(0), normToUint16((tX+w)/tWidth), normToUint16((tY+0)/tHeight), c},
+			{float(w), float(h), normToUint16((tX+w)/tWidth), normToUint16((tY+h)/tHeight), c}
 		};
 		};
 
 
 		// Copy vertex data to the glyph and set proper bearing.
 		// Copy vertex data to the glyph and set proper bearing.
@@ -341,12 +348,57 @@ float Font::getKerning(uint32 leftglyph, uint32 rightglyph)
 	return k;
 	return k;
 }
 }
 
 
+void Font::getCodepointsFromString(const std::string &text, Codepoints &codepoints)
+{
+	codepoints.reserve(text.size());
+
+	try
+	{
+		utf8::iterator<std::string::const_iterator> i(text.begin(), text.begin(), text.end());
+		utf8::iterator<std::string::const_iterator> end(text.end(), text.begin(), text.end());
+
+		while (i != end)
+		{
+			uint32 g = *i++;
+			codepoints.push_back(g);
+		}
+	}
+	catch (utf8::exception &e)
+	{
+		throw love::Exception("UTF-8 decoding error: %s", e.what());
+	}
+}
+
+void Font::getCodepointsFromString(const std::vector<ColoredString> &strs, ColoredCodepoints &codepoints)
+{
+	if (strs.empty())
+		return;
+
+	codepoints.cps.reserve(strs[0].str.size());
+
+	for (const ColoredString &cstr : strs)
+	{
+		IndexedColor c = {cstr.color, (int) codepoints.cps.size()};
+		codepoints.colors.push_back(c);
+
+		getCodepointsFromString(cstr.str, codepoints.cps);
+	}
+
+	if (codepoints.colors.size() == 1)
+	{
+		IndexedColor c = codepoints.colors[0];
+
+		if (c.index == 0 && c.color == Color(255, 255, 255, 255))
+			codepoints.colors.pop_back();
+	}
+}
+
 float Font::getHeight() const
 float Font::getHeight() const
 {
 {
 	return (float) height;
 	return (float) height;
 }
 }
 
 
-std::vector<Font::DrawCommand> Font::generateVertices(const std::string &text, std::vector<GlyphVertex> &vertices, float extra_spacing, Vector offset, TextInfo *info)
+std::vector<Font::DrawCommand> Font::generateVertices(const ColoredCodepoints &codepoints, std::vector<GlyphVertex> &vertices, float extra_spacing, Vector offset, TextInfo *info)
 {
 {
 	// Spacing counter and newline handling.
 	// Spacing counter and newline handling.
 	float dx = offset.x;
 	float dx = offset.x;
@@ -356,96 +408,104 @@ std::vector<Font::DrawCommand> Font::generateVertices(const std::string &text, s
 	int maxwidth = 0;
 	int maxwidth = 0;
 
 
 	// Keeps track of when we need to switch textures in our vertex array.
 	// Keeps track of when we need to switch textures in our vertex array.
-	std::vector<DrawCommand> drawcommands;
+	std::vector<DrawCommand> commands;
 
 
 	// Pre-allocate space for the maximum possible number of vertices.
 	// Pre-allocate space for the maximum possible number of vertices.
 	size_t vertstartsize = vertices.size();
 	size_t vertstartsize = vertices.size();
-	vertices.reserve(vertstartsize + text.length() * 4);
+	vertices.reserve(vertstartsize + codepoints.cps.size() * 4);
 
 
 	uint32 prevglyph = 0;
 	uint32 prevglyph = 0;
 
 
-	try
+	Color curcolor(255, 255, 255, 255);
+	int curcolori = -1;
+	int ncolors = (int) codepoints.colors.size();
+
+	for (int i = 0; i < (int) codepoints.cps.size(); i++)
 	{
 	{
-		utf8::iterator<std::string::const_iterator> i(text.begin(), text.begin(), text.end());
-		utf8::iterator<std::string::const_iterator> end(text.end(), text.begin(), text.end());
+		uint32 g = codepoints.cps[i];
 
 
-		while (i != end)
+		if (curcolori + 1 < ncolors && codepoints.colors[curcolori + 1].index == i)
+			curcolor = codepoints.colors[++curcolori].color;
+
+		if (g == '\n')
 		{
 		{
-			uint32 g = *i++;
+			if (dx > maxwidth)
+				maxwidth = (int) dx;
 
 
-			if (g == '\n')
-			{
-				if (dx > maxwidth)
-					maxwidth = (int) dx;
+			// Wrap newline, but do not print it.
+			dy += floorf(getHeight() * getLineHeight() + 0.5f);
+			dx = offset.x;
+			continue;
+		}
 
 
-				// Wrap newline, but do not print it.
-				dy += floorf(getHeight() * getLineHeight() + 0.5f);
-				dx = offset.x;
-				continue;
-			}
+		uint32 cacheid = textureCacheID;
+
+		const Glyph &glyph = findGlyph(g);
 
 
-			uint32 cacheid = textureCacheID;
+		// If findGlyph invalidates the texture cache, re-start the loop.
+		if (cacheid != textureCacheID)
+		{
+			i = 0;
+			maxwidth = 0;
+			dx = offset.x;
+			dy = offset.y;
+			commands.clear();
+			vertices.resize(vertstartsize);
+			prevglyph = 0;
+			curcolori = -1;
+			curcolor = Color(255, 255, 255, 255);
+			continue;
+		}
 
 
-			const Glyph &glyph = findGlyph(g);
+		// Add kerning to the current horizontal offset.
+		dx += getKerning(prevglyph, g);
 
 
-			// If findGlyph invalidates the texture cache, re-start the loop.
-			if (cacheid != textureCacheID)
+		if (glyph.texture != 0)
+		{
+			// Copy the vertices and set their colors and relative positions.
+			for (int j = 0; j < 4; j++)
 			{
 			{
-				i = utf8::iterator<std::string::const_iterator>(text.begin(), text.begin(), text.end());
-				maxwidth = 0;
-				dx = offset.x;
-				dy = offset.y;
-				drawcommands.clear();
-				vertices.resize(vertstartsize);
-				prevglyph = 0;
-				continue;
+				vertices.push_back(glyph.vertices[j]);
+				vertices.back().x += dx;
+				vertices.back().y += dy + lineheight;
+				vertices.back().color = curcolor;
 			}
 			}
 
 
-			// Add kerning to the current horizontal offset.
-			dx += getKerning(prevglyph, g);
-
-			if (glyph.texture != 0)
+			// Check if glyph texture has changed since the last iteration.
+			if (commands.empty() || commands.back().texture != glyph.texture)
 			{
 			{
-				// Copy the vertices and set their proper relative positions.
-				for (int j = 0; j < 4; j++)
-				{
-					vertices.push_back(glyph.vertices[j]);
-					vertices.back().x += dx;
-					vertices.back().y += dy + lineheight;
-				}
+				// Add a new draw command if the texture has changed.
+				DrawCommand cmd;
+				cmd.startvertex = (int) vertices.size() - 4;
+				cmd.vertexcount = 0;
+				cmd.texture = glyph.texture;
+				commands.push_back(cmd);
+			}
 
 
-				// Check if glyph texture has changed since the last iteration.
-				if (drawcommands.empty() || drawcommands.back().texture != glyph.texture)
-				{
-					// Add a new draw command if the texture has changed.
-					DrawCommand cmd;
-					cmd.startvertex = (int) vertices.size() - 4;
-					cmd.vertexcount = 0;
-					cmd.texture = glyph.texture;
-					drawcommands.push_back(cmd);
-				}
+			commands.back().vertexcount += 4;
+		}
 
 
-				drawcommands.back().vertexcount += 4;
-			}
+		// Advance the x position for the next glyph.
+		dx += glyph.spacing;
 
 
-			// Advance the x position for the next glyph.
-			dx += glyph.spacing;
+		// Account for extra spacing given to space characters.
+		if (g == ' ' && extra_spacing != 0.0f)
+			dx = floorf(dx + extra_spacing);
 
 
-			// Account for extra spacing given to space characters.
-			if (g == ' ' && extra_spacing != 0.0f)
-				dx = floorf(dx + extra_spacing);
+		prevglyph = g;
 
 
-			prevglyph = g;
-		}
 	}
 	}
-	catch (utf8::exception &e)
+
+	const auto drawsort = [](const DrawCommand &a, const DrawCommand &b) -> bool
 	{
 	{
-		throw love::Exception("UTF-8 decoding error: %s", e.what());
-	}
+		// Texture binds are expensive, so we should sort by that first.
+		if (a.texture != b.texture)
+			return a.texture < b.texture;
+		else
+			return a.startvertex < b.startvertex;
+	};
 
 
-	// Sort draw commands by texture first, and quad position in memory second
-	// (using the struct's < operator).
-	std::sort(drawcommands.begin(), drawcommands.end());
+	std::sort(commands.begin(), commands.end(), drawsort);
 
 
 	if (dx > maxwidth)
 	if (dx > maxwidth)
 		maxwidth = (int) dx;
 		maxwidth = (int) dx;
@@ -455,43 +515,44 @@ std::vector<Font::DrawCommand> Font::generateVertices(const std::string &text, s
 		info->width = maxwidth - offset.x;;
 		info->width = maxwidth - offset.x;;
 		info->height = (int) dy + (dx > 0.0f ? floorf(getHeight() * getLineHeight() + 0.5f) : 0) - offset.y;
 		info->height = (int) dy + (dx > 0.0f ? floorf(getHeight() * getLineHeight() + 0.5f) : 0) - offset.y;
 	}
 	}
+	
+	return commands;
+}
 
 
-	return drawcommands;
+std::vector<Font::DrawCommand> Font::generateVertices(const std::string &text, std::vector<GlyphVertex> &vertices, float extra_spacing, Vector offset, TextInfo *info)
+{
+	ColoredCodepoints codepoints;
+	getCodepointsFromString(text, codepoints.cps);
+	return generateVertices(codepoints, vertices, extra_spacing, offset, info);
 }
 }
 
 
-std::vector<Font::DrawCommand> Font::generateVerticesFormatted(const std::string &text, float wrap, AlignMode align, std::vector<GlyphVertex> &vertices, TextInfo *info)
+std::vector<Font::DrawCommand> Font::generateVerticesFormatted(const ColoredCodepoints &text, float wrap, AlignMode align, std::vector<GlyphVertex> &vertices, TextInfo *info)
 {
 {
-	if (wrap < 0.0f)
-		wrap = std::numeric_limits<float>::max();
+	
+	wrap = std::max(wrap, 0.0f);
 
 
 	uint32 cacheid = textureCacheID;
 	uint32 cacheid = textureCacheID;
 
 
 	std::vector<DrawCommand> drawcommands;
 	std::vector<DrawCommand> drawcommands;
-	vertices.reserve(text.length() * 4);
+	vertices.reserve(text.cps.size() * 4);
 
 
-	// wrappedlines indicates which lines were automatically wrapped. It
-	// has the same number of elements as lines_to_draw.
-	std::vector<bool> wrappedlines;
 	std::vector<int> widths;
 	std::vector<int> widths;
-	std::vector<std::string> lines;
+	std::vector<ColoredCodepoints> lines;
 
 
-	// We only need the list of wrapped lines in 'justify' mode.
-	getWrap(text, wrap, lines, &widths, align == ALIGN_JUSTIFY ? &wrappedlines : nullptr);
+	getWrap(text, wrap, lines, &widths);
 
 
-	float extraspacing = 0.0f;
-	int numspaces = 0;
-	int i = 0;
 	float y = 0.0f;
 	float y = 0.0f;
-	float maxwidth = 0;
+	float maxwidth = 0.0f;
 
 
-	for (const std::string &line : lines)
+	for (int i = 0; i < (int) lines.size(); i++)
 	{
 	{
-		extraspacing = 0.0f;
+		const auto &line = lines[i];
+
 		float width = (float) widths[i];
 		float width = (float) widths[i];
 		love::Vector offset(0.0f, floorf(y));
 		love::Vector offset(0.0f, floorf(y));
+		float extraspacing = 0.0f;
 
 
-		if (width > maxwidth)
-			maxwidth = width;
+		maxwidth = std::max(width, maxwidth);
 
 
 		switch (align)
 		switch (align)
 		{
 		{
@@ -502,22 +563,24 @@ std::vector<Font::DrawCommand> Font::generateVerticesFormatted(const std::string
 			offset.x = floorf((wrap - width) / 2.0f);
 			offset.x = floorf((wrap - width) / 2.0f);
 			break;
 			break;
 		case ALIGN_JUSTIFY:
 		case ALIGN_JUSTIFY:
-			numspaces = (int) std::count(line.begin(), line.end(), ' ');
-			if (wrappedlines[i] && numspaces >= 1)
-				extraspacing = (wrap - width) / float(numspaces);
+		{
+			float numspaces = (float) std::count(line.cps.begin(), line.cps.end(), ' ');
+			if (width < wrap && numspaces >= 1)
+				extraspacing = (wrap - width) / numspaces;
 			else
 			else
 				extraspacing = 0.0f;
 				extraspacing = 0.0f;
 			break;
 			break;
+		}
 		case ALIGN_LEFT:
 		case ALIGN_LEFT:
 		default:
 		default:
 			break;
 			break;
 		}
 		}
 
 
-		std::vector<DrawCommand> commands = generateVertices(line, vertices, extraspacing, offset);
+		std::vector<DrawCommand> newcommands = generateVertices(line, vertices, extraspacing, offset);
 
 
-		if (!commands.empty())
+		if (!newcommands.empty())
 		{
 		{
-			auto firstcmd = commands.begin();
+			auto firstcmd = newcommands.begin();
 
 
 			// If the first draw command in the new list has the same texture
 			// If the first draw command in the new list has the same texture
 			// as the last one in the existing list we're building and its
 			// as the last one in the existing list we're building and its
@@ -533,11 +596,10 @@ std::vector<Font::DrawCommand> Font::generateVerticesFormatted(const std::string
 			}
 			}
 
 
 			// Append the new draw commands to the list we're building.
 			// Append the new draw commands to the list we're building.
-			drawcommands.insert(drawcommands.end(), firstcmd, commands.end());
+			drawcommands.insert(drawcommands.end(), firstcmd, newcommands.end());
 		}
 		}
 
 
 		y += getHeight() * getLineHeight();
 		y += getHeight() * getLineHeight();
-		i++;
 	}
 	}
 
 
 	if (info != nullptr)
 	if (info != nullptr)
@@ -597,30 +659,37 @@ void Font::printv(const Matrix4 &t, const std::vector<DrawCommand> &drawcommands
 	OpenGL::TempTransform transform(gl);
 	OpenGL::TempTransform transform(gl);
 	transform.get() *= t;
 	transform.get() *= t;
 
 
-	gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD);
-
 	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(GlyphVertex), &vertices[0].x);
 	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(GlyphVertex), &vertices[0].x);
-	glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(GlyphVertex), &vertices[0].s);
+	glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_UNSIGNED_SHORT, GL_TRUE, sizeof(GlyphVertex), &vertices[0].s);
+	glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(GlyphVertex), &vertices[0].color.r);
+
+	gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD | ATTRIBFLAG_COLOR);
 
 
 	drawVertices(drawcommands);
 	drawVertices(drawcommands);
 }
 }
 
 
-void Font::print(const std::string &text, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
+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)
 {
 {
+	ColoredCodepoints codepoints;
+	getCodepointsFromString(text, codepoints);
+
 	std::vector<GlyphVertex> vertices;
 	std::vector<GlyphVertex> vertices;
-	std::vector<DrawCommand> drawcommands = generateVertices(text, vertices);
+	std::vector<DrawCommand> drawcommands = generateVertices(codepoints, vertices);
 
 
-	Matrix4 t(ceilf(x), ceilf(y), angle, sx, sy, ox, oy, kx, ky);
+	Matrix4 t(x, y, angle, sx, sy, ox, oy, kx, ky);
 
 
 	printv(t, drawcommands, vertices);
 	printv(t, drawcommands, vertices);
 }
 }
 
 
-void Font::printf(const std::string &text, float x, float y, float wrap, AlignMode align, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
+void Font::printf(const std::vector<ColoredString> &text, float x, float y, float wrap, AlignMode align, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 {
+	ColoredCodepoints codepoints;
+	getCodepointsFromString(text, codepoints);
+
 	std::vector<GlyphVertex> vertices;
 	std::vector<GlyphVertex> vertices;
-	std::vector<DrawCommand> drawcommands = generateVerticesFormatted(text, wrap, align, vertices);
+	std::vector<DrawCommand> drawcommands = generateVerticesFormatted(codepoints, wrap, align, vertices);
 
 
-	Matrix4 t(ceilf(x), ceilf(y), angle, sx, sy, ox, oy, kx, ky);
+	Matrix4 t(x, y, angle, sx, sy, ox, oy, kx, ky);
 
 
 	printv(t, drawcommands, vertices);
 	printv(t, drawcommands, vertices);
 }
 }
@@ -636,15 +705,16 @@ int Font::getWidth(const std::string &str)
 	while (getline(iss, line, '\n'))
 	while (getline(iss, line, '\n'))
 	{
 	{
 		int width = 0;
 		int width = 0;
+		uint32 prevglyph = 0;
 		try
 		try
 		{
 		{
-			uint32 prevglyph = 0;
-
 			utf8::iterator<std::string::const_iterator> i(line.begin(), line.begin(), line.end());
 			utf8::iterator<std::string::const_iterator> i(line.begin(), line.begin(), line.end());
 			utf8::iterator<std::string::const_iterator> end(line.end(), line.begin(), line.end());
 			utf8::iterator<std::string::const_iterator> end(line.end(), line.begin(), line.end());
+
 			while (i != end)
 			while (i != end)
 			{
 			{
 				uint32 c = *i++;
 				uint32 c = *i++;
+
 				const Glyph &g = findGlyph(c);
 				const Glyph &g = findGlyph(c);
 				width += g.spacing + getKerning(prevglyph, c);
 				width += g.spacing + getKerning(prevglyph, c);
 
 
@@ -668,69 +738,180 @@ int Font::getWidth(char character)
 	return g.spacing;
 	return g.spacing;
 }
 }
 
 
-void Font::getWrap(const std::string &text, float wrap, std::vector<std::string> &lines, std::vector<int> *linewidths, std::vector<bool> *wrappedlines)
+void Font::getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector<ColoredCodepoints> &lines, std::vector<int> *linewidths)
 {
 {
-	const float width_space = (float) getWidth(' ');
+	// Per-line info.
+	float width = 0.0f;
+	float widthbeforelastspace = 0.0f;
+	float widthoftrailingspace = 0.0f;
+	uint32 prevglyph = 0;
 
 
-	std::istringstream iss(text);
-	std::string line;
-	std::ostringstream string_builder;
+	int lastspaceindex = -1;
 
 
-	// Split text at newlines.
-	while (std::getline(iss, line, '\n'))
-	{
-		std::vector<std::string> words;
-		std::istringstream word_iss(line);
+	// Keeping the indexed colors "in sync" is a bit tricky, since we split
+	// things up and we might skip some glyphs but we don't want to skip any
+	// color which starts at those indices.
+	Color curcolor(255, 255, 255, 255);
+	bool addcurcolor = false;
+	int curcolori = -1;
+	int endcolori = (int) codepoints.colors.size() - 1;
 
 
-		// split line into words
-		std::copy(std::istream_iterator<std::string>(word_iss), std::istream_iterator<std::string>(), std::back_inserter(words));
+	// A wrapped line of text.
+	ColoredCodepoints wline;
 
 
-		float width = 0.0f;
-		float oldwidth = 0.0f;
-		string_builder.str("");
+	int i = 0;
+	while (i < (int) codepoints.cps.size())
+	{
+		uint32 c = codepoints.cps[i];
+
+		// Determine the current color before doing anything else, to make sure
+		// it's still applied to future glyphs even if this one is skipped.
+		if (curcolori < endcolori && codepoints.colors[curcolori + 1].index == i)
+		{
+			curcolor = codepoints.colors[curcolori + 1].color;
+			curcolori++;
+			addcurcolor = true;
+		}
 
 
-		// Put words back together until a wrap occurs.
-		for (const std::string &word : words)
+		// Split text at newlines.
+		if (c == '\n')
 		{
 		{
-			float wordwidth = (float) getWidth(word);
-			width += wordwidth;
+			lines.push_back(wline);
+
+			// Ignore the width of any trailing spaces, for individual lines.
+			if (linewidths)
+				linewidths->push_back(width - widthoftrailingspace);
+
+			// Make sure the new line keeps any color that was set previously.
+			addcurcolor = true;
+
+			width = widthbeforelastspace = widthoftrailingspace = 0.0f;
+			prevglyph = 0; // Reset kerning information.
+			lastspaceindex = -1;
+			wline.cps.clear();
+			wline.colors.clear();
+			i++;
 
 
-			// On wordwrap, push line to line buffer and clear string builder.
-			if (width > wrap && oldwidth > 0)
+			continue;
+		}
+
+		const Glyph &g = findGlyph(c);
+		float charwidth = g.spacing + getKerning(prevglyph, c);
+		float newwidth = width + charwidth;
+
+		// Wrap the line if it exceeds the wrap limit. Don't wrap yet if we're
+		// processing a newline character, though.
+		if (c != ' ' && newwidth > wraplimit)
+		{
+			// If this is the first character in the line and it exceeds the
+			// limit, skip it completely.
+			if (wline.cps.empty())
+				i++;
+			else if (lastspaceindex != -1)
 			{
 			{
-				int realw = (int) width;
+				// 'Rewind' to the last seen space, if the line has one.
+				// FIXME: This could be more efficient...
+				while (!wline.cps.empty() && wline.cps.back() != ' ')
+					wline.cps.pop_back();
 
 
-				// Remove trailing space.
-				std::string tmp = string_builder.str();
-				lines.push_back(tmp.substr(0,tmp.size()-1));
-				string_builder.str("");
-				width = wordwidth;
-				realw -= (int) width;
+				while (!wline.colors.empty() && wline.colors.back().index >= (int) wline.cps.size())
+					wline.colors.pop_back();
 
 
-				if (linewidths)
-					linewidths->push_back(realw);
+				// Also 'rewind' to the color that the last character is using.
+				for (int colori = curcolori; colori >= 0; colori--)
+				{
+					if (codepoints.colors[colori].index <= lastspaceindex)
+					{
+						curcolor = codepoints.colors[colori].color;
+						curcolori = colori;
+						break;
+					}
+				}
 
 
-				// Indicate that this line was automatically wrapped.
-				if (wrappedlines)
-					wrappedlines->push_back(true);
+				// Ignore the width of trailing spaces in wrapped lines.
+				width = widthbeforelastspace;
+
+				i = lastspaceindex;
+				i++; // Start the next line after the space.
 			}
 			}
 
 
-			string_builder << word << " ";
+			lines.push_back(wline);
+
+			if (linewidths)
+				linewidths->push_back(width);
+
+			addcurcolor = true;
+
+			prevglyph = 0;
+			width = widthbeforelastspace = widthoftrailingspace = 0.0f;
+			wline.cps.clear();
+			wline.colors.clear();
+			lastspaceindex = -1;
+
+			continue;
+		}
+
+		if (prevglyph != ' ' && c == ' ')
+			widthbeforelastspace = width;
+
+		width = newwidth;
+		prevglyph = c;
+
+		if (addcurcolor)
+		{
+			wline.colors.push_back({curcolor, (int) wline.cps.size()});
+			addcurcolor = false;
+		}
+
+		wline.cps.push_back(c);
 
 
-			width += width_space;
-			oldwidth = width;
+		// Keep track of the last seen space, so we can "rewind" to it when
+		// wrapping.
+		if (c == ' ')
+		{
+			lastspaceindex = i;
+			widthoftrailingspace += charwidth;
 		}
 		}
+		else if (c != '\n')
+			widthoftrailingspace = 0.0f;
 
 
-		// Push last line.
+		i++;
+	}
+
+	// Push the last line.
+	if (!wline.cps.empty())
+	{
+		lines.push_back(wline);
+
+		// Ignore the width of any trailing spaces, for individual lines.
 		if (linewidths)
 		if (linewidths)
-			linewidths->push_back(width);
+			linewidths->push_back(width - widthoftrailingspace);
+	}
+}
+
+void Font::getWrap(const std::string &text, float wraplimit, std::vector<std::string> &lines, std::vector<int> *linewidths)
+{
+	ColoredCodepoints codepoints;
+	getCodepointsFromString(text, codepoints.cps);
 
 
-		std::string tmp = string_builder.str();
-		lines.push_back(tmp.substr(0,tmp.size()-1));
+	std::vector<ColoredCodepoints> codepointlines;
+	getWrap(codepoints, wraplimit, codepointlines, linewidths);
+
+	std::string line;
+
+	for (const ColoredCodepoints &codepoints : codepointlines)
+	{
+		line.clear();
+		line.reserve(codepoints.cps.size());
+
+		for (uint32 codepoint : codepoints.cps)
+		{
+			char character[5] = {'\0'};
+			char *end = utf8::unchecked::append(codepoint, character);
+			line.append(character, end - character);
+		}
 
 
-		// Indicate that this line was not automatically wrapped.
-		if (wrappedlines)
-			wrappedlines->push_back(false);
+		lines.push_back(line);
 	}
 	}
 }
 }
 
 
@@ -870,10 +1051,10 @@ bool Font::getConstant(AlignMode in, const char  *&out)
 
 
 StringMap<Font::AlignMode, Font::ALIGN_MAX_ENUM>::Entry Font::alignModeEntries[] =
 StringMap<Font::AlignMode, Font::ALIGN_MAX_ENUM>::Entry Font::alignModeEntries[] =
 {
 {
-	{ "left", Font::ALIGN_LEFT },
-	{ "right", Font::ALIGN_RIGHT },
-	{ "center", Font::ALIGN_CENTER },
-	{ "justify", Font::ALIGN_JUSTIFY },
+	{ "left", ALIGN_LEFT },
+	{ "right", ALIGN_RIGHT },
+	{ "center", ALIGN_CENTER },
+	{ "justify", ALIGN_JUSTIFY },
 };
 };
 
 
 StringMap<Font::AlignMode, Font::ALIGN_MAX_ENUM> Font::alignModes(Font::alignModeEntries, sizeof(Font::alignModeEntries));
 StringMap<Font::AlignMode, Font::ALIGN_MAX_ENUM> Font::alignModes(Font::alignModeEntries, sizeof(Font::alignModeEntries));

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

@@ -50,6 +50,8 @@ class Font : public Object, public Volatile
 {
 {
 public:
 public:
 
 
+	typedef std::vector<uint32> Codepoints;
+
 	enum AlignMode
 	enum AlignMode
 	{
 	{
 		ALIGN_LEFT,
 		ALIGN_LEFT,
@@ -59,10 +61,29 @@ public:
 		ALIGN_MAX_ENUM
 		ALIGN_MAX_ENUM
 	};
 	};
 
 
+	struct ColoredString
+	{
+		std::string str;
+		Color color;
+	};
+
+	struct IndexedColor
+	{
+		Color color;
+		int index;
+	};
+
+	struct ColoredCodepoints
+	{
+		std::vector<uint32> cps;
+		std::vector<IndexedColor> colors;
+	};
+
 	struct GlyphVertex
 	struct GlyphVertex
 	{
 	{
-		float x, y;
-		float s, t;
+		float  x, y;
+		uint16 s, t;
+		Color  color;
 	};
 	};
 
 
 	struct TextInfo
 	struct TextInfo
@@ -77,27 +98,22 @@ public:
 		GLuint texture;
 		GLuint texture;
 		int startvertex;
 		int startvertex;
 		int vertexcount;
 		int vertexcount;
-
-		// used when sorting with std::sort.
-		bool operator < (const DrawCommand &other) const
-		{
-			// Texture binds are expensive, so we should sort by that first.
-			if (texture != other.texture)
-				return texture < other.texture;
-			else
-				return startvertex < other.startvertex;
-		}
 	};
 	};
 
 
 	Font(love::font::Rasterizer *r, const Texture::Filter &filter = Texture::getDefaultFilter());
 	Font(love::font::Rasterizer *r, const Texture::Filter &filter = Texture::getDefaultFilter());
 
 
 	virtual ~Font();
 	virtual ~Font();
 
 
+	std::vector<DrawCommand> generateVertices(const ColoredCodepoints &codepoints, std::vector<GlyphVertex> &vertices, float extra_spacing = 0.0f, Vector offset = {}, TextInfo *info = nullptr);
 	std::vector<DrawCommand> generateVertices(const std::string &text, std::vector<GlyphVertex> &vertices, float extra_spacing = 0.0f, Vector offset = Vector(), TextInfo *info = nullptr);
 	std::vector<DrawCommand> generateVertices(const std::string &text, std::vector<GlyphVertex> &vertices, float extra_spacing = 0.0f, Vector offset = Vector(), TextInfo *info = nullptr);
-	std::vector<DrawCommand> generateVerticesFormatted(const std::string &text, float wrap, AlignMode align, std::vector<GlyphVertex> &vertices, TextInfo *info = nullptr);
+
+	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);
 
 
+	static void getCodepointsFromString(const std::string &str, Codepoints &codepoints);
+	static void getCodepointsFromString(const std::vector<ColoredString> &strs, ColoredCodepoints &codepoints);
+
 	/**
 	/**
 	 * Prints the text at the designated position with rotation and scaling.
 	 * Prints the text at the designated position with rotation and scaling.
 	 *
 	 *
@@ -112,9 +128,9 @@ public:
 	 * @param kx Shear along the x axis.
 	 * @param kx Shear along the x axis.
 	 * @param ky Shear along the y axis.
 	 * @param ky Shear along the y axis.
 	 **/
 	 **/
-	void print(const std::string &text, float x, float y, float angle = 0.0f, float sx = 1.0f, float sy = 1.0f, float ox = 0.0f, float oy = 0.0f, float kx = 0.0f, float ky = 0.0f);
+	void print(const std::vector<ColoredString> &text, float x, float y, float angle = 0.0f, float sx = 1.0f, float sy = 1.0f, float ox = 0.0f, float oy = 0.0f, float kx = 0.0f, float ky = 0.0f);
 
 
-	void printf(const std::string &text, float x, float y, float wrap, AlignMode align, float angle = 0.0f, float sx = 1.0f, float sy = 1.0f, float ox = 0.0f, float oy = 0.0f, float kx = 0.0f, float ky = 0.0f);
+	void printf(const std::vector<ColoredString> &text, float x, float y, float wrap, AlignMode align, float angle = 0.0f, float sx = 1.0f, float sy = 1.0f, float ox = 0.0f, float oy = 0.0f, float kx = 0.0f, float ky = 0.0f);
 
 
 	/**
 	/**
 	 * Returns the height of the font.
 	 * Returns the height of the font.
@@ -140,13 +156,12 @@ public:
 	 * and optionally the number of lines
 	 * and optionally the number of lines
 	 *
 	 *
 	 * @param text The input text
 	 * @param text The input text
-	 * @param wrap The number of pixels to wrap at
+	 * @param wraplimit The number of pixels to wrap at
 	 * @param max_width Optional output of the maximum width
 	 * @param max_width Optional output of the maximum width
-	 * @param wrapped_lines Optional output indicating which lines were
-	 *        auto-wrapped. Indices correspond to indices of the returned value.
 	 * Returns a vector with the lines.
 	 * Returns a vector with the lines.
 	 **/
 	 **/
-	void getWrap(const std::string &text, float wrap, std::vector<std::string> &lines, std::vector<int> *line_widths = 0, std::vector<bool> *wrapped_lines = 0);
+	void getWrap(const std::string &text, float wraplimit, std::vector<std::string> &lines, std::vector<int> *line_widths = nullptr);
+	void getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector<ColoredCodepoints> &lines, std::vector<int> *line_widths = nullptr);
 
 
 	/**
 	/**
 	 * Sets the line height (which should be a number to multiply the font size by,
 	 * Sets the line height (which should be a number to multiply the font size by,

+ 4 - 3
jni/love/src/modules/graphics/opengl/GLBuffer.cpp

@@ -90,6 +90,9 @@ void *GLBuffer::map()
 
 
 void GLBuffer::unmapStatic(size_t offset, size_t size)
 void GLBuffer::unmapStatic(size_t offset, size_t size)
 {
 {
+	if (size == 0)
+		return;
+
 	// Upload the mapped data to the buffer.
 	// Upload the mapped data to the buffer.
 	glBufferSubData(getTarget(), (GLintptr) offset, (GLsizeiptr) size, memory_map + offset);
 	glBufferSubData(getTarget(), (GLintptr) offset, (GLsizeiptr) size, memory_map + offset);
 }
 }
@@ -163,12 +166,10 @@ void GLBuffer::setMappedRangeModified(size_t offset, size_t modifiedsize)
 	// from the start of section a to the end of section b as modified if both
 	// from the start of section a to the end of section b as modified if both
 	// a and b are marked as modified.
 	// a and b are marked as modified.
 
 
-	size_t old_range_end = modified_offset + modified_offset;
-
+	size_t old_range_end = modified_offset + modified_size;
 	modified_offset = std::min(modified_offset, offset);
 	modified_offset = std::min(modified_offset, offset);
 
 
 	size_t new_range_end = std::max(offset + modifiedsize, old_range_end);
 	size_t new_range_end = std::max(offset + modifiedsize, old_range_end);
-
 	modified_size = new_range_end - modified_offset;
 	modified_size = new_range_end - modified_offset;
 }
 }
 
 

+ 62 - 38
jni/love/src/modules/graphics/opengl/Graphics.cpp

@@ -86,6 +86,11 @@ Graphics::~Graphics()
 		Shader::defaultShader->release();
 		Shader::defaultShader->release();
 		Shader::defaultShader = nullptr;
 		Shader::defaultShader = nullptr;
 	}
 	}
+	if (Shader::defaultVideoShader)
+	{
+		Shader::defaultVideoShader->release();
+		Shader::defaultVideoShader = nullptr;
+	}
 
 
 	if (quadIndices)
 	if (quadIndices)
 		delete quadIndices;
 		delete quadIndices;
@@ -110,7 +115,7 @@ void Graphics::restoreState(const DisplayState &s)
 	setPointSize(s.pointSize);
 	setPointSize(s.pointSize);
 
 
 	if (s.scissor)
 	if (s.scissor)
-		setScissor(s.scissorBox.x, s.scissorBox.y, s.scissorBox.w, s.scissorBox.h);
+		setScissor(s.scissorRect.x, s.scissorRect.y, s.scissorRect.w, s.scissorRect.h);
 	else
 	else
 		setScissor();
 		setScissor();
 
 
@@ -131,11 +136,10 @@ void Graphics::restoreStateChecked(const DisplayState &s)
 {
 {
 	const DisplayState &cur = states.back();
 	const DisplayState &cur = states.back();
 
 
-	if (*(uint32 *) &s.color.r != *(uint32 *) &cur.color.r)
+	if (s.color != cur.color)
 		setColor(s.color);
 		setColor(s.color);
 
 
-	if (*(uint32 *) &s.backgroundColor.r != *(uint32 *) &cur.backgroundColor.r)
-		setBackgroundColor(s.backgroundColor);
+	setBackgroundColor(s.backgroundColor);
 
 
 	if (s.blendMode != cur.blendMode || s.blendMultiplyAlpha != cur.blendMultiplyAlpha)
 	if (s.blendMode != cur.blendMode || s.blendMultiplyAlpha != cur.blendMultiplyAlpha)
 		setBlendMode(s.blendMode, s.blendMultiplyAlpha);
 		setBlendMode(s.blendMode, s.blendMultiplyAlpha);
@@ -148,10 +152,10 @@ void Graphics::restoreStateChecked(const DisplayState &s)
 	if (s.pointSize != cur.pointSize)
 	if (s.pointSize != cur.pointSize)
 		setPointSize(s.pointSize);
 		setPointSize(s.pointSize);
 
 
-	if (s.scissor != cur.scissor || (s.scissor && !(s.scissorBox == cur.scissorBox)))
+	if (s.scissor != cur.scissor || (s.scissor && !(s.scissorRect == cur.scissorRect)))
 	{
 	{
 		if (s.scissor)
 		if (s.scissor)
-			setScissor(s.scissorBox.x, s.scissorBox.y, s.scissorBox.w, s.scissorBox.h);
+			setScissor(s.scissorRect.x, s.scissorRect.y, s.scissorRect.w, s.scissorRect.h);
 		else
 		else
 			setScissor();
 			setScissor();
 	}
 	}
@@ -295,6 +299,10 @@ bool Graphics::setMode(int width, int height)
 
 
 	setDebug(enabledebug);
 	setDebug(enabledebug);
 
 
+	// Reload all volatile objects.
+	if (!Volatile::loadAll())
+		::printf("Could not reload all volatile objects.\n");
+
 	// Create a quad indices object owned by love.graphics, so at least one
 	// Create a quad indices object owned by love.graphics, so at least one
 	// QuadIndices object is alive at all times while love.graphics is alive.
 	// QuadIndices object is alive at all times while love.graphics is alive.
 	// This makes sure there aren't too many expensive destruction/creations of
 	// This makes sure there aren't too many expensive destruction/creations of
@@ -303,10 +311,6 @@ bool Graphics::setMode(int width, int height)
 	if (quadIndices == nullptr)
 	if (quadIndices == nullptr)
 		quadIndices = new QuadIndices(20);
 		quadIndices = new QuadIndices(20);
 
 
-	// Reload all volatile objects.
-	if (!Volatile::loadAll())
-		::printf("Could not reload all volatile objects.\n");
-
 	// Restore the graphics state.
 	// Restore the graphics state.
 	restoreState(states.back());
 	restoreState(states.back());
 
 
@@ -321,6 +325,13 @@ bool Graphics::setMode(int width, int height)
 		Shader::defaultShader = newShader(Shader::defaultCode[renderer]);
 		Shader::defaultShader = newShader(Shader::defaultCode[renderer]);
 	}
 	}
 
 
+	// and a default video shader.
+	if (!Shader::defaultVideoShader)
+	{
+		Renderer renderer = GLAD_ES_VERSION_2_0 ? RENDERER_OPENGLES : RENDERER_OPENGL;
+		Shader::defaultVideoShader = newShader(Shader::defaultVideoCode[renderer]);
+	}
+
 	// A shader should always be active, but the default shader shouldn't be
 	// A shader should always be active, but the default shader shouldn't be
 	// returned by getShader(), so we don't do setShader(defaultShader).
 	// returned by getShader(), so we don't do setShader(defaultShader).
 	if (!Shader::current)
 	if (!Shader::current)
@@ -581,14 +592,35 @@ bool Graphics::isCreated() const
 
 
 void Graphics::setScissor(int x, int y, int width, int height)
 void Graphics::setScissor(int x, int y, int width, int height)
 {
 {
-	OpenGL::Viewport box = {x, y, width, height};
+	ScissorRect rect = {x, y, width, height};
 
 
 	glEnable(GL_SCISSOR_TEST);
 	glEnable(GL_SCISSOR_TEST);
 	// OpenGL's reversed y-coordinate is compensated for in OpenGL::setScissor.
 	// OpenGL's reversed y-coordinate is compensated for in OpenGL::setScissor.
-	gl.setScissor(box);
+	gl.setScissor({rect.x, rect.y, rect.w, rect.h});
 
 
 	states.back().scissor = true;
 	states.back().scissor = true;
-	states.back().scissorBox = box;
+	states.back().scissorRect = rect;
+}
+
+void Graphics::intersectScissor(int x, int y, int width, int height)
+{
+	ScissorRect rect = states.back().scissorRect;
+
+	if (!states.back().scissor)
+	{
+		rect.x = 0;
+		rect.y = 0;
+		rect.w = std::numeric_limits<int>::max();
+		rect.h = std::numeric_limits<int>::max();
+	}
+
+	int x1 = std::max(rect.x, x);
+	int y1 = std::max(rect.y, y);
+
+	int x2 = std::min(rect.x + rect.w, x + width);
+	int y2 = std::min(rect.y + rect.h, y + height);
+
+	setScissor(x1, y1, std::max(0, x2 - x1), std::max(0, y2 - y1));
 }
 }
 
 
 void Graphics::setScissor()
 void Graphics::setScissor()
@@ -601,10 +633,10 @@ bool Graphics::getScissor(int &x, int &y, int &width, int &height) const
 {
 {
 	const DisplayState &state = states.back();
 	const DisplayState &state = states.back();
 
 
-	x = state.scissorBox.x;
-	y = state.scissorBox.y;
-	width = state.scissorBox.w;
-	height = state.scissorBox.h;
+	x = state.scissorRect.x;
+	y = state.scissorRect.y;
+	width = state.scissorRect.w;
+	height = state.scissorRect.h;
 
 
 	return state.scissor;
 	return state.scissor;
 }
 }
@@ -688,7 +720,7 @@ Image *Graphics::newImage(const std::vector<love::image::CompressedImageData *>
 	return new Image(cdata, flags);
 	return new Image(cdata, flags);
 }
 }
 
 
-Quad *Graphics::newQuad(Quad::Viewport v, float sw, float sh)
+Quad *Graphics::newQuad(Quad::Viewport v, double sw, double sh)
 {
 {
 	return new Quad(v, sw, sh);
 	return new Quad(v, sw, sh);
 }
 }
@@ -794,11 +826,16 @@ Mesh *Graphics::newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, con
 	return new Mesh(vertexformat, data, datasize, drawmode, usage);
 	return new Mesh(vertexformat, data, datasize, drawmode, usage);
 }
 }
 
 
-Text *Graphics::newText(Font *font, const std::string &text)
+Text *Graphics::newText(Font *font, const std::vector<Font::ColoredString> &text)
 {
 {
 	return new Text(font, text);
 	return new Text(font, text);
 }
 }
 
 
+Video *Graphics::newVideo(love::video::VideoStream *stream)
+{
+	return new Video(stream);
+}
+
 bool Graphics::isGammaCorrect() const
 bool Graphics::isGammaCorrect() const
 {
 {
 	return love::graphics::isGammaCorrect();
 	return love::graphics::isGammaCorrect();
@@ -1085,7 +1122,7 @@ bool Graphics::isWireframe() const
 	return states.back().wireframe;
 	return states.back().wireframe;
 }
 }
 
 
-void Graphics::print(const std::string &str, float x, float y , float angle, float sx, float sy, float ox, float oy, float kx, float ky)
+void Graphics::print(const std::vector<Font::ColoredString> &str, float x, float y , float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 {
 	checkSetDefaultFont();
 	checkSetDefaultFont();
 
 
@@ -1095,7 +1132,7 @@ void Graphics::print(const std::string &str, float x, float y , float angle, flo
 		state.font->print(str, x, y, angle, sx, sy, ox, oy, kx, ky);
 		state.font->print(str, x, y, angle, sx, sy, ox, oy, kx, ky);
 }
 }
 
 
-void Graphics::printf(const std::string &str, float x, float y, float wrap, Font::AlignMode align, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
+void Graphics::printf(const std::vector<Font::ColoredString> &str, float x, float y, float wrap, Font::AlignMode align, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 {
 	checkSetDefaultFont();
 	checkSetDefaultFont();
 
 
@@ -1278,21 +1315,8 @@ void Graphics::arc(DrawMode mode, float x, float y, float radius, float angle1,
 		coords[2 * (i+1) + 1] = y + radius * sinf(phi);
 		coords[2 * (i+1) + 1] = y + radius * sinf(phi);
 	}
 	}
 
 
-	// GL_POLYGON can only fill-draw convex polygons, so we need to do stuff manually here
-	if (mode == DRAW_LINE)
-	{
-		polyline(coords, num_coords); // Artifacts at sharp angles if set to looping.
-	}
-	else
-	{
-		OpenGL::TempDebugGroup debuggroup("Filled arc draw");
-
-		gl.prepareDraw();
-		gl.bindTexture(gl.getDefaultTexture());
-		gl.useVertexAttribArrays(ATTRIBFLAG_POS);
-		glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, 0, coords);
-		gl.drawArrays(GL_TRIANGLE_FAN, 0, points + 2);
-	}
+	// NOTE: We rely on polygon() using GL_TRIANGLE_FAN, when fill mode is used.
+	polygon(mode, coords, num_coords);
 
 
 	delete[] coords;
 	delete[] coords;
 }
 }
@@ -1482,10 +1506,10 @@ bool Graphics::isSupported(Support feature) const
 {
 {
 	switch (feature)
 	switch (feature)
 	{
 	{
-	case SUPPORT_MULTI_CANVAS:
-		return Canvas::isMultiCanvasSupported();
 	case SUPPORT_MULTI_CANVAS_FORMATS:
 	case SUPPORT_MULTI_CANVAS_FORMATS:
 		return Canvas::isMultiFormatMultiCanvasSupported();
 		return Canvas::isMultiFormatMultiCanvasSupported();
+	case SUPPORT_CLAMP_ZERO:
+		return gl.isClampZeroTextureWrapSupported();
 	default:
 	default:
 		return false;
 		return false;
 	}
 	}

+ 12 - 5
jni/love/src/modules/graphics/opengl/Graphics.h

@@ -37,6 +37,8 @@
 
 
 #include "window/Window.h"
 #include "window/Window.h"
 
 
+#include "video/VideoStream.h"
+
 #include "Font.h"
 #include "Font.h"
 #include "Image.h"
 #include "Image.h"
 #include "graphics/Quad.h"
 #include "graphics/Quad.h"
@@ -47,6 +49,7 @@
 #include "Shader.h"
 #include "Shader.h"
 #include "Mesh.h"
 #include "Mesh.h"
 #include "Text.h"
 #include "Text.h"
+#include "Video.h"
 
 
 namespace love
 namespace love
 {
 {
@@ -126,6 +129,8 @@ public:
 	 **/
 	 **/
 	void setScissor(int x, int y, int width, int height);
 	void setScissor(int x, int y, int width, int height);
 
 
+	void intersectScissor(int x, int y, int width, int height);
+
 	/**
 	/**
 	 * Clears any scissor that has been created.
 	 * Clears any scissor that has been created.
 	 **/
 	 **/
@@ -160,7 +165,7 @@ public:
 	Image *newImage(const std::vector<love::image::ImageData *> &data, 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);
 	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, double sw, double sh);
 
 
 	/**
 	/**
 	 * Creates a Font object.
 	 * Creates a Font object.
@@ -181,7 +186,9 @@ public:
 	Mesh *newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, int vertexcount, Mesh::DrawMode drawmode, Mesh::Usage usage);
 	Mesh *newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, int vertexcount, Mesh::DrawMode drawmode, Mesh::Usage usage);
 	Mesh *newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, const void *data, size_t datasize, Mesh::DrawMode drawmode, Mesh::Usage usage);
 	Mesh *newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, const void *data, size_t datasize, Mesh::DrawMode drawmode, Mesh::Usage usage);
 
 
-	Text *newText(Font *font, const std::string &text = "");
+	Text *newText(Font *font, const std::vector<Font::ColoredString> &text = {});
+
+	Video *newVideo(love::video::VideoStream *stream);
 
 
 	bool isGammaCorrect() const;
 	bool isGammaCorrect() const;
 
 
@@ -326,7 +333,7 @@ public:
 	 * @param kx Shear along the x-axis.
 	 * @param kx Shear along the x-axis.
 	 * @param ky Shear along the y-axis.
 	 * @param ky Shear along the y-axis.
 	 **/
 	 **/
-	void print(const std::string &str, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
+	void print(const std::vector<Font::ColoredString> &str, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 
 
 	/**
 	/**
 	 * Draw formatted text on screen at the specified coordinates.
 	 * Draw formatted text on screen at the specified coordinates.
@@ -344,7 +351,7 @@ public:
 	 * @param kx Shear along the x-axis.
 	 * @param kx Shear along the x-axis.
 	 * @param ky Shear along the y-axis.
 	 * @param ky Shear along the y-axis.
 	 **/
 	 **/
-	void printf(const std::string &str, float x, float y, float wrap, Font::AlignMode align, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
+	void printf(const std::vector<Font::ColoredString> &str, float x, float y, float wrap, Font::AlignMode align, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 
 
 	/**
 	/**
 	 * Draws a point at (x,y).
 	 * Draws a point at (x,y).
@@ -478,7 +485,7 @@ private:
 		float pointSize = 1.0f;
 		float pointSize = 1.0f;
 
 
 		bool scissor = false;
 		bool scissor = false;
-		OpenGL::Viewport scissorBox = OpenGL::Viewport();
+		ScissorRect scissorRect = ScissorRect();
 
 
 		// Stencil.
 		// Stencil.
 		bool stencilTest = false;
 		bool stencilTest = false;

+ 117 - 106
jni/love/src/modules/graphics/opengl/Image.cpp

@@ -356,7 +356,7 @@ bool Image::loadVolatile()
 
 
 		GLenum glerr = glGetError();
 		GLenum glerr = glGetError();
 		if (glerr != GL_NO_ERROR)
 		if (glerr != GL_NO_ERROR)
-			throw love::Exception("Cannot create image (error code 0x%x)", glerr);
+			throw love::Exception("Cannot create image (OpenGL error: %s)", OpenGL::errorString(glerr));
 	}
 	}
 	catch (love::Exception &)
 	catch (love::Exception &)
 	{
 	{
@@ -531,8 +531,16 @@ bool Image::setWrap(const Texture::Wrap &w)
 		wrap.s = wrap.t = WRAP_CLAMP;
 		wrap.s = wrap.t = WRAP_CLAMP;
 	}
 	}
 
 
+	if (!gl.isClampZeroTextureWrapSupported())
+	{
+		if (wrap.s == WRAP_CLAMP_ZERO)
+			wrap.s = WRAP_CLAMP;
+		if (wrap.t == WRAP_CLAMP_ZERO)
+			wrap.t = WRAP_CLAMP;
+	}
+
 	gl.bindTexture(texture);
 	gl.bindTexture(texture);
-	gl.setTextureWrap(w);
+	gl.setTextureWrap(wrap);
 
 
 	return success;
 	return success;
 }
 }
@@ -589,112 +597,101 @@ bool Image::isCompressed() const
 
 
 GLenum Image::getCompressedFormat(image::CompressedImageData::Format cformat, bool &isSRGB) const
 GLenum Image::getCompressedFormat(image::CompressedImageData::Format cformat, bool &isSRGB) const
 {
 {
+	using image::CompressedImageData;
+
 	switch (cformat)
 	switch (cformat)
 	{
 	{
-	case image::CompressedImageData::FORMAT_DXT1:
-		if (isSRGB)
-			return GL_COMPRESSED_SRGB_S3TC_DXT1_EXT;
-		else
-			return GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
-	case image::CompressedImageData::FORMAT_DXT3:
-		if (isSRGB)
-			return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
-		else
-			return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
-	case image::CompressedImageData::FORMAT_DXT5:
-		if (isSRGB)
-			return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
-		else
-			return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
-	case image::CompressedImageData::FORMAT_BC4:
+	case CompressedImageData::FORMAT_DXT1:
+		return isSRGB ? GL_COMPRESSED_SRGB_S3TC_DXT1_EXT : GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
+	case CompressedImageData::FORMAT_DXT3:
+		return isSRGB ? GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT : GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+	case CompressedImageData::FORMAT_DXT5:
+		return isSRGB ? GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT : GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+	case CompressedImageData::FORMAT_BC4:
 		isSRGB = false;
 		isSRGB = false;
 		return GL_COMPRESSED_RED_RGTC1;
 		return GL_COMPRESSED_RED_RGTC1;
-	case image::CompressedImageData::FORMAT_BC4s:
+	case CompressedImageData::FORMAT_BC4s:
 		isSRGB = false;
 		isSRGB = false;
 		return GL_COMPRESSED_SIGNED_RED_RGTC1;
 		return GL_COMPRESSED_SIGNED_RED_RGTC1;
-	case image::CompressedImageData::FORMAT_BC5:
+	case CompressedImageData::FORMAT_BC5:
 		isSRGB = false;
 		isSRGB = false;
 		return GL_COMPRESSED_RG_RGTC2;
 		return GL_COMPRESSED_RG_RGTC2;
-	case image::CompressedImageData::FORMAT_BC5s:
+	case CompressedImageData::FORMAT_BC5s:
 		isSRGB = false;
 		isSRGB = false;
 		return GL_COMPRESSED_SIGNED_RG_RGTC2;
 		return GL_COMPRESSED_SIGNED_RG_RGTC2;
-	case image::CompressedImageData::FORMAT_BC6H:
+	case CompressedImageData::FORMAT_BC6H:
 		isSRGB = false;
 		isSRGB = false;
 		return GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT;
 		return GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT;
-	case image::CompressedImageData::FORMAT_BC6Hs:
+	case CompressedImageData::FORMAT_BC6Hs:
 		isSRGB = false;
 		isSRGB = false;
 		return GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT;
 		return GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT;
-	case image::CompressedImageData::FORMAT_BC7:
-		if (isSRGB)
-			return GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM;
-		else
-			return GL_COMPRESSED_RGBA_BPTC_UNORM;
-	case image::CompressedImageData::FORMAT_ETC1:
+	case CompressedImageData::FORMAT_BC7:
+		return isSRGB ? GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM : GL_COMPRESSED_RGBA_BPTC_UNORM;
+	case CompressedImageData::FORMAT_PVR1_RGB2:
+		return isSRGB ? GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT : GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
+	case CompressedImageData::FORMAT_PVR1_RGB4:
+		return isSRGB ? GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT : GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
+	case CompressedImageData::FORMAT_PVR1_RGBA2:
+		return isSRGB ? GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT : GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
+	case CompressedImageData::FORMAT_PVR1_RGBA4:
+		return isSRGB ? GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT : GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
+	case CompressedImageData::FORMAT_ETC1:
 		// The ETC2 format can load ETC1 textures.
 		// The ETC2 format can load ETC1 textures.
 		if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility)
 		if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility)
-		{
-			if (isSRGB)
-				return GL_COMPRESSED_SRGB8_ETC2;
-			else
-				return GL_COMPRESSED_RGB8_ETC2;
-		}
+			return isSRGB ? GL_COMPRESSED_SRGB8_ETC2 : GL_COMPRESSED_RGB8_ETC2;
 		else
 		else
 		{
 		{
 			isSRGB = false;
 			isSRGB = false;
 			return GL_ETC1_RGB8_OES;
 			return GL_ETC1_RGB8_OES;
 		}
 		}
-	case image::CompressedImageData::FORMAT_ETC2_RGB:
-		if (isSRGB)
-			return GL_COMPRESSED_SRGB8_ETC2;
-		else
-			return GL_COMPRESSED_RGB8_ETC2;
-	case image::CompressedImageData::FORMAT_ETC2_RGBA:
-		if (isSRGB)
-			return GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC;
-		else
-			return GL_COMPRESSED_RGBA8_ETC2_EAC;
-	case image::CompressedImageData::FORMAT_ETC2_RGBA1:
-		if (isSRGB)
-			return GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2;
-		else
-			return GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
-	case image::CompressedImageData::FORMAT_EAC_R:
+	case CompressedImageData::FORMAT_ETC2_RGB:
+		return isSRGB ? GL_COMPRESSED_SRGB8_ETC2 : GL_COMPRESSED_RGB8_ETC2;
+	case CompressedImageData::FORMAT_ETC2_RGBA:
+		return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : GL_COMPRESSED_RGBA8_ETC2_EAC;
+	case CompressedImageData::FORMAT_ETC2_RGBA1:
+		return isSRGB ? GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 : GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
+	case CompressedImageData::FORMAT_EAC_R:
 		isSRGB = false;
 		isSRGB = false;
 		return GL_COMPRESSED_R11_EAC;
 		return GL_COMPRESSED_R11_EAC;
-	case image::CompressedImageData::FORMAT_EAC_Rs:
+	case CompressedImageData::FORMAT_EAC_Rs:
 		isSRGB = false;
 		isSRGB = false;
 		return GL_COMPRESSED_SIGNED_R11_EAC;
 		return GL_COMPRESSED_SIGNED_R11_EAC;
-	case image::CompressedImageData::FORMAT_EAC_RG:
+	case CompressedImageData::FORMAT_EAC_RG:
 		isSRGB = false;
 		isSRGB = false;
 		return GL_COMPRESSED_RG11_EAC;
 		return GL_COMPRESSED_RG11_EAC;
-	case image::CompressedImageData::FORMAT_EAC_RGs:
+	case CompressedImageData::FORMAT_EAC_RGs:
 		isSRGB = false;
 		isSRGB = false;
 		return GL_COMPRESSED_SIGNED_RG11_EAC;
 		return GL_COMPRESSED_SIGNED_RG11_EAC;
-	case image::CompressedImageData::FORMAT_PVR1_RGB2:
-		if (isSRGB)
-			return GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT;
-		else
-			return GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
-	case image::CompressedImageData::FORMAT_PVR1_RGB4:
-		if (isSRGB)
-			return GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT;
-		else
-			return GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
-	case image::CompressedImageData::FORMAT_PVR1_RGBA2:
-		if (isSRGB)
-			return GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT;
-		else
-			return GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
-	case image::CompressedImageData::FORMAT_PVR1_RGBA4:
-		if (isSRGB)
-			return GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT;
-		else
-			return GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
+	case CompressedImageData::FORMAT_ASTC_4x4:
+		return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : GL_COMPRESSED_RGBA_ASTC_4x4_KHR;
+	case CompressedImageData::FORMAT_ASTC_5x4:
+		return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : GL_COMPRESSED_RGBA_ASTC_5x4_KHR;
+	case CompressedImageData::FORMAT_ASTC_5x5:
+		return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : GL_COMPRESSED_RGBA_ASTC_5x5_KHR;
+	case CompressedImageData::FORMAT_ASTC_6x5:
+		return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : GL_COMPRESSED_RGBA_ASTC_6x5_KHR;
+	case CompressedImageData::FORMAT_ASTC_6x6:
+		return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : GL_COMPRESSED_RGBA_ASTC_6x6_KHR;
+	case CompressedImageData::FORMAT_ASTC_8x5:
+		return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : GL_COMPRESSED_RGBA_ASTC_8x5_KHR;
+	case CompressedImageData::FORMAT_ASTC_8x6:
+		return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : GL_COMPRESSED_RGBA_ASTC_8x6_KHR;
+	case CompressedImageData::FORMAT_ASTC_8x8:
+		return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : GL_COMPRESSED_RGBA_ASTC_8x8_KHR;
+	case CompressedImageData::FORMAT_ASTC_10x5:
+		return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : GL_COMPRESSED_RGBA_ASTC_10x5_KHR;
+	case CompressedImageData::FORMAT_ASTC_10x6:
+		return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : GL_COMPRESSED_RGBA_ASTC_10x6_KHR;
+	case CompressedImageData::FORMAT_ASTC_10x8:
+		return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : GL_COMPRESSED_RGBA_ASTC_10x8_KHR;
+	case CompressedImageData::FORMAT_ASTC_10x10:
+		return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : GL_COMPRESSED_RGBA_ASTC_10x10_KHR;
+	case CompressedImageData::FORMAT_ASTC_12x10:
+		return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : GL_COMPRESSED_RGBA_ASTC_12x10_KHR;
+	case CompressedImageData::FORMAT_ASTC_12x12:
+		return isSRGB ? GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : GL_COMPRESSED_RGBA_ASTC_12x12_KHR;
 	default:
 	default:
-		if (isSRGB)
-			return GL_SRGB8_ALPHA8;
-		else
-			return GL_RGBA8;
+		return isSRGB ? GL_SRGB8_ALPHA8 : GL_RGBA8;
 	}
 	}
 }
 }
 
 
@@ -705,42 +702,56 @@ bool Image::hasAnisotropicFilteringSupport()
 
 
 bool Image::hasCompressedTextureSupport(image::CompressedImageData::Format format, bool sRGB)
 bool Image::hasCompressedTextureSupport(image::CompressedImageData::Format format, bool sRGB)
 {
 {
+	using image::CompressedImageData;
+
 	switch (format)
 	switch (format)
 	{
 	{
-	case image::CompressedImageData::FORMAT_DXT1:
+	case CompressedImageData::FORMAT_DXT1:
 		return GLAD_EXT_texture_compression_s3tc || GLAD_EXT_texture_compression_dxt1;
 		return GLAD_EXT_texture_compression_s3tc || GLAD_EXT_texture_compression_dxt1;
-	case image::CompressedImageData::FORMAT_DXT3:
+	case CompressedImageData::FORMAT_DXT3:
 		return GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt3;
 		return GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt3;
-	case image::CompressedImageData::FORMAT_DXT5:
+	case CompressedImageData::FORMAT_DXT5:
 		return GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt5;
 		return GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt5;
-	case image::CompressedImageData::FORMAT_BC4:
-	case image::CompressedImageData::FORMAT_BC4s:
-	case image::CompressedImageData::FORMAT_BC5:
-	case image::CompressedImageData::FORMAT_BC5s:
+	case CompressedImageData::FORMAT_BC4:
+	case CompressedImageData::FORMAT_BC4s:
+	case CompressedImageData::FORMAT_BC5:
+	case CompressedImageData::FORMAT_BC5s:
 		return (GLAD_VERSION_3_0 || GLAD_ARB_texture_compression_rgtc || GLAD_EXT_texture_compression_rgtc);
 		return (GLAD_VERSION_3_0 || GLAD_ARB_texture_compression_rgtc || GLAD_EXT_texture_compression_rgtc);
-	case image::CompressedImageData::FORMAT_BC6H:
-	case image::CompressedImageData::FORMAT_BC6Hs:
-	case image::CompressedImageData::FORMAT_BC7:
+	case CompressedImageData::FORMAT_BC6H:
+	case CompressedImageData::FORMAT_BC6Hs:
+	case CompressedImageData::FORMAT_BC7:
 		return GLAD_VERSION_4_2 || GLAD_ARB_texture_compression_bptc;
 		return GLAD_VERSION_4_2 || GLAD_ARB_texture_compression_bptc;
-	case image::CompressedImageData::FORMAT_ETC1:
+	case CompressedImageData::FORMAT_PVR1_RGB2:
+	case CompressedImageData::FORMAT_PVR1_RGB4:
+	case CompressedImageData::FORMAT_PVR1_RGBA2:
+	case CompressedImageData::FORMAT_PVR1_RGBA4:
+		return sRGB ? GLAD_EXT_pvrtc_sRGB : GLAD_IMG_texture_compression_pvrtc;
+	case CompressedImageData::FORMAT_ETC1:
 		// ETC2 support guarantees ETC1 support as well.
 		// ETC2 support guarantees ETC1 support as well.
 		return GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility || GLAD_OES_compressed_ETC1_RGB8_texture;
 		return GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility || GLAD_OES_compressed_ETC1_RGB8_texture;
-	case image::CompressedImageData::FORMAT_ETC2_RGB:
-	case image::CompressedImageData::FORMAT_ETC2_RGBA:
-	case image::CompressedImageData::FORMAT_ETC2_RGBA1:
-	case image::CompressedImageData::FORMAT_EAC_R:
-	case image::CompressedImageData::FORMAT_EAC_Rs:
-	case image::CompressedImageData::FORMAT_EAC_RG:
-	case image::CompressedImageData::FORMAT_EAC_RGs:
+	case CompressedImageData::FORMAT_ETC2_RGB:
+	case CompressedImageData::FORMAT_ETC2_RGBA:
+	case CompressedImageData::FORMAT_ETC2_RGBA1:
+	case CompressedImageData::FORMAT_EAC_R:
+	case CompressedImageData::FORMAT_EAC_Rs:
+	case CompressedImageData::FORMAT_EAC_RG:
+	case CompressedImageData::FORMAT_EAC_RGs:
 		return GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility;
 		return GLAD_ES_VERSION_3_0 || GLAD_VERSION_4_3 || GLAD_ARB_ES3_compatibility;
-	case image::CompressedImageData::FORMAT_PVR1_RGB2:
-	case image::CompressedImageData::FORMAT_PVR1_RGB4:
-	case image::CompressedImageData::FORMAT_PVR1_RGBA2:
-	case image::CompressedImageData::FORMAT_PVR1_RGBA4:
-		if (sRGB)
-			return GLAD_EXT_pvrtc_sRGB;
-		else
-			return GLAD_IMG_texture_compression_pvrtc;
+	case CompressedImageData::FORMAT_ASTC_4x4:
+	case CompressedImageData::FORMAT_ASTC_5x4:
+	case CompressedImageData::FORMAT_ASTC_5x5:
+	case CompressedImageData::FORMAT_ASTC_6x5:
+	case CompressedImageData::FORMAT_ASTC_6x6:
+	case CompressedImageData::FORMAT_ASTC_8x5:
+	case CompressedImageData::FORMAT_ASTC_8x6:
+	case CompressedImageData::FORMAT_ASTC_8x8:
+	case CompressedImageData::FORMAT_ASTC_10x5:
+	case CompressedImageData::FORMAT_ASTC_10x6:
+	case CompressedImageData::FORMAT_ASTC_10x8:
+	case CompressedImageData::FORMAT_ASTC_10x10:
+	case CompressedImageData::FORMAT_ASTC_12x10:
+	case CompressedImageData::FORMAT_ASTC_12x12:
+		return /*GLAD_ES_VERSION_3_2 ||*/ GLAD_KHR_texture_compression_astc_ldr;
 	default:
 	default:
 		return false;
 		return false;
 	}
 	}
@@ -763,8 +774,8 @@ bool Image::getConstant(FlagType in, const char *&out)
 
 
 StringMap<Image::FlagType, Image::FLAG_TYPE_MAX_ENUM>::Entry Image::flagNameEntries[] =
 StringMap<Image::FlagType, Image::FLAG_TYPE_MAX_ENUM>::Entry Image::flagNameEntries[] =
 {
 {
-	{"mipmaps", Image::FLAG_TYPE_MIPMAPS},
-	{"linear", Image::FLAG_TYPE_LINEAR},
+	{"mipmaps", FLAG_TYPE_MIPMAPS},
+	{"linear", FLAG_TYPE_LINEAR},
 };
 };
 
 
 StringMap<Image::FlagType, Image::FLAG_TYPE_MAX_ENUM> Image::flagNames(Image::flagNameEntries, sizeof(Image::flagNameEntries));
 StringMap<Image::FlagType, Image::FLAG_TYPE_MAX_ENUM> Image::flagNames(Image::flagNameEntries, sizeof(Image::flagNameEntries));

+ 70 - 52
jni/love/src/modules/graphics/opengl/Mesh.cpp

@@ -64,6 +64,7 @@ Mesh::Mesh(const std::vector<AttribFormat> &vertexformat, const void *data, size
 	, vertexCount(0)
 	, vertexCount(0)
 	, vertexStride(0)
 	, vertexStride(0)
 	, ibo(nullptr)
 	, ibo(nullptr)
+	, useIndexBuffer(false)
 	, elementCount(0)
 	, elementCount(0)
 	, elementDataType(0)
 	, elementDataType(0)
 	, drawMode(drawmode)
 	, drawMode(drawmode)
@@ -90,6 +91,7 @@ Mesh::Mesh(const std::vector<AttribFormat> &vertexformat, int vertexcount, DrawM
 	, vertexCount((size_t) vertexcount)
 	, vertexCount((size_t) vertexcount)
 	, vertexStride(0)
 	, vertexStride(0)
 	, ibo(nullptr)
 	, ibo(nullptr)
+	, useIndexBuffer(false)
 	, elementCount(0)
 	, elementCount(0)
 	, elementDataType(getGLDataTypeFromMax(vertexcount))
 	, elementDataType(getGLDataTypeFromMax(vertexcount))
 	, drawMode(drawmode)
 	, drawMode(drawmode)
@@ -147,7 +149,7 @@ void Mesh::setupAttachedAttributes()
 		if (attachedAttributes.find(name) != attachedAttributes.end())
 		if (attachedAttributes.find(name) != attachedAttributes.end())
 			throw love::Exception("Duplicate vertex attribute name: %s", name.c_str());
 			throw love::Exception("Duplicate vertex attribute name: %s", name.c_str());
 
 
-		attachedAttributes[name] = {this, i, true};
+		attachedAttributes[name] = {this, (int) i, true};
 	}
 	}
 }
 }
 
 
@@ -285,6 +287,17 @@ Mesh::DataType Mesh::getAttributeInfo(int attribindex, int &components) const
 	return type;
 	return type;
 }
 }
 
 
+int Mesh::getAttributeIndex(const std::string &name) const
+{
+	for (int i = 0; i < (int) vertexFormat.size(); i++)
+	{
+		if (vertexFormat[i].name == name)
+			return i;
+	}
+
+	return -1;
+}
+
 void Mesh::setAttributeEnabled(const std::string &name, bool enable)
 void Mesh::setAttributeEnabled(const std::string &name, bool enable)
 {
 {
 	auto it = attachedAttributes.find(name);
 	auto it = attachedAttributes.find(name);
@@ -326,20 +339,10 @@ void Mesh::attachAttribute(const std::string &name, Mesh *mesh)
 		oldattrib = it->second;
 		oldattrib = it->second;
 
 
 	newattrib.mesh = mesh;
 	newattrib.mesh = mesh;
-	newattrib.index = std::numeric_limits<size_t>::max();
 	newattrib.enabled = oldattrib.mesh ? oldattrib.enabled : true;
 	newattrib.enabled = oldattrib.mesh ? oldattrib.enabled : true;
+	newattrib.index = mesh->getAttributeIndex(name);
 
 
-	// Find the index of the attribute in the mesh.
-	for (size_t i = 0; i < mesh->vertexFormat.size(); i++)
-	{
-		if (mesh->vertexFormat[i].name == name)
-		{
-			newattrib.index = i;
-			break;
-		}
-	}
-
-	if (newattrib.index == std::numeric_limits<size_t>::max())
+	if (newattrib.index < 0)
 		throw love::Exception("The specified mesh does not have a vertex attribute named '%s'", name.c_str());
 		throw love::Exception("The specified mesh does not have a vertex attribute named '%s'", name.c_str());
 
 
 	if (newattrib.mesh != this)
 	if (newattrib.mesh != this)
@@ -357,11 +360,10 @@ void *Mesh::mapVertexData()
 	return vbo->map();
 	return vbo->map();
 }
 }
 
 
-void Mesh::unmapVertexData()
+void Mesh::unmapVertexData(size_t modifiedoffset, size_t modifiedsize)
 {
 {
-	// Assume the whole buffer was modified.
 	GLBuffer::Bind bind(*vbo);
 	GLBuffer::Bind bind(*vbo);
-	vbo->setMappedRangeModified(0, vbo->getSize());
+	vbo->setMappedRangeModified(modifiedoffset, modifiedsize);
 	vbo->unmap();
 	vbo->unmap();
 }
 }
 
 
@@ -414,6 +416,7 @@ void Mesh::setVertexMap(const std::vector<uint32> &map)
 	if (!ibo && size > 0)
 	if (!ibo && size > 0)
 		ibo = new GLBuffer(size, nullptr, GL_ELEMENT_ARRAY_BUFFER, vbo->getUsage());
 		ibo = new GLBuffer(size, nullptr, GL_ELEMENT_ARRAY_BUFFER, vbo->getUsage());
 
 
+	useIndexBuffer = true;
 	elementCount = map.size();
 	elementCount = map.size();
 
 
 	if (!ibo || elementCount == 0)
 	if (!ibo || elementCount == 0)
@@ -437,6 +440,11 @@ void Mesh::setVertexMap(const std::vector<uint32> &map)
 	elementDataType = datatype;
 	elementDataType = datatype;
 }
 }
 
 
+void Mesh::setVertexMap()
+{
+	useIndexBuffer = false;
+}
+
 /**
 /**
  * Copies index data from a mapped buffer to a vector.
  * Copies index data from a mapped buffer to a vector.
  **/
  **/
@@ -448,14 +456,17 @@ static void copyFromIndexBuffer(void *buffer, size_t count, std::vector<uint32>
 		indices.push_back((uint32) elems[i]);
 		indices.push_back((uint32) elems[i]);
 }
 }
 
 
-void Mesh::getVertexMap(std::vector<uint32> &map) const
+bool Mesh::getVertexMap(std::vector<uint32> &map) const
 {
 {
-	if (!ibo || elementCount == 0)
-		return;
+	if (!useIndexBuffer)
+		return false;
 
 
 	map.clear();
 	map.clear();
 	map.reserve(elementCount);
 	map.reserve(elementCount);
 
 
+	if (!ibo || elementCount == 0)
+		return true;
+
 	GLBuffer::Bind ibobind(*ibo);
 	GLBuffer::Bind ibobind(*ibo);
 
 
 	// We unmap the buffer in Mesh::draw, Mesh::setVertexMap, and Mesh::flush.
 	// We unmap the buffer in Mesh::draw, Mesh::setVertexMap, and Mesh::flush.
@@ -472,6 +483,8 @@ void Mesh::getVertexMap(std::vector<uint32> &map) const
 		copyFromIndexBuffer<uint32>(buffer, elementCount, map);
 		copyFromIndexBuffer<uint32>(buffer, elementCount, map);
 		break;
 		break;
 	}
 	}
+
+	return true;
 }
 }
 
 
 size_t Mesh::getVertexMapCount() const
 size_t Mesh::getVertexMapCount() const
@@ -524,6 +537,39 @@ void Mesh::getDrawRange(int &min, int &max) const
 	max = rangeMax;
 	max = rangeMax;
 }
 }
 
 
+int Mesh::bindAttributeToShaderInput(int attributeindex, const std::string &inputname)
+{
+	const AttribFormat &format = vertexFormat[attributeindex];
+
+	GLint attriblocation = -1;
+
+	// If the attribute is one of the LOVE-defined ones, use the constant
+	// attribute index for it, otherwise query the index from the shader.
+	VertexAttribID builtinattrib;
+	if (Shader::getConstant(inputname.c_str(), builtinattrib))
+		attriblocation = (GLint) builtinattrib;
+	else if (Shader::current)
+		attriblocation = Shader::current->getAttribLocation(inputname);
+
+	// The active shader might not use this vertex attribute name.
+	if (attriblocation < 0)
+		return attriblocation;
+
+	// Needed for unmap and glVertexAttribPointer.
+	GLBuffer::Bind vbobind(*vbo);
+
+	// Make sure the buffer isn't mapped (sends data to GPU if needed.)
+	vbo->unmap();
+
+	const void *gloffset = vbo->getPointer(getAttributeOffset(attributeindex));
+	GLenum datatype = getGLDataType(format.type);
+	GLboolean normalized = (datatype == GL_UNSIGNED_BYTE);
+
+	glVertexAttribPointer(attriblocation, format.components, datatype, normalized, vertexStride, gloffset);
+
+	return attriblocation;
+}
+
 void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 {
 	OpenGL::TempDebugGroup debuggroup("Mesh draw");
 	OpenGL::TempDebugGroup debuggroup("Mesh draw");
@@ -536,43 +582,15 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 			continue;
 			continue;
 
 
 		Mesh *mesh = attrib.second.mesh;
 		Mesh *mesh = attrib.second.mesh;
-		const AttribFormat &format = mesh->vertexFormat[attrib.second.index];
+		int location = mesh->bindAttributeToShaderInput(attrib.second.index, attrib.first);
 
 
-		GLint attriblocation = -1;
-
-		// If the attribute is one of the LOVE-defined ones, use the constant
-		// attribute index for it, otherwise query the index from the shader.
-		VertexAttribID builtinattrib;
-		if (Shader::getConstant(format.name.c_str(), builtinattrib))
-			attriblocation = (GLint) builtinattrib;
-		else if (Shader::current)
-			attriblocation = Shader::current->getAttribLocation(format.name);
-
-		// The active shader might not use this vertex attribute name.
-		if (attriblocation < 0)
-			continue;
-
-		// Needed for unmap and glVertexAttribPointer.
-		GLBuffer::Bind vbobind(*mesh->vbo);
-
-		// Make sure the buffer isn't mapped (sends data to GPU if needed.)
-		mesh->vbo->unmap();
-
-		size_t offset = mesh->getAttributeOffset(attrib.second.index);
-		const void *gloffset = mesh->vbo->getPointer(offset);
-		GLenum datatype = getGLDataType(format.type);
-		GLboolean normalized = (datatype == GL_UNSIGNED_BYTE);
-
-		glVertexAttribPointer(attriblocation, format.components, datatype, normalized, mesh->vertexStride, gloffset);
-
-		enabledattribs |= 1 << uint32(attriblocation);
+		if (location >= 0)
+			enabledattribs |= 1u << (uint32) location;
 	}
 	}
 
 
+	// Not supported on all platforms or GL versions, I believe.
 	if (!(enabledattribs & ATTRIBFLAG_POS))
 	if (!(enabledattribs & ATTRIBFLAG_POS))
-	{
-		// Not supported on all platforms or GL versions at least, I believe.
 		throw love::Exception("Mesh must have an enabled VertexPosition attribute to be drawn.");
 		throw love::Exception("Mesh must have an enabled VertexPosition attribute to be drawn.");
-	}
 
 
 	gl.useVertexAttribArrays(enabledattribs);
 	gl.useVertexAttribArrays(enabledattribs);
 
 
@@ -588,7 +606,7 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 
 
 	gl.prepareDraw();
 	gl.prepareDraw();
 
 
-	if (ibo && elementCount > 0)
+	if (useIndexBuffer && ibo && elementCount > 0)
 	{
 	{
 		// Use the custom vertex map (index buffer) to draw the vertices.
 		// Use the custom vertex map (index buffer) to draw the vertices.
 		GLBuffer::Bind ibo_bind(*ibo);
 		GLBuffer::Bind ibo_bind(*ibo);

+ 8 - 3
jni/love/src/modules/graphics/opengl/Mesh.h

@@ -126,6 +126,7 @@ public:
 	 **/
 	 **/
 	const std::vector<AttribFormat> &getVertexFormat() const;
 	const std::vector<AttribFormat> &getVertexFormat() const;
 	DataType getAttributeInfo(int attribindex, int &components) const;
 	DataType getAttributeInfo(int attribindex, int &components) const;
+	int getAttributeIndex(const std::string &name) const;
 
 
 	/**
 	/**
 	 * Sets whether a specific vertex attribute is used when drawing the Mesh.
 	 * Sets whether a specific vertex attribute is used when drawing the Mesh.
@@ -140,7 +141,7 @@ public:
 	void attachAttribute(const std::string &name, Mesh *mesh);
 	void attachAttribute(const std::string &name, Mesh *mesh);
 
 
 	void *mapVertexData();
 	void *mapVertexData();
-	void unmapVertexData();
+	void unmapVertexData(size_t modifiedoffset = 0, size_t modifiedsize = -1);
 
 
 	/**
 	/**
 	 * Flushes all modified data to the GPU.
 	 * Flushes all modified data to the GPU.
@@ -154,12 +155,13 @@ public:
 	 * {0, 1, 2, 3, 4, ...}
 	 * {0, 1, 2, 3, 4, ...}
 	 **/
 	 **/
 	void setVertexMap(const std::vector<uint32> &map);
 	void setVertexMap(const std::vector<uint32> &map);
+	void setVertexMap();
 
 
 	/**
 	/**
 	 * Fills the uint32 vector passed into the method with the previously set
 	 * Fills the uint32 vector passed into the method with the previously set
 	 * vertex map (index buffer) values.
 	 * vertex map (index buffer) values.
 	 **/
 	 **/
-	void getVertexMap(std::vector<uint32> &map) const;
+	bool getVertexMap(std::vector<uint32> &map) const;
 
 
 	/**
 	/**
 	 * Gets the total number of elements in the vertex map array.
 	 * Gets the total number of elements in the vertex map array.
@@ -192,6 +194,8 @@ public:
 	void setDrawRange();
 	void setDrawRange();
 	void getDrawRange(int &min, int &max) const;
 	void getDrawRange(int &min, int &max) const;
 
 
+	int bindAttributeToShaderInput(int attributeindex, const std::string &inputname);
+
 	// Implements Drawable.
 	// Implements Drawable.
 	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) override;
 	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) override;
 
 
@@ -211,7 +215,7 @@ private:
 	struct AttachedAttribute
 	struct AttachedAttribute
 	{
 	{
 		Mesh *mesh;
 		Mesh *mesh;
-		size_t index;
+		int index;
 		bool enabled;
 		bool enabled;
 	};
 	};
 
 
@@ -242,6 +246,7 @@ private:
 
 
 	// Element (vertex index) buffer, for the vertex map.
 	// Element (vertex index) buffer, for the vertex map.
 	GLBuffer *ibo;
 	GLBuffer *ibo;
+	bool useIndexBuffer;
 	size_t elementCount;
 	size_t elementCount;
 	GLenum elementDataType;
 	GLenum elementDataType;
 
 

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

@@ -32,6 +32,7 @@
 
 
 // C
 // C
 #include <cstring>
 #include <cstring>
+#include <cstdio>
 
 
 // For SDL_GL_GetProcAddress.
 // For SDL_GL_GetProcAddress.
 #include <SDL_video.h>
 #include <SDL_video.h>
@@ -601,24 +602,32 @@ void OpenGL::setTextureFilter(graphics::Texture::Filter &f)
 		f.anisotropy = 1.0f;
 		f.anisotropy = 1.0f;
 }
 }
 
 
-void OpenGL::setTextureWrap(const graphics::Texture::Wrap &w)
+GLint OpenGL::getGLWrapMode(Texture::WrapMode wmode)
 {
 {
-	auto glWrapMode = [](Texture::WrapMode wmode) -> GLint
+	switch (wmode)
 	{
 	{
-		switch (wmode)
-		{
-		case Texture::WRAP_CLAMP:
-		default:
-			return GL_CLAMP_TO_EDGE;
-		case Texture::WRAP_REPEAT:
-			return GL_REPEAT;
-		case Texture::WRAP_MIRRORED_REPEAT:
-			return GL_MIRRORED_REPEAT;
-		}
-	};
+	case Texture::WRAP_CLAMP:
+	default:
+		return GL_CLAMP_TO_EDGE;
+	case Texture::WRAP_CLAMP_ZERO:
+		return GL_CLAMP_TO_BORDER;
+	case Texture::WRAP_REPEAT:
+		return GL_REPEAT;
+	case Texture::WRAP_MIRRORED_REPEAT:
+		return GL_MIRRORED_REPEAT;
+	}
+
+}
+
+void OpenGL::setTextureWrap(const graphics::Texture::Wrap &w)
+{
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, getGLWrapMode(w.s));
+	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, getGLWrapMode(w.t));
+}
 
 
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glWrapMode(w.s));
-	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glWrapMode(w.t));
+bool OpenGL::isClampZeroTextureWrapSupported() const
+{
+	return GLAD_VERSION_1_3 || GLAD_EXT_texture_border_clamp || GLAD_NV_texture_border_clamp;
 }
 }
 
 
 int OpenGL::getMaxTextureSize() const
 int OpenGL::getMaxTextureSize() const
@@ -643,7 +652,7 @@ int OpenGL::getMaxTextureUnits() const
 
 
 void OpenGL::updateTextureMemorySize(size_t oldsize, size_t newsize)
 void OpenGL::updateTextureMemorySize(size_t oldsize, size_t newsize)
 {
 {
-	int64 memsize = (int64) stats.textureMemory + ((int64 )newsize -  (int64) oldsize);
+	int64 memsize = (int64) stats.textureMemory + ((int64) newsize - (int64) oldsize);
 	stats.textureMemory = (size_t) std::max(memsize, (int64) 0);
 	stats.textureMemory = (size_t) std::max(memsize, (int64) 0);
 }
 }
 
 
@@ -652,6 +661,36 @@ OpenGL::Vendor OpenGL::getVendor() const
 	return vendor;
 	return vendor;
 }
 }
 
 
+const char *OpenGL::errorString(GLenum errorcode)
+{
+	switch (errorcode)
+	{
+	case GL_NO_ERROR:
+		return "no error";
+	case GL_INVALID_ENUM:
+		return "invalid enum";
+	case GL_INVALID_VALUE:
+		return "invalid value";
+	case GL_INVALID_OPERATION:
+		return "invalid operation";
+	case GL_OUT_OF_MEMORY:
+		return "out of memory";
+	case GL_INVALID_FRAMEBUFFER_OPERATION:
+		return "invalid framebuffer operation";
+	case GL_CONTEXT_LOST:
+		return "OpenGL context has been lost";
+	default:
+		break;
+	}
+
+	static char text[64] = {};
+
+	memset(text, 0, sizeof(text));
+	sprintf(text, "0x%x", errorcode);
+
+	return text;
+}
+
 const char *OpenGL::debugSeverityString(GLenum severity)
 const char *OpenGL::debugSeverityString(GLenum severity)
 {
 {
 	switch (severity)
 	switch (severity)

+ 8 - 2
jni/love/src/modules/graphics/opengl/OpenGL.h

@@ -143,7 +143,7 @@ public:
 	{
 	{
 	public:
 	public:
 
 
-#if defined(DEBUG) && DEBUG == 1
+#if defined(LOVE_IOS)
 		TempDebugGroup(const char *name)
 		TempDebugGroup(const char *name)
 		{
 		{
 			if (GLAD_EXT_debug_marker)
 			if (GLAD_EXT_debug_marker)
@@ -153,7 +153,7 @@ public:
 		TempDebugGroup(const char *) {}
 		TempDebugGroup(const char *) {}
 #endif
 #endif
 
 
-#if defined(DEBUG) && DEBUG == 1
+#if defined(LOVE_IOS)
 		~TempDebugGroup()
 		~TempDebugGroup()
 		{
 		{
 			if (GLAD_EXT_debug_marker)
 			if (GLAD_EXT_debug_marker)
@@ -312,6 +312,8 @@ public:
 	 **/
 	 **/
 	void setTextureWrap(const graphics::Texture::Wrap &w);
 	void setTextureWrap(const graphics::Texture::Wrap &w);
 
 
+	bool isClampZeroTextureWrapSupported() const;
+
 	/**
 	/**
 	 * Returns the maximum supported width or height of a texture.
 	 * Returns the maximum supported width or height of a texture.
 	 **/
 	 **/
@@ -339,6 +341,8 @@ public:
 	 **/
 	 **/
 	Vendor getVendor() const;
 	Vendor getVendor() const;
 
 
+	static const char *errorString(GLenum errorcode);
+
 	// Get human-readable strings for debug info.
 	// Get human-readable strings for debug info.
 	static const char *debugSeverityString(GLenum severity);
 	static const char *debugSeverityString(GLenum severity);
 	static const char *debugSourceString(GLenum source);
 	static const char *debugSourceString(GLenum source);
@@ -352,6 +356,8 @@ private:
 	void initMatrices();
 	void initMatrices();
 	void createDefaultTexture();
 	void createDefaultTexture();
 
 
+	GLint getGLWrapMode(Texture::WrapMode wmode);
+
 	bool contextInitialized;
 	bool contextInitialized;
 
 
 	float maxAnisotropy;
 	float maxAnisotropy;

+ 18 - 931
jni/love/src/modules/graphics/opengl/ParticleSystem.cpp

@@ -22,8 +22,6 @@
 #include "common/config.h"
 #include "common/config.h"
 #include "ParticleSystem.h"
 #include "ParticleSystem.h"
 
 
-#include "common/math.h"
-#include "modules/math/RandomGenerator.h"
 #include "OpenGL.h"
 #include "OpenGL.h"
 
 
 // STD
 // STD
@@ -38,812 +36,58 @@ namespace graphics
 namespace opengl
 namespace opengl
 {
 {
 
 
-namespace
-{
-
-love::math::RandomGenerator rng;
-
-float calculate_variation(float inner, float outer, float var)
-{
-	float low = inner - (outer/2.0f)*var;
-	float high = inner + (outer/2.0f)*var;
-	float r = (float) rng.random();
-	return low*(1-r)+high*r;
-}
-
-} // anonymous namespace
-
 ParticleSystem::ParticleSystem(Texture *texture, uint32 size)
 ParticleSystem::ParticleSystem(Texture *texture, uint32 size)
-	: pMem(nullptr)
-	, pFree(nullptr)
-	, pHead(nullptr)
-	, pTail(nullptr)
+	: love::graphics::ParticleSystem(texture, size)
 	, particleVerts(nullptr)
 	, particleVerts(nullptr)
-	, quadIndices(1)
-	, texture(texture)
-	, active(true)
-	, insertMode(INSERT_MODE_TOP)
-	, maxParticles(0)
-	, activeParticles(0)
-	, emissionRate(0)
-	, emitCounter(0)
-	, areaSpreadDistribution(DISTRIBUTION_NONE)
-	, lifetime(-1)
-	, life(0)
-	, particleLifeMin(0)
-	, particleLifeMax(0)
-	, direction(0)
-	, spread(0)
-	, speedMin(0)
-	, speedMax(0)
-	, linearAccelerationMin(0, 0)
-	, linearAccelerationMax(0, 0)
-	, radialAccelerationMin(0)
-	, radialAccelerationMax(0)
-	, tangentialAccelerationMin(0)
-	, tangentialAccelerationMax(0)
-	, linearDampingMin(0.0f)
-	, linearDampingMax(0.0f)
-	, sizeVariation(0)
-	, rotationMin(0)
-	, rotationMax(0)
-	, spinStart(0)
-	, spinEnd(0)
-	, spinVariation(0)
-	, offset(float(texture->getWidth())*0.5f, float(texture->getHeight())*0.5f)
-	, defaultOffset(true)
-	, relativeRotation(false)
+	, quadIndices(size)
 {
 {
-	if (size == 0 || size > MAX_PARTICLES)
-		throw love::Exception("Invalid ParticleSystem size.");
-
-	sizes.push_back(1.0f);
-	colors.push_back(Colorf(1.0f, 1.0f, 1.0f, 1.0f));
-	setBufferSize(size);
+	createVertices(size);
 }
 }
 
 
 ParticleSystem::ParticleSystem(const ParticleSystem &p)
 ParticleSystem::ParticleSystem(const ParticleSystem &p)
-	: pMem(nullptr)
-	, pFree(nullptr)
-	, pHead(nullptr)
-	, pTail(nullptr)
+	: love::graphics::ParticleSystem(p)
 	, particleVerts(nullptr)
 	, particleVerts(nullptr)
 	, quadIndices(p.quadIndices)
 	, quadIndices(p.quadIndices)
-	, texture(p.texture)
-	, active(p.active)
-	, insertMode(p.insertMode)
-	, maxParticles(p.maxParticles)
-	, activeParticles(0)
-	, emissionRate(p.emissionRate)
-	, emitCounter(0.0f)
-	, position(p.position)
-	, prevPosition(p.prevPosition)
-	, areaSpreadDistribution(p.areaSpreadDistribution)
-	, areaSpread(p.areaSpread)
-	, lifetime(p.lifetime)
-	, life(p.lifetime) // Initialize with the maximum life time.
-	, particleLifeMin(p.particleLifeMin)
-	, particleLifeMax(p.particleLifeMax)
-	, direction(p.direction)
-	, spread(p.spread)
-	, speedMin(p.speedMin)
-	, speedMax(p.speedMax)
-	, linearAccelerationMin(p.linearAccelerationMin)
-	, linearAccelerationMax(p.linearAccelerationMax)
-	, radialAccelerationMin(p.radialAccelerationMin)
-	, radialAccelerationMax(p.radialAccelerationMax)
-	, tangentialAccelerationMin(p.tangentialAccelerationMin)
-	, tangentialAccelerationMax(p.tangentialAccelerationMax)
-	, linearDampingMin(p.linearDampingMin)
-	, linearDampingMax(p.linearDampingMax)
-	, sizes(p.sizes)
-	, sizeVariation(p.sizeVariation)
-	, rotationMin(p.rotationMin)
-	, rotationMax(p.rotationMax)
-	, spinStart(p.spinStart)
-	, spinEnd(p.spinEnd)
-	, spinVariation(p.spinVariation)
-	, offset(p.offset)
-	, defaultOffset(p.defaultOffset)
-	, colors(p.colors)
-	, quads(p.quads)
-	, relativeRotation(p.relativeRotation)
 {
 {
-	setBufferSize(maxParticles);
+	createVertices(maxParticles);
 }
 }
 
 
 ParticleSystem::~ParticleSystem()
 ParticleSystem::~ParticleSystem()
 {
 {
-	deleteBuffers();
-}
-
-ParticleSystem *ParticleSystem::clone()
-{
-	return new ParticleSystem(*this);
-}
-
-void ParticleSystem::resetOffset()
-{
-	if (quads.empty())
-		offset = love::Vector(float(texture->getWidth())*0.5f, float(texture->getHeight())*0.5f);
-	else
-	{
-		Quad::Viewport v = quads[0]->getViewport();
-		offset = love::Vector(v.x*0.5f, v.y*0.5f);
-	}
+	delete[] particleVerts;
 }
 }
 
 
-void ParticleSystem::createBuffers(size_t size)
+void ParticleSystem::createVertices(size_t numparticles)
 {
 {
 	try
 	try
 	{
 	{
-		pFree = pMem = new Particle[size];
-		particleVerts = new love::Vertex[size * 4];
-		maxParticles = (uint32) size;
+		love::Vertex *pverts = new love::Vertex[numparticles * 4];
+		delete[] particleVerts;
+		particleVerts = pverts;
 	}
 	}
-	catch (std::bad_alloc &)
+	catch (std::exception &)
 	{
 	{
-		deleteBuffers();
-		throw love::Exception("Out of memory");
+		throw love::Exception("Out of memory.");
 	}
 	}
 }
 }
 
 
-void ParticleSystem::deleteBuffers()
+ParticleSystem *ParticleSystem::clone()
 {
 {
-	// Clean up for great gracefulness!
-	delete[] pMem;
-	delete[] particleVerts;
-
-	pMem = nullptr;
-	particleVerts = nullptr;
-	maxParticles = 0;
-	activeParticles = 0;
+	return new ParticleSystem(*this);
 }
 }
 
 
 void ParticleSystem::setBufferSize(uint32 size)
 void ParticleSystem::setBufferSize(uint32 size)
 {
 {
-	if (size == 0 || size > MAX_PARTICLES)
-		throw love::Exception("Invalid buffer size");
-	quadIndices = QuadIndices(size);
-	deleteBuffers();
-	createBuffers(size);
-	reset();
-}
-
-uint32 ParticleSystem::getBufferSize() const
-{
-	return maxParticles;
-}
-
-void ParticleSystem::addParticle(float t)
-{
-	if (isFull())
-		return;
-
-	// Gets a free particle and updates the allocation pointer.
-	Particle *p = pFree++;
-	initParticle(p, t);
-
-	switch (insertMode)
-	{
-	default:
-	case INSERT_MODE_TOP:
-		insertTop(p);
-		break;
-	case INSERT_MODE_BOTTOM:
-		insertBottom(p);
-		break;
-	case INSERT_MODE_RANDOM:
-		insertRandom(p);
-		break;
-	}
-
-	activeParticles++;
-}
-
-void ParticleSystem::initParticle(Particle *p, float t)
-{
-	float min,max;
-
-	// Linearly interpolate between the previous and current emitter position.
-	love::Vector pos = prevPosition + (position - prevPosition) * t;
-
-	min = particleLifeMin;
-	max = particleLifeMax;
-	if (min == max)
-		p->life = min;
-	else
-		p->life = (float) rng.random(min, max);
-	p->lifetime = p->life;
-
-	p->position = pos;
-
-	switch (areaSpreadDistribution)
-	{
-	case DISTRIBUTION_UNIFORM:
-		p->position.x += (float) rng.random(-areaSpread.getX(), areaSpread.getX());
-		p->position.y += (float) rng.random(-areaSpread.getY(), areaSpread.getY());
-		break;
-	case DISTRIBUTION_NORMAL:
-		p->position.x += (float) rng.randomNormal(areaSpread.getX());
-		p->position.y += (float) rng.randomNormal(areaSpread.getY());
-		break;
-	case DISTRIBUTION_NONE:
-	default:
-		break;
-	}
-
-	p->origin = pos;
-
-	min = speedMin;
-	max = speedMax;
-	float speed = (float) rng.random(min, max);
-
-	min = direction - spread/2.0f;
-	max = direction + spread/2.0f;
-	float dir = (float) rng.random(min, max);
-
-	p->velocity = love::Vector(cosf(dir), sinf(dir)) * speed;
-
-	p->linearAcceleration.x = (float) rng.random(linearAccelerationMin.x, linearAccelerationMax.x);
-	p->linearAcceleration.y = (float) rng.random(linearAccelerationMin.y, linearAccelerationMax.y);
-
-	min = radialAccelerationMin;
-	max = radialAccelerationMax;
-	p->radialAcceleration = (float) rng.random(min, max);
-
-	min = tangentialAccelerationMin;
-	max = tangentialAccelerationMax;
-	p->tangentialAcceleration = (float) rng.random(min, max);
-
-	min = linearDampingMin;
-	max = linearDampingMax;
-	p->linearDamping = (float) rng.random(min, max);
-
-	p->sizeOffset       = (float) rng.random(sizeVariation); // time offset for size change
-	p->sizeIntervalSize = (1.0f - (float) rng.random(sizeVariation)) - p->sizeOffset;
-	p->size = sizes[(size_t)(p->sizeOffset - .5f) * (sizes.size() - 1)];
-
-	min = rotationMin;
-	max = rotationMax;
-	p->spinStart = calculate_variation(spinStart, spinEnd, spinVariation);
-	p->spinEnd = calculate_variation(spinEnd, spinStart, spinVariation);
-	p->rotation = (float) rng.random(min, max);
-
-	p->angle = p->rotation;
-	if (relativeRotation)
-		p->angle += atan2f(p->velocity.y, p->velocity.x);
-
-	p->color = colors[0];
-
-	p->quadIndex = 0;
-}
-
-void ParticleSystem::insertTop(Particle *p)
-{
-	if (pHead == nullptr)
-	{
-		pHead = p;
-		p->prev = nullptr;
-	}
-	else
-	{
-		pTail->next = p;
-		p->prev = pTail;
-	}
-	p->next = nullptr;
-	pTail = p;
-}
-
-void ParticleSystem::insertBottom(Particle *p)
-{
-	if (pTail == nullptr)
-	{
-		pTail = p;
-		p->next = nullptr;
-	}
-	else
-	{
-		pHead->prev = p;
-		p->next = pHead;
-	}
-	p->prev = nullptr;
-	pHead = p;
-}
-
-void ParticleSystem::insertRandom(Particle *p)
-{
-	// Nonuniform, but 64-bit is so large nobody will notice. Hopefully.
-	uint64 pos = rng.rand() % ((int64) activeParticles + 1);
-
-	// Special case where the particle gets inserted before the head.
-	if (pos == activeParticles)
-	{
-		Particle *pA = pHead;
-		if (pA)
-			pA->prev = p;
-		p->prev = nullptr;
-		p->next = pA;
-		pHead = p;
-		return;
-	}
-
-	// Inserts the particle after the randomly selected particle.
-	Particle *pA = pMem + pos;
-	Particle *pB = pA->next;
-	pA->next = p;
-	if (pB)
-		pB->prev = p;
-	else
-		pTail = p;
-	p->prev = pA;
-	p->next = pB;
-}
-
-ParticleSystem::Particle *ParticleSystem::removeParticle(Particle *p)
-{
-	// The linked list is updated in this function and old pointers may be
-	// invalidated. The returned pointer will inform the caller of the new
-	// pointer to the next particle.
-	Particle *pNext = nullptr;
-
-	// Removes the particle from the linked list.
-	if (p->prev)
-		p->prev->next = p->next;
-	else
-		pHead = p->next;
-
-	if (p->next)
-	{
-		p->next->prev = p->prev;
-		pNext = p->next;
-	}
-	else
-		pTail = p->prev;
-
-	// The (in memory) last particle can now be moved into the free slot.
-	// It will skip the moving if it happens to be the removed particle.
-	pFree--;
-	if (p != pFree)
-	{
-		*p = *pFree;
-		if (pNext == pFree)
-			pNext = p;
-
-		if (p->prev)
-			p->prev->next = p;
-		else
-			pHead = p;
-
-		if (p->next)
-			p->next->prev = p;
-		else
-			pTail = p;
-	}
-
-	activeParticles--;
-	return pNext;
-}
-
-void ParticleSystem::setTexture(Texture *tex)
-{
-	texture.set(tex);
-
-	if (defaultOffset)
-		resetOffset();
-}
-
-Texture *ParticleSystem::getTexture() const
-{
-	return texture.get();
-}
-
-void ParticleSystem::setInsertMode(InsertMode mode)
-{
-	insertMode = mode;
-}
-
-ParticleSystem::InsertMode ParticleSystem::getInsertMode() const
-{
-	return insertMode;
-}
-
-void ParticleSystem::setEmissionRate(float rate)
-{
-	if (rate < 0.0f)
-		throw love::Exception("Invalid emission rate");
-	emissionRate = rate;
-}
-
-float ParticleSystem::getEmissionRate() const
-{
-	return emissionRate;
-}
-
-void ParticleSystem::setEmitterLifetime(float life)
-{
-	this->life = lifetime = life;
-}
-
-float ParticleSystem::getEmitterLifetime() const
-{
-	return lifetime;
-}
-
-void ParticleSystem::setParticleLifetime(float min, float max)
-{
-	particleLifeMin = min;
-	if (max == 0)
-		particleLifeMax = min;
-	else
-		particleLifeMax = max;
-}
-
-void ParticleSystem::getParticleLifetime(float &min, float &max) const
-{
-	min = particleLifeMin;
-	max = particleLifeMax;
-}
-
-void ParticleSystem::setPosition(float x, float y)
-{
-	position = love::Vector(x, y);
-	prevPosition = position;
-}
-
-const love::Vector &ParticleSystem::getPosition() const
-{
-	return position;
-}
-
-void ParticleSystem::moveTo(float x, float y)
-{
-	position = love::Vector(x, y);
-}
-
-void ParticleSystem::setAreaSpread(AreaSpreadDistribution distribution, float x, float y)
-{
-	areaSpread = love::Vector(x, y);
-	areaSpreadDistribution = distribution;
-}
-
-ParticleSystem::AreaSpreadDistribution ParticleSystem::getAreaSpreadDistribution() const
-{
-	return areaSpreadDistribution;
-}
-
-const love::Vector &ParticleSystem::getAreaSpreadParameters() const
-{
-	return areaSpread;
-}
-
-void ParticleSystem::setDirection(float direction)
-{
-	this->direction = direction;
-}
-
-float ParticleSystem::getDirection() const
-{
-	return direction;
-}
-
-void ParticleSystem::setSpread(float spread)
-{
-	this->spread = spread;
-}
-
-float ParticleSystem::getSpread() const
-{
-	return spread;
-}
-
-void ParticleSystem::setSpeed(float speed)
-{
-	speedMin = speedMax = speed;
-}
-
-void ParticleSystem::setSpeed(float min, float max)
-{
-	speedMin = min;
-	speedMax = max;
-}
-
-void ParticleSystem::getSpeed(float &min, float &max) const
-{
-	min = speedMin;
-	max = speedMax;
-}
-
-void ParticleSystem::setLinearAcceleration(float x, float y)
-{
-	linearAccelerationMin.x = linearAccelerationMax.x = x;
-	linearAccelerationMin.y = linearAccelerationMax.y = y;
-}
-
-void ParticleSystem::setLinearAcceleration(float xmin, float ymin, float xmax, float ymax)
-{
-	linearAccelerationMin = love::Vector(xmin, ymin);
-	linearAccelerationMax = love::Vector(xmax, ymax);
-}
-
-void ParticleSystem::getLinearAcceleration(love::Vector &min, love::Vector &max) const
-{
-	min = linearAccelerationMin;
-	max = linearAccelerationMax;
-}
+	love::graphics::ParticleSystem::setBufferSize(size);
 
 
-void ParticleSystem::setRadialAcceleration(float acceleration)
-{
-	radialAccelerationMin = radialAccelerationMax = acceleration;
-}
-
-void ParticleSystem::setRadialAcceleration(float min, float max)
-{
-	radialAccelerationMin = min;
-	radialAccelerationMax = max;
-}
-
-void ParticleSystem::getRadialAcceleration(float &min, float &max) const
-{
-	min = radialAccelerationMin;
-	max = radialAccelerationMax;
-}
-
-void ParticleSystem::setTangentialAcceleration(float acceleration)
-{
-	tangentialAccelerationMin = tangentialAccelerationMax = acceleration;
-}
-
-void ParticleSystem::setTangentialAcceleration(float min, float max)
-{
-	tangentialAccelerationMin = min;
-	tangentialAccelerationMax = max;
-}
-
-void ParticleSystem::getTangentialAcceleration(float &min, float &max) const
-{
-	min = tangentialAccelerationMin;
-	max = tangentialAccelerationMax;
-}
-
-void ParticleSystem::setLinearDamping(float min, float max)
-{
-	linearDampingMin = min;
-	linearDampingMax = max;
-}
-
-void ParticleSystem::getLinearDamping(float &min, float &max) const
-{
-	min = linearDampingMin;
-	max = linearDampingMax;
-}
-
-void ParticleSystem::setSize(float size)
-{
-	sizes.resize(1);
-	sizes[0] = size;
-}
-
-void ParticleSystem::setSizes(const std::vector<float> &newSizes)
-{
-	sizes = newSizes;
-}
-
-const std::vector<float> &ParticleSystem::getSizes() const
-{
-	return sizes;
-}
-
-void ParticleSystem::setSizeVariation(float variation)
-{
-	sizeVariation = variation;
-}
-
-float ParticleSystem::getSizeVariation() const
-{
-	return sizeVariation;
-}
-
-void ParticleSystem::setRotation(float rotation)
-{
-	rotationMin = rotationMax = rotation;
-}
-
-void ParticleSystem::setRotation(float min, float max)
-{
-	rotationMin = min;
-	rotationMax = max;
-}
-
-void ParticleSystem::getRotation(float &min, float &max) const
-{
-	min = rotationMin;
-	max = rotationMax;
-}
-
-void ParticleSystem::setSpin(float spin)
-{
-	spinStart = spin;
-	spinEnd = spin;
-}
-
-void ParticleSystem::setSpin(float start, float end)
-{
-	spinStart = start;
-	spinEnd = end;
-}
-
-void ParticleSystem::getSpin(float &start, float &end) const
-{
-	start = spinStart;
-	end = spinEnd;
-}
-
-void ParticleSystem::setSpinVariation(float variation)
-{
-	spinVariation = variation;
-}
-
-float ParticleSystem::getSpinVariation() const
-{
-	return spinVariation;
-}
-
-void ParticleSystem::setOffset(float x, float y)
-{
-	offset = love::Vector(x, y);
-	defaultOffset = false;
-}
-
-love::Vector ParticleSystem::getOffset() const
-{
-	return offset;
-}
-
-void ParticleSystem::setColor(const std::vector<Colorf> &newColors)
-{
-	colors = newColors;
-
-	for (Colorf &c : colors)
-	{
-		// We want to store the colors as [0, 1], rather than [0, 255].
-		c.r /= 255.0f;
-		c.g /= 255.0f;
-		c.b /= 255.0f;
-		c.a /= 255.0f;
-	}
-}
-
-std::vector<Colorf> ParticleSystem::getColor() const
-{
-	// The particle system stores colors in the range of [0, 1]...
-	std::vector<Colorf> ncolors(colors);
-
-	for (Colorf &c : ncolors)
-	{
-		c.r *= 255.0f;
-		c.g *= 255.0f;
-		c.b *= 255.0f;
-		c.a *= 255.0f;
-	}
-
-	return ncolors;
-}
-
-void ParticleSystem::setQuads(const std::vector<Quad *> &newQuads)
-{
-	std::vector<StrongRef<Quad>> quadlist;
-	quadlist.reserve(newQuads.size());
-
-	for (Quad *q : newQuads)
-		quadlist.push_back(q);
-
-	quads = quadlist;
-
-	if (defaultOffset)
-		resetOffset();
-}
-
-void ParticleSystem::setQuads()
-{
-	quads.clear();
-}
-
-std::vector<Quad *> ParticleSystem::getQuads() const
-{
-	std::vector<Quad *> quadlist;
-	quadlist.reserve(quads.size());
-
-	for (const StrongRef<Quad> &q : quads)
-		quadlist.push_back(q.get());
-
-	return quadlist;
-}
-
-void ParticleSystem::setRelativeRotation(bool enable)
-{
-	relativeRotation = enable;
-}
-
-bool ParticleSystem::hasRelativeRotation() const
-{
-	return relativeRotation;
-}
-
-uint32 ParticleSystem::getCount() const
-{
-	return activeParticles;
-}
-
-void ParticleSystem::start()
-{
-	active = true;
-}
-
-void ParticleSystem::stop()
-{
-	active = false;
-	life = lifetime;
-	emitCounter = 0;
-}
-
-void ParticleSystem::pause()
-{
-	active = false;
-}
-
-void ParticleSystem::reset()
-{
-	if (pMem == nullptr)
-		return;
-
-	pFree = pMem;
-	pHead = nullptr;
-	pTail = nullptr;
-	activeParticles = 0;
-	life = lifetime;
-	emitCounter = 0;
-}
-
-void ParticleSystem::emit(uint32 num)
-{
-	if (!active)
-		return;
-
-	num = std::min(num, maxParticles - activeParticles);
-
-	while(num--)
-		addParticle(1.0f);
-}
-
-bool ParticleSystem::isActive() const
-{
-	return active;
-}
-
-bool ParticleSystem::isPaused() const
-{
-	return !active && life < lifetime;
-}
-
-bool ParticleSystem::isStopped() const
-{
-	return !active && life >= lifetime;
-}
-
-bool ParticleSystem::isEmpty() const
-{
-	return activeParticles == 0;
-}
-
-bool ParticleSystem::isFull() const
-{
-	return activeParticles == maxParticles;
+	quadIndices = QuadIndices(size);
+	createVertices(size);
 }
 }
 
 
 void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 {
 	uint32 pCount = getCount();
 	uint32 pCount = getCount();
+
 	if (pCount == 0 || texture.get() == nullptr || pMem == nullptr || particleVerts == nullptr)
 	if (pCount == 0 || texture.get() == nullptr || pMem == nullptr || particleVerts == nullptr)
 		return;
 		return;
 
 
@@ -904,163 +148,6 @@ void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, flo
 	}
 	}
 }
 }
 
 
-void ParticleSystem::update(float dt)
-{
-	if (pMem == nullptr || dt == 0.0f)
-		return;
-
-	// Traverse all particles and update.
-	Particle *p = pHead;
-
-	while (p)
-	{
-		// Decrease lifespan.
-		p->life -= dt;
-
-		if (p->life <= 0)
-			p = removeParticle(p);
-		else
-		{
-			// Temp variables.
-			love::Vector radial, tangential;
-			love::Vector ppos = p->position;
-
-			// Get vector from particle center to particle.
-			radial = ppos - p->origin;
-			radial.normalize();
-			tangential = radial;
-
-			// Resize radial acceleration.
-			radial *= p->radialAcceleration;
-
-			// Calculate tangential acceleration.
-			{
-				float a = tangential.getX();
-				tangential.setX(-tangential.getY());
-				tangential.setY(a);
-			}
-
-			// Resize tangential.
-			tangential *= p->tangentialAcceleration;
-
-			// Update velocity.
-			p->velocity += (radial + tangential + p->linearAcceleration) * dt;
-
-			// Apply damping.
-			p->velocity *= 1.0f / (1.0f + p->linearDamping * dt);
-
-			// Modify position.
-			ppos += p->velocity * dt;
-
-			p->position = ppos;
-
-			const float t = 1.0f - p->life / p->lifetime;
-
-			// Rotate.
-			p->rotation += (p->spinStart * (1.0f - t) + p->spinEnd * t) * dt;
-
-			p->angle = p->rotation;
-
-			if (relativeRotation)
-				p->angle += atan2f(p->velocity.y, p->velocity.x);
-
-			// Change size according to given intervals:
-			// i = 0       1       2      3          n-1
-			//     |-------|-------|------|--- ... ---|
-			// t = 0    1/(n-1)        3/(n-1)        1
-			//
-			// `s' is the interpolation variable scaled to the current
-			// interval width, e.g. if n = 5 and t = 0.3, then the current
-			// indices are 1,2 and s = 0.3 - 0.25 = 0.05
-			float s = p->sizeOffset + t * p->sizeIntervalSize; // size variation
-			s *= (float)(sizes.size() - 1); // 0 <= s < sizes.size()
-			size_t i = (size_t)s;
-			size_t k = (i == sizes.size() - 1) ? i : i + 1; // boundary check (prevents failing on t = 1.0f)
-			s -= (float)i; // transpose s to be in interval [0:1]: i <= s < i + 1 ~> 0 <= s < 1
-			p->size = sizes[i] * (1.0f - s) + sizes[k] * s;
-
-			// Update color according to given intervals (as above)
-			s = t * (float)(colors.size() - 1);
-			i = (size_t)s;
-			k = (i == colors.size() - 1) ? i : i + 1;
-			s -= (float)i;                            // 0 <= s <= 1
-			p->color = colors[i] * (1.0f - s) + colors[k] * s;
-
-			// Update the quad index.
-			k = quads.size();
-			if (k > 0)
-			{
-				s = t * (float) k; // [0:numquads-1] (clamped below)
-				i = (s > 0.0f) ? (size_t) s : 0;
-				p->quadIndex = (int) ((i < k) ? i : k - 1);
-			}
-
-			// Next particle.
-			p = p->next;
-		}
-	}
-
-	// Make some more particles.
-	if (active)
-	{
-		float rate = 1.0f / emissionRate; // the amount of time between each particle emit
-		emitCounter += dt;
-		float total = emitCounter - rate;
-		while (emitCounter > rate)
-		{
-			addParticle(1.0f - (emitCounter - rate) / total);
-			emitCounter -= rate;
-		}
-		/*int particles = (int)(emissionRate * dt);
-		 for (int i = 0; i != particles; i++)
-		 add();*/
-
-		life -= dt;
-		if (lifetime != -1 && life < 0)
-			stop();
-	}
-
-	prevPosition = position;
-}
-
-bool ParticleSystem::getConstant(const char *in, AreaSpreadDistribution &out)
-{
-	return distributions.find(in, out);
-}
-
-bool ParticleSystem::getConstant(AreaSpreadDistribution in, const char *&out)
-{
-	return distributions.find(in, out);
-}
-
-bool ParticleSystem::getConstant(const char *in, InsertMode &out)
-{
-	return insertModes.find(in, out);
-}
-
-bool ParticleSystem::getConstant(InsertMode in, const char *&out)
-{
-	return insertModes.find(in, out);
-}
-
-StringMap<ParticleSystem::AreaSpreadDistribution, ParticleSystem::DISTRIBUTION_MAX_ENUM>::Entry ParticleSystem::distributionsEntries[] =
-{
-	{ "none",    ParticleSystem::DISTRIBUTION_NONE },
-	{ "uniform", ParticleSystem::DISTRIBUTION_UNIFORM },
-	{ "normal",  ParticleSystem::DISTRIBUTION_NORMAL },
-};
-
-StringMap<ParticleSystem::AreaSpreadDistribution, ParticleSystem::DISTRIBUTION_MAX_ENUM> ParticleSystem::distributions(ParticleSystem::distributionsEntries, sizeof(ParticleSystem::distributionsEntries));
-
-StringMap<ParticleSystem::InsertMode, ParticleSystem::INSERT_MODE_MAX_ENUM>::Entry ParticleSystem::insertModesEntries[] =
-{
-	{ "top",    ParticleSystem::INSERT_MODE_TOP },
-	{ "bottom", ParticleSystem::INSERT_MODE_BOTTOM },
-	{ "random", ParticleSystem::INSERT_MODE_RANDOM },
-};
-
-StringMap<ParticleSystem::InsertMode, ParticleSystem::INSERT_MODE_MAX_ENUM> ParticleSystem::insertModes(ParticleSystem::insertModesEntries, sizeof(ParticleSystem::insertModesEntries));
-
 } // opengl
 } // opengl
 } // graphics
 } // graphics
 } // love
 } // love

+ 7 - 631
jni/love/src/modules/graphics/opengl/ParticleSystem.h

@@ -22,18 +22,9 @@
 #define LOVE_GRAPHICS_OPENGL_PARTICLE_SYSTEM_H
 #define LOVE_GRAPHICS_OPENGL_PARTICLE_SYSTEM_H
 
 
 // LOVE
 // LOVE
-#include "common/int.h"
-#include "common/math.h"
-#include "common/Vector.h"
-#include "graphics/Drawable.h"
-#include "graphics/Color.h"
-#include "graphics/Quad.h"
-#include "graphics/Texture.h"
+#include "graphics/ParticleSystem.h"
 #include "GLBuffer.h"
 #include "GLBuffer.h"
 
 
-// STL
-#include <vector>
-
 namespace love
 namespace love
 {
 {
 namespace graphics
 namespace graphics
@@ -41,643 +32,28 @@ namespace graphics
 namespace opengl
 namespace opengl
 {
 {
 
 
-/**
- * A class for creating, moving and drawing particles.
- * A big thanks to bobthebloke.org
- **/
-class ParticleSystem : public Drawable
+class ParticleSystem : public love::graphics::ParticleSystem
 {
 {
 public:
 public:
-	/**
-	 * Type of distribution new particles are drawn from: None, uniform, normal.
-	 */
-	enum AreaSpreadDistribution
-	{
-		DISTRIBUTION_NONE,
-		DISTRIBUTION_UNIFORM,
-		DISTRIBUTION_NORMAL,
-		DISTRIBUTION_MAX_ENUM
-	};
-
-	/**
-	 * Insertion modes of new particles in the list: top, bottom, random.
-	 */
-	enum InsertMode
-	{
-		INSERT_MODE_TOP,
-		INSERT_MODE_BOTTOM,
-		INSERT_MODE_RANDOM,
-		INSERT_MODE_MAX_ENUM
-	};
 
 
-	/**
-	 * Maximum numbers of particles in a ParticleSystem.
-	 * This limit comes from the fact that a quad requires four vertices and the
-	 * OpenGL API where GLsizei is a signed int.
-	 **/
-	static const uint32 MAX_PARTICLES = LOVE_INT32_MAX / 4;
-
-	/**
-	 * Creates a particle system with the specified buffer size and texture.
-	 **/
 	ParticleSystem(Texture *texture, uint32 buffer);
 	ParticleSystem(Texture *texture, uint32 buffer);
 	ParticleSystem(const ParticleSystem &p);
 	ParticleSystem(const ParticleSystem &p);
 
 
-	/**
-	 * Deletes any allocated memory.
-	 **/
 	virtual ~ParticleSystem();
 	virtual ~ParticleSystem();
 
 
-	/**
-	 * Creates an identical copy of this ParticleSystem. The clone does not
-	 * duplicate any existing particles from this ParticleSystem, just the
-	 * settable parameters.
-	 **/
-	ParticleSystem *clone();
-
-	/**
-	 * Sets the texture used in the particle system.
-	 * @param texture The new texture.
-	 **/
-	void setTexture(Texture *texture);
-
-	/**
-	 * Returns the texture used when drawing the particle system.
-	 **/
-	Texture *getTexture() const;
-
-	/**
-	 * Clears the current buffer and allocates the appropriate amount of space for the buffer.
-	 * @param size The new buffer size.
-	 **/
-	void setBufferSize(uint32 size);
-
-	/**
-	 * Returns the total amount of particles this ParticleSystem can have active
-	 * at any given point in time.
-	 **/
-	uint32 getBufferSize() const;
-
-	/**
-	 * Sets the insert mode for new particles.
-	 * @param mode The new insert mode.
-	 */
-	void setInsertMode(InsertMode mode);
-
-	/**
-	 * Returns the current insert mode.
-	 */
-	InsertMode getInsertMode() const;
-
-	/**
-	 * Sets the emission rate.
-	 * @param rate The amount of particles per second.
-	 **/
-	void setEmissionRate(float rate);
-
-	/**
-	 * Returns the number of particles created per second.
-	 **/
-	float getEmissionRate() const;
-
-	/**
-	 * Sets the lifetime of the particle emitter (-1 means eternal)
-	 * @param life The lifetime (in seconds).
-	 **/
-	void setEmitterLifetime(float life);
-
-	/**
-	 * Returns the lifetime of the particle emitter.
-	 **/
-	float getEmitterLifetime() const;
-
-	/**
-	 * Sets the life range of the particles.
-	 * @param min The minimum life.
-	 * @param max The maximum life (if 0, then becomes the same as minimum life).
-	 **/
-	void setParticleLifetime(float min, float max = 0);
-
-	/**
-	 * Gets the lifetime of a particle.
-	 * @param[out] min The minimum life.
-	 * @param[out] max The maximum life.
-	 **/
-	void getParticleLifetime(float &min, float &max) const;
-
-	/**
-	 * Sets the position of the center of the emitter.
-	 * Used to move the emitter without changing the position of already existing particles.
-	 * @param x The x-coordinate.
-	 * @param y The y-coordinate.
-	 **/
-	void setPosition(float x, float y);
-
-	/**
-	 * Returns the position of the emitter.
-	 **/
-	const love::Vector &getPosition() const;
-
-	/**
-	 * Moves the position of the center of the emitter.
-	 * When update is called, newly spawned particles will appear in a line
-	 * between the old emitter position and where the emitter was moved to,
-	 * resulting in a smoother-feeling particle system if moveTo is called
-	 * repeatedly.
-	 **/
-	void moveTo(float x, float y);
-
-	/**
-	 * Sets the emission area spread parameters and distribution type. The interpretation of
-	 * the parameters depends on the distribution type:
-	 *
-	 * * None:    Parameters are ignored. No area spread.
-	 * * Uniform: Parameters denote maximal (symmetric) displacement from emitter position.
-	 * * Normal:  Parameters denote the standard deviation in x and y direction. x and y are assumed to be uncorrelated.
-	 * @param x First parameter. Interpretation depends on distribution type.
-	 * @param y Second parameter. Interpretation depends on distribution type.
-	 * @param distribution Distribution type
-	 **/
-	void setAreaSpread(AreaSpreadDistribution distribution, float x, float y);
-
-	/**
-	 * Returns area spread distribution type.
-	 **/
-	AreaSpreadDistribution getAreaSpreadDistribution() const;
-
-	/**
-	 * Returns area spread parameters.
-	 **/
-	const love::Vector &getAreaSpreadParameters() const;
-
-	/**
-	 * Sets the direction of the particle emitter.
-	 * @param direction The direction (in degrees).
-	 **/
-	void setDirection(float direction);
-
-	/**
-	 * Returns the direction of the particle emitter (in radians).
-	 **/
-	float getDirection() const;
-
-	/**
-	 * Sets the spread of the particle emitter.
-	 * @param spread The spread (in radians).
-	 **/
-	void setSpread(float spread);
-
-	/**
-	 * Returns the directional spread of the emitter (in radians).
-	 **/
-	float getSpread() const;
-
-	/**
-	 * Sets the speed of the particles.
-	 * @param speed The speed.
-	 **/
-	void setSpeed(float speed);
-
-	/**
-	 * Sets the speed of the particles.
-	 * @param min The minimum speed.
-	 * @param max The maximum speed.
-	 **/
-	void setSpeed(float min, float max);
-
-	/**
-	 * Gets the speed of the particles.
-	 * @param[out] min The minimum speed.
-	 * @param[out] max The maximum speed.
-	 **/
-	void getSpeed(float &min, float &max) const;
-
-	/**
-	 * Sets the linear acceleration (the acceleration along the x and y axes).
-	 * @param x The acceleration along the x-axis.
-	 * @param y The acceleration along the y-axis.
-	 **/
-	void setLinearAcceleration(float x, float y);
-
-	/**
-	 * Sets the linear acceleration (the acceleration along the x and y axes).
-	 * @param xmin The minimum amount of acceleration along the x-axis.
-	 * @param ymin The minimum amount of acceleration along the y-axis.
-	 * @param xmax The maximum amount of acceleration along the x-axis.
-	 * @param ymax The maximum amount of acceleration along the y-axis.
-	 **/
-	void setLinearAcceleration(float xmin, float ymin, float xmax, float ymax);
-
-	/**
-	 * Gets the linear acceleration of the particles.
-	 * @param[out] min The minimum acceleration.
-	 * @param[out] max The maximum acceleration.
-	 **/
-	void getLinearAcceleration(love::Vector &min, love::Vector &max) const;
-
-	/**
-	 * Sets the radial acceleration (the acceleration towards the particle emitter).
-	 * @param acceleration The amount of acceleration.
-	 **/
-	void setRadialAcceleration(float acceleration);
-
-	/**
-	 * Sets the radial acceleration (the acceleration towards the particle emitter).
-	 * @param min The minimum acceleration.
-	 * @param max The maximum acceleration.
-	 **/
-	void setRadialAcceleration(float min, float max);
-
-	/**
-	 * Gets the radial acceleration.
-	 * @param[out] min The minimum amount of radial acceleration.
-	 * @param[out] max The maximum amount of radial acceleration.
-	 **/
-	void getRadialAcceleration(float &min, float &max) const;
-
-	/**
-	 * Sets the tangential acceleration (the acceleration perpendicular to the particle's direction).
-	 * @param acceleration The amount of acceleration.
-	 **/
-	void setTangentialAcceleration(float acceleration);
-
-	/**
-	 * Sets the tangential acceleration (the acceleration perpendicular to the particle's direction).
-	 * @param min The minimum acceleration.
-	 * @param max The maximum acceleration.
-	 **/
-	void setTangentialAcceleration(float min, float max);
-
-	/**
-	 * Gets the tangential acceleration.
-	 * @param[out] min The minimum tangential acceleration.
-	 * @param[out] max The maximum tangential acceleration.
-	 **/
-	void getTangentialAcceleration(float &min, float &max) const;
-
-	/**
-	 * Sets the amount of linear damping. Damping reduces the velocity of
-	 * particles over time. A value of 0 corresponds to no damping.
-	 **/
-	void setLinearDamping(float min, float max);
-
-	/**
-	 * Gets the current amount of linear damping.
-	 **/
-	void getLinearDamping(float &min, float &max) const;
-
-	/**
-	 * Sets the size of the sprite (1.0 being the default size).
-	 * @param size The size of the sprite.
-	 **/
-	void setSize(float size);
-
-	/**
-	 * Sets the sizes of the sprite upon creation and upon death (1.0 being the default size).
-	 * @param newSizes Array of sizes
-	 **/
-	void setSizes(const std::vector<float> &newSizes);
-
-	/**
-	 * Returns the sizes of the particle sprites.
-	 **/
-	const std::vector<float> &getSizes() const;
-
-	/**
-	 * Sets the amount of variation to the sprite's beginning size (0 being no variation and 1.0 a random size between start and end).
-	 * @param variation The amount of variation.
-	 **/
-	void setSizeVariation(float variation);
-
-	/**
-	 * Returns the amount of initial size variation between particles.
-	 **/
-	float getSizeVariation() const;
-
-	/**
-	 * Sets the amount of rotation a sprite starts out with.
-	 * @param rotation The amount of rotation.
-	 **/
-	void setRotation(float rotation);
-
-	/**
-	 * Sets the amount of rotation a sprite starts out with (a random value between min and max).
-	 * @param min The minimum amount of rotation.
-	 * @param max The maximum amount of rotation.
-	 **/
-	void setRotation(float min, float max);
-
-	/**
-	 * Gets the initial amount of rotation of a particle, in radians.
-	 * @param[out] min The minimum initial rotation.
-	 * @param[out] max The maximum initial rotation.
-	 **/
-	void getRotation(float &min, float &max) const;
-
-	/**
-	 * Sets the spin of the sprite.
-	 * @param spin The spin of the sprite (in degrees).
-	 **/
-	void setSpin(float spin);
-
-	/**
-	 * Sets the spin of the sprite upon particle creation and death.
-	 * @param start The spin of the sprite upon creation (in radians / second).
-	 * @param end The spin of the sprite upon death (in radians / second).
-	 **/
-	void setSpin(float start, float end);
-
-	/**
-	 * Gets the amount of spin of a particle during its lifetime.
-	 * @param[out] start The initial spin, in radians / s.
-	 * @param[out] end The final spin, in radians / s.
-	 **/
-	void getSpin(float &start, float &end) const;
-
-	/**
-	 * Sets the variation of the start spin (0 being no variation and 1 being a random spin between start and end).
-	 * @param variation The variation.
-	 **/
-	void setSpinVariation(float variation);
-
-	/**
-	 * Returns the amount of variation of the start spin of a particle.
-	 **/
-	float getSpinVariation() const;
-
-	/**
-	 * Sets the particles' offsets for rotation.
-	 * @param x The x offset.
-	 * @param y The y offset.
-	 **/
-	void setOffset(float x, float y);
-
-	/**
-	 * Returns of the particle offset.
-	 **/
-	love::Vector getOffset() const;
-
-	/**
-	 * Sets the color of the particles.
-	 * @param newColors Array of colors
-	 **/
-	void setColor(const std::vector<Colorf> &newColors);
-
-	/**
-	 * Returns the color of the particles.
-	 **/
-	std::vector<Colorf> getColor() const;
-
-	/**
-	 * Sets a list of Quads to use for particles over their lifetime.
-	 **/
-	void setQuads(const std::vector<Quad *> &newQuads);
-	void setQuads();
-
-	/**
-	 * Gets the Quads used when drawing the particles.
-	 **/
-	std::vector<Quad *> getQuads() const;
+	ParticleSystem *clone() override;
+	void setBufferSize(uint32 size) override;
+	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) override;
 
 
-	/**
-	 * sets whether particle angles & rotations are relative to their velocities.
-	 **/
-	void setRelativeRotation(bool enable);
-	bool hasRelativeRotation() const;
+private:
 
 
-	/**
-	 * Returns the amount of particles that are currently active in the system.
-	 **/
-	uint32 getCount() const;
-
-	/**
-	 * Starts/resumes the particle emitter.
-	 **/
-	void start();
-
-	/**
-	 * Stops the particle emitter and resets.
-	 **/
-	void stop();
-
-	/**
-	 * Pauses the particle emitter.
-	 **/
-	void pause();
-
-	/**
-	 * Resets the particle emitter.
-	 **/
-	void reset();
-
-	/**
-	 * Instantly emits a number of particles.
-	 * @param num The number of particles to emit.
-	 **/
-	void emit(uint32 num);
-
-	/**
-	 * Returns whether the particle emitter is active.
-	 **/
-	bool isActive() const;
-
-	/**
-	 * Returns whether the particle emitter is paused.
-	 **/
-	bool isPaused() const;
-
-	bool isStopped() const;
-
-	/**
-	 * Returns whether the particle system is empty of particles or not.
-	 **/
-	bool isEmpty() const;
-
-	/**
-	 * Returns whether the amount of particles has reached the buffer limit or not.
-	 **/
-	bool isFull() const;
-
-	/**
-	 * Draws the particle emitter at the specified position.
-	 * @param x The x-coordinate.
-	 * @param y The y-coordinate.
-	 **/
-	virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
-
-	/**
-	 * Updates the particle system.
-	 * @param dt Time since last update.
-	 **/
-	void update(float dt);
-
-	static bool getConstant(const char *in, AreaSpreadDistribution &out);
-	static bool getConstant(AreaSpreadDistribution in, const char *&out);
-
-	static bool getConstant(const char *in, InsertMode &out);
-	static bool getConstant(InsertMode in, const char *&out);
-
-protected:
-
-	// Represents a single particle.
-	struct Particle
-	{
-		Particle *prev;
-		Particle *next;
-
-		float lifetime;
-		float life;
-
-		love::Vector position;
-
-		// Particles gravitate towards this point.
-		love::Vector origin;
-
-		love::Vector velocity;
-		love::Vector linearAcceleration;
-		float radialAcceleration;
-		float tangentialAcceleration;
-
-		float linearDamping;
-
-		float size;
-		float sizeOffset;
-		float sizeIntervalSize;
-
-		float rotation; // Amount of rotation applied to the final angle.
-		float angle;
-		float spinStart;
-		float spinEnd;
-
-		Colorf color;
-
-		int quadIndex;
-	};
-
-	// Pointer to the beginning of the allocated memory.
-	Particle *pMem;
-
-	// Pointer to a free particle.
-	Particle *pFree;
-
-	// Pointer to the start of the linked list.
-	Particle *pHead;
-
-	// Pointer to the end of the linked list.
-	Particle *pTail;
+	void createVertices(size_t numparticles);
 
 
 	// array of transformed vertex data for all particles, for drawing
 	// array of transformed vertex data for all particles, for drawing
 	Vertex *particleVerts;
 	Vertex *particleVerts;
 
 
 	// Vertex index buffer.
 	// Vertex index buffer.
 	QuadIndices quadIndices;
 	QuadIndices quadIndices;
-
-	// The texture to be drawn.
-	StrongRef<Texture> texture;
-
-	// Whether the particle emitter is active.
-	bool active;
-
-	// Insert mode of new particles.
-	InsertMode insertMode;
-
-	// The maximum number of particles.
-	uint32 maxParticles;
-
-	// The number of active particles.
-	uint32 activeParticles;
-
-	// The emission rate (particles/sec).
-	float emissionRate;
-
-	// Used to determine when a particle should be emitted.
-	float emitCounter;
-
-	// The relative position of the particle emitter.
-	love::Vector position;
-	love::Vector prevPosition;
-
-	// Emission area spread.
-	AreaSpreadDistribution areaSpreadDistribution;
-	love::Vector areaSpread;
-
-	// The lifetime of the particle emitter (-1 means infinite) and the life it has left.
-	float lifetime;
-	float life;
-
-	// The particle life.
-	float particleLifeMin;
-	float particleLifeMax;
-
-	// The direction (and spread) the particles will be emitted in. Measured in radians.
-	float direction;
-	float spread;
-
-	// The speed.
-	float speedMin;
-	float speedMax;
-
-	// Acceleration along the x and y axes.
-	love::Vector linearAccelerationMin;
-	love::Vector linearAccelerationMax;
-
-	// Acceleration towards the emitter's center
-	float radialAccelerationMin;
-	float radialAccelerationMax;
-
-	// Acceleration perpendicular to the particle's direction.
-	float tangentialAccelerationMin;
-	float tangentialAccelerationMax;
-
-	float linearDampingMin;
-	float linearDampingMax;
-
-	// Size.
-	std::vector<float> sizes;
-	float sizeVariation;
-
-	// Rotation
-	float rotationMin;
-	float rotationMax;
-
-	// Spin.
-	float spinStart;
-	float spinEnd;
-	float spinVariation;
-
-	// Offsets
-	love::Vector offset;
-
-	// Is the ParticleSystem using a default offset?
-	bool defaultOffset;
-
-	// Color.
-	std::vector<Colorf> colors;
-
-	// Quads.
-	std::vector<StrongRef<Quad>> quads;
-
-	bool relativeRotation;
-
-	void resetOffset();
-
-	void createBuffers(size_t size);
-	void deleteBuffers();
-
-	void addParticle(float t);
-	Particle *removeParticle(Particle *p);
-
-	// Called by addParticle.
-	void initParticle(Particle *p, float t);
-	void insertTop(Particle *p);
-	void insertBottom(Particle *p);
-	void insertRandom(Particle *p);
-
-	static StringMap<AreaSpreadDistribution, DISTRIBUTION_MAX_ENUM>::Entry distributionsEntries[];
-	static StringMap<AreaSpreadDistribution, DISTRIBUTION_MAX_ENUM> distributions;
-
-	static StringMap<InsertMode, INSERT_MODE_MAX_ENUM>::Entry insertModesEntries[];
-	static StringMap<InsertMode, INSERT_MODE_MAX_ENUM> insertModes;
 };
 };
 
 
 } // opengl
 } // opengl

+ 60 - 0
jni/love/src/modules/graphics/opengl/Shader.cpp

@@ -64,8 +64,10 @@ namespace
 
 
 Shader *Shader::current = nullptr;
 Shader *Shader::current = nullptr;
 Shader *Shader::defaultShader = nullptr;
 Shader *Shader::defaultShader = nullptr;
+Shader *Shader::defaultVideoShader = nullptr;
 
 
 Shader::ShaderSource Shader::defaultCode[Graphics::RENDERER_MAX_ENUM];
 Shader::ShaderSource Shader::defaultCode[Graphics::RENDERER_MAX_ENUM];
+Shader::ShaderSource Shader::defaultVideoCode[Graphics::RENDERER_MAX_ENUM];
 
 
 std::vector<int> Shader::textureCounters;
 std::vector<int> Shader::textureCounters;
 
 
@@ -77,6 +79,7 @@ Shader::Shader(const ShaderSource &source)
 	, lastCanvas((Canvas *) -1)
 	, lastCanvas((Canvas *) -1)
 	, lastViewport()
 	, lastViewport()
 	, lastPointSize(0.0f)
 	, lastPointSize(0.0f)
+	, videoTextureUnits()
 {
 {
 	if (source.vertex.empty() && source.pixel.empty())
 	if (source.vertex.empty() && source.pixel.empty())
 		throw love::Exception("Cannot create shader: no source code!");
 		throw love::Exception("Cannot create shader: no source code!");
@@ -225,6 +228,9 @@ bool Shader::loadVolatile()
 	lastProjectionMatrix.setTranslation(nan, nan);
 	lastProjectionMatrix.setTranslation(nan, nan);
 	lastTransformMatrix.setTranslation(nan, nan);
 	lastTransformMatrix.setTranslation(nan, nan);
 
 
+	for (int i = 0; i < 3; i++)
+		videoTextureUnits[i] = 0;
+
 	// zero out active texture list
 	// zero out active texture list
 	activeTexUnits.clear();
 	activeTexUnits.clear();
 	activeTexUnits.insert(activeTexUnits.begin(), gl.getMaxTextureUnits() - 1, 0);
 	activeTexUnits.insert(activeTexUnits.begin(), gl.getMaxTextureUnits() - 1, 0);
@@ -635,6 +641,57 @@ bool Shader::hasVertexAttrib(VertexAttribID attrib) const
 	return builtinAttributes[int(attrib)] != -1;
 	return builtinAttributes[int(attrib)] != -1;
 }
 }
 
 
+void Shader::setVideoTextures(GLuint ytexture, GLuint cbtexture, GLuint crtexture)
+{
+	TemporaryAttacher attacher(this);
+
+	// Set up the texture units that will be used by the shader to sample from
+	// the textures, if they haven't been set up yet.
+	if (videoTextureUnits[0] == 0)
+	{
+		const GLint locs[3] = {
+			builtinUniforms[BUILTIN_VIDEO_Y_CHANNEL],
+			builtinUniforms[BUILTIN_VIDEO_CB_CHANNEL],
+			builtinUniforms[BUILTIN_VIDEO_CR_CHANNEL]
+		};
+
+		const char *names[3] = {nullptr, nullptr, nullptr};
+		builtinNames.find(BUILTIN_VIDEO_Y_CHANNEL,  names[0]);
+		builtinNames.find(BUILTIN_VIDEO_CB_CHANNEL, names[1]);
+		builtinNames.find(BUILTIN_VIDEO_CR_CHANNEL, names[2]);
+
+		for (int i = 0; i < 3; i++)
+		{
+			if (locs[i] >= 0 && names[i] != nullptr)
+			{
+				videoTextureUnits[i] = getTextureUnit(names[i]);
+
+				// Increment global shader texture id counter for this texture
+				// unit, if we haven't already.
+				if (activeTexUnits[videoTextureUnits[i] - 1] == 0)
+					++textureCounters[videoTextureUnits[i] - 1];
+
+				glUniform1i(locs[i], videoTextureUnits[i]);
+			}
+		}
+	}
+
+	const GLuint textures[3] = {ytexture, cbtexture, crtexture};
+
+	// Bind the textures to their respective texture units.
+	for (int i = 0; i < 3; i++)
+	{
+		if (videoTextureUnits[i] != 0)
+		{
+			// Store texture id so it can be re-bound later.
+			activeTexUnits[videoTextureUnits[i] - 1] = textures[i];
+			gl.bindTextureToUnit(textures[i], videoTextureUnits[i], false);
+		}
+	}
+
+	gl.setTextureUnit(0);
+}
+
 void Shader::checkSetScreenParams()
 void Shader::checkSetScreenParams()
 {
 {
 	OpenGL::Viewport view = gl.getViewport();
 	OpenGL::Viewport view = gl.getViewport();
@@ -898,6 +955,9 @@ StringMap<Shader::BuiltinUniform, Shader::BUILTIN_MAX_ENUM>::Entry Shader::built
 	{"NormalMatrix", Shader::BUILTIN_NORMAL_MATRIX},
 	{"NormalMatrix", Shader::BUILTIN_NORMAL_MATRIX},
 	{"love_PointSize", Shader::BUILTIN_POINT_SIZE},
 	{"love_PointSize", Shader::BUILTIN_POINT_SIZE},
 	{"love_ScreenSize", Shader::BUILTIN_SCREEN_SIZE},
 	{"love_ScreenSize", Shader::BUILTIN_SCREEN_SIZE},
+	{"love_VideoYChannel", Shader::BUILTIN_VIDEO_Y_CHANNEL},
+	{"love_VideoCbChannel", Shader::BUILTIN_VIDEO_CB_CHANNEL},
+	{"love_VideoCrChannel", Shader::BUILTIN_VIDEO_CR_CHANNEL},
 };
 };
 
 
 StringMap<Shader::BuiltinUniform, Shader::BUILTIN_MAX_ENUM> Shader::builtinNames(Shader::builtinNameEntries, sizeof(Shader::builtinNameEntries));
 StringMap<Shader::BuiltinUniform, Shader::BUILTIN_MAX_ENUM> Shader::builtinNames(Shader::builtinNameEntries, sizeof(Shader::builtinNameEntries));

+ 8 - 0
jni/love/src/modules/graphics/opengl/Shader.h

@@ -64,6 +64,9 @@ public:
 		BUILTIN_NORMAL_MATRIX,
 		BUILTIN_NORMAL_MATRIX,
 		BUILTIN_POINT_SIZE,
 		BUILTIN_POINT_SIZE,
 		BUILTIN_SCREEN_SIZE,
 		BUILTIN_SCREEN_SIZE,
+		BUILTIN_VIDEO_Y_CHANNEL,
+		BUILTIN_VIDEO_CB_CHANNEL,
+		BUILTIN_VIDEO_CR_CHANNEL,
 		BUILTIN_MAX_ENUM
 		BUILTIN_MAX_ENUM
 	};
 	};
 
 
@@ -89,9 +92,11 @@ public:
 
 
 	// Pointer to the default Shader.
 	// Pointer to the default Shader.
 	static Shader *defaultShader;
 	static Shader *defaultShader;
+	static Shader *defaultVideoShader;
 
 
 	// Default shader code (a shader is always required internally.)
 	// Default shader code (a shader is always required internally.)
 	static ShaderSource defaultCode[Graphics::RENDERER_MAX_ENUM];
 	static ShaderSource defaultCode[Graphics::RENDERER_MAX_ENUM];
+	static ShaderSource defaultVideoCode[Graphics::RENDERER_MAX_ENUM];
 
 
 	/**
 	/**
 	 * Creates a new Shader using a list of source codes.
 	 * Creates a new Shader using a list of source codes.
@@ -182,6 +187,7 @@ public:
 	 **/
 	 **/
 	bool hasVertexAttrib(VertexAttribID attrib) const;
 	bool hasVertexAttrib(VertexAttribID attrib) const;
 
 
+	void setVideoTextures(GLuint ytexture, GLuint cbtexture, GLuint crtexture);
 	void checkSetScreenParams();
 	void checkSetScreenParams();
 	void checkSetPointSize(float size);
 	void checkSetPointSize(float size);
 	void checkSetBuiltinUniforms();
 	void checkSetBuiltinUniforms();
@@ -263,6 +269,8 @@ private:
 	Matrix4 lastTransformMatrix;
 	Matrix4 lastTransformMatrix;
 	Matrix4 lastProjectionMatrix;
 	Matrix4 lastProjectionMatrix;
 
 
+	GLuint videoTextureUnits[3];
+
 	// Counts total number of textures bound to each texture unit in all shaders
 	// Counts total number of textures bound to each texture unit in all shaders
 	static std::vector<int> textureCounters;
 	static std::vector<int> textureCounters;
 
 

+ 56 - 28
jni/love/src/modules/graphics/opengl/SpriteBatch.cpp

@@ -53,23 +53,9 @@ SpriteBatch::SpriteBatch(Texture *texture, int size, Mesh::Usage usage)
 		throw love::Exception("Invalid SpriteBatch size.");
 		throw love::Exception("Invalid SpriteBatch size.");
 
 
 	GLenum gl_usage = Mesh::getGLBufferUsage(usage);
 	GLenum gl_usage = Mesh::getGLBufferUsage(usage);
+	size_t vertex_size = sizeof(Vertex) * 4 * size;
 
 
-	const size_t vertex_size = sizeof(Vertex) * 4 * size;
-
-	try
-	{
-		array_buf = new GLBuffer(vertex_size, nullptr, GL_ARRAY_BUFFER, gl_usage, GLBuffer::MAP_EXPLICIT_RANGE_MODIFY);
-	}
-	catch (love::Exception &)
-	{
-		delete array_buf;
-		throw;
-	}
-	catch (std::bad_alloc &)
-	{
-		delete array_buf;
-		throw love::Exception("Out of memory.");
-	}
+	array_buf = new GLBuffer(vertex_size, nullptr, GL_ARRAY_BUFFER, gl_usage, GLBuffer::MAP_EXPLICIT_RANGE_MODIFY);
 }
 }
 
 
 SpriteBatch::~SpriteBatch()
 SpriteBatch::~SpriteBatch()
@@ -207,6 +193,28 @@ int SpriteBatch::getBufferSize() const
 	return size;
 	return size;
 }
 }
 
 
+void SpriteBatch::attachAttribute(const std::string &name, Mesh *mesh)
+{
+	AttachedAttribute oldattrib = {};
+	AttachedAttribute newattrib = {};
+
+	if (mesh->getVertexCount() < (size_t) getBufferSize() * 4)
+		throw love::Exception("Mesh has too few vertices to be attached to this SpriteBatch (at least %d vertices are required)", getBufferSize()*4);
+
+	auto it = attached_attributes.find(name);
+	if (it != attached_attributes.end())
+		oldattrib = it->second;
+
+	newattrib.index = mesh->getAttributeIndex(name);
+
+	if (newattrib.index < 0)
+		throw love::Exception("The specified mesh does not have a vertex attribute named '%s'", name.c_str());
+
+	newattrib.mesh = mesh;
+
+	attached_attributes[name] = newattrib;
+}
+
 void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 {
 	const size_t pos_offset   = offsetof(Vertex, x);
 	const size_t pos_offset   = offsetof(Vertex, x);
@@ -223,27 +231,47 @@ void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float
 
 
 	gl.bindTexture(*(GLuint *) texture->getHandle());
 	gl.bindTexture(*(GLuint *) texture->getHandle());
 
 
-	GLBuffer::Bind array_bind(*array_buf);
-	GLBuffer::Bind element_bind(*quad_indices.getBuffer());
-
-	// Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.)
-	array_buf->unmap();
-
 	uint32 enabledattribs = ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD;
 	uint32 enabledattribs = ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD;
 
 
-	// Apply per-sprite color, if a color is set.
-	if (color)
 	{
 	{
-		enabledattribs |= ATTRIBFLAG_COLOR;
-		glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), array_buf->getPointer(color_offset));
+		// Scope this bind so it doesn't interfere with the
+		// Mesh::bindAttributeToShaderInput calls below.
+		GLBuffer::Bind array_bind(*array_buf);
+
+		// Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.)
+		array_buf->unmap();
+
+		// Apply per-sprite color, if a color is set.
+		if (color)
+		{
+			enabledattribs |= ATTRIBFLAG_COLOR;
+			glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), array_buf->getPointer(color_offset));
+		}
+
+		glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), array_buf->getPointer(pos_offset));
+		glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), array_buf->getPointer(texel_offset));
 	}
 	}
 
 
-	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), array_buf->getPointer(pos_offset));
-	glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), array_buf->getPointer(texel_offset));
+	for (const auto &it : attached_attributes)
+	{
+		Mesh *mesh = it.second.mesh.get();
+
+		// We have to do this check here as well because setBufferSize can be
+		// called after attachAttribute.
+		if (mesh->getVertexCount() < (size_t) getBufferSize() * 4)
+			throw love::Exception("Mesh with attribute '%s' attached to this SpriteBatch has too few vertices", it.first.c_str());
+
+		int location = mesh->bindAttributeToShaderInput(it.second.index, it.first);
+
+		if (location >= 0)
+			enabledattribs |= 1u << (uint32) location;
+	}
 
 
 	gl.useVertexAttribArrays(enabledattribs);
 	gl.useVertexAttribArrays(enabledattribs);
 
 
 	gl.prepareDraw();
 	gl.prepareDraw();
+
+	GLBuffer::Bind element_bind(*quad_indices.getBuffer());
 	gl.drawElements(GL_TRIANGLES, (GLsizei) quad_indices.getIndexCount(next), quad_indices.getType(), quad_indices.getPointer(0));
 	gl.drawElements(GL_TRIANGLES, (GLsizei) quad_indices.getIndexCount(next), quad_indices.getType(), quad_indices.getPointer(0));
 }
 }
 
 

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

@@ -24,6 +24,9 @@
 // C
 // C
 #include <cstring>
 #include <cstring>
 
 
+// C++
+#include <unordered_map>
+
 // LOVE
 // LOVE
 #include "common/math.h"
 #include "common/math.h"
 #include "common/Matrix.h"
 #include "common/Matrix.h"
@@ -98,11 +101,23 @@ public:
 	 **/
 	 **/
 	int getBufferSize() const;
 	int getBufferSize() const;
 
 
+	/**
+	 * Attaches a specific vertex attribute from a Mesh to this SpriteBatch.
+	 * The vertex attribute will be used when drawing the SpriteBatch.
+	 **/
+	void attachAttribute(const std::string &name, Mesh *mesh);
+
 	// Implements Drawable.
 	// Implements Drawable.
 	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 
 
 private:
 private:
 
 
+	struct AttachedAttribute
+	{
+		StrongRef<Mesh> mesh;
+		int index;
+	};
+
 	void addv(const Vertex *v, const Matrix3 &m, int index);
 	void addv(const Vertex *v, const Matrix3 &m, int index);
 
 
 	/**
 	/**
@@ -129,6 +144,8 @@ private:
 	GLBuffer *array_buf;
 	GLBuffer *array_buf;
 	QuadIndices quad_indices;
 	QuadIndices quad_indices;
 
 
+	std::unordered_map<std::string, AttachedAttribute> attached_attributes;
+
 }; // SpriteBatch
 }; // SpriteBatch
 
 
 } // opengl
 } // opengl

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

@@ -30,10 +30,9 @@ namespace graphics
 namespace opengl
 namespace opengl
 {
 {
 
 
-Text::Text(Font *font, const std::string &text)
+Text::Text(Font *font, const std::vector<Font::ColoredString> &text)
 	: font(font)
 	: font(font)
 	, vbo(nullptr)
 	, vbo(nullptr)
-	, text_info()
 	, vert_offset(0)
 	, vert_offset(0)
 	, texture_cache_id((uint32) -1)
 	, texture_cache_id((uint32) -1)
 {
 {
@@ -82,7 +81,7 @@ void Text::uploadVertices(const std::vector<Font::GlyphVertex> &vertices, size_t
 		vbo = new_vbo;
 		vbo = new_vbo;
 	}
 	}
 
 
-	if (vbo != nullptr)
+	if (vbo != nullptr && datasize > 0)
 	{
 	{
 		GLBuffer::Bind bind(*vbo);
 		GLBuffer::Bind bind(*vbo);
 		vbodata = (uint8 *) vbo->map();
 		vbodata = (uint8 *) vbo->map();
@@ -113,11 +112,13 @@ void Text::addTextData(const TextData &t)
 	std::vector<Font::GlyphVertex> vertices;
 	std::vector<Font::GlyphVertex> vertices;
 	std::vector<Font::DrawCommand> new_commands;
 	std::vector<Font::DrawCommand> new_commands;
 
 
+	Font::TextInfo text_info;
+
 	// We only have formatted text if the align mode is valid.
 	// We only have formatted text if the align mode is valid.
 	if (t.align == Font::ALIGN_MAX_ENUM)
 	if (t.align == Font::ALIGN_MAX_ENUM)
-		new_commands = font->generateVertices(t.text, vertices, 0.0f, Vector(0.0f, 0.0f), &text_info);
+		new_commands = font->generateVertices(t.codepoints, vertices, 0.0f, Vector(0.0f, 0.0f), &text_info);
 	else
 	else
-		new_commands = font->generateVerticesFormatted(t.text, t.wrap, t.align, vertices, &text_info);
+		new_commands = font->generateVerticesFormatted(t.codepoints, t.wrap, t.align, vertices, &text_info);
 
 
 	if (t.use_matrix)
 	if (t.use_matrix)
 		t.matrix.transform(&vertices[0], &vertices[0], (int) vertices.size());
 		t.matrix.transform(&vertices[0], &vertices[0], (int) vertices.size());
@@ -158,27 +159,29 @@ void Text::addTextData(const TextData &t)
 	}
 	}
 
 
 	vert_offset = voffset + vertices.size();
 	vert_offset = voffset + vertices.size();
+
 	text_data.push_back(t);
 	text_data.push_back(t);
+	text_data.back().text_info = text_info;
 
 
 	// Font::generateVertices can invalidate the font's texture cache.
 	// Font::generateVertices can invalidate the font's texture cache.
 	if (font->getTextureCacheID() != texture_cache_id)
 	if (font->getTextureCacheID() != texture_cache_id)
 		regenerateVertices();
 		regenerateVertices();
 }
 }
 
 
-void Text::set(const std::string &text)
+void Text::set(const std::vector<Font::ColoredString> &text)
 {
 {
-	if (text.empty())
-		return set();
-
-	addTextData({text, -1.0f, Font::ALIGN_MAX_ENUM, false, false, Matrix3()});
+	return set(text, -1.0f, Font::ALIGN_MAX_ENUM);
 }
 }
 
 
-void Text::set(const std::string &text, float wrap, Font::AlignMode align)
+void Text::set(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align)
 {
 {
-	if (text.empty())
+	if (text.empty() || (text.size() == 1 && text[0].str.empty()))
 		return set();
 		return set();
 
 
-	addTextData({text, wrap, align, false, false, Matrix3()});
+	Font::ColoredCodepoints codepoints;
+	Font::getCodepointsFromString(text, codepoints);
+
+	addTextData({codepoints, wrap, align, {}, false, false, Matrix3()});
 }
 }
 
 
 void Text::set()
 void Text::set()
@@ -186,24 +189,21 @@ void Text::set()
 	clear();
 	clear();
 }
 }
 
 
-void Text::add(const std::string &text, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
+int Text::add(const std::vector<Font::ColoredString> &text, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 {
-	if (text.empty())
-		return;
-
-	Matrix3 m(x, y, angle, sx, sy, ox, oy, kx, ky);
-
-	addTextData({text, -1.0f, Font::ALIGN_MAX_ENUM, true, true, m});
+	return addf(text, -1.0f, Font::ALIGN_MAX_ENUM, x, y, angle, sx, sy, ox, oy, kx, ky);
 }
 }
 
 
-void Text::addf(const std::string &text, float wrap, Font::AlignMode align, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
+int Text::addf(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 {
-	if (text.empty())
-		return;
+	Font::ColoredCodepoints codepoints;
+	Font::getCodepointsFromString(text, codepoints);
 
 
 	Matrix3 m(x, y, angle, sx, sy, ox, oy, kx, ky);
 	Matrix3 m(x, y, angle, sx, sy, ox, oy, kx, ky);
 
 
-	addTextData({text, wrap, align, true, true, m});
+	addTextData({codepoints, wrap, align, {}, true, true, m});
+
+	return (int) text_data.size() - 1;
 }
 }
 
 
 void Text::clear()
 void Text::clear()
@@ -211,7 +211,6 @@ void Text::clear()
 	text_data.clear();
 	text_data.clear();
 	draw_commands.clear();
 	draw_commands.clear();
 	texture_cache_id = font->getTextureCacheID();
 	texture_cache_id = font->getTextureCacheID();
-	text_info = {};
 	vert_offset = 0;
 	vert_offset = 0;
 }
 }
 
 
@@ -226,12 +225,13 @@ void Text::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 	if (font->getTextureCacheID() != texture_cache_id)
 	if (font->getTextureCacheID() != texture_cache_id)
 		regenerateVertices();
 		regenerateVertices();
 
 
-	const size_t pos_offset = offsetof(Font::GlyphVertex, x);
-	const size_t tex_offset = offsetof(Font::GlyphVertex, s);
+	const size_t pos_offset   = offsetof(Font::GlyphVertex, x);
+	const size_t tex_offset   = offsetof(Font::GlyphVertex, s);
+	const size_t color_offset = offsetof(Font::GlyphVertex, color.r);
 	const size_t stride = sizeof(Font::GlyphVertex);
 	const size_t stride = sizeof(Font::GlyphVertex);
 
 
 	OpenGL::TempTransform transform(gl);
 	OpenGL::TempTransform transform(gl);
-	transform.get() *= Matrix4(ceilf(x), ceilf(y), angle, sx, sy, ox, oy, kx, ky);
+	transform.get() *= Matrix4(x, y, angle, sx, sy, ox, oy, kx, ky);
 
 
 	{
 	{
 		GLBuffer::Bind bind(*vbo);
 		GLBuffer::Bind bind(*vbo);
@@ -239,10 +239,11 @@ void Text::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 
 
 		// Font::drawVertices expects AttribPointer calls to be done already.
 		// Font::drawVertices expects AttribPointer calls to be done already.
 		glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, vbo->getPointer(pos_offset));
 		glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, stride, vbo->getPointer(pos_offset));
-		glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, stride, vbo->getPointer(tex_offset));
+		glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_UNSIGNED_SHORT, GL_TRUE, stride, vbo->getPointer(tex_offset));
+		glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, vbo->getPointer(color_offset));
 	}
 	}
 
 
-	gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD);
+	gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD | ATTRIBFLAG_COLOR);
 
 
 	font->drawVertices(draw_commands);
 	font->drawVertices(draw_commands);
 }
 }
@@ -262,14 +263,26 @@ Font *Text::getFont() const
 	return font.get();
 	return font.get();
 }
 }
 
 
-int Text::getWidth() const
+int Text::getWidth(int index) const
 {
 {
-	return text_info.width;
+	if (index < 0)
+		index = std::max((int) text_data.size() - 1, 0);
+
+	if (index >= (int) text_data.size())
+		return 0;
+
+	return text_data[index].text_info.width;
 }
 }
 
 
-int Text::getHeight() const
+int Text::getHeight(int index) const
 {
 {
-	return text_info.height;
+	if (index < 0)
+		index = std::max((int) text_data.size() - 1, 0);
+
+	if (index >= (int) text_data.size())
+		return 0;
+
+	return text_data[index].text_info.height;
 }
 }
 
 
 } // opengl
 } // opengl

+ 9 - 9
jni/love/src/modules/graphics/opengl/Text.h

@@ -38,15 +38,15 @@ class Text : public Drawable
 {
 {
 public:
 public:
 
 
-	Text(Font *font, const std::string &text = "");
+	Text(Font *font, const std::vector<Font::ColoredString> &text = {});
 	virtual ~Text();
 	virtual ~Text();
 
 
-	void set(const std::string &text);
-	void set(const std::string &text, float wrap, Font::AlignMode align);
+	void set(const std::vector<Font::ColoredString> &text);
+	void set(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align);
 	void set();
 	void set();
 
 
-	void add(const std::string &text, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
-	void addf(const std::string &text, float wrap, Font::AlignMode align, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
+	int add(const std::vector<Font::ColoredString> &text, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
+	int addf(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 	void clear();
 	void clear();
 
 
 	// Implements Drawable.
 	// Implements Drawable.
@@ -58,20 +58,21 @@ public:
 	/**
 	/**
 	 * Gets the width of the currently set text.
 	 * Gets the width of the currently set text.
 	 **/
 	 **/
-	int getWidth() const;
+	int getWidth(int index = 0) const;
 
 
 	/**
 	/**
 	 * Gets the height of the currently set text.
 	 * Gets the height of the currently set text.
 	 **/
 	 **/
-	int getHeight() const;
+	int getHeight(int index = 0) const;
 
 
 private:
 private:
 
 
 	struct TextData
 	struct TextData
 	{
 	{
-		std::string text;
+		Font::ColoredCodepoints codepoints;
 		float wrap;
 		float wrap;
 		Font::AlignMode align;
 		Font::AlignMode align;
+		Font::TextInfo text_info;
 		bool use_matrix;
 		bool use_matrix;
 		bool append_vertices;
 		bool append_vertices;
 		Matrix3 matrix;
 		Matrix3 matrix;
@@ -87,7 +88,6 @@ private:
 	std::vector<Font::DrawCommand> draw_commands;
 	std::vector<Font::DrawCommand> draw_commands;
 
 
 	std::vector<TextData> text_data;
 	std::vector<TextData> text_data;
-	Font::TextInfo text_info;
 
 
 	size_t vert_offset;
 	size_t vert_offset;
 
 

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

@@ -0,0 +1,212 @@
+/**
+ * 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.
+ **/
+
+#include "Video.h"
+
+// LOVE
+#include "Shader.h"
+
+namespace love
+{
+namespace graphics
+{
+namespace opengl
+{
+
+Video::Video(love::video::VideoStream *stream)
+	: stream(stream)
+	, filter(Texture::getDefaultFilter())
+{
+	filter.mipmap = Texture::FILTER_NONE;
+
+	stream->fillBackBuffer();
+
+	for (int i = 0; i < 4; i++)
+		vertices[i].r = vertices[i].g = vertices[i].b = vertices[i].a = 255;
+
+	// Vertices are ordered for use with triangle strips:
+	// 0----2
+	// |  / |
+	// | /  |
+	// 1----3
+	vertices[0].x = 0.0f;
+	vertices[0].y = 0.0f;
+	vertices[1].x = 0.0f;
+	vertices[1].y = (float) stream->getHeight();
+	vertices[2].x = (float) stream->getWidth();
+	vertices[2].y = 0.0f;
+	vertices[3].x = (float) stream->getWidth();
+	vertices[3].y = (float) stream->getHeight();
+
+	vertices[0].s = 0.0f;
+	vertices[0].t = 0.0f;
+	vertices[1].s = 0.0f;
+	vertices[1].t = 1.0f;
+	vertices[2].s = 1.0f;
+	vertices[2].t = 0.0f;
+	vertices[3].s = 1.0f;
+	vertices[3].t = 1.0f;
+
+	loadVolatile();
+}
+
+Video::~Video()
+{
+	unloadVolatile();
+}
+
+bool Video::loadVolatile()
+{
+	glGenTextures(3, &textures[0]);
+
+	// Create the textures using the initial frame data.
+	auto frame = (const love::video::VideoStream::Frame*) stream->getFrontBuffer();
+
+	gl.bindTexture(textures[0]);
+	gl.setTextureFilter(filter);
+
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, frame->yw, frame->yh,
+	             0, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->yplane);
+
+	gl.bindTexture(textures[1]);
+	gl.setTextureFilter(filter);
+
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, frame->cw, frame->ch,
+	             0, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->cbplane);
+
+	gl.bindTexture(textures[2]);
+	gl.setTextureFilter(filter);
+
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, frame->cw, frame->ch,
+	             0, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->crplane);
+
+	return true;
+}
+
+void Video::unloadVolatile()
+{
+	for (int i = 0; i < 3; i++)
+	{
+		gl.deleteTexture(textures[i]);
+		textures[i] = 0;
+	}
+}
+
+love::video::VideoStream *Video::getStream()
+{
+	return stream;
+}
+
+void Video::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
+{
+	update();
+
+	Shader *shader = Shader::current;
+	bool defaultShader = (shader == Shader::defaultShader);
+	if (defaultShader)
+	{
+		// If we're still using the default shader, substitute the video version
+		Shader::defaultVideoShader->attach();
+		shader = Shader::defaultVideoShader;
+	}
+
+	shader->setVideoTextures(textures[0], textures[1], textures[2]);
+
+	OpenGL::TempTransform transform(gl);
+	transform.get() *= Matrix4(x, y, angle, sx, sy, ox, oy, kx, ky);
+
+	gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD);
+
+	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), &vertices[0].x);
+	glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), &vertices[0].s);
+
+	gl.prepareDraw();
+	gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+	// If we were using the default shader, reattach it
+	if (defaultShader)
+		Shader::defaultShader->attach();
+}
+
+void Video::update()
+{
+	bool bufferschanged = stream->swapBuffers();
+	stream->fillBackBuffer();
+
+	if (bufferschanged)
+	{
+		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);
+
+		gl.bindTexture(textures[1]);
+		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frame->cw, frame->ch,
+		                GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->cbplane);
+
+		gl.bindTexture(textures[2]);
+		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frame->cw, frame->ch,
+		                GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->crplane);
+	}
+}
+
+love::audio::Source *Video::getSource()
+{
+	return source;
+}
+
+void Video::setSource(love::audio::Source *source)
+{
+	this->source = source;
+}
+
+int Video::getWidth() const
+{
+	return stream->getWidth();
+}
+
+int Video::getHeight() const
+{
+	return stream->getHeight();
+}
+
+void Video::setFilter(const Texture::Filter &f)
+{
+	if (!Texture::validateFilter(f, false))
+		throw love::Exception("Invalid texture filter.");
+
+	filter = f;
+
+	for (int i = 0; i < 3; i++)
+	{
+		gl.bindTexture(textures[i]);
+		gl.setTextureFilter(filter);
+	}
+}
+
+const Texture::Filter &Video::getFilter() const
+{
+	return filter;
+}
+
+} // opengl
+} // graphics
+} // love

+ 79 - 0
jni/love/src/modules/graphics/opengl/Video.h

@@ -0,0 +1,79 @@
+/**
+ * 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.
+ **/
+
+#pragma once
+
+// LOVE
+#include "common/math.h"
+#include "graphics/Drawable.h"
+#include "graphics/Volatile.h"
+#include "video/VideoStream.h"
+#include "audio/Source.h"
+
+#include "OpenGL.h"
+
+namespace love
+{
+namespace graphics
+{
+namespace opengl
+{
+
+class Video : public Drawable, public Volatile
+{
+public:
+
+	Video(love::video::VideoStream *stream);
+	~Video();
+
+	// Volatile
+	bool loadVolatile();
+	void unloadVolatile();
+
+	love::video::VideoStream *getStream();
+	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
+
+	love::audio::Source *getSource();
+	void setSource(love::audio::Source *source);
+
+	int getWidth() const;
+	int getHeight() const;
+
+	void setFilter(const Texture::Filter &f);
+	const Texture::Filter &getFilter() const;
+
+private:
+
+	void update();
+
+	StrongRef<love::video::VideoStream> stream;
+	StrongRef<love::audio::Source> source;
+
+	GLuint textures[3];
+
+	Vertex vertices[4];
+
+	Texture::Filter filter;
+
+}; // Video
+
+} // opengl
+} // graphics
+} // love

+ 2 - 11
jni/love/src/modules/graphics/opengl/wrap_Canvas.cpp

@@ -101,17 +101,8 @@ int w_Canvas_getMSAA(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
-static const luaL_Reg functions[] =
+static const luaL_Reg w_Canvas_functions[] =
 {
 {
-	// From wrap_Texture.
-	{ "getWidth", w_Texture_getWidth },
-	{ "getHeight", w_Texture_getHeight },
-	{ "getDimensions", w_Texture_getDimensions },
-	{ "setFilter", w_Texture_setFilter },
-	{ "getFilter", w_Texture_getFilter },
-	{ "setWrap", w_Texture_setWrap },
-	{ "getWrap", w_Texture_getWrap },
-
 	{ "renderTo", w_Canvas_renderTo },
 	{ "renderTo", w_Canvas_renderTo },
 	{ "newImageData", w_Canvas_newImageData },
 	{ "newImageData", w_Canvas_newImageData },
 	{ "getFormat", w_Canvas_getFormat },
 	{ "getFormat", w_Canvas_getFormat },
@@ -121,7 +112,7 @@ static const luaL_Reg functions[] =
 
 
 extern "C" int luaopen_canvas(lua_State *L)
 extern "C" int luaopen_canvas(lua_State *L)
 {
 {
-	return luax_register_type(L, GRAPHICS_CANVAS_ID, functions);
+	return luax_register_type(L, GRAPHICS_CANVAS_ID, "Canvas", w_Texture_functions, w_Canvas_functions, nullptr);
 }
 }
 
 
 } // opengl
 } // opengl

+ 0 - 4
jni/love/src/modules/graphics/opengl/wrap_Canvas.h

@@ -35,10 +35,6 @@ namespace opengl
 
 
 //see Canvas.h
 //see Canvas.h
 Canvas *luax_checkcanvas(lua_State *L, int idx);
 Canvas *luax_checkcanvas(lua_State *L, int idx);
-int w_Canvas_renderTo(lua_State *L);
-int w_Canvas_newImageData(lua_State *L);
-int w_Canvas_getFormat(lua_State *L);
-int w_Canvas_getMSAA(lua_State *L);
 extern "C" int luaopen_canvas(lua_State *L);
 extern "C" int luaopen_canvas(lua_State *L);
 
 
 } // opengl
 } // opengl

+ 2 - 2
jni/love/src/modules/graphics/opengl/wrap_Font.cpp

@@ -184,7 +184,7 @@ int w_Font_setFallbacks(lua_State *L)
 	return 0;
 	return 0;
 }
 }
 
 
-static const luaL_Reg functions[] =
+static const luaL_Reg w_Font_functions[] =
 {
 {
 	{ "getHeight", w_Font_getHeight },
 	{ "getHeight", w_Font_getHeight },
 	{ "getWidth", w_Font_getWidth },
 	{ "getWidth", w_Font_getWidth },
@@ -203,7 +203,7 @@ static const luaL_Reg functions[] =
 
 
 extern "C" int luaopen_font(lua_State *L)
 extern "C" int luaopen_font(lua_State *L)
 {
 {
-	return luax_register_type(L, GRAPHICS_FONT_ID, functions);
+	return luax_register_type(L, GRAPHICS_FONT_ID, "Font", w_Font_functions, nullptr);
 }
 }
 
 
 } // opengl
 } // opengl

+ 0 - 12
jni/love/src/modules/graphics/opengl/wrap_Font.h

@@ -33,18 +33,6 @@ namespace opengl
 {
 {
 
 
 Font *luax_checkfont(lua_State *L, int idx);
 Font *luax_checkfont(lua_State *L, int idx);
-int w_Font_getHeight(lua_State *L);
-int w_Font_getWidth(lua_State *L);
-int w_Font_getWrap(lua_State *L);
-int w_Font_setLineHeight(lua_State *L);
-int w_Font_getLineHeight(lua_State *L);
-int w_Font_setFilter(lua_State *L);
-int w_Font_getFilter(lua_State *L);
-int w_Font_getAscent(lua_State *L);
-int w_Font_getDescent(lua_State *L);
-int w_Font_getBaseline(lua_State *L);
-int w_Font_hasGlyphs(lua_State *L);
-int w_Font_setFallbacks(lua_State *L);
 extern "C" int luaopen_font(lua_State *L);
 extern "C" int luaopen_font(lua_State *L);
 
 
 } // opengl
 } // opengl

+ 72 - 15
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 "video/VideoStream.h"
 #include "image/wrap_Image.h"
 #include "image/wrap_Image.h"
 
 
 #include <cassert>
 #include <cassert>
@@ -184,6 +185,20 @@ int w_setScissor(lua_State *L)
 	return 0;
 	return 0;
 }
 }
 
 
+int w_intersectScissor(lua_State *L)
+{
+	int x = (int) luaL_checknumber(L, 1);
+	int y = (int) luaL_checknumber(L, 2);
+	int w = (int) luaL_checknumber(L, 3);
+	int h = (int) luaL_checknumber(L, 4);
+
+	if (w < 0 || h < 0)
+		return luaL_error(L, "Can't set scissor with negative width and/or height.");
+
+	instance()->intersectScissor(x, y, w, h);
+	return 0;
+}
+
 int w_getScissor(lua_State *L)
 int w_getScissor(lua_State *L)
 {
 {
 	int x, y, w, h;
 	int x, y, w, h;
@@ -354,13 +369,13 @@ int w_newImage(lua_State *L)
 int w_newQuad(lua_State *L)
 int w_newQuad(lua_State *L)
 {
 {
 	Quad::Viewport v;
 	Quad::Viewport v;
-	v.x = (float) luaL_checknumber(L, 1);
-	v.y = (float) luaL_checknumber(L, 2);
-	v.w = (float) luaL_checknumber(L, 3);
-	v.h = (float) luaL_checknumber(L, 4);
+	v.x = luaL_checknumber(L, 1);
+	v.y = luaL_checknumber(L, 2);
+	v.w = luaL_checknumber(L, 3);
+	v.h = luaL_checknumber(L, 4);
 
 
-	float sw = (float) luaL_checknumber(L, 5);
-	float sh = (float) luaL_checknumber(L, 6);
+	double sw = luaL_checknumber(L, 5);
+	double sh = luaL_checknumber(L, 6);
 
 
 	Quad *quad = instance()->newQuad(v, sw, sh);
 	Quad *quad = instance()->newQuad(v, sw, sh);
 	luax_pushtype(L, GRAPHICS_QUAD_ID, quad);
 	luax_pushtype(L, GRAPHICS_QUAD_ID, quad);
@@ -837,7 +852,9 @@ int w_newText(lua_State *L)
 		luax_catchexcept(L, [&](){ t = instance()->newText(font); });
 		luax_catchexcept(L, [&](){ t = instance()->newText(font); });
 	else
 	else
 	{
 	{
-		std::string text = luax_checkstring(L, 2);
+		std::vector<Font::ColoredString> text;
+		luax_checkcoloredstring(L, 2, text);
+
 		luax_catchexcept(L, [&](){ t = instance()->newText(font, text); });
 		luax_catchexcept(L, [&](){ t = instance()->newText(font, text); });
 	}
 	}
 
 
@@ -846,6 +863,20 @@ int w_newText(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
+int w_newVideo(lua_State *L)
+{
+	if (!luax_istype(L, 1, VIDEO_VIDEO_STREAM_ID))
+		luax_convobj(L, 1, "video", "newVideoStream");
+
+	auto stream = luax_checktype<love::video::VideoStream>(L, 1, VIDEO_VIDEO_STREAM_ID);
+	Video *video = nullptr;
+
+	luax_catchexcept(L, [&]() { video = instance()->newVideo(stream); });
+	luax_pushtype(L, GRAPHICS_VIDEO_ID, video);
+	video->release();
+	return 1;
+}
+
 int w_setColor(lua_State *L)
 int w_setColor(lua_State *L)
 {
 {
 	Colorf c;
 	Colorf c;
@@ -1258,25 +1289,37 @@ int w_setDefaultShaderCode(lua_State *L)
 	lua_getfield(L, 1, "opengl");
 	lua_getfield(L, 1, "opengl");
 	lua_rawgeti(L, -1, 1);
 	lua_rawgeti(L, -1, 1);
 	lua_rawgeti(L, -2, 2);
 	lua_rawgeti(L, -2, 2);
+	lua_rawgeti(L, -3, 3);
 
 
 	Shader::ShaderSource openglcode;
 	Shader::ShaderSource openglcode;
-	openglcode.vertex = luax_checkstring(L, -2);
-	openglcode.pixel = luax_checkstring(L, -1);
+	openglcode.vertex = luax_checkstring(L, -3);
+	openglcode.pixel = luax_checkstring(L, -2);
+
+	Shader::ShaderSource openglVideocode;
+	openglVideocode.vertex = luax_checkstring(L, -3);
+	openglVideocode.pixel = luax_checkstring(L, -1);
 
 
-	lua_pop(L, 3);
+	lua_pop(L, 4);
 
 
 	lua_getfield(L, 1, "opengles");
 	lua_getfield(L, 1, "opengles");
 	lua_rawgeti(L, -1, 1);
 	lua_rawgeti(L, -1, 1);
 	lua_rawgeti(L, -2, 2);
 	lua_rawgeti(L, -2, 2);
+	lua_rawgeti(L, -3, 3);
 
 
 	Shader::ShaderSource openglescode;
 	Shader::ShaderSource openglescode;
-	openglescode.vertex = luax_checkstring(L, -2);
-	openglescode.pixel = luax_checkstring(L, -1);
+	openglescode.vertex = luax_checkstring(L, -3);
+	openglescode.pixel = luax_checkstring(L, -2);
 
 
-	lua_pop(L, 3);
+	Shader::ShaderSource openglesVideocode;
+	openglesVideocode.vertex = luax_checkstring(L, -3);
+	openglesVideocode.pixel = luax_checkstring(L, -1);
+
+	lua_pop(L, 4);
 
 
 	Shader::defaultCode[Graphics::RENDERER_OPENGL]   = openglcode;
 	Shader::defaultCode[Graphics::RENDERER_OPENGL]   = openglcode;
 	Shader::defaultCode[Graphics::RENDERER_OPENGLES] = openglescode;
 	Shader::defaultCode[Graphics::RENDERER_OPENGLES] = openglescode;
+	Shader::defaultVideoCode[Graphics::RENDERER_OPENGL]   = openglVideocode;
+	Shader::defaultVideoCode[Graphics::RENDERER_OPENGLES] = openglesVideocode;
 
 
 	return 0;
 	return 0;
 }
 }
@@ -1452,7 +1495,9 @@ int w_draw(lua_State *L)
 
 
 int w_print(lua_State *L)
 int w_print(lua_State *L)
 {
 {
-	std::string str = luax_checkstring(L, 1);
+	std::vector<Font::ColoredString> str;
+	luax_checkcoloredstring(L, 1, str);
+
 	float x = (float)luaL_optnumber(L, 2, 0.0);
 	float x = (float)luaL_optnumber(L, 2, 0.0);
 	float y = (float)luaL_optnumber(L, 3, 0.0);
 	float y = (float)luaL_optnumber(L, 3, 0.0);
 	float angle = (float)luaL_optnumber(L, 4, 0.0f);
 	float angle = (float)luaL_optnumber(L, 4, 0.0f);
@@ -1471,7 +1516,9 @@ int w_print(lua_State *L)
 
 
 int w_printf(lua_State *L)
 int w_printf(lua_State *L)
 {
 {
-	std::string str = luax_checkstring(L, 1);
+	std::vector<Font::ColoredString> str;
+	luax_checkcoloredstring(L, 1, str);
+
 	float x = (float)luaL_checknumber(L, 2);
 	float x = (float)luaL_checknumber(L, 2);
 	float y = (float)luaL_checknumber(L, 3);
 	float y = (float)luaL_checknumber(L, 3);
 	float wrap = (float)luaL_checknumber(L, 4);
 	float wrap = (float)luaL_checknumber(L, 4);
@@ -1843,6 +1890,7 @@ static const luaL_Reg functions[] =
 	{ "newShader", w_newShader },
 	{ "newShader", w_newShader },
 	{ "newMesh", w_newMesh },
 	{ "newMesh", w_newMesh },
 	{ "newText", w_newText },
 	{ "newText", w_newText },
+	{ "_newVideo", w_newVideo },
 
 
 	{ "setColor", w_setColor },
 	{ "setColor", w_setColor },
 	{ "getColor", w_getColor },
 	{ "getColor", w_getColor },
@@ -1899,6 +1947,7 @@ static const luaL_Reg functions[] =
 	{ "getDimensions", w_getDimensions },
 	{ "getDimensions", w_getDimensions },
 
 
 	{ "setScissor", w_setScissor },
 	{ "setScissor", w_setScissor },
+	{ "intersectScissor", w_intersectScissor },
 	{ "getScissor", w_getScissor },
 	{ "getScissor", w_getScissor },
 
 
 	{ "stencil", w_stencil },
 	{ "stencil", w_stencil },
@@ -1925,9 +1974,16 @@ static const luaL_Reg functions[] =
 	{ 0, 0 }
 	{ 0, 0 }
 };
 };
 
 
+static int luaopen_drawable(lua_State *L)
+{
+	return luax_register_type(L, GRAPHICS_DRAWABLE_ID, "Drawable", nullptr);
+}
+
 // Types for this module.
 // Types for this module.
 static const lua_CFunction types[] =
 static const lua_CFunction types[] =
 {
 {
+	luaopen_drawable,
+	luaopen_texture,
 	luaopen_font,
 	luaopen_font,
 	luaopen_image,
 	luaopen_image,
 	luaopen_quad,
 	luaopen_quad,
@@ -1937,6 +1993,7 @@ static const lua_CFunction types[] =
 	luaopen_shader,
 	luaopen_shader,
 	luaopen_mesh,
 	luaopen_mesh,
 	luaopen_text,
 	luaopen_text,
+	luaopen_video,
 	0
 	0
 };
 };
 
 

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

@@ -32,6 +32,7 @@
 #include "wrap_Shader.h"
 #include "wrap_Shader.h"
 #include "wrap_Mesh.h"
 #include "wrap_Mesh.h"
 #include "wrap_Text.h"
 #include "wrap_Text.h"
+#include "wrap_Video.h"
 #include "Graphics.h"
 #include "Graphics.h"
 
 
 namespace love
 namespace love
@@ -41,85 +42,6 @@ namespace graphics
 namespace opengl
 namespace opengl
 {
 {
 
 
-int w_reset(lua_State *L);
-int w_clear(lua_State *L);
-int w_discard(lua_State *L);
-int w_present(lua_State *L);
-int w_isCreated(lua_State *L);
-int w_isActive(lua_State *L);
-int w_isGammaCorrect(lua_State *L);
-int w_getWidth(lua_State *L);
-int w_getHeight(lua_State *L);
-int w_getDimensions(lua_State *L);
-int w_setScissor(lua_State *L);
-int w_getScissor(lua_State *L);
-int w_stencil(lua_State *L);
-int w_setStencilTest(lua_State *L);
-int w_getStencilTest(lua_State *L);
-int w_newImage(lua_State *L);
-int w_newQuad(lua_State *L);
-int w_newFont(lua_State *L);
-int w_newImageFont(lua_State *L);
-int w_newSpriteBatch(lua_State *L);
-int w_newParticleSystem(lua_State *L);
-int w_newCanvas(lua_State *L);
-int w_newShader(lua_State *L);
-int w_newMesh(lua_State *L);
-int w_newText(lua_State *L);
-int w_setColor(lua_State *L);
-int w_getColor(lua_State *L);
-int w_setBackgroundColor(lua_State *L);
-int w_getBackgroundColor(lua_State *L);
-int w_setNewFont(lua_State *L);
-int w_setFont(lua_State *L);
-int w_getFont(lua_State *L);
-int w_setColorMask(lua_State *L);
-int w_getColorMask(lua_State *L);
-int w_setBlendMode(lua_State *L);
-int w_getBlendMode(lua_State *L);
-int w_setDefaultFilter(lua_State *L);
-int w_getDefaultFilter(lua_State *L);
-int w_setDefaultMipmapFilter(lua_State *L);
-int w_getDefaultMipmapFilter(lua_State *L);
-int w_setLineWidth(lua_State *L);
-int w_setLineStyle(lua_State *L);
-int w_setLineJoin(lua_State *L);
-int w_getLineWidth(lua_State *L);
-int w_getLineStyle(lua_State *L);
-int w_getLineJoin(lua_State *L);
-int w_setPointSize(lua_State *L);
-int w_getPointSize(lua_State *L);
-int w_setWireframe(lua_State *L);
-int w_isWireframe(lua_State *L);
-int w_newScreenshot(lua_State *L);
-int w_setCanvas(lua_State *L);
-int w_getCanvas(lua_State *L);
-int w_setShader(lua_State *L);
-int w_getShader(lua_State *L);
-int w_setDefaultShaderCode(lua_State *L);
-int w_getSupported(lua_State *L);
-int w_getCanvasFormats(lua_State *L);
-int w_getCompressedImageFormats(lua_State *L);
-int w_getRendererInfo(lua_State *L);
-int w_getSystemLimits(lua_State *L);
-int w_getStats(lua_State *L);
-int w_draw(lua_State *L);
-int w_print(lua_State *L);
-int w_printf(lua_State *L);
-int w_points(lua_State *L);
-int w_line(lua_State *L);
-int w_rectangle(lua_State *L);
-int w_circle(lua_State *L);
-int w_ellipse(lua_State *L);
-int w_arc(lua_State *L);
-int w_polygon(lua_State *L);
-int w_push(lua_State *L);
-int w_pop(lua_State *L);
-int w_rotate(lua_State *L);
-int w_scale(lua_State *L);
-int w_translate(lua_State *L);
-int w_shear(lua_State *L);
-int w_origin(lua_State *L);
 extern "C" LOVE_EXPORT int luaopen_love_graphics(lua_State *L);
 extern "C" LOVE_EXPORT int luaopen_love_graphics(lua_State *L);
 
 
 } // opengl
 } // opengl

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

@@ -159,12 +159,34 @@ precision mediump float;
 #endif
 #endif
 
 
 varying mediump vec4 VaryingTexCoord;
 varying mediump vec4 VaryingTexCoord;
-varying lowp vec4 VaryingColor;
+varying mediump vec4 VaryingColor;
 
 
 #define love_Canvases gl_FragData
 #define love_Canvases gl_FragData
 
 
 uniform sampler2D _tex0_;]],
 uniform sampler2D _tex0_;]],
 
 
+	FUNCTIONS = [[
+uniform sampler2D love_VideoYChannel;
+uniform sampler2D love_VideoCbChannel;
+uniform sampler2D love_VideoCrChannel;
+
+vec4 VideoTexel(vec2 texcoords)
+{
+	vec3 yuv;
+	yuv[0] = Texel(love_VideoYChannel, texcoords).r;
+	yuv[1] = Texel(love_VideoCbChannel, texcoords).r;
+	yuv[2] = Texel(love_VideoCrChannel, texcoords).r;
+	yuv += vec3(-0.0627451017, -0.501960814, -0.501960814);
+
+	vec4 color;
+	color.r = dot(yuv, vec3(1.164,  0.000,  1.596));
+	color.g = dot(yuv, vec3(1.164, -0.391, -0.813));
+	color.b = dot(yuv, vec3(1.164,  2.018,  0.000));
+	color.a = 1.0;
+
+	return gammaCorrectColor(color);
+}]],
+
 	FOOTER = [[
 	FOOTER = [[
 void main() {
 void main() {
 	// fix crashing issue in OSX when _tex0_ is unused within effect()
 	// fix crashing issue in OSX when _tex0_ is unused within effect()
@@ -209,6 +231,7 @@ local function createPixelCode(pixelcode, is_multicanvas, lang)
 		love.graphics.isGammaCorrect() and "#define LOVE_GAMMA_CORRECT 1" or "",
 		love.graphics.isGammaCorrect() and "#define LOVE_GAMMA_CORRECT 1" or "",
 		GLSL.PIXEL.HEADER, GLSL.UNIFORMS,
 		GLSL.PIXEL.HEADER, GLSL.UNIFORMS,
 		GLSL.FUNCTIONS,
 		GLSL.FUNCTIONS,
+		GLSL.PIXEL.FUNCTIONS,
 		lang == "glsles" and "#line 1" or "#line 0",
 		lang == "glsles" and "#line 1" or "#line 0",
 		pixelcode,
 		pixelcode,
 		is_multicanvas and GLSL.PIXEL.FOOTER_MULTI_CANVAS or GLSL.PIXEL.FOOTER,
 		is_multicanvas and GLSL.PIXEL.FOOTER_MULTI_CANVAS or GLSL.PIXEL.FOOTER,
@@ -307,23 +330,47 @@ vec4 position(mat4 transform_proj, vec4 vertpos) {
 	return transform_proj * vertpos;
 	return transform_proj * vertpos;
 }]],
 }]],
 	pixel = [[
 	pixel = [[
-vec4 effect(lowp vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord) {
+vec4 effect(mediump vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord) {
 	return Texel(tex, texcoord) * vcolor;
 	return Texel(tex, texcoord) * vcolor;
-}]]
+}]],
+	videopixel = [[
+vec4 effect(mediump vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord) {
+	return VideoTexel(texcoord) * vcolor;
+}]],
 }
 }
 
 
 local defaults = {
 local defaults = {
 	opengl = {
 	opengl = {
 		createVertexCode(defaultcode.vertex, "glsl"),
 		createVertexCode(defaultcode.vertex, "glsl"),
 		createPixelCode(defaultcode.pixel, false, "glsl"),
 		createPixelCode(defaultcode.pixel, false, "glsl"),
+		createPixelCode(defaultcode.videopixel, false, "glsl"),
 	},
 	},
 	opengles = {
 	opengles = {
 		createVertexCode(defaultcode.vertex, "glsles"),
 		createVertexCode(defaultcode.vertex, "glsles"),
 		createPixelCode(defaultcode.pixel, false, "glsles"),
 		createPixelCode(defaultcode.pixel, false, "glsles"),
+		createPixelCode(defaultcode.videopixel, false, "glsles"),
 	},
 	},
 }
 }
 
 
 love.graphics._setDefaultShaderCode(defaults)
 love.graphics._setDefaultShaderCode(defaults)
 
 
+function love.graphics.newVideo(file, loadaudio)
+	local video = love.graphics._newVideo(file)
+	local source, success
+
+	if loadaudio ~= false then
+		success, source = pcall(love.audio.newSource, video:getStream():getFilename())
+	end
+	if success then
+		video:setSource(source)
+	elseif loadaudio == true then
+		error("Video had no audio track", 2)
+	else
+		video:getStream():setSync(love.video.newRemote())
+	end
+
+	return video
+end
+
 -- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string.
 -- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string.
 --)luastring"--"
 --)luastring"--"

+ 2 - 11
jni/love/src/modules/graphics/opengl/wrap_Image.cpp

@@ -137,17 +137,8 @@ int w_Image_getFlags(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
-static const luaL_Reg functions[] =
+static const luaL_Reg w_Image_functions[] =
 {
 {
-	// From wrap_Texture.
-	{ "getWidth", w_Texture_getWidth },
-	{ "getHeight", w_Texture_getHeight },
-	{ "getDimensions", w_Texture_getDimensions },
-	{ "setFilter", w_Texture_setFilter },
-	{ "getFilter", w_Texture_getFilter },
-	{ "setWrap", w_Texture_setWrap },
-	{ "getWrap", w_Texture_getWrap },
-
 	{ "setMipmapFilter", w_Image_setMipmapFilter },
 	{ "setMipmapFilter", w_Image_setMipmapFilter },
 	{ "getMipmapFilter", w_Image_getMipmapFilter },
 	{ "getMipmapFilter", w_Image_getMipmapFilter },
 	{ "isCompressed", w_Image_isCompressed },
 	{ "isCompressed", w_Image_isCompressed },
@@ -159,7 +150,7 @@ static const luaL_Reg functions[] =
 
 
 extern "C" int luaopen_image(lua_State *L)
 extern "C" int luaopen_image(lua_State *L)
 {
 {
-	return luax_register_type(L, GRAPHICS_IMAGE_ID, functions);
+	return luax_register_type(L, GRAPHICS_IMAGE_ID, "Image", w_Texture_functions, w_Image_functions, nullptr);
 }
 }
 
 
 } // opengl
 } // opengl

+ 0 - 6
jni/love/src/modules/graphics/opengl/wrap_Image.h

@@ -34,12 +34,6 @@ namespace opengl
 {
 {
 
 
 Image *luax_checkimage(lua_State *L, int idx);
 Image *luax_checkimage(lua_State *L, int idx);
-int w_Image_setMipmapFilter(lua_State *L);
-int w_Image_getMipmapFilter(lua_State *L);
-int w_Image_isCompressed(lua_State *L);
-int w_Image_refresh(lua_State *L);
-int w_Image_getData(lua_State *L);
-int w_Image_getFlags(lua_State *L);
 extern "C" int luaopen_image(lua_State *L);
 extern "C" int luaopen_image(lua_State *L);
 
 
 } // opengl
 } // opengl

+ 29 - 7
jni/love/src/modules/graphics/opengl/wrap_Mesh.cpp

@@ -109,10 +109,15 @@ int w_Mesh_setVertices(lua_State *L)
 {
 {
 	Mesh *t = luax_checkmesh(L, 1);
 	Mesh *t = luax_checkmesh(L, 1);
 	luaL_checktype(L, 2, LUA_TTABLE);
 	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 nvertices = luax_objlen(L, 2);
 	size_t nvertices = luax_objlen(L, 2);
-	if (nvertices != t->getVertexCount())
-		return luaL_error(L, "Invalid number of vertices (expected %d, got %d)", (int) t->getVertexCount(), (int) nvertices);
+
+	if (vertoffset + nvertices > t->getVertexCount())
+		return luaL_error(L, "Too many vertices (expected at most %d, got %d)", (int) t->getVertexCount() - (int) vertoffset, (int) nvertices);
 
 
 	const std::vector<Mesh::AttribFormat> &vertexformat = t->getVertexFormat();
 	const std::vector<Mesh::AttribFormat> &vertexformat = t->getVertexFormat();
 
 
@@ -120,7 +125,10 @@ int w_Mesh_setVertices(lua_State *L)
 	for (const Mesh::AttribFormat &format : vertexformat)
 	for (const Mesh::AttribFormat &format : vertexformat)
 		ncomponents += format.components;
 		ncomponents += format.components;
 
 
-	char *data = (char *) t->mapVertexData();
+	size_t stride = t->getVertexStride();
+	size_t byteoffset = vertoffset * stride;
+
+	char *data = (char *) t->mapVertexData() + byteoffset;
 
 
 	for (size_t i = 0; i < nvertices; i++)
 	for (size_t i = 0; i < nvertices; i++)
 	{
 	{
@@ -145,7 +153,7 @@ int w_Mesh_setVertices(lua_State *L)
 		lua_pop(L, ncomponents + 1);
 		lua_pop(L, ncomponents + 1);
 	}
 	}
 
 
-	t->unmapVertexData();
+	t->unmapVertexData(byteoffset, nvertices * stride);
 	return 0;
 	return 0;
 }
 }
 
 
@@ -331,6 +339,13 @@ int w_Mesh_setVertexMap(lua_State *L)
 {
 {
 	Mesh *t = luax_checkmesh(L, 1);
 	Mesh *t = luax_checkmesh(L, 1);
 
 
+	if (lua_isnoneornil(L, 2))
+	{
+		// Disable the vertex map / index buffer.
+		luax_catchexcept(L, [&](){ t->setVertexMap(); });
+		return 0;
+	}
+
 	bool is_table = lua_istable(L, 2);
 	bool is_table = lua_istable(L, 2);
 	int nargs = is_table ? (int) luax_objlen(L, 2) : lua_gettop(L) - 1;
 	int nargs = is_table ? (int) luax_objlen(L, 2) : lua_gettop(L) - 1;
 
 
@@ -361,7 +376,14 @@ int w_Mesh_getVertexMap(lua_State *L)
 	Mesh *t = luax_checkmesh(L, 1);
 	Mesh *t = luax_checkmesh(L, 1);
 
 
 	std::vector<uint32> vertex_map;
 	std::vector<uint32> vertex_map;
-	luax_catchexcept(L, [&](){ t->getVertexMap(vertex_map); });
+	bool has_vertex_map = false;
+	luax_catchexcept(L, [&](){ has_vertex_map = t->getVertexMap(vertex_map); });
+
+	if (!has_vertex_map)
+	{
+		lua_pushnil(L);
+		return 1;
+	}
 
 
 	int element_count = (int) vertex_map.size();
 	int element_count = (int) vertex_map.size();
 
 
@@ -468,7 +490,7 @@ int w_Mesh_getDrawRange(lua_State *L)
 	return 2;
 	return 2;
 }
 }
 
 
-static const luaL_Reg functions[] =
+static const luaL_Reg w_Mesh_functions[] =
 {
 {
 	{ "setVertices", w_Mesh_setVertices },
 	{ "setVertices", w_Mesh_setVertices },
 	{ "setVertex", w_Mesh_setVertex },
 	{ "setVertex", w_Mesh_setVertex },
@@ -494,7 +516,7 @@ static const luaL_Reg functions[] =
 
 
 extern "C" int luaopen_mesh(lua_State *L)
 extern "C" int luaopen_mesh(lua_State *L)
 {
 {
-	return luax_register_type(L, GRAPHICS_MESH_ID, functions);
+	return luax_register_type(L, GRAPHICS_MESH_ID, "Mesh", w_Mesh_functions, nullptr);
 }
 }
 
 
 } // opengl
 } // opengl

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

@@ -36,25 +36,6 @@ char *luax_writeAttributeData(lua_State *L, int startidx, Mesh::DataType type, i
 const char *luax_readAttributeData(lua_State *L, Mesh::DataType type, int components, const char *data);
 const char *luax_readAttributeData(lua_State *L, Mesh::DataType type, int components, const char *data);
 
 
 Mesh *luax_checkmesh(lua_State *L, int idx);
 Mesh *luax_checkmesh(lua_State *L, int idx);
-int w_Mesh_setVertices(lua_State *L);
-int w_Mesh_setVertex(lua_State *L);
-int w_Mesh_getVertex(lua_State *L);
-int w_Mesh_setVertexAttribute(lua_State *L);
-int w_Mesh_getVertexAttribute(lua_State *L);
-int w_Mesh_getVertexCount(lua_State *L);
-int w_Mesh_getVertexFormat(lua_State *L);
-int w_Mesh_setAttributeEnabled(lua_State *L);
-int w_Mesh_isAttributeEnabled(lua_State *L);
-int w_Mesh_attachAttribute(lua_State *L);
-int w_Mesh_flush(lua_State *L);
-int w_Mesh_setVertexMap(lua_State *L);
-int w_Mesh_getVertexMap(lua_State *L);
-int w_Mesh_setTexture(lua_State *L);
-int w_Mesh_getTexture(lua_State *L);
-int w_Mesh_setDrawMode(lua_State *L);
-int w_Mesh_getDrawMode(lua_State *L);
-int w_Mesh_setDrawRange(lua_State *L);
-int w_Mesh_getDrawRange(lua_State *L);
 extern "C" int luaopen_mesh(lua_State *L);
 extern "C" int luaopen_mesh(lua_State *L);
 
 
 } // opengl
 } // opengl

+ 2 - 2
jni/love/src/modules/graphics/opengl/wrap_ParticleSystem.cpp

@@ -705,7 +705,7 @@ int w_ParticleSystem_update(lua_State *L)
 	return 0;
 	return 0;
 }
 }
 
 
-static const luaL_Reg functions[] =
+static const luaL_Reg w_ParticleSystem_functions[] =
 {
 {
 	{ "clone", w_ParticleSystem_clone },
 	{ "clone", w_ParticleSystem_clone },
 	{ "setTexture", w_ParticleSystem_setTexture },
 	{ "setTexture", w_ParticleSystem_setTexture },
@@ -772,7 +772,7 @@ static const luaL_Reg functions[] =
 
 
 extern "C" int luaopen_particlesystem(lua_State *L)
 extern "C" int luaopen_particlesystem(lua_State *L)
 {
 {
-	return luax_register_type(L, GRAPHICS_PARTICLE_SYSTEM_ID, functions);
+	return luax_register_type(L, GRAPHICS_PARTICLE_SYSTEM_ID, "ParticleSystem", w_ParticleSystem_functions, nullptr);
 }
 }
 
 
 } // opengl
 } // opengl

+ 0 - 60
jni/love/src/modules/graphics/opengl/wrap_ParticleSystem.h

@@ -33,66 +33,6 @@ namespace opengl
 {
 {
 
 
 ParticleSystem *luax_checkparticlesystem(lua_State *L, int idx);
 ParticleSystem *luax_checkparticlesystem(lua_State *L, int idx);
-int w_ParticleSystem_clone(lua_State *L);
-int w_ParticleSystem_setTexture(lua_State *L);
-int w_ParticleSystem_getTexture(lua_State *L);
-int w_ParticleSystem_setBufferSize(lua_State *L);
-int w_ParticleSystem_getBufferSize(lua_State *L);
-int w_ParticleSystem_setInsertMode(lua_State *L);
-int w_ParticleSystem_getInsertMode(lua_State *L);
-int w_ParticleSystem_setEmissionRate(lua_State *L);
-int w_ParticleSystem_getEmissionRate(lua_State *L);
-int w_ParticleSystem_setEmitterLifetime(lua_State *L);
-int w_ParticleSystem_getEmitterLifetime(lua_State *L);
-int w_ParticleSystem_setParticleLifetime(lua_State *L);
-int w_ParticleSystem_getParticleLifetime(lua_State *L);
-int w_ParticleSystem_setPosition(lua_State *L);
-int w_ParticleSystem_getPosition(lua_State *L);
-int w_ParticleSystem_moveTo(lua_State *L);
-int w_ParticleSystem_setAreaSpread(lua_State *L);
-int w_ParticleSystem_getAreaSpread(lua_State *L);
-int w_ParticleSystem_setDirection(lua_State *L);
-int w_ParticleSystem_getDirection(lua_State *L);
-int w_ParticleSystem_setSpread(lua_State *L);
-int w_ParticleSystem_getSpread(lua_State *L);
-int w_ParticleSystem_setSpeed(lua_State *L);
-int w_ParticleSystem_getSpeed(lua_State *L);
-int w_ParticleSystem_setLinearAcceleration(lua_State *L);
-int w_ParticleSystem_getLinearAcceleration(lua_State *L);
-int w_ParticleSystem_setRadialAcceleration(lua_State *L);
-int w_ParticleSystem_getRadialAcceleration(lua_State *L);
-int w_ParticleSystem_setTangentialAcceleration(lua_State *L);
-int w_ParticleSystem_getTangentialAcceleration(lua_State *L);
-int w_ParticleSystem_setLinearDamping(lua_State *L);
-int w_ParticleSystem_getLinearDamping(lua_State *L);
-int w_ParticleSystem_setSizes(lua_State *L);
-int w_ParticleSystem_getSizes(lua_State *L);
-int w_ParticleSystem_setSizeVariation(lua_State *L);
-int w_ParticleSystem_getSizeVariation(lua_State *L);
-int w_ParticleSystem_setRotation(lua_State *L);
-int w_ParticleSystem_getRotation(lua_State *L);
-int w_ParticleSystem_setSpin(lua_State *L);
-int w_ParticleSystem_getSpin(lua_State *L);
-int w_ParticleSystem_setSpinVariation(lua_State *L);
-int w_ParticleSystem_getSpinVariation(lua_State *L);
-int w_ParticleSystem_setColors(lua_State *L);
-int w_ParticleSystem_getColors(lua_State *L);
-int w_ParticleSystem_setQuads(lua_State *L);
-int w_ParticleSystem_getQuads(lua_State *L);
-int w_ParticleSystem_setOffset(lua_State *L);
-int w_ParticleSystem_getOffset(lua_State *L);
-int w_ParticleSystem_setRelativeRotation(lua_State *L);
-int w_ParticleSystem_hasRelativeRotation(lua_State *L);
-int w_ParticleSystem_getCount(lua_State *L);
-int w_ParticleSystem_start(lua_State *L);
-int w_ParticleSystem_stop(lua_State *L);
-int w_ParticleSystem_pause(lua_State *L);
-int w_ParticleSystem_reset(lua_State *L);
-int w_ParticleSystem_emit(lua_State *L);
-int w_ParticleSystem_isActive(lua_State *L);
-int w_ParticleSystem_isPaused(lua_State *L);
-int w_ParticleSystem_isStopped(lua_State *L);
-int w_ParticleSystem_update(lua_State *L);
 extern "C" int luaopen_particlesystem(lua_State *L);
 extern "C" int luaopen_particlesystem(lua_State *L);
 
 
 } // opengl
 } // opengl

+ 60 - 56
jni/love/src/modules/graphics/opengl/wrap_Shader.cpp

@@ -25,6 +25,7 @@
 #include <string>
 #include <string>
 #include <iostream>
 #include <iostream>
 #include <algorithm>
 #include <algorithm>
+#include <cmath>
 
 
 namespace love
 namespace love
 {
 {
@@ -229,38 +230,86 @@ int w_Shader_sendMatrix(lua_State *L)
 	if (!lua_istable(L, 3))
 	if (!lua_istable(L, 3))
 		return luax_typerror(L, 3, "matrix table");
 		return luax_typerror(L, 3, "matrix table");
 
 
-	lua_getfield(L, 3, "dimension");
-	int dimension = (int) lua_tointeger(L, -1);
+	int dimension = 0;
+
+	lua_rawgeti(L, 3, 1);
+	if (lua_istable(L, -1))
+		dimension = (int) luax_objlen(L, 3);
 	lua_pop(L, 1);
 	lua_pop(L, 1);
 
 
+	if (dimension == 0)
+	{
+		lua_getfield(L, 3, "dimension");
+
+		if (!lua_isnoneornil(L, -1))
+			dimension = (int) lua_tointeger(L, -1);
+		else
+			dimension = (int) sqrtf((float) luax_objlen(L, 3));
+
+		lua_pop(L, 1);
+	}
+
 	if (dimension < 2 || dimension > 4)
 	if (dimension < 2 || dimension > 4)
 		return luaL_error(L, "Invalid matrix size: %dx%d (only 2x2, 3x3 and 4x4 matrices are supported).",
 		return luaL_error(L, "Invalid matrix size: %dx%d (only 2x2, 3x3 and 4x4 matrices are supported).",
 						  dimension, dimension);
 						  dimension, dimension);
 
 
 	float *values = new float[dimension * dimension * count];
 	float *values = new float[dimension * dimension * count];
+
 	for (int i = 0; i < count; ++i)
 	for (int i = 0; i < count; ++i)
 	{
 	{
-		lua_getfield(L, 3+i, "dimension");
-		if (lua_tointeger(L, -1) != dimension)
+		int other_dimension = 0;
+
+		lua_rawgeti(L, 3+i, 1);
+		bool table_of_tables = lua_istable(L, -1);
+
+		if (table_of_tables)
+			other_dimension = luax_objlen(L, -1);
+
+		lua_pop(L, 1);
+
+		if (!table_of_tables)
+			other_dimension = (int) sqrtf((float) luax_objlen(L, 3+i));
+
+		if (other_dimension != dimension)
 		{
 		{
 			// You unlock this door with the key of imagination. Beyond it is
 			// You unlock this door with the key of imagination. Beyond it is
 			// another dimension: a dimension of sound, a dimension of sight,
 			// another dimension: a dimension of sound, a dimension of sight,
 			// a dimension of mind. You're moving into a land of both shadow
 			// a dimension of mind. You're moving into a land of both shadow
 			// and substance, of things and ideas. You've just crossed over
 			// and substance, of things and ideas. You've just crossed over
 			// into... the Twilight Zone.
 			// into... the Twilight Zone.
-			int other_dimension = (int) lua_tointeger(L, -1);
 			delete[] values;
 			delete[] values;
 			return luaL_error(L, "Invalid matrix size at argument %d: Expected size %dx%d, got %dx%d.",
 			return luaL_error(L, "Invalid matrix size at argument %d: Expected size %dx%d, got %dx%d.",
 							  3+i, dimension, dimension, other_dimension, other_dimension);
 							  3+i, dimension, dimension, other_dimension, other_dimension);
 		}
 		}
 
 
-		for (int k = 1; k <= dimension*dimension; ++k)
+		if (table_of_tables)
 		{
 		{
-			lua_rawgeti(L, 3+i, k);
-			values[i * dimension * dimension + k - 1] = (float)lua_tonumber(L, -1);
+			int n = 0;
+
+			for (int j = 1; j <= dimension; j++)
+			{
+				lua_rawgeti(L, 3+i, j);
+
+				for (int k = 1; k <= dimension; k++)
+				{
+					lua_rawgeti(L, -k, k);
+					values[i * dimension * dimension + n] = (float) lua_tonumber(L, -1);
+					n++;
+				}
+
+				lua_pop(L, dimension + 1);
+			}
 		}
 		}
+		else
+		{
+			for (int k = 1; k <= dimension*dimension; k++)
+			{
+				lua_rawgeti(L, 3+i, k);
+				values[i * dimension * dimension + k - 1] = (float) lua_tonumber(L, -1);
+			}
 
 
-		lua_pop(L, 1 + dimension);
+			lua_pop(L, dimension*dimension);
+		}
 	}
 	}
 
 
 	luax_catchexcept(L,
 	luax_catchexcept(L,
@@ -281,48 +330,6 @@ int w_Shader_sendTexture(lua_State *L)
 	return 0;
 	return 0;
 }
 }
 
 
-// Convert matrices on the stack for use with sendMatrix.
-static void w_convertMatrices(lua_State *L, int idx)
-{
-	int matrixcount = lua_gettop(L) - (idx - 1);
-
-	for (int matrix = idx; matrix < idx + matrixcount; matrix++)
-	{
-		luaL_checktype(L, matrix, LUA_TTABLE);
-		int dimension = (int) luax_objlen(L, matrix);
-
-		int newi = 1;
-		lua_createtable(L, dimension * dimension, 0);
-
-		// Collapse {{a,b,c}, {d,e,f}, ...} to {a,b,c, d,e,f, ...}
-		for (int i = 1; i <= (int) luax_objlen(L, matrix); i++)
-		{
-			// Push args[matrix][i] onto the stack.
-			lua_rawgeti(L, matrix, i);
-			luaL_checktype(L, -1, LUA_TTABLE);
-
-			for (int j = 1; j <= (int) luax_objlen(L, -1); j++)
-			{
-				// Push args[matrix[i][j] onto the stack.
-				lua_rawgeti(L, -1, j);
-				luaL_checktype(L, -1, LUA_TNUMBER);
-
-				// newtable[newi] = args[matrix][i][j]
-				lua_rawseti(L, -3, newi++);
-			}
-
-			lua_pop(L, 1);
-		}
-
-		// newtable.dimension = #args[matrix]
-		lua_pushinteger(L, dimension);
-		lua_setfield(L, -2, "dimension");
-
-		// Replace args[i] with the new table
-		lua_replace(L, matrix);
-	}
-}
-
 int w_Shader_send(lua_State *L)
 int w_Shader_send(lua_State *L)
 {
 {
 	int ttype = lua_type(L, 3);
 	int ttype = lua_type(L, 3);
@@ -352,10 +359,7 @@ int w_Shader_send(lua_State *L)
 		if (ttype == LUA_TNUMBER || ttype == LUA_TBOOLEAN)
 		if (ttype == LUA_TNUMBER || ttype == LUA_TBOOLEAN)
 			return w_Shader_sendFloat(L);
 			return w_Shader_sendFloat(L);
 		else if (ttype == LUA_TTABLE)
 		else if (ttype == LUA_TTABLE)
-		{
-			w_convertMatrices(L, 3);
 			return w_Shader_sendMatrix(L);
 			return w_Shader_sendMatrix(L);
-		}
 
 
 		break;
 		break;
 	default:
 	default:
@@ -397,7 +401,7 @@ int w_Shader_getExternVariable(lua_State *L)
 	return 3;
 	return 3;
 }
 }
 
 
-static const luaL_Reg functions[] =
+static const luaL_Reg w_Shader_functions[] =
 {
 {
 	{ "getWarnings", w_Shader_getWarnings },
 	{ "getWarnings", w_Shader_getWarnings },
 	{ "sendInt",     w_Shader_sendInt },
 	{ "sendInt",     w_Shader_sendInt },
@@ -413,7 +417,7 @@ static const luaL_Reg functions[] =
 
 
 extern "C" int luaopen_shader(lua_State *L)
 extern "C" int luaopen_shader(lua_State *L)
 {
 {
-	return luax_register_type(L, GRAPHICS_SHADER_ID, functions);
+	return luax_register_type(L, GRAPHICS_SHADER_ID, "Shader", w_Shader_functions, nullptr);
 }
 }
 
 
 } // opengl
 } // opengl

+ 0 - 8
jni/love/src/modules/graphics/opengl/wrap_Shader.h

@@ -33,14 +33,6 @@ namespace opengl
 {
 {
 
 
 Shader *luax_checkshader(lua_State *L, int idx);
 Shader *luax_checkshader(lua_State *L, int idx);
-int w_Shader_getWarnings(lua_State *L);
-int w_Shader_sendInt(lua_State *L);
-int w_Shader_sendFloat(lua_State *L);
-int w_Shader_sendColor(lua_State *L);
-int w_Shader_sendMatrix(lua_State *L);
-int w_Shader_sendTexture(lua_State *L);
-int w_Shader_send(lua_State *L);
-int w_Shader_getExternVariable(lua_State *L);
 extern "C" int luaopen_shader(lua_State *L);
 extern "C" int luaopen_shader(lua_State *L);
 
 
 } // opengl
 } // opengl

+ 13 - 2
jni/love/src/modules/graphics/opengl/wrap_SpriteBatch.cpp

@@ -203,7 +203,17 @@ int w_SpriteBatch_getBufferSize(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
-static const luaL_Reg functions[] =
+int w_SpriteBatch_attachAttribute(lua_State *L)
+{
+	SpriteBatch *t = luax_checkspritebatch(L, 1);
+	const char *name = luaL_checkstring(L, 2);
+	Mesh *m = luax_checktype<Mesh>(L, 3, GRAPHICS_MESH_ID);
+
+	luax_catchexcept(L, [&](){ t->attachAttribute(name, m); });
+	return 0;
+}
+
+static const luaL_Reg w_SpriteBatch_functions[] =
 {
 {
 	{ "add", w_SpriteBatch_add },
 	{ "add", w_SpriteBatch_add },
 	{ "set", w_SpriteBatch_set },
 	{ "set", w_SpriteBatch_set },
@@ -216,12 +226,13 @@ static const luaL_Reg functions[] =
 	{ "getCount", w_SpriteBatch_getCount },
 	{ "getCount", w_SpriteBatch_getCount },
 	{ "setBufferSize", w_SpriteBatch_setBufferSize },
 	{ "setBufferSize", w_SpriteBatch_setBufferSize },
 	{ "getBufferSize", w_SpriteBatch_getBufferSize },
 	{ "getBufferSize", w_SpriteBatch_getBufferSize },
+	{ "attachAttribute", w_SpriteBatch_attachAttribute },
 	{ 0, 0 }
 	{ 0, 0 }
 };
 };
 
 
 extern "C" int luaopen_spritebatch(lua_State *L)
 extern "C" int luaopen_spritebatch(lua_State *L)
 {
 {
-	return luax_register_type(L, GRAPHICS_SPRITE_BATCH_ID, functions);
+	return luax_register_type(L, GRAPHICS_SPRITE_BATCH_ID, "SpriteBatch", w_SpriteBatch_functions, nullptr);
 }
 }
 
 
 } // opengl
 } // opengl

Some files were not shown because too many files changed in this diff