Browse Source

Merge minor into dynamiccore2

--HG--
branch : dynamiccore2
Bart van Strien 8 years ago
parent
commit
24a7a3d367
61 changed files with 4816 additions and 1600 deletions
  1. 1 0
      .hgtags
  2. 24 0
      CMakeLists.txt
  3. 1 1
      changes.txt
  4. 126 0
      platform/xcode/liblove.xcodeproj/project.pbxproj
  5. 9 0
      platform/xcode/love.xcodeproj/project.pbxproj
  6. 12 0
      src/common/Matrix.cpp
  7. 15 0
      src/common/Matrix.h
  8. 889 0
      src/libraries/xxHash/xxhash.c
  9. 293 0
      src/libraries/xxHash/xxhash.h
  10. 3 27
      src/modules/audio/Audio.h
  11. 39 0
      src/modules/audio/RecordingDevice.cpp
  12. 104 0
      src/modules/audio/RecordingDevice.h
  13. 2 16
      src/modules/audio/null/Audio.cpp
  14. 3 4
      src/modules/audio/null/Audio.h
  15. 92 0
      src/modules/audio/null/RecordingDevice.cpp
  16. 58 0
      src/modules/audio/null/RecordingDevice.h
  17. 98 62
      src/modules/audio/openal/Audio.cpp
  18. 13 6
      src/modules/audio/openal/Audio.h
  19. 157 0
      src/modules/audio/openal/RecordingDevice.cpp
  20. 78 0
      src/modules/audio/openal/RecordingDevice.h
  21. 54 55
      src/modules/audio/openal/Source.cpp
  22. 0 9
      src/modules/audio/openal/Source.h
  23. 17 46
      src/modules/audio/wrap_Audio.cpp
  24. 1 0
      src/modules/audio/wrap_Audio.h
  25. 155 0
      src/modules/audio/wrap_RecordingDevice.cpp
  26. 40 0
      src/modules/audio/wrap_RecordingDevice.h
  27. 17 0
      src/modules/event/sdl/Event.cpp
  28. 2 0
      src/modules/event/sdl/Event.h
  29. 6 5
      src/modules/event/wrap_Event.cpp
  30. 3 0
      src/modules/graphics/Graphics.cpp
  31. 16 1
      src/modules/graphics/Graphics.h
  32. 56 409
      src/modules/graphics/opengl/Canvas.cpp
  33. 15 37
      src/modules/graphics/opengl/Canvas.h
  34. 591 237
      src/modules/graphics/opengl/Graphics.cpp
  35. 105 38
      src/modules/graphics/opengl/Graphics.h
  36. 3 0
      src/modules/graphics/opengl/Image.cpp
  37. 21 3
      src/modules/graphics/opengl/Mesh.cpp
  38. 2 1
      src/modules/graphics/opengl/Mesh.h
  39. 109 16
      src/modules/graphics/opengl/OpenGL.cpp
  40. 24 5
      src/modules/graphics/opengl/OpenGL.h
  41. 307 191
      src/modules/graphics/opengl/Shader.cpp
  42. 22 34
      src/modules/graphics/opengl/Shader.h
  43. 0 51
      src/modules/graphics/opengl/wrap_Canvas.cpp
  44. 402 215
      src/modules/graphics/opengl/wrap_Graphics.cpp
  45. 13 1
      src/modules/graphics/opengl/wrap_Mesh.cpp
  46. 33 22
      src/modules/graphics/opengl/wrap_Shader.cpp
  47. 32 18
      src/modules/graphics/opengl/wrap_SpriteBatch.cpp
  48. 47 28
      src/modules/graphics/opengl/wrap_Text.cpp
  49. 11 0
      src/modules/math/MathModule.cpp
  50. 4 0
      src/modules/math/MathModule.h
  51. 133 0
      src/modules/math/Transform.cpp
  52. 85 0
      src/modules/math/Transform.h
  53. 29 0
      src/modules/math/wrap_Math.cpp
  54. 305 0
      src/modules/math/wrap_Transform.cpp
  55. 36 0
      src/modules/math/wrap_Transform.h
  56. 8 0
      src/modules/window/Window.h
  57. 30 16
      src/modules/window/sdl/Window.cpp
  58. 4 0
      src/modules/window/sdl/Window.h
  59. 9 7
      src/modules/window/wrap_Window.cpp
  60. 19 11
      src/scripts/boot.lua
  61. 33 28
      src/scripts/boot.lua.h

+ 1 - 0
.hgtags

@@ -10,3 +10,4 @@ e0f98d53debb62347c6433ca0534a0f77f15f76f 0.8.0
 a5e405cdf14d030b71d09a105086797942ae91a9 0.9.2
 14717f0fb5d96d380c10fe6f41b74b7652c7d836 0.10.0
 3cb3559eebb89803c0b7fa83d51d8e48a2b20b7a 0.10.1
+afc69c4f714534c64e6f8c8660b724b5b736d7f3 0.10.2

+ 24 - 0
CMakeLists.txt

@@ -309,10 +309,14 @@ set(LOVE_SRC_MODULE_AUDIO_ROOT
 	src/modules/audio/Audio.h
 	src/modules/audio/Source.cpp
 	src/modules/audio/Source.h
+	src/modules/audio/RecordingDevice.cpp
+	src/modules/audio/RecordingDevice.h
 	src/modules/audio/wrap_Audio.cpp
 	src/modules/audio/wrap_Audio.h
 	src/modules/audio/wrap_Source.cpp
 	src/modules/audio/wrap_Source.h
+	src/modules/audio/wrap_RecordingDevice.cpp
+	src/modules/audio/wrap_RecordingDevice.h
 )
 
 set(LOVE_SRC_MODULE_AUDIO_NULL
@@ -320,6 +324,8 @@ set(LOVE_SRC_MODULE_AUDIO_NULL
 	src/modules/audio/null/Audio.h
 	src/modules/audio/null/Source.cpp
 	src/modules/audio/null/Source.h
+	src/modules/audio/null/RecordingDevice.cpp
+	src/modules/audio/null/RecordingDevice.h
 )
 
 set(LOVE_SRC_MODULE_AUDIO_OPENAL
@@ -329,6 +335,8 @@ set(LOVE_SRC_MODULE_AUDIO_OPENAL
 	src/modules/audio/openal/Pool.h
 	src/modules/audio/openal/Source.cpp
 	src/modules/audio/openal/Source.h
+	src/modules/audio/openal/RecordingDevice.cpp
+	src/modules/audio/openal/RecordingDevice.h
 )
 
 set(LOVE_SRC_MODULE_AUDIO
@@ -647,6 +655,8 @@ set(LOVE_SRC_MODULE_MATH
 	src/modules/math/MathModule.h
 	src/modules/math/RandomGenerator.cpp
 	src/modules/math/RandomGenerator.h
+	src/modules/math/Transform.cpp
+	src/modules/math/Transform.h
 	src/modules/math/wrap_BezierCurve.cpp
 	src/modules/math/wrap_BezierCurve.h
 	src/modules/math/wrap_CompressedData.cpp
@@ -655,6 +665,8 @@ set(LOVE_SRC_MODULE_MATH
 	src/modules/math/wrap_Math.h
 	src/modules/math/wrap_RandomGenerator.cpp
 	src/modules/math/wrap_RandomGenerator.h
+	src/modules/math/wrap_Transform.cpp
+	src/modules/math/wrap_Transform.h
 )
 
 source_group("modules\\math" FILES ${LOVE_SRC_MODULE_MATH})
@@ -1418,6 +1430,17 @@ set(LOVE_SRC_3P_WUFF
 
 add_library(love_3p_wuff ${LOVE_SRC_3P_WUFF})
 
+#
+# xxHash
+#
+
+set(LOVE_SRC_3P_XXHASH
+	src/libraries/xxHash/xxhash.c
+	src/libraries/xxHash/xxhash.h
+)
+
+add_library(love_3p_xxhash ${LOVE_SRC_3P_XXHASH})
+
 set(LOVE_3P
 	love_3p_box2d
 	love_3p_ddsparse
@@ -1429,6 +1452,7 @@ set(LOVE_3P
 	love_3p_lz4
 	love_3p_noise1234
 	love_3p_wuff
+	love_3p_xxhash
 )
 
 love_disable_warnings(love_3p_box2d love_3p_enet love_3p_luasocket)

+ 1 - 1
changes.txt

@@ -43,7 +43,7 @@ Released: N/A
 LOVE 0.10.2 [Super Toast]
 -------------------------
 
-Released: N/A
+Released: 2016-10-31
 
   * Added lovec.exe in Windows. It is the same as love.exe but built with the Console subsystem, so it always uses or provides a console.
   * Added the ability to restart the game via love.event.quit("restart").

+ 126 - 0
platform/xcode/liblove.xcodeproj/project.pbxproj

@@ -880,6 +880,45 @@
 		FA4943551D9DDFFE00E8D38A /* HashFunction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1DC2611C5D9555008F99A0 /* HashFunction.cpp */; };
 		FA4B66C91ABBCF1900558F15 /* Timer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4B66C81ABBCF1900558F15 /* Timer.cpp */; };
 		FA4B66CA1ABBCF1900558F15 /* Timer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4B66C81ABBCF1900558F15 /* Timer.cpp */; };
+		FA4F2B791DE0125B00CA37D7 /* xxhash.c in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2B771DE0125B00CA37D7 /* xxhash.c */; };
+		FA4F2B7A1DE0125B00CA37D7 /* xxhash.h in Headers */ = {isa = PBXBuildFile; fileRef = FA4F2B781DE0125B00CA37D7 /* xxhash.h */; };
+		FA4F2B7B1DE0181B00CA37D7 /* xxhash.c in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2B771DE0125B00CA37D7 /* xxhash.c */; };
+		FA4F2BA61DE1E36400CA37D7 /* RecordingDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BA21DE1E36400CA37D7 /* RecordingDevice.cpp */; };
+		FA4F2BA71DE1E36400CA37D7 /* RecordingDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = FA4F2BA31DE1E36400CA37D7 /* RecordingDevice.h */; };
+		FA4F2BA81DE1E36400CA37D7 /* wrap_RecordingDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BA41DE1E36400CA37D7 /* wrap_RecordingDevice.cpp */; };
+		FA4F2BA91DE1E36400CA37D7 /* wrap_RecordingDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = FA4F2BA51DE1E36400CA37D7 /* wrap_RecordingDevice.h */; };
+		FA4F2BAC1DE1E37000CA37D7 /* RecordingDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BAA1DE1E37000CA37D7 /* RecordingDevice.cpp */; };
+		FA4F2BAD1DE1E37000CA37D7 /* RecordingDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = FA4F2BAB1DE1E37000CA37D7 /* RecordingDevice.h */; };
+		FA4F2BB01DE1E37B00CA37D7 /* RecordingDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BAE1DE1E37B00CA37D7 /* RecordingDevice.cpp */; };
+		FA4F2BB11DE1E37B00CA37D7 /* RecordingDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = FA4F2BAF1DE1E37B00CA37D7 /* RecordingDevice.h */; };
+		FA4F2BB21DE1E4B400CA37D7 /* RecordingDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BAE1DE1E37B00CA37D7 /* RecordingDevice.cpp */; };
+		FA4F2BB31DE1E4B800CA37D7 /* RecordingDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BA21DE1E36400CA37D7 /* RecordingDevice.cpp */; };
+		FA4F2BB41DE1E4BD00CA37D7 /* RecordingDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BAA1DE1E37000CA37D7 /* RecordingDevice.cpp */; };
+		FA4F2BB51DE1E4C300CA37D7 /* wrap_RecordingDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BA41DE1E36400CA37D7 /* wrap_RecordingDevice.cpp */; };
+		FA4F2BE31DE6650600CA37D7 /* Transform.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BDF1DE6650600CA37D7 /* Transform.cpp */; };
+		FA4F2BE41DE6650600CA37D7 /* Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = FA4F2BE01DE6650600CA37D7 /* Transform.h */; };
+		FA4F2BE51DE6650600CA37D7 /* wrap_Transform.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BE11DE6650600CA37D7 /* wrap_Transform.cpp */; };
+		FA4F2BE61DE6650600CA37D7 /* wrap_Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = FA4F2BE21DE6650600CA37D7 /* wrap_Transform.h */; };
+		FA4F2BE71DE6650D00CA37D7 /* Transform.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BDF1DE6650600CA37D7 /* Transform.cpp */; };
+		FA4F2BE81DE6651000CA37D7 /* wrap_Transform.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA4F2BE11DE6650600CA37D7 /* wrap_Transform.cpp */; };
+		FA4F2C031DE936C200CA37D7 /* auxiliar.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFB9D1D9F6D490055D849 /* auxiliar.c */; };
+		FA4F2C041DE936C600CA37D7 /* buffer.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFB9F1D9F6D490055D849 /* buffer.c */; };
+		FA4F2C051DE936C900CA37D7 /* compat.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBA11D9F6D490055D849 /* compat.c */; };
+		FA4F2C061DE936CD00CA37D7 /* except.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBA31D9F6D490055D849 /* except.c */; };
+		FA4F2C071DE936DA00CA37D7 /* inet.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBAB1D9F6D490055D849 /* inet.c */; };
+		FA4F2C081DE936DD00CA37D7 /* io.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBAD1D9F6D490055D849 /* io.c */; };
+		FA4F2C091DE936E200CA37D7 /* luasocket.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBB11D9F6D490055D849 /* luasocket.c */; };
+		FA4F2C0A1DE936E600CA37D7 /* mime.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBB61D9F6D490055D849 /* mime.c */; };
+		FA4F2C0B1DE936EA00CA37D7 /* options.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBBA1D9F6D490055D849 /* options.c */; };
+		FA4F2C0C1DE936ED00CA37D7 /* select.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBBD1D9F6D490055D849 /* select.c */; };
+		FA4F2C0D1DE936F100CA37D7 /* serial.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBBF1D9F6D490055D849 /* serial.c */; };
+		FA4F2C0E1DE936FE00CA37D7 /* tcp.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBC51D9F6D490055D849 /* tcp.c */; };
+		FA4F2C0F1DE936FE00CA37D7 /* timeout.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBC71D9F6D490055D849 /* timeout.c */; };
+		FA4F2C101DE936FE00CA37D7 /* udp.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBCB1D9F6D490055D849 /* udp.c */; };
+		FA4F2C111DE936FE00CA37D7 /* unix.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBCD1D9F6D490055D849 /* unix.c */; };
+		FA4F2C121DE936FE00CA37D7 /* unixtcp.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBCF1D9F6D490055D849 /* unixtcp.c */; };
+		FA4F2C131DE936FE00CA37D7 /* unixudp.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBD11D9F6D490055D849 /* unixudp.c */; };
+		FA4F2C141DE936FE00CA37D7 /* usocket.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBD51D9F6D490055D849 /* usocket.c */; };
 		FA56D9BC1C208A0200D8D3C7 /* libmodplug.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FA56D9BA1C2089EE00D8D3C7 /* libmodplug.a */; };
 		FA577AB016C7507900860150 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA577A7916C71A1700860150 /* Cocoa.framework */; };
 		FA577AC216C7512D00860150 /* FreeType.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA577A6716C719D900860150 /* FreeType.framework */; };
@@ -1575,6 +1614,20 @@
 		FA41A3C61C0A1F950084430C /* ASTCHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ASTCHandler.cpp; sourceTree = "<group>"; };
 		FA41A3C71C0A1F950084430C /* ASTCHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTCHandler.h; sourceTree = "<group>"; };
 		FA4B66C81ABBCF1900558F15 /* Timer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Timer.cpp; sourceTree = "<group>"; };
+		FA4F2B771DE0125B00CA37D7 /* xxhash.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xxhash.c; sourceTree = "<group>"; };
+		FA4F2B781DE0125B00CA37D7 /* xxhash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xxhash.h; sourceTree = "<group>"; };
+		FA4F2BA21DE1E36400CA37D7 /* RecordingDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecordingDevice.cpp; sourceTree = "<group>"; };
+		FA4F2BA31DE1E36400CA37D7 /* RecordingDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecordingDevice.h; sourceTree = "<group>"; };
+		FA4F2BA41DE1E36400CA37D7 /* wrap_RecordingDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_RecordingDevice.cpp; sourceTree = "<group>"; };
+		FA4F2BA51DE1E36400CA37D7 /* wrap_RecordingDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_RecordingDevice.h; sourceTree = "<group>"; };
+		FA4F2BAA1DE1E37000CA37D7 /* RecordingDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecordingDevice.cpp; sourceTree = "<group>"; };
+		FA4F2BAB1DE1E37000CA37D7 /* RecordingDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecordingDevice.h; sourceTree = "<group>"; };
+		FA4F2BAE1DE1E37B00CA37D7 /* RecordingDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecordingDevice.cpp; sourceTree = "<group>"; };
+		FA4F2BAF1DE1E37B00CA37D7 /* RecordingDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecordingDevice.h; sourceTree = "<group>"; };
+		FA4F2BDF1DE6650600CA37D7 /* Transform.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Transform.cpp; sourceTree = "<group>"; };
+		FA4F2BE01DE6650600CA37D7 /* Transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Transform.h; sourceTree = "<group>"; };
+		FA4F2BE11DE6650600CA37D7 /* wrap_Transform.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Transform.cpp; sourceTree = "<group>"; };
+		FA4F2BE21DE6650600CA37D7 /* wrap_Transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Transform.h; sourceTree = "<group>"; };
 		FA56D9BA1C2089EE00D8D3C7 /* libmodplug.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libmodplug.a; sourceTree = "<group>"; };
 		FA577A6716C719D900860150 /* FreeType.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FreeType.framework; path = /Library/Frameworks/FreeType.framework; sourceTree = "<absolute>"; };
 		FA577A6D16C719EA00860150 /* Lua.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Lua.framework; path = /Library/Frameworks/Lua.framework; sourceTree = "<absolute>"; };
@@ -1783,6 +1836,7 @@
 				FA1557BE1CE90A2C00AFF582 /* tinyexr */,
 				FA0B7A191A958EA3000E1D17 /* utf8 */,
 				FA0B7A1F1A958EA3000E1D17 /* Wuff */,
+				FA4F2B761DE0125B00CA37D7 /* xxHash */,
 			);
 			name = libraries;
 			path = ../../src/libraries;
@@ -2195,10 +2249,14 @@
 				FA0B7B3F1A95902C000E1D17 /* Audio.h */,
 				FA0B7B401A95902C000E1D17 /* null */,
 				FA0B7B451A95902C000E1D17 /* openal */,
+				FA4F2BA21DE1E36400CA37D7 /* RecordingDevice.cpp */,
+				FA4F2BA31DE1E36400CA37D7 /* RecordingDevice.h */,
 				FA0B7B4C1A95902C000E1D17 /* Source.cpp */,
 				FA0B7B4D1A95902C000E1D17 /* Source.h */,
 				FA0B7B4E1A95902C000E1D17 /* wrap_Audio.cpp */,
 				FA0B7B4F1A95902C000E1D17 /* wrap_Audio.h */,
+				FA4F2BA41DE1E36400CA37D7 /* wrap_RecordingDevice.cpp */,
+				FA4F2BA51DE1E36400CA37D7 /* wrap_RecordingDevice.h */,
 				FA0B7B501A95902C000E1D17 /* wrap_Source.cpp */,
 				FA0B7B511A95902C000E1D17 /* wrap_Source.h */,
 			);
@@ -2210,6 +2268,8 @@
 			children = (
 				FA0B7B411A95902C000E1D17 /* Audio.cpp */,
 				FA0B7B421A95902C000E1D17 /* Audio.h */,
+				FA4F2BAA1DE1E37000CA37D7 /* RecordingDevice.cpp */,
+				FA4F2BAB1DE1E37000CA37D7 /* RecordingDevice.h */,
 				FA0B7B431A95902C000E1D17 /* Source.cpp */,
 				FA0B7B441A95902C000E1D17 /* Source.h */,
 			);
@@ -2223,6 +2283,8 @@
 				FA0B7B471A95902C000E1D17 /* Audio.h */,
 				FA0B7B481A95902C000E1D17 /* Pool.cpp */,
 				FA0B7B491A95902C000E1D17 /* Pool.h */,
+				FA4F2BAE1DE1E37B00CA37D7 /* RecordingDevice.cpp */,
+				FA4F2BAF1DE1E37B00CA37D7 /* RecordingDevice.h */,
 				FA0B7B4A1A95902C000E1D17 /* Source.cpp */,
 				FA0B7B4B1A95902C000E1D17 /* Source.h */,
 			);
@@ -2525,6 +2587,8 @@
 				FA0B7C041A95902C000E1D17 /* MathModule.h */,
 				FA0B7C051A95902C000E1D17 /* RandomGenerator.cpp */,
 				FA0B7C061A95902C000E1D17 /* RandomGenerator.h */,
+				FA4F2BDF1DE6650600CA37D7 /* Transform.cpp */,
+				FA4F2BE01DE6650600CA37D7 /* Transform.h */,
 				FA0B7C071A95902C000E1D17 /* wrap_BezierCurve.cpp */,
 				FA0B7C081A95902C000E1D17 /* wrap_BezierCurve.h */,
 				FAB17BEE1ABFB37500F9BA27 /* wrap_CompressedData.cpp */,
@@ -2535,6 +2599,8 @@
 				FA0B7C0B1A95902C000E1D17 /* wrap_RandomGenerator.cpp */,
 				FA0B7C0C1A95902C000E1D17 /* wrap_RandomGenerator.h */,
 				FA2E9BFE1C19E00C0004A1EE /* wrap_RandomGenerator.lua */,
+				FA4F2BE11DE6650600CA37D7 /* wrap_Transform.cpp */,
+				FA4F2BE21DE6650600CA37D7 /* wrap_Transform.h */,
 			);
 			path = math;
 			sourceTree = "<group>";
@@ -2890,6 +2956,15 @@
 			path = theora;
 			sourceTree = "<group>";
 		};
+		FA4F2B761DE0125B00CA37D7 /* xxHash */ = {
+			isa = PBXGroup;
+			children = (
+				FA4F2B771DE0125B00CA37D7 /* xxhash.c */,
+				FA4F2B781DE0125B00CA37D7 /* xxhash.h */,
+			);
+			path = xxHash;
+			sourceTree = "<group>";
+		};
 		FA56D9B91C2089CE00D8D3C7 /* modplug */ = {
 			isa = PBXGroup;
 			children = (
@@ -3023,6 +3098,7 @@
 				FA0B7D381A95902C000E1D17 /* Font.h in Headers */,
 				FA0B7EC31A95902C000E1D17 /* threads.h in Headers */,
 				FA0B7AC21A958EA3000E1D17 /* enet.h in Headers */,
+				FA4F2BA71DE1E36400CA37D7 /* RecordingDevice.h in Headers */,
 				FA0B7E201A95902C000E1D17 /* Physics.h in Headers */,
 				217DFBEA1D9F6D490055D849 /* io.h in Headers */,
 				FA0B79221A958E3B000E1D17 /* delay.h in Headers */,
@@ -3147,6 +3223,7 @@
 				FA0B7E6B1A95902C000E1D17 /* wrap_PulleyJoint.h in Headers */,
 				FA0B7E051A95902C000E1D17 /* Contact.h in Headers */,
 				FA0B7A691A958EA3000E1D17 /* b2Island.h in Headers */,
+				FA4F2BE41DE6650600CA37D7 /* Transform.h in Headers */,
 				FA0B7E0E1A95902C000E1D17 /* Fixture.h in Headers */,
 				FA0B7A401A958EA3000E1D17 /* b2ChainShape.h in Headers */,
 				217DFBE81D9F6D490055D849 /* inet.h in Headers */,
@@ -3174,6 +3251,7 @@
 				FA0B7A341A958EA3000E1D17 /* b2Collision.h in Headers */,
 				217DFBDA1D9F6D490055D849 /* auxiliar.h in Headers */,
 				FA0B7EC71A95902C000E1D17 /* ThreadModule.h in Headers */,
+				FA4F2BAD1DE1E37000CA37D7 /* RecordingDevice.h in Headers */,
 				FA0B792B1A958E3B000E1D17 /* Matrix.h in Headers */,
 				FA0B7DA41A95902C000E1D17 /* PKMHandler.h in Headers */,
 				FA0B7AC81A958EA3000E1D17 /* utility.h in Headers */,
@@ -3221,6 +3299,7 @@
 				217DFBF71D9F6D490055D849 /* options.h in Headers */,
 				FA0B7E3B1A95902C000E1D17 /* World.h in Headers */,
 				FA0B7DC31A95902C000E1D17 /* wrap_Joystick.h in Headers */,
+				FA4F2BE61DE6650600CA37D7 /* wrap_Transform.h in Headers */,
 				FA0B7EE71A95902D000E1D17 /* Window.h in Headers */,
 				FA0B7E651A95902C000E1D17 /* wrap_PolygonShape.h in Headers */,
 				FA0B7AC91A958EA3000E1D17 /* win32.h in Headers */,
@@ -3242,6 +3321,7 @@
 				FA0B7D0B1A95902C000E1D17 /* wrap_FileData.h in Headers */,
 				FA0B7DF91A95902C000E1D17 /* Body.h in Headers */,
 				FA0B7DB91A95902C000E1D17 /* Joystick.h in Headers */,
+				FA4F2BB11DE1E37B00CA37D7 /* RecordingDevice.h in Headers */,
 				FA0B7E2C1A95902C000E1D17 /* RevoluteJoint.h in Headers */,
 				FA8951A41AA2EDF300EC385A /* wrap_Event.h in Headers */,
 				FA0B7B2A1A958EA3000E1D17 /* simplexnoise1234.h in Headers */,
@@ -3284,8 +3364,10 @@
 				FA0B79451A958E3B000E1D17 /* Variant.h in Headers */,
 				FA0B7D691A95902C000E1D17 /* wrap_ParticleSystem.h in Headers */,
 				FA0B7E5C1A95902C000E1D17 /* wrap_MotorJoint.h in Headers */,
+				FA4F2BA91DE1E36400CA37D7 /* wrap_RecordingDevice.h in Headers */,
 				FA0B7A7C1A958EA3000E1D17 /* b2Contact.h in Headers */,
 				FA0B7A881A958EA3000E1D17 /* b2PolygonAndCircleContact.h in Headers */,
+				FA4F2B7A1DE0125B00CA37D7 /* xxhash.h in Headers */,
 				FA0B7DDE1A95902C000E1D17 /* wrap_BezierCurve.h in Headers */,
 				FA0B7DED1A95902C000E1D17 /* Cursor.h in Headers */,
 				FA0B7E501A95902C000E1D17 /* wrap_Fixture.h in Headers */,
@@ -3452,7 +3534,9 @@
 				FA0B7EE31A95902D000E1D17 /* Window.cpp in Sources */,
 				FA0B7AAB1A958EA3000E1D17 /* b2WeldJoint.cpp in Sources */,
 				FA0B7EC21A95902C000E1D17 /* threads.cpp in Sources */,
+				FA4F2C051DE936C900CA37D7 /* compat.c in Sources */,
 				FA0B79371A958E3B000E1D17 /* macosx.mm in Sources */,
+				FA4F2BE81DE6651000CA37D7 /* wrap_Transform.cpp in Sources */,
 				FA0B7D011A95902C000E1D17 /* Filesystem.cpp in Sources */,
 				FA0B7ED91A95902D000E1D17 /* wrap_Timer.cpp in Sources */,
 				FA0B7DD11A95902C000E1D17 /* love.cpp in Sources */,
@@ -3464,6 +3548,7 @@
 				FA0B7E6D1A95902C000E1D17 /* wrap_RevoluteJoint.cpp in Sources */,
 				FA0B7A5F1A958EA3000E1D17 /* b2Body.cpp in Sources */,
 				FA0B7E641A95902C000E1D17 /* wrap_PolygonShape.cpp in Sources */,
+				FA4F2C031DE936C200CA37D7 /* auxiliar.c in Sources */,
 				FA0B7E731A95902C000E1D17 /* wrap_Shape.cpp in Sources */,
 				FA0B7CFE1A95902C000E1D17 /* File.cpp in Sources */,
 				FA27B39E1B498151008A9DCE /* Video.cpp in Sources */,
@@ -3481,6 +3566,7 @@
 				FA0B7D0A1A95902C000E1D17 /* wrap_FileData.cpp in Sources */,
 				FA0B7ABE1A958EA3000E1D17 /* compress.c in Sources */,
 				FA0B7CF21A95902C000E1D17 /* DroppedFile.cpp in Sources */,
+				FA4F2C141DE936FE00CA37D7 /* usocket.c in Sources */,
 				FA0B7D8A1A95902C000E1D17 /* CompressedImageData.cpp in Sources */,
 				FA0B7AD21A958EA3000E1D17 /* protocol.c in Sources */,
 				FA0B7D1F1A95902C000E1D17 /* ImageRasterizer.cpp in Sources */,
@@ -3498,12 +3584,15 @@
 				FA0B7E191A95902C000E1D17 /* MotorJoint.cpp in Sources */,
 				FA0B7E4F1A95902C000E1D17 /* wrap_Fixture.cpp in Sources */,
 				FA0B7EBF1A95902C000E1D17 /* Thread.cpp in Sources */,
+				FA4F2C121DE936FE00CA37D7 /* unixtcp.c in Sources */,
 				FA0B7EB91A95902C000E1D17 /* Channel.cpp in Sources */,
 				FA4B66CA1ABBCF1900558F15 /* Timer.cpp in Sources */,
 				FA0B7A3F1A958EA3000E1D17 /* b2ChainShape.cpp in Sources */,
 				FA0B7E921A95902C000E1D17 /* ModPlugDecoder.cpp in Sources */,
 				FA0B7E521A95902C000E1D17 /* wrap_FrictionJoint.cpp in Sources */,
+				FA4F2BB51DE1E4C300CA37D7 /* wrap_RecordingDevice.cpp in Sources */,
 				FA0B7A311A958EA3000E1D17 /* b2CollidePolygon.cpp in Sources */,
+				FA4F2C111DE936FE00CA37D7 /* unix.c in Sources */,
 				FA0B7A931A958EA3000E1D17 /* b2GearJoint.cpp in Sources */,
 				FA0B7E0D1A95902C000E1D17 /* Fixture.cpp in Sources */,
 				FA0B7D191A95902C000E1D17 /* TrueTypeRasterizer.cpp in Sources */,
@@ -3520,6 +3609,7 @@
 				FA0B7ED21A95902C000E1D17 /* wrap_ThreadModule.cpp in Sources */,
 				FA0B7EE01A95902D000E1D17 /* wrap_Touch.cpp in Sources */,
 				FA0B7A3C1A958EA3000E1D17 /* b2TimeOfImpact.cpp in Sources */,
+				FA4F2C081DE936DD00CA37D7 /* io.c in Sources */,
 				FA0B7CDD1A95902C000E1D17 /* Source.cpp in Sources */,
 				FA0B7DC51A95902C000E1D17 /* wrap_JoystickModule.cpp in Sources */,
 				FA0B7E701A95902C000E1D17 /* wrap_RopeJoint.cpp in Sources */,
@@ -3534,6 +3624,7 @@
 				FA0B7D681A95902C000E1D17 /* wrap_ParticleSystem.cpp in Sources */,
 				FA0B7AD51A958EA3000E1D17 /* unix.c in Sources */,
 				FAB17BE71ABFAA9000F9BA27 /* lz4.c in Sources */,
+				FA4F2BE71DE6650D00CA37D7 /* Transform.cpp in Sources */,
 				FA0B7D651A95902C000E1D17 /* wrap_Mesh.cpp in Sources */,
 				FA0B7A531A958EA3000E1D17 /* b2Math.cpp in Sources */,
 				FA0B7CDA1A95902C000E1D17 /* Pool.cpp in Sources */,
@@ -3560,13 +3651,16 @@
 				FA0B7A871A958EA3000E1D17 /* b2PolygonAndCircleContact.cpp in Sources */,
 				FA0B7EF21A959D2C000E1D17 /* ios.mm in Sources */,
 				FA0B7D3A1A95902C000E1D17 /* Graphics.cpp in Sources */,
+				FA4F2BB21DE1E4B400CA37D7 /* RecordingDevice.cpp in Sources */,
 				FA0B7A2D1A958EA3000E1D17 /* b2CollideCircle.cpp in Sources */,
+				FA4F2C0F1DE936FE00CA37D7 /* timeout.c in Sources */,
 				FA59A2D31C06481400328DBA /* ParticleSystem.cpp in Sources */,
 				FA0B7E131A95902C000E1D17 /* GearJoint.cpp in Sources */,
 				FA0B7DC21A95902C000E1D17 /* wrap_Joystick.cpp in Sources */,
 				FA0B7CD41A95902C000E1D17 /* Source.cpp in Sources */,
 				FAA3A9AF1B7D465A00CED060 /* android.cpp in Sources */,
 				FA0B7CD11A95902C000E1D17 /* Audio.cpp in Sources */,
+				FA4F2B7B1DE0181B00CA37D7 /* xxhash.c in Sources */,
 				FA0B7D131A95902C000E1D17 /* Font.cpp in Sources */,
 				FA0B7EC91A95902C000E1D17 /* threads.cpp in Sources */,
 				FA0B7A781A958EA3000E1D17 /* b2CircleContact.cpp in Sources */,
@@ -3591,13 +3685,19 @@
 				FA0B7A7E1A958EA3000E1D17 /* b2ContactSolver.cpp in Sources */,
 				FA0B7D971A95902C000E1D17 /* ImageData.cpp in Sources */,
 				FA0B7E581A95902C000E1D17 /* wrap_Joint.cpp in Sources */,
+				FA4F2C0A1DE936E600CA37D7 /* mime.c in Sources */,
 				FA0B7E311A95902C000E1D17 /* Shape.cpp in Sources */,
 				FA0B7E491A95902C000E1D17 /* wrap_DistanceJoint.cpp in Sources */,
 				FA0B7A391A958EA3000E1D17 /* b2DynamicTree.cpp in Sources */,
+				FA4F2C101DE936FE00CA37D7 /* udp.c in Sources */,
 				FA0B7A681A958EA3000E1D17 /* b2Island.cpp in Sources */,
 				FA0B7E2B1A95902C000E1D17 /* RevoluteJoint.cpp in Sources */,
 				FA0B7B291A958EA3000E1D17 /* simplexnoise1234.cpp in Sources */,
 				FA0B7D261A95902C000E1D17 /* wrap_Font.cpp in Sources */,
+				FA4F2BB31DE1E4B800CA37D7 /* RecordingDevice.cpp in Sources */,
+				FA4F2C071DE936DA00CA37D7 /* inet.c in Sources */,
+				FA4F2C0B1DE936EA00CA37D7 /* options.c in Sources */,
+				FA4F2C0D1DE936F100CA37D7 /* serial.c in Sources */,
 				FA0B7E0A1A95902C000E1D17 /* EdgeShape.cpp in Sources */,
 				FAB17BF11ABFB37500F9BA27 /* wrap_CompressedData.cpp in Sources */,
 				FA0B7CF81A95902C000E1D17 /* FileData.cpp in Sources */,
@@ -3605,6 +3705,7 @@
 				FA0B7E981A95902C000E1D17 /* Sound.cpp in Sources */,
 				FA0B7E371A95902C000E1D17 /* WheelJoint.cpp in Sources */,
 				FA0B7A8A1A958EA3000E1D17 /* b2PolygonContact.cpp in Sources */,
+				FA4F2C0C1DE936ED00CA37D7 /* select.c in Sources */,
 				FA0B7D8E1A95902C000E1D17 /* ddsHandler.cpp in Sources */,
 				FA0B7DFE1A95902C000E1D17 /* ChainShape.cpp in Sources */,
 				FA0B7A451A958EA3000E1D17 /* b2EdgeShape.cpp in Sources */,
@@ -3629,6 +3730,7 @@
 				FA0B7CE31A95902C000E1D17 /* wrap_Audio.cpp in Sources */,
 				FA0B7B381A958EA3000E1D17 /* wuff_internal.c in Sources */,
 				FA0B7DF81A95902C000E1D17 /* Body.cpp in Sources */,
+				FA4F2BB41DE1E4BD00CA37D7 /* RecordingDevice.cpp in Sources */,
 				FA0B7DF51A95902C000E1D17 /* wrap_Mouse.cpp in Sources */,
 				FA0B7D6E1A95902C000E1D17 /* wrap_Shader.cpp in Sources */,
 				FA0B7E861A95902C000E1D17 /* CoreAudioDecoder.cpp in Sources */,
@@ -3647,12 +3749,14 @@
 				FA0B7E1C1A95902C000E1D17 /* MouseJoint.cpp in Sources */,
 				FA0B7CF51A95902C000E1D17 /* File.cpp in Sources */,
 				FA0B7E341A95902C000E1D17 /* WeldJoint.cpp in Sources */,
+				FA4F2C091DE936E200CA37D7 /* luasocket.c in Sources */,
 				FA0B7D5C1A95902C000E1D17 /* wrap_Font.cpp in Sources */,
 				FA0B7B221A958EA3000E1D17 /* luasocket.cpp in Sources */,
 				FA0B7D311A95902C000E1D17 /* Graphics.cpp in Sources */,
 				FA0B7E9E1A95902C000E1D17 /* WaveDecoder.cpp in Sources */,
 				FA0B7EB31A95902C000E1D17 /* System.cpp in Sources */,
 				FA0B7D1C1A95902C000E1D17 /* GlyphData.cpp in Sources */,
+				FA4F2C061DE936CD00CA37D7 /* except.c in Sources */,
 				FA0B7AAE1A958EA3000E1D17 /* b2WheelJoint.cpp in Sources */,
 				FA0B7E671A95902C000E1D17 /* wrap_PrismaticJoint.cpp in Sources */,
 				FA0B7DCE1A95902C000E1D17 /* wrap_Keyboard.cpp in Sources */,
@@ -3668,6 +3772,7 @@
 				FA620A3B1AA305F6005DB4C2 /* types.cpp in Sources */,
 				FA0B7DD41A95902C000E1D17 /* BezierCurve.cpp in Sources */,
 				FA0B7E7C1A95902C000E1D17 /* wrap_World.cpp in Sources */,
+				FA4F2C0E1DE936FE00CA37D7 /* tcp.c in Sources */,
 				FA0B7D911A95902C000E1D17 /* FormatHandler.cpp in Sources */,
 				FA0B7D431A95902C000E1D17 /* OpenGL.cpp in Sources */,
 				FA0B7DBF1A95902C000E1D17 /* JoystickModule.cpp in Sources */,
@@ -3684,6 +3789,7 @@
 				FA0B7E251A95902C000E1D17 /* PrismaticJoint.cpp in Sources */,
 				FA0B7A561A958EA3000E1D17 /* b2Settings.cpp in Sources */,
 				FA0B7E611A95902C000E1D17 /* wrap_Physics.cpp in Sources */,
+				FA4F2C041DE936C600CA37D7 /* buffer.c in Sources */,
 				FA0B7DC81A95902C000E1D17 /* Keyboard.cpp in Sources */,
 				FA4943551D9DDFFE00E8D38A /* HashFunction.cpp in Sources */,
 				FA0B7EAD1A95902C000E1D17 /* wrap_SoundData.cpp in Sources */,
@@ -3695,6 +3801,7 @@
 				FA0B7DE61A95902C000E1D17 /* Cursor.cpp in Sources */,
 				FA0B7EDC1A95902D000E1D17 /* Touch.cpp in Sources */,
 				FA0B7CE91A95902C000E1D17 /* Event.cpp in Sources */,
+				FA4F2C131DE936FE00CA37D7 /* unixudp.c in Sources */,
 				FA0B7A961A958EA3000E1D17 /* b2Joint.cpp in Sources */,
 				FA0B7A621A958EA3000E1D17 /* b2ContactManager.cpp in Sources */,
 				FA0B7A2F1A958EA3000E1D17 /* b2CollideEdge.cpp in Sources */,
@@ -3750,7 +3857,9 @@
 				FA0B7E631A95902C000E1D17 /* wrap_PolygonShape.cpp in Sources */,
 				FA0B7E721A95902C000E1D17 /* wrap_Shape.cpp in Sources */,
 				FA0B7CFD1A95902C000E1D17 /* File.cpp in Sources */,
+				FA4F2BB01DE1E37B00CA37D7 /* RecordingDevice.cpp in Sources */,
 				FA27B39D1B498151008A9DCE /* Video.cpp in Sources */,
+				FA4F2BAC1DE1E37000CA37D7 /* RecordingDevice.cpp in Sources */,
 				FA0B7A2E1A958EA3000E1D17 /* b2CollideEdge.cpp in Sources */,
 				FA0B7E691A95902C000E1D17 /* wrap_PulleyJoint.cpp in Sources */,
 				FA0B7DB71A95902C000E1D17 /* Joystick.cpp in Sources */,
@@ -3806,6 +3915,7 @@
 				FA0B7EDF1A95902D000E1D17 /* wrap_Touch.cpp in Sources */,
 				FA0B794A1A958E3B000E1D17 /* wrap_Data.cpp in Sources */,
 				217DFBFB1D9F6D490055D849 /* serial.c in Sources */,
+				FA4F2BE51DE6650600CA37D7 /* wrap_Transform.cpp in Sources */,
 				217DFC0D1D9F6D490055D849 /* unixudp.c in Sources */,
 				FA0B7CDC1A95902C000E1D17 /* Source.cpp in Sources */,
 				FA0B7DC41A95902C000E1D17 /* wrap_JoystickModule.cpp in Sources */,
@@ -3894,6 +4004,7 @@
 				FA0B7DA51A95902C000E1D17 /* PNGHandler.cpp in Sources */,
 				FA0B7B371A958EA3000E1D17 /* wuff_internal.c in Sources */,
 				FA0B7E971A95902C000E1D17 /* Sound.cpp in Sources */,
+				FA4F2B791DE0125B00CA37D7 /* xxhash.c in Sources */,
 				FA0B7E361A95902C000E1D17 /* WheelJoint.cpp in Sources */,
 				FA0B7A471A958EA3000E1D17 /* b2PolygonShape.cpp in Sources */,
 				FA0B7D8D1A95902C000E1D17 /* ddsHandler.cpp in Sources */,
@@ -3906,6 +4017,7 @@
 				FA0B7A981A958EA3000E1D17 /* b2MotorJoint.cpp in Sources */,
 				FA0B7E061A95902C000E1D17 /* DistanceJoint.cpp in Sources */,
 				FA620A321AA2F8DB005DB4C2 /* wrap_Quad.cpp in Sources */,
+				FA4F2BA61DE1E36400CA37D7 /* RecordingDevice.cpp in Sources */,
 				FA0B793B1A958E3B000E1D17 /* runtime.cpp in Sources */,
 				FA0B7E5D1A95902C000E1D17 /* wrap_MouseJoint.cpp in Sources */,
 				FA0B7A741A958EA3000E1D17 /* b2ChainAndPolygonContact.cpp in Sources */,
@@ -3988,6 +4100,7 @@
 				FA0B7CDF1A95902C000E1D17 /* Source.cpp in Sources */,
 				FA0B7ECE1A95902C000E1D17 /* wrap_LuaThread.cpp in Sources */,
 				FA0B79431A958E3B000E1D17 /* Variant.cpp in Sources */,
+				FA4F2BE31DE6650600CA37D7 /* Transform.cpp in Sources */,
 				FA0B7EA01A95902C000E1D17 /* Sound.cpp in Sources */,
 				FA0B7DE51A95902C000E1D17 /* Cursor.cpp in Sources */,
 				FA0B7EDB1A95902D000E1D17 /* Touch.cpp in Sources */,
@@ -3998,6 +4111,7 @@
 				FA0B7A891A958EA3000E1D17 /* b2PolygonContact.cpp in Sources */,
 				FA0B7E1E1A95902C000E1D17 /* Physics.cpp in Sources */,
 				FA0B7E811A95902C000E1D17 /* Shape.cpp in Sources */,
+				FA4F2BA81DE1E36400CA37D7 /* wrap_RecordingDevice.cpp in Sources */,
 				FA0B7B251A958EA3000E1D17 /* lutf8lib.c in Sources */,
 				FA0B7ED41A95902D000E1D17 /* Timer.cpp in Sources */,
 				FA19C4C51B4B0BD50059B0B3 /* wrap_Video.cpp in Sources */,
@@ -4323,6 +4437,7 @@
 				DYLIB_CURRENT_VERSION = 9.0;
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
+					"$(PROJECT_DIR)/macosx/Frameworks",
 					/Library/Frameworks,
 					"$(LOCAL_LIBRARY_DIR)/Frameworks",
 				);
@@ -4330,6 +4445,9 @@
 				GCC_ENABLE_OBJC_EXCEPTIONS = YES;
 				HEADER_SEARCH_PATHS = (
 					"$(inherited)",
+					"$(PROJECT_DIR)/macosx/Frameworks/FreeType.framework/Headers",
+					"$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers",
+					"$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers",
 					/Library/Frameworks/FreeType.framework/Headers,
 					/Library/Frameworks/Lua.framework/Headers,
 					/Library/Frameworks/SDL2.framework/Headers,
@@ -4357,6 +4475,7 @@
 				DYLIB_CURRENT_VERSION = 9.0;
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
+					"$(PROJECT_DIR)/macosx/Frameworks",
 					/Library/Frameworks,
 					"$(LOCAL_LIBRARY_DIR)/Frameworks",
 				);
@@ -4364,6 +4483,9 @@
 				GCC_ENABLE_OBJC_EXCEPTIONS = YES;
 				HEADER_SEARCH_PATHS = (
 					"$(inherited)",
+					"$(PROJECT_DIR)/macosx/Frameworks/FreeType.framework/Headers",
+					"$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers",
+					"$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers",
 					/Library/Frameworks/FreeType.framework/Headers,
 					/Library/Frameworks/Lua.framework/Headers,
 					/Library/Frameworks/SDL2.framework/Headers,
@@ -4392,6 +4514,7 @@
 				DYLIB_CURRENT_VERSION = 9.0;
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
+					"$(PROJECT_DIR)/macosx/Frameworks",
 					/Library/Frameworks,
 					"$(LOCAL_LIBRARY_DIR)/Frameworks",
 				);
@@ -4399,6 +4522,9 @@
 				GCC_ENABLE_OBJC_EXCEPTIONS = YES;
 				HEADER_SEARCH_PATHS = (
 					"$(inherited)",
+					"$(PROJECT_DIR)/macosx/Frameworks/FreeType.framework/Headers",
+					"$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers",
+					"$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers",
 					/Library/Frameworks/FreeType.framework/Headers,
 					/Library/Frameworks/Lua.framework/Headers,
 					/Library/Frameworks/SDL2.framework/Headers,

+ 9 - 0
platform/xcode/love.xcodeproj/project.pbxproj

@@ -430,6 +430,7 @@
 				COMBINE_HIDPI_IMAGES = YES;
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
+					"$(PROJECT_DIR)/macosx/Frameworks",
 					/Library/Frameworks,
 					"\"$(SRCROOT)/build/Release\"",
 					"\"$(SRCROOT)/build/Debug\"",
@@ -439,6 +440,8 @@
 				GCC_OPTIMIZATION_LEVEL = 0;
 				HEADER_SEARCH_PATHS = (
 					"$(inherited)",
+					"$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers",
+					"$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers",
 					/Library/Frameworks/Lua.framework/Headers,
 					/Library/Frameworks/SDL2.framework/Headers,
 				);
@@ -458,6 +461,7 @@
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
+					"$(PROJECT_DIR)/macosx/Frameworks",
 					/Library/Frameworks,
 					"\"$(SRCROOT)/build/Release\"",
 					"\"$(SRCROOT)/build/Debug\"",
@@ -465,6 +469,8 @@
 				);
 				HEADER_SEARCH_PATHS = (
 					"$(inherited)",
+					"$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers",
+					"$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers",
 					/Library/Frameworks/Lua.framework/Headers,
 					/Library/Frameworks/SDL2.framework/Headers,
 				);
@@ -841,6 +847,7 @@
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
+					"$(PROJECT_DIR)/macosx/Frameworks",
 					/Library/Frameworks,
 					"\"$(SRCROOT)/build/Release\"",
 					"\"$(SRCROOT)/build/Debug\"",
@@ -848,6 +855,8 @@
 				);
 				HEADER_SEARCH_PATHS = (
 					"$(inherited)",
+					"$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers",
+					"$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers",
 					/Library/Frameworks/Lua.framework/Headers,
 					/Library/Frameworks/SDL2.framework/Headers,
 				);

+ 12 - 0
src/common/Matrix.cpp

@@ -73,6 +73,12 @@ Matrix4::Matrix4()
 {
 	setIdentity();
 }
+
+
+Matrix4::Matrix4(const float elements[16])
+{
+	memcpy(e, elements, sizeof(float) * 16);
+}
 	
 Matrix4::Matrix4(float t00, float t10, float t01, float t11, float x, float y)
 {
@@ -143,6 +149,12 @@ void Matrix4::setShear(float kx, float ky)
 	e[1] = ky;
 	e[4] = kx;
 }
+
+void Matrix4::getApproximateScale(float &sx, float &sy) const
+{
+	sx = sqrtf(e[0] * e[0] + e[4] * e[4]);
+	sy = sqrtf(e[1] * e[1] + e[5] * e[5]);
+}
 	
 void Matrix4::setRawTransformation(float t00, float t10, float t01, float t11, float x, float y)
 {

+ 15 - 0
src/common/Matrix.h

@@ -52,6 +52,12 @@ public:
 	 **/
 	Matrix4(float t00, float t10, float t01, float t11, float x, float y);
 
+	/**
+	 * Creates a new matrix from the specified elements. Be sure to pass
+	 * exactly 16 elements in!
+	 **/
+	Matrix4(const float elements[16]);
+
 	/**
 	 * Creates a new matrix set to a transformation.
 	 **/
@@ -112,6 +118,12 @@ public:
 	 * @param ky Shear along y-axis.
 	 **/
 	void setShear(float kx, float ky);
+
+	/**
+	 * Calculates the scale factors for a 2D affine transform. The output values
+	 * are absolute (not signed).
+	 **/
+	void getApproximateScale(float &sx, float &sy) const;
 	
 	/**
 	 * Sets a transformation's values directly. Useful if you want to modify them inplace,
@@ -182,6 +194,9 @@ public:
 	template <typename V>
 	void transform(V *dst, const V *src, int size) const;
 
+	/**
+	 * Computes and returns the inverse of the matrix.
+	 **/
 	Matrix4 inverse() const;
 
 	/**

+ 889 - 0
src/libraries/xxHash/xxhash.c

@@ -0,0 +1,889 @@
+/*
+*  xxHash - Fast Hash algorithm
+*  Copyright (C) 2012-2016, Yann Collet
+*
+*  BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
+*
+*  Redistribution and use in source and binary forms, with or without
+*  modification, are permitted provided that the following conditions are
+*  met:
+*
+*  * Redistributions of source code must retain the above copyright
+*  notice, this list of conditions and the following disclaimer.
+*  * Redistributions in binary form must reproduce the above
+*  copyright notice, this list of conditions and the following disclaimer
+*  in the documentation and/or other materials provided with the
+*  distribution.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+*  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+*  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+*  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+*  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+*  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+*  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+*  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+*  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+*  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+*  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*
+*  You can contact the author at :
+*  - xxHash homepage: http://www.xxhash.com
+*  - xxHash source repository : https://github.com/Cyan4973/xxHash
+*/
+
+
+/* *************************************
+*  Tuning parameters
+***************************************/
+/*!XXH_FORCE_MEMORY_ACCESS :
+ * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable.
+ * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal.
+ * The below switch allow to select different access method for improved performance.
+ * Method 0 (default) : use `memcpy()`. Safe and portable.
+ * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable).
+ *            This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`.
+ * Method 2 : direct access. This method doesn't depend on compiler but violate C standard.
+ *            It can generate buggy code on targets which do not support unaligned memory accesses.
+ *            But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6)
+ * See http://stackoverflow.com/a/32095106/646947 for details.
+ * Prefer these methods in priority order (0 > 1 > 2)
+ */
+#ifndef XXH_FORCE_MEMORY_ACCESS   /* can be defined externally, on command line for example */
+#  if defined(__GNUC__) && ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) )
+#    define XXH_FORCE_MEMORY_ACCESS 2
+#  elif defined(__INTEL_COMPILER) || \
+  (defined(__GNUC__) && ( defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) ))
+#    define XXH_FORCE_MEMORY_ACCESS 1
+#  endif
+#endif
+
+/*!XXH_ACCEPT_NULL_INPUT_POINTER :
+ * If the input pointer is a null pointer, xxHash default behavior is to trigger a memory access error, since it is a bad pointer.
+ * When this option is enabled, xxHash output for null input pointers will be the same as a null-length input.
+ * By default, this option is disabled. To enable it, uncomment below define :
+ */
+/* #define XXH_ACCEPT_NULL_INPUT_POINTER 1 */
+
+/*!XXH_FORCE_NATIVE_FORMAT :
+ * By default, xxHash library provides endian-independent Hash values, based on little-endian convention.
+ * Results are therefore identical for little-endian and big-endian CPU.
+ * This comes at a performance cost for big-endian CPU, since some swapping is required to emulate little-endian format.
+ * Should endian-independence be of no importance for your application, you may set the #define below to 1,
+ * to improve speed for Big-endian CPU.
+ * This option has no impact on Little_Endian CPU.
+ */
+#ifndef XXH_FORCE_NATIVE_FORMAT   /* can be defined externally */
+#  define XXH_FORCE_NATIVE_FORMAT 0
+#endif
+
+/*!XXH_FORCE_ALIGN_CHECK :
+ * This is a minor performance trick, only useful with lots of very small keys.
+ * It means : check for aligned/unaligned input.
+ * The check costs one initial branch per hash; set to 0 when the input data
+ * is guaranteed to be aligned.
+ */
+#ifndef XXH_FORCE_ALIGN_CHECK /* can be defined externally */
+#  if defined(__i386) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64)
+#    define XXH_FORCE_ALIGN_CHECK 0
+#  else
+#    define XXH_FORCE_ALIGN_CHECK 1
+#  endif
+#endif
+
+
+/* *************************************
+*  Includes & Memory related functions
+***************************************/
+/*! Modify the local functions below should you wish to use some other memory routines
+*   for malloc(), free() */
+#include <stdlib.h>
+static void* XXH_malloc(size_t s) { return malloc(s); }
+static void  XXH_free  (void* p)  { free(p); }
+/*! and for memcpy() */
+#include <string.h>
+static void* XXH_memcpy(void* dest, const void* src, size_t size) { return memcpy(dest,src,size); }
+
+#define XXH_STATIC_LINKING_ONLY
+#include "xxhash.h"
+
+
+/* *************************************
+*  Compiler Specific Options
+***************************************/
+#ifdef _MSC_VER    /* Visual Studio */
+#  pragma warning(disable : 4127)      /* disable: C4127: conditional expression is constant */
+#  define FORCE_INLINE static __forceinline
+#else
+#  if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L   /* C99 */
+#    ifdef __GNUC__
+#      define FORCE_INLINE static inline __attribute__((always_inline))
+#    else
+#      define FORCE_INLINE static inline
+#    endif
+#  else
+#    define FORCE_INLINE static
+#  endif /* __STDC_VERSION__ */
+#endif
+
+
+/* *************************************
+*  Basic Types
+***************************************/
+#ifndef MEM_MODULE
+# if !defined (__VMS) && (defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )
+#   include <stdint.h>
+    typedef uint8_t  BYTE;
+    typedef uint16_t U16;
+    typedef uint32_t U32;
+    typedef  int32_t S32;
+# else
+    typedef unsigned char      BYTE;
+    typedef unsigned short     U16;
+    typedef unsigned int       U32;
+    typedef   signed int       S32;
+# endif
+#endif
+
+#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2))
+
+/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */
+static U32 XXH_read32(const void* memPtr) { return *(const U32*) memPtr; }
+
+#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1))
+
+/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */
+/* currently only defined for gcc and icc */
+typedef union { U32 u32; } __attribute__((packed)) unalign;
+static U32 XXH_read32(const void* ptr) { return ((const unalign*)ptr)->u32; }
+
+#else
+
+/* portable and safe solution. Generally efficient.
+ * see : http://stackoverflow.com/a/32095106/646947
+ */
+static U32 XXH_read32(const void* memPtr)
+{
+    U32 val;
+    memcpy(&val, memPtr, sizeof(val));
+    return val;
+}
+
+#endif   /* XXH_FORCE_DIRECT_MEMORY_ACCESS */
+
+
+/* ****************************************
+*  Compiler-specific Functions and Macros
+******************************************/
+#define XXH_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
+
+/* Note : although _rotl exists for minGW (GCC under windows), performance seems poor */
+#if defined(_MSC_VER)
+#  define XXH_rotl32(x,r) _rotl(x,r)
+#  define XXH_rotl64(x,r) _rotl64(x,r)
+#else
+#  define XXH_rotl32(x,r) ((x << r) | (x >> (32 - r)))
+#  define XXH_rotl64(x,r) ((x << r) | (x >> (64 - r)))
+#endif
+
+#if defined(_MSC_VER)     /* Visual Studio */
+#  define XXH_swap32 _byteswap_ulong
+#elif XXH_GCC_VERSION >= 403
+#  define XXH_swap32 __builtin_bswap32
+#else
+static U32 XXH_swap32 (U32 x)
+{
+    return  ((x << 24) & 0xff000000 ) |
+            ((x <<  8) & 0x00ff0000 ) |
+            ((x >>  8) & 0x0000ff00 ) |
+            ((x >> 24) & 0x000000ff );
+}
+#endif
+
+
+/* *************************************
+*  Architecture Macros
+***************************************/
+typedef enum { XXH_bigEndian=0, XXH_littleEndian=1 } XXH_endianess;
+
+/* XXH_CPU_LITTLE_ENDIAN can be defined externally, for example on the compiler command line */
+#ifndef XXH_CPU_LITTLE_ENDIAN
+    static const int g_one = 1;
+#   define XXH_CPU_LITTLE_ENDIAN   (*(const char*)(&g_one))
+#endif
+
+
+/* ***************************
+*  Memory reads
+*****************************/
+typedef enum { XXH_aligned, XXH_unaligned } XXH_alignment;
+
+FORCE_INLINE U32 XXH_readLE32_align(const void* ptr, XXH_endianess endian, XXH_alignment align)
+{
+    if (align==XXH_unaligned)
+        return endian==XXH_littleEndian ? XXH_read32(ptr) : XXH_swap32(XXH_read32(ptr));
+    else
+        return endian==XXH_littleEndian ? *(const U32*)ptr : XXH_swap32(*(const U32*)ptr);
+}
+
+FORCE_INLINE U32 XXH_readLE32(const void* ptr, XXH_endianess endian)
+{
+    return XXH_readLE32_align(ptr, endian, XXH_unaligned);
+}
+
+static U32 XXH_readBE32(const void* ptr)
+{
+    return XXH_CPU_LITTLE_ENDIAN ? XXH_swap32(XXH_read32(ptr)) : XXH_read32(ptr);
+}
+
+
+/* *************************************
+*  Macros
+***************************************/
+#define XXH_STATIC_ASSERT(c)   { enum { XXH_static_assert = 1/(int)(!!(c)) }; }    /* use only *after* variable declarations */
+XXH_PUBLIC_API unsigned XXH_versionNumber (void) { return XXH_VERSION_NUMBER; }
+
+
+/* *******************************************************************
+*  32-bits hash functions
+*********************************************************************/
+static const U32 PRIME32_1 = 2654435761U;
+static const U32 PRIME32_2 = 2246822519U;
+static const U32 PRIME32_3 = 3266489917U;
+static const U32 PRIME32_4 =  668265263U;
+static const U32 PRIME32_5 =  374761393U;
+
+static U32 XXH32_round(U32 seed, U32 input)
+{
+    seed += input * PRIME32_2;
+    seed  = XXH_rotl32(seed, 13);
+    seed *= PRIME32_1;
+    return seed;
+}
+
+FORCE_INLINE U32 XXH32_endian_align(const void* input, size_t len, U32 seed, XXH_endianess endian, XXH_alignment align)
+{
+    const BYTE* p = (const BYTE*)input;
+    const BYTE* bEnd = p + len;
+    U32 h32;
+#define XXH_get32bits(p) XXH_readLE32_align(p, endian, align)
+
+#ifdef XXH_ACCEPT_NULL_INPUT_POINTER
+    if (p==NULL) {
+        len=0;
+        bEnd=p=(const BYTE*)(size_t)16;
+    }
+#endif
+
+    if (len>=16) {
+        const BYTE* const limit = bEnd - 16;
+        U32 v1 = seed + PRIME32_1 + PRIME32_2;
+        U32 v2 = seed + PRIME32_2;
+        U32 v3 = seed + 0;
+        U32 v4 = seed - PRIME32_1;
+
+        do {
+            v1 = XXH32_round(v1, XXH_get32bits(p)); p+=4;
+            v2 = XXH32_round(v2, XXH_get32bits(p)); p+=4;
+            v3 = XXH32_round(v3, XXH_get32bits(p)); p+=4;
+            v4 = XXH32_round(v4, XXH_get32bits(p)); p+=4;
+        } while (p<=limit);
+
+        h32 = XXH_rotl32(v1, 1) + XXH_rotl32(v2, 7) + XXH_rotl32(v3, 12) + XXH_rotl32(v4, 18);
+    } else {
+        h32  = seed + PRIME32_5;
+    }
+
+    h32 += (U32) len;
+
+    while (p+4<=bEnd) {
+        h32 += XXH_get32bits(p) * PRIME32_3;
+        h32  = XXH_rotl32(h32, 17) * PRIME32_4 ;
+        p+=4;
+    }
+
+    while (p<bEnd) {
+        h32 += (*p) * PRIME32_5;
+        h32 = XXH_rotl32(h32, 11) * PRIME32_1 ;
+        p++;
+    }
+
+    h32 ^= h32 >> 15;
+    h32 *= PRIME32_2;
+    h32 ^= h32 >> 13;
+    h32 *= PRIME32_3;
+    h32 ^= h32 >> 16;
+
+    return h32;
+}
+
+
+XXH_PUBLIC_API unsigned int XXH32 (const void* input, size_t len, unsigned int seed)
+{
+#if 0
+    /* Simple version, good for code maintenance, but unfortunately slow for small inputs */
+    XXH32_state_t state;
+    XXH32_reset(&state, seed);
+    XXH32_update(&state, input, len);
+    return XXH32_digest(&state);
+#else
+    XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;
+
+    if (XXH_FORCE_ALIGN_CHECK) {
+        if ((((size_t)input) & 3) == 0) {   /* Input is 4-bytes aligned, leverage the speed benefit */
+            if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)
+                return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned);
+            else
+                return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned);
+    }   }
+
+    if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)
+        return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned);
+    else
+        return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned);
+#endif
+}
+
+
+
+/*======   Hash streaming   ======*/
+
+XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void)
+{
+    return (XXH32_state_t*)XXH_malloc(sizeof(XXH32_state_t));
+}
+XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr)
+{
+    XXH_free(statePtr);
+    return XXH_OK;
+}
+
+XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dstState, const XXH32_state_t* srcState)
+{
+    memcpy(dstState, srcState, sizeof(*dstState));
+}
+
+XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, unsigned int seed)
+{
+    XXH32_state_t state;   /* using a local state to memcpy() in order to avoid strict-aliasing warnings */
+    memset(&state, 0, sizeof(state)-4);   /* do not write into reserved, for future removal */
+    state.v1 = seed + PRIME32_1 + PRIME32_2;
+    state.v2 = seed + PRIME32_2;
+    state.v3 = seed + 0;
+    state.v4 = seed - PRIME32_1;
+    memcpy(statePtr, &state, sizeof(state));
+    return XXH_OK;
+}
+
+
+FORCE_INLINE XXH_errorcode XXH32_update_endian (XXH32_state_t* state, const void* input, size_t len, XXH_endianess endian)
+{
+    const BYTE* p = (const BYTE*)input;
+    const BYTE* const bEnd = p + len;
+
+#ifdef XXH_ACCEPT_NULL_INPUT_POINTER
+    if (input==NULL) return XXH_ERROR;
+#endif
+
+    state->total_len_32 += (unsigned)len;
+    state->large_len |= (len>=16) | (state->total_len_32>=16);
+
+    if (state->memsize + len < 16)  {   /* fill in tmp buffer */
+        XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, len);
+        state->memsize += (unsigned)len;
+        return XXH_OK;
+    }
+
+    if (state->memsize) {   /* some data left from previous update */
+        XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, 16-state->memsize);
+        {   const U32* p32 = state->mem32;
+            state->v1 = XXH32_round(state->v1, XXH_readLE32(p32, endian)); p32++;
+            state->v2 = XXH32_round(state->v2, XXH_readLE32(p32, endian)); p32++;
+            state->v3 = XXH32_round(state->v3, XXH_readLE32(p32, endian)); p32++;
+            state->v4 = XXH32_round(state->v4, XXH_readLE32(p32, endian)); p32++;
+        }
+        p += 16-state->memsize;
+        state->memsize = 0;
+    }
+
+    if (p <= bEnd-16) {
+        const BYTE* const limit = bEnd - 16;
+        U32 v1 = state->v1;
+        U32 v2 = state->v2;
+        U32 v3 = state->v3;
+        U32 v4 = state->v4;
+
+        do {
+            v1 = XXH32_round(v1, XXH_readLE32(p, endian)); p+=4;
+            v2 = XXH32_round(v2, XXH_readLE32(p, endian)); p+=4;
+            v3 = XXH32_round(v3, XXH_readLE32(p, endian)); p+=4;
+            v4 = XXH32_round(v4, XXH_readLE32(p, endian)); p+=4;
+        } while (p<=limit);
+
+        state->v1 = v1;
+        state->v2 = v2;
+        state->v3 = v3;
+        state->v4 = v4;
+    }
+
+    if (p < bEnd) {
+        XXH_memcpy(state->mem32, p, (size_t)(bEnd-p));
+        state->memsize = (unsigned)(bEnd-p);
+    }
+
+    return XXH_OK;
+}
+
+XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* state_in, const void* input, size_t len)
+{
+    XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;
+
+    if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)
+        return XXH32_update_endian(state_in, input, len, XXH_littleEndian);
+    else
+        return XXH32_update_endian(state_in, input, len, XXH_bigEndian);
+}
+
+
+
+FORCE_INLINE U32 XXH32_digest_endian (const XXH32_state_t* state, XXH_endianess endian)
+{
+    const BYTE * p = (const BYTE*)state->mem32;
+    const BYTE* const bEnd = (const BYTE*)(state->mem32) + state->memsize;
+    U32 h32;
+
+    if (state->large_len) {
+        h32 = XXH_rotl32(state->v1, 1) + XXH_rotl32(state->v2, 7) + XXH_rotl32(state->v3, 12) + XXH_rotl32(state->v4, 18);
+    } else {
+        h32 = state->v3 /* == seed */ + PRIME32_5;
+    }
+
+    h32 += state->total_len_32;
+
+    while (p+4<=bEnd) {
+        h32 += XXH_readLE32(p, endian) * PRIME32_3;
+        h32  = XXH_rotl32(h32, 17) * PRIME32_4;
+        p+=4;
+    }
+
+    while (p<bEnd) {
+        h32 += (*p) * PRIME32_5;
+        h32  = XXH_rotl32(h32, 11) * PRIME32_1;
+        p++;
+    }
+
+    h32 ^= h32 >> 15;
+    h32 *= PRIME32_2;
+    h32 ^= h32 >> 13;
+    h32 *= PRIME32_3;
+    h32 ^= h32 >> 16;
+
+    return h32;
+}
+
+
+XXH_PUBLIC_API unsigned int XXH32_digest (const XXH32_state_t* state_in)
+{
+    XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;
+
+    if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)
+        return XXH32_digest_endian(state_in, XXH_littleEndian);
+    else
+        return XXH32_digest_endian(state_in, XXH_bigEndian);
+}
+
+
+/*======   Canonical representation   ======*/
+
+/*! Default XXH result types are basic unsigned 32 and 64 bits.
+*   The canonical representation follows human-readable write convention, aka big-endian (large digits first).
+*   These functions allow transformation of hash result into and from its canonical format.
+*   This way, hash values can be written into a file or buffer, and remain comparable across different systems and programs.
+*/
+
+XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash)
+{
+    XXH_STATIC_ASSERT(sizeof(XXH32_canonical_t) == sizeof(XXH32_hash_t));
+    if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap32(hash);
+    memcpy(dst, &hash, sizeof(*dst));
+}
+
+XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src)
+{
+    return XXH_readBE32(src);
+}
+
+
+#ifndef XXH_NO_LONG_LONG
+
+/* *******************************************************************
+*  64-bits hash functions
+*********************************************************************/
+
+/*======   Memory access   ======*/
+
+#ifndef MEM_MODULE
+# define MEM_MODULE
+# if !defined (__VMS) && (defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )
+#   include <stdint.h>
+    typedef uint64_t U64;
+# else
+    typedef unsigned long long U64;   /* if your compiler doesn't support unsigned long long, replace by another 64-bit type here. Note that xxhash.h will also need to be updated. */
+# endif
+#endif
+
+
+#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2))
+
+/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */
+static U64 XXH_read64(const void* memPtr) { return *(const U64*) memPtr; }
+
+#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1))
+
+/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */
+/* currently only defined for gcc and icc */
+typedef union { U32 u32; U64 u64; } __attribute__((packed)) unalign64;
+static U64 XXH_read64(const void* ptr) { return ((const unalign64*)ptr)->u64; }
+
+#else
+
+/* portable and safe solution. Generally efficient.
+ * see : http://stackoverflow.com/a/32095106/646947
+ */
+
+static U64 XXH_read64(const void* memPtr)
+{
+    U64 val;
+    memcpy(&val, memPtr, sizeof(val));
+    return val;
+}
+
+#endif   /* XXH_FORCE_DIRECT_MEMORY_ACCESS */
+
+#if defined(_MSC_VER)     /* Visual Studio */
+#  define XXH_swap64 _byteswap_uint64
+#elif XXH_GCC_VERSION >= 403
+#  define XXH_swap64 __builtin_bswap64
+#else
+static U64 XXH_swap64 (U64 x)
+{
+    return  ((x << 56) & 0xff00000000000000ULL) |
+            ((x << 40) & 0x00ff000000000000ULL) |
+            ((x << 24) & 0x0000ff0000000000ULL) |
+            ((x << 8)  & 0x000000ff00000000ULL) |
+            ((x >> 8)  & 0x00000000ff000000ULL) |
+            ((x >> 24) & 0x0000000000ff0000ULL) |
+            ((x >> 40) & 0x000000000000ff00ULL) |
+            ((x >> 56) & 0x00000000000000ffULL);
+}
+#endif
+
+FORCE_INLINE U64 XXH_readLE64_align(const void* ptr, XXH_endianess endian, XXH_alignment align)
+{
+    if (align==XXH_unaligned)
+        return endian==XXH_littleEndian ? XXH_read64(ptr) : XXH_swap64(XXH_read64(ptr));
+    else
+        return endian==XXH_littleEndian ? *(const U64*)ptr : XXH_swap64(*(const U64*)ptr);
+}
+
+FORCE_INLINE U64 XXH_readLE64(const void* ptr, XXH_endianess endian)
+{
+    return XXH_readLE64_align(ptr, endian, XXH_unaligned);
+}
+
+static U64 XXH_readBE64(const void* ptr)
+{
+    return XXH_CPU_LITTLE_ENDIAN ? XXH_swap64(XXH_read64(ptr)) : XXH_read64(ptr);
+}
+
+
+/*======   xxh64   ======*/
+
+static const U64 PRIME64_1 = 11400714785074694791ULL;
+static const U64 PRIME64_2 = 14029467366897019727ULL;
+static const U64 PRIME64_3 =  1609587929392839161ULL;
+static const U64 PRIME64_4 =  9650029242287828579ULL;
+static const U64 PRIME64_5 =  2870177450012600261ULL;
+
+static U64 XXH64_round(U64 acc, U64 input)
+{
+    acc += input * PRIME64_2;
+    acc  = XXH_rotl64(acc, 31);
+    acc *= PRIME64_1;
+    return acc;
+}
+
+static U64 XXH64_mergeRound(U64 acc, U64 val)
+{
+    val  = XXH64_round(0, val);
+    acc ^= val;
+    acc  = acc * PRIME64_1 + PRIME64_4;
+    return acc;
+}
+
+FORCE_INLINE U64 XXH64_endian_align(const void* input, size_t len, U64 seed, XXH_endianess endian, XXH_alignment align)
+{
+    const BYTE* p = (const BYTE*)input;
+    const BYTE* bEnd = p + len;
+    U64 h64;
+#define XXH_get64bits(p) XXH_readLE64_align(p, endian, align)
+
+#ifdef XXH_ACCEPT_NULL_INPUT_POINTER
+    if (p==NULL) {
+        len=0;
+        bEnd=p=(const BYTE*)(size_t)32;
+    }
+#endif
+
+    if (len>=32) {
+        const BYTE* const limit = bEnd - 32;
+        U64 v1 = seed + PRIME64_1 + PRIME64_2;
+        U64 v2 = seed + PRIME64_2;
+        U64 v3 = seed + 0;
+        U64 v4 = seed - PRIME64_1;
+
+        do {
+            v1 = XXH64_round(v1, XXH_get64bits(p)); p+=8;
+            v2 = XXH64_round(v2, XXH_get64bits(p)); p+=8;
+            v3 = XXH64_round(v3, XXH_get64bits(p)); p+=8;
+            v4 = XXH64_round(v4, XXH_get64bits(p)); p+=8;
+        } while (p<=limit);
+
+        h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18);
+        h64 = XXH64_mergeRound(h64, v1);
+        h64 = XXH64_mergeRound(h64, v2);
+        h64 = XXH64_mergeRound(h64, v3);
+        h64 = XXH64_mergeRound(h64, v4);
+
+    } else {
+        h64  = seed + PRIME64_5;
+    }
+
+    h64 += (U64) len;
+
+    while (p+8<=bEnd) {
+        U64 const k1 = XXH64_round(0, XXH_get64bits(p));
+        h64 ^= k1;
+        h64  = XXH_rotl64(h64,27) * PRIME64_1 + PRIME64_4;
+        p+=8;
+    }
+
+    if (p+4<=bEnd) {
+        h64 ^= (U64)(XXH_get32bits(p)) * PRIME64_1;
+        h64 = XXH_rotl64(h64, 23) * PRIME64_2 + PRIME64_3;
+        p+=4;
+    }
+
+    while (p<bEnd) {
+        h64 ^= (*p) * PRIME64_5;
+        h64 = XXH_rotl64(h64, 11) * PRIME64_1;
+        p++;
+    }
+
+    h64 ^= h64 >> 33;
+    h64 *= PRIME64_2;
+    h64 ^= h64 >> 29;
+    h64 *= PRIME64_3;
+    h64 ^= h64 >> 32;
+
+    return h64;
+}
+
+
+XXH_PUBLIC_API unsigned long long XXH64 (const void* input, size_t len, unsigned long long seed)
+{
+#if 0
+    /* Simple version, good for code maintenance, but unfortunately slow for small inputs */
+    XXH64_state_t state;
+    XXH64_reset(&state, seed);
+    XXH64_update(&state, input, len);
+    return XXH64_digest(&state);
+#else
+    XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;
+
+    if (XXH_FORCE_ALIGN_CHECK) {
+        if ((((size_t)input) & 7)==0) {  /* Input is aligned, let's leverage the speed advantage */
+            if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)
+                return XXH64_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned);
+            else
+                return XXH64_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned);
+    }   }
+
+    if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)
+        return XXH64_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned);
+    else
+        return XXH64_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned);
+#endif
+}
+
+/*======   Hash Streaming   ======*/
+
+XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void)
+{
+    return (XXH64_state_t*)XXH_malloc(sizeof(XXH64_state_t));
+}
+XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr)
+{
+    XXH_free(statePtr);
+    return XXH_OK;
+}
+
+XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dstState, const XXH64_state_t* srcState)
+{
+    memcpy(dstState, srcState, sizeof(*dstState));
+}
+
+XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH64_state_t* statePtr, unsigned long long seed)
+{
+    XXH64_state_t state;   /* using a local state to memcpy() in order to avoid strict-aliasing warnings */
+    memset(&state, 0, sizeof(state)-8);   /* do not write into reserved, for future removal */
+    state.v1 = seed + PRIME64_1 + PRIME64_2;
+    state.v2 = seed + PRIME64_2;
+    state.v3 = seed + 0;
+    state.v4 = seed - PRIME64_1;
+    memcpy(statePtr, &state, sizeof(state));
+    return XXH_OK;
+}
+
+FORCE_INLINE XXH_errorcode XXH64_update_endian (XXH64_state_t* state, const void* input, size_t len, XXH_endianess endian)
+{
+    const BYTE* p = (const BYTE*)input;
+    const BYTE* const bEnd = p + len;
+
+#ifdef XXH_ACCEPT_NULL_INPUT_POINTER
+    if (input==NULL) return XXH_ERROR;
+#endif
+
+    state->total_len += len;
+
+    if (state->memsize + len < 32) {  /* fill in tmp buffer */
+        XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, len);
+        state->memsize += (U32)len;
+        return XXH_OK;
+    }
+
+    if (state->memsize) {   /* tmp buffer is full */
+        XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, 32-state->memsize);
+        state->v1 = XXH64_round(state->v1, XXH_readLE64(state->mem64+0, endian));
+        state->v2 = XXH64_round(state->v2, XXH_readLE64(state->mem64+1, endian));
+        state->v3 = XXH64_round(state->v3, XXH_readLE64(state->mem64+2, endian));
+        state->v4 = XXH64_round(state->v4, XXH_readLE64(state->mem64+3, endian));
+        p += 32-state->memsize;
+        state->memsize = 0;
+    }
+
+    if (p+32 <= bEnd) {
+        const BYTE* const limit = bEnd - 32;
+        U64 v1 = state->v1;
+        U64 v2 = state->v2;
+        U64 v3 = state->v3;
+        U64 v4 = state->v4;
+
+        do {
+            v1 = XXH64_round(v1, XXH_readLE64(p, endian)); p+=8;
+            v2 = XXH64_round(v2, XXH_readLE64(p, endian)); p+=8;
+            v3 = XXH64_round(v3, XXH_readLE64(p, endian)); p+=8;
+            v4 = XXH64_round(v4, XXH_readLE64(p, endian)); p+=8;
+        } while (p<=limit);
+
+        state->v1 = v1;
+        state->v2 = v2;
+        state->v3 = v3;
+        state->v4 = v4;
+    }
+
+    if (p < bEnd) {
+        XXH_memcpy(state->mem64, p, (size_t)(bEnd-p));
+        state->memsize = (unsigned)(bEnd-p);
+    }
+
+    return XXH_OK;
+}
+
+XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* state_in, const void* input, size_t len)
+{
+    XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;
+
+    if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)
+        return XXH64_update_endian(state_in, input, len, XXH_littleEndian);
+    else
+        return XXH64_update_endian(state_in, input, len, XXH_bigEndian);
+}
+
+FORCE_INLINE U64 XXH64_digest_endian (const XXH64_state_t* state, XXH_endianess endian)
+{
+    const BYTE * p = (const BYTE*)state->mem64;
+    const BYTE* const bEnd = (const BYTE*)state->mem64 + state->memsize;
+    U64 h64;
+
+    if (state->total_len >= 32) {
+        U64 const v1 = state->v1;
+        U64 const v2 = state->v2;
+        U64 const v3 = state->v3;
+        U64 const v4 = state->v4;
+
+        h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18);
+        h64 = XXH64_mergeRound(h64, v1);
+        h64 = XXH64_mergeRound(h64, v2);
+        h64 = XXH64_mergeRound(h64, v3);
+        h64 = XXH64_mergeRound(h64, v4);
+    } else {
+        h64  = state->v3 + PRIME64_5;
+    }
+
+    h64 += (U64) state->total_len;
+
+    while (p+8<=bEnd) {
+        U64 const k1 = XXH64_round(0, XXH_readLE64(p, endian));
+        h64 ^= k1;
+        h64  = XXH_rotl64(h64,27) * PRIME64_1 + PRIME64_4;
+        p+=8;
+    }
+
+    if (p+4<=bEnd) {
+        h64 ^= (U64)(XXH_readLE32(p, endian)) * PRIME64_1;
+        h64  = XXH_rotl64(h64, 23) * PRIME64_2 + PRIME64_3;
+        p+=4;
+    }
+
+    while (p<bEnd) {
+        h64 ^= (*p) * PRIME64_5;
+        h64  = XXH_rotl64(h64, 11) * PRIME64_1;
+        p++;
+    }
+
+    h64 ^= h64 >> 33;
+    h64 *= PRIME64_2;
+    h64 ^= h64 >> 29;
+    h64 *= PRIME64_3;
+    h64 ^= h64 >> 32;
+
+    return h64;
+}
+
+XXH_PUBLIC_API unsigned long long XXH64_digest (const XXH64_state_t* state_in)
+{
+    XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN;
+
+    if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT)
+        return XXH64_digest_endian(state_in, XXH_littleEndian);
+    else
+        return XXH64_digest_endian(state_in, XXH_bigEndian);
+}
+
+
+/*====== Canonical representation   ======*/
+
+XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash)
+{
+    XXH_STATIC_ASSERT(sizeof(XXH64_canonical_t) == sizeof(XXH64_hash_t));
+    if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap64(hash);
+    memcpy(dst, &hash, sizeof(*dst));
+}
+
+XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src)
+{
+    return XXH_readBE64(src);
+}
+
+#endif  /* XXH_NO_LONG_LONG */

+ 293 - 0
src/libraries/xxHash/xxhash.h

@@ -0,0 +1,293 @@
+/*
+   xxHash - Extremely Fast Hash algorithm
+   Header File
+   Copyright (C) 2012-2016, Yann Collet.
+
+   BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions are
+   met:
+
+       * Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+       * Redistributions in binary form must reproduce the above
+   copyright notice, this list of conditions and the following disclaimer
+   in the documentation and/or other materials provided with the
+   distribution.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   You can contact the author at :
+   - xxHash source repository : https://github.com/Cyan4973/xxHash
+*/
+
+/* Notice extracted from xxHash homepage :
+
+xxHash is an extremely fast Hash algorithm, running at RAM speed limits.
+It also successfully passes all tests from the SMHasher suite.
+
+Comparison (single thread, Windows Seven 32 bits, using SMHasher on a Core 2 Duo @3GHz)
+
+Name            Speed       Q.Score   Author
+xxHash          5.4 GB/s     10
+CrapWow         3.2 GB/s      2       Andrew
+MumurHash 3a    2.7 GB/s     10       Austin Appleby
+SpookyHash      2.0 GB/s     10       Bob Jenkins
+SBox            1.4 GB/s      9       Bret Mulvey
+Lookup3         1.2 GB/s      9       Bob Jenkins
+SuperFastHash   1.2 GB/s      1       Paul Hsieh
+CityHash64      1.05 GB/s    10       Pike & Alakuijala
+FNV             0.55 GB/s     5       Fowler, Noll, Vo
+CRC32           0.43 GB/s     9
+MD5-32          0.33 GB/s    10       Ronald L. Rivest
+SHA1-32         0.28 GB/s    10
+
+Q.Score is a measure of quality of the hash function.
+It depends on successfully passing SMHasher test set.
+10 is a perfect score.
+
+A 64-bits version, named XXH64, is available since r35.
+It offers much better speed, but for 64-bits applications only.
+Name     Speed on 64 bits    Speed on 32 bits
+XXH64       13.8 GB/s            1.9 GB/s
+XXH32        6.8 GB/s            6.0 GB/s
+*/
+
+#ifndef XXHASH_H_5627135585666179
+#define XXHASH_H_5627135585666179 1
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+
+/* ****************************
+*  Definitions
+******************************/
+#include <stddef.h>   /* size_t */
+typedef enum { XXH_OK=0, XXH_ERROR } XXH_errorcode;
+
+
+/* ****************************
+*  API modifier
+******************************/
+/** XXH_PRIVATE_API
+*   This is useful to include xxhash functions in `static` mode
+*   in order to inline them, and remove their symbol from the public list.
+*   Methodology :
+*     #define XXH_PRIVATE_API
+*     #include "xxhash.h"
+*   `xxhash.c` is automatically included.
+*   It's not useful to compile and link it as a separate module.
+*/
+#ifdef XXH_PRIVATE_API
+#  ifndef XXH_STATIC_LINKING_ONLY
+#    define XXH_STATIC_LINKING_ONLY
+#  endif
+#  if defined(__GNUC__)
+#    define XXH_PUBLIC_API static __inline __attribute__((unused))
+#  elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
+#    define XXH_PUBLIC_API static inline
+#  elif defined(_MSC_VER)
+#    define XXH_PUBLIC_API static __inline
+#  else
+#    define XXH_PUBLIC_API static   /* this version may generate warnings for unused static functions; disable the relevant warning */
+#  endif
+#else
+#  define XXH_PUBLIC_API   /* do nothing */
+#endif /* XXH_PRIVATE_API */
+
+/*!XXH_NAMESPACE, aka Namespace Emulation :
+
+If you want to include _and expose_ xxHash functions from within your own library,
+but also want to avoid symbol collisions with other libraries which may also include xxHash,
+
+you can use XXH_NAMESPACE, to automatically prefix any public symbol from xxhash library
+with the value of XXH_NAMESPACE (therefore, avoid NULL and numeric values).
+
+Note that no change is required within the calling program as long as it includes `xxhash.h` :
+regular symbol name will be automatically translated by this header.
+*/
+#ifdef XXH_NAMESPACE
+#  define XXH_CAT(A,B) A##B
+#  define XXH_NAME2(A,B) XXH_CAT(A,B)
+#  define XXH_versionNumber XXH_NAME2(XXH_NAMESPACE, XXH_versionNumber)
+#  define XXH32 XXH_NAME2(XXH_NAMESPACE, XXH32)
+#  define XXH32_createState XXH_NAME2(XXH_NAMESPACE, XXH32_createState)
+#  define XXH32_freeState XXH_NAME2(XXH_NAMESPACE, XXH32_freeState)
+#  define XXH32_reset XXH_NAME2(XXH_NAMESPACE, XXH32_reset)
+#  define XXH32_update XXH_NAME2(XXH_NAMESPACE, XXH32_update)
+#  define XXH32_digest XXH_NAME2(XXH_NAMESPACE, XXH32_digest)
+#  define XXH32_copyState XXH_NAME2(XXH_NAMESPACE, XXH32_copyState)
+#  define XXH32_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH32_canonicalFromHash)
+#  define XXH32_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH32_hashFromCanonical)
+#  define XXH64 XXH_NAME2(XXH_NAMESPACE, XXH64)
+#  define XXH64_createState XXH_NAME2(XXH_NAMESPACE, XXH64_createState)
+#  define XXH64_freeState XXH_NAME2(XXH_NAMESPACE, XXH64_freeState)
+#  define XXH64_reset XXH_NAME2(XXH_NAMESPACE, XXH64_reset)
+#  define XXH64_update XXH_NAME2(XXH_NAMESPACE, XXH64_update)
+#  define XXH64_digest XXH_NAME2(XXH_NAMESPACE, XXH64_digest)
+#  define XXH64_copyState XXH_NAME2(XXH_NAMESPACE, XXH64_copyState)
+#  define XXH64_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH64_canonicalFromHash)
+#  define XXH64_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH64_hashFromCanonical)
+#endif
+
+
+/* *************************************
+*  Version
+***************************************/
+#define XXH_VERSION_MAJOR    0
+#define XXH_VERSION_MINOR    6
+#define XXH_VERSION_RELEASE  2
+#define XXH_VERSION_NUMBER  (XXH_VERSION_MAJOR *100*100 + XXH_VERSION_MINOR *100 + XXH_VERSION_RELEASE)
+XXH_PUBLIC_API unsigned XXH_versionNumber (void);
+
+
+/*-**********************************************************************
+*  32-bits hash
+************************************************************************/
+typedef unsigned int XXH32_hash_t;
+
+/*! XXH32() :
+    Calculate the 32-bits hash of sequence "length" bytes stored at memory address "input".
+    The memory between input & input+length must be valid (allocated and read-accessible).
+    "seed" can be used to alter the result predictably.
+    Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark) : 5.4 GB/s */
+XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t length, unsigned int seed);
+
+/*======   Streaming   ======*/
+typedef struct XXH32_state_s XXH32_state_t;   /* incomplete type */
+XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void);
+XXH_PUBLIC_API XXH_errorcode  XXH32_freeState(XXH32_state_t* statePtr);
+XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dst_state, const XXH32_state_t* src_state);
+
+XXH_PUBLIC_API XXH_errorcode XXH32_reset  (XXH32_state_t* statePtr, unsigned int seed);
+XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* statePtr, const void* input, size_t length);
+XXH_PUBLIC_API XXH32_hash_t  XXH32_digest (const XXH32_state_t* statePtr);
+
+/*
+These functions generate the xxHash of an input provided in multiple segments.
+Note that, for small input, they are slower than single-call functions, due to state management.
+For small input, prefer `XXH32()` and `XXH64()` .
+
+XXH state must first be allocated, using XXH*_createState() .
+
+Start a new hash by initializing state with a seed, using XXH*_reset().
+
+Then, feed the hash state by calling XXH*_update() as many times as necessary.
+Obviously, input must be allocated and read accessible.
+The function returns an error code, with 0 meaning OK, and any other value meaning there is an error.
+
+Finally, a hash value can be produced anytime, by using XXH*_digest().
+This function returns the nn-bits hash as an int or long long.
+
+It's still possible to continue inserting input into the hash state after a digest,
+and generate some new hashes later on, by calling again XXH*_digest().
+
+When done, free XXH state space if it was allocated dynamically.
+*/
+
+/*======   Canonical representation   ======*/
+
+typedef struct { unsigned char digest[4]; } XXH32_canonical_t;
+XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash);
+XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src);
+
+/* Default result type for XXH functions are primitive unsigned 32 and 64 bits.
+*  The canonical representation uses human-readable write convention, aka big-endian (large digits first).
+*  These functions allow transformation of hash result into and from its canonical format.
+*  This way, hash values can be written into a file / memory, and remain comparable on different systems and programs.
+*/
+
+
+#ifndef XXH_NO_LONG_LONG
+/*-**********************************************************************
+*  64-bits hash
+************************************************************************/
+typedef unsigned long long XXH64_hash_t;
+
+/*! XXH64() :
+    Calculate the 64-bits hash of sequence of length "len" stored at memory address "input".
+    "seed" can be used to alter the result predictably.
+    This function runs faster on 64-bits systems, but slower on 32-bits systems (see benchmark).
+*/
+XXH_PUBLIC_API XXH64_hash_t XXH64 (const void* input, size_t length, unsigned long long seed);
+
+/*======   Streaming   ======*/
+typedef struct XXH64_state_s XXH64_state_t;   /* incomplete type */
+XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void);
+XXH_PUBLIC_API XXH_errorcode  XXH64_freeState(XXH64_state_t* statePtr);
+XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dst_state, const XXH64_state_t* src_state);
+
+XXH_PUBLIC_API XXH_errorcode XXH64_reset  (XXH64_state_t* statePtr, unsigned long long seed);
+XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* statePtr, const void* input, size_t length);
+XXH_PUBLIC_API XXH64_hash_t  XXH64_digest (const XXH64_state_t* statePtr);
+
+/*======   Canonical representation   ======*/
+typedef struct { unsigned char digest[8]; } XXH64_canonical_t;
+XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash);
+XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src);
+#endif  /* XXH_NO_LONG_LONG */
+
+
+#ifdef XXH_STATIC_LINKING_ONLY
+
+/* ================================================================================================
+   This section contains definitions which are not guaranteed to remain stable.
+   They may change in future versions, becoming incompatible with a different version of the library.
+   They shall only be used with static linking.
+   Never use these definitions in association with dynamic linking !
+=================================================================================================== */
+
+/* These definitions are only meant to make possible
+   static allocation of XXH state, on stack or in a struct for example.
+   Never use members directly. */
+
+struct XXH32_state_s {
+   unsigned total_len_32;
+   unsigned large_len;
+   unsigned v1;
+   unsigned v2;
+   unsigned v3;
+   unsigned v4;
+   unsigned mem32[4];   /* buffer defined as U32 for alignment */
+   unsigned memsize;
+   unsigned reserved;   /* never read nor write, will be removed in a future version */
+};   /* typedef'd to XXH32_state_t */
+
+#ifndef XXH_NO_LONG_LONG   /* remove 64-bits support */
+struct XXH64_state_s {
+   unsigned long long total_len;
+   unsigned long long v1;
+   unsigned long long v2;
+   unsigned long long v3;
+   unsigned long long v4;
+   unsigned long long mem64[4];   /* buffer defined as U64 for alignment */
+   unsigned memsize;
+   unsigned reserved[2];          /* never read nor write, will be removed in a future version */
+};   /* typedef'd to XXH64_state_t */
+#endif
+
+#ifdef XXH_PRIVATE_API
+#  include "xxhash.c"   /* include xxhash function bodies as `static`, for inlining */
+#endif
+
+#endif /* XXH_STATIC_LINKING_ONLY */
+
+
+#if defined (__cplusplus)
+}
+#endif
+
+#endif /* XXHASH_H_5627135585666179 */

+ 3 - 27
src/modules/audio/Audio.h

@@ -28,6 +28,7 @@
 #include "common/Module.h"
 #include "common/StringMap.h"
 #include "Source.h"
+#include "RecordingDevice.h"
 
 namespace love
 {
@@ -189,34 +190,9 @@ public:
 	virtual float getDopplerScale() const = 0;
 
 	/**
-	 * Begins recording audio input from the microphone.
+	 * @return Reference to a vector of pointers to recording devices. May be empty.
 	 **/
-	virtual void record() = 0;
-
-	/**
-	 * Gets a section of recorded audio.
-	 * Per OpenAL, the measurement begins from the start of the
-	 * audio data in memory, which is after the last time this function
-	 * was called. If this function has not been called yet this recording
-	 * session, it just grabs from the beginning.
-	 * @return All the recorded SoundData thus far.
-	 **/
-	virtual love::sound::SoundData *getRecordedData() = 0;
-
-	/**
-	 * Stops recording and, if passed true, returns all the recorded audio
-	 * not already gotten by getRecordedData.
-	 * @param returnData Whether to return recorded audio.
-	 * @return if returnData, all the recorded audio yet to be gotten,
-	 * otherwise NULL.
-	 **/
-	virtual love::sound::SoundData *stopRecording(bool returnData) = 0;
-
-	/**
-	 * Checks whether LOVE is able to record audio input.
-	 * @return hasMic Whether LOVE has a microphone enabled.
-	 **/
-	virtual bool canRecord() = 0;
+	virtual const std::vector<RecordingDevice*> &getRecordingDevices() = 0;
 
 	/**
 	 * Gets the distance model used for attenuation.

+ 39 - 0
src/modules/audio/RecordingDevice.cpp

@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2006-2016 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 "RecordingDevice.h"
+
+namespace love
+{
+namespace audio
+{
+
+love::Type RecordingDevice::type("RecordingDevice", &Object::type);
+
+RecordingDevice::RecordingDevice()
+{
+}
+
+RecordingDevice::~RecordingDevice()
+{
+}
+
+} //audio
+} //love

+ 104 - 0
src/modules/audio/RecordingDevice.h

@@ -0,0 +1,104 @@
+/**
+ * Copyright (c) 2006-2016 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 = 0; 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_AUDIO_RECORDING_DEVICE_H
+#define LOVE_AUDIO_RECORDING_DEVICE_H
+
+#include "common/Object.h"
+#include "sound/SoundData.h"
+
+#include <string>
+
+namespace love
+{
+namespace audio
+{
+
+class RecordingDevice : public love::Object
+{
+public:
+
+	static love::Type type;
+
+	RecordingDevice();
+	virtual ~RecordingDevice();
+
+	/**
+	 * Begins audio input recording process. using default (previous) parameters.
+	 * @return True if recording started successfully.
+	 **/
+	virtual bool start() = 0;
+
+	/**
+	 * Begins audio input recording process.
+	 * @param samples Number of samples to buffer.
+	 * @param sampleRate Desired sample rate.
+	 * @param bitDepth Desired bit depth (8 or 16).
+	 * @param channels Desired number of channels. 
+	 * @return True if recording started successfully.
+	 **/
+	virtual bool start(int samples, int sampleRate, int bitDepth, int channels) = 0;
+
+	/** 
+	 * Stops audio input recording.
+	 **/
+	virtual void stop() = 0;
+
+	/**
+	 * Retreives recorded data. 
+	 * @return SoundData containing data obtained from recording device.
+	 **/
+	virtual love::sound::SoundData *getData() = 0;
+
+	/**
+	 * @return C string device name.
+	 **/ 
+	virtual const char *getName() const = 0;
+
+	/**
+	 * @return Number of samples currently recorded.
+	 **/
+	virtual int getSampleCount() const = 0;
+
+	/**
+	 * @return Sample rate for recording.
+	 **/
+	virtual int getSampleRate() const = 0;
+
+	/**
+	 * @return Bit depth for recording.
+	 **/
+	virtual int getBitDepth() const = 0;
+
+	/**
+	 * @return Number of channels for recording.
+	 **/
+	virtual int getChannels() const = 0;
+
+	/**
+	 * @return True if currently recording.
+	 **/
+	virtual bool isRecording() const = 0;
+}; //RecordingDevice
+
+} //audio
+} //love
+
+#endif //LOVE_AUDIO_RECORDING_DEVICE_H

+ 2 - 16
src/modules/audio/null/Audio.cpp

@@ -144,23 +144,9 @@ float Audio::getDopplerScale() const
 	return 1.0f;
 }
 
-void Audio::record()
+const std::vector<love::audio::RecordingDevice*> &Audio::getRecordingDevices()
 {
-}
-
-love::sound::SoundData *Audio::getRecordedData()
-{
-	return NULL;
-}
-
-love::sound::SoundData *Audio::stopRecording(bool)
-{
-	return NULL;
-}
-
-bool Audio::canRecord()
-{
-	return false;
+	return capture;
 }
 
 Audio::DistanceModel Audio::getDistanceModel() const

+ 3 - 4
src/modules/audio/null/Audio.h

@@ -24,6 +24,7 @@
 // LOVE
 #include "audio/Audio.h"
 
+#include "RecordingDevice.h"
 #include "Source.h"
 
 namespace love
@@ -70,10 +71,7 @@ public:
 	void setDopplerScale(float scale);
 	float getDopplerScale() const;
 
-	void record();
-	love::sound::SoundData *getRecordedData();
-	love::sound::SoundData *stopRecording(bool returnData);
-	bool canRecord();
+	const std::vector<love::audio::RecordingDevice*> &getRecordingDevices();
 
 	DistanceModel getDistanceModel() const;
 	void setDistanceModel(DistanceModel distanceModel);
@@ -81,6 +79,7 @@ public:
 private:
 	float volume;
 	DistanceModel distanceModel;
+	std::vector<love::audio::RecordingDevice*> capture;
 
 }; // Audio
 

+ 92 - 0
src/modules/audio/null/RecordingDevice.cpp

@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2006-2016 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 = 0; 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 "RecordingDevice.h"
+#include "Audio.h"
+
+namespace love
+{
+namespace audio
+{
+namespace null
+{
+
+const char *RecordingDevice::name = "null";
+
+RecordingDevice::RecordingDevice(const char *)
+{
+}
+
+RecordingDevice::~RecordingDevice()
+{
+}
+
+bool RecordingDevice::start()
+{
+	return false;
+}
+
+bool RecordingDevice::start(int, int, int, int)
+{
+	return false;
+}
+
+void RecordingDevice::stop()
+{
+}
+
+love::sound::SoundData *RecordingDevice::getData()
+{
+	return nullptr;
+}
+
+int RecordingDevice::getSampleCount() const
+{
+	return 0;
+}
+
+int RecordingDevice::getSampleRate() const
+{
+	return 8000;
+}
+
+int RecordingDevice::getBitDepth() const
+{
+	return 16;
+}
+
+int RecordingDevice::getChannels() const
+{
+	return 1;
+}
+
+const char *RecordingDevice::getName() const
+{
+	return name;
+}
+
+bool RecordingDevice::isRecording() const
+{
+	return false;
+}
+
+} //null
+} //audio
+} //love

+ 58 - 0
src/modules/audio/null/RecordingDevice.h

@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2006-2016 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 = 0; 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_AUDIO_NULL_RECORDING_DEVICE_H
+#define LOVE_AUDIO_NULL_RECORDING_DEVICE_H
+
+#include "audio/RecordingDevice.h"
+#include "sound/SoundData.h"
+
+namespace love
+{
+namespace audio
+{
+namespace null
+{
+
+class RecordingDevice : public love::audio::RecordingDevice
+{
+public:
+	RecordingDevice(const char *name);
+	virtual ~RecordingDevice();
+	virtual bool start();
+	virtual bool start(int samples, int sampleRate, int bitDepth, int channels);
+	virtual void stop();
+	virtual love::sound::SoundData *getData();
+	virtual const char *getName() const;
+	virtual int getSampleCount() const;
+	virtual int getSampleRate() const;
+	virtual int getBitDepth() const;
+	virtual int getChannels() const;
+	virtual bool isRecording() const;
+
+private:
+	static const char *name;
+}; //RecordingDevice
+
+} //null
+} //audio
+} //love
+
+#endif //LOVE_AUDIO_NULL_RECORDING_DEVICE_H

+ 98 - 62
src/modules/audio/openal/Audio.cpp

@@ -20,10 +20,11 @@
 
 #include "Audio.h"
 #include "common/delay.h"
-
+#include "RecordingDevice.h"
 #include "sound/Decoder.h"
 
 #include <cstdlib>
+#include <iostream>
 
 namespace love
 {
@@ -67,9 +68,29 @@ void Audio::PoolThread::setFinish()
 	finish = true;
 }
 
+ALenum Audio::getFormat(int bitDepth, int channels)
+{
+	if (bitDepth != 8 && bitDepth != 16)
+		return AL_NONE;
+
+	if (channels == 1)
+		return bitDepth == 8 ? AL_FORMAT_MONO8 : AL_FORMAT_MONO16;
+	else if (channels == 2)
+		return bitDepth == 8 ? AL_FORMAT_STEREO8 : AL_FORMAT_STEREO16;
+	#ifdef AL_EXT_MCFORMATS
+	else if (alIsExtensionPresent("AL_EXT_MCFORMATS"))
+	{
+		if (channels == 6)
+			return bitDepth == 8 ? AL_FORMAT_51CHN8 : AL_FORMAT_51CHN16;
+		else if (channels == 8)
+			return bitDepth == 8 ? AL_FORMAT_71CHN8 : AL_FORMAT_71CHN16;
+	}
+	#endif
+	return AL_NONE;
+}
+
 Audio::Audio()
 	: device(nullptr)
-	, capture(nullptr)
 	, context(nullptr)
 	, pool(nullptr)
 	, poolThread(nullptr)
@@ -89,26 +110,6 @@ Audio::Audio()
 	if (!alcMakeContextCurrent(context) || alcGetError(device) != ALC_NO_ERROR)
 		throw love::Exception("Could not make context current.");
 
-	/*std::string captureName(alcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER));
-	const ALCchar * devices = alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);
-	while (*devices)
-	{
-		std::string device(devices);
-		devices += device.size() + 1;
-		if (device.find("Mic") != std::string::npos || device.find("mic") != std::string::npos)
-		{
-			captureName = device;
-		}
-	}
-
-	capture = alcCaptureOpenDevice(captureName.c_str(), 8000, AL_FORMAT_MONO16, 262144); // about 32 seconds
-
-	if (!capture)
-	{
-		// We're not going to prevent LOVE from running without a microphone, but we should warn, at least
-		std::cerr << "Warning, couldn't open capture device! No audio input!" << std::endl;
-	}*/
-
 	// pool must be allocated after AL context.
 	try
 	{
@@ -118,8 +119,10 @@ Audio::Audio()
 	{
 		alcMakeContextCurrent(nullptr);
 		alcDestroyContext(context);
-		//if (capture) alcCaptureCloseDevice(capture);
 		alcCloseDevice(device);
+		for (auto c : capture)
+			delete c;
+
 		throw;
 	}
 
@@ -137,8 +140,9 @@ Audio::~Audio()
 
 	alcMakeContextCurrent(nullptr);
 	alcDestroyContext(context);
-	//if (capture) alcCaptureCloseDevice(capture);
 	alcCloseDevice(device);
+	for (auto c : capture)
+		delete c;
 }
 
 
@@ -265,44 +269,6 @@ float Audio::getDopplerScale() const
 	return alGetFloat(AL_DOPPLER_FACTOR);
 }
 
-void Audio::record()
-{
-	if (!canRecord()) return;
-	alcCaptureStart(capture);
-}
-
-love::sound::SoundData *Audio::getRecordedData()
-{
-	if (!canRecord())
-		return NULL;
-	int samplerate = 8000;
-	ALCint samples;
-	alcGetIntegerv(capture, ALC_CAPTURE_SAMPLES, 4, &samples);
-	void *data = malloc(samples * (2/sizeof(char)));
-	alcCaptureSamples(capture, data, samples);
-	love::sound::SoundData *sd = new love::sound::SoundData(data, samples, samplerate, 16, 1);
-	free(data);
-	return sd;
-}
-
-love::sound::SoundData *Audio::stopRecording(bool returnData)
-{
-	if (!canRecord())
-		return NULL;
-	love::sound::SoundData *sd = NULL;
-	if (returnData)
-	{
-		sd = getRecordedData();
-	}
-	alcCaptureStop(capture);
-	return sd;
-}
-
-bool Audio::canRecord()
-{
-	return (capture != NULL);
-}
-
 Audio::DistanceModel Audio::getDistanceModel() const
 {
 	return distanceModel;
@@ -347,6 +313,76 @@ void Audio::setDistanceModel(DistanceModel distanceModel)
 	}
 }
 
+const std::vector<love::audio::RecordingDevice*> &Audio::getRecordingDevices()
+{
+	std::vector<std::string> devnames;
+	std::vector<love::audio::RecordingDevice*> devices;
+
+	std::string defaultname(alcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER));
+
+	//no device name obtained from AL, fallback to reading from device
+	if (defaultname.length() == 0)
+	{
+		//use some safe basic parameters - 8 kHz, 8 bits, 1 channel
+		ALCdevice *defaultdevice = alcCaptureOpenDevice(NULL, 8000, 8, 1);
+		if (alGetError() == AL_NO_ERROR)
+		{
+			defaultname = alcGetString(defaultdevice, ALC_CAPTURE_DEVICE_SPECIFIER);
+			alcCaptureCloseDevice(defaultdevice);
+		}
+		else
+		{
+			//failed to open default recording device - bail, return empty list
+			capture.clear();
+			return capture;
+		}
+	}
+
+	devnames.reserve(capture.size());
+	devnames.push_back(defaultname);
+
+	//find devices name list
+	const ALCchar *devstr = alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);
+	size_t offset = 0;
+	while (true)
+	{
+		if (devstr[offset] == '\0')
+			break;
+		std::string str((ALCchar*)&devstr[offset]);
+		if (str != defaultname)
+			devnames.push_back(str);
+		offset += str.length() + 1;
+	}
+
+	devices.reserve(devnames.size());
+	//build ordered list of devices
+	for (int i = 0; i < (int) devnames.size(); i++)
+	{
+		devices.push_back(nullptr);
+		auto d = devices.end() - 1;
+
+		for (auto c : capture)
+			if (devnames[i] == c->getName())
+				*d = c;
+
+		if (*d == nullptr)
+			*d = new RecordingDevice(devnames[i].c_str());
+		else
+			(*d)->retain();
+	}
+
+	for (auto c : capture)
+		c->release();
+	capture.clear();
+	capture.reserve(devices.size());
+
+	//this needs to be executed in specific order
+	for (unsigned int i = 0; i < devnames.size(); i++)
+		capture.push_back(devices[i]);
+
+	return capture;
+}
+
 } // openal
 } // audio
 } // love

+ 13 - 6
src/modules/audio/openal/Audio.h

@@ -29,6 +29,7 @@
 
 // LOVE
 #include "audio/Audio.h"
+#include "audio/RecordingDevice.h"
 #include "common/config.h"
 #include "sound/SoundData.h"
 
@@ -65,6 +66,15 @@ public:
 	Audio();
 	~Audio();
 
+	/**
+	 * Gets the OpenAL format identifier based on number of
+	 * channels and bits.
+	 * @param channels.
+	 * @param bitDepth Either 8-bit samples, or 16-bit samples.
+	 * @return One of AL_FORMAT_*, or AL_NONE if unsupported format.
+	 **/
+	static ALenum getFormat(int bitDepth, int channels);
+
 	// Implements Module.
 	const char *getName() const;
 
@@ -95,10 +105,7 @@ public:
 	void setDopplerScale(float scale);
 	float getDopplerScale() const;
 
-	void record();
-	love::sound::SoundData *getRecordedData();
-	love::sound::SoundData *stopRecording(bool returnData);
-	bool canRecord();
+	const std::vector<love::audio::RecordingDevice*> &getRecordingDevices();
 
 	DistanceModel getDistanceModel() const;
 	void setDistanceModel(DistanceModel distanceModel);
@@ -108,8 +115,8 @@ private:
 	// The OpenAL device.
 	ALCdevice *device;
 
-	// The OpenAL capture device (microphone).
-	ALCdevice *capture;
+	// The OpenAL capture devices.
+	std::vector<love::audio::RecordingDevice*> capture;
 
 	// The OpenAL context.
 	ALCcontext *context;

+ 157 - 0
src/modules/audio/openal/RecordingDevice.cpp

@@ -0,0 +1,157 @@
+/**
+ * Copyright (c) 2006-2016 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 = 0; 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 "RecordingDevice.h"
+#include "Audio.h"
+#include "sound/Sound.h"
+
+namespace love
+{
+namespace audio
+{
+namespace openal
+{
+
+#define soundInstance() (Module::getInstance<love::sound::Sound>(Module::M_SOUND))
+
+class InvalidFormatException : public love::Exception
+{
+public:
+
+	InvalidFormatException(int channels, int bitdepth)
+		: Exception("Recording %d channels with %d bits per sample is not supported.", channels, bitdepth)
+	{
+	}
+
+};
+
+RecordingDevice::RecordingDevice(const char *name) 
+	: name(name)
+{
+}
+
+RecordingDevice::~RecordingDevice()
+{
+	if (!isRecording())
+		return;
+
+	alcCaptureStop(device);
+	alcCaptureCloseDevice(device);
+}
+
+bool RecordingDevice::start()
+{
+	return start(samples, sampleRate, bitDepth, channels);
+}
+
+bool RecordingDevice::start(int samples, int sampleRate, int bitDepth, int channels)
+{
+	if (isRecording())
+	{
+		alcCaptureStop(device);
+		alcCaptureCloseDevice(device);
+	}
+
+	ALenum format = Audio::getFormat(bitDepth, channels);
+	if (format == AL_NONE)
+		throw InvalidFormatException(channels, bitDepth);
+
+	if (samples <= 0)
+		throw love::Exception("Invalid number of samples.");
+
+	if (sampleRate <= 0)
+		throw love::Exception("Invalid sample rate.");
+
+	device = alcCaptureOpenDevice(name.c_str(), sampleRate, format, samples);
+	if (device == nullptr)
+		return false;
+
+	alcCaptureStart(device);
+	this->samples = samples;
+	this->sampleRate = sampleRate;
+	this->bitDepth = bitDepth;
+	this->channels = channels;
+	return true;
+}
+
+void RecordingDevice::stop()
+{
+	if (!isRecording())
+		return;
+
+	alcCaptureStop(device);
+	alcCaptureCloseDevice(device);
+	device = nullptr;
+}
+
+love::sound::SoundData *RecordingDevice::getData()
+{
+	if (!isRecording())
+		return nullptr;
+
+	int samples = getSampleCount();
+	if (samples == 0)
+		return nullptr;
+
+	love::sound::SoundData *soundData = soundInstance()->newSoundData(samples, sampleRate, bitDepth, channels);
+
+	alcCaptureSamples(device, soundData->getData(), samples);
+
+	return soundData;
+}
+
+int RecordingDevice::getSampleCount() const
+{
+	if (!isRecording())
+		return 0;
+
+	ALCint samples;
+	alcGetIntegerv(device, ALC_CAPTURE_SAMPLES, sizeof(ALCint), &samples);
+	return (int)samples;
+}
+
+int RecordingDevice::getSampleRate() const
+{
+	return sampleRate;
+}
+
+int RecordingDevice::getBitDepth() const
+{
+	return bitDepth;
+}
+
+int RecordingDevice::getChannels() const
+{
+	return channels;
+}
+
+const char *RecordingDevice::getName() const
+{
+	return name.c_str();
+}
+
+bool RecordingDevice::isRecording() const
+{
+	return device != nullptr;
+}
+
+} //openal
+} //audio
+} //love

+ 78 - 0
src/modules/audio/openal/RecordingDevice.h

@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) 2006-2016 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 = 0; 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_AUDIO_OPENAL_RECORDING_DEVICE_H
+#define LOVE_AUDIO_OPENAL_RECORDING_DEVICE_H
+
+#include "common/config.h"
+
+#ifdef LOVE_APPLE_USE_FRAMEWORKS
+#ifdef LOVE_IOS
+#include <OpenAL/alc.h>
+#include <OpenAL/al.h>
+#else
+#include <OpenAL-Soft/alc.h>
+#include <OpenAL-Soft/al.h>
+#endif
+#else
+#include <AL/alc.h>
+#include <AL/al.h>
+#endif
+
+#include "audio/RecordingDevice.h"
+#include "sound/SoundData.h"
+
+namespace love
+{
+namespace audio
+{
+namespace openal
+{
+
+class RecordingDevice : public love::audio::RecordingDevice
+{
+public:
+	RecordingDevice(const char *name);
+	virtual ~RecordingDevice();
+	virtual bool start();
+	virtual bool start(int samples, int sampleRate, int bitDepth, int channels);
+	virtual void stop();
+	virtual love::sound::SoundData *getData();
+	virtual const char *getName() const;
+	virtual int getSampleCount() const;
+	virtual int getSampleRate() const;
+	virtual int getBitDepth() const;
+	virtual int getChannels() const;
+	virtual bool isRecording() const;
+
+private:
+	int samples = 8192;
+	int sampleRate = 8000;
+	int bitDepth = 16;
+	int channels = 1;
+	std::string name;
+	ALCdevice *device = nullptr;
+}; //RecordingDevice
+
+} //openal
+} //audio
+} //love
+
+#endif //LOVE_AUDIO_OPENAL_RECORDING_DEVICE_H

+ 54 - 55
src/modules/audio/openal/Source.cpp

@@ -20,6 +20,7 @@
 
 #include "Source.h"
 #include "Pool.h"
+#include "Audio.h"
 #include "common/math.h"
 
 // STD
@@ -119,8 +120,8 @@ Source::Source(Pool *pool, love::sound::SoundData *soundData)
 	, channels(soundData->getChannels())
 	, bitDepth(soundData->getBitDepth())
 {
-	ALenum fmt = getFormat(soundData->getChannels(), soundData->getBitDepth());
-	if (fmt == 0)
+	ALenum fmt = Audio::getFormat(soundData->getBitDepth(), soundData->getChannels());
+	if (fmt == AL_NONE)
 		throw InvalidFormatException(soundData->getChannels(), soundData->getBitDepth());
 
 	staticBuffer.set(new StaticDataBuffer(fmt, soundData->getData(), (ALsizei) soundData->getSize(), sampleRate), Acquire::NORETAIN);
@@ -141,7 +142,7 @@ Source::Source(Pool *pool, love::sound::Decoder *decoder)
 	, decoder(decoder)
 	, unusedBufferTop(MAX_BUFFERS - 1)
 {
-	if (getFormat(decoder->getChannels(), decoder->getBitDepth()) == 0)
+	if (Audio::getFormat(decoder->getBitDepth(), decoder->getChannels()) == AL_NONE)
 		throw InvalidFormatException(decoder->getChannels(), decoder->getBitDepth());
 
 	alGenBuffers(MAX_BUFFERS, streamBuffers);
@@ -162,8 +163,8 @@ Source::Source(Pool *pool, int sampleRate, int bitDepth, int channels)
 	, channels(channels)
 	, bitDepth(bitDepth)
 {
-	ALenum fmt = getFormat(channels, bitDepth);
-	if (fmt == 0)
+	ALenum fmt = Audio::getFormat(bitDepth, channels);
+	if (fmt == AL_NONE)
 		throw InvalidFormatException(channels, bitDepth);
 
 	alGenBuffers(MAX_BUFFERS, streamBuffers);
@@ -280,10 +281,12 @@ bool Source::update()
 	switch (sourceType)
 	{
 		case TYPE_STATIC:
+		{
 			// Looping mode could have changed.
-			// FIXME: make looping mode not change without you noticing so that this is not needed
+			// FIXME: make looping mode change atomically so this is not needed
 			alSourcei(source, AL_LOOPING, isLooping() ? AL_TRUE : AL_FALSE);
 			return !isFinished();
+		}
 		case TYPE_STREAM:
 			if (!isFinished())
 			{
@@ -401,24 +404,26 @@ void Source::seekAtomic(float offset, void *unit)
 		break;
 	}
 
+	bool wasPlaying = isPlaying();
 	switch (sourceType)
 	{
 		case TYPE_STATIC:
-			alSourcef(source, AL_SAMPLE_OFFSET, offsetSamples);
-			offsetSamples = offsetSeconds = 0;
+			if (valid)
+			{
+				alSourcef(source, AL_SAMPLE_OFFSET, offsetSamples);
+				offsetSamples = offsetSeconds = 0;
+			}
 			break;
 		case TYPE_STREAM:
 		{
-			bool wasPlaying = isPlaying();
-
 			// To drain all buffers
 			if (valid)
-				stopAtomic();
+				stop();
 
 			decoder->seek(offsetSeconds);
 
 			if (wasPlaying)
-				playAtomic(source);
+				play();
 
 			break;
 		}
@@ -454,6 +459,13 @@ void Source::seekAtomic(float offset, void *unit)
 		case TYPE_MAX_ENUM:
 			break;
 	}
+	if (wasPlaying && (alGetError() == AL_INVALID_VALUE || (sourceType == TYPE_STREAM && !isPlaying())))
+	{
+		stop();
+		if (isLooping())
+			play();
+		return;
+	}
 	this->offsetSamples = offsetSamples;
 	this->offsetSeconds = offsetSeconds;
 }
@@ -688,7 +700,7 @@ bool Source::queueAtomic(void *data, ALsizei length)
 		if (buffer == AL_NONE)
 			return false;
 
-		alBufferData(buffer, getFormat(channels, bitDepth), data, length, sampleRate);
+		alBufferData(buffer, Audio::getFormat(bitDepth, channels), data, length, sampleRate);
 		alSourceQueueBuffers(source, 1, &buffer);
 		unusedBufferPop();
 	}
@@ -699,7 +711,7 @@ bool Source::queueAtomic(void *data, ALsizei length)
 			return false;
 
 		//stack acts as queue while stopped
-		alBufferData(buffer, getFormat(channels, bitDepth), data, length, sampleRate);
+		alBufferData(buffer, Audio::getFormat(bitDepth, channels), data, length, sampleRate);
 		unusedBufferQueue(buffer);
 	}
 	bufferedBytes += length;
@@ -734,9 +746,6 @@ void Source::prepareAtomic()
 	{
 		case TYPE_STATIC:
 			alSourcei(source, AL_BUFFER, staticBuffer->getBuffer());
-			//source can be seeked while not valid
-			if (offsetSamples >= 0) 
-				alSourcef(source, AL_SAMPLE_OFFSET, offsetSamples);
 			break;
 		case TYPE_STREAM:
 			while (unusedBufferPeek() != AL_NONE)
@@ -760,8 +769,6 @@ void Source::prepareAtomic()
 			for (unsigned int i = top + 1; i < MAX_BUFFERS; i++)
 				unusedBufferPush(unusedBuffers[i]);
 
-			if (offsetSamples >= 0) 
-				alSourcef(source, AL_SAMPLE_OFFSET, offsetSamples);
 			break;
 		}
 		case TYPE_MAX_ENUM:
@@ -774,7 +781,6 @@ void Source::teardownAtomic()
 	switch (sourceType)
 	{
 		case TYPE_STATIC:
-			alSourcef(source, AL_SAMPLE_OFFSET, 0);
 			break;
 		case TYPE_STREAM:
 		{
@@ -832,16 +838,32 @@ bool Source::playAtomic(ALuint source)
 
 	alSourcePlay(source);
 
-	// alSourcePlay may fail if the system has reached its limit of simultaneous
-	// playing sources.
 	bool success = alGetError() == AL_NO_ERROR;
 
-	valid = true; //if it fails it will be set to false again
-	//but this prevents a horrible, horrible bug
+	if (sourceType == TYPE_STREAM)
+	{
+		valid = true; //isPlaying() needs source to be valid
+		if (!isPlaying())
+			success = false;
+	}
+	else if (success)
+	{
+		alSourcef(source, AL_SAMPLE_OFFSET, offsetSamples);
+		success = alGetError() == AL_NO_ERROR;
+	}
 
+	if (!success)
+	{
+		valid = true; //stop() needs source to be valid
+		stop();
+	}
 	if (sourceType != TYPE_STREAM)
 		offsetSamples = offsetSeconds = 0;
 
+	//this is set to success state afterwards anyway, but setting it here
+	//to true preemptively avoids race condition with update bug
+	valid = true; 
+
 	return success;
 }
 
@@ -862,7 +884,12 @@ void Source::pauseAtomic()
 void Source::resumeAtomic()
 {
 	if (valid && !isPlaying())
+	{
 		alSourcePlay(source);
+
+		if (alGetError() == AL_INVALID_VALUE || (sourceType == TYPE_STREAM && unusedBufferTop == MAX_BUFFERS - 1))
+			stop();
+	}
 }
 
 bool Source::playAtomic(const std::vector<love::audio::Source*> &sources, const std::vector<ALuint> &ids, const std::vector<char> &wasPlaying)
@@ -941,7 +968,7 @@ void Source::pauseAtomic(const std::vector<love::audio::Source*> &sources)
 
 void Source::reset()
 {
-	alSourcei(source, AL_BUFFER, 0);
+	alSourcei(source, AL_BUFFER, AL_NONE);
 	alSourcefv(source, AL_POSITION, position);
 	alSourcefv(source, AL_VELOCITY, velocity);
 	alSourcefv(source, AL_DIRECTION, direction);
@@ -966,34 +993,6 @@ void Source::setFloatv(float *dst, const float *src) const
 	dst[2] = src[2];
 }
 
-ALenum Source::getFormat(int channels, int bitDepth) const
-{
-	if (channels == 1 && bitDepth == 8)
-		return AL_FORMAT_MONO8;
-	else if (channels == 1 && bitDepth == 16)
-		return AL_FORMAT_MONO16;
-	else if (channels == 2 && bitDepth == 8)
-		return AL_FORMAT_STEREO8;
-	else if (channels == 2 && bitDepth == 16)
-		return AL_FORMAT_STEREO16;
-
-#ifdef AL_EXT_MCFORMATS
-	if (alIsExtensionPresent("AL_EXT_MCFORMATS"))
-	{
-		if (channels == 6 && bitDepth == 8)
-			return AL_FORMAT_51CHN8;
-		else if (channels == 6 && bitDepth == 16)
-			return AL_FORMAT_51CHN16;
-		else if (channels == 8 && bitDepth == 8)
-			return AL_FORMAT_71CHN8;
-		else if (channels == 8 && bitDepth == 16)
-			return AL_FORMAT_71CHN16;
-	}
-#endif
-
-	return 0;
-}
-
 ALuint Source::unusedBufferPeek()
 {
 	return (unusedBufferTop < 0) ? AL_NONE : unusedBuffers[unusedBufferTop];
@@ -1029,9 +1028,9 @@ int Source::streamAtomic(ALuint buffer, love::sound::Decoder *d)
 	// OpenAL implementations are allowed to ignore 0-size alBufferData calls.
 	if (decoded > 0)
 	{
-		int fmt = getFormat(d->getChannels(), d->getBitDepth());
+		int fmt = Audio::getFormat(d->getBitDepth(), d->getChannels());
 
-		if (fmt != 0)
+		if (fmt != AL_NONE)
 			alBufferData(buffer, fmt, d->getBuffer(), decoded, d->getSampleRate());
 		else
 			decoded = 0;

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

@@ -163,15 +163,6 @@ private:
 
 	void setFloatv(float *dst, const float *src) const;
 
-	/**
-	 * Gets the OpenAL format identifier based on number of
-	 * channels and bits.
-	 * @param channels Either 1 (mono) or 2 (stereo).
-	 * @param bitDepth Either 8-bit samples, or 16-bit samples.
-	 * @return One of AL_FORMAT_*, or 0 if unsupported format.
-	 **/
-	ALenum getFormat(int channels, int bitDepth) const;
-
 	int streamAtomic(ALuint buffer, love::sound::Decoder *d);
 
 	ALuint unusedBufferPeek();

+ 17 - 46
src/modules/audio/wrap_Audio.cpp

@@ -257,49 +257,6 @@ int w_getDopplerScale(lua_State *L)
 	return 1;
 }
 
-int w_record(lua_State *)
-{
-	instance()->record();
-	return 0;
-}
-
-int w_getRecordedData(lua_State *L)
-{
-	love::sound::SoundData *sd = instance()->getRecordedData();
-	if (!sd)
-		lua_pushnil(L);
-	else
-	{
-		luax_pushtype(L, sd);
-		sd->release();
-	}
-	return 1;
-}
-
-int w_stopRecording(lua_State *L)
-{
-	if (luax_optboolean(L, 1, true))
-	{
-		love::sound::SoundData *sd = instance()->stopRecording(true);
-		if (!sd)
-			lua_pushnil(L);
-		else
-		{
-			luax_pushtype(L, sd);
-			sd->release();
-		}
-		return 1;
-	}
-	instance()->stopRecording(false);
-	return 0;
-}
-
-int w_canRecord(lua_State *L)
-{
-	luax_pushboolean(L, instance()->canRecord());
-	return 1;
-}
-
 int w_setDistanceModel(lua_State *L)
 {
 	const char *modelStr = luaL_checkstring(L, 1);
@@ -320,6 +277,21 @@ int w_getDistanceModel(lua_State *L)
 	return 1;
 }
 
+int w_getRecordingDevices(lua_State *L)
+{
+	const std::vector<RecordingDevice*> &devices = instance()->getRecordingDevices();
+
+	lua_createtable(L, devices.size(), 0);
+
+	for (unsigned int i = 0; i < devices.size(); i++)
+	{
+		luax_pushtype(L, devices[i]);
+		lua_rawseti(L, -2, i + 1);
+	}
+
+	return 1;
+}
+
 // List of functions to wrap.
 static const luaL_Reg functions[] =
 {
@@ -339,17 +311,16 @@ static const luaL_Reg functions[] =
 	{ "getVelocity", w_getVelocity },
 	{ "setDopplerScale", w_setDopplerScale },
 	{ "getDopplerScale", w_getDopplerScale },
-	/*{ "record", w_record },
-	{ "getRecordedData", w_getRecordedData },
-	{ "stopRecording", w_stopRecording },*/
 	{ "setDistanceModel", w_setDistanceModel },
 	{ "getDistanceModel", w_getDistanceModel },
+	{ "getRecordingDevices", w_getRecordingDevices },
 	{ 0, 0 }
 };
 
 static const lua_CFunction types[] =
 {
 	luaopen_source,
+	luaopen_recordingdevice,
 	0
 };
 

+ 1 - 0
src/modules/audio/wrap_Audio.h

@@ -26,6 +26,7 @@
 #include "common/runtime.h"
 #include "Audio.h"
 #include "wrap_Source.h"
+#include "wrap_RecordingDevice.h"
 
 namespace love
 {

+ 155 - 0
src/modules/audio/wrap_RecordingDevice.cpp

@@ -0,0 +1,155 @@
+/**
+ * Copyright (c) 2006-2016 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 "wrap_RecordingDevice.h"
+#include "wrap_Audio.h"
+
+#include "sound/SoundData.h"
+namespace love
+{
+namespace audio
+{
+
+RecordingDevice *luax_checkrecordingdevice(lua_State *L, int idx)
+{
+	return luax_checktype<RecordingDevice>(L, idx);
+}
+
+int w_RecordingDevice_start(lua_State *L)
+{
+	RecordingDevice *d = luax_checkrecordingdevice(L, 1);
+	if (lua_gettop(L) > 1)
+	{
+		int samples = (int) luaL_checkinteger(L, 2);
+		int sampleRate = (int) luaL_checkinteger(L, 3);
+		int bitDepth = (int) luaL_checkinteger(L, 4);
+		int channels = (int) luaL_checkinteger(L, 5);
+		luax_catchexcept(L, [&](){ 
+			lua_pushboolean(L, d->start(samples, sampleRate, bitDepth, channels));
+		});
+	}
+	else
+		luax_catchexcept(L, [&](){ lua_pushboolean(L, d->start()); });
+
+	return 1;
+}
+
+int w_RecordingDevice_stop(lua_State *L)
+{
+	RecordingDevice *d = luax_checkrecordingdevice(L, 1);
+	love::sound::SoundData *s = nullptr;
+
+	luax_catchexcept(L, [&](){ s = d->getData(); });
+
+	d->stop();
+
+	if (s != nullptr)
+	{
+		luax_pushtype(L, s);
+		s->release();
+	}
+	else
+		lua_pushnil(L);
+
+	return 1;
+}
+
+int w_RecordingDevice_getData(lua_State *L)
+{
+	RecordingDevice *d = luax_checkrecordingdevice(L, 1);
+	love::sound::SoundData *s = nullptr;
+
+	luax_catchexcept(L, [&](){ s = d->getData(); });
+
+	if (s != nullptr)
+	{
+		luax_pushtype(L, s);
+		s->release();
+	}
+	else
+		lua_pushnil(L);
+
+	return 1;
+}
+
+int w_RecordingDevice_getSampleCount(lua_State *L)
+{
+	RecordingDevice *d = luax_checkrecordingdevice(L, 1);
+	lua_pushnumber(L, d->getSampleCount());
+	return 1;
+}
+
+int w_RecordingDevice_getSampleRate(lua_State *L)
+{
+	RecordingDevice *d = luax_checkrecordingdevice(L, 1);
+	lua_pushnumber(L, d->getSampleRate());
+	return 1;
+}
+
+int w_RecordingDevice_getBitDepth(lua_State *L)
+{
+	RecordingDevice *d = luax_checkrecordingdevice(L, 1);
+	lua_pushnumber(L, d->getBitDepth());
+	return 1;
+}
+
+int w_RecordingDevice_getChannels(lua_State *L)
+{
+	RecordingDevice *d = luax_checkrecordingdevice(L, 1);
+	lua_pushnumber(L, d->getChannels());
+	return 1;
+}
+
+int w_RecordingDevice_getName(lua_State *L)
+{
+	RecordingDevice *d = luax_checkrecordingdevice(L, 1);
+	lua_pushstring(L, d->getName());
+	return 1;
+}
+
+int w_RecordingDevice_isRecording(lua_State *L)
+{
+	RecordingDevice *d = luax_checkrecordingdevice(L, 1);
+	lua_pushboolean(L, d->isRecording());
+	return 1;
+}
+
+static const luaL_Reg w_RecordingDevice_functions[] =
+{
+	{ "start", w_RecordingDevice_start },
+	{ "stop", w_RecordingDevice_stop },
+	{ "getData", w_RecordingDevice_getData },
+	{ "getSampleCount", w_RecordingDevice_getSampleCount },
+	{ "getSampleRate", w_RecordingDevice_getSampleRate },
+	{ "getBitDepth", w_RecordingDevice_getBitDepth },
+	{ "getChannels", w_RecordingDevice_getChannels },
+	{ "getName", w_RecordingDevice_getName },
+	{ "isRecording", w_RecordingDevice_isRecording },
+	{ 0, 0 }
+};
+
+extern "C" int luaopen_recordingdevice(lua_State *L)
+{
+	int ret = luax_register_type(L, &RecordingDevice::type, w_RecordingDevice_functions, nullptr);
+	return ret;
+}
+
+} //audio
+} //love

+ 40 - 0
src/modules/audio/wrap_RecordingDevice.h

@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2006-2016 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_AUDIO_WRAP_RECORDING_DEVICE_H
+#define LOVE_AUDIO_WRAP_RECORDING_DEVICE_H
+
+// LOVE
+#include "common/runtime.h"
+#include "RecordingDevice.h"
+
+namespace love
+{
+namespace audio
+{
+
+RecordingDevice *luax_checkrecordingdevice(lua_State *L, int idx);
+extern "C" int luaopen_recordingdevice(lua_State *L);
+
+} // audio
+} // love
+
+#endif //LOVE_AUDIO_WRAP_RECORDING_DEVICE_H
+

+ 17 - 0
src/modules/event/sdl/Event.cpp

@@ -112,6 +112,8 @@ Event::~Event()
 
 void Event::pump()
 {
+	exceptionIfInRenderPass();
+
 	SDL_Event e;
 
 	while (SDL_PollEvent(&e))
@@ -127,6 +129,8 @@ void Event::pump()
 
 Message *Event::wait()
 {
+	exceptionIfInRenderPass();
+
 	SDL_Event e;
 
 	if (SDL_WaitEvent(&e) != 1)
@@ -137,6 +141,8 @@ Message *Event::wait()
 
 void Event::clear()
 {
+	exceptionIfInRenderPass();
+
 	SDL_Event e;
 
 	while (SDL_PollEvent(&e))
@@ -147,6 +153,17 @@ void Event::clear()
 	love::event::Event::clear();
 }
 
+void Event::exceptionIfInRenderPass()
+{
+	// Some core OS graphics functionality (e.g. swap buffers on some platforms)
+	// happens inside SDL_PumpEvents - which is called by SDL_PollEvent and
+	// friends. It's probably a bad idea to call those functions while we're in
+	// a render pass.
+	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
+	if (gfx != nullptr && gfx->isPassActive())
+		throw love::Exception("Cannot call this function while a render pass is active in love.graphics.");
+}
+
 Message *Event::convert(const SDL_Event &e) const
 {
 	Message *msg = nullptr;

+ 2 - 0
src/modules/event/sdl/Event.h

@@ -68,6 +68,8 @@ public:
 
 private:
 
+	void exceptionIfInRenderPass();
+
 	Message *convert(const SDL_Event &e) const;
 	Message *convertJoystickEvent(const SDL_Event &e) const;
 	Message *convertWindowEvent(const SDL_Event &e) const;

+ 6 - 5
src/modules/event/wrap_Event.cpp

@@ -53,15 +53,16 @@ int w_poll(lua_State *L)
 	return 1;
 }
 
-int w_pump(lua_State *)
+int w_pump(lua_State *L)
 {
-	instance()->pump();
+	luax_catchexcept(L, [&]() { instance()->pump(); });
 	return 0;
 }
 
 int w_wait(lua_State *L)
 {
-	Message *m = instance()->wait();
+	Message *m = nullptr;
+	luax_catchexcept(L, [&]() { m = instance()->wait(); });
 	if (m)
 	{
 		int args = m->toLua(L);
@@ -85,9 +86,9 @@ int w_push(lua_State *L)
 	return 1;
 }
 
-int w_clear(lua_State *)
+int w_clear(lua_State *L)
 {
-	instance()->clear();
+	luax_catchexcept(L, [&]() { instance()->clear(); });
 	return 0;
 }
 

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

@@ -260,6 +260,8 @@ StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM>::Entry Graphics::featur
 	{ "multicanvasformats", FEATURE_MULTI_CANVAS_FORMATS },
 	{ "clampzero", FEATURE_CLAMP_ZERO },
 	{ "lighten", FEATURE_LIGHTEN },
+	{ "fullnpot", FEATURE_FULL_NPOT },
+	{ "pixelshaderhighp", FEATURE_PIXEL_SHADER_HIGHP },
 };
 
 StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM> Graphics::features(Graphics::featureEntries, sizeof(Graphics::featureEntries));
@@ -270,6 +272,7 @@ StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM>::Entry Graphics::syst
 	{ "texturesize", LIMIT_TEXTURE_SIZE },
 	{ "multicanvas", LIMIT_MULTI_CANVAS },
 	{ "canvasmsaa",  LIMIT_CANVAS_MSAA  },
+	{ "anisotropy",  LIMIT_ANISOTROPY   },
 };
 
 StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM> Graphics::systemLimits(Graphics::systemLimitEntries, sizeof(Graphics::systemLimitEntries));

+ 16 - 1
src/modules/graphics/Graphics.h

@@ -34,6 +34,8 @@ namespace love
 namespace graphics
 {
 
+const int MAX_COLOR_RENDER_TARGETS = 16;
+
 /**
  * Globally sets whether gamma correction is enabled. Ideally this should be set
  * prior to using any Graphics module function.
@@ -59,6 +61,14 @@ void gammaCorrectColor(Colorf &c);
  **/
 void unGammaCorrectColor(Colorf &c);
 
+class RenderOutsidePassException : public love::Exception
+{
+public:
+	RenderOutsidePassException()
+		: Exception("Cannot draw outside of a render pass!")
+	{}
+};
+
 class Graphics : public Module
 {
 public:
@@ -143,6 +153,8 @@ public:
 		FEATURE_MULTI_CANVAS_FORMATS,
 		FEATURE_CLAMP_ZERO,
 		FEATURE_LIGHTEN,
+		FEATURE_FULL_NPOT,
+		FEATURE_PIXEL_SHADER_HIGHP,
 		FEATURE_MAX_ENUM
 	};
 
@@ -159,6 +171,7 @@ public:
 		LIMIT_TEXTURE_SIZE,
 		LIMIT_MULTI_CANVAS,
 		LIMIT_CANVAS_MSAA,
+		LIMIT_ANISOTROPY,
 		LIMIT_MAX_ENUM
 	};
 
@@ -180,7 +193,7 @@ public:
 	struct Stats
 	{
 		int drawCalls;
-		int canvasSwitches;
+		int renderPasses;
 		int shaderSwitches;
 		int canvases;
 		int images;
@@ -259,6 +272,8 @@ public:
 	 **/
 	virtual bool isActive() const = 0;
 
+	virtual bool isPassActive() const = 0;
+
 	static bool getConstant(const char *in, DrawMode &out);
 	static bool getConstant(DrawMode in, const char *&out);
 

+ 56 - 409
src/modules/graphics/opengl/Canvas.cpp

@@ -36,11 +36,10 @@ namespace opengl
 static GLenum createFBO(GLuint &framebuffer, GLuint texture)
 {
 	// get currently bound fbo to reset to it later
-	GLint current_fbo;
-	glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &current_fbo);
+	GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
 
 	glGenFramebuffers(1, &framebuffer);
-	gl.bindFramebuffer(GL_FRAMEBUFFER, framebuffer);
+	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, framebuffer);
 
 	if (texture != 0)
 	{
@@ -53,14 +52,19 @@ static GLenum createFBO(GLuint &framebuffer, GLuint texture)
 
 	GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
 
-	// unbind framebuffer
-	gl.bindFramebuffer(GL_FRAMEBUFFER, (GLuint) current_fbo);
-
+	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
 	return status;
 }
 
-static GLenum createMSAABuffer(int width, int height, int &samples, GLenum iformat, GLuint &buffer)
+static bool createMSAABuffer(int width, int height, int &samples, GLenum iformat, GLuint &buffer)
 {
+	GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
+
+	// Temporary FBO used to clear the renderbuffer.
+	GLuint fbo = 0;
+	glGenFramebuffers(1, &fbo);
+	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo);
+
 	glGenRenderbuffers(1, &buffer);
 	glBindRenderbuffer(GL_RENDERBUFFER, buffer);
 
@@ -73,7 +77,7 @@ static GLenum createMSAABuffer(int width, int height, int &samples, GLenum iform
 
 	GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
 
-	if (status == GL_FRAMEBUFFER_COMPLETE)
+	if (status == GL_FRAMEBUFFER_COMPLETE && samples > 1)
 	{
 		// Initialize the buffer to transparent black.
 		glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
@@ -83,23 +87,22 @@ static GLenum createMSAABuffer(int width, int height, int &samples, GLenum iform
 	{
 		glDeleteRenderbuffers(1, &buffer);
 		buffer = 0;
+		samples = 0;
 	}
 
-	return status;
+	gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo);
+	gl.deleteFramebuffer(fbo);
+
+	return status == GL_FRAMEBUFFER_COMPLETE && samples > 1;
 }
 
 love::Type Canvas::type("Canvas", &Texture::type);
-Canvas *Canvas::current = nullptr;
-OpenGL::Viewport Canvas::systemViewport = OpenGL::Viewport();
-bool Canvas::screenHasSRGB = false;
 int Canvas::canvasCount = 0;
 
 Canvas::Canvas(int width, int height, Format format, int msaa)
 	: fbo(0)
-    , resolve_fbo(0)
 	, texture(0)
     , msaa_buffer(0)
-	, depth_stencil(0)
 	, format(format)
     , requested_samples(msaa)
 	, actual_samples(0)
@@ -144,65 +147,18 @@ Canvas::Canvas(int width, int height, Format format, int msaa)
 Canvas::~Canvas()
 {
 	--canvasCount;
-
-	// reset framebuffer if still using this one
-	if (current == this)
-		stopGrab();
-
 	unloadVolatile();
 }
 
-bool Canvas::createMSAAFBO(GLenum internalformat)
-{
-	actual_samples = requested_samples;
-
-	if (actual_samples <= 1)
-	{
-		actual_samples = 0;
-		return false;
-	}
-
-	// Create our FBO without a texture.
-	status = createFBO(fbo, 0);
-
-	GLuint previous = gl.getDefaultFBO();
-	if (current != this)
-	{
-		if (current != nullptr)
-			previous = current->fbo;
-
-		gl.bindFramebuffer(GL_FRAMEBUFFER, fbo);
-	}
-
-	// Create and attach the MSAA buffer for our FBO.
-	status = createMSAABuffer(width, height, actual_samples, internalformat, msaa_buffer);
-
-	// Create the FBO used for the MSAA resolve, and attach the texture.
-	if (status == GL_FRAMEBUFFER_COMPLETE)
-		status = createFBO(resolve_fbo, texture);
-
-	if (status != GL_FRAMEBUFFER_COMPLETE)
-	{
-		// Clean up.
-		glDeleteFramebuffers(1, &fbo);
-		glDeleteFramebuffers(1, &resolve_fbo);
-		glDeleteRenderbuffers(1, &msaa_buffer);
-		fbo = msaa_buffer = resolve_fbo = 0;
-		actual_samples = 0;
-	}
-
-	if (current != this)
-		gl.bindFramebuffer(GL_FRAMEBUFFER, previous);
-
-	return status == GL_FRAMEBUFFER_COMPLETE;
-}
-
 bool Canvas::loadVolatile()
 {
+	if (texture != 0)
+		return true;
+
 	OpenGL::TempDebugGroup debuggroup("Canvas load");
 
-	fbo = depth_stencil = texture = 0;
-	resolve_fbo = msaa_buffer = 0;
+	fbo = texture = 0;
+	msaa_buffer = 0;
 	status = GL_FRAMEBUFFER_COMPLETE;
 
 	// glTexImage2D is guaranteed to error in this case.
@@ -240,8 +196,8 @@ bool Canvas::loadVolatile()
 	while (glGetError() != GL_NO_ERROR)
 		/* Clear the error buffer. */;
 
-	glTexImage2D(GL_TEXTURE_2D, 0, iformat, width, height, 0,
-	             externalformat, textype, nullptr);
+	glTexImage2D(GL_TEXTURE_2D, 0, iformat, width, height, 0, externalformat,
+	             textype, nullptr);
 
 	if (glGetError() != GL_NO_ERROR)
 	{
@@ -251,24 +207,27 @@ bool Canvas::loadVolatile()
 		return false;
 	}
 
-	// Try to create a MSAA FBO if requested. On failure (or no requested MSAA),
-	// fall back to a regular FBO.
-	if (!createMSAAFBO(internalformat))
-		status = createFBO(fbo, texture);
+	// Create a canvas-local FBO used for glReadPixels as well as MSAA blitting.
+	status = createFBO(fbo, texture);
 
 	if (status != GL_FRAMEBUFFER_COMPLETE)
-    {
-        if (fbo != 0)
-        {
-			glDeleteFramebuffers(1, &fbo);
-            fbo = 0;
-        }
+	{
+		if (fbo != 0)
+		{
+			gl.deleteFramebuffer(fbo);
+			fbo = 0;
+		}
 		return false;
-    }
+	}
+
+	actual_samples = requested_samples == 1 ? 0 : requested_samples;
+
+	if (actual_samples > 0 && !createMSAABuffer(width, height, actual_samples, internalformat, msaa_buffer))
+		actual_samples = 0;
 
 	size_t prevmemsize = texture_memory;
 
-	texture_memory = (getFormatBitsPerPixel(format) * width * height) / 8;
+	texture_memory = ((getFormatBitsPerPixel(format) * width) / 8) * height;
 	if (msaa_buffer != 0)
 		texture_memory += (texture_memory * actual_samples);
 
@@ -279,33 +238,35 @@ bool Canvas::loadVolatile()
 
 void Canvas::unloadVolatile()
 {
-	glDeleteFramebuffers(1, &fbo);
-	glDeleteFramebuffers(1, &resolve_fbo);
+	if (fbo != 0)
+		gl.deleteFramebuffer(fbo);
 
-	glDeleteRenderbuffers(1, &depth_stencil);
-	glDeleteRenderbuffers(1, &msaa_buffer);
+	if (msaa_buffer != 0)
+		glDeleteRenderbuffers(1, &msaa_buffer);
 
-	gl.deleteTexture(texture);
+	if (texture != 0)
+		gl.deleteTexture(texture);
 
 	fbo = 0;
-	resolve_fbo = 0;
-	depth_stencil = 0;
 	msaa_buffer = 0;
 	texture = 0;
 
-	attachedCanvases.clear();
-
 	gl.updateTextureMemorySize(texture_memory, 0);
 	texture_memory = 0;
 }
 
 void Canvas::drawv(const Matrix4 &t, const Vertex *v)
 {
-	// FIXME: This doesn't handle cases where the Canvas is used as a texture
-	// in a SpriteBatch, Mesh, or ParticleSystem, or when the Canvas is used in
-	// a shader as a non-default texture.
-	if (Canvas::current == this)
-		throw love::Exception("Cannot draw a Canvas to itself.");
+	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
+	if (gfx != nullptr)
+	{
+		const PassInfo &info = gfx->getActivePass();
+		for (const auto &attachment : info.colorAttachments)
+		{
+			if (attachment.canvas == this)
+				throw love::Exception("Cannot render a Canvas to itself!");
+		}
+	}
 
 	OpenGL::TempDebugGroup debuggroup("Canvas draw");
 
@@ -378,320 +339,6 @@ const void *Canvas::getHandle() const
 	return &texture;
 }
 
-void Canvas::setupGrab()
-{
-	// already grabbing
-	if (current == this)
-		return;
-
-	// cleanup after previous Canvas
-	if (current != nullptr)
-	{
-		systemViewport = current->systemViewport;
-		current->stopGrab(true);
-	}
-	else
-		systemViewport = gl.getViewport();
-
-	// indicate we are using this Canvas.
-	current = this;
-
-	// bind the framebuffer object.
-	gl.bindFramebuffer(GL_FRAMEBUFFER, fbo);
-	gl.setViewport({0, 0, width, height});
-
-	// Set up the projection matrix
-	gl.matrices.projection.push_back(Matrix4::ortho(0.0, (float) width, 0.0, (float) height));
-}
-
-void Canvas::startGrab(const std::vector<Canvas *> &canvases)
-{
-	// Whether the new canvas list is different from the old one.
-	// A more thorough check is done below.
-	bool canvaseschanged = canvases.size() != attachedCanvases.size();
-	bool hasSRGBcanvas = getSizedFormat(format) == FORMAT_SRGB;
-
-	if (canvases.size() > 0)
-	{
-		if ((int) canvases.size() + 1 > gl.getMaxRenderTargets())
-			throw love::Exception("This system can't simultaneously render to %d canvases.", canvases.size()+1);
-
-		if (actual_samples != 0)
-			throw love::Exception("Multi-canvas rendering is not supported with MSAA.");
-	}
-
-	bool multiformatsupported = isMultiFormatMultiCanvasSupported();
-
-	for (size_t i = 0; i < canvases.size(); i++)
-	{
-		if (canvases[i]->getWidth() != width || canvases[i]->getHeight() != height)
-			throw love::Exception("All canvases must have the same dimensions.");
-
-		Format otherformat = canvases[i]->getTextureFormat();
-
-		if (otherformat != format && !multiformatsupported)
-			throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats.");
-
-		if (canvases[i]->getMSAA() != 0)
-			throw love::Exception("Multi-canvas rendering is not supported with MSAA.");
-
-		if (!canvaseschanged && canvases[i] != attachedCanvases[i])
-			canvaseschanged = true;
-
-		if (getSizedFormat(otherformat) == FORMAT_SRGB)
-			hasSRGBcanvas = true;
-	}
-
-	OpenGL::TempDebugGroup debuggroup("Canvas set");
-
-	setupGrab();
-
-	// Make sure the correct sRGB setting is used when drawing to the canvases.
-	if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control)
-	{
-		if (hasSRGBcanvas && !gl.hasFramebufferSRGB())
-			gl.setFramebufferSRGB(true);
-		else if (!hasSRGBcanvas && gl.hasFramebufferSRGB())
-			gl.setFramebufferSRGB(false);
-	}
-
-	// Don't attach anything if there's nothing to change.
-	if (!canvaseschanged)
-		return;
-
-	// Attach the canvas textures to the active FBO and set up MRTs.
-	std::vector<GLenum> drawbuffers;
-	drawbuffers.reserve(canvases.size() + 1);
-
-	drawbuffers.push_back(GL_COLOR_ATTACHMENT0);
-
-	// Attach the canvas textures to the currently bound framebuffer.
-	for (int i = 0; i < (int) canvases.size(); i++)
-	{
-		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1 + i,
-		                       GL_TEXTURE_2D, *(GLuint *) canvases[i]->getHandle(), 0);
-
-		drawbuffers.push_back(GL_COLOR_ATTACHMENT1 + i);
-	}
-
-	// set up multiple render targets
-	glDrawBuffers((int) drawbuffers.size(), &drawbuffers[0]);
-
-	// We want to avoid reference cycles, so we don't retain the attached
-	// Canvases here. The code in Graphics::setCanvas retains them.
-
-	attachedCanvases = canvases;
-}
-
-void Canvas::startGrab()
-{
-	OpenGL::TempDebugGroup debuggroup("Canvas set");
-
-	setupGrab();
-
-	// Make sure the correct sRGB setting is used when drawing to the canvas.
-	if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control)
-	{
-		bool isSRGB = getSizedFormat(format) == FORMAT_SRGB;
-		if (isSRGB && !gl.hasFramebufferSRGB())
-			gl.setFramebufferSRGB(true);
-		else if (!isSRGB && gl.hasFramebufferSRGB())
-			gl.setFramebufferSRGB(false);
-	}
-
-	if (attachedCanvases.size() > 0)
-	{
-		// Make sure the FBO is only using a single draw buffer.
-		// GLES3 only has glDrawBuffers, so we avoid using glDrawBuffer.
-		const GLenum buffers[] = {GL_COLOR_ATTACHMENT0};
-		glDrawBuffers(1, buffers);
-
-		attachedCanvases.clear();
-	}
-}
-
-void Canvas::stopGrab(bool switchingToOtherCanvas)
-{
-	// i am not grabbing. leave me alone
-	if (current != this)
-		return;
-
-	OpenGL::TempDebugGroup debuggroup("Canvas un-set");
-
-	// Make sure the canvas texture is up to date if we're using MSAA.
-	resolveMSAA(false);
-
-	if (gl.matrices.projection.size() > 1)
-		gl.matrices.projection.pop_back();
-
-	if (!switchingToOtherCanvas)
-	{
-		// bind system framebuffer.
-		gl.bindFramebuffer(GL_FRAMEBUFFER, gl.getDefaultFBO());
-		current = nullptr;
-		gl.setViewport(systemViewport);
-
-		if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control)
-		{
-			if (screenHasSRGB && !gl.hasFramebufferSRGB())
-				gl.setFramebufferSRGB(true);
-			else if (!screenHasSRGB && gl.hasFramebufferSRGB())
-				gl.setFramebufferSRGB(false);
-		}
-	}
-}
-
-bool Canvas::checkCreateStencil()
-{
-	// Do nothing if we've already created the stencil buffer.
-	if (depth_stencil != 0)
-		return true;
-
-	OpenGL::TempDebugGroup debuggroup("Canvas create stencil");
-
-	if (current != this)
-		gl.bindFramebuffer(GL_FRAMEBUFFER, fbo);
-
-	GLenum format = GL_STENCIL_INDEX8;
-	std::vector<GLenum> attachments = {GL_STENCIL_ATTACHMENT};
-
-	// Prefer a combined depth/stencil buffer.
-	if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object)
-	{
-		format = GL_DEPTH24_STENCIL8;
-		attachments = {GL_DEPTH_STENCIL_ATTACHMENT};
-	}
-	else if (GLAD_EXT_packed_depth_stencil || GLAD_OES_packed_depth_stencil)
-	{
-		format = GL_DEPTH24_STENCIL8;
-		attachments = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
-	}
-
-	glGenRenderbuffers(1, &depth_stencil);
-	glBindRenderbuffer(GL_RENDERBUFFER, depth_stencil);
-
-	if (requested_samples > 1)
-		glRenderbufferStorageMultisample(GL_RENDERBUFFER, requested_samples, format, width, height);
-	else
-		glRenderbufferStorage(GL_RENDERBUFFER, format, width, height);
-
-	// Attach the buffer to the framebuffer object.
-	for (GLenum attachment : attachments)
-		glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, depth_stencil);
-
-	glBindRenderbuffer(GL_RENDERBUFFER, 0);
-
-	bool success = glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
-
-	// We don't want the stencil buffer filled with garbage.
-	if (success)
-		glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-	else
-	{
-		glDeleteRenderbuffers(1, &depth_stencil);
-		depth_stencil = 0;
-	}
-
-	if (current && current != this)
-		gl.bindFramebuffer(GL_FRAMEBUFFER, current->fbo);
-	else if (!current)
-		gl.bindFramebuffer(GL_FRAMEBUFFER, gl.getDefaultFBO());
-
-	return success;
-}
-
-love::image::ImageData *Canvas::newImageData(love::image::Image *image, int x, int y, int w, int h)
-{
-	if (x < 0 || y < 0 || w <= 0 || h <= 0 || (x + w) > width || (y + h) > height)
-		throw love::Exception("Invalid ImageData rectangle dimensions.");
-
-	GLenum datatype = GL_UNSIGNED_BYTE;
-	image::ImageData::Format imageformat = image::ImageData::FORMAT_RGBA8;
-
-	switch (getSizedFormat(format))
-	{
-	case FORMAT_RGB10A2: // FIXME: Conversions aren't supported in GLES
-		datatype = GL_UNSIGNED_SHORT;
-		imageformat = image::ImageData::FORMAT_RGBA16;
-		break;
-	case FORMAT_R16F:
-	case FORMAT_RG16F:
-	case FORMAT_RGBA16F:
-	case FORMAT_RG11B10F: // FIXME: Conversions aren't supported in GLES
-		datatype = GL_HALF_FLOAT;
-		imageformat = image::ImageData::FORMAT_RGBA16F;
-		break;
-	case FORMAT_R32F:
-	case FORMAT_RG32F:
-	case FORMAT_RGBA32F:
-		datatype = GL_FLOAT;
-		imageformat = image::ImageData::FORMAT_RGBA32F;
-		break;
-	default:
-		break;
-	}
-
-	size_t size = w * h * image::ImageData::getPixelSize(imageformat);
-	uint8 *pixels = nullptr;
-	try
-	{
-		pixels = new uint8[size];
-	}
-	catch (std::bad_alloc &)
-	{
-		throw love::Exception("Out of memory.");
-	}
-
-	// Make sure the canvas texture is up to date if we're using MSAA.
-	if (current == this)
-		resolveMSAA(false);
-
-	// Our texture is attached to 'resolve_fbo' when we use MSAA.
-	if (resolve_fbo != 0)
-		gl.bindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo);
-	else
-		gl.bindFramebuffer(GL_FRAMEBUFFER, fbo);
-
-	glReadPixels(x, y, w, h, GL_RGBA, datatype, pixels);
-
-	GLuint prevfbo = current ? current->fbo : gl.getDefaultFBO();
-	gl.bindFramebuffer(GL_FRAMEBUFFER, prevfbo);
-
-	// The new ImageData now owns the pixel data, so we don't delete it here.
-	return image->newImageData(w, h, imageformat, pixels, true);
-}
-
-bool Canvas::resolveMSAA(bool restoreprev)
-{
-	if (resolve_fbo == 0 || msaa_buffer == 0)
-		return false;
-
-	OpenGL::TempDebugGroup debuggroup("Canvas MSAA resolve");
-
-	GLint w = width;
-	GLint h = height;
-
-	// Do the MSAA resolve by blitting the MSAA renderbuffer to the texture.
-	// For many of the MSAA extensions that add suffixes to the functions, we
-	// assign function pointers in OpenGL.cpp so we can call the core functions.
-
-	gl.bindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
-	gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo);
-
-	if (GLAD_APPLE_framebuffer_multisample)
-		glResolveMultisampleFramebufferAPPLE();
-	else
-		glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
-
-	if (restoreprev)
-	{
-		GLuint fbo = current ? current->fbo : gl.getDefaultFBO();
-		gl.bindFramebuffer(GL_FRAMEBUFFER, fbo);
-	}
-
-	return true;
-}
-
 Canvas::Format Canvas::getSizedFormat(Canvas::Format format)
 {
 	switch (format)
@@ -950,7 +597,7 @@ bool Canvas::isFormatSupported(Canvas::Format format)
 
 	GLuint fbo = 0;
 	supported = (createFBO(fbo, texture) == GL_FRAMEBUFFER_COMPLETE);
-	glDeleteFramebuffers(1, &fbo);
+	gl.deleteFramebuffer(fbo);
 
 	gl.deleteTexture(texture);
 

+ 15 - 37
src/modules/graphics/opengl/Canvas.h

@@ -84,26 +84,6 @@ public:
 	bool setWrap(const Texture::Wrap &w) override;
 	const void *getHandle() const override;
 
-	/**
-	 * @param canvases A list of other canvases to temporarily attach to this one,
-	 * to allow drawing to multiple canvases at once.
-	 **/
-	void startGrab(const std::vector<Canvas *> &canvases);
-	void startGrab();
-	void stopGrab(bool switchingToOtherCanvas = false);
-
-	/**
-	 * Create and attach a stencil buffer to this Canvas' framebuffer, if necessary.
-	 **/
-	bool checkCreateStencil();
-
-	love::image::ImageData *newImageData(love::image::Image *image, int x, int y, int w, int h);
-
-	inline const std::vector<Canvas *> &getAttachedCanvases() const
-	{
-		return attachedCanvases;
-	}
-
 	inline GLenum getStatus() const
 	{
 		return status;
@@ -119,19 +99,26 @@ public:
 		return actual_samples;
 	}
 
+	inline int getRequestedMSAA() const
+	{
+		return requested_samples;
+	}
+
+	inline ptrdiff_t getMSAAHandle() const
+	{
+		return msaa_buffer;
+	}
+
+	inline GLuint getFBO() const
+	{
+		return fbo;
+	}
+
 	static Format getSizedFormat(Format format);
 	static bool isSupported();
 	static bool isMultiFormatMultiCanvasSupported();
 	static bool isFormatSupported(Format format);
 
-	static Canvas *current;
-
-	// The viewport dimensions of the system (default) framebuffer.
-	static OpenGL::Viewport systemViewport;
-
-	// Whether the main screen should have linear -> sRGB conversions enabled.
-	static bool screenHasSRGB;
-
 	static int canvasCount;
 
 	static bool getConstant(const char *in, Format &out);
@@ -139,29 +126,20 @@ public:
 
 private:
 
-	void setupGrab();
-
-	bool createMSAAFBO(GLenum internalformat);
-	bool resolveMSAA(bool restoreprev);
-
 	void drawv(const Matrix4 &t, const Vertex *v);
 
 	static void convertFormat(Format format, GLenum &internalformat, GLenum &externalformat, GLenum &type);
 	static size_t getFormatBitsPerPixel(Format format);
 
 	GLuint fbo;
-	GLuint resolve_fbo;
 
 	GLuint texture;
 	GLuint msaa_buffer;
-	GLuint depth_stencil;
 
 	Format format;
 
 	GLenum status;
 
-	std::vector<Canvas *> attachedCanvases;
-
 	int requested_samples;
 	int actual_samples;
 

File diff suppressed because it is too large
+ 591 - 237
src/modules/graphics/opengl/Graphics.cpp


+ 105 - 38
src/modules/graphics/opengl/Graphics.h

@@ -24,6 +24,7 @@
 // STD
 #include <stack>
 #include <vector>
+#include <unordered_map>
 
 // OpenGL
 #include "OpenGL.h"
@@ -35,10 +36,10 @@
 #include "image/Image.h"
 #include "image/ImageData.h"
 
-#include "window/Window.h"
-
 #include "video/VideoStream.h"
 
+#include "math/Transform.h"
+
 #include "Font.h"
 #include "Image.h"
 #include "graphics/Quad.h"
@@ -53,21 +54,63 @@
 
 namespace love
 {
+
+class Reference;
+
 namespace graphics
 {
 namespace opengl
 {
 
+struct PassInfo
+{
+	enum BeginAction
+	{
+		BEGIN_LOAD,
+		BEGIN_CLEAR,
+		BEGIN_DISCARD,
+	};
+
+	enum EndAction
+	{
+		END_STORE,
+		END_DISCARD,
+	};
+
+	struct ColorAttachment
+	{
+		Canvas *canvas = nullptr;
+		Colorf clearColor = Colorf(0.0f, 0.0f, 0.0f, 0.0f);
+		BeginAction beginAction = BEGIN_LOAD;
+	};
+
+	ColorAttachment colorAttachments[MAX_COLOR_RENDER_TARGETS];
+	int colorAttachmentCount = 0;
+
+	bool stencil = false;
+
+	bool addColorAttachment(const ColorAttachment &attachment)
+	{
+		if (colorAttachmentCount + 1 < MAX_COLOR_RENDER_TARGETS)
+		{
+			colorAttachments[colorAttachmentCount++] = attachment;
+			return true;
+		}
+
+		return false;
+	}
+};
+
 class Graphics : public love::graphics::Graphics
 {
 public:
 
-	struct OptionalColorf
-	{
-		float r, g, b, a;
-		bool enabled;
+	typedef void (*ScreenshotCallback)(love::image::ImageData *i, Reference *ref, void *ud);
 
-		Colorf toColor() const { return Colorf(r, g, b, a); }
+	struct ScreenshotInfo
+	{
+		ScreenshotCallback callback;
+		Reference *ref;
 	};
 
 	Graphics();
@@ -92,25 +135,19 @@ public:
 	 **/
 	void reset();
 
-	/**
-	 * Clears the screen to a specific color.
-	 **/
-	void clear(Colorf c);
+	void beginPass(PassInfo::BeginAction beginAction, Colorf clearColor);
+	void beginPass(const PassInfo &info);
 
-	/**
-	 * Clears each active canvas to a different color.
-	 **/
-	void clear(const std::vector<OptionalColorf> &colors);
+	void endPass();
+	void endPass(int sX, int sY, int sW, int sH, const ScreenshotInfo *info, void *screenshotCallbackData);
 
-	/**
-	 * Discards the contents of the screen.
-	 **/
-	void discard(const std::vector<bool> &colorbuffers, bool stencil);
+	const PassInfo &getActivePass() const;
+	virtual bool isPassActive() const;
 
 	/**
 	 * Flips buffers. (Rendered geometry is presented on screen).
 	 **/
-	void present();
+	void present(void *screenshotCallbackData);
 
 	/**
 	 * Gets the width of the current graphics viewport.
@@ -122,6 +159,9 @@ public:
 	 **/
 	int getHeight() const;
 
+	int getPassWidth() const;
+	int getPassHeight() const;
+
 	/**
 	 * True if a graphics viewport is set.
 	 **/
@@ -231,13 +271,6 @@ public:
 
 	Shader *getShader() const;
 
-	void setCanvas(Canvas *canvas);
-	void setCanvas(const std::vector<Canvas *> &canvases);
-	void setCanvas(const std::vector<StrongRef<Canvas>> &canvases);
-	void setCanvas();
-
-	std::vector<Canvas *> getCanvas() const;
-
 	/**
 	 * Sets the enabled color components when rendering.
 	 **/
@@ -330,6 +363,9 @@ public:
 	 **/
 	bool isWireframe() const;
 
+	void draw(Drawable *drawable, const Matrix4 &m);
+	void drawq(Texture *texture, Quad *quad, const Matrix4 &m);
+
 	/**
 	 * Draws text at the specified coordinates
 	 **/
@@ -422,12 +458,7 @@ public:
 	 **/
 	void polygon(DrawMode mode, const float *coords, size_t count);
 
-	/**
-	 * Creates a screenshot of the view and saves it to the default folder.
-	 * @param image The love.image module.
-	 * @param copyAlpha If the alpha channel should be copied or set to full opacity (1.0).
-	 **/
-	love::image::ImageData *newScreenshot(love::image::Image *image, bool copyAlpha = true);
+	void captureScreenshot(const ScreenshotInfo &info);
 
 	/**
 	 * Returns system-dependent renderer information.
@@ -459,6 +490,10 @@ public:
 	void translate(float x, float y);
 	void shear(float kx, float ky);
 	void origin();
+
+	void applyTransform(love::math::Transform *transform);
+	void replaceTransform(love::math::Transform *transform);
+
 	Vector transformPoint(Vector point);
 	Vector inverseTransformPoint(Vector point);
 
@@ -488,8 +523,6 @@ private:
 		StrongRef<Font> font;
 		StrongRef<Shader> shader;
 
-		std::vector<StrongRef<Canvas>> canvases;
-
 		ColorMask colorMask = ColorMask(true, true, true, true);
 
 		bool wireframe = false;
@@ -500,18 +533,46 @@ private:
 		float defaultMipmapSharpness = 0.0f;
 	};
 
+	struct CurrentPass
+	{
+		PassInfo info;
+		bool active = false;
+	};
+
+	struct PassBufferInfo
+	{
+		bool stencil;
+		Canvas *canvases[MAX_COLOR_RENDER_TARGETS];
+	};
+
+	struct CachedRenderbuffer
+	{
+		int w;
+		int h;
+		int samples;
+		GLenum attachments[2];
+		GLuint renderbuffer;
+	};
+
 	void restoreState(const DisplayState &s);
 	void restoreStateChecked(const DisplayState &s);
 
+	void bindCachedFBOForPass(const PassInfo &pass);
+	void discard(OpenGL::FramebufferTarget target, const std::vector<bool> &colorbuffers, bool depthstencil);
+	GLuint attachCachedStencilBuffer(int w, int h, int samples);
+
 	void checkSetDefaultFont();
 
 	int calculateEllipsePoints(float rx, float ry) const;
 
-	StrongRef<love::window::Window> currentWindow;
-
 	StrongRef<Font> defaultFont;
 
-	std::vector<double> pixelSizeStack; // stores current size of a pixel (needed for line drawing)
+	std::vector<double> pixelScaleStack;
+
+	std::vector<ScreenshotInfo> pendingScreenshotCallbacks;
+
+	std::unordered_map<uint32, GLuint> framebufferObjects;
+	std::vector<CachedRenderbuffer> stencilBuffers;
 
 	QuadIndices *quadIndices;
 
@@ -520,11 +581,17 @@ private:
 	bool created;
 	bool active;
 
+	bool canCaptureScreenshot;
+
+	CurrentPass currentPass;
+
 	bool writingToStencil;
 
 	std::vector<DisplayState> states;
 	std::vector<StackType> stackTypes; // Keeps track of the pushed stack types.
 
+	int renderPassCount;
+
 	static const size_t MAX_USER_STACK_DEPTH = 64;
 
 }; // Graphics

+ 3 - 0
src/modules/graphics/opengl/Image.cpp

@@ -283,6 +283,9 @@ void Image::loadFromImageData()
 
 bool Image::loadVolatile()
 {
+	if (texture != 0)
+		return true;
+
 	OpenGL::TempDebugGroup debuggroup("Image load");
 
 	if (isCompressed() && !hasCompressedTextureSupport(cdata[0]->getFormat(), sRGB))

+ 21 - 3
src/modules/graphics/opengl/Mesh.cpp

@@ -311,7 +311,7 @@ bool Mesh::isAttributeEnabled(const std::string &name) const
 	return it->second.enabled;
 }
 
-void Mesh::attachAttribute(const std::string &name, Mesh *mesh)
+void Mesh::attachAttribute(const std::string &name, Mesh *mesh, const std::string &attachname)
 {
 	if (mesh != this)
 	{
@@ -333,10 +333,10 @@ void Mesh::attachAttribute(const std::string &name, Mesh *mesh)
 
 	newattrib.mesh = mesh;
 	newattrib.enabled = oldattrib.mesh ? oldattrib.enabled : true;
-	newattrib.index = mesh->getAttributeIndex(name);
+	newattrib.index = mesh->getAttributeIndex(attachname);
 
 	if (newattrib.index < 0)
-		throw love::Exception("The specified mesh does not have a vertex attribute named '%s'", name.c_str());
+		throw love::Exception("The specified mesh does not have a vertex attribute named '%s'", attachname.c_str());
 
 	if (newattrib.mesh != this)
 		newattrib.mesh->retain();
@@ -347,6 +347,24 @@ void Mesh::attachAttribute(const std::string &name, Mesh *mesh)
 		oldattrib.mesh->release();
 }
 
+bool Mesh::detachAttribute(const std::string &name)
+{
+	auto it = attachedAttributes.find(name);
+
+	if (it != attachedAttributes.end() && it->second.mesh != this)
+	{
+		it->second.mesh->release();
+		attachedAttributes.erase(it);
+
+		if (getAttributeIndex(name) != -1)
+			attachAttribute(name, this, name);
+
+		return true;
+	}
+
+	return false;
+}
+
 void *Mesh::mapVertexData()
 {
 	return vbo->map();

+ 2 - 1
src/modules/graphics/opengl/Mesh.h

@@ -140,7 +140,8 @@ public:
 	 * Attaches a vertex attribute from another Mesh to this one. The attribute
 	 * will be used when drawing this Mesh.
 	 **/
-	void attachAttribute(const std::string &name, Mesh *mesh);
+	void attachAttribute(const std::string &name, Mesh *mesh, const std::string &attachname);
+	bool detachAttribute(const std::string &name);
 
 	void *mapVertexData();
 	void unmapVertexData(size_t modifiedoffset = 0, size_t modifiedsize = -1);

+ 109 - 16
src/modules/graphics/opengl/OpenGL.cpp

@@ -23,7 +23,6 @@
 #include "OpenGL.h"
 
 #include "Shader.h"
-#include "Canvas.h"
 #include "common/Exception.h"
 
 // C++
@@ -66,6 +65,7 @@ static void *LOVEGetProcAddress(const char *name)
 OpenGL::OpenGL()
 	: stats()
 	, contextInitialized(false)
+	, pixelShaderHighpSupported(false)
 	, maxAnisotropy(1.0f)
 	, maxTextureSize(0)
 	, maxRenderTargets(1)
@@ -75,7 +75,6 @@ OpenGL::OpenGL()
 	, state()
 {
 	matrices.transform.reserve(10);
-	matrices.projection.reserve(2);
 }
 
 bool OpenGL::initContext()
@@ -135,6 +134,10 @@ void OpenGL::setupContext()
 	else
 		state.pointSize = 1.0f;
 
+	for (int i = 0; i < 2; i++)
+		state.boundFramebuffers[i] = std::numeric_limits<GLuint>::max();
+	bindFramebuffer(FRAMEBUFFER_ALL, getDefaultFBO());
+
 	if (GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB
 		|| GLAD_EXT_sRGB_write_control)
 	{
@@ -266,6 +269,16 @@ void OpenGL::initOpenGLFunctions()
 
 void OpenGL::initMaxValues()
 {
+	if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0)
+	{
+		GLint range = 0;
+		GLint precision = 0;
+		glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_HIGH_FLOAT, &range, &precision);
+		pixelShaderHighpSupported = range > 0;
+	}
+	else
+		pixelShaderHighpSupported = true;
+
 	// We'll need this value to clamp anisotropy.
 	if (GLAD_EXT_texture_filter_anisotropic)
 		glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropy);
@@ -283,7 +296,7 @@ void OpenGL::initMaxValues()
 		glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxdrawbuffers);
 	}
 
-	maxRenderTargets = std::min(maxattachments, maxdrawbuffers);
+	maxRenderTargets = std::max(std::min(maxattachments, maxdrawbuffers), 1);
 
 	if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object
 		|| GLAD_EXT_framebuffer_multisample || GLAD_APPLE_framebuffer_multisample
@@ -304,10 +317,9 @@ void OpenGL::initMaxValues()
 void OpenGL::initMatrices()
 {
 	matrices.transform.clear();
-	matrices.projection.clear();
 
 	matrices.transform.push_back(Matrix4());
-	matrices.projection.push_back(Matrix4());
+	matrices.projection = Matrix4();
 }
 
 void OpenGL::createDefaultTexture()
@@ -361,7 +373,7 @@ void OpenGL::prepareDraw()
 	// because uniform uploads can be significantly slower than glLoadMatrix.
 	if (GLAD_VERSION_1_0)
 	{
-		const Matrix4 &curproj = matrices.projection.back();
+		const Matrix4 &curproj = matrices.projection;
 		const Matrix4 &curxform = matrices.transform.back();
 
 		const Matrix4 &lastproj = state.lastProjectionMatrix;
@@ -374,7 +386,7 @@ void OpenGL::prepareDraw()
 			glLoadMatrixf(curproj.getElements());
 			glMatrixMode(GL_MODELVIEW);
 
-			state.lastProjectionMatrix = matrices.projection.back();
+			state.lastProjectionMatrix = matrices.projection;
 		}
 
 		// Same with the transform matrix.
@@ -463,7 +475,7 @@ void OpenGL::useVertexAttribArrays(uint32 arraybits)
 		glVertexAttrib4f(ATTRIB_COLOR, 1.0f, 1.0f, 1.0f, 1.0f);
 }
 
-void OpenGL::setViewport(const OpenGL::Viewport &v)
+void OpenGL::setViewport(const OpenGL::Viewport &v, bool canvasActive)
 {
 	glViewport(v.x, v.y, v.w, v.h);
 	state.viewport = v;
@@ -471,7 +483,7 @@ void OpenGL::setViewport(const OpenGL::Viewport &v)
 	// glScissor starts from the lower left, so we compensate when setting the
 	// scissor. When the viewport is changed, we need to manually update the
 	// scissor again.
-	setScissor(state.scissor);
+	setScissor(state.scissor, canvasActive);
 }
 
 OpenGL::Viewport OpenGL::getViewport() const
@@ -479,9 +491,9 @@ OpenGL::Viewport OpenGL::getViewport() const
 	return state.viewport;
 }
 
-void OpenGL::setScissor(const OpenGL::Viewport &v)
+void OpenGL::setScissor(const OpenGL::Viewport &v, bool canvasActive)
 {
-	if (Canvas::current)
+	if (canvasActive)
 		glScissor(v.x, v.y, v.w, v.h);
 	else
 	{
@@ -526,12 +538,53 @@ bool OpenGL::hasFramebufferSRGB() const
 	return state.framebufferSRGBEnabled;
 }
 
-void OpenGL::bindFramebuffer(GLenum target, GLuint framebuffer)
+void OpenGL::bindFramebuffer(FramebufferTarget target, GLuint framebuffer)
 {
-	glBindFramebuffer(target, framebuffer);
+	bool bindingmodified = false;
+
+	if ((target & FRAMEBUFFER_DRAW) && state.boundFramebuffers[0] != framebuffer)
+	{
+		bindingmodified = true;
+		state.boundFramebuffers[0] = framebuffer;
+	}
+
+	if ((target & FRAMEBUFFER_READ) && state.boundFramebuffers[1] != framebuffer)
+	{
+		bindingmodified = true;
+		state.boundFramebuffers[1] = framebuffer;
+	}
 
-	if (target == GL_FRAMEBUFFER)
-		++stats.framebufferBinds;
+	if (bindingmodified)
+	{
+		GLenum gltarget = GL_FRAMEBUFFER;
+		if (target == FRAMEBUFFER_DRAW)
+			gltarget = GL_DRAW_FRAMEBUFFER;
+		else if (target == FRAMEBUFFER_READ)
+			gltarget = GL_READ_FRAMEBUFFER;
+
+		glBindFramebuffer(gltarget, framebuffer);
+	}
+}
+
+GLenum OpenGL::getFramebuffer(FramebufferTarget target) const
+{
+	if (target & FRAMEBUFFER_DRAW)
+		return state.boundFramebuffers[0];
+	else if (target & FRAMEBUFFER_READ)
+		return state.boundFramebuffers[1];
+	else
+		return 0;
+}
+
+void OpenGL::deleteFramebuffer(GLuint framebuffer)
+{
+	glDeleteFramebuffers(1, &framebuffer);
+
+	for (int i = 0; i < 2; i++)
+	{
+		if (state.boundFramebuffers[i] == framebuffer)
+			state.boundFramebuffers[i] = 0;
+	}
 }
 
 void OpenGL::useProgram(GLuint program)
@@ -673,6 +726,11 @@ bool OpenGL::isClampZeroTextureWrapSupported() const
 	return GLAD_VERSION_1_3 || GLAD_EXT_texture_border_clamp || GLAD_NV_texture_border_clamp;
 }
 
+bool OpenGL::isPixelShaderHighpSupported() const
+{
+	return pixelShaderHighpSupported;
+}
+
 int OpenGL::getMaxTextureSize() const
 {
 	return maxTextureSize;
@@ -680,7 +738,7 @@ int OpenGL::getMaxTextureSize() const
 
 int OpenGL::getMaxRenderTargets() const
 {
-	return maxRenderTargets;
+	return std::min(maxRenderTargets, MAX_COLOR_RENDER_TARGETS);
 }
 
 int OpenGL::getMaxRenderbufferSamples() const
@@ -698,6 +756,11 @@ float OpenGL::getMaxPointSize() const
 	return maxPointSize;
 }
 
+float OpenGL::getMaxAnisotropy() const
+{
+	return maxAnisotropy;
+}
+
 void OpenGL::updateTextureMemorySize(size_t oldsize, size_t newsize)
 {
 	int64 memsize = (int64) stats.textureMemory + ((int64) newsize - (int64) oldsize);
@@ -739,6 +802,36 @@ const char *OpenGL::errorString(GLenum errorcode)
 	return text;
 }
 
+const char *OpenGL::framebufferStatusString(GLenum status)
+{
+	switch (status)
+	{
+	case GL_FRAMEBUFFER_COMPLETE:
+		return "complete (success)";
+	case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
+		return "Texture format cannot be rendered to on this system.";
+	case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
+		return "Error in graphics driver (missing render texture attachment)";
+	case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
+		return "Error in graphics driver (incomplete draw buffer)";
+	case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
+		return "Error in graphics driver (incomplete read buffer)";
+	case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
+		return "Canvas with the specified MSAA count cannot be rendered to on this system.";
+	case GL_FRAMEBUFFER_UNSUPPORTED:
+		return "Renderable textures are unsupported";
+	default:
+		break;
+	}
+
+	static char text[64] = {};
+
+	memset(text, 0, sizeof(text));
+	sprintf(text, "0x%x", status);
+
+	return text;
+}
+
 const char *OpenGL::debugSeverityString(GLenum severity)
 {
 	switch (severity)

+ 24 - 5
src/modules/graphics/opengl/OpenGL.h

@@ -104,6 +104,13 @@ public:
 		VENDOR_UNKNOWN
 	};
 
+	enum FramebufferTarget
+	{
+		FRAMEBUFFER_READ = (1 << 0),
+		FRAMEBUFFER_DRAW = (1 << 1),
+		FRAMEBUFFER_ALL  = (FRAMEBUFFER_READ | FRAMEBUFFER_DRAW),
+	};
+
 	// A rectangle representing an OpenGL viewport or a scissor box.
 	struct Viewport
 	{
@@ -119,7 +126,7 @@ public:
 	struct
 	{
 		std::vector<Matrix4> transform;
-		std::vector<Matrix4> projection;
+		Matrix4 projection;
 	} matrices;
 
 	class TempTransform
@@ -173,7 +180,6 @@ public:
 	{
 		size_t textureMemory;
 		int    drawCalls;
-		int    framebufferBinds;
 		int    shaderSwitches;
 	} stats;
 
@@ -282,7 +288,7 @@ public:
 	 * Sets the OpenGL rendering viewport to the specified rectangle.
 	 * The y-coordinate starts at the top.
 	 **/
-	void setViewport(const Viewport &v);
+	void setViewport(const Viewport &v, bool canvasActive);
 
 	/**
 	 * Gets the current OpenGL rendering viewport rectangle.
@@ -293,7 +299,7 @@ public:
 	 * Sets the scissor box to the specified rectangle.
 	 * The y-coordinate starts at the top and is flipped internally.
 	 **/
-	void setScissor(const Viewport &v);
+	void setScissor(const Viewport &v, bool canvasActive);
 
 	/**
 	 * Gets the current scissor box (regardless of whether scissoring is enabled.)
@@ -323,7 +329,9 @@ public:
 	/**
 	 * Binds a Framebuffer Object to the specified target.
 	 **/
-	void bindFramebuffer(GLenum target, GLuint framebuffer);
+	void bindFramebuffer(FramebufferTarget target, GLuint framebuffer);
+	GLuint getFramebuffer(FramebufferTarget target) const;
+	void deleteFramebuffer(GLuint framebuffer);
 
 	/**
 	 * Calls glUseProgram.
@@ -375,6 +383,7 @@ public:
 	void setTextureWrap(const graphics::Texture::Wrap &w);
 
 	bool isClampZeroTextureWrapSupported() const;
+	bool isPixelShaderHighpSupported() const;
 
 	/**
 	 * Returns the maximum supported width or height of a texture.
@@ -401,6 +410,12 @@ public:
 	 **/
 	float getMaxPointSize() const;
 
+	/**
+	 * Returns the maximum anisotropic filtering value that can be used for
+	 * Texture filtering.
+	 **/
+	float getMaxAnisotropy() const;
+
 
 	void updateTextureMemorySize(size_t oldsize, size_t newsize);
 
@@ -413,6 +428,7 @@ public:
 	static GLint getGLWrapMode(Texture::WrapMode wmode);
 
 	static const char *errorString(GLenum errorcode);
+	static const char *framebufferStatusString(GLenum status);
 
 	// Get human-readable strings for debug info.
 	static const char *debugSeverityString(GLenum severity);
@@ -429,6 +445,7 @@ private:
 
 	bool contextInitialized;
 
+	bool pixelShaderHighpSupported;
 	float maxAnisotropy;
 	int maxTextureSize;
 	int maxRenderTargets;
@@ -456,6 +473,8 @@ private:
 
 		float pointSize;
 
+		GLuint boundFramebuffers[2];
+
 		bool framebufferSRGBEnabled;
 
 		GLuint defaultTexture;

+ 307 - 191
src/modules/graphics/opengl/Shader.cpp

@@ -22,11 +22,12 @@
 #include "common/config.h"
 
 #include "Shader.h"
-#include "Canvas.h"
+#include "Graphics.h"
 
 // C++
 #include <algorithm>
 #include <limits>
+#include <sstream>
 
 namespace love
 {
@@ -41,11 +42,12 @@ namespace
 	// reattaches the originally active program when destroyed
 	struct TemporaryAttacher
 	{
-		TemporaryAttacher(Shader *shader)
+		TemporaryAttacher(Shader *shader, bool attachNow)
 		: curShader(shader)
 		, prevShader(Shader::current)
 		{
-			curShader->attach(true);
+			if (attachNow)
+				attach();
 		}
 
 		~TemporaryAttacher()
@@ -56,6 +58,11 @@ namespace
 				curShader->detach();
 		}
 
+		void attach()
+		{
+			curShader->attach(true);
+		}
+
 		Shader *curShader;
 		Shader *prevShader;
 	};
@@ -70,14 +77,12 @@ Shader *Shader::defaultVideoShader = nullptr;
 Shader::ShaderSource Shader::defaultCode[Graphics::RENDERER_MAX_ENUM][2];
 Shader::ShaderSource Shader::defaultVideoCode[Graphics::RENDERER_MAX_ENUM][2];
 
-std::vector<int> Shader::textureCounters;
-
 Shader::Shader(const ShaderSource &source)
 	: shaderSource(source)
 	, program(0)
 	, builtinUniforms()
 	, builtinAttributes()
-	, lastCanvas((Canvas *) -1)
+	, canvasWasActive(false)
 	, lastViewport()
 	, lastPointSize(0.0f)
 	, videoTextureUnits()
@@ -85,10 +90,6 @@ Shader::Shader(const ShaderSource &source)
 	if (source.vertex.empty() && source.pixel.empty())
 		throw love::Exception("Cannot create shader: no source code!");
 
-	// initialize global texture id counters if needed
-	if ((int) textureCounters.size() < gl.getMaxTextureUnits() - 1)
-		textureCounters.resize(gl.getMaxTextureUnits() - 1, 0);
-
 	// load shader source and create program object
 	loadVolatile();
 }
@@ -98,12 +99,25 @@ Shader::~Shader()
 	if (current == this)
 		detach();
 
-	for (const auto &retainable : boundRetainables)
-		retainable.second->release();
+	unloadVolatile();
+
+	for (const auto &p : uniforms)
+	{
+		// Allocated with malloc().
+		if (p.second.data != nullptr)
+			free(p.second.data);
 
-	boundRetainables.clear();
+		if (p.second.baseType == UNIFORM_SAMPLER)
+		{
+			for (int i = 0; i < p.second.count; i++)
+			{
+				if (p.second.textures[i] != nullptr)
+					p.second.textures[i]->release();
+			}
 
-	unloadVolatile();
+			delete[] p.second.textures;
+		}
+	}
 }
 
 GLuint Shader::compileCode(ShaderStage stage, const std::string &code)
@@ -177,8 +191,6 @@ void Shader::mapActiveUniforms()
 	for (int i = 0; i < int(BUILTIN_MAX_ENUM); i++)
 		builtinUniforms[i] = -1;
 
-	uniforms.clear();
-
 	GLint activeprogram = 0;
 	glGetIntegerv(GL_CURRENT_PROGRAM, &activeprogram);
 
@@ -190,6 +202,9 @@ void Shader::mapActiveUniforms()
 	GLchar cname[256];
 	const GLint bufsize = (GLint) (sizeof(cname) / sizeof(GLchar));
 
+	std::map<std::string, UniformInfo> olduniforms = uniforms;
+	uniforms.clear();
+
 	for (int i = 0; i < numuniforms; i++)
 	{
 		GLsizei namelen = 0;
@@ -207,12 +222,6 @@ void Shader::mapActiveUniforms()
 		else
 			u.components = getUniformTypeComponents(gltype);
 
-		// Initialize all samplers to 0. Both GLSL and GLSL ES are supposed to
-		// do this themselves, but some Android devices (galaxy tab 3 and 4)
-		// don't seem to do it...
-		if (u.baseType == UNIFORM_SAMPLER)
-			glUniform1i(u.location, 0);
-
 		// glGetActiveUniform appends "[0]" to the end of array uniform names...
 		if (u.name.length() > 3)
 		{
@@ -226,8 +235,138 @@ void Shader::mapActiveUniforms()
 		if (builtinNames.find(u.name.c_str(), builtin))
 			builtinUniforms[int(builtin)] = u.location;
 
-		if (u.location != -1)
-			uniforms[u.name] = u;
+		if (u.location == -1)
+			continue;
+
+		// Make sure previously set uniform data is preserved, and shader-
+		// initialized values are retrieved.
+		auto oldu = olduniforms.find(u.name);
+		if (oldu != olduniforms.end())
+		{
+			u.data = oldu->second.data;
+			u.textures = oldu->second.textures;
+
+			updateUniform(&u, u.count, true);
+
+			if (u.baseType == UNIFORM_SAMPLER)
+			{
+				// Make sure all stored textures have their Volatiles loaded
+				// before the sendTextures call, since it calls getHandle().
+				for (int i = 0; i < u.count; i++)
+				{
+					if (u.textures[i] == nullptr)
+						continue;
+
+					Volatile *v = dynamic_cast<Volatile *>(u.textures[i]);
+					if (v != nullptr)
+						v->loadVolatile();
+				}
+
+				sendTextures(&u, u.textures, u.count, true);
+			}
+		}
+		else
+		{
+			size_t datasize = 0;
+
+			switch (u.baseType)
+			{
+			case UNIFORM_FLOAT:
+				datasize = sizeof(float) * u.components * u.count;
+				u.data = malloc(datasize);
+				break;
+			case UNIFORM_INT:
+			case UNIFORM_BOOL:
+			case UNIFORM_SAMPLER:
+				datasize = sizeof(int) * u.components * u.count;
+				u.data = malloc(datasize);
+				break;
+			case UNIFORM_MATRIX:
+				datasize = sizeof(float) * (u.matrix.rows * u.matrix.columns) * u.count;
+				u.data = malloc(datasize);
+				break;
+			default:
+				break;
+			}
+
+			if (datasize > 0)
+			{
+				memset(u.data, 0, datasize);
+
+				if (u.baseType == UNIFORM_SAMPLER)
+				{
+					// Initialize all samplers to 0. Both GLSL and GLSL ES are
+					// supposed to do this themselves, but some Android devices
+					// (galaxy tab 3 and 4) don't seem to do it...
+					glUniform1iv(u.location, u.count, u.ints);
+
+					u.textures = new Texture*[u.count];
+					memset(u.textures, 0, sizeof(Texture *) * u.count);
+				}
+			}
+
+			size_t offset = 0;
+
+			// Store any shader-initialized values in our own memory.
+			for (int i = 0; i < u.count; i++)
+			{
+				GLint location = u.location;
+
+				if (u.count > 1)
+				{
+					std::ostringstream ss;
+					ss << i;
+
+					std::string indexname = u.name + "[" + ss.str() + "]";
+					location = glGetUniformLocation(program, indexname.c_str());
+				}
+
+				if (location == -1)
+					continue;
+
+				switch (u.baseType)
+				{
+				case UNIFORM_FLOAT:
+					glGetUniformfv(program, location, &u.floats[offset]);
+					offset += u.components;
+					break;
+				case UNIFORM_INT:
+				case UNIFORM_BOOL:
+					glGetUniformiv(program, location, &u.ints[offset]);
+					offset += u.components;
+					break;
+				case UNIFORM_MATRIX:
+					glGetUniformfv(program, location, &u.floats[offset]);
+					offset += u.matrix.rows * u.matrix.columns;
+					break;
+				default:
+					break;
+				}
+			}
+		}
+
+		uniforms[u.name] = u;
+	}
+
+	// Make sure uniforms that existed before but don't exist anymore are
+	// cleaned up. This theoretically shouldn't happen, but...
+	for (const auto &p : olduniforms)
+	{
+		if (uniforms.find(p.first) == uniforms.end())
+		{
+			free(p.second.data);
+
+			if (p.second.baseType != UNIFORM_SAMPLER)
+				continue;
+
+			for (int i = 0; i < p.second.count; i++)
+			{
+				if (p.second.textures[i] != nullptr)
+					p.second.textures[i]->release();
+			}
+
+			delete[] p.second.textures;
+		}
 	}
 
 	gl.useProgram(activeprogram);
@@ -238,7 +377,7 @@ bool Shader::loadVolatile()
 	OpenGL::TempDebugGroup debuggroup("Shader load");
 
     // Recreating the shader program will invalidate uniforms that rely on these.
-    lastCanvas = (Canvas *) -1;
+	canvasWasActive = false;
     lastViewport = OpenGL::Viewport();
 
 	lastPointSize = -1.0f;
@@ -252,8 +391,8 @@ bool Shader::loadVolatile()
 		videoTextureUnits[i] = 0;
 
 	// zero out active texture list
-	activeTexUnits.clear();
-	activeTexUnits.insert(activeTexUnits.begin(), gl.getMaxTextureUnits() - 1, 0);
+	textureUnits.clear();
+	textureUnits.resize(gl.getMaxTextureUnits(), TextureUnit());
 
 	std::vector<GLuint> shaderids;
 
@@ -340,31 +479,21 @@ bool Shader::loadVolatile()
 
 void Shader::unloadVolatile()
 {
-	if (current == this)
-		gl.useProgram(0);
-
 	if (program != 0)
 	{
+		if (current == this)
+			gl.useProgram(0);
+
 		glDeleteProgram(program);
 		program = 0;
 	}
 
-	// decrement global texture id counters for texture units which had textures bound from this shader
-	for (size_t i = 0; i < activeTexUnits.size(); ++i)
-	{
-		if (activeTexUnits[i] > 0)
-			textureCounters[i] = std::max(textureCounters[i] - 1, 0);
-	}
-
 	// active texture list is probably invalid, clear it
-	activeTexUnits.clear();
-	activeTexUnits.resize(gl.getMaxTextureUnits() - 1, 0);
+	textureUnits.clear();
+	textureUnits.resize(gl.getMaxTextureUnits(), TextureUnit());
 
 	attributes.clear();
 
-	// same with uniform location list
-	uniforms.clear();
-
 	// And the locations of any built-in uniform variables.
 	for (int i = 0; i < int(BUILTIN_MAX_ENUM); i++)
 		builtinUniforms[i] = -1;
@@ -421,10 +550,10 @@ void Shader::attach(bool temporary)
 		{
 			// make sure all sent textures are properly bound to their respective texture units
 			// note: list potentially contains texture ids of deleted/invalid textures!
-			for (int i = 0; i < (int) activeTexUnits.size(); ++i)
+			for (int i = 1; i < (int) textureUnits.size(); ++i)
 			{
-				if (activeTexUnits[i] > 0)
-					gl.bindTextureToUnit(activeTexUnits[i], i + 1, false);
+				if (textureUnits[i].active)
+					gl.bindTextureToUnit(textureUnits[i].texture, i, false);
 			}
 		}
 	}
@@ -456,160 +585,148 @@ const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const
 	return &(it->second);
 }
 
-void Shader::sendInts(const UniformInfo *info, const int *vec, int count)
+void Shader::updateUniform(const UniformInfo *info, int count, bool internalUpdate)
 {
-	if (info->baseType != UNIFORM_INT && info->baseType != UNIFORM_BOOL)
-		return;
-
-	TemporaryAttacher attacher(this);
+	TemporaryAttacher attacher(this, !internalUpdate);
 
 	int location = info->location;
+	UniformType type = info->baseType;
 
-	switch (info->components)
+	if (type == UNIFORM_FLOAT)
 	{
-	case 4:
-		glUniform4iv(location, count, vec);
-		break;
-	case 3:
-		glUniform3iv(location, count, vec);
-		break;
-	case 2:
-		glUniform2iv(location, count, vec);
-		break;
-	case 1:
-	default:
-		glUniform1iv(location, count, vec);
-		break;
+		switch (info->components)
+		{
+		case 1:
+			glUniform1fv(location, count, info->floats);
+			break;
+		case 2:
+			glUniform2fv(location, count, info->floats);
+			break;
+		case 3:
+			glUniform3fv(location, count, info->floats);
+			break;
+		case 4:
+			glUniform4fv(location, count, info->floats);
+			break;
+		}
 	}
-}
-
-void Shader::sendFloats(const UniformInfo *info, const float *vec, int count)
-{
-	if (info->baseType != UNIFORM_FLOAT && info->baseType != UNIFORM_BOOL)
-		return;
-
-	TemporaryAttacher attacher(this);
-
-	int location = info->location;
-
-	switch (info->components)
+	else if (type == UNIFORM_INT || type == UNIFORM_BOOL || type == UNIFORM_SAMPLER)
 	{
-	case 4:
-		glUniform4fv(location, count, vec);
-		break;
-	case 3:
-		glUniform3fv(location, count, vec);
-		break;
-	case 2:
-		glUniform2fv(location, count, vec);
-		break;
-	case 1:
-	default:
-		glUniform1fv(location, count, vec);
-		break;
+		switch (info->components)
+		{
+		case 1:
+			glUniform1iv(location, count, info->ints);
+			break;
+		case 2:
+			glUniform2iv(location, count, info->ints);
+			break;
+		case 3:
+			glUniform3iv(location, count, info->ints);
+			break;
+		case 4:
+			glUniform4iv(location, count, info->ints);
+			break;
+		}
+	}
+	else if (type == UNIFORM_MATRIX)
+	{
+		int columns = info->matrix.columns;
+		int rows = info->matrix.rows;
+
+		if (columns == 2 && rows == 2)
+			glUniformMatrix2fv(location, count, GL_FALSE, info->floats);
+		else if (columns == 3 && rows == 3)
+			glUniformMatrix3fv(location, count, GL_FALSE, info->floats);
+		else if (columns == 4 && rows == 4)
+			glUniformMatrix4fv(location, count, GL_FALSE, info->floats);
+		else if (columns == 2 && rows == 3)
+			glUniformMatrix2x3fv(location, count, GL_FALSE, info->floats);
+		else if (columns == 2 && rows == 4)
+			glUniformMatrix2x4fv(location, count, GL_FALSE, info->floats);
+		else if (columns == 3 && rows == 2)
+			glUniformMatrix3x2fv(location, count, GL_FALSE, info->floats);
+		else if (columns == 3 && rows == 4)
+			glUniformMatrix3x4fv(location, count, GL_FALSE, info->floats);
+		else if (columns == 4 && rows == 2)
+			glUniformMatrix4x2fv(location, count, GL_FALSE, info->floats);
+		else if (columns == 4 && rows == 3)
+			glUniformMatrix4x3fv(location, count, GL_FALSE, info->floats);
 	}
 }
 
-void Shader::sendMatrices(const UniformInfo *info, const float *m, int count)
+int Shader::getFreeTextureUnits(int count)
 {
-	if (info->baseType != UNIFORM_MATRIX)
-		return;
+	int startunit = -1;
 
-	TemporaryAttacher attacher(this);
+	// Ignore the first texture unit for Shader-local texture bindings.
+	for (int i = 1; i < (int) textureUnits.size(); i++)
+	{
+		if (!textureUnits[i].active && i + count <= (int) textureUnits.size())
+		{
+			startunit = i;
+			break;
+		}
+	}
 
-	int location = info->location;
+	if (startunit == -1)
+		throw love::Exception("No more texture units available for shader.");
 
-	int columns = info->matrix.columns;
-	int rows = info->matrix.rows;
-
-	if (columns == 2 && rows == 2)
-		glUniformMatrix2fv(location, count, GL_FALSE, m);
-	else if (columns == 3 && rows == 3)
-		glUniformMatrix3fv(location, count, GL_FALSE, m);
-	else if (columns == 4 && rows == 4)
-		glUniformMatrix4fv(location, count, GL_FALSE, m);
-	else if (columns == 2 && rows == 3)
-		glUniformMatrix2x3fv(location, count, GL_FALSE, m);
-	else if (columns == 2 && rows == 4)
-		glUniformMatrix2x4fv(location, count, GL_FALSE, m);
-	else if (columns == 3 && rows == 2)
-		glUniformMatrix3x2fv(location, count, GL_FALSE, m);
-	else if (columns == 3 && rows == 4)
-		glUniformMatrix3x4fv(location, count, GL_FALSE, m);
-	else if (columns == 4 && rows == 2)
-		glUniformMatrix4x2fv(location, count, GL_FALSE, m);
-	else if (columns == 4 && rows == 3)
-		glUniformMatrix4x3fv(location, count, GL_FALSE, m);
+	return startunit;
 }
 
-void Shader::sendTexture(const UniformInfo *info, Texture *texture)
+void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count, bool internalUpdate)
 {
 	if (info->baseType != UNIFORM_SAMPLER)
 		return;
 
-	GLuint gltex = *(GLuint *) texture->getHandle();
-
-	TemporaryAttacher attacher(this);
-
-	int texunit = getTextureUnit(info->name);
-
-	// bind texture to assigned texture unit and send uniform to shader program
-	gl.bindTextureToUnit(gltex, texunit, false);
+	count = std::min(count, info->count);
+	bool updateuniform = false;
 
-	glUniform1i(info->location, texunit);
-
-	// increment global shader texture id counter for this texture unit, if we haven't already
-	if (activeTexUnits[texunit-1] == 0)
-		++textureCounters[texunit-1];
-
-	// store texture id so it can be re-bound to the proper texture unit later
-	activeTexUnits[texunit-1] = gltex;
-
-	retainObject(info->name, texture);
-}
-
-void Shader::retainObject(const std::string &name, Object *object)
-{
-	object->retain();
+	// Make sure the shader's samplers are associated with texture units.
+	for (int i = 0; i < count; i++)
+	{
+		if (info->ints[i] == 0 && textures[i] != nullptr)
+		{
+			int texunit = getFreeTextureUnits(1);
+			textureUnits[texunit].active = true;
 
-	auto it = boundRetainables.find(name);
-	if (it != boundRetainables.end())
-		it->second->release();
+			info->ints[i] = texunit;
+			updateuniform = true;
+		}
+	}
 
-	boundRetainables[name] = object;
-}
+	if (updateuniform)
+		updateUniform(info, count, internalUpdate);
 
-int Shader::getTextureUnit(const std::string &name)
-{
-	auto it = texUnitPool.find(name);
+	// Bind the textures to the texture units.
+	for (int i = 0; i < count; i++)
+	{
+		if (textures[i] != nullptr)
+			textures[i]->retain();
 
-	if (it != texUnitPool.end())
-		return it->second;
+		if (info->textures[i] != nullptr)
+			info->textures[i]->release();
 
-	int texunit = 1;
+		info->textures[i] = textures[i];
 
-	// prefer texture units which are unused by all other shaders
-	auto freeunit_it = std::find(textureCounters.begin(), textureCounters.end(), 0);
+		int texunit = info->ints[i];
 
-	if (freeunit_it != textureCounters.end())
-	{
-		// we don't want to use unit 0
-		texunit = (int) std::distance(textureCounters.begin(), freeunit_it) + 1;
-	}
-	else
-	{
-		// no completely unused texture units exist, try to use next free slot in our own list
-		auto nextunit_it = std::find(activeTexUnits.begin(), activeTexUnits.end(), 0);
+		if (textures[i] != nullptr)
+		{
+			GLuint gltex = *(GLuint *) textures[i]->getHandle();
 
-		if (nextunit_it == activeTexUnits.end())
-			throw love::Exception("No more texture units available for shader.");
+			gl.bindTextureToUnit(gltex, texunit, false);
 
-		// we don't want to use unit 0
-		texunit = (int) std::distance(activeTexUnits.begin(), nextunit_it) + 1;
+			// store texture id so it can be re-bound to the proper texture unit later
+			textureUnits[texunit].texture = gltex;
+		}
+		else
+		{
+			gl.bindTextureToUnit(0, texunit, false);
+			textureUnits[texunit].texture = 0;
+			textureUnits[texunit].active = false;
+		}
 	}
-
-	texUnitPool[name] = texunit;
-	return texunit;
 }
 
 bool Shader::hasUniform(const std::string &name) const
@@ -636,12 +753,12 @@ bool Shader::hasVertexAttrib(VertexAttribID attrib) const
 
 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)
 	{
+		TemporaryAttacher attacher(this, true);
+
 		const GLint locs[3] = {
 			builtinUniforms[BUILTIN_VIDEO_Y_CHANNEL],
 			builtinUniforms[BUILTIN_VIDEO_CB_CHANNEL],
@@ -657,14 +774,15 @@ void Shader::setVideoTextures(GLuint ytexture, GLuint cbtexture, GLuint crtextur
 		{
 			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 UniformInfo *info = getUniformInfo(names[i]);
+				if (info != nullptr)
+				{
+					videoTextureUnits[i] = getFreeTextureUnits(1);
+					textureUnits[videoTextureUnits[i]].active = true;
+
+					info->ints[0] = videoTextureUnits[i];
+					updateUniform(info, 1);
+				}
 			}
 		}
 	}
@@ -677,7 +795,7 @@ void Shader::setVideoTextures(GLuint ytexture, GLuint cbtexture, GLuint crtextur
 		if (videoTextureUnits[i] != 0)
 		{
 			// Store texture id so it can be re-bound later.
-			activeTexUnits[videoTextureUnits[i] - 1] = textures[i];
+			textureUnits[videoTextureUnits[i]].texture = textures[i];
 			gl.bindTextureToUnit(textures[i], videoTextureUnits[i], false);
 		}
 	}
@@ -687,7 +805,10 @@ void Shader::checkSetScreenParams()
 {
 	OpenGL::Viewport view = gl.getViewport();
 
-	if (view == lastViewport && lastCanvas == Canvas::current)
+	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
+	bool canvasActive = gfx->getActivePass().colorAttachmentCount > 0;
+
+	if (view == lastViewport && canvasWasActive == canvasActive)
 		return;
 
 	// In the shader, we do pixcoord.y = gl_FragCoord.y * params.z + params.w.
@@ -698,7 +819,7 @@ void Shader::checkSetScreenParams()
 		0.0f, 0.0f,
 	};
 
-	if (Canvas::current != nullptr)
+	if (canvasActive)
 	{
 		// No flipping: pixcoord.y = gl_FragCoord.y * 1.0 + 0.0.
 		params[2] = 1.0f;
@@ -716,11 +837,11 @@ void Shader::checkSetScreenParams()
 
 	if (location >= 0)
 	{
-		TemporaryAttacher attacher(this);
+		TemporaryAttacher attacher(this, true);
 		glUniform4fv(location, 1, params);
 	}
 
-	lastCanvas = Canvas::current;
+	canvasWasActive = canvasActive;
 	lastViewport = view;
 }
 
@@ -733,7 +854,7 @@ void Shader::checkSetPointSize(float size)
 
 	if (location >= 0)
 	{
-		TemporaryAttacher attacher(this);
+		TemporaryAttacher attacher(this, true);
 		glUniform1f(location, size);
 	}
 
@@ -751,9 +872,9 @@ void Shader::checkSetBuiltinUniforms()
 		checkSetPointSize(gl.getPointSize());
 
 		const Matrix4 &curxform = gl.matrices.transform.back();
-		const Matrix4 &curproj = gl.matrices.projection.back();
+		const Matrix4 &curproj = gl.matrices.projection;
 
-		TemporaryAttacher attacher(this);
+		TemporaryAttacher attacher(this, true);
 
 		bool tpmatrixneedsupdate = false;
 
@@ -800,11 +921,6 @@ void Shader::checkSetBuiltinUniforms()
 	}
 }
 
-const std::map<std::string, Object *> &Shader::getBoundRetainables() const
-{
-	return boundRetainables;
-}
-
 std::string Shader::getGLSLVersion()
 {
 	const char *tmp = (const char *) glGetString(GL_SHADING_LANGUAGE_VERSION);

+ 22 - 34
src/modules/graphics/opengl/Shader.h

@@ -41,8 +41,6 @@ namespace graphics
 namespace opengl
 {
 
-class Canvas;
-
 // A GLSL shader
 class Shader : public Object, public Volatile
 {
@@ -100,13 +98,24 @@ public:
 	{
 		int location;
 		int count;
+
 		union
 		{
 			int components;
 			MatrixSize matrix;
 		};
+
 		UniformType baseType;
 		std::string name;
+
+		union
+		{
+			void *data;
+			float *floats;
+			int *ints;
+		};
+
+		Texture **textures;
 	};
 
 	// Pointer to currently active Shader.
@@ -151,11 +160,9 @@ public:
 	std::string getWarnings() const;
 
 	const UniformInfo *getUniformInfo(const std::string &name) const;
+	void updateUniform(const UniformInfo *info, int count, bool internalUpdate = false);
 
-	void sendInts(const UniformInfo *info, const int *vec, int count);
-	void sendFloats(const UniformInfo *info, const float *vec, int count);
-	void sendMatrices(const UniformInfo *info, const float *m, int count);
-	void sendTexture(const UniformInfo *info, Texture *texture);
+	void sendTextures(const UniformInfo *info, Texture **textures, int count, bool internalUpdate = false);
 
 	/**
 	 * Gets whether a uniform with the specified name exists and is actively
@@ -175,24 +182,11 @@ public:
 	void checkSetPointSize(float size);
 	void checkSetBuiltinUniforms();
 
-	const std::map<std::string, Object *> &getBoundRetainables() const;
-
 	GLuint getProgram() const
 	{
 		return program;
 	}
 
-	template <typename T>
-	T *getScratchBuffer(size_t count)
-	{
-		size_t bytes = sizeof(T) * count;
-
-		if (scratchBuffer.size() < bytes)
-			scratchBuffer.resize(bytes);
-
-		return (T *) scratchBuffer.data();
-	}
-
 	static std::string getGLSLVersion();
 	static bool isSupported();
 
@@ -204,6 +198,12 @@ public:
 
 private:
 
+	struct TextureUnit
+	{
+		GLuint texture = 0;
+		bool active = false;
+	};
+
 	// Map active uniform names to their locations.
 	void mapActiveUniforms();
 
@@ -213,9 +213,7 @@ private:
 
 	GLuint compileCode(ShaderStage stage, const std::string &code);
 
-	int getTextureUnit(const std::string &name);
-
-	void retainObject(const std::string &name, Object *object);
+	int getFreeTextureUnits(int count);
 
 	// Get any warnings or errors generated only by the shader program object.
 	std::string getProgramWarnings() const;
@@ -241,14 +239,9 @@ private:
 	std::map<std::string, UniformInfo> uniforms;
 
 	// Texture unit pool for setting images
-	std::map<std::string, GLint> texUnitPool; // texUnitPool[name] = textureunit
-	std::vector<GLuint> activeTexUnits; // activeTexUnits[textureunit-1] = textureid
-
-	// Uniform name to retainable objects
-	std::map<std::string, Object*> boundRetainables;
+	std::vector<TextureUnit> textureUnits;
 
-	// Pointer to the active Canvas when the screen params were last checked.
-	Canvas *lastCanvas;
+	bool canvasWasActive;
 	OpenGL::Viewport lastViewport;
 
 	float lastPointSize;
@@ -258,11 +251,6 @@ private:
 
 	GLuint videoTextureUnits[3];
 
-	std::vector<char> scratchBuffer;
-
-	// Counts total number of textures bound to each texture unit in all shaders
-	static std::vector<int> textureCounters;
-
 	static StringMap<ShaderStage, STAGE_MAX_ENUM>::Entry stageNameEntries[];
 	static StringMap<ShaderStage, STAGE_MAX_ENUM> stageNames;
 

+ 0 - 51
src/modules/graphics/opengl/wrap_Canvas.cpp

@@ -33,55 +33,6 @@ Canvas *luax_checkcanvas(lua_State *L, int idx)
 	return luax_checktype<Canvas>(L, idx);
 }
 
-int w_Canvas_renderTo(lua_State *L)
-{
-	Canvas *canvas = luax_checkcanvas(L, 1);
-	luaL_checktype(L, 2, LUA_TFUNCTION);
-
-	auto graphics = Module::getInstance<Graphics>(Module::M_GRAPHICS);
-
-	if (graphics)
-	{
-		// Save the current Canvas so we can restore it when we're done.
-		std::vector<Canvas *> oldcanvases = graphics->getCanvas();
-
-		for (Canvas *c : oldcanvases)
-			c->retain();
-
-		luax_catchexcept(L, [&](){ graphics->setCanvas(canvas); });
-
-		lua_settop(L, 2); // make sure the function is on top of the stack
-		int status = lua_pcall(L, 0, 0, 0);
-
-		graphics->setCanvas(oldcanvases);
-
-		for (Canvas *c : oldcanvases)
-			c->release();
-
-		if (status != 0)
-			return lua_error(L);
-	}
-
-	return 0;
-}
-
-int w_Canvas_newImageData(lua_State *L)
-{
-	Canvas *canvas = luax_checkcanvas(L, 1);
-	love::image::Image *image = luax_getmodule<love::image::Image>(L);
-	int x = (int) luaL_optnumber(L, 2, 0);
-	int y = (int) luaL_optnumber(L, 3, 0);
-	int w = (int) luaL_optnumber(L, 4, canvas->getWidth());
-	int h = (int) luaL_optnumber(L, 5, canvas->getHeight());
-
-	love::image::ImageData *img = nullptr;
-	luax_catchexcept(L, [&](){ img = canvas->newImageData(image, x, y, w, h); });
-
-	luax_pushtype(L, img);
-	img->release();
-	return 1;
-}
-
 int w_Canvas_getFormat(lua_State *L)
 {
 	Canvas *canvas = luax_checkcanvas(L, 1);
@@ -103,8 +54,6 @@ int w_Canvas_getMSAA(lua_State *L)
 
 static const luaL_Reg w_Canvas_functions[] =
 {
-	{ "renderTo", w_Canvas_renderTo },
-	{ "newImageData", w_Canvas_newImageData },
 	{ "getFormat", w_Canvas_getFormat },
 	{ "getMSAA", w_Canvas_getMSAA },
 	{ 0, 0 }

+ 402 - 215
src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -27,6 +27,8 @@
 #include "filesystem/wrap_Filesystem.h"
 #include "video/VideoStream.h"
 #include "image/wrap_Image.h"
+#include "common/Reference.h"
+#include "math/wrap_Transform.h"
 
 #include <cassert>
 #include <cstring>
@@ -60,117 +62,315 @@ int w_reset(lua_State *)
 	return 0;
 }
 
-int w_clear(lua_State *L)
+int w_present(lua_State *L)
 {
-	Colorf color;
+	luax_catchexcept(L, [&]() { instance()->present(L); });
+	return 0;
+}
+
+int w_isCreated(lua_State *L)
+{
+	luax_pushboolean(L, instance()->isCreated());
+	return 1;
+}
+
+int w_isActive(lua_State *L)
+{
+	luax_pushboolean(L, instance()->isActive());
+	return 1;
+}
+
+int w_isGammaCorrect(lua_State *L)
+{
+	luax_pushboolean(L, instance()->isGammaCorrect());
+	return 1;
+}
+
+int w_getWidth(lua_State *L)
+{
+	lua_pushinteger(L, instance()->getWidth());
+	return 1;
+}
+
+int w_getHeight(lua_State *L)
+{
+	lua_pushinteger(L, instance()->getHeight());
+	return 1;
+}
+
+int w_getDimensions(lua_State *L)
+{
+	lua_pushinteger(L, instance()->getWidth());
+	lua_pushinteger(L, instance()->getHeight());
+	return 2;
+}
+
+int w_getPassWidth(lua_State *L)
+{
+	lua_pushinteger(L, instance()->getPassWidth());
+	return 1;
+}
+
+int w_getPassHeight(lua_State *L)
+{
+	lua_pushinteger(L, instance()->getPassHeight());
+	return 1;
+}
+
+int w_getPassDimensions(lua_State *L)
+{
+	lua_pushinteger(L, instance()->getPassWidth());
+	lua_pushinteger(L, instance()->getPassHeight());
+	return 2;
+}
+
+static int w__beginPass(lua_State *L)
+{
+	int nextstartidx = 1;
 
 	if (lua_isnoneornil(L, 1))
-		color.set(0.0, 0.0, 0.0, 0.0);
-	else if (lua_istable(L, 1))
 	{
-		std::vector<Graphics::OptionalColorf> colors((size_t) lua_gettop(L));
+		luax_catchexcept(L, [&]() { instance()->beginPass(PassInfo::BEGIN_LOAD, Colorf()); });
+		nextstartidx = 1;
+	}
+	else if (lua_isnumber(L, 1))
+	{
+		Colorf c;
+		c.r = (float) luaL_checknumber(L, 1);
+		c.g = (float) luaL_checknumber(L, 2);
+		c.b = (float) luaL_checknumber(L, 3);
 
-		for (int i = 0; i < lua_gettop(L); i++)
+		if (lua_isnumber(L, 4))
+		{
+			c.a = (float) lua_tonumber(L, 4);
+			nextstartidx = 5;
+		}
+		else
 		{
-			if (lua_isnoneornil(L, i + 1) || luax_objlen(L, i + 1) == 0)
+			c.a = 1.0f;
+			nextstartidx = 4;
+		}
+
+		luax_catchexcept(L, [&]() { instance()->beginPass(PassInfo::BEGIN_CLEAR, c); });
+	}
+	else if (luax_istype(L, 1, Canvas::type))
+	{
+		PassInfo::ColorAttachment attachment;
+		attachment.canvas = luax_checkcanvas(L, 1);
+		attachment.beginAction = PassInfo::BEGIN_LOAD;
+
+		if (lua_isnumber(L, 2))
+		{
+			attachment.beginAction = PassInfo::BEGIN_CLEAR;
+			attachment.clearColor.r = (float) luaL_checknumber(L, 2);
+			attachment.clearColor.g = (float) luaL_checknumber(L, 3);
+			attachment.clearColor.b = (float) luaL_checknumber(L, 4);
+
+			if (lua_isnumber(L, 5))
 			{
-				colors[i].enabled = false;
-				continue;
+				attachment.clearColor.a = (float) lua_tonumber(L, 5);
+				nextstartidx = 6;
 			}
+			else
+			{
+				attachment.clearColor.a = 1.0f;
+				nextstartidx = 5;
+			}
+		}
+		else
+			nextstartidx = 2;
 
-			for (int j = 1; j <= 4; j++)
-				lua_rawgeti(L, i + 1, j);
-
-			colors[i].enabled = true;
-			colors[i].r = (float) luaL_checknumber(L, -4);
-			colors[i].g = (float) luaL_checknumber(L, -3);
-			colors[i].b = (float) luaL_checknumber(L, -2);
-			colors[i].a = (float) luaL_optnumber(L, -1, 1.0);
+		PassInfo info;
+		info.addColorAttachment(attachment);
 
-			lua_pop(L, 4);
+		if (lua_isboolean(L, nextstartidx))
+		{
+			info.stencil = luax_toboolean(L, nextstartidx);
+			nextstartidx++;
 		}
+		else
+			info.stencil = false;
 
-		luax_catchexcept(L, [&]() { instance()->clear(colors); });
-		return 0;
+		luax_catchexcept(L, [&]() { instance()->beginPass(info); });
 	}
 	else
 	{
-		color.r = (float) luaL_checknumber(L, 1);
-		color.g = (float) luaL_checknumber(L, 2);
-		color.b = (float) luaL_checknumber(L, 3);
-		color.a = (float) luaL_optnumber(L, 4, 1.0);
+		luaL_checktype(L, 1, LUA_TTABLE);
+
+		int nattachments = std::max((int) luax_objlen(L, 1), 1);
+
+		if (nattachments > MAX_COLOR_RENDER_TARGETS)
+			return luaL_error(L, "Cannot render to %d Canvases at once!", nattachments);
+
+		PassInfo info;
+
+		for (int i = 1; i <= nattachments; i++)
+		{
+			lua_rawgeti(L, 1, i);
+			luaL_checktype(L, -1, LUA_TTABLE);
+
+			PassInfo::ColorAttachment attachment;
+			attachment.beginAction = PassInfo::BEGIN_LOAD;
+
+			lua_rawgeti(L, -1, 1);
+			attachment.canvas = luax_checkcanvas(L, -1);
+
+			lua_rawgeti(L, -2, 2);
+			if (!lua_isnoneornil(L, -1))
+			{
+				attachment.beginAction = PassInfo::BEGIN_CLEAR;
+
+				for (int j = 3; j < 6; j++)
+					lua_rawgeti(L, -j, j);
+
+				attachment.clearColor.r = (float) luaL_checknumber(L, -4);
+				attachment.clearColor.g = (float) luaL_checknumber(L, -3);
+				attachment.clearColor.b = (float) luaL_checknumber(L, -2);
+				attachment.clearColor.a = (float) luaL_optnumber(L, -1, 1.0);
+			}
+
+			lua_pop(L, 2 + (attachment.beginAction == PassInfo::BEGIN_CLEAR ? 4 : 1));
+
+			info.addColorAttachment(attachment);
+		}
+
+		info.stencil = luax_boolflag(L, 1, "stencil", false);
+
+		luax_catchexcept(L, [&]() { instance()->beginPass(info); });
+
+		nextstartidx = 2;
 	}
 
-	luax_catchexcept(L, [&]() { instance()->clear(color); });
+	return nextstartidx;
+}
+
+int w_beginPass(lua_State *L)
+{
+	w__beginPass(L);
 	return 0;
 }
 
-int w_discard(lua_State *L)
+static void screenshotCallback(love::image::ImageData *i, Reference *ref, void *gd)
 {
-	std::vector<bool> colorbuffers;
+	if (i != nullptr)
+	{
+		lua_State *L = (lua_State *) gd;
+		ref->push(L);
+		delete ref;
+		luax_pushtype(L, i);
+		lua_call(L, 1, 0);
+	}
+	else
+		delete ref;
+}
 
-	if (lua_istable(L, 1))
+static int w__endPass(lua_State *L, int startidx)
+{
+	if (lua_isnoneornil(L, startidx))
 	{
-		for (size_t i = 1; i <= luax_objlen(L, 1); i++)
-		{
-			lua_rawgeti(L, 1, i);
-			colorbuffers.push_back(luax_optboolean(L, -1, true));
-			lua_pop(L, 1);
-		}
+		luax_catchexcept(L, []() { instance()->endPass(); });
 	}
 	else
 	{
-		bool discardcolor = luax_optboolean(L, 1, true);
-		size_t numbuffers = std::max((size_t) 1, instance()->getCanvas().size());
-		colorbuffers = std::vector<bool>(numbuffers, discardcolor);
+		int x, y, w, h;
+
+		if (lua_isnumber(L, startidx))
+		{
+			x = (int) luaL_checknumber(L, 1);
+			y = (int) luaL_checknumber(L, 2);
+			w = (int) luaL_checknumber(L, 3);
+			h = (int) luaL_checknumber(L, 4);
+			startidx += 4;
+		}
+		else
+		{
+			x = 0;
+			y = 0;
+			w = instance()->getPassWidth();
+			h = instance()->getPassHeight();
+		}
+
+		luaL_checktype(L, startidx, LUA_TFUNCTION);
+
+		Graphics::ScreenshotInfo info;
+		info.callback = screenshotCallback;
+
+		lua_pushvalue(L, startidx);
+		info.ref = luax_refif(L, LUA_TFUNCTION);
+		lua_pop(L, 1);
+
+		luax_catchexcept(L,
+			[&]() { instance()->endPass(x, y, w, h, &info, L); },
+			[&](bool except) { if (except) delete info.ref; }
+		);
 	}
 
-	bool stencil = luax_optboolean(L, 2, true);
-	instance()->discard(colorbuffers, stencil);
 	return 0;
 }
 
-int w_present(lua_State *)
+int w_endPass(lua_State *L)
 {
-	instance()->present();
-	return 0;
+	return w__endPass(L, 1);
 }
 
-int w_isCreated(lua_State *L)
+int w_renderPass(lua_State *L)
 {
-	luax_pushboolean(L, instance()->isCreated());
-	return 1;
-}
+	int startidx = w__beginPass(L);
 
-int w_isActive(lua_State *L)
-{
-	luax_pushboolean(L, instance()->isActive());
-	return 1;
-}
+	if (lua_type(L, startidx) != LUA_TFUNCTION)
+	{
+		w__endPass(L, startidx + 1);
+		luaL_checktype(L, startidx, LUA_TFUNCTION);
+		return 0;
+	}
 
-int w_isGammaCorrect(lua_State *L)
-{
-	luax_pushboolean(L, instance()->isGammaCorrect());
-	return 1;
+	int nargs = lua_gettop(L) - startidx;
+	int status = lua_pcall(L, nargs, 0, 0);
+
+	w__endPass(L, startidx + 1);
+
+	if (status != 0)
+		return lua_error(L);
+
+	return 0;
 }
 
-int w_getWidth(lua_State *L)
+int w_isPassActive(lua_State *L)
 {
-	lua_pushinteger(L, instance()->getWidth());
+	luax_pushboolean(L, instance()->isPassActive());
 	return 1;
 }
 
-int w_getHeight(lua_State *L)
+int w_getPassCanvases(lua_State *L)
 {
-	lua_pushinteger(L, instance()->getHeight());
-	return 1;
+	if (!instance()->isPassActive())
+		return 0;
+
+	const PassInfo &info = instance()->getActivePass();
+
+	for (const auto &attachment : info.colorAttachments)
+		luax_pushtype(L, attachment.canvas);
+
+	return info.colorAttachmentCount;
 }
 
-int w_getDimensions(lua_State *L)
+int w_captureScreenshot(lua_State *L)
 {
-	lua_pushinteger(L, instance()->getWidth());
-	lua_pushinteger(L, instance()->getHeight());
-	return 2;
+	luaL_checktype(L, 1, LUA_TFUNCTION);
+
+	Graphics::ScreenshotInfo info;
+	info.callback = screenshotCallback;
+
+	lua_pushvalue(L, 1);
+	info.ref = luax_refif(L, LUA_TFUNCTION);
+	lua_pop(L, 1);
+
+	luax_catchexcept(L,
+		[&]() { instance()->captureScreenshot(info); },
+		[&](bool except) { if (except) delete info.ref; }
+	);
+
+	return 0;
 }
 
 int w_setScissor(lua_State *L)
@@ -243,13 +443,13 @@ int w_stencil(lua_State *L)
 	if (lua_toboolean(L, 4) == 0)
 		instance()->clearStencil();
 
-	instance()->drawToStencilBuffer(action, stencilvalue);
+	luax_catchexcept(L, [&](){ instance()->drawToStencilBuffer(action, stencilvalue); });
 
 	// Call stencilfunc()
 	lua_pushvalue(L, 1);
 	lua_call(L, 0, 0);
 
-	instance()->stopDrawToStencilBuffer();
+	luax_catchexcept(L, [&](){ instance()->stopDrawToStencilBuffer(); });
 	return 0;
 }
 
@@ -268,7 +468,7 @@ int w_setStencilTest(lua_State *L)
 		comparevalue = (int) luaL_checknumber(L, 2);
 	}
 
-	instance()->setStencilTest(compare, comparevalue);
+	luax_catchexcept(L, [&](){ instance()->setStencilTest(compare, comparevalue); });
 	return 0;
 }
 
@@ -1246,79 +1446,6 @@ int w_isWireframe(lua_State *L)
 	return 1;
 }
 
-int w_newScreenshot(lua_State *L)
-{
-	love::image::Image *image = luax_getmodule<love::image::Image>(L);
-	bool copyAlpha = luax_optboolean(L, 1, false);
-	love::image::ImageData *i = 0;
-
-	luax_catchexcept(L, [&](){ i = instance()->newScreenshot(image, copyAlpha); });
-
-	luax_pushtype(L, i);
-	i->release();
-	return 1;
-}
-
-int w_setCanvas(lua_State *L)
-{
-	// Disable stencil writes.
-	instance()->stopDrawToStencilBuffer();
-
-	// called with none -> reset to default buffer
-	if (lua_isnoneornil(L, 1))
-	{
-		instance()->setCanvas();
-		return 0;
-	}
-
-	bool is_table = lua_istable(L, 1);
-	std::vector<Canvas *> canvases;
-
-	if (is_table)
-	{
-		for (int i = 1; i <= (int) luax_objlen(L, 1); i++)
-		{
-			lua_rawgeti(L, 1, i);
-			canvases.push_back(luax_checkcanvas(L, -1));
-			lua_pop(L, 1);
-		}
-	}
-	else
-	{
-		for (int i = 1; i <= lua_gettop(L); i++)
-			canvases.push_back(luax_checkcanvas(L, i));
-	}
-
-	luax_catchexcept(L, [&]() {
-		if (canvases.size() > 0)
-			instance()->setCanvas(canvases);
-		else
-			instance()->setCanvas();
-	});
-
-	return 0;
-}
-
-int w_getCanvas(lua_State *L)
-{
-	const std::vector<Canvas *> canvases = instance()->getCanvas();
-	int n = 0;
-
-	for (Canvas *c : canvases)
-	{
-		luax_pushtype(L, c);
-		n++;
-	}
-
-	if (n == 0)
-	{
-		lua_pushnil(L);
-		n = 1;
-	}
-
-	return n;
-}
-
 int w_setShader(lua_State *L)
 {
 	if (lua_isnoneornil(L,1))
@@ -1497,8 +1624,8 @@ int w_getStats(lua_State *L)
 	lua_pushinteger(L, stats.drawCalls);
 	lua_setfield(L, -2, "drawcalls");
 
-	lua_pushinteger(L, stats.canvasSwitches);
-	lua_setfield(L, -2, "canvasswitches");
+	lua_pushinteger(L, stats.renderPasses);
+	lua_setfield(L, -2, "renderpasses");
 
 	lua_pushinteger(L, stats.shaderSwitches);
 	lua_setfield(L, -2, "shaderswitches");
@@ -1541,24 +1668,37 @@ int w_draw(lua_State *L)
 		startidx = 2;
 	}
 
-	float x  = (float) luaL_optnumber(L, startidx + 0, 0.0);
-	float y  = (float) luaL_optnumber(L, startidx + 1, 0.0);
-	float a  = (float) luaL_optnumber(L, startidx + 2, 0.0);
-	float sx = (float) luaL_optnumber(L, startidx + 3, 1.0);
-	float sy = (float) luaL_optnumber(L, startidx + 4, sx);
-	float ox = (float) luaL_optnumber(L, startidx + 5, 0.0);
-	float oy = (float) luaL_optnumber(L, startidx + 6, 0.0);
-	float kx = (float) luaL_optnumber(L, startidx + 7, 0.0);
-	float ky = (float) luaL_optnumber(L, startidx + 8, 0.0);
+	if (luax_istype(L, startidx, math::Transform::type))
+	{
+		math::Transform *tf = luax_totype<math::Transform>(L, startidx);
+		luax_catchexcept(L, [&]() {
+			if (texture && quad)
+				instance()->drawq(texture, quad, tf->getMatrix());
+			else
+				instance()->draw(drawable, tf->getMatrix());
+		});
+	}
+	else
+	{
+		float x  = (float) luaL_optnumber(L, startidx + 0, 0.0);
+		float y  = (float) luaL_optnumber(L, startidx + 1, 0.0);
+		float a  = (float) luaL_optnumber(L, startidx + 2, 0.0);
+		float sx = (float) luaL_optnumber(L, startidx + 3, 1.0);
+		float sy = (float) luaL_optnumber(L, startidx + 4, sx);
+		float ox = (float) luaL_optnumber(L, startidx + 5, 0.0);
+		float oy = (float) luaL_optnumber(L, startidx + 6, 0.0);
+		float kx = (float) luaL_optnumber(L, startidx + 7, 0.0);
+		float ky = (float) luaL_optnumber(L, startidx + 8, 0.0);
 
-	Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
+		Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
 
-	luax_catchexcept(L, [&]() {
-		if (texture && quad)
-			texture->drawq(quad, m);
-		else if (drawable)
-			drawable->draw(m);
-	});
+		luax_catchexcept(L, [&]() {
+			if (texture && quad)
+				instance()->drawq(texture, quad, m);
+			else if (drawable)
+				instance()->draw(drawable, m);
+		});
+	}
 
 	return 0;
 }
@@ -1568,19 +1708,27 @@ int w_print(lua_State *L)
 	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);
-	float sx = (float)luaL_optnumber(L, 5, 1.0f);
-	float sy = (float)luaL_optnumber(L, 6, sx);
-	float ox = (float)luaL_optnumber(L, 7, 0.0f);
-	float oy = (float)luaL_optnumber(L, 8, 0.0f);
-	float kx = (float)luaL_optnumber(L, 9, 0.0f);
-	float ky = (float)luaL_optnumber(L, 10, 0.0f);
+	if (luax_istype(L, 2, math::Transform::type))
+	{
+		math::Transform *tf = luax_totype<math::Transform>(L, 2);
+		luax_catchexcept(L, [&](){ instance()->print(str, tf->getMatrix()); });
+	}
+	else
+	{
+		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);
+		float sx = (float)luaL_optnumber(L, 5, 1.0f);
+		float sy = (float)luaL_optnumber(L, 6, sx);
+		float ox = (float)luaL_optnumber(L, 7, 0.0f);
+		float oy = (float)luaL_optnumber(L, 8, 0.0f);
+		float kx = (float)luaL_optnumber(L, 9, 0.0f);
+		float ky = (float)luaL_optnumber(L, 10, 0.0f);
 
-	Matrix4 m(x, y, angle, sx, sy, ox, oy, kx, ky);
+		Matrix4 m(x, y, angle, sx, sy, ox, oy, kx, ky);
 
-	luax_catchexcept(L, [&](){ instance()->print(str, m); });
+		luax_catchexcept(L, [&](){ instance()->print(str, m); });
+	}
 	return 0;
 }
 
@@ -1589,36 +1737,38 @@ int w_printf(lua_State *L)
 	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);
-
-	float angle = 0.0f;
-	float sx = 1.0f, sy = 1.0f;
-	float ox = 0.0f, oy = 0.0f;
-	float kx = 0.0f, ky = 0.0f;
-
 	Font::AlignMode align = Font::ALIGN_LEFT;
+	Matrix4 m;
 
-	if (lua_gettop(L) >= 5)
+	int formatidx = 4;
+
+	if (luax_istype(L, 2, math::Transform::type))
 	{
-		if (!lua_isnil(L, 5))
-		{
-			const char *str = luaL_checkstring(L, 5);
-			if (!Font::getConstant(str, align))
-				return luaL_error(L, "Incorrect alignment: %s", str);
-		}
+		math::Transform *tf = luax_totype<math::Transform>(L, 2);
+		m = tf->getMatrix();
+		formatidx = 3;
+	}
+	else
+	{
+		float x = (float)luaL_checknumber(L, 2);
+		float y = (float)luaL_checknumber(L, 3);
 
-		angle = (float) luaL_optnumber(L, 6, 0.0f);
-		sx = (float) luaL_optnumber(L, 7, 1.0f);
-		sy = (float) luaL_optnumber(L, 8, sx);
-		ox = (float) luaL_optnumber(L, 9, 0.0f);
-		oy = (float) luaL_optnumber(L, 10, 0.0f);
-		kx = (float) luaL_optnumber(L, 11, 0.0f);
-		ky = (float) luaL_optnumber(L, 12, 0.0f);
+		float angle = (float) luaL_optnumber(L, 6, 0.0f);
+		float sx = (float) luaL_optnumber(L, 7, 1.0f);
+		float sy = (float) luaL_optnumber(L, 8, sx);
+		float ox = (float) luaL_optnumber(L, 9, 0.0f);
+		float oy = (float) luaL_optnumber(L, 10, 0.0f);
+		float kx = (float) luaL_optnumber(L, 11, 0.0f);
+		float ky = (float) luaL_optnumber(L, 12, 0.0f);
+
+		m = Matrix4(x, y, angle, sx, sy, ox, oy, kx, ky);
 	}
 
-	Matrix4 m(x, y, angle, sx, sy, ox, oy, kx, ky);
+	float wrap = (float)luaL_checknumber(L, formatidx);
+
+	const char *astr = lua_isnoneornil(L, formatidx + 1) ? nullptr : luaL_checkstring(L, formatidx + 1);
+	if (astr != nullptr && !Font::getConstant(astr, align))
+		return luaL_error(L, "Incorrect alignment: %s", astr);
 
 	luax_catchexcept(L, [&](){ instance()->printf(str, wrap, align, m); });
 	return 0;
@@ -1698,11 +1848,14 @@ int w_points(lua_State *L)
 			coords[i] = luax_tofloat(L, i + 1);
 	}
 
-	instance()->points(coords, colors, numpoints);
-
-	delete[] coords;
-	if (colors)
-		delete[] colors;
+	luax_catchexcept(L,
+		[&](){ instance()->points(coords, colors, numpoints); },
+		[&](bool) {
+			delete[] coords;
+			if (colors)
+				delete[] colors;
+		}
+	);
 
 	return 0;
 }
@@ -1738,9 +1891,11 @@ int w_line(lua_State *L)
 			coords[i] = luax_tofloat(L, i + 1);
 	}
 
-	instance()->polyline(coords, args);
+	luax_catchexcept(L,
+		[&](){ instance()->polyline(coords, args); },
+		[&](bool) { delete[] coords; }
+	);
 
-	delete[] coords;
 	return 0;
 }
 
@@ -1766,11 +1921,11 @@ int w_rectangle(lua_State *L)
 	float ry = (float)luaL_optnumber(L, 7, rx);
 
 	if (lua_isnoneornil(L, 8))
-		instance()->rectangle(mode, x, y, w, h, rx, ry);
+		luax_catchexcept(L, [&](){ instance()->rectangle(mode, x, y, w, h, rx, ry); });
 	else
 	{
 		int points = (int) luaL_checknumber(L, 8);
-		instance()->rectangle(mode, x, y, w, h, rx, ry, points);
+		luax_catchexcept(L, [&](){ instance()->rectangle(mode, x, y, w, h, rx, ry, points); });
 	}
 
 	return 0;
@@ -1788,11 +1943,11 @@ int w_circle(lua_State *L)
 	float radius = (float)luaL_checknumber(L, 4);
 
 	if (lua_isnoneornil(L, 5))
-		instance()->circle(mode, x, y, radius);
+		luax_catchexcept(L, [&](){ instance()->circle(mode, x, y, radius); });
 	else
 	{
 		int points = (int) luaL_checknumber(L, 5);
-		instance()->circle(mode, x, y, radius, points);
+		luax_catchexcept(L, [&](){ instance()->circle(mode, x, y, radius, points); });
 	}
 
 	return 0;
@@ -1811,11 +1966,11 @@ int w_ellipse(lua_State *L)
 	float b = (float)luaL_optnumber(L, 5, a);
 
 	if (lua_isnoneornil(L, 6))
-		instance()->ellipse(mode, x, y, a, b);
+		luax_catchexcept(L, [&](){ instance()->ellipse(mode, x, y, a, b); });
 	else
 	{
 		int points = (int) luaL_checknumber(L, 6);
-		instance()->ellipse(mode, x, y, a, b, points);
+		luax_catchexcept(L, [&](){ instance()->ellipse(mode, x, y, a, b, points); });
 	}
 
 	return 0;
@@ -1848,11 +2003,11 @@ int w_arc(lua_State *L)
 	float angle2 = (float) luaL_checknumber(L, startidx + 4);
 
 	if (lua_isnoneornil(L, startidx + 5))
-		instance()->arc(drawmode, arcmode, x, y, radius, angle1, angle2);
+		luax_catchexcept(L, [&](){ instance()->arc(drawmode, arcmode, x, y, radius, angle1, angle2); });
 	else
 	{
 		int points = (int) luaL_checknumber(L, startidx + 5);
-		instance()->arc(drawmode, arcmode, x, y, radius, angle1, angle2, points);
+		luax_catchexcept(L, [&](){ instance()->arc(drawmode, arcmode, x, y, radius, angle1, angle2, points); });
 	}
 
 	return 0;
@@ -1900,8 +2055,11 @@ int w_polygon(lua_State *L)
 	// make a closed loop
 	coords[args]   = coords[0];
 	coords[args+1] = coords[1];
-	instance()->polygon(mode, coords, args+2);
-	delete[] coords;
+
+	luax_catchexcept(L,
+		[&](){ instance()->polygon(mode, coords, args+2); },
+		[&](bool) { delete[] coords; }
+	);
 
 	return 0;
 }
@@ -1914,6 +2072,13 @@ int w_push(lua_State *L)
 		return luaL_error(L, "Invalid graphics stack type: %s", sname);
 
 	luax_catchexcept(L, [&](){ instance()->push(stype); });
+
+	if (luax_istype(L, 2, math::Transform::type))
+	{
+		math::Transform *t = luax_totype<math::Transform>(L, 2);
+		instance()->applyTransform(t);
+	}
+
 	return 0;
 }
 
@@ -1960,6 +2125,20 @@ int w_origin(lua_State * /*L*/)
 	return 0;
 }
 
+int w_applyTransform(lua_State *L)
+{
+	math::Transform *t = math::luax_checktransform(L, 1);
+	instance()->applyTransform(t);
+	return 0;
+}
+
+int w_replaceTransform(lua_State *L)
+{
+	math::Transform *t = math::luax_checktransform(L, 1);
+	instance()->replaceTransform(t);
+	return 0;
+}
+
 int w_transformPoint(lua_State *L)
 {
 	Vector p;
@@ -1987,8 +2166,6 @@ int w_inverseTransformPoint(lua_State *L)
 static const luaL_Reg functions[] =
 {
 	{ "reset", w_reset },
-	{ "clear", w_clear },
-	{ "discard", w_discard },
 	{ "present", w_present },
 
 	{ "newImage", w_newImage },
@@ -2030,9 +2207,6 @@ static const luaL_Reg functions[] =
 	{ "getPointSize", w_getPointSize },
 	{ "setWireframe", w_setWireframe },
 	{ "isWireframe", w_isWireframe },
-	{ "newScreenshot", w_newScreenshot },
-	{ "setCanvas", w_setCanvas },
-	{ "getCanvas", w_getCanvas },
 
 	{ "setShader", w_setShader },
 	{ "getShader", w_getShader },
@@ -2046,6 +2220,14 @@ static const luaL_Reg functions[] =
 	{ "getSystemLimits", w_getSystemLimits },
 	{ "getStats", w_getStats },
 
+	{ "beginPass", w_beginPass },
+	{ "endPass", w_endPass },
+	{ "renderPass", w_renderPass },
+	{ "isPassActive", w_isPassActive },
+	{ "getPassCanvases", w_getPassCanvases },
+
+	{ "captureScreenshot", w_captureScreenshot },
+
 	{ "draw", w_draw },
 
 	{ "print", w_print },
@@ -2057,6 +2239,9 @@ static const luaL_Reg functions[] =
 	{ "getWidth", w_getWidth },
 	{ "getHeight", w_getHeight },
 	{ "getDimensions", w_getDimensions },
+	{ "getPassWidth", w_getPassWidth },
+	{ "getPassHeight", w_getPassHeight },
+	{ "getPassDimensions", w_getPassDimensions },
 
 	{ "setScissor", w_setScissor },
 	{ "intersectScissor", w_intersectScissor },
@@ -2082,6 +2267,8 @@ static const luaL_Reg functions[] =
 	{ "translate", w_translate },
 	{ "shear", w_shear },
 	{ "origin", w_origin },
+	{ "applyTransform", w_applyTransform },
+	{ "replaceTransform", w_replaceTransform },
 	{ "transformPoint", w_transformPoint },
 	{ "inverseTransformPoint", w_inverseTransformPoint },
 

+ 13 - 1
src/modules/graphics/opengl/wrap_Mesh.cpp

@@ -338,10 +338,21 @@ int w_Mesh_attachAttribute(lua_State *L)
 	Mesh *t = luax_checkmesh(L, 1);
 	const char *name = luaL_checkstring(L, 2);
 	Mesh *mesh = luax_checkmesh(L, 3);
-	luax_catchexcept(L, [&](){ t->attachAttribute(name, mesh); });
+	const char *attachname = luaL_optstring(L, 4, name);
+	luax_catchexcept(L, [&](){ t->attachAttribute(name, mesh, attachname); });
 	return 0;
 }
 
+int w_Mesh_detachAttribute(lua_State *L)
+{
+	Mesh *t = luax_checkmesh(L, 1);
+	const char *name = luaL_checkstring(L, 2);
+	bool success = false;
+	luax_catchexcept(L, [&](){ success = t->detachAttribute(name); });
+	luax_pushboolean(L, success);
+	return 1;
+}
+
 int w_Mesh_flush(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
@@ -514,6 +525,7 @@ static const luaL_Reg w_Mesh_functions[] =
 	{ "setAttributeEnabled", w_Mesh_setAttributeEnabled },
 	{ "isAttributeEnabled", w_Mesh_isAttributeEnabled },
 	{ "attachAttribute", w_Mesh_attachAttribute },
+	{ "detachAttribute", w_Mesh_detachAttribute },
 	{ "flush", w_Mesh_flush },
 	{ "setVertexMap", w_Mesh_setVertexMap },
 	{ "getVertexMap", w_Mesh_getVertexMap },

+ 33 - 22
src/modules/graphics/opengl/wrap_Shader.cpp

@@ -21,6 +21,7 @@
 #include "wrap_Shader.h"
 #include "graphics/wrap_Texture.h"
 #include "math/MathModule.h"
+#include "math/Transform.h"
 
 #include <string>
 #include <algorithm>
@@ -48,14 +49,12 @@ int w_Shader_getWarnings(lua_State *L)
 
 static int _getCount(lua_State *L, int startidx, const Shader::UniformInfo *info)
 {
-	return std::min(std::max(lua_gettop(L) - startidx, 1), info->count);
+	return std::min(std::max(lua_gettop(L) - startidx + 1, 1), info->count);
 }
 
 template <typename T>
-static T *_getNumbers(lua_State *L, int startidx, Shader *shader, int components, int count)
+static void _updateNumbers(lua_State *L, int startidx, T *values, int components, int count)
 {
-	T *values = shader->getScratchBuffer<T>(components * count);
-
 	if (components == 1)
 	{
 		for (int i = 0; i < count; ++i)
@@ -76,16 +75,15 @@ static T *_getNumbers(lua_State *L, int startidx, Shader *shader, int components
 			lua_pop(L, components);
 		}
 	}
-
-	return values;
 }
 
 int w_Shader_sendFloats(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info, bool colors)
 {
 	int count = _getCount(L, startidx, info);
 	int components = info->components;
+	float *values = info->floats;
 
-	float *values = _getNumbers<float>(L, startidx, shader, components, count);
+	_updateNumbers(L, startidx, values, components, count);
 
 	if (colors && graphics::isGammaCorrect())
 	{
@@ -99,15 +97,15 @@ int w_Shader_sendFloats(lua_State *L, int startidx, Shader *shader, const Shader
 		}
 	}
 
-	luax_catchexcept(L, [&]() { shader->sendFloats(info, values, count); });
+	luax_catchexcept(L, [&]() { shader->updateUniform(info, count); });
 	return 0;
 }
 
 int w_Shader_sendInts(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info)
 {
 	int count = _getCount(L, startidx, info);
-	int *values = _getNumbers<int>(L, startidx, shader, info->components, count);
-	luax_catchexcept(L, [&]() { shader->sendInts(info, values, count); });
+	_updateNumbers(L, startidx, info->ints, info->components, count);
+	luax_catchexcept(L, [&]() { shader->updateUniform(info, count); });
 	return 0;
 }
 
@@ -116,15 +114,15 @@ int w_Shader_sendBooleans(lua_State *L, int startidx, Shader *shader, const Shad
 	int count = _getCount(L, startidx, info);
 	int components = info->components;
 
-	// We have to send booleans as ints or floats.
-	float *values = shader->getScratchBuffer<float>(components * count);
+	// We have to send booleans as ints.
+	int *values = info->ints;
 
 	if (components == 1)
 	{
 		for (int i = 0; i < count; i++)
 		{
 			luaL_checktype(L, startidx + i, LUA_TBOOLEAN);
-			values[i] = (float) lua_toboolean(L, startidx + i);
+			values[i] = (int) lua_toboolean(L, startidx + i);
 		}
 	}
 	else
@@ -137,14 +135,14 @@ int w_Shader_sendBooleans(lua_State *L, int startidx, Shader *shader, const Shad
 			{
 				lua_rawgeti(L, startidx + i, k);
 				luaL_checktype(L, -1, LUA_TBOOLEAN);
-				values[i * components + k - 1] = (float) lua_toboolean(L, -1);
+				values[i * components + k - 1] = (int) lua_toboolean(L, -1);
 			}
 
 			lua_pop(L, components);
 		}
 	}
 
-	luax_catchexcept(L, [&]() { shader->sendFloats(info, values, count); });
+	luax_catchexcept(L, [&]() { shader->updateUniform(info, count); });
 	return 0;
 }
 
@@ -163,10 +161,17 @@ int w_Shader_sendMatrices(lua_State *L, int startidx, Shader *shader, const Shad
 	int rows = info->matrix.rows;
 	int elements = columns * rows;
 
-	float *values = shader->getScratchBuffer<float>(elements * count);
+	float *values = info->floats;
 
 	for (int i = 0; i < count; i++)
 	{
+		if (columns == 4 && rows == 4 && luax_istype(L, startidx + i, math::Transform::type))
+		{
+			math::Transform *t = luax_totype<math::Transform>(L, startidx + i);
+			memcpy(&values[i * 16], t->getMatrix().getElements(), sizeof(float) * 16);
+			continue;
+		}
+
 		luaL_checktype(L, startidx + i, LUA_TTABLE);
 
 		lua_rawgeti(L, startidx + i, 1);
@@ -243,15 +248,21 @@ int w_Shader_sendMatrices(lua_State *L, int startidx, Shader *shader, const Shad
 		}
 	}
 
-	shader->sendMatrices(info, values, count);
+	shader->updateUniform(info, count);
 	return 0;
 }
 
-int w_Shader_sendTexture(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info)
+int w_Shader_sendTextures(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info)
 {
-	// We don't support arrays of textures (yet).
-	Texture *texture = luax_checktexture(L, startidx);
-	luax_catchexcept(L, [&]() { shader->sendTexture(info, texture); });
+	int count = _getCount(L, startidx, info);
+
+	std::vector<Texture *> textures;
+	textures.reserve(count);
+
+	for (int i = 0; i < count; i++)
+		textures.push_back(luax_checktexture(L, startidx + i));
+
+	luax_catchexcept(L, [&]() { shader->sendTextures(info, textures.data(), count); });
 	return 0;
 }
 
@@ -277,7 +288,7 @@ int w_Shader_send(lua_State *L)
 	case Shader::UNIFORM_BOOL:
 		return w_Shader_sendBooleans(L, startidx, shader, info);
 	case Shader::UNIFORM_SAMPLER:
-		return w_Shader_sendTexture(L, startidx, shader, info);
+		return w_Shader_sendTextures(L, startidx, shader, info);
 	default:
 		return luaL_error(L, "Unknown variable type for shader uniform '%s", name);
 	}

+ 32 - 18
src/modules/graphics/opengl/wrap_SpriteBatch.cpp

@@ -23,6 +23,7 @@
 #include "Image.h"
 #include "Canvas.h"
 #include "graphics/wrap_Texture.h"
+#include "math/wrap_Transform.h"
 
 // C++
 #include <typeinfo>
@@ -51,24 +52,37 @@ static inline int w_SpriteBatch_add_or_set(lua_State *L, SpriteBatch *t, int sta
 	else if (lua_isnil(L, startidx) && !lua_isnoneornil(L, startidx + 1))
 		return luax_typerror(L, startidx, "Quad");
 
-	float x  = (float) luaL_optnumber(L, startidx + 0, 0.0);
-	float y  = (float) luaL_optnumber(L, startidx + 1, 0.0);
-	float a  = (float) luaL_optnumber(L, startidx + 2, 0.0);
-	float sx = (float) luaL_optnumber(L, startidx + 3, 1.0);
-	float sy = (float) luaL_optnumber(L, startidx + 4, sx);
-	float ox = (float) luaL_optnumber(L, startidx + 5, 0.0);
-	float oy = (float) luaL_optnumber(L, startidx + 6, 0.0);
-	float kx = (float) luaL_optnumber(L, startidx + 7, 0.0);
-	float ky = (float) luaL_optnumber(L, startidx + 8, 0.0);
-
-	Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
-
-	luax_catchexcept(L, [&]() {
-		if (quad)
-			index = t->addq(quad, m, index);
-		else
-			index = t->add(m, index);
-	});
+	if (luax_istype(L, startidx, math::Transform::type))
+	{
+		math::Transform *tf = luax_totype<math::Transform>(L, startidx);
+		luax_catchexcept(L, [&]() {
+			if (quad)
+				index = t->addq(quad, tf->getMatrix(), index);
+			else
+				index = t->add(tf->getMatrix(), index);
+		});
+	}
+	else
+	{
+		float x  = (float) luaL_optnumber(L, startidx + 0, 0.0);
+		float y  = (float) luaL_optnumber(L, startidx + 1, 0.0);
+		float a  = (float) luaL_optnumber(L, startidx + 2, 0.0);
+		float sx = (float) luaL_optnumber(L, startidx + 3, 1.0);
+		float sy = (float) luaL_optnumber(L, startidx + 4, sx);
+		float ox = (float) luaL_optnumber(L, startidx + 5, 0.0);
+		float oy = (float) luaL_optnumber(L, startidx + 6, 0.0);
+		float kx = (float) luaL_optnumber(L, startidx + 7, 0.0);
+		float ky = (float) luaL_optnumber(L, startidx + 8, 0.0);
+
+		Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
+
+		luax_catchexcept(L, [&]() {
+			if (quad)
+				index = t->addq(quad, m, index);
+			else
+				index = t->add(m, index);
+		});
+	}
 
 	return index;
 }

+ 47 - 28
src/modules/graphics/opengl/wrap_Text.cpp

@@ -19,6 +19,7 @@
  **/
 
 #include "wrap_Text.h"
+#include "math/wrap_Transform.h"
 
 namespace love
 {
@@ -126,24 +127,33 @@ int w_Text_add(lua_State *L)
 {
 	Text *t = luax_checktext(L, 1);
 
+	int index = 0;
+
 	std::vector<Font::ColoredString> text;
 	luax_checkcoloredstring(L, 2, text);
 
-	float x  = (float) luaL_optnumber(L, 3, 0.0);
-	float y  = (float) luaL_optnumber(L, 4, 0.0);
-	float a  = (float) luaL_optnumber(L, 5, 0.0);
-	float sx = (float) luaL_optnumber(L, 6, 1.0);
-	float sy = (float) luaL_optnumber(L, 7, sx);
-	float ox = (float) luaL_optnumber(L, 8, 0.0);
-	float oy = (float) luaL_optnumber(L, 9, 0.0);
-	float kx = (float) luaL_optnumber(L, 10, 0.0);
-	float ky = (float) luaL_optnumber(L, 11, 0.0);
-
-	Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
-	int index = 0;
-	luax_catchexcept(L, [&](){ index = t->add(text, m); });
-	lua_pushnumber(L, index + 1);
+	if (luax_istype(L, 3, math::Transform::type))
+	{
+		math::Transform *tf = luax_totype<math::Transform>(L, 3);
+		luax_catchexcept(L, [&](){ index = t->add(text, tf->getMatrix()); });
+	}
+	else
+	{
+		float x  = (float) luaL_optnumber(L, 3, 0.0);
+		float y  = (float) luaL_optnumber(L, 4, 0.0);
+		float a  = (float) luaL_optnumber(L, 5, 0.0);
+		float sx = (float) luaL_optnumber(L, 6, 1.0);
+		float sy = (float) luaL_optnumber(L, 7, sx);
+		float ox = (float) luaL_optnumber(L, 8, 0.0);
+		float oy = (float) luaL_optnumber(L, 9, 0.0);
+		float kx = (float) luaL_optnumber(L, 10, 0.0);
+		float ky = (float) luaL_optnumber(L, 11, 0.0);
+
+		Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
+		luax_catchexcept(L, [&](){ index = t->add(text, m); });
+	}
 
+	lua_pushnumber(L, index + 1);
 	return 1;
 }
 
@@ -151,6 +161,8 @@ int w_Text_addf(lua_State *L)
 {
 	Text *t = luax_checktext(L, 1);
 
+	int index = 0;
+
 	std::vector<Font::ColoredString> text;
 	luax_checkcoloredstring(L, 2, text);
 
@@ -162,21 +174,28 @@ int w_Text_addf(lua_State *L)
 	if (!Font::getConstant(alignstr, align))
 		return luaL_error(L, "Invalid align mode: %s", alignstr);
 
-	float x  = (float) luaL_optnumber(L, 5, 0.0);
-	float y  = (float) luaL_optnumber(L, 6, 0.0);
-	float a  = (float) luaL_optnumber(L, 7, 0.0);
-	float sx = (float) luaL_optnumber(L, 8, 1.0);
-	float sy = (float) luaL_optnumber(L, 9, sx);
-	float ox = (float) luaL_optnumber(L, 10, 0.0);
-	float oy = (float) luaL_optnumber(L, 11, 0.0);
-	float kx = (float) luaL_optnumber(L, 12, 0.0);
-	float ky = (float) luaL_optnumber(L, 13, 0.0);
-
-	Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
-	int index = 0;
-	luax_catchexcept(L, [&](){ index = t->addf(text, wrap, align, m); });
-	lua_pushnumber(L, index + 1);
+	if (luax_istype(L, 5, math::Transform::type))
+	{
+		math::Transform *tf = luax_totype<math::Transform>(L, 5);
+		luax_catchexcept(L, [&](){ index = t->addf(text, wrap, align, tf->getMatrix()); });
+	}
+	else
+	{
+		float x  = (float) luaL_optnumber(L, 5, 0.0);
+		float y  = (float) luaL_optnumber(L, 6, 0.0);
+		float a  = (float) luaL_optnumber(L, 7, 0.0);
+		float sx = (float) luaL_optnumber(L, 8, 1.0);
+		float sy = (float) luaL_optnumber(L, 9, sx);
+		float ox = (float) luaL_optnumber(L, 10, 0.0);
+		float oy = (float) luaL_optnumber(L, 11, 0.0);
+		float kx = (float) luaL_optnumber(L, 12, 0.0);
+		float ky = (float) luaL_optnumber(L, 13, 0.0);
+
+		Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
+		luax_catchexcept(L, [&](){ index = t->addf(text, wrap, align, m); });
+	}
 
+	lua_pushnumber(L, index + 1);
 	return 1;
 }
 

+ 11 - 0
src/modules/math/MathModule.cpp

@@ -25,6 +25,7 @@
 #include "common/int.h"
 #include "common/StringMap.h"
 #include "BezierCurve.h"
+#include "Transform.h"
 
 // STL
 #include <cmath>
@@ -374,6 +375,16 @@ BezierCurve *Math::newBezierCurve(const std::vector<Vector> &points)
 	return new BezierCurve(points);
 }
 
+Transform *Math::newTransform()
+{
+	return new Transform();
+}
+
+Transform *Math::newTransform(float x, float y, float a, float sx, float sy, float ox, float oy, float kx, float ky)
+{
+	return new Transform(x, y, a, sx, sy, ox, oy, kx, ky);
+}
+
 std::string hash(HashFunction::Function function, Data *input)
 {
 	return hash(function, (const char*) input->getData(), input->getSize());

+ 4 - 0
src/modules/math/MathModule.h

@@ -45,6 +45,7 @@ namespace math
 {
 
 class BezierCurve;
+class Transform;
 
 struct Triangle
 {
@@ -176,6 +177,9 @@ public:
 	 **/
 	BezierCurve *newBezierCurve(const std::vector<Vector> &points);
 
+	Transform *newTransform();
+	Transform *newTransform(float x, float y, float a, float sx, float sy, float ox, float oy, float kx, float ky);
+
 	// Implements Module.
 	virtual ModuleType getModuleType() const
 	{

+ 133 - 0
src/modules/math/Transform.cpp

@@ -0,0 +1,133 @@
+/**
+ * Copyright (c) 2006-2016 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 "Transform.h"
+
+namespace love
+{
+namespace math
+{
+
+love::Type Transform::type("Transform", &Object::type);
+
+Transform::Transform()
+	: matrix()
+	, inverseDirty(true)
+	, inverseMatrix()
+{
+}
+
+Transform::Transform(const Matrix4 &m)
+	: matrix(m)
+	, inverseDirty(true)
+	, inverseMatrix()
+{
+}
+
+Transform::Transform(float x, float y, float a, float sx, float sy, float ox, float oy, float kx, float ky)
+	: matrix(x, y, a, sx, sy, ox, oy, kx, ky)
+	, inverseDirty(true)
+	, inverseMatrix()
+{
+}
+
+Transform::~Transform()
+{
+}
+
+Transform *Transform::clone()
+{
+	return new Transform(*this);
+}
+
+Transform *Transform::inverse()
+{
+	return new Transform(getInverseMatrix());
+}
+
+void Transform::apply(Transform *other)
+{
+	matrix *= other->getMatrix();
+	inverseDirty = true;
+}
+
+void Transform::translate(float x, float y)
+{
+	matrix.translate(x, y);
+	inverseDirty = true;
+}
+
+void Transform::rotate(float angle)
+{
+	matrix.rotate(angle);
+	inverseDirty = true;
+}
+
+void Transform::scale(float x, float y)
+{
+	matrix.scale(x, y);
+	inverseDirty = true;
+}
+
+void Transform::shear(float x, float y)
+{
+	matrix.shear(x, y);
+	inverseDirty = true;
+}
+
+void Transform::reset()
+{
+	matrix.setIdentity();
+	inverseDirty = true;
+}
+
+void Transform::setTransformation(float x, float y, float a, float sx, float sy, float ox, float oy, float kx, float ky)
+{
+	matrix.setTransformation(x, y, a, sx, sy, ox, oy, kx, ky);
+	inverseDirty = true;
+}
+
+love::Vector Transform::transformPoint(love::Vector p) const
+{
+	love::Vector result;
+	matrix.transform(&result, &p, 1);
+	return result;
+}
+
+love::Vector Transform::inverseTransformPoint(love::Vector p)
+{
+	love::Vector result;
+	getInverseMatrix().transform(&result, &p, 1);
+	return result;
+}
+
+const Matrix4 &Transform::getMatrix() const
+{
+	return matrix;
+}
+
+void Transform::setMatrix(const Matrix4 &m)
+{
+	matrix = m;
+	inverseDirty = true;
+}
+
+} // math
+} // love

+ 85 - 0
src/modules/math/Transform.h

@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2006-2016 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/Object.h"
+#include "common/Matrix.h"
+#include "common/Vector.h"
+
+namespace love
+{
+namespace math
+{
+
+class Transform : public Object
+{
+public:
+
+	static love::Type type;
+
+	Transform();
+	Transform(const Matrix4 &m);
+	Transform(float x, float y, float a, float sx, float sy, float ox, float oy, float kx, float ky);
+
+	virtual ~Transform();
+
+	Transform *clone();
+	Transform *inverse();
+
+	void apply(Transform *other);
+
+	void translate(float x, float y);
+	void rotate(float angle);
+	void scale(float x, float y);
+	void shear(float x, float y);
+
+	void reset();
+	void setTransformation(float x, float y, float a, float sx, float sy, float ox, float oy, float kx, float ky);
+
+	love::Vector transformPoint(love::Vector p) const;
+	love::Vector inverseTransformPoint(love::Vector p);
+
+	const Matrix4 &getMatrix() const;
+	void setMatrix(const Matrix4 &m);
+
+private:
+
+	inline const Matrix4 &getInverseMatrix()
+	{
+		if (inverseDirty)
+		{
+			inverseDirty = false;
+			inverseMatrix = matrix.inverse();
+		}
+
+		return inverseMatrix;
+	}
+	
+	Matrix4 matrix;
+	bool inverseDirty;
+	Matrix4 inverseMatrix;
+
+}; // Transform
+
+
+} // math
+} // love

+ 29 - 0
src/modules/math/wrap_Math.cpp

@@ -22,8 +22,10 @@
 #include "wrap_RandomGenerator.h"
 #include "wrap_BezierCurve.h"
 #include "wrap_CompressedData.h"
+#include "wrap_Transform.h"
 #include "MathModule.h"
 #include "BezierCurve.h"
+#include "Transform.h"
 #include "common/b64.h"
 
 #include <cmath>
@@ -118,6 +120,31 @@ int w_newBezierCurve(lua_State *L)
 	return 1;
 }
 
+int w_newTransform(lua_State *L)
+{
+	Transform *t = nullptr;
+
+	if (lua_isnoneornil(L, 1))
+		t = Math::instance.newTransform();
+	else
+	{
+		float x =  (float) luaL_checknumber(L, 1);
+		float y =  (float) luaL_checknumber(L, 2);
+		float a =  (float) luaL_optnumber(L, 3, 0.0);
+		float sx = (float) luaL_optnumber(L, 4, 1.0);
+		float sy = (float) luaL_optnumber(L, 5, sx);
+		float ox = (float) luaL_optnumber(L, 6, 0.0);
+		float oy = (float) luaL_optnumber(L, 7, 0.0);
+		float kx = (float) luaL_optnumber(L, 8, 0.0);
+		float ky = (float) luaL_optnumber(L, 9, 0.0);
+		t = Math::instance.newTransform(x, y, a, sx, sy, ox, oy, kx, ky);
+	}
+
+	luax_pushtype(L, t);
+	t->release();
+	return 1;
+}
+
 int w_triangulate(lua_State *L)
 {
 	std::vector<love::Vector> vertices;
@@ -507,6 +534,7 @@ static const luaL_Reg functions[] =
 	{ "_getRandomGenerator", w__getRandomGenerator },
 	{ "newRandomGenerator", w_newRandomGenerator },
 	{ "newBezierCurve", w_newBezierCurve },
+	{ "newTransform", w_newTransform },
 	{ "triangulate", w_triangulate },
 	{ "isConvex", w_isConvex },
 	{ "gammaToLinear", w_gammaToLinear },
@@ -525,6 +553,7 @@ static const lua_CFunction types[] =
 	luaopen_randomgenerator,
 	luaopen_beziercurve,
 	luaopen_compresseddata,
+	luaopen_transform,
 	0
 };
 

+ 305 - 0
src/modules/math/wrap_Transform.cpp

@@ -0,0 +1,305 @@
+/**
+ * Copyright (c) 2006-2016 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 "wrap_Transform.h"
+
+namespace love
+{
+namespace math
+{
+
+Transform *luax_checktransform(lua_State *L, int idx)
+{
+	return luax_checktype<Transform>(L, idx, Transform::type);
+}
+
+int w_Transform_clone(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	Transform *newtransform = t->clone();
+	luax_pushtype(L, newtransform);
+	newtransform->release();
+	return 1;
+}
+
+int w_Transform_inverse(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	Transform *inverse = t->inverse();
+	luax_pushtype(L, inverse);
+	inverse->release();
+	return 1;
+}
+
+int w_Transform_apply(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	Transform *other = luax_checktransform(L, 2);
+	t->apply(other);
+	lua_pushvalue(L, 1);
+	return 1;
+}
+
+int w_Transform_translate(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	float x = (float) luaL_checknumber(L, 2);
+	float y = (float) luaL_checknumber(L, 3);
+	t->translate(x, y);
+	lua_pushvalue(L, 1);
+	return 1;
+}
+
+int w_Transform_rotate(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	float angle = (float) luaL_checknumber(L, 2);
+	t->rotate(angle);
+	lua_pushvalue(L, 1);
+	return 1;
+}
+
+int w_Transform_scale(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	float sx = (float) luaL_checknumber(L, 2);
+	float sy = (float) luaL_optnumber(L, 3, sx);
+	t->scale(sx, sy);
+	lua_pushvalue(L, 1);
+	return 1;
+}
+
+int w_Transform_shear(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	float kx = (float) luaL_checknumber(L, 2);
+	float ky = (float) luaL_checknumber(L, 3);
+	t->shear(kx, ky);
+	lua_pushvalue(L, 1);
+	return 1;
+}
+
+int w_Transform_reset(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	t->reset();
+	lua_pushvalue(L, 1);
+	return 1;
+}
+
+int w_Transform_setTransformation(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	float x =  (float) luaL_optnumber(L, 2, 0.0);
+	float y =  (float) luaL_optnumber(L, 3, 0.0);
+	float a =  (float) luaL_optnumber(L, 4, 0.0);
+	float sx = (float) luaL_optnumber(L, 5, 1.0);
+	float sy = (float) luaL_optnumber(L, 6, sx);
+	float ox = (float) luaL_optnumber(L, 7, 0.0);
+	float oy = (float) luaL_optnumber(L, 8, 0.0);
+	float kx = (float) luaL_optnumber(L, 9, 0.0);
+	float ky = (float) luaL_optnumber(L, 10, 0.0);
+	t->setTransformation(x, y, a, sx, sy, ox, oy, kx, ky);
+	lua_pushvalue(L, 1);
+	return 1;
+}
+
+int w_Transform_setMatrix(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+
+	bool columnmajor = false;
+
+	int idx = 2;
+	if (lua_isboolean(L, idx))
+	{
+		columnmajor = lua_toboolean(L, idx);
+		idx++;
+	}
+
+	float elements[16];
+
+	if (lua_istable(L, idx))
+	{
+		lua_rawgeti(L, idx, 1);
+		bool tableoftables = lua_istable(L, -1);
+		lua_pop(L, 1);
+
+		if (tableoftables)
+		{
+			if (columnmajor)
+			{
+				for (int column = 0; column < 4; column++)
+				{
+					lua_rawgeti(L, idx, column + 1);
+
+					for (int row = 0; row < 4; row++)
+					{
+						lua_rawgeti(L, -(row + 1), row + 1);
+						elements[column * 4 + row] = (float) luaL_checknumber(L, -1);
+					}
+
+					lua_pop(L, 4 + 1);
+				}
+			}
+			else
+			{
+				for (int row = 0; row < 4; row++)
+				{
+					lua_rawgeti(L, idx, row + 1);
+
+					for (int column = 0; column < 4; column++)
+					{
+						// The table has the matrix elements laid out in row-major
+						// order, but we need to store them column-major in memory.
+						lua_rawgeti(L, -(column + 1), column + 1);
+						elements[column * 4 + row] = (float) luaL_checknumber(L, -1);
+					}
+
+					lua_pop(L, 4 + 1);
+				}
+			}
+		}
+		else
+		{
+			if (columnmajor)
+			{
+				for (int column = 0; column < 4; column++)
+				{
+					for (int row = 0; row < 4; row++)
+					{
+						lua_rawgeti(L, idx, column * 4 + row + 1);
+						elements[column * 4 + row] = (float) luaL_checknumber(L, -1);
+					}
+				}
+			}
+			else
+			{
+				for (int column = 0; column < 4; column++)
+				{
+					for (int row = 0; row < 4; row++)
+					{
+						// The table has the matrix elements laid out in row-major
+						// order, but we need to store them column-major in memory.
+						lua_rawgeti(L, idx, row * 4 + column + 1);
+						elements[column * 4 + row] = (float) luaL_checknumber(L, -1);
+					}
+				}
+			}
+
+			lua_pop(L, 16);
+		}
+	}
+	else
+	{
+		if (columnmajor)
+		{
+			for (int i = 0; i < 16; i++)
+				elements[i] = (float) luaL_checknumber(L, idx + i);
+		}
+		else
+		{
+			for (int column = 0; column < 4; column++)
+			{
+				for (int row = 0; row < 4; row++)
+					elements[column * 4 + row] = (float) luaL_checknumber(L, row * 4 + column + idx);
+			}
+		}
+	}
+
+	t->setMatrix(Matrix4(elements));
+	lua_pushvalue(L, 1);
+	return 1;
+}
+
+int w_Transform_getMatrix(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	const float *elements = t->getMatrix().getElements();
+
+	// We want to push elements in row-major order, but they're stored column-
+	// major.
+	for (int row = 0; row < 4; row++)
+	{
+		for (int column = 0; column < 4; column++)
+			lua_pushnumber(L, elements[column * 4 + row]);
+	}
+
+	return 16;
+}
+
+int w_Transform_transformPoint(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	love::Vector p;
+	p.x = (float) luaL_checknumber(L, 2);
+	p.y = (float) luaL_checknumber(L, 3);
+	p = t->transformPoint(p);
+	lua_pushnumber(L, p.x);
+	lua_pushnumber(L, p.y);
+	return 2;
+}
+
+int w_Transform_inverseTransformPoint(lua_State *L)
+{
+	Transform *t = luax_checktransform(L, 1);
+	love::Vector p;
+	p.x = (float) luaL_checknumber(L, 2);
+	p.y = (float) luaL_checknumber(L, 3);
+	p = t->inverseTransformPoint(p);
+	lua_pushnumber(L, p.x);
+	lua_pushnumber(L, p.y);
+	return 2;
+}
+
+int w_Transform__mul(lua_State *L)
+{
+	Transform *t1 = luax_checktransform(L, 1);
+	Transform *t2 = luax_checktransform(L, 2);
+	Transform *t3 = new Transform(t1->getMatrix() * t2->getMatrix());
+	luax_pushtype(L, t3);
+	t3->release();
+	return 1;
+}
+
+static const luaL_Reg functions[] =
+{
+	{ "clone", w_Transform_clone },
+	{ "inverse", w_Transform_inverse },
+	{ "apply", w_Transform_apply },
+	{ "translate", w_Transform_translate },
+	{ "rotate", w_Transform_rotate },
+	{ "scale", w_Transform_scale },
+	{ "shear", w_Transform_shear },
+	{ "reset", w_Transform_reset },
+	{ "setTransformation", w_Transform_setTransformation },
+	{ "transformPoint", w_Transform_transformPoint },
+	{ "inverseTransformPoint", w_Transform_inverseTransformPoint },
+	{ "__mul", w_Transform__mul },
+	{ 0, 0 }
+};
+
+extern "C" int luaopen_transform(lua_State *L)
+{
+	return luax_register_type(L, &Transform::type, functions, nullptr);
+}
+
+} // math
+} // love

+ 36 - 0
src/modules/math/wrap_Transform.h

@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2006-2016 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 "Transform.h"
+#include "common/runtime.h"
+
+namespace love
+{
+namespace math
+{
+
+Transform *luax_checktransform(lua_State *L, int idx);
+extern "C" int luaopen_transform(lua_State *L);
+
+} // math
+} // love

+ 8 - 0
src/modules/window/Window.h

@@ -32,6 +32,12 @@
 
 namespace love
 {
+
+namespace graphics
+{
+class Graphics;
+}
+
 namespace window
 {
 
@@ -108,6 +114,8 @@ public:
 	// Implements Module.
 	virtual ModuleType getModuleType() const { return M_WINDOW; }
 
+	virtual void setGraphics(graphics::Graphics *graphics) = 0;
+
 	virtual bool setWindow(int width = 800, int height = 600, WindowSettings *settings = nullptr) = 0;
 	virtual void getWindow(int &width, int &height, WindowSettings &settings) = 0;
 

+ 30 - 16
src/modules/window/sdl/Window.cpp

@@ -79,9 +79,16 @@ Window::~Window()
 {
 	close();
 
+	graphics.set(nullptr);
+
 	SDL_QuitSubSystem(SDL_INIT_VIDEO);
 }
 
+void Window::setGraphics(graphics::Graphics *graphics)
+{
+	this->graphics.set(graphics);
+}
+
 void Window::setGLFramebufferAttributes(int msaa, bool sRGB)
 {
 	// Set GL window / framebuffer attributes.
@@ -401,6 +408,12 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla
 
 bool Window::setWindow(int width, int height, WindowSettings *settings)
 {
+	if (!graphics.get())
+		graphics.set(Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS));
+
+	if (graphics.get() && graphics->isPassActive())
+		throw love::Exception("setMode cannot be called while a render pass is active in love.graphics.");
+
 	WindowSettings f;
 
 	if (settings)
@@ -507,9 +520,8 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 
 	updateSettings(f, false);
 
-	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-	if (gfx != nullptr)
-		gfx->setMode(pixelWidth, pixelHeight);
+	if (graphics.get())
+		graphics->setMode(pixelWidth, pixelHeight);
 
 #ifdef LOVE_ANDROID
 	love::android::setImmersive(f.fullscreen);
@@ -528,9 +540,8 @@ bool Window::onSizeChanged(int width, int height)
 
 	SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight);
 
-	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-	if (gfx != nullptr)
-		gfx->setViewportSize(pixelWidth, pixelHeight);
+	if (graphics.get())
+		graphics->setViewportSize(pixelWidth, pixelHeight);
 
 	return true;
 }
@@ -596,13 +607,9 @@ void Window::updateSettings(const WindowSettings &newsettings, bool updateGraphi
 	// May be 0 if the refresh rate can't be determined.
 	settings.refreshrate = (double) dmode.refresh_rate;
 
-	if (updateGraphicsViewport)
-	{
-		// Update the viewport size now instead of waiting for event polling.
-		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-		if (gfx != nullptr)
-			gfx->setViewportSize(pixelWidth, pixelHeight);
-	}
+	// Update the viewport size now instead of waiting for event polling.
+	if (updateGraphicsViewport && graphics.get())
+		graphics->setViewportSize(pixelWidth, pixelHeight);
 }
 
 void Window::getWindow(int &width, int &height, WindowSettings &newsettings)
@@ -618,9 +625,13 @@ void Window::getWindow(int &width, int &height, WindowSettings &newsettings)
 
 void Window::close()
 {
-	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-	if (gfx != nullptr)
-		gfx->unSetMode();
+	if (graphics.get())
+	{
+		if (graphics->isPassActive())
+			throw love::Exception("close cannot be called while a render pass is active in love.graphics.");
+
+		graphics->unSetMode();
+	}
 
 	if (context)
 	{
@@ -646,6 +657,9 @@ bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype)
 	if (!window)
 		return false;
 
+	if (graphics.get() && graphics->isPassActive())
+		throw love::Exception("setFullscreen cannot be called while a render pass is active in love.graphics.");
+
 	WindowSettings newsettings = settings;
 	newsettings.fullscreen = fullscreen;
 	newsettings.fstype = fstype;

+ 4 - 0
src/modules/window/sdl/Window.h

@@ -41,6 +41,8 @@ public:
 	Window();
 	~Window();
 
+	void setGraphics(graphics::Graphics *graphics);
+
 	bool setWindow(int width = 800, int height = 600, WindowSettings *settings = nullptr);
 	void getWindow(int &width, int &height, WindowSettings &settings);
 
@@ -149,6 +151,8 @@ private:
 	bool hasSDL203orEarlier;
 	ContextAttribs contextAttribs;
 
+	StrongRef<graphics::Graphics> graphics;
+
 }; // Window
 
 } // sdl

+ 9 - 7
src/modules/window/wrap_Window.cpp

@@ -119,7 +119,7 @@ int w_setMode(lua_State *L)
 
 	if (lua_isnoneornil(L, 3))
 	{
-		luax_pushboolean(L, instance()->setWindow(w, h, nullptr));
+		luax_catchexcept(L, [&](){ luax_pushboolean(L, instance()->setWindow(w, h, nullptr)); });
 		return 1;
 	}
 
@@ -266,10 +266,12 @@ int w_setFullscreen(lua_State *L)
 		return luaL_error(L, "Invalid fullscreen type: %s", typestr);
 
 	bool success = false;
-	if (fstype == Window::FULLSCREEN_MAX_ENUM)
-		success = instance()->setFullscreen(fullscreen);
-	else
-		success = instance()->setFullscreen(fullscreen, fstype);
+	luax_catchexcept(L, [&]() {
+		if (fstype == Window::FULLSCREEN_MAX_ENUM)
+			success = instance()->setFullscreen(fullscreen);
+		else
+			success = instance()->setFullscreen(fullscreen, fstype);
+	});
 
 	luax_pushboolean(L, success);
 	return 1;
@@ -296,9 +298,9 @@ int w_isOpen(lua_State *L)
 	return 1;
 }
 
-int w_close(lua_State * /*L*/)
+int w_close(lua_State *L)
 {
-	instance()->close();
+	luax_catchexcept(L, [&]() { instance()->close(); });
 	return 0;
 }
 

+ 19 - 11
src/scripts/boot.lua

@@ -122,11 +122,10 @@ function love.arg.parse_option(m, i)
 		m.arg = {}
 		for j=i,i+m.a-1 do
 			table.insert(m.arg, arg[j])
-			i = j
 		end
 	end
 
-	return i
+	return m.a
 end
 
 function love.arg.parse_options()
@@ -134,15 +133,17 @@ function love.arg.parse_options()
 	local game
 	local argc = #arg
 
-	for i=1,argc do
+	local i = 1
+	while i <= argc do
 		-- Look for options.
-		local s, e, m = string.find(arg[i], "%-%-(.+)")
+		local m = string.match(arg[i], "^%-%-(.+)")
 
 		if m and love.arg.options[m] then
-			i = love.arg.parse_option(love.arg.options[m], i+1)
+			i = i + love.arg.parse_option(love.arg.options[m], i+1)
 		elseif not game then
 			game = i
 		end
+		i = i + 1
 	end
 
 	if not love.arg.options.game.set then
@@ -545,9 +546,14 @@ function love.run()
 		if love.update then love.update(dt) end -- will pass 0 if love.timer is disabled
 
 		if love.graphics and love.graphics.isActive() then
-			love.graphics.clear(love.graphics.getBackgroundColor())
 			love.graphics.origin()
+
+			if love.drawpasses then love.drawpasses() end
+
+			love.graphics.beginPass(love.graphics.getBackgroundColor())
 			if love.draw then love.draw() end
+			love.graphics.endPass()
+
 			love.graphics.present()
 		end
 
@@ -598,15 +604,18 @@ function love.errhand(msg)
 		end
 	end
 	if love.audio then love.audio.stop() end
+
 	love.graphics.reset()
+	if love.graphics.isPassActive() then
+		love.graphics.endPass()
+	end
+
 	local font = love.graphics.setNewFont(math.floor(love.window.toPixels(14)))
 
-	love.graphics.setBackgroundColor(89/255, 157/255, 220/255)
 	love.graphics.setColor(1, 1, 1, 1)
 
 	local trace = debug.traceback()
 
-	love.graphics.clear(love.graphics.getBackgroundColor())
 	love.graphics.origin()
 
 	local err = {}
@@ -628,9 +637,7 @@ function love.errhand(msg)
 
 	local function draw()
 		local pos = love.window.toPixels(70)
-		love.graphics.clear(love.graphics.getBackgroundColor())
 		love.graphics.printf(p, pos, pos, love.graphics.getWidth() - pos)
-		love.graphics.present()
 	end
 
 	while true do
@@ -652,7 +659,8 @@ function love.errhand(msg)
 			end
 		end
 
-		draw()
+		love.graphics.renderPass(89/255, 157/255, 220/255, draw)
+		love.graphics.present()
 
 		if love.timer then
 			love.timer.sleep(0.1)

+ 33 - 28
src/scripts/boot.lua.h

@@ -218,30 +218,33 @@ const unsigned char boot_lua[] =
 	0x64, 0x6f, 0x0a,
 	0x09, 0x09, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x28, 0x6d, 0x2e, 
 	0x61, 0x72, 0x67, 0x2c, 0x20, 0x61, 0x72, 0x67, 0x5b, 0x6a, 0x5d, 0x29, 0x0a,
-	0x09, 0x09, 0x09, 0x69, 0x20, 0x3d, 0x20, 0x6a, 0x0a,
 	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
-	0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x69, 0x0a,
+	0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6d, 0x2e, 0x61, 0x0a,
 	0x65, 0x6e, 0x64, 0x0a,
 	0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x61, 0x72, 0x67, 0x2e, 
 	0x70, 0x61, 0x72, 0x73, 0x65, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x28, 0x29, 0x0a,
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x67, 0x61, 0x6d, 0x65, 0x0a,
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x61, 0x72, 0x67, 0x63, 0x20, 0x3d, 0x20, 0x23, 0x61, 0x72, 0x67, 0x0a,
-	0x09, 0x66, 0x6f, 0x72, 0x20, 0x69, 0x3d, 0x31, 0x2c, 0x61, 0x72, 0x67, 0x63, 0x20, 0x64, 0x6f, 0x0a,
+	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x69, 0x20, 0x3d, 0x20, 0x31, 0x0a,
+	0x09, 0x77, 0x68, 0x69, 0x6c, 0x65, 0x20, 0x69, 0x20, 0x3c, 0x3d, 0x20, 0x61, 0x72, 0x67, 0x63, 0x20, 0x64, 
+	0x6f, 0x0a,
 	0x09, 0x09, 0x2d, 0x2d, 0x20, 0x4c, 0x6f, 0x6f, 0x6b, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6f, 0x70, 0x74, 0x69, 
 	0x6f, 0x6e, 0x73, 0x2e, 0x0a,
-	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x2c, 0x20, 0x65, 0x2c, 0x20, 0x6d, 0x20, 0x3d, 0x20, 
-	0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x66, 0x69, 0x6e, 0x64, 0x28, 0x61, 0x72, 0x67, 0x5b, 0x69, 0x5d, 
-	0x2c, 0x20, 0x22, 0x25, 0x2d, 0x25, 0x2d, 0x28, 0x2e, 0x2b, 0x29, 0x22, 0x29, 0x0a,
+	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6d, 0x20, 0x3d, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 
+	0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x28, 0x61, 0x72, 0x67, 0x5b, 0x69, 0x5d, 0x2c, 0x20, 0x22, 0x5e, 0x25, 
+	0x2d, 0x25, 0x2d, 0x28, 0x2e, 0x2b, 0x29, 0x22, 0x29, 0x0a,
 	0x09, 0x09, 0x69, 0x66, 0x20, 0x6d, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x61, 0x72, 
 	0x67, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5b, 0x6d, 0x5d, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
-	0x09, 0x09, 0x09, 0x69, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x61, 0x72, 0x67, 0x2e, 0x70, 0x61, 
-	0x72, 0x73, 0x65, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x61, 0x72, 
-	0x67, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5b, 0x6d, 0x5d, 0x2c, 0x20, 0x69, 0x2b, 0x31, 0x29, 0x0a,
+	0x09, 0x09, 0x09, 0x69, 0x20, 0x3d, 0x20, 0x69, 0x20, 0x2b, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x61, 0x72, 
+	0x67, 0x2e, 0x70, 0x61, 0x72, 0x73, 0x65, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6c, 0x6f, 0x76, 
+	0x65, 0x2e, 0x61, 0x72, 0x67, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5b, 0x6d, 0x5d, 0x2c, 0x20, 
+	0x69, 0x2b, 0x31, 0x29, 0x0a,
 	0x09, 0x09, 0x65, 0x6c, 0x73, 0x65, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x67, 0x61, 0x6d, 0x65, 0x20, 
 	0x74, 0x68, 0x65, 0x6e, 0x0a,
 	0x09, 0x09, 0x09, 0x67, 0x61, 0x6d, 0x65, 0x20, 0x3d, 0x20, 0x69, 0x0a,
 	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
+	0x09, 0x09, 0x69, 0x20, 0x3d, 0x20, 0x69, 0x20, 0x2b, 0x20, 0x31, 0x0a,
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x61, 0x72, 0x67, 0x2e, 0x6f, 
 	0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x73, 0x65, 0x74, 0x20, 0x74, 0x68, 
@@ -1005,14 +1008,19 @@ const unsigned char boot_lua[] =
 	0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 
 	0x20, 0x61, 0x6e, 0x64, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 
 	0x2e, 0x69, 0x73, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x28, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
-	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x63, 
-	0x6c, 0x65, 0x61, 0x72, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 
-	0x2e, 0x67, 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 
-	0x72, 0x28, 0x29, 0x29, 0x0a,
 	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6f, 
 	0x72, 0x69, 0x67, 0x69, 0x6e, 0x28, 0x29, 0x0a,
+	0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x70, 0x61, 0x73, 
+	0x73, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x64, 0x72, 0x61, 0x77, 
+	0x70, 0x61, 0x73, 0x73, 0x65, 0x73, 0x28, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a,
+	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x62, 
+	0x65, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 
+	0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 
+	0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x29, 0x29, 0x0a,
 	0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x20, 0x74, 0x68, 
 	0x65, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a,
+	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x65, 
+	0x6e, 0x64, 0x50, 0x61, 0x73, 0x73, 0x28, 0x29, 0x0a,
 	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 
 	0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x28, 0x29, 0x0a,
 	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
@@ -1098,23 +1106,21 @@ const unsigned char boot_lua[] =
 	0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x72, 0x65, 0x73, 
 	0x65, 0x74, 0x28, 0x29, 0x0a,
+	0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 
+	0x69, 0x73, 0x50, 0x61, 0x73, 0x73, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x28, 0x29, 0x20, 0x74, 0x68, 0x65, 
+	0x6e, 0x0a,
+	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x65, 0x6e, 
+	0x64, 0x50, 0x61, 0x73, 0x73, 0x28, 0x29, 0x0a,
+	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 
 	0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x4e, 0x65, 0x77, 0x46, 0x6f, 
 	0x6e, 0x74, 0x28, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x28, 0x6c, 0x6f, 0x76, 0x65, 
 	0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x74, 0x6f, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x28, 0x31, 
 	0x34, 0x29, 0x29, 0x29, 0x0a,
 	0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 
-	0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x38, 0x39, 
-	0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x31, 0x35, 0x37, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x32, 0x30, 
-	0x2f, 0x32, 0x35, 0x35, 0x29, 0x0a,
-	0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 
 	0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x29, 0x0a,
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x74, 0x72, 0x61, 0x63, 0x65, 0x20, 0x3d, 0x20, 0x64, 0x65, 0x62, 
 	0x75, 0x67, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x62, 0x61, 0x63, 0x6b, 0x28, 0x29, 0x0a,
-	0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x63, 0x6c, 0x65, 
-	0x61, 0x72, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 
-	0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 
-	0x29, 0x29, 0x0a,
 	0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6f, 0x72, 0x69, 
 	0x67, 0x69, 0x6e, 0x28, 0x29, 0x0a,
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x65, 0x72, 0x72, 0x20, 0x3d, 0x20, 0x7b, 0x7d, 0x0a,
@@ -1148,16 +1154,10 @@ const unsigned char boot_lua[] =
 	0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x70, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 
 	0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x74, 0x6f, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x28, 0x37, 
 	0x30, 0x29, 0x0a,
-	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x63, 0x6c, 
-	0x65, 0x61, 0x72, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 
-	0x67, 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 
-	0x28, 0x29, 0x29, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 
 	0x69, 0x6e, 0x74, 0x66, 0x28, 0x70, 0x2c, 0x20, 0x70, 0x6f, 0x73, 0x2c, 0x20, 0x70, 0x6f, 0x73, 0x2c, 0x20, 
 	0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x57, 
 	0x69, 0x64, 0x74, 0x68, 0x28, 0x29, 0x20, 0x2d, 0x20, 0x70, 0x6f, 0x73, 0x29, 0x0a,
-	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 
-	0x65, 0x73, 0x65, 0x6e, 0x74, 0x28, 0x29, 0x0a,
 	0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x77, 0x68, 0x69, 0x6c, 0x65, 0x20, 0x74, 0x72, 0x75, 0x65, 0x20, 0x64, 0x6f, 0x0a,
 	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x75, 0x6d, 0x70, 0x28, 
@@ -1194,7 +1194,12 @@ const unsigned char boot_lua[] =
 	0x09, 0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
 	0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
-	0x09, 0x09, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x0a,
+	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x72, 0x65, 
+	0x6e, 0x64, 0x65, 0x72, 0x50, 0x61, 0x73, 0x73, 0x28, 0x38, 0x39, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x31, 
+	0x35, 0x37, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x32, 0x30, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x64, 
+	0x72, 0x61, 0x77, 0x29, 0x0a,
+	0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 
+	0x65, 0x73, 0x65, 0x6e, 0x74, 0x28, 0x29, 0x0a,
 	0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x20, 0x74, 0x68, 
 	0x65, 0x6e, 0x0a,
 	0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x2e, 0x73, 0x6c, 0x65, 0x65, 

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