Browse Source

Updated LÖVE source to revision cff5bc8604bd

Martin Felis 10 years ago
parent
commit
66685a2ade
100 changed files with 3728 additions and 2867 deletions
  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_TOUCH,
 		M_WINDOW,
+		M_VIDEO,
 		M_MAX_ENUM
 	};
 

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

@@ -117,6 +117,16 @@ public:
 		return object;
 	}
 
+	operator bool() const
+	{
+		return object != nullptr;
+	}
+
+	operator T*() const
+	{
+		return object;
+	}
+
 	void set(T *obj)
 	{
 		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
 {
 
-extern StringMap<Type, TYPE_MAX_ENUM> types;
-
 static love::Type extractudatatype(lua_State *L, int idx)
 {
 	Type t = INVALID_ID;
@@ -36,7 +34,7 @@ static love::Type extractudatatype(lua_State *L, int idx)
 	lua_pushvalue(L, idx);
 	int result = lua_pcall(L, 1, 1, 0);
 	if (result == 0)
-		types.find(lua_tostring(L, -1), t);
+		getTypeName(lua_tostring(L, -1), t);
 	if (result == 0 || result == LUA_ERRRUN)
 		lua_pop(L, 1);
 	return t;
@@ -165,7 +163,7 @@ Variant *Variant::fromLua(lua_State *L, int n, bool allowTables)
 		if (allowTables)
 		{
 			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);
 			while (lua_next(L, n))
 			{
@@ -230,7 +228,7 @@ void Variant::toLua(lua_State *L)
 		// I can do (at the moment).
 		break;
 	case TABLE:
-		lua_newtable(L);
+		lua_createtable(L, 0, (int) data.table->size());
 		for (size_t i = 0; i < data.table->size(); ++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;
 		} string;
 		void *userdata;
-		std::vector<std::pair<Variant*, Variant*> > *table;
+		std::vector<std::pair<Variant*, Variant*>> *table;
 	} data;
 
 private:

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

@@ -141,6 +141,8 @@
 #	define LOVE_ENABLE_TOUCH
 #	define LOVE_ENABLE_TOUCH_SDL
 #	define LOVE_ENABLE_UTF8
+#	define LOVE_ENABLE_VIDEO
+#	define LOVE_ENABLE_VIDEO_THEORA
 #	define LOVE_ENABLE_WINDOW
 #	define LOVE_ENABLE_WINDOW_SDL
 #	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)
 {
+	love::addTypeName(m.type, m.name);
+
 	// Put a reference to the C++ module in Lua.
 	luax_insistregistry(L, REGISTRY_MODULES);
 
@@ -270,12 +272,9 @@ int luax_preload(lua_State *L, lua_CFunction f, const char *name)
 	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.
 	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
 		lua_pop(L, 1);
 
-	luaL_newmetatable(L, tname);
+	luaL_newmetatable(L, name);
 
 	// m.__index = m
 	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");
 
 	// Add tostring function.
-	lua_pushstring(L, tname);
+	lua_pushstring(L, name);
 	lua_pushcclosure(L, w__tostring, 1);
 	lua_setfield(L, -2, "__tostring");
 
 	// Add type
-	lua_pushstring(L, tname);
+	lua_pushstring(L, name);
 	lua_pushcclosure(L, w__type, 1);
 	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_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);
-
-	if (pushmetatable)
-		return 1; // leave the metatable on the stack.
+	va_end(fs);
 
 	lua_pop(L, 1); // Pops metatable.
 	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)
 {
 	if (tindex < 0)
@@ -403,7 +411,7 @@ void luax_rawnewtype(lua_State *L, love::Type type, love::Object *object)
 	u->type = type;
 
 	const char *name = "Invalid";
-	getType(type, name);
+	getTypeName(type, name);
 
 	luaL_newmetatable(L, name);
 	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
 			// describe its type properly, so we only use it for love types.
 			love::Type t;
-			if (!love::getType(argtname, t))
+			if (!love::getTypeName(argtname, t))
 				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 t = INVALID_ID;
-	getType(luaL_checkstring(L, idx), t);
+	getTypeName(luaL_checkstring(L, idx), 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.
  * @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
@@ -434,7 +439,7 @@ T *luax_checktype(lua_State *L, int idx, love::Type type)
 	if (lua_type(L, idx) != LUA_TUSERDATA)
 	{
 		const char *name = "Invalid";
-		getType(type, name);
+		getTypeName(type, 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])
 	{
 		const char *name = "Invalid";
-		getType(type, name);
+		getTypeName(type, name);
 		luax_typerror(L, idx, name);
 	}
 
@@ -454,7 +459,7 @@ template <typename T>
 T *luax_getmodule(lua_State *L, love::Type type)
 {
 	const char *name = "Invalid";
-	getType(type, name);
+	getTypeName(type, name);
 
 	luax_insistregistry(L, REGISTRY_MODULES);
 	lua_getfield(L, -1, name);
@@ -476,7 +481,7 @@ template <typename T>
 T *luax_optmodule(lua_State *L, love::Type type)
 {
 	const char *name = "Invalid";
-	getType(type, name);
+	getTypeName(type, name);
 
 	luax_insistregistry(L, REGISTRY_MODULES);
 	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[DATA_ID] = (one << DATA_ID) | b[OBJECT_ID];
 	b[MODULE_ID] = (one << MODULE_ID) | b[OBJECT_ID];
+	b[STREAM_ID] = (one << STREAM_ID) | b[OBJECT_ID];
 
 	// Filesystem.
 	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_MESH_ID] = (one << GRAPHICS_MESH_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.
 	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_CHANNEL_ID] = (one << THREAD_CHANNEL_ID) | b[OBJECT_ID];
 
+	// Video
+	b[VIDEO_VIDEO_STREAM_ID] = (one << VIDEO_VIDEO_STREAM_ID) | b[STREAM_ID];
+
 	// Modules.
 	b[MODULE_FILESYSTEM_ID] = (one << MODULE_FILESYSTEM_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();
 
-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);
 }
 
-bool getType(love::Type in, const char *&out)
+bool getTypeName(love::Type in, const char *&out)
 {
 	return types.find(in, out);
 }

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

@@ -34,6 +34,7 @@ enum Type
 	OBJECT_ID,
 	DATA_ID,
 	MODULE_ID,
+	STREAM_ID,
 
 	// Filesystem.
 	FILESYSTEM_FILE_ID,
@@ -56,6 +57,7 @@ enum Type
 	GRAPHICS_SHADER_ID,
 	GRAPHICS_MESH_ID,
 	GRAPHICS_TEXT_ID,
+	GRAPHICS_VIDEO_ID,
 
 	// Image
 	IMAGE_IMAGE_DATA_ID,
@@ -106,6 +108,9 @@ enum Type
 	THREAD_THREAD_ID,
 	THREAD_CHANNEL_ID,
 
+	// Video
+	VIDEO_VIDEO_STREAM_ID,
+
 	// The modules themselves. Only add abstracted modules here.
 	MODULE_FILESYSTEM_ID,
 	MODULE_GRAPHICS_ID,
@@ -123,8 +128,9 @@ typedef std::bitset<TYPE_MAX_ENUM> TypeBits;
  **/
 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
 

+ 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 char *VERSION = LOVE_VERSION_STRING;
 static const char *VERSION_COMPATIBILITY[] =  { VERSION, 0 };
-static const char *VERSION_CODENAME = "";
+static const char *VERSION_CODENAME = "Super Toast";
 
 } // 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)
 {
-	luax_register_type(L, DATA_ID, w_Data_functions);
+	luax_register_type(L, DATA_ID, "Data", w_Data_functions, nullptr);
 	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);
-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);
+extern const luaL_Reg w_Data_functions[];
 
 } // love
 

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

@@ -22,6 +22,11 @@
 #include <string.h>
 
 b2ChainShape::~b2ChainShape()
+{
+	Clear();
+}
+
+void b2ChainShape::Clear()
 {
 	b2Free(m_vertices);
 	m_vertices = NULL;
@@ -56,10 +61,8 @@ void b2ChainShape::CreateChain(const b2Vec2* vertices, int32 count)
 	b2Assert(count >= 2);
 	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.
-		b2Assert(b2DistanceSquared(v1, v2) > b2_linearSlop * b2_linearSlop);
+		b2Assert(b2DistanceSquared(vertices[i-1], vertices[i]) > b2_linearSlop * b2_linearSlop);
 	}
 
 	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.
 	~b2ChainShape();
 
+	/// Clear all data.
+	void Clear();
+
 	/// Create a loop. This automatically adjusts connectivity.
 	/// @param vertices an array of vertices, these are copied
 	/// @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;
 		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;
 				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;
 
 	// Copy vertices.

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

@@ -25,9 +25,9 @@
 struct 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

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

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

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

@@ -23,7 +23,7 @@
 
 #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.
 void* b2Alloc(int32 size)

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

@@ -26,6 +26,8 @@
 
 #define B2_DEBUG_SOLVER 0
 
+bool g_blockSolve = true;
+
 struct b2ContactPositionConstraint
 {
 	b2Vec2 localPoints[b2_maxManifoldPoints];
@@ -213,7 +215,7 @@ void b2ContactSolver::InitializeVelocityConstraints()
 		}
 
 		// 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* vcp2 = vc->points + 1;
@@ -341,29 +343,32 @@ void b2ContactSolver::SolveVelocityConstraints()
 		}
 
 		// 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
 		{

+ 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_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 k12 = iA * m_s1 + iB * m_s2;
 		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;
 		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
 	{
 		K.GetSymInverse33(&m_mass);
@@ -271,7 +277,17 @@ bool b2WeldJoint::SolvePositionConstraints(const b2SolverData& data)
 
 		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);
 
 		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
-/// 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.
 class b2WheelJoint : public b2Joint
 {

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

@@ -37,7 +37,7 @@
 b2World::b2World(const b2Vec2& gravity)
 {
 	m_destructionListener = NULL;
-	m_debugDraw = NULL;
+	g_debugDraw = NULL;
 
 	m_bodyList = NULL;
 	m_jointList = NULL;
@@ -101,7 +101,7 @@ void b2World::SetContactListener(b2ContactListener* listener)
 
 void b2World::SetDebugDraw(b2Draw* debugDraw)
 {
-	m_debugDraw = debugDraw;
+	g_debugDraw = debugDraw;
 }
 
 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;
 			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;
 
@@ -1049,7 +1049,7 @@ void b2World::DrawShape(b2Fixture* fixture, const b2Transform& xf, const b2Color
 			b2EdgeShape* edge = (b2EdgeShape*)fixture->GetShape();
 			b2Vec2 v1 = b2Mul(xf, edge->m_vertex1);
 			b2Vec2 v2 = b2Mul(xf, edge->m_vertex2);
-			m_debugDraw->DrawSegment(v1, v2, color);
+			g_debugDraw->DrawSegment(v1, v2, color);
 		}
 		break;
 
@@ -1063,8 +1063,8 @@ void b2World::DrawShape(b2Fixture* fixture, const b2Transform& xf, const b2Color
 			for (int32 i = 1; i < count; ++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;
 			}
 		}
@@ -1082,7 +1082,7 @@ void b2World::DrawShape(b2Fixture* fixture, const b2Transform& xf, const b2Color
 				vertices[i] = b2Mul(xf, poly->m_vertices[i]);
 			}
 
-			m_debugDraw->DrawSolidPolygon(vertices, vertexCount, color);
+			g_debugDraw->DrawSolidPolygon(vertices, vertexCount, color);
 		}
 		break;
             
@@ -1107,7 +1107,7 @@ void b2World::DrawJoint(b2Joint* joint)
 	switch (joint->GetType())
 	{
 	case e_distanceJoint:
-		m_debugDraw->DrawSegment(p1, p2, color);
+		g_debugDraw->DrawSegment(p1, p2, color);
 		break;
 
 	case e_pulleyJoint:
@@ -1115,9 +1115,9 @@ void b2World::DrawJoint(b2Joint* joint)
 			b2PulleyJoint* pulley = (b2PulleyJoint*)joint;
 			b2Vec2 s1 = pulley->GetGroundAnchorA();
 			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;
 
@@ -1126,20 +1126,20 @@ void b2World::DrawJoint(b2Joint* joint)
 		break;
 
 	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()
 {
-	if (m_debugDraw == NULL)
+	if (g_debugDraw == NULL)
 	{
 		return;
 	}
 
-	uint32 flags = m_debugDraw->GetFlags();
+	uint32 flags = g_debugDraw->GetFlags();
 
 	if (flags & b2Draw::e_shapeBit)
 	{
@@ -1191,7 +1191,7 @@ void b2World::DrawDebugData()
 			//b2Vec2 cA = fixtureA->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[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();
 			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;
 
 	b2DestructionListener* m_destructionListener;
-	b2Draw* m_debugDraw;
+	b2Draw* g_debugDraw;
 
 	// This is used to compute the time step ratio to
 	// 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
 
    Do this:
@@ -25,13 +25,16 @@
 
       TGA (not sure what subset, if a subset)
       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)
       HDR (radiance rgbE format)
       PIC (Softimage PIC)
       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 arbitrary I/O callbacks
       - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON)
@@ -143,6 +146,10 @@
 
 
    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.04  (2015-04-15) try to re-enable SIMD on MinGW 64-bit
       2.03  (2015-04-12) additional corruption checking
@@ -175,38 +182,41 @@
     Tom Seddon (pic)                             the Horde3D community
     Thatcher Ulrich (psd)                        Janez Zemva
     Ken Miller (pgm, ppm)                        Jonathan Blow
-                                                 Laurent Gomila
+    urraka@github (animated gif)                 Laurent Gomila
                                                  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
                                                  Guillaume George
-  If your name should be here but                Jerry Jansson
-  isn't, let Sean know.                          Hayaki Saito
+                                                 Jerry Jansson
+                                                 Hayaki Saito
                                                  Johan Duparc
                                                  Ronny Chevalier
-                                                 Michal Cichon
-                                                 Tero Hanninen
-                                                 Sergio Gonzalez
+ Optimizations & bugfixes                        Michal Cichon
+    Fabian "ryg" Giesen                          Tero Hanninen
+    Arseny Kapoulkine                            Sergio Gonzalez
                                                  Cass Everitt
                                                  Engin Manap
-                                                 Martins Mozeiko
-                                                 Joseph Thomson
+  If your name should be here but                Martins Mozeiko
+  isn't, let Sean know.                          Joseph Thomson
                                                  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 *img_buffer, *img_buffer_end;
-   stbi_uc *img_buffer_original;
+   stbi_uc *img_buffer_original, *img_buffer_original_end;
 } 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->read_from_callbacks = 0;
    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
@@ -772,6 +782,7 @@ static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *
    s->read_from_callbacks = 1;
    s->img_buffer_original = s->buffer_start;
    stbi__refill_buffer(s);
+   s->img_buffer_original_end = s->img_buffer_end;
 }
 
 #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
    // 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_end = s->img_buffer_original_end;
 }
 
 #ifndef STBI_NO_JPEG
@@ -900,8 +912,8 @@ static void *stbi__malloc(size_t size)
    #define stbi__err(x,y)  stbi__err(x)
 #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)
 {
@@ -988,6 +1000,7 @@ static unsigned char *stbi__load_flip(stbi__context *s, int *x, int *y, int *com
    return result;
 }
 
+#ifndef STBI_NO_HDR
 static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp)
 {
    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
 
@@ -1152,6 +1165,7 @@ STBIDEF int      stbi_is_hdr_from_file(FILE *f)
    stbi__start_file(&s,f);
    return stbi__hdr_test(&s);
    #else
+   STBI_NOTUSED(f);
    return 0;
    #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);
    return stbi__hdr_test(&s);
    #else
+   STBI_NOTUSED(clbk);
+   STBI_NOTUSED(user);
    return 0;
    #endif
 }
@@ -1278,23 +1294,29 @@ static int stbi__get16be(stbi__context *s)
    return (z << 8) + stbi__get8(s);
 }
 
+#if !defined(STBI_NO_PNG) || !defined(STBI_NO_PSD)
 static stbi__uint32 stbi__get32be(stbi__context *s)
 {
    stbi__uint32 z = 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)
 {
    int z = stbi__get8(s);
    return z + (stbi__get8(s) << 8);
 }
+#endif
 
+#ifndef STBI_NO_BMP
 static stbi__uint32 stbi__get32le(stbi__context *s)
 {
    stbi__uint32 z = stbi__get16le(s);
    return z + (stbi__get16le(s) << 16);
 }
+#endif
 
 #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) {
          for(--i; i >= 0; --i) {
             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");
       }
@@ -3500,10 +3522,10 @@ static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num)
          z->size [c] = (stbi_uc     ) s;
          z->value[c] = (stbi__uint16) i;
          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];
@@ -3542,7 +3564,7 @@ static void stbi__fill_bits(stbi__zbuf *z)
 {
    do {
       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;
    } 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 (img_n != out_n) {
+            int q;
             // insert alpha = 255
-            stbi_uc *cur = a->out + stride*j;
-            int i;
+            cur = a->out + stride*j;
             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 {
                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)
 {
    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];
    int psize=0,i,j,compress=0,width;
    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;
                   mb = 0xffu <<  0;
                   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 {
                   mr = 31u << 10;
                   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);
                z += 3;
                a = (easy == 2 ? stbi__get8(s) : 255);
+               all_a |= a;
                if (target == 4) out[z++] = a;
             }
          } 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 & mb, bshift, bcount));
                a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255);
+               all_a |= a;
                if (target == 4) out[z++] = STBI__BYTECAST(a);
             }
          }
          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) {
       stbi_uc t;
       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) {
       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);
       }
    } else  {
@@ -5053,6 +5082,7 @@ static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int
    int   pixelCount;
    int channelCount, compression;
    int channel, i, count, len;
+   int bitdepth;
    int w,h;
    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);
 
    // 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.
    // 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;
          if (channel >= channelCount) {
             // 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 {
             // Read the RLE data.
             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;
          if (channel > channelCount) {
             // 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 {
             // 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 (comp) *comp = channelCount;
+   if (comp) *comp = 4;
    *y = h;
    *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
                      stbi_uc value[4];
-                     int i;
 
                      if (count==128)
                         count = stbi__get16be(s);
@@ -5435,8 +5472,8 @@ typedef struct
 typedef struct
 {
    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 lpal[256][4];
    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)
 {
    stbi_uc lzw_cs;
-   stbi__int32 len, code;
+   stbi__int32 len, init_code;
    stbi__uint32 first;
    stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear;
    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;
    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
@@ -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];
-   // @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)
 {
    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 (;;) {
       switch (stbi__get8(s)) {
          case 0x2C: /* Image Descriptor */
          {
+            int prev_trans = -1;
             stbi__int32 x, y, w, h;
             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);
                g->color_table = (stbi_uc *) g->lpal;
             } 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->color_table = (stbi_uc *) g->pal;
             } else
                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);
             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;
          }
 
@@ -5725,7 +5777,7 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i
                len = stbi__get8(s);
                if (len == 4) {
                   g->eflags = stbi__get8(s);
-                  stbi__get16le(s); // delay
+                  g->delay = stbi__get16le(s);
                   g->transparent = stbi__get8(s);
                } else {
                   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) {
       *x = g.w;
       *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;
 }
@@ -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;
    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);
    *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)) {
-       stbi__rewind( s );
-       return 0;
+      stbi__rewind( s );
+      return 0;
    }
 
    stbi__skip(s, 8);
@@ -6292,6 +6356,12 @@ STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int
 
 /*
    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.04  (2015-04-15) try to re-enable SIMD on MinGW 64-bit
       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
 #include "common/macosx.h"
+#include <unistd.h>
 #endif // LOVE_MACOSX
 
 #ifdef LOVE_IOS
@@ -110,12 +111,16 @@ static void get_app_arguments(int argc, char **argv, int &new_argc, char **&new_
 	}
 
 #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())
-	{
 		temp_argv.insert(temp_argv.begin() + 1, dropfilestr);
-	}
 	else
 #endif
 	{

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

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

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

@@ -54,6 +54,7 @@ public:
 	virtual float getVolume() const;
 	virtual void seek(float offset, Unit unit);
 	virtual float tell(Unit unit);
+	virtual double getDuration(Unit unit);
 	virtual void setPosition(float *v);
 	virtual void getPosition(float *v) const;
 	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);
 }
 
+double Pool::getDuration(Source *source, void *unit)
+{
+	thread::Lock lock(mutex);
+	return source->getDurationAtomic(unit);
+}
+
 ALuint Pool::findi(const Source *source) const
 {
 	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 seek(Source *source, float offset, void *unit);
 	float tell(Source *source, void *unit);
+	double getDuration(Source *source, void *unit);
 
 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)
+	: size(size)
 {
 	alGenBuffers(1, &buffer);
 	alBufferData(buffer, format, data, size, freq);
@@ -95,6 +96,7 @@ Source::Source(Pool *pool, love::sound::SoundData *soundData)
 	, offsetSeconds(0)
 	, sampleRate(soundData->getSampleRate())
 	, channels(soundData->getChannels())
+	, bitDepth(soundData->getBitDepth())
 	, decoder(nullptr)
 	, toLoop(0)
 {
@@ -134,6 +136,7 @@ Source::Source(Pool *pool, love::sound::Decoder *decoder)
 	, offsetSeconds(0)
 	, sampleRate(decoder->getSampleRate())
 	, channels(decoder->getChannels())
+	, bitDepth(decoder->getBitDepth())
 	, decoder(decoder)
 	, toLoop(0)
 {
@@ -169,6 +172,7 @@ Source::Source(const Source &s)
 	, offsetSeconds(0)
 	, sampleRate(s.sampleRate)
 	, channels(s.channels)
+	, bitDepth(s.bitDepth)
 	, decoder(nullptr)
 	, toLoop(0)
 {
@@ -308,8 +312,11 @@ bool Source::update()
 			offsetSamples += (curOffsetSamples - newOffsetSamples);
 			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;
@@ -438,6 +445,36 @@ float Source::tell(Source::Unit 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)
 {
 	if (channels > 1)
@@ -575,8 +612,11 @@ bool Source::playAtomic()
 
 		for (unsigned int i = 0; i < MAX_BUFFERS; i++)
 		{
-			streamAtomic(streamBuffers[i], decoder.get());
+			if (streamAtomic(streamBuffers[i], decoder.get()) == 0)
+				break;
+
 			++usedBuffers;
+
 			if (decoder->isFinished())
 				break;
 		}
@@ -736,13 +776,18 @@ ALenum Source::getFormat(int channels, int bitDepth) const
 int Source::streamAtomic(ALuint buffer, love::sound::Decoder *d)
 {
 	// 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())
 	{

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

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

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

@@ -32,29 +32,6 @@ namespace love
 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);
 
 } // audio

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

@@ -140,6 +140,19 @@ int w_Source_tell(lua_State *L)
 	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)
 {
 	Source *t = luax_checksource(L, 1);
@@ -357,7 +370,7 @@ int w_Source_getType(lua_State *L)
 	return 1;
 }
 
-static const luaL_Reg functions[] =
+static const luaL_Reg w_Source_functions[] =
 {
 	{ "clone", w_Source_clone },
 
@@ -373,6 +386,7 @@ static const luaL_Reg functions[] =
 	{ "getVolume", w_Source_getVolume },
 	{ "seek", w_Source_seek },
 	{ "tell", w_Source_tell },
+	{ "getDuration", w_Source_getDuration },
 	{ "setPosition", w_Source_setPosition },
 	{ "getPosition", w_Source_getPosition },
 	{ "setVelocity", w_Source_setVelocity },
@@ -406,7 +420,7 @@ static const luaL_Reg functions[] =
 
 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

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

@@ -30,41 +30,6 @@ namespace audio
 {
 
 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);
 
 } // 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_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));
 		msg = new Message("focus", vargs);
 		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.type = MODULE_ID;
 	w.functions = functions;
-	w.types = 0;
+	w.types = nullptr;
 
 	return luax_register_module(L, w);
 }

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

@@ -30,13 +30,6 @@ namespace love
 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);
 
 } // 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))
 		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)
@@ -223,7 +219,7 @@ bool Filesystem::setSource(const char *source)
 	std::string new_search_path = source;
 
 #ifdef LOVE_ANDROID
-	if (!love::android::createStorageDirectories ())
+	if (!love::android::createStorageDirectories())
 		SDL_Log("Error creating storage directories!");
 
 	char* game_archive_ptr = NULL;
@@ -664,6 +660,17 @@ int64 Filesystem::getSize(const char *filename) const
 
 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);
 }
 

+ 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);
 }
 
-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)
 {
-	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

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

@@ -413,7 +413,7 @@ int w_File_getExtension(lua_State *L)
 	return 1;
 }
 
-static const luaL_Reg functions[] =
+const luaL_Reg w_File_functions[] =
 {
 	{ "getSize", w_File_getSize },
 	{ "open", w_File_open },
@@ -436,7 +436,7 @@ static const luaL_Reg functions[] =
 
 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

+ 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, ...);
 
 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(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 const luaL_Reg w_File_functions[];
+
 } // filesystem
 } // 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[] =
 {
-	// Data
-	{ "getString", w_Data_getString },
-	{ "getPointer", w_Data_getPointer },
-	{ "getSize", w_Data_getSize },
-
 	{ "getFilename", w_FileData_getFilename },
 	{ "getExtension", w_FileData_getExtension },
 
@@ -61,7 +56,7 @@ static const luaL_Reg w_FileData_functions[] =
 
 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

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

@@ -31,8 +31,6 @@ namespace filesystem
 {
 
 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);
 
 } // filesystem

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

@@ -155,19 +155,28 @@ int w_newFile(lua_State *L)
 	return 1;
 }
 
-FileData *luax_getfiledata(lua_State *L, int idx)
+File *luax_getfile(lua_State *L, int idx)
 {
-	FileData *data = nullptr;
 	File *file = nullptr;
-
 	if (lua_isstring(L, idx))
 	{
 		const char *filename = luaL_checkstring(L, idx);
 		file = instance()->newFile(filename);
 	}
-	else if (luax_istype(L, idx, FILESYSTEM_FILE_ID))
-	{
+	else
 		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();
 	}
 	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
 #include "common/runtime.h"
+#include "File.h"
 #include "FileData.h"
 
 namespace love
@@ -38,45 +39,9 @@ namespace filesystem
  * May trigger a Lua error.
  **/
 FileData *luax_getfiledata(lua_State *L, int idx);
+File *luax_getfile(lua_State *L, int idx);
 
 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 extloader(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.
 	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.
 				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)
 	{
-		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) + 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;
 }
 
-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 },
 	{ "getHeight", w_GlyphData_getHeight },
 	{ "getDimensions", w_GlyphData_getDimensions },
@@ -136,7 +131,7 @@ static const luaL_Reg functions[] =
 
 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

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

@@ -33,15 +33,6 @@ namespace font
 {
 
 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);
 
 } // font

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

@@ -124,7 +124,7 @@ int w_Rasterizer_hasGlyphs(lua_State *L)
 	return 1;
 }
 
-static const luaL_Reg functions[] =
+const luaL_Reg w_Rasterizer_functions[] =
 {
 	{ "getHeight", w_Rasterizer_getHeight },
 	{ "getAdvance", w_Rasterizer_getAdvance },
@@ -139,7 +139,7 @@ static const luaL_Reg functions[] =
 
 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

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

@@ -31,14 +31,6 @@ namespace font
 {
 
 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);
 
 } // font

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

@@ -44,11 +44,26 @@ struct ColorT
 		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*=(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>
 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[] =
 {
-	{ "multicanvas", SUPPORT_MULTI_CANVAS },
 	{ "multicanvasformats", SUPPORT_MULTI_CANVAS_FORMATS },
+	{ "clampzero", SUPPORT_CLAMP_ZERO },
 };
 
 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
 	{
-		SUPPORT_MULTI_CANVAS,
 		SUPPORT_MULTI_CANVAS_FORMATS,
+		SUPPORT_CLAMP_ZERO,
 		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();
 
 	// 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
 {
 
-Quad::Quad(const Quad::Viewport &v, float sw, float sh)
+Quad::Quad(const Quad::Viewport &v, double sw, double sh)
 	: sw(sw)
 	, 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;
 
@@ -53,20 +53,20 @@ void Quad::refresh(const Quad::Viewport &v, float sw, float sh)
 	vertices[0].x = 0.0f;
 	vertices[0].y = 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[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)

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

@@ -36,14 +36,14 @@ public:
 
 	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();
 
-	void refresh(const Viewport &v, float sw, float sh);
+	void refresh(const Viewport &v, double sw, double sh);
 	void setViewport(const Viewport &v);
 	Viewport getViewport() const;
 
@@ -54,8 +54,8 @@ private:
 	Vertex vertices[4];
 
 	Viewport viewport;
-	float sw;
-	float sh;
+	double sw;
+	double sh;
 
 }; // 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[] =
 {
-	{ "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::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));

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

@@ -43,6 +43,7 @@ public:
 	enum WrapMode
 	{
 		WRAP_CLAMP,
+		WRAP_CLAMP_ZERO,
 		WRAP_REPEAT,
 		WRAP_MIRRORED_REPEAT,
 		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;
 	}
 
+	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.setTextureWrap(wrap);
 
@@ -407,9 +415,6 @@ void Canvas::startGrab(const std::vector<Canvas *> &canvases)
 
 	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())
 			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;
 }
 
-bool Canvas::isMultiCanvasSupported()
-{
-	// system must support at least 4 simultaneous active canvases.
-	return gl.getMaxRenderTargets() >= 4;
-}
-
 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};

+ 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_RGBA8,    // RGBA with 8 bits per channel.
 		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_MAX_ENUM
 	};
@@ -118,7 +118,6 @@ public:
 	}
 
 	static bool isSupported();
-	static bool isMultiCanvasSupported();
 	static bool isMultiFormatMultiCanvasSupported();
 	static bool isFormatSupported(Format format);
 

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

@@ -38,6 +38,11 @@ namespace graphics
 namespace opengl
 {
 
+static inline uint16 normToUint16(double n)
+{
+	return (uint16) (n * LOVE_UINT16_MAX);
+}
+
 int Font::fontCount = 0;
 
 Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter)
@@ -272,18 +277,20 @@ const Font::Glyph &Font::addGlyph(uint32 glyph)
 
 		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
 		// |  / |
 		// | /  |
 		// 1----3
 		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.
@@ -341,12 +348,57 @@ float Font::getKerning(uint32 leftglyph, uint32 rightglyph)
 	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
 {
 	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.
 	float dx = offset.x;
@@ -356,96 +408,104 @@ std::vector<Font::DrawCommand> Font::generateVertices(const std::string &text, s
 	int maxwidth = 0;
 
 	// 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.
 	size_t vertstartsize = vertices.size();
-	vertices.reserve(vertstartsize + text.length() * 4);
+	vertices.reserve(vertstartsize + codepoints.cps.size() * 4);
 
 	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)
 		maxwidth = (int) dx;
@@ -455,43 +515,44 @@ std::vector<Font::DrawCommand> Font::generateVertices(const std::string &text, s
 		info->width = maxwidth - offset.x;;
 		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;
 
 	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<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 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];
 		love::Vector offset(0.0f, floorf(y));
+		float extraspacing = 0.0f;
 
-		if (width > maxwidth)
-			maxwidth = width;
+		maxwidth = std::max(width, maxwidth);
 
 		switch (align)
 		{
@@ -502,22 +563,24 @@ std::vector<Font::DrawCommand> Font::generateVerticesFormatted(const std::string
 			offset.x = floorf((wrap - width) / 2.0f);
 			break;
 		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
 				extraspacing = 0.0f;
 			break;
+		}
 		case ALIGN_LEFT:
 		default:
 			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
 			// 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.
-			drawcommands.insert(drawcommands.end(), firstcmd, commands.end());
+			drawcommands.insert(drawcommands.end(), firstcmd, newcommands.end());
 		}
 
 		y += getHeight() * getLineHeight();
-		i++;
 	}
 
 	if (info != nullptr)
@@ -597,30 +659,37 @@ void Font::printv(const Matrix4 &t, const std::vector<DrawCommand> &drawcommands
 	OpenGL::TempTransform transform(gl);
 	transform.get() *= t;
 
-	gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD);
-
 	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);
 }
 
-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<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);
 }
 
-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<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);
 }
@@ -636,15 +705,16 @@ int Font::getWidth(const std::string &str)
 	while (getline(iss, line, '\n'))
 	{
 		int width = 0;
+		uint32 prevglyph = 0;
 		try
 		{
-			uint32 prevglyph = 0;
-
 			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());
+
 			while (i != end)
 			{
 				uint32 c = *i++;
+
 				const Glyph &g = findGlyph(c);
 				width += g.spacing + getKerning(prevglyph, c);
 
@@ -668,69 +738,180 @@ int Font::getWidth(char character)
 	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)
-			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[] =
 {
-	{ "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));

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

@@ -50,6 +50,8 @@ class Font : public Object, public Volatile
 {
 public:
 
+	typedef std::vector<uint32> Codepoints;
+
 	enum AlignMode
 	{
 		ALIGN_LEFT,
@@ -59,10 +61,29 @@ public:
 		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
 	{
-		float x, y;
-		float s, t;
+		float  x, y;
+		uint16 s, t;
+		Color  color;
 	};
 
 	struct TextInfo
@@ -77,27 +98,22 @@ public:
 		GLuint texture;
 		int startvertex;
 		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());
 
 	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> 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);
 
+	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.
 	 *
@@ -112,9 +128,9 @@ public:
 	 * @param kx Shear along the x 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.
@@ -140,13 +156,12 @@ public:
 	 * and optionally the number of lines
 	 *
 	 * @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 wrapped_lines Optional output indicating which lines were
-	 *        auto-wrapped. Indices correspond to indices of the returned value.
 	 * 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,

+ 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)
 {
+	if (size == 0)
+		return;
+
 	// Upload the mapped data to the buffer.
 	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
 	// 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);
 
 	size_t new_range_end = std::max(offset + modifiedsize, old_range_end);
-
 	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 = nullptr;
 	}
+	if (Shader::defaultVideoShader)
+	{
+		Shader::defaultVideoShader->release();
+		Shader::defaultVideoShader = nullptr;
+	}
 
 	if (quadIndices)
 		delete quadIndices;
@@ -110,7 +115,7 @@ void Graphics::restoreState(const DisplayState &s)
 	setPointSize(s.pointSize);
 
 	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
 		setScissor();
 
@@ -131,11 +136,10 @@ void Graphics::restoreStateChecked(const DisplayState &s)
 {
 	const DisplayState &cur = states.back();
 
-	if (*(uint32 *) &s.color.r != *(uint32 *) &cur.color.r)
+	if (s.color != cur.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)
 		setBlendMode(s.blendMode, s.blendMultiplyAlpha);
@@ -148,10 +152,10 @@ void Graphics::restoreStateChecked(const DisplayState &s)
 	if (s.pointSize != cur.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)
-			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
 			setScissor();
 	}
@@ -295,6 +299,10 @@ bool Graphics::setMode(int width, int height)
 
 	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
 	// QuadIndices object is alive at all times while love.graphics is alive.
 	// 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)
 		quadIndices = new QuadIndices(20);
 
-	// Reload all volatile objects.
-	if (!Volatile::loadAll())
-		::printf("Could not reload all volatile objects.\n");
-
 	// Restore the graphics state.
 	restoreState(states.back());
 
@@ -321,6 +325,13 @@ bool Graphics::setMode(int width, int height)
 		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
 	// returned by getShader(), so we don't do setShader(defaultShader).
 	if (!Shader::current)
@@ -581,14 +592,35 @@ bool Graphics::isCreated() const
 
 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);
 	// 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().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()
@@ -601,10 +633,10 @@ bool Graphics::getScissor(int &x, int &y, int &width, int &height) const
 {
 	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;
 }
@@ -688,7 +720,7 @@ Image *Graphics::newImage(const std::vector<love::image::CompressedImageData *>
 	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);
 }
@@ -794,11 +826,16 @@ Mesh *Graphics::newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, con
 	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);
 }
 
+Video *Graphics::newVideo(love::video::VideoStream *stream)
+{
+	return new Video(stream);
+}
+
 bool Graphics::isGammaCorrect() const
 {
 	return love::graphics::isGammaCorrect();
@@ -1085,7 +1122,7 @@ bool Graphics::isWireframe() const
 	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();
 
@@ -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);
 }
 
-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();
 
@@ -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);
 	}
 
-	// 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;
 }
@@ -1482,10 +1506,10 @@ bool Graphics::isSupported(Support feature) const
 {
 	switch (feature)
 	{
-	case SUPPORT_MULTI_CANVAS:
-		return Canvas::isMultiCanvasSupported();
 	case SUPPORT_MULTI_CANVAS_FORMATS:
 		return Canvas::isMultiFormatMultiCanvasSupported();
+	case SUPPORT_CLAMP_ZERO:
+		return gl.isClampZeroTextureWrapSupported();
 	default:
 		return false;
 	}

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

@@ -37,6 +37,8 @@
 
 #include "window/Window.h"
 
+#include "video/VideoStream.h"
+
 #include "Font.h"
 #include "Image.h"
 #include "graphics/Quad.h"
@@ -47,6 +49,7 @@
 #include "Shader.h"
 #include "Mesh.h"
 #include "Text.h"
+#include "Video.h"
 
 namespace love
 {
@@ -126,6 +129,8 @@ public:
 	 **/
 	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.
 	 **/
@@ -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::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.
@@ -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, 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;
 
@@ -326,7 +333,7 @@ public:
 	 * @param kx Shear along the x-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.
@@ -344,7 +351,7 @@ public:
 	 * @param kx Shear along the x-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).
@@ -478,7 +485,7 @@ private:
 		float pointSize = 1.0f;
 
 		bool scissor = false;
-		OpenGL::Viewport scissorBox = OpenGL::Viewport();
+		ScissorRect scissorRect = ScissorRect();
 
 		// Stencil.
 		bool stencilTest = false;

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

@@ -356,7 +356,7 @@ bool Image::loadVolatile()
 
 		GLenum glerr = glGetError();
 		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 &)
 	{
@@ -531,8 +531,16 @@ bool Image::setWrap(const Texture::Wrap &w)
 		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.setTextureWrap(w);
+	gl.setTextureWrap(wrap);
 
 	return success;
 }
@@ -589,112 +597,101 @@ bool Image::isCompressed() const
 
 GLenum Image::getCompressedFormat(image::CompressedImageData::Format cformat, bool &isSRGB) const
 {
+	using image::CompressedImageData;
+
 	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;
 		return GL_COMPRESSED_RED_RGTC1;
-	case image::CompressedImageData::FORMAT_BC4s:
+	case CompressedImageData::FORMAT_BC4s:
 		isSRGB = false;
 		return GL_COMPRESSED_SIGNED_RED_RGTC1;
-	case image::CompressedImageData::FORMAT_BC5:
+	case CompressedImageData::FORMAT_BC5:
 		isSRGB = false;
 		return GL_COMPRESSED_RG_RGTC2;
-	case image::CompressedImageData::FORMAT_BC5s:
+	case CompressedImageData::FORMAT_BC5s:
 		isSRGB = false;
 		return GL_COMPRESSED_SIGNED_RG_RGTC2;
-	case image::CompressedImageData::FORMAT_BC6H:
+	case CompressedImageData::FORMAT_BC6H:
 		isSRGB = false;
 		return GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT;
-	case image::CompressedImageData::FORMAT_BC6Hs:
+	case CompressedImageData::FORMAT_BC6Hs:
 		isSRGB = false;
 		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.
 		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
 		{
 			isSRGB = false;
 			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;
 		return GL_COMPRESSED_R11_EAC;
-	case image::CompressedImageData::FORMAT_EAC_Rs:
+	case CompressedImageData::FORMAT_EAC_Rs:
 		isSRGB = false;
 		return GL_COMPRESSED_SIGNED_R11_EAC;
-	case image::CompressedImageData::FORMAT_EAC_RG:
+	case CompressedImageData::FORMAT_EAC_RG:
 		isSRGB = false;
 		return GL_COMPRESSED_RG11_EAC;
-	case image::CompressedImageData::FORMAT_EAC_RGs:
+	case CompressedImageData::FORMAT_EAC_RGs:
 		isSRGB = false;
 		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:
-		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)
 {
+	using image::CompressedImageData;
+
 	switch (format)
 	{
-	case image::CompressedImageData::FORMAT_DXT1:
+	case CompressedImageData::FORMAT_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;
-	case image::CompressedImageData::FORMAT_DXT5:
+	case CompressedImageData::FORMAT_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);
-	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;
-	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.
 		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;
-	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:
 		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[] =
 {
-	{"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));

+ 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)
 	, vertexStride(0)
 	, ibo(nullptr)
+	, useIndexBuffer(false)
 	, elementCount(0)
 	, elementDataType(0)
 	, drawMode(drawmode)
@@ -90,6 +91,7 @@ Mesh::Mesh(const std::vector<AttribFormat> &vertexformat, int vertexcount, DrawM
 	, vertexCount((size_t) vertexcount)
 	, vertexStride(0)
 	, ibo(nullptr)
+	, useIndexBuffer(false)
 	, elementCount(0)
 	, elementDataType(getGLDataTypeFromMax(vertexcount))
 	, drawMode(drawmode)
@@ -147,7 +149,7 @@ void Mesh::setupAttachedAttributes()
 		if (attachedAttributes.find(name) != attachedAttributes.end())
 			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;
 }
 
+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)
 {
 	auto it = attachedAttributes.find(name);
@@ -326,20 +339,10 @@ void Mesh::attachAttribute(const std::string &name, Mesh *mesh)
 		oldattrib = it->second;
 
 	newattrib.mesh = mesh;
-	newattrib.index = std::numeric_limits<size_t>::max();
 	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());
 
 	if (newattrib.mesh != this)
@@ -357,11 +360,10 @@ void *Mesh::mapVertexData()
 	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);
-	vbo->setMappedRangeModified(0, vbo->getSize());
+	vbo->setMappedRangeModified(modifiedoffset, modifiedsize);
 	vbo->unmap();
 }
 
@@ -414,6 +416,7 @@ void Mesh::setVertexMap(const std::vector<uint32> &map)
 	if (!ibo && size > 0)
 		ibo = new GLBuffer(size, nullptr, GL_ELEMENT_ARRAY_BUFFER, vbo->getUsage());
 
+	useIndexBuffer = true;
 	elementCount = map.size();
 
 	if (!ibo || elementCount == 0)
@@ -437,6 +440,11 @@ void Mesh::setVertexMap(const std::vector<uint32> &map)
 	elementDataType = datatype;
 }
 
+void Mesh::setVertexMap()
+{
+	useIndexBuffer = false;
+}
+
 /**
  * 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]);
 }
 
-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.reserve(elementCount);
 
+	if (!ibo || elementCount == 0)
+		return true;
+
 	GLBuffer::Bind ibobind(*ibo);
 
 	// 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);
 		break;
 	}
+
+	return true;
 }
 
 size_t Mesh::getVertexMapCount() const
@@ -524,6 +537,39 @@ void Mesh::getDrawRange(int &min, int &max) const
 	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)
 {
 	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;
 
 		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))
-	{
-		// 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.");
-	}
 
 	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();
 
-	if (ibo && elementCount > 0)
+	if (useIndexBuffer && ibo && elementCount > 0)
 	{
 		// Use the custom vertex map (index buffer) to draw the vertices.
 		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;
 	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.
@@ -140,7 +141,7 @@ public:
 	void attachAttribute(const std::string &name, Mesh *mesh);
 
 	void *mapVertexData();
-	void unmapVertexData();
+	void unmapVertexData(size_t modifiedoffset = 0, size_t modifiedsize = -1);
 
 	/**
 	 * Flushes all modified data to the GPU.
@@ -154,12 +155,13 @@ public:
 	 * {0, 1, 2, 3, 4, ...}
 	 **/
 	void setVertexMap(const std::vector<uint32> &map);
+	void setVertexMap();
 
 	/**
 	 * Fills the uint32 vector passed into the method with the previously set
 	 * 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.
@@ -192,6 +194,8 @@ public:
 	void setDrawRange();
 	void getDrawRange(int &min, int &max) const;
 
+	int bindAttributeToShaderInput(int attributeindex, const std::string &inputname);
+
 	// Implements Drawable.
 	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
 	{
 		Mesh *mesh;
-		size_t index;
+		int index;
 		bool enabled;
 	};
 
@@ -242,6 +246,7 @@ private:
 
 	// Element (vertex index) buffer, for the vertex map.
 	GLBuffer *ibo;
+	bool useIndexBuffer;
 	size_t elementCount;
 	GLenum elementDataType;
 

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

@@ -32,6 +32,7 @@
 
 // C
 #include <cstring>
+#include <cstdio>
 
 // For SDL_GL_GetProcAddress.
 #include <SDL_video.h>
@@ -601,24 +602,32 @@ void OpenGL::setTextureFilter(graphics::Texture::Filter &f)
 		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
@@ -643,7 +652,7 @@ int OpenGL::getMaxTextureUnits() const
 
 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);
 }
 
@@ -652,6 +661,36 @@ OpenGL::Vendor OpenGL::getVendor() const
 	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)
 {
 	switch (severity)

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

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

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

@@ -22,8 +22,6 @@
 #include "common/config.h"
 #include "ParticleSystem.h"
 
-#include "common/math.h"
-#include "modules/math/RandomGenerator.h"
 #include "OpenGL.h"
 
 // STD
@@ -38,812 +36,58 @@ namespace graphics
 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)
-	: pMem(nullptr)
-	, pFree(nullptr)
-	, pHead(nullptr)
-	, pTail(nullptr)
+	: love::graphics::ParticleSystem(texture, size)
 	, 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)
-	: pMem(nullptr)
-	, pFree(nullptr)
-	, pHead(nullptr)
-	, pTail(nullptr)
+	: love::graphics::ParticleSystem(p)
 	, particleVerts(nullptr)
 	, 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()
 {
-	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
 	{
-		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)
 {
-	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)
 {
 	uint32 pCount = getCount();
+
 	if (pCount == 0 || texture.get() == nullptr || pMem == nullptr || particleVerts == nullptr)
 		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
 } // graphics
 } // love

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

@@ -22,18 +22,9 @@
 #define LOVE_GRAPHICS_OPENGL_PARTICLE_SYSTEM_H
 
 // 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"
 
-// STL
-#include <vector>
-
 namespace love
 {
 namespace graphics
@@ -41,643 +32,28 @@ namespace graphics
 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:
-	/**
-	 * 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.
-	 **/
-	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
 	Vertex *particleVerts;
 
 	// Vertex index buffer.
 	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

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

@@ -64,8 +64,10 @@ namespace
 
 Shader *Shader::current = nullptr;
 Shader *Shader::defaultShader = nullptr;
+Shader *Shader::defaultVideoShader = nullptr;
 
 Shader::ShaderSource Shader::defaultCode[Graphics::RENDERER_MAX_ENUM];
+Shader::ShaderSource Shader::defaultVideoCode[Graphics::RENDERER_MAX_ENUM];
 
 std::vector<int> Shader::textureCounters;
 
@@ -77,6 +79,7 @@ Shader::Shader(const ShaderSource &source)
 	, lastCanvas((Canvas *) -1)
 	, lastViewport()
 	, lastPointSize(0.0f)
+	, videoTextureUnits()
 {
 	if (source.vertex.empty() && source.pixel.empty())
 		throw love::Exception("Cannot create shader: no source code!");
@@ -225,6 +228,9 @@ bool Shader::loadVolatile()
 	lastProjectionMatrix.setTranslation(nan, nan);
 	lastTransformMatrix.setTranslation(nan, nan);
 
+	for (int i = 0; i < 3; i++)
+		videoTextureUnits[i] = 0;
+
 	// zero out active texture list
 	activeTexUnits.clear();
 	activeTexUnits.insert(activeTexUnits.begin(), gl.getMaxTextureUnits() - 1, 0);
@@ -635,6 +641,57 @@ bool Shader::hasVertexAttrib(VertexAttribID attrib) const
 	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()
 {
 	OpenGL::Viewport view = gl.getViewport();
@@ -898,6 +955,9 @@ StringMap<Shader::BuiltinUniform, Shader::BUILTIN_MAX_ENUM>::Entry Shader::built
 	{"NormalMatrix", Shader::BUILTIN_NORMAL_MATRIX},
 	{"love_PointSize", Shader::BUILTIN_POINT_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));

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

@@ -64,6 +64,9 @@ public:
 		BUILTIN_NORMAL_MATRIX,
 		BUILTIN_POINT_SIZE,
 		BUILTIN_SCREEN_SIZE,
+		BUILTIN_VIDEO_Y_CHANNEL,
+		BUILTIN_VIDEO_CB_CHANNEL,
+		BUILTIN_VIDEO_CR_CHANNEL,
 		BUILTIN_MAX_ENUM
 	};
 
@@ -89,9 +92,11 @@ public:
 
 	// Pointer to the default Shader.
 	static Shader *defaultShader;
+	static Shader *defaultVideoShader;
 
 	// Default shader code (a shader is always required internally.)
 	static ShaderSource defaultCode[Graphics::RENDERER_MAX_ENUM];
+	static ShaderSource defaultVideoCode[Graphics::RENDERER_MAX_ENUM];
 
 	/**
 	 * Creates a new Shader using a list of source codes.
@@ -182,6 +187,7 @@ public:
 	 **/
 	bool hasVertexAttrib(VertexAttribID attrib) const;
 
+	void setVideoTextures(GLuint ytexture, GLuint cbtexture, GLuint crtexture);
 	void checkSetScreenParams();
 	void checkSetPointSize(float size);
 	void checkSetBuiltinUniforms();
@@ -263,6 +269,8 @@ private:
 	Matrix4 lastTransformMatrix;
 	Matrix4 lastProjectionMatrix;
 
+	GLuint videoTextureUnits[3];
+
 	// Counts total number of textures bound to each texture unit in all shaders
 	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.");
 
 	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()
@@ -207,6 +193,28 @@ int SpriteBatch::getBufferSize() const
 	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)
 {
 	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());
 
-	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;
 
-	// 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.prepareDraw();
+
+	GLBuffer::Bind element_bind(*quad_indices.getBuffer());
 	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
 #include <cstring>
 
+// C++
+#include <unordered_map>
+
 // LOVE
 #include "common/math.h"
 #include "common/Matrix.h"
@@ -98,11 +101,23 @@ public:
 	 **/
 	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.
 	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 
 private:
 
+	struct AttachedAttribute
+	{
+		StrongRef<Mesh> mesh;
+		int index;
+	};
+
 	void addv(const Vertex *v, const Matrix3 &m, int index);
 
 	/**
@@ -129,6 +144,8 @@ private:
 	GLBuffer *array_buf;
 	QuadIndices quad_indices;
 
+	std::unordered_map<std::string, AttachedAttribute> attached_attributes;
+
 }; // SpriteBatch
 
 } // opengl

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

@@ -30,10 +30,9 @@ namespace graphics
 namespace opengl
 {
 
-Text::Text(Font *font, const std::string &text)
+Text::Text(Font *font, const std::vector<Font::ColoredString> &text)
 	: font(font)
 	, vbo(nullptr)
-	, text_info()
 	, vert_offset(0)
 	, texture_cache_id((uint32) -1)
 {
@@ -82,7 +81,7 @@ void Text::uploadVertices(const std::vector<Font::GlyphVertex> &vertices, size_t
 		vbo = new_vbo;
 	}
 
-	if (vbo != nullptr)
+	if (vbo != nullptr && datasize > 0)
 	{
 		GLBuffer::Bind bind(*vbo);
 		vbodata = (uint8 *) vbo->map();
@@ -113,11 +112,13 @@ void Text::addTextData(const TextData &t)
 	std::vector<Font::GlyphVertex> vertices;
 	std::vector<Font::DrawCommand> new_commands;
 
+	Font::TextInfo text_info;
+
 	// We only have formatted text if the align mode is valid.
 	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
-		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)
 		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();
+
 	text_data.push_back(t);
+	text_data.back().text_info = text_info;
 
 	// Font::generateVertices can invalidate the font's texture cache.
 	if (font->getTextureCacheID() != texture_cache_id)
 		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();
 
-	addTextData({text, wrap, align, false, false, Matrix3()});
+	Font::ColoredCodepoints codepoints;
+	Font::getCodepointsFromString(text, codepoints);
+
+	addTextData({codepoints, wrap, align, {}, false, false, Matrix3()});
 }
 
 void Text::set()
@@ -186,24 +189,21 @@ void Text::set()
 	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);
 
-	addTextData({text, wrap, align, true, true, m});
+	addTextData({codepoints, wrap, align, {}, true, true, m});
+
+	return (int) text_data.size() - 1;
 }
 
 void Text::clear()
@@ -211,7 +211,6 @@ void Text::clear()
 	text_data.clear();
 	draw_commands.clear();
 	texture_cache_id = font->getTextureCacheID();
-	text_info = {};
 	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)
 		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);
 
 	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);
@@ -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.
 		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);
 }
@@ -262,14 +263,26 @@ Font *Text::getFont() const
 	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

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

@@ -38,15 +38,15 @@ class Text : public Drawable
 {
 public:
 
-	Text(Font *font, const std::string &text = "");
+	Text(Font *font, const std::vector<Font::ColoredString> &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 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();
 
 	// Implements Drawable.
@@ -58,20 +58,21 @@ public:
 	/**
 	 * 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.
 	 **/
-	int getHeight() const;
+	int getHeight(int index = 0) const;
 
 private:
 
 	struct TextData
 	{
-		std::string text;
+		Font::ColoredCodepoints codepoints;
 		float wrap;
 		Font::AlignMode align;
+		Font::TextInfo text_info;
 		bool use_matrix;
 		bool append_vertices;
 		Matrix3 matrix;
@@ -87,7 +88,6 @@ private:
 	std::vector<Font::DrawCommand> draw_commands;
 
 	std::vector<TextData> text_data;
-	Font::TextInfo text_info;
 
 	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;
 }
 
-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 },
 	{ "newImageData", w_Canvas_newImageData },
 	{ "getFormat", w_Canvas_getFormat },
@@ -121,7 +112,7 @@ static const luaL_Reg functions[] =
 
 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

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

@@ -35,10 +35,6 @@ namespace opengl
 
 //see Canvas.h
 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);
 
 } // 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;
 }
 
-static const luaL_Reg functions[] =
+static const luaL_Reg w_Font_functions[] =
 {
 	{ "getHeight", w_Font_getHeight },
 	{ "getWidth", w_Font_getWidth },
@@ -203,7 +203,7 @@ static const luaL_Reg functions[] =
 
 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

+ 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);
-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);
 
 } // opengl

+ 72 - 15
jni/love/src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -25,6 +25,7 @@
 #include "image/Image.h"
 #include "font/Rasterizer.h"
 #include "filesystem/wrap_Filesystem.h"
+#include "video/VideoStream.h"
 #include "image/wrap_Image.h"
 
 #include <cassert>
@@ -184,6 +185,20 @@ int w_setScissor(lua_State *L)
 	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 x, y, w, h;
@@ -354,13 +369,13 @@ int w_newImage(lua_State *L)
 int w_newQuad(lua_State *L)
 {
 	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);
 	luax_pushtype(L, GRAPHICS_QUAD_ID, quad);
@@ -837,7 +852,9 @@ int w_newText(lua_State *L)
 		luax_catchexcept(L, [&](){ t = instance()->newText(font); });
 	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); });
 	}
 
@@ -846,6 +863,20 @@ int w_newText(lua_State *L)
 	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)
 {
 	Colorf c;
@@ -1258,25 +1289,37 @@ int w_setDefaultShaderCode(lua_State *L)
 	lua_getfield(L, 1, "opengl");
 	lua_rawgeti(L, -1, 1);
 	lua_rawgeti(L, -2, 2);
+	lua_rawgeti(L, -3, 3);
 
 	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_rawgeti(L, -1, 1);
 	lua_rawgeti(L, -2, 2);
+	lua_rawgeti(L, -3, 3);
 
 	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_OPENGLES] = openglescode;
+	Shader::defaultVideoCode[Graphics::RENDERER_OPENGL]   = openglVideocode;
+	Shader::defaultVideoCode[Graphics::RENDERER_OPENGLES] = openglesVideocode;
 
 	return 0;
 }
@@ -1452,7 +1495,9 @@ int w_draw(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 y = (float)luaL_optnumber(L, 3, 0.0);
 	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)
 {
-	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 y = (float)luaL_checknumber(L, 3);
 	float wrap = (float)luaL_checknumber(L, 4);
@@ -1843,6 +1890,7 @@ static const luaL_Reg functions[] =
 	{ "newShader", w_newShader },
 	{ "newMesh", w_newMesh },
 	{ "newText", w_newText },
+	{ "_newVideo", w_newVideo },
 
 	{ "setColor", w_setColor },
 	{ "getColor", w_getColor },
@@ -1899,6 +1947,7 @@ static const luaL_Reg functions[] =
 	{ "getDimensions", w_getDimensions },
 
 	{ "setScissor", w_setScissor },
+	{ "intersectScissor", w_intersectScissor },
 	{ "getScissor", w_getScissor },
 
 	{ "stencil", w_stencil },
@@ -1925,9 +1974,16 @@ static const luaL_Reg functions[] =
 	{ 0, 0 }
 };
 
+static int luaopen_drawable(lua_State *L)
+{
+	return luax_register_type(L, GRAPHICS_DRAWABLE_ID, "Drawable", nullptr);
+}
+
 // Types for this module.
 static const lua_CFunction types[] =
 {
+	luaopen_drawable,
+	luaopen_texture,
 	luaopen_font,
 	luaopen_image,
 	luaopen_quad,
@@ -1937,6 +1993,7 @@ static const lua_CFunction types[] =
 	luaopen_shader,
 	luaopen_mesh,
 	luaopen_text,
+	luaopen_video,
 	0
 };
 

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

@@ -32,6 +32,7 @@
 #include "wrap_Shader.h"
 #include "wrap_Mesh.h"
 #include "wrap_Text.h"
+#include "wrap_Video.h"
 #include "Graphics.h"
 
 namespace love
@@ -41,85 +42,6 @@ namespace graphics
 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);
 
 } // opengl

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

@@ -159,12 +159,34 @@ precision mediump float;
 #endif
 
 varying mediump vec4 VaryingTexCoord;
-varying lowp vec4 VaryingColor;
+varying mediump vec4 VaryingColor;
 
 #define love_Canvases gl_FragData
 
 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 = [[
 void main() {
 	// 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 "",
 		GLSL.PIXEL.HEADER, GLSL.UNIFORMS,
 		GLSL.FUNCTIONS,
+		GLSL.PIXEL.FUNCTIONS,
 		lang == "glsles" and "#line 1" or "#line 0",
 		pixelcode,
 		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;
 }]],
 	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;
-}]]
+}]],
+	videopixel = [[
+vec4 effect(mediump vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord) {
+	return VideoTexel(texcoord) * vcolor;
+}]],
 }
 
 local defaults = {
 	opengl = {
 		createVertexCode(defaultcode.vertex, "glsl"),
 		createPixelCode(defaultcode.pixel, false, "glsl"),
+		createPixelCode(defaultcode.videopixel, false, "glsl"),
 	},
 	opengles = {
 		createVertexCode(defaultcode.vertex, "glsles"),
 		createPixelCode(defaultcode.pixel, false, "glsles"),
+		createPixelCode(defaultcode.videopixel, false, "glsles"),
 	},
 }
 
 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.
 --)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;
 }
 
-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 },
 	{ "getMipmapFilter", w_Image_getMipmapFilter },
 	{ "isCompressed", w_Image_isCompressed },
@@ -159,7 +150,7 @@ static const luaL_Reg functions[] =
 
 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

+ 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);
-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);
 
 } // 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);
 	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);
-	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();
 
@@ -120,7 +125,10 @@ int w_Mesh_setVertices(lua_State *L)
 	for (const Mesh::AttribFormat &format : vertexformat)
 		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++)
 	{
@@ -145,7 +153,7 @@ int w_Mesh_setVertices(lua_State *L)
 		lua_pop(L, ncomponents + 1);
 	}
 
-	t->unmapVertexData();
+	t->unmapVertexData(byteoffset, nvertices * stride);
 	return 0;
 }
 
@@ -331,6 +339,13 @@ int w_Mesh_setVertexMap(lua_State *L)
 {
 	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);
 	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);
 
 	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();
 
@@ -468,7 +490,7 @@ int w_Mesh_getDrawRange(lua_State *L)
 	return 2;
 }
 
-static const luaL_Reg functions[] =
+static const luaL_Reg w_Mesh_functions[] =
 {
 	{ "setVertices", w_Mesh_setVertices },
 	{ "setVertex", w_Mesh_setVertex },
@@ -494,7 +516,7 @@ static const luaL_Reg functions[] =
 
 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

+ 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);
 
 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);
 
 } // 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;
 }
 
-static const luaL_Reg functions[] =
+static const luaL_Reg w_ParticleSystem_functions[] =
 {
 	{ "clone", w_ParticleSystem_clone },
 	{ "setTexture", w_ParticleSystem_setTexture },
@@ -772,7 +772,7 @@ static const luaL_Reg functions[] =
 
 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

+ 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);
-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);
 
 } // opengl

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

@@ -25,6 +25,7 @@
 #include <string>
 #include <iostream>
 #include <algorithm>
+#include <cmath>
 
 namespace love
 {
@@ -229,38 +230,86 @@ int w_Shader_sendMatrix(lua_State *L)
 	if (!lua_istable(L, 3))
 		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);
 
+	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)
 		return luaL_error(L, "Invalid matrix size: %dx%d (only 2x2, 3x3 and 4x4 matrices are supported).",
 						  dimension, dimension);
 
 	float *values = new float[dimension * dimension * count];
+
 	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
 			// another dimension: a dimension of sound, a dimension of sight,
 			// a dimension of mind. You're moving into a land of both shadow
 			// and substance, of things and ideas. You've just crossed over
 			// into... the Twilight Zone.
-			int other_dimension = (int) lua_tointeger(L, -1);
 			delete[] values;
 			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);
 		}
 
-		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,
@@ -281,48 +330,6 @@ int w_Shader_sendTexture(lua_State *L)
 	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 ttype = lua_type(L, 3);
@@ -352,10 +359,7 @@ int w_Shader_send(lua_State *L)
 		if (ttype == LUA_TNUMBER || ttype == LUA_TBOOLEAN)
 			return w_Shader_sendFloat(L);
 		else if (ttype == LUA_TTABLE)
-		{
-			w_convertMatrices(L, 3);
 			return w_Shader_sendMatrix(L);
-		}
 
 		break;
 	default:
@@ -397,7 +401,7 @@ int w_Shader_getExternVariable(lua_State *L)
 	return 3;
 }
 
-static const luaL_Reg functions[] =
+static const luaL_Reg w_Shader_functions[] =
 {
 	{ "getWarnings", w_Shader_getWarnings },
 	{ "sendInt",     w_Shader_sendInt },
@@ -413,7 +417,7 @@ static const luaL_Reg functions[] =
 
 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

+ 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);
-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);
 
 } // 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;
 }
 
-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 },
 	{ "set", w_SpriteBatch_set },
@@ -216,12 +226,13 @@ static const luaL_Reg functions[] =
 	{ "getCount", w_SpriteBatch_getCount },
 	{ "setBufferSize", w_SpriteBatch_setBufferSize },
 	{ "getBufferSize", w_SpriteBatch_getBufferSize },
+	{ "attachAttribute", w_SpriteBatch_attachAttribute },
 	{ 0, 0 }
 };
 
 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

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